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> 2885f6df14SAxel 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 5385f6df14SAxel Lin static bool nowayout = WATCHDOG_NOWAYOUT; 5485f6df14SAxel Lin module_param(nowayout, bool, 0); 5585f6df14SAxel Lin MODULE_PARM_DESC(nowayout, 5685f6df14SAxel Lin "Watchdog cannot be stopped once started (default=" 5785f6df14SAxel Lin __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 5885f6df14SAxel Lin 5985f6df14SAxel Lin static unsigned int heartbeat = DEFAULT_HEARTBEAT; 6085f6df14SAxel Lin module_param(heartbeat, uint, 0); 6185f6df14SAxel Lin MODULE_PARM_DESC(heartbeat, 6285f6df14SAxel Lin "Watchdog heartbeat period in seconds from 1 to " 6385f6df14SAxel Lin __MODULE_STRING(MAX_HEARTBEAT) ", default " 6485f6df14SAxel Lin __MODULE_STRING(DEFAULT_HEARTBEAT)); 6585f6df14SAxel Lin 6685f6df14SAxel Lin struct jz4740_wdt_drvdata { 6785f6df14SAxel Lin struct watchdog_device wdt; 68f865c352SPaul Cercueil void __iomem *base; 69f865c352SPaul Cercueil struct clk *rtc_clk; 7085f6df14SAxel Lin }; 71f865c352SPaul Cercueil 7285f6df14SAxel Lin static int jz4740_wdt_ping(struct watchdog_device *wdt_dev) 73f865c352SPaul Cercueil { 7485f6df14SAxel Lin struct jz4740_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev); 7585f6df14SAxel Lin 7685f6df14SAxel Lin writew(0x0, drvdata->base + JZ_REG_WDT_TIMER_COUNTER); 7785f6df14SAxel Lin return 0; 78f865c352SPaul Cercueil } 79f865c352SPaul Cercueil 8085f6df14SAxel Lin static int jz4740_wdt_set_timeout(struct watchdog_device *wdt_dev, 8185f6df14SAxel Lin unsigned int new_timeout) 82f865c352SPaul Cercueil { 8385f6df14SAxel 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 8885f6df14SAxel Lin rtc_clk_rate = clk_get_rate(drvdata->rtc_clk); 89f865c352SPaul Cercueil 9085f6df14SAxel 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 10285f6df14SAxel Lin writeb(0x0, drvdata->base + JZ_REG_WDT_COUNTER_ENABLE); 10385f6df14SAxel Lin writew(clock_div, drvdata->base + JZ_REG_WDT_TIMER_CONTROL); 104f865c352SPaul Cercueil 10585f6df14SAxel Lin writew((u16)timeout_value, drvdata->base + JZ_REG_WDT_TIMER_DATA); 10685f6df14SAxel Lin writew(0x0, drvdata->base + JZ_REG_WDT_TIMER_COUNTER); 107f865c352SPaul Cercueil writew(clock_div | JZ_WDT_CLOCK_RTC, 10885f6df14SAxel Lin drvdata->base + JZ_REG_WDT_TIMER_CONTROL); 109f865c352SPaul Cercueil 11085f6df14SAxel Lin writeb(0x1, drvdata->base + JZ_REG_WDT_COUNTER_ENABLE); 11185f6df14SAxel Lin 1120197c1c4SWim Van Sebroeck wdt_dev->timeout = new_timeout; 11385f6df14SAxel Lin return 0; 114f865c352SPaul Cercueil } 115f865c352SPaul Cercueil 11685f6df14SAxel Lin static int jz4740_wdt_start(struct watchdog_device *wdt_dev) 117f865c352SPaul Cercueil { 118f865c352SPaul Cercueil jz4740_timer_enable_watchdog(); 11985f6df14SAxel Lin jz4740_wdt_set_timeout(wdt_dev, wdt_dev->timeout); 12085f6df14SAxel Lin 12185f6df14SAxel Lin return 0; 122f865c352SPaul Cercueil } 123f865c352SPaul Cercueil 12485f6df14SAxel Lin static int jz4740_wdt_stop(struct watchdog_device *wdt_dev) 125f865c352SPaul Cercueil { 12685f6df14SAxel Lin struct jz4740_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev); 12785f6df14SAxel Lin 128f865c352SPaul Cercueil jz4740_timer_disable_watchdog(); 12985f6df14SAxel Lin writeb(0x0, drvdata->base + JZ_REG_WDT_COUNTER_ENABLE); 13085f6df14SAxel Lin 13185f6df14SAxel Lin return 0; 132f865c352SPaul Cercueil } 133f865c352SPaul Cercueil 13485f6df14SAxel Lin static const struct watchdog_info jz4740_wdt_info = { 13585f6df14SAxel Lin .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, 136f865c352SPaul Cercueil .identity = "jz4740 Watchdog", 137f865c352SPaul Cercueil }; 138f865c352SPaul Cercueil 13985f6df14SAxel Lin static const struct watchdog_ops jz4740_wdt_ops = { 140f865c352SPaul Cercueil .owner = THIS_MODULE, 14185f6df14SAxel Lin .start = jz4740_wdt_start, 14285f6df14SAxel Lin .stop = jz4740_wdt_stop, 14385f6df14SAxel Lin .ping = jz4740_wdt_ping, 14485f6df14SAxel Lin .set_timeout = jz4740_wdt_set_timeout, 145f865c352SPaul Cercueil }; 146f865c352SPaul Cercueil 147*2d991a16SBill Pemberton static int jz4740_wdt_probe(struct platform_device *pdev) 148f865c352SPaul Cercueil { 14985f6df14SAxel Lin struct jz4740_wdt_drvdata *drvdata; 15085f6df14SAxel Lin struct watchdog_device *jz4740_wdt; 151f865c352SPaul Cercueil struct resource *res; 15285f6df14SAxel Lin int ret; 15385f6df14SAxel Lin 15485f6df14SAxel Lin drvdata = devm_kzalloc(&pdev->dev, sizeof(struct jz4740_wdt_drvdata), 15585f6df14SAxel Lin GFP_KERNEL); 15685f6df14SAxel Lin if (!drvdata) { 15785f6df14SAxel Lin dev_err(&pdev->dev, "Unable to alloacate watchdog device\n"); 15885f6df14SAxel Lin return -ENOMEM; 15985f6df14SAxel Lin } 16085f6df14SAxel Lin 16185f6df14SAxel Lin if (heartbeat < 1 || heartbeat > MAX_HEARTBEAT) 16285f6df14SAxel Lin heartbeat = DEFAULT_HEARTBEAT; 16385f6df14SAxel Lin 16485f6df14SAxel Lin jz4740_wdt = &drvdata->wdt; 16585f6df14SAxel Lin jz4740_wdt->info = &jz4740_wdt_info; 16685f6df14SAxel Lin jz4740_wdt->ops = &jz4740_wdt_ops; 16785f6df14SAxel Lin jz4740_wdt->timeout = heartbeat; 16885f6df14SAxel Lin jz4740_wdt->min_timeout = 1; 16985f6df14SAxel Lin jz4740_wdt->max_timeout = MAX_HEARTBEAT; 17085f6df14SAxel Lin watchdog_set_nowayout(jz4740_wdt, nowayout); 17185f6df14SAxel Lin watchdog_set_drvdata(jz4740_wdt, drvdata); 172f865c352SPaul Cercueil 173f865c352SPaul Cercueil res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 17485f6df14SAxel Lin drvdata->base = devm_request_and_ioremap(&pdev->dev, res); 17585f6df14SAxel Lin if (drvdata->base == NULL) { 176f865c352SPaul Cercueil ret = -EBUSY; 17785f6df14SAxel Lin goto err_out; 178f865c352SPaul Cercueil } 179f865c352SPaul Cercueil 18085f6df14SAxel Lin drvdata->rtc_clk = clk_get(NULL, "rtc"); 18185f6df14SAxel Lin if (IS_ERR(drvdata->rtc_clk)) { 18285f6df14SAxel Lin dev_err(&pdev->dev, "cannot find RTC clock\n"); 18385f6df14SAxel Lin ret = PTR_ERR(drvdata->rtc_clk); 18485f6df14SAxel Lin goto err_out; 185f865c352SPaul Cercueil } 186f865c352SPaul Cercueil 18785f6df14SAxel Lin ret = watchdog_register_device(&drvdata->wdt); 18885f6df14SAxel Lin if (ret < 0) 189f865c352SPaul Cercueil goto err_disable_clk; 190f865c352SPaul Cercueil 19185f6df14SAxel Lin platform_set_drvdata(pdev, drvdata); 192f865c352SPaul Cercueil return 0; 193f865c352SPaul Cercueil 194f865c352SPaul Cercueil err_disable_clk: 19585f6df14SAxel Lin clk_put(drvdata->rtc_clk); 19685f6df14SAxel Lin err_out: 197f865c352SPaul Cercueil return ret; 198f865c352SPaul Cercueil } 199f865c352SPaul Cercueil 200f865c352SPaul Cercueil static int __devexit jz4740_wdt_remove(struct platform_device *pdev) 201f865c352SPaul Cercueil { 20285f6df14SAxel Lin struct jz4740_wdt_drvdata *drvdata = platform_get_drvdata(pdev); 203f865c352SPaul Cercueil 20485f6df14SAxel Lin jz4740_wdt_stop(&drvdata->wdt); 20585f6df14SAxel Lin watchdog_unregister_device(&drvdata->wdt); 20685f6df14SAxel Lin clk_put(drvdata->rtc_clk); 207f865c352SPaul Cercueil 208f865c352SPaul Cercueil return 0; 209f865c352SPaul Cercueil } 210f865c352SPaul Cercueil 211f865c352SPaul Cercueil static struct platform_driver jz4740_wdt_driver = { 212f865c352SPaul Cercueil .probe = jz4740_wdt_probe, 21382268714SBill Pemberton .remove = jz4740_wdt_remove, 214f865c352SPaul Cercueil .driver = { 215f865c352SPaul Cercueil .name = "jz4740-wdt", 216f865c352SPaul Cercueil .owner = THIS_MODULE, 217f865c352SPaul Cercueil }, 218f865c352SPaul Cercueil }; 219f865c352SPaul Cercueil 220b8ec6118SAxel Lin module_platform_driver(jz4740_wdt_driver); 221f865c352SPaul Cercueil 222f865c352SPaul Cercueil MODULE_AUTHOR("Paul Cercueil <paul@crapouillou.net>"); 223f865c352SPaul Cercueil MODULE_DESCRIPTION("jz4740 Watchdog Driver"); 224f865c352SPaul Cercueil MODULE_LICENSE("GPL"); 225f865c352SPaul Cercueil MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); 226f865c352SPaul Cercueil MODULE_ALIAS("platform:jz4740-wdt"); 227