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/miscdevice.h> 21f865c352SPaul Cercueil #include <linux/watchdog.h> 22f865c352SPaul Cercueil #include <linux/init.h> 23f865c352SPaul Cercueil #include <linux/platform_device.h> 24f865c352SPaul Cercueil #include <linux/io.h> 25f865c352SPaul Cercueil #include <linux/device.h> 26f865c352SPaul Cercueil #include <linux/clk.h> 27f865c352SPaul Cercueil #include <linux/slab.h> 28*85f6df14SAxel Lin #include <linux/err.h> 29f865c352SPaul Cercueil 30f865c352SPaul Cercueil #include <asm/mach-jz4740/timer.h> 31f865c352SPaul Cercueil 32f865c352SPaul Cercueil #define JZ_REG_WDT_TIMER_DATA 0x0 33f865c352SPaul Cercueil #define JZ_REG_WDT_COUNTER_ENABLE 0x4 34f865c352SPaul Cercueil #define JZ_REG_WDT_TIMER_COUNTER 0x8 35f865c352SPaul Cercueil #define JZ_REG_WDT_TIMER_CONTROL 0xC 36f865c352SPaul Cercueil 37f865c352SPaul Cercueil #define JZ_WDT_CLOCK_PCLK 0x1 38f865c352SPaul Cercueil #define JZ_WDT_CLOCK_RTC 0x2 39f865c352SPaul Cercueil #define JZ_WDT_CLOCK_EXT 0x4 40f865c352SPaul Cercueil 41f865c352SPaul Cercueil #define JZ_WDT_CLOCK_DIV_SHIFT 3 42f865c352SPaul Cercueil 43f865c352SPaul Cercueil #define JZ_WDT_CLOCK_DIV_1 (0 << JZ_WDT_CLOCK_DIV_SHIFT) 44f865c352SPaul Cercueil #define JZ_WDT_CLOCK_DIV_4 (1 << JZ_WDT_CLOCK_DIV_SHIFT) 45f865c352SPaul Cercueil #define JZ_WDT_CLOCK_DIV_16 (2 << JZ_WDT_CLOCK_DIV_SHIFT) 46f865c352SPaul Cercueil #define JZ_WDT_CLOCK_DIV_64 (3 << JZ_WDT_CLOCK_DIV_SHIFT) 47f865c352SPaul Cercueil #define JZ_WDT_CLOCK_DIV_256 (4 << JZ_WDT_CLOCK_DIV_SHIFT) 48f865c352SPaul Cercueil #define JZ_WDT_CLOCK_DIV_1024 (5 << JZ_WDT_CLOCK_DIV_SHIFT) 49f865c352SPaul Cercueil 50f865c352SPaul Cercueil #define DEFAULT_HEARTBEAT 5 51f865c352SPaul Cercueil #define MAX_HEARTBEAT 2048 52f865c352SPaul Cercueil 53*85f6df14SAxel Lin static bool nowayout = WATCHDOG_NOWAYOUT; 54*85f6df14SAxel Lin module_param(nowayout, bool, 0); 55*85f6df14SAxel Lin MODULE_PARM_DESC(nowayout, 56*85f6df14SAxel Lin "Watchdog cannot be stopped once started (default=" 57*85f6df14SAxel Lin __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 58*85f6df14SAxel Lin 59*85f6df14SAxel Lin static unsigned int heartbeat = DEFAULT_HEARTBEAT; 60*85f6df14SAxel Lin module_param(heartbeat, uint, 0); 61*85f6df14SAxel Lin MODULE_PARM_DESC(heartbeat, 62*85f6df14SAxel Lin "Watchdog heartbeat period in seconds from 1 to " 63*85f6df14SAxel Lin __MODULE_STRING(MAX_HEARTBEAT) ", default " 64*85f6df14SAxel Lin __MODULE_STRING(DEFAULT_HEARTBEAT)); 65*85f6df14SAxel Lin 66*85f6df14SAxel Lin struct jz4740_wdt_drvdata { 67*85f6df14SAxel Lin struct watchdog_device wdt; 68f865c352SPaul Cercueil void __iomem *base; 69f865c352SPaul Cercueil struct clk *rtc_clk; 70*85f6df14SAxel Lin }; 71f865c352SPaul Cercueil 72*85f6df14SAxel Lin static int jz4740_wdt_ping(struct watchdog_device *wdt_dev) 73f865c352SPaul Cercueil { 74*85f6df14SAxel Lin struct jz4740_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev); 75*85f6df14SAxel Lin 76*85f6df14SAxel Lin writew(0x0, drvdata->base + JZ_REG_WDT_TIMER_COUNTER); 77*85f6df14SAxel Lin return 0; 78f865c352SPaul Cercueil } 79f865c352SPaul Cercueil 80*85f6df14SAxel Lin static int jz4740_wdt_set_timeout(struct watchdog_device *wdt_dev, 81*85f6df14SAxel Lin unsigned int new_timeout) 82f865c352SPaul Cercueil { 83*85f6df14SAxel Lin struct jz4740_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev); 84f865c352SPaul Cercueil unsigned int rtc_clk_rate; 85f865c352SPaul Cercueil unsigned int timeout_value; 86f865c352SPaul Cercueil unsigned short clock_div = JZ_WDT_CLOCK_DIV_1; 87f865c352SPaul Cercueil 88*85f6df14SAxel Lin rtc_clk_rate = clk_get_rate(drvdata->rtc_clk); 89f865c352SPaul Cercueil 90*85f6df14SAxel Lin timeout_value = rtc_clk_rate * new_timeout; 91f865c352SPaul Cercueil while (timeout_value > 0xffff) { 92f865c352SPaul Cercueil if (clock_div == JZ_WDT_CLOCK_DIV_1024) { 93f865c352SPaul Cercueil /* Requested timeout too high; 94f865c352SPaul Cercueil * use highest possible value. */ 95f865c352SPaul Cercueil timeout_value = 0xffff; 96f865c352SPaul Cercueil break; 97f865c352SPaul Cercueil } 98f865c352SPaul Cercueil timeout_value >>= 2; 99f865c352SPaul Cercueil clock_div += (1 << JZ_WDT_CLOCK_DIV_SHIFT); 100f865c352SPaul Cercueil } 101f865c352SPaul Cercueil 102*85f6df14SAxel Lin writeb(0x0, drvdata->base + JZ_REG_WDT_COUNTER_ENABLE); 103*85f6df14SAxel Lin writew(clock_div, drvdata->base + JZ_REG_WDT_TIMER_CONTROL); 104f865c352SPaul Cercueil 105*85f6df14SAxel Lin writew((u16)timeout_value, drvdata->base + JZ_REG_WDT_TIMER_DATA); 106*85f6df14SAxel Lin writew(0x0, drvdata->base + JZ_REG_WDT_TIMER_COUNTER); 107f865c352SPaul Cercueil writew(clock_div | JZ_WDT_CLOCK_RTC, 108*85f6df14SAxel Lin drvdata->base + JZ_REG_WDT_TIMER_CONTROL); 109f865c352SPaul Cercueil 110*85f6df14SAxel Lin writeb(0x1, drvdata->base + JZ_REG_WDT_COUNTER_ENABLE); 111*85f6df14SAxel Lin 112*85f6df14SAxel Lin return 0; 113f865c352SPaul Cercueil } 114f865c352SPaul Cercueil 115*85f6df14SAxel Lin static int jz4740_wdt_start(struct watchdog_device *wdt_dev) 116f865c352SPaul Cercueil { 117f865c352SPaul Cercueil jz4740_timer_enable_watchdog(); 118*85f6df14SAxel Lin jz4740_wdt_set_timeout(wdt_dev, wdt_dev->timeout); 119*85f6df14SAxel Lin 120*85f6df14SAxel Lin return 0; 121f865c352SPaul Cercueil } 122f865c352SPaul Cercueil 123*85f6df14SAxel Lin static int jz4740_wdt_stop(struct watchdog_device *wdt_dev) 124f865c352SPaul Cercueil { 125*85f6df14SAxel Lin struct jz4740_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev); 126*85f6df14SAxel Lin 127f865c352SPaul Cercueil jz4740_timer_disable_watchdog(); 128*85f6df14SAxel Lin writeb(0x0, drvdata->base + JZ_REG_WDT_COUNTER_ENABLE); 129*85f6df14SAxel Lin 130*85f6df14SAxel Lin return 0; 131f865c352SPaul Cercueil } 132f865c352SPaul Cercueil 133*85f6df14SAxel Lin static const struct watchdog_info jz4740_wdt_info = { 134*85f6df14SAxel Lin .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, 135f865c352SPaul Cercueil .identity = "jz4740 Watchdog", 136f865c352SPaul Cercueil }; 137f865c352SPaul Cercueil 138*85f6df14SAxel Lin static const struct watchdog_ops jz4740_wdt_ops = { 139f865c352SPaul Cercueil .owner = THIS_MODULE, 140*85f6df14SAxel Lin .start = jz4740_wdt_start, 141*85f6df14SAxel Lin .stop = jz4740_wdt_stop, 142*85f6df14SAxel Lin .ping = jz4740_wdt_ping, 143*85f6df14SAxel Lin .set_timeout = jz4740_wdt_set_timeout, 144f865c352SPaul Cercueil }; 145f865c352SPaul Cercueil 146f865c352SPaul Cercueil static int __devinit jz4740_wdt_probe(struct platform_device *pdev) 147f865c352SPaul Cercueil { 148*85f6df14SAxel Lin struct jz4740_wdt_drvdata *drvdata; 149*85f6df14SAxel Lin struct watchdog_device *jz4740_wdt; 150f865c352SPaul Cercueil struct resource *res; 151*85f6df14SAxel Lin int ret; 152*85f6df14SAxel Lin 153*85f6df14SAxel Lin drvdata = devm_kzalloc(&pdev->dev, sizeof(struct jz4740_wdt_drvdata), 154*85f6df14SAxel Lin GFP_KERNEL); 155*85f6df14SAxel Lin if (!drvdata) { 156*85f6df14SAxel Lin dev_err(&pdev->dev, "Unable to alloacate watchdog device\n"); 157*85f6df14SAxel Lin return -ENOMEM; 158*85f6df14SAxel Lin } 159*85f6df14SAxel Lin 160*85f6df14SAxel Lin if (heartbeat < 1 || heartbeat > MAX_HEARTBEAT) 161*85f6df14SAxel Lin heartbeat = DEFAULT_HEARTBEAT; 162*85f6df14SAxel Lin 163*85f6df14SAxel Lin jz4740_wdt = &drvdata->wdt; 164*85f6df14SAxel Lin jz4740_wdt->info = &jz4740_wdt_info; 165*85f6df14SAxel Lin jz4740_wdt->ops = &jz4740_wdt_ops; 166*85f6df14SAxel Lin jz4740_wdt->timeout = heartbeat; 167*85f6df14SAxel Lin jz4740_wdt->min_timeout = 1; 168*85f6df14SAxel Lin jz4740_wdt->max_timeout = MAX_HEARTBEAT; 169*85f6df14SAxel Lin watchdog_set_nowayout(jz4740_wdt, nowayout); 170*85f6df14SAxel Lin watchdog_set_drvdata(jz4740_wdt, drvdata); 171f865c352SPaul Cercueil 172f865c352SPaul Cercueil res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 173*85f6df14SAxel Lin drvdata->base = devm_request_and_ioremap(&pdev->dev, res); 174*85f6df14SAxel Lin if (drvdata->base == NULL) { 175f865c352SPaul Cercueil ret = -EBUSY; 176*85f6df14SAxel Lin goto err_out; 177f865c352SPaul Cercueil } 178f865c352SPaul Cercueil 179*85f6df14SAxel Lin drvdata->rtc_clk = clk_get(NULL, "rtc"); 180*85f6df14SAxel Lin if (IS_ERR(drvdata->rtc_clk)) { 181*85f6df14SAxel Lin dev_err(&pdev->dev, "cannot find RTC clock\n"); 182*85f6df14SAxel Lin ret = PTR_ERR(drvdata->rtc_clk); 183*85f6df14SAxel Lin goto err_out; 184f865c352SPaul Cercueil } 185f865c352SPaul Cercueil 186*85f6df14SAxel Lin ret = watchdog_register_device(&drvdata->wdt); 187*85f6df14SAxel Lin if (ret < 0) 188f865c352SPaul Cercueil goto err_disable_clk; 189f865c352SPaul Cercueil 190*85f6df14SAxel Lin platform_set_drvdata(pdev, drvdata); 191f865c352SPaul Cercueil return 0; 192f865c352SPaul Cercueil 193f865c352SPaul Cercueil err_disable_clk: 194*85f6df14SAxel Lin clk_put(drvdata->rtc_clk); 195*85f6df14SAxel Lin err_out: 196f865c352SPaul Cercueil return ret; 197f865c352SPaul Cercueil } 198f865c352SPaul Cercueil 199f865c352SPaul Cercueil static int __devexit jz4740_wdt_remove(struct platform_device *pdev) 200f865c352SPaul Cercueil { 201*85f6df14SAxel Lin struct jz4740_wdt_drvdata *drvdata = platform_get_drvdata(pdev); 202f865c352SPaul Cercueil 203*85f6df14SAxel Lin jz4740_wdt_stop(&drvdata->wdt); 204*85f6df14SAxel Lin watchdog_unregister_device(&drvdata->wdt); 205*85f6df14SAxel Lin clk_put(drvdata->rtc_clk); 206f865c352SPaul Cercueil 207f865c352SPaul Cercueil return 0; 208f865c352SPaul Cercueil } 209f865c352SPaul Cercueil 210f865c352SPaul Cercueil static struct platform_driver jz4740_wdt_driver = { 211f865c352SPaul Cercueil .probe = jz4740_wdt_probe, 212f865c352SPaul Cercueil .remove = __devexit_p(jz4740_wdt_remove), 213f865c352SPaul Cercueil .driver = { 214f865c352SPaul Cercueil .name = "jz4740-wdt", 215f865c352SPaul Cercueil .owner = THIS_MODULE, 216f865c352SPaul Cercueil }, 217f865c352SPaul Cercueil }; 218f865c352SPaul Cercueil 219b8ec6118SAxel Lin module_platform_driver(jz4740_wdt_driver); 220f865c352SPaul Cercueil 221f865c352SPaul Cercueil MODULE_AUTHOR("Paul Cercueil <paul@crapouillou.net>"); 222f865c352SPaul Cercueil MODULE_DESCRIPTION("jz4740 Watchdog Driver"); 223f865c352SPaul Cercueil MODULE_LICENSE("GPL"); 224f865c352SPaul Cercueil MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); 225f865c352SPaul Cercueil MODULE_ALIAS("platform:jz4740-wdt"); 226