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> 27*6b96c722SZubair 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 127f865c352SPaul Cercueil jz4740_timer_disable_watchdog(); 12885f6df14SAxel Lin writeb(0x0, drvdata->base + JZ_REG_WDT_COUNTER_ENABLE); 12985f6df14SAxel Lin 13085f6df14SAxel Lin return 0; 131f865c352SPaul Cercueil } 132f865c352SPaul Cercueil 13385f6df14SAxel Lin static const struct watchdog_info jz4740_wdt_info = { 13485f6df14SAxel Lin .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, 135f865c352SPaul Cercueil .identity = "jz4740 Watchdog", 136f865c352SPaul Cercueil }; 137f865c352SPaul Cercueil 13885f6df14SAxel Lin static const struct watchdog_ops jz4740_wdt_ops = { 139f865c352SPaul Cercueil .owner = THIS_MODULE, 14085f6df14SAxel Lin .start = jz4740_wdt_start, 14185f6df14SAxel Lin .stop = jz4740_wdt_stop, 14285f6df14SAxel Lin .ping = jz4740_wdt_ping, 14385f6df14SAxel Lin .set_timeout = jz4740_wdt_set_timeout, 144f865c352SPaul Cercueil }; 145f865c352SPaul Cercueil 146*6b96c722SZubair Lutfullah Kakakhel #ifdef CONFIG_OF 147*6b96c722SZubair Lutfullah Kakakhel static const struct of_device_id jz4740_wdt_of_matches[] = { 148*6b96c722SZubair Lutfullah Kakakhel { .compatible = "ingenic,jz4740-watchdog", }, 149*6b96c722SZubair Lutfullah Kakakhel { /* sentinel */ } 150*6b96c722SZubair Lutfullah Kakakhel }; 151*6b96c722SZubair Lutfullah Kakakhel MODULE_DEVICE_TABLE(of, jz4740_wdt_of_matches) 152*6b96c722SZubair Lutfullah Kakakhel #endif 153*6b96c722SZubair Lutfullah Kakakhel 1542d991a16SBill Pemberton static int jz4740_wdt_probe(struct platform_device *pdev) 155f865c352SPaul Cercueil { 15685f6df14SAxel Lin struct jz4740_wdt_drvdata *drvdata; 15785f6df14SAxel Lin struct watchdog_device *jz4740_wdt; 158f865c352SPaul Cercueil struct resource *res; 15985f6df14SAxel Lin int ret; 16085f6df14SAxel Lin 16185f6df14SAxel Lin drvdata = devm_kzalloc(&pdev->dev, sizeof(struct jz4740_wdt_drvdata), 16285f6df14SAxel Lin GFP_KERNEL); 16385f6df14SAxel Lin if (!drvdata) { 16485f6df14SAxel Lin dev_err(&pdev->dev, "Unable to alloacate watchdog device\n"); 16585f6df14SAxel Lin return -ENOMEM; 16685f6df14SAxel Lin } 16785f6df14SAxel Lin 16885f6df14SAxel Lin if (heartbeat < 1 || heartbeat > MAX_HEARTBEAT) 16985f6df14SAxel Lin heartbeat = DEFAULT_HEARTBEAT; 17085f6df14SAxel Lin 17185f6df14SAxel Lin jz4740_wdt = &drvdata->wdt; 17285f6df14SAxel Lin jz4740_wdt->info = &jz4740_wdt_info; 17385f6df14SAxel Lin jz4740_wdt->ops = &jz4740_wdt_ops; 17485f6df14SAxel Lin jz4740_wdt->timeout = heartbeat; 17585f6df14SAxel Lin jz4740_wdt->min_timeout = 1; 17685f6df14SAxel Lin jz4740_wdt->max_timeout = MAX_HEARTBEAT; 17785f6df14SAxel Lin watchdog_set_nowayout(jz4740_wdt, nowayout); 17885f6df14SAxel Lin watchdog_set_drvdata(jz4740_wdt, drvdata); 179f865c352SPaul Cercueil 180f865c352SPaul Cercueil res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 1814c271bb6SThierry Reding drvdata->base = devm_ioremap_resource(&pdev->dev, res); 1824c271bb6SThierry Reding if (IS_ERR(drvdata->base)) { 1834c271bb6SThierry Reding ret = PTR_ERR(drvdata->base); 18485f6df14SAxel Lin goto err_out; 185f865c352SPaul Cercueil } 186f865c352SPaul Cercueil 1875f314970SLars-Peter Clausen drvdata->rtc_clk = clk_get(&pdev->dev, "rtc"); 18885f6df14SAxel Lin if (IS_ERR(drvdata->rtc_clk)) { 18985f6df14SAxel Lin dev_err(&pdev->dev, "cannot find RTC clock\n"); 19085f6df14SAxel Lin ret = PTR_ERR(drvdata->rtc_clk); 19185f6df14SAxel Lin goto err_out; 192f865c352SPaul Cercueil } 193f865c352SPaul Cercueil 19485f6df14SAxel Lin ret = watchdog_register_device(&drvdata->wdt); 19585f6df14SAxel Lin if (ret < 0) 196f865c352SPaul Cercueil goto err_disable_clk; 197f865c352SPaul Cercueil 19885f6df14SAxel Lin platform_set_drvdata(pdev, drvdata); 199f865c352SPaul Cercueil return 0; 200f865c352SPaul Cercueil 201f865c352SPaul Cercueil err_disable_clk: 20285f6df14SAxel Lin clk_put(drvdata->rtc_clk); 20385f6df14SAxel Lin err_out: 204f865c352SPaul Cercueil return ret; 205f865c352SPaul Cercueil } 206f865c352SPaul Cercueil 2074b12b896SBill Pemberton static int jz4740_wdt_remove(struct platform_device *pdev) 208f865c352SPaul Cercueil { 20985f6df14SAxel Lin struct jz4740_wdt_drvdata *drvdata = platform_get_drvdata(pdev); 210f865c352SPaul Cercueil 21185f6df14SAxel Lin jz4740_wdt_stop(&drvdata->wdt); 21285f6df14SAxel Lin watchdog_unregister_device(&drvdata->wdt); 21385f6df14SAxel Lin clk_put(drvdata->rtc_clk); 214f865c352SPaul Cercueil 215f865c352SPaul Cercueil return 0; 216f865c352SPaul Cercueil } 217f865c352SPaul Cercueil 218f865c352SPaul Cercueil static struct platform_driver jz4740_wdt_driver = { 219f865c352SPaul Cercueil .probe = jz4740_wdt_probe, 22082268714SBill Pemberton .remove = jz4740_wdt_remove, 221f865c352SPaul Cercueil .driver = { 222f865c352SPaul Cercueil .name = "jz4740-wdt", 223*6b96c722SZubair Lutfullah Kakakhel .of_match_table = of_match_ptr(jz4740_wdt_of_matches), 224f865c352SPaul Cercueil }, 225f865c352SPaul Cercueil }; 226f865c352SPaul Cercueil 227b8ec6118SAxel Lin module_platform_driver(jz4740_wdt_driver); 228f865c352SPaul Cercueil 229f865c352SPaul Cercueil MODULE_AUTHOR("Paul Cercueil <paul@crapouillou.net>"); 230f865c352SPaul Cercueil MODULE_DESCRIPTION("jz4740 Watchdog Driver"); 231f865c352SPaul Cercueil MODULE_LICENSE("GPL"); 232f865c352SPaul Cercueil MODULE_ALIAS("platform:jz4740-wdt"); 233