1f865c352SPaul Cercueil /* 2f865c352SPaul Cercueil * Copyright (C) 2010, Paul Cercueil <paul@crapouillou.net> 3f865c352SPaul Cercueil * JZ4740 Watchdog driver 4f865c352SPaul Cercueil * 5f865c352SPaul Cercueil * This program is free software; you can redistribute it and/or modify it 6f865c352SPaul Cercueil * under the terms of the GNU General Public License as published by the 7f865c352SPaul Cercueil * Free Software Foundation; either version 2 of the License, or (at your 8f865c352SPaul Cercueil * option) any later version. 9f865c352SPaul Cercueil * 10f865c352SPaul Cercueil * You should have received a copy of the GNU General Public License along 11f865c352SPaul Cercueil * with this program; if not, write to the Free Software Foundation, Inc., 12f865c352SPaul Cercueil * 675 Mass Ave, Cambridge, MA 02139, USA. 13f865c352SPaul Cercueil * 14f865c352SPaul Cercueil */ 15f865c352SPaul Cercueil 16f865c352SPaul Cercueil #include <linux/module.h> 17f865c352SPaul Cercueil #include <linux/moduleparam.h> 18f865c352SPaul Cercueil #include <linux/types.h> 19f865c352SPaul Cercueil #include <linux/kernel.h> 20f865c352SPaul Cercueil #include <linux/watchdog.h> 21f865c352SPaul Cercueil #include <linux/platform_device.h> 22f865c352SPaul Cercueil #include <linux/io.h> 23f865c352SPaul Cercueil #include <linux/device.h> 24f865c352SPaul Cercueil #include <linux/clk.h> 25f865c352SPaul Cercueil #include <linux/slab.h> 2685f6df14SAxel Lin #include <linux/err.h> 276b96c722SZubair Lutfullah Kakakhel #include <linux/of.h> 28f865c352SPaul Cercueil 29f865c352SPaul Cercueil #include <asm/mach-jz4740/timer.h> 30f865c352SPaul Cercueil 31f865c352SPaul Cercueil #define JZ_REG_WDT_TIMER_DATA 0x0 32f865c352SPaul Cercueil #define JZ_REG_WDT_COUNTER_ENABLE 0x4 33f865c352SPaul Cercueil #define JZ_REG_WDT_TIMER_COUNTER 0x8 34f865c352SPaul Cercueil #define JZ_REG_WDT_TIMER_CONTROL 0xC 35f865c352SPaul Cercueil 36f865c352SPaul Cercueil #define JZ_WDT_CLOCK_PCLK 0x1 37f865c352SPaul Cercueil #define JZ_WDT_CLOCK_RTC 0x2 38f865c352SPaul Cercueil #define JZ_WDT_CLOCK_EXT 0x4 39f865c352SPaul Cercueil 40f865c352SPaul Cercueil #define JZ_WDT_CLOCK_DIV_SHIFT 3 41f865c352SPaul Cercueil 42f865c352SPaul Cercueil #define JZ_WDT_CLOCK_DIV_1 (0 << JZ_WDT_CLOCK_DIV_SHIFT) 43f865c352SPaul Cercueil #define JZ_WDT_CLOCK_DIV_4 (1 << JZ_WDT_CLOCK_DIV_SHIFT) 44f865c352SPaul Cercueil #define JZ_WDT_CLOCK_DIV_16 (2 << JZ_WDT_CLOCK_DIV_SHIFT) 45f865c352SPaul Cercueil #define JZ_WDT_CLOCK_DIV_64 (3 << JZ_WDT_CLOCK_DIV_SHIFT) 46f865c352SPaul Cercueil #define JZ_WDT_CLOCK_DIV_256 (4 << JZ_WDT_CLOCK_DIV_SHIFT) 47f865c352SPaul Cercueil #define JZ_WDT_CLOCK_DIV_1024 (5 << JZ_WDT_CLOCK_DIV_SHIFT) 48f865c352SPaul Cercueil 49f865c352SPaul Cercueil #define DEFAULT_HEARTBEAT 5 50f865c352SPaul Cercueil #define MAX_HEARTBEAT 2048 51f865c352SPaul Cercueil 5285f6df14SAxel Lin static bool nowayout = WATCHDOG_NOWAYOUT; 5385f6df14SAxel Lin module_param(nowayout, bool, 0); 5485f6df14SAxel Lin MODULE_PARM_DESC(nowayout, 5585f6df14SAxel Lin "Watchdog cannot be stopped once started (default=" 5685f6df14SAxel Lin __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 5785f6df14SAxel Lin 5885f6df14SAxel Lin static unsigned int heartbeat = DEFAULT_HEARTBEAT; 5985f6df14SAxel Lin module_param(heartbeat, uint, 0); 6085f6df14SAxel Lin MODULE_PARM_DESC(heartbeat, 6185f6df14SAxel Lin "Watchdog heartbeat period in seconds from 1 to " 6285f6df14SAxel Lin __MODULE_STRING(MAX_HEARTBEAT) ", default " 6385f6df14SAxel Lin __MODULE_STRING(DEFAULT_HEARTBEAT)); 6485f6df14SAxel Lin 6585f6df14SAxel Lin struct jz4740_wdt_drvdata { 6685f6df14SAxel Lin struct watchdog_device wdt; 67f865c352SPaul Cercueil void __iomem *base; 68f865c352SPaul Cercueil struct clk *rtc_clk; 6985f6df14SAxel Lin }; 70f865c352SPaul Cercueil 7185f6df14SAxel Lin static int jz4740_wdt_ping(struct watchdog_device *wdt_dev) 72f865c352SPaul Cercueil { 7385f6df14SAxel Lin struct jz4740_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev); 7485f6df14SAxel Lin 7585f6df14SAxel Lin writew(0x0, drvdata->base + JZ_REG_WDT_TIMER_COUNTER); 7685f6df14SAxel Lin return 0; 77f865c352SPaul Cercueil } 78f865c352SPaul Cercueil 7985f6df14SAxel Lin static int jz4740_wdt_set_timeout(struct watchdog_device *wdt_dev, 8085f6df14SAxel Lin unsigned int new_timeout) 81f865c352SPaul Cercueil { 8285f6df14SAxel Lin struct jz4740_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev); 83f865c352SPaul Cercueil unsigned int rtc_clk_rate; 84f865c352SPaul Cercueil unsigned int timeout_value; 85f865c352SPaul Cercueil unsigned short clock_div = JZ_WDT_CLOCK_DIV_1; 86f865c352SPaul Cercueil 8785f6df14SAxel Lin rtc_clk_rate = clk_get_rate(drvdata->rtc_clk); 88f865c352SPaul Cercueil 8985f6df14SAxel Lin timeout_value = rtc_clk_rate * new_timeout; 90f865c352SPaul Cercueil while (timeout_value > 0xffff) { 91f865c352SPaul Cercueil if (clock_div == JZ_WDT_CLOCK_DIV_1024) { 92f865c352SPaul Cercueil /* Requested timeout too high; 93f865c352SPaul Cercueil * use highest possible value. */ 94f865c352SPaul Cercueil timeout_value = 0xffff; 95f865c352SPaul Cercueil break; 96f865c352SPaul Cercueil } 97f865c352SPaul Cercueil timeout_value >>= 2; 98f865c352SPaul Cercueil clock_div += (1 << JZ_WDT_CLOCK_DIV_SHIFT); 99f865c352SPaul Cercueil } 100f865c352SPaul Cercueil 10185f6df14SAxel Lin writeb(0x0, drvdata->base + JZ_REG_WDT_COUNTER_ENABLE); 10285f6df14SAxel Lin writew(clock_div, drvdata->base + JZ_REG_WDT_TIMER_CONTROL); 103f865c352SPaul Cercueil 10485f6df14SAxel Lin writew((u16)timeout_value, drvdata->base + JZ_REG_WDT_TIMER_DATA); 10585f6df14SAxel Lin writew(0x0, drvdata->base + JZ_REG_WDT_TIMER_COUNTER); 106f865c352SPaul Cercueil writew(clock_div | JZ_WDT_CLOCK_RTC, 10785f6df14SAxel Lin drvdata->base + JZ_REG_WDT_TIMER_CONTROL); 108f865c352SPaul Cercueil 10985f6df14SAxel Lin writeb(0x1, drvdata->base + JZ_REG_WDT_COUNTER_ENABLE); 11085f6df14SAxel Lin 1110197c1c4SWim Van Sebroeck wdt_dev->timeout = new_timeout; 11285f6df14SAxel Lin return 0; 113f865c352SPaul Cercueil } 114f865c352SPaul Cercueil 11585f6df14SAxel Lin static int jz4740_wdt_start(struct watchdog_device *wdt_dev) 116f865c352SPaul Cercueil { 117f865c352SPaul Cercueil jz4740_timer_enable_watchdog(); 11885f6df14SAxel Lin jz4740_wdt_set_timeout(wdt_dev, wdt_dev->timeout); 11985f6df14SAxel Lin 12085f6df14SAxel Lin return 0; 121f865c352SPaul Cercueil } 122f865c352SPaul Cercueil 12385f6df14SAxel Lin static int jz4740_wdt_stop(struct watchdog_device *wdt_dev) 124f865c352SPaul Cercueil { 12585f6df14SAxel Lin struct jz4740_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev); 12685f6df14SAxel Lin 12785f6df14SAxel Lin writeb(0x0, drvdata->base + JZ_REG_WDT_COUNTER_ENABLE); 128212c1054SPaul Cercueil jz4740_timer_disable_watchdog(); 12985f6df14SAxel Lin 13085f6df14SAxel Lin return 0; 131f865c352SPaul Cercueil } 132f865c352SPaul Cercueil 133b4918057SPaul Cercueil static int jz4740_wdt_restart(struct watchdog_device *wdt_dev, 134b4918057SPaul Cercueil unsigned long action, void *data) 135b4918057SPaul Cercueil { 136b4918057SPaul Cercueil wdt_dev->timeout = 0; 137b4918057SPaul Cercueil jz4740_wdt_start(wdt_dev); 138b4918057SPaul Cercueil return 0; 139b4918057SPaul Cercueil } 140b4918057SPaul Cercueil 14185f6df14SAxel Lin static const struct watchdog_info jz4740_wdt_info = { 14285f6df14SAxel Lin .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, 143f865c352SPaul Cercueil .identity = "jz4740 Watchdog", 144f865c352SPaul Cercueil }; 145f865c352SPaul Cercueil 14685f6df14SAxel Lin static const struct watchdog_ops jz4740_wdt_ops = { 147f865c352SPaul Cercueil .owner = THIS_MODULE, 14885f6df14SAxel Lin .start = jz4740_wdt_start, 14985f6df14SAxel Lin .stop = jz4740_wdt_stop, 15085f6df14SAxel Lin .ping = jz4740_wdt_ping, 15185f6df14SAxel Lin .set_timeout = jz4740_wdt_set_timeout, 152b4918057SPaul Cercueil .restart = jz4740_wdt_restart, 153f865c352SPaul Cercueil }; 154f865c352SPaul Cercueil 1556b96c722SZubair Lutfullah Kakakhel #ifdef CONFIG_OF 1566b96c722SZubair Lutfullah Kakakhel static const struct of_device_id jz4740_wdt_of_matches[] = { 1576b96c722SZubair Lutfullah Kakakhel { .compatible = "ingenic,jz4740-watchdog", }, 15871246c35SMathieu Malaterre { .compatible = "ingenic,jz4780-watchdog", }, 1596b96c722SZubair Lutfullah Kakakhel { /* sentinel */ } 1606b96c722SZubair Lutfullah Kakakhel }; 16135ffa961SStephen Boyd MODULE_DEVICE_TABLE(of, jz4740_wdt_of_matches); 1626b96c722SZubair Lutfullah Kakakhel #endif 1636b96c722SZubair Lutfullah Kakakhel 1642d991a16SBill Pemberton static int jz4740_wdt_probe(struct platform_device *pdev) 165f865c352SPaul Cercueil { 166*02189bb9SGuenter Roeck struct device *dev = &pdev->dev; 16785f6df14SAxel Lin struct jz4740_wdt_drvdata *drvdata; 16885f6df14SAxel Lin struct watchdog_device *jz4740_wdt; 16985f6df14SAxel Lin int ret; 17085f6df14SAxel Lin 171*02189bb9SGuenter Roeck drvdata = devm_kzalloc(dev, sizeof(struct jz4740_wdt_drvdata), 17285f6df14SAxel Lin GFP_KERNEL); 173e26e74b1SColin Ian King if (!drvdata) 17485f6df14SAxel Lin return -ENOMEM; 17585f6df14SAxel Lin 17685f6df14SAxel Lin if (heartbeat < 1 || heartbeat > MAX_HEARTBEAT) 17785f6df14SAxel Lin heartbeat = DEFAULT_HEARTBEAT; 17885f6df14SAxel Lin 17985f6df14SAxel Lin jz4740_wdt = &drvdata->wdt; 18085f6df14SAxel Lin jz4740_wdt->info = &jz4740_wdt_info; 18185f6df14SAxel Lin jz4740_wdt->ops = &jz4740_wdt_ops; 18285f6df14SAxel Lin jz4740_wdt->timeout = heartbeat; 18385f6df14SAxel Lin jz4740_wdt->min_timeout = 1; 18485f6df14SAxel Lin jz4740_wdt->max_timeout = MAX_HEARTBEAT; 185*02189bb9SGuenter Roeck jz4740_wdt->parent = dev; 18685f6df14SAxel Lin watchdog_set_nowayout(jz4740_wdt, nowayout); 18785f6df14SAxel Lin watchdog_set_drvdata(jz4740_wdt, drvdata); 188f865c352SPaul Cercueil 1890f0a6a28SGuenter Roeck drvdata->base = devm_platform_ioremap_resource(pdev, 0); 1906bdbc1f7SPaul Cercueil if (IS_ERR(drvdata->base)) 1916bdbc1f7SPaul Cercueil return PTR_ERR(drvdata->base); 192f865c352SPaul Cercueil 193*02189bb9SGuenter Roeck drvdata->rtc_clk = devm_clk_get(dev, "rtc"); 19485f6df14SAxel Lin if (IS_ERR(drvdata->rtc_clk)) { 195*02189bb9SGuenter Roeck dev_err(dev, "cannot find RTC clock\n"); 1966bdbc1f7SPaul Cercueil return PTR_ERR(drvdata->rtc_clk); 197f865c352SPaul Cercueil } 198f865c352SPaul Cercueil 199*02189bb9SGuenter Roeck ret = devm_watchdog_register_device(dev, &drvdata->wdt); 20085f6df14SAxel Lin if (ret < 0) 2016bdbc1f7SPaul Cercueil return ret; 202f865c352SPaul Cercueil 2036bdbc1f7SPaul Cercueil return 0; 204f865c352SPaul Cercueil } 205f865c352SPaul Cercueil 206f865c352SPaul Cercueil static struct platform_driver jz4740_wdt_driver = { 207f865c352SPaul Cercueil .probe = jz4740_wdt_probe, 208f865c352SPaul Cercueil .driver = { 209f865c352SPaul Cercueil .name = "jz4740-wdt", 2106b96c722SZubair Lutfullah Kakakhel .of_match_table = of_match_ptr(jz4740_wdt_of_matches), 211f865c352SPaul Cercueil }, 212f865c352SPaul Cercueil }; 213f865c352SPaul Cercueil 214b8ec6118SAxel Lin module_platform_driver(jz4740_wdt_driver); 215f865c352SPaul Cercueil 216f865c352SPaul Cercueil MODULE_AUTHOR("Paul Cercueil <paul@crapouillou.net>"); 217f865c352SPaul Cercueil MODULE_DESCRIPTION("jz4740 Watchdog Driver"); 218f865c352SPaul Cercueil MODULE_LICENSE("GPL"); 219f865c352SPaul Cercueil MODULE_ALIAS("platform:jz4740-wdt"); 220