1a912e80bSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later 2f865c352SPaul Cercueil /* 3f865c352SPaul Cercueil * Copyright (C) 2010, Paul Cercueil <paul@crapouillou.net> 4f865c352SPaul Cercueil * JZ4740 Watchdog driver 5f865c352SPaul Cercueil */ 6f865c352SPaul Cercueil 7df04cce3SPaul Cercueil #include <linux/mfd/ingenic-tcu.h> 8f865c352SPaul Cercueil #include <linux/module.h> 9f865c352SPaul Cercueil #include <linux/moduleparam.h> 10f865c352SPaul Cercueil #include <linux/types.h> 11f865c352SPaul Cercueil #include <linux/kernel.h> 12f865c352SPaul Cercueil #include <linux/watchdog.h> 13f865c352SPaul Cercueil #include <linux/platform_device.h> 14f865c352SPaul Cercueil #include <linux/io.h> 15f865c352SPaul Cercueil #include <linux/device.h> 16f865c352SPaul Cercueil #include <linux/clk.h> 17f865c352SPaul Cercueil #include <linux/slab.h> 1885f6df14SAxel Lin #include <linux/err.h> 196b96c722SZubair Lutfullah Kakakhel #include <linux/of.h> 20f865c352SPaul Cercueil 21f865c352SPaul Cercueil #include <asm/mach-jz4740/timer.h> 22f865c352SPaul Cercueil 23f865c352SPaul Cercueil #define JZ_WDT_CLOCK_PCLK 0x1 24f865c352SPaul Cercueil #define JZ_WDT_CLOCK_RTC 0x2 25f865c352SPaul Cercueil #define JZ_WDT_CLOCK_EXT 0x4 26f865c352SPaul Cercueil 27df04cce3SPaul Cercueil #define JZ_WDT_CLOCK_DIV_1 (0 << TCU_TCSR_PRESCALE_LSB) 28df04cce3SPaul Cercueil #define JZ_WDT_CLOCK_DIV_4 (1 << TCU_TCSR_PRESCALE_LSB) 29df04cce3SPaul Cercueil #define JZ_WDT_CLOCK_DIV_16 (2 << TCU_TCSR_PRESCALE_LSB) 30df04cce3SPaul Cercueil #define JZ_WDT_CLOCK_DIV_64 (3 << TCU_TCSR_PRESCALE_LSB) 31df04cce3SPaul Cercueil #define JZ_WDT_CLOCK_DIV_256 (4 << TCU_TCSR_PRESCALE_LSB) 32df04cce3SPaul Cercueil #define JZ_WDT_CLOCK_DIV_1024 (5 << TCU_TCSR_PRESCALE_LSB) 33f865c352SPaul Cercueil 34f865c352SPaul Cercueil #define DEFAULT_HEARTBEAT 5 35f865c352SPaul Cercueil #define MAX_HEARTBEAT 2048 36f865c352SPaul Cercueil 3785f6df14SAxel Lin static bool nowayout = WATCHDOG_NOWAYOUT; 3885f6df14SAxel Lin module_param(nowayout, bool, 0); 3985f6df14SAxel Lin MODULE_PARM_DESC(nowayout, 4085f6df14SAxel Lin "Watchdog cannot be stopped once started (default=" 4185f6df14SAxel Lin __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 4285f6df14SAxel Lin 4385f6df14SAxel Lin static unsigned int heartbeat = DEFAULT_HEARTBEAT; 4485f6df14SAxel Lin module_param(heartbeat, uint, 0); 4585f6df14SAxel Lin MODULE_PARM_DESC(heartbeat, 4685f6df14SAxel Lin "Watchdog heartbeat period in seconds from 1 to " 4785f6df14SAxel Lin __MODULE_STRING(MAX_HEARTBEAT) ", default " 4885f6df14SAxel Lin __MODULE_STRING(DEFAULT_HEARTBEAT)); 4985f6df14SAxel Lin 5085f6df14SAxel Lin struct jz4740_wdt_drvdata { 5185f6df14SAxel Lin struct watchdog_device wdt; 52f865c352SPaul Cercueil void __iomem *base; 53f865c352SPaul Cercueil struct clk *rtc_clk; 5485f6df14SAxel Lin }; 55f865c352SPaul Cercueil 5685f6df14SAxel Lin static int jz4740_wdt_ping(struct watchdog_device *wdt_dev) 57f865c352SPaul Cercueil { 5885f6df14SAxel Lin struct jz4740_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev); 5985f6df14SAxel Lin 60df04cce3SPaul Cercueil writew(0x0, drvdata->base + TCU_REG_WDT_TCNT); 6185f6df14SAxel Lin return 0; 62f865c352SPaul Cercueil } 63f865c352SPaul Cercueil 6485f6df14SAxel Lin static int jz4740_wdt_set_timeout(struct watchdog_device *wdt_dev, 6585f6df14SAxel Lin unsigned int new_timeout) 66f865c352SPaul Cercueil { 6785f6df14SAxel Lin struct jz4740_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev); 68f865c352SPaul Cercueil unsigned int rtc_clk_rate; 69f865c352SPaul Cercueil unsigned int timeout_value; 70f865c352SPaul Cercueil unsigned short clock_div = JZ_WDT_CLOCK_DIV_1; 71*9b346118SPaul Cercueil u8 tcer; 72f865c352SPaul Cercueil 7385f6df14SAxel Lin rtc_clk_rate = clk_get_rate(drvdata->rtc_clk); 74f865c352SPaul Cercueil 7585f6df14SAxel Lin timeout_value = rtc_clk_rate * new_timeout; 76f865c352SPaul Cercueil while (timeout_value > 0xffff) { 77f865c352SPaul Cercueil if (clock_div == JZ_WDT_CLOCK_DIV_1024) { 78f865c352SPaul Cercueil /* Requested timeout too high; 79f865c352SPaul Cercueil * use highest possible value. */ 80f865c352SPaul Cercueil timeout_value = 0xffff; 81f865c352SPaul Cercueil break; 82f865c352SPaul Cercueil } 83f865c352SPaul Cercueil timeout_value >>= 2; 84df04cce3SPaul Cercueil clock_div += (1 << TCU_TCSR_PRESCALE_LSB); 85f865c352SPaul Cercueil } 86f865c352SPaul Cercueil 87*9b346118SPaul Cercueil tcer = readb(drvdata->base + TCU_REG_WDT_TCER); 88df04cce3SPaul Cercueil writeb(0x0, drvdata->base + TCU_REG_WDT_TCER); 89df04cce3SPaul Cercueil writew(clock_div, drvdata->base + TCU_REG_WDT_TCSR); 90f865c352SPaul Cercueil 91df04cce3SPaul Cercueil writew((u16)timeout_value, drvdata->base + TCU_REG_WDT_TDR); 92df04cce3SPaul Cercueil writew(0x0, drvdata->base + TCU_REG_WDT_TCNT); 93df04cce3SPaul Cercueil writew(clock_div | JZ_WDT_CLOCK_RTC, drvdata->base + TCU_REG_WDT_TCSR); 94f865c352SPaul Cercueil 95*9b346118SPaul Cercueil if (tcer & TCU_WDT_TCER_TCEN) 96*9b346118SPaul Cercueil writeb(TCU_WDT_TCER_TCEN, drvdata->base + TCU_REG_WDT_TCER); 9785f6df14SAxel Lin 980197c1c4SWim Van Sebroeck wdt_dev->timeout = new_timeout; 9985f6df14SAxel Lin return 0; 100f865c352SPaul Cercueil } 101f865c352SPaul Cercueil 10285f6df14SAxel Lin static int jz4740_wdt_start(struct watchdog_device *wdt_dev) 103f865c352SPaul Cercueil { 104*9b346118SPaul Cercueil struct jz4740_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev); 105*9b346118SPaul Cercueil u8 tcer; 106*9b346118SPaul Cercueil 107*9b346118SPaul Cercueil tcer = readb(drvdata->base + TCU_REG_WDT_TCER); 108*9b346118SPaul Cercueil 109f865c352SPaul Cercueil jz4740_timer_enable_watchdog(); 11085f6df14SAxel Lin jz4740_wdt_set_timeout(wdt_dev, wdt_dev->timeout); 11185f6df14SAxel Lin 112*9b346118SPaul Cercueil /* Start watchdog if it wasn't started already */ 113*9b346118SPaul Cercueil if (!(tcer & TCU_WDT_TCER_TCEN)) 114*9b346118SPaul Cercueil writeb(TCU_WDT_TCER_TCEN, drvdata->base + TCU_REG_WDT_TCER); 115*9b346118SPaul Cercueil 11685f6df14SAxel Lin return 0; 117f865c352SPaul Cercueil } 118f865c352SPaul Cercueil 11985f6df14SAxel Lin static int jz4740_wdt_stop(struct watchdog_device *wdt_dev) 120f865c352SPaul Cercueil { 12185f6df14SAxel Lin struct jz4740_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev); 12285f6df14SAxel Lin 123df04cce3SPaul Cercueil writeb(0x0, drvdata->base + TCU_REG_WDT_TCER); 124212c1054SPaul Cercueil jz4740_timer_disable_watchdog(); 12585f6df14SAxel Lin 12685f6df14SAxel Lin return 0; 127f865c352SPaul Cercueil } 128f865c352SPaul Cercueil 129b4918057SPaul Cercueil static int jz4740_wdt_restart(struct watchdog_device *wdt_dev, 130b4918057SPaul Cercueil unsigned long action, void *data) 131b4918057SPaul Cercueil { 132b4918057SPaul Cercueil wdt_dev->timeout = 0; 133b4918057SPaul Cercueil jz4740_wdt_start(wdt_dev); 134b4918057SPaul Cercueil return 0; 135b4918057SPaul Cercueil } 136b4918057SPaul Cercueil 13785f6df14SAxel Lin static const struct watchdog_info jz4740_wdt_info = { 13885f6df14SAxel Lin .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, 139f865c352SPaul Cercueil .identity = "jz4740 Watchdog", 140f865c352SPaul Cercueil }; 141f865c352SPaul Cercueil 14285f6df14SAxel Lin static const struct watchdog_ops jz4740_wdt_ops = { 143f865c352SPaul Cercueil .owner = THIS_MODULE, 14485f6df14SAxel Lin .start = jz4740_wdt_start, 14585f6df14SAxel Lin .stop = jz4740_wdt_stop, 14685f6df14SAxel Lin .ping = jz4740_wdt_ping, 14785f6df14SAxel Lin .set_timeout = jz4740_wdt_set_timeout, 148b4918057SPaul Cercueil .restart = jz4740_wdt_restart, 149f865c352SPaul Cercueil }; 150f865c352SPaul Cercueil 1516b96c722SZubair Lutfullah Kakakhel #ifdef CONFIG_OF 1526b96c722SZubair Lutfullah Kakakhel static const struct of_device_id jz4740_wdt_of_matches[] = { 1536b96c722SZubair Lutfullah Kakakhel { .compatible = "ingenic,jz4740-watchdog", }, 15471246c35SMathieu Malaterre { .compatible = "ingenic,jz4780-watchdog", }, 1556b96c722SZubair Lutfullah Kakakhel { /* sentinel */ } 1566b96c722SZubair Lutfullah Kakakhel }; 15735ffa961SStephen Boyd MODULE_DEVICE_TABLE(of, jz4740_wdt_of_matches); 1586b96c722SZubair Lutfullah Kakakhel #endif 1596b96c722SZubair Lutfullah Kakakhel 1602d991a16SBill Pemberton static int jz4740_wdt_probe(struct platform_device *pdev) 161f865c352SPaul Cercueil { 16202189bb9SGuenter Roeck struct device *dev = &pdev->dev; 16385f6df14SAxel Lin struct jz4740_wdt_drvdata *drvdata; 16485f6df14SAxel Lin struct watchdog_device *jz4740_wdt; 16585f6df14SAxel Lin int ret; 16685f6df14SAxel Lin 16702189bb9SGuenter Roeck drvdata = devm_kzalloc(dev, sizeof(struct jz4740_wdt_drvdata), 16885f6df14SAxel Lin GFP_KERNEL); 169e26e74b1SColin Ian King if (!drvdata) 17085f6df14SAxel Lin return -ENOMEM; 17185f6df14SAxel Lin 17285f6df14SAxel Lin if (heartbeat < 1 || heartbeat > MAX_HEARTBEAT) 17385f6df14SAxel Lin heartbeat = DEFAULT_HEARTBEAT; 17485f6df14SAxel Lin 17585f6df14SAxel Lin jz4740_wdt = &drvdata->wdt; 17685f6df14SAxel Lin jz4740_wdt->info = &jz4740_wdt_info; 17785f6df14SAxel Lin jz4740_wdt->ops = &jz4740_wdt_ops; 17885f6df14SAxel Lin jz4740_wdt->timeout = heartbeat; 17985f6df14SAxel Lin jz4740_wdt->min_timeout = 1; 18085f6df14SAxel Lin jz4740_wdt->max_timeout = MAX_HEARTBEAT; 18102189bb9SGuenter Roeck jz4740_wdt->parent = dev; 18285f6df14SAxel Lin watchdog_set_nowayout(jz4740_wdt, nowayout); 18385f6df14SAxel Lin watchdog_set_drvdata(jz4740_wdt, drvdata); 184f865c352SPaul Cercueil 1850f0a6a28SGuenter Roeck drvdata->base = devm_platform_ioremap_resource(pdev, 0); 1866bdbc1f7SPaul Cercueil if (IS_ERR(drvdata->base)) 1876bdbc1f7SPaul Cercueil return PTR_ERR(drvdata->base); 188f865c352SPaul Cercueil 18902189bb9SGuenter Roeck drvdata->rtc_clk = devm_clk_get(dev, "rtc"); 19085f6df14SAxel Lin if (IS_ERR(drvdata->rtc_clk)) { 19102189bb9SGuenter Roeck dev_err(dev, "cannot find RTC clock\n"); 1926bdbc1f7SPaul Cercueil return PTR_ERR(drvdata->rtc_clk); 193f865c352SPaul Cercueil } 194f865c352SPaul Cercueil 1959ee644c9SWolfram Sang return devm_watchdog_register_device(dev, &drvdata->wdt); 196f865c352SPaul Cercueil } 197f865c352SPaul Cercueil 198f865c352SPaul Cercueil static struct platform_driver jz4740_wdt_driver = { 199f865c352SPaul Cercueil .probe = jz4740_wdt_probe, 200f865c352SPaul Cercueil .driver = { 201f865c352SPaul Cercueil .name = "jz4740-wdt", 2026b96c722SZubair Lutfullah Kakakhel .of_match_table = of_match_ptr(jz4740_wdt_of_matches), 203f865c352SPaul Cercueil }, 204f865c352SPaul Cercueil }; 205f865c352SPaul Cercueil 206b8ec6118SAxel Lin module_platform_driver(jz4740_wdt_driver); 207f865c352SPaul Cercueil 208f865c352SPaul Cercueil MODULE_AUTHOR("Paul Cercueil <paul@crapouillou.net>"); 209f865c352SPaul Cercueil MODULE_DESCRIPTION("jz4740 Watchdog Driver"); 210f865c352SPaul Cercueil MODULE_LICENSE("GPL"); 211f865c352SPaul Cercueil MODULE_ALIAS("platform:jz4740-wdt"); 212