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> 8*6d532143SPaul Cercueil #include <linux/mfd/syscon.h> 9f865c352SPaul Cercueil #include <linux/module.h> 10f865c352SPaul Cercueil #include <linux/moduleparam.h> 11f865c352SPaul Cercueil #include <linux/types.h> 12f865c352SPaul Cercueil #include <linux/kernel.h> 13f865c352SPaul Cercueil #include <linux/watchdog.h> 14f865c352SPaul Cercueil #include <linux/platform_device.h> 15f865c352SPaul Cercueil #include <linux/io.h> 16f865c352SPaul Cercueil #include <linux/device.h> 17f865c352SPaul Cercueil #include <linux/clk.h> 18f865c352SPaul Cercueil #include <linux/slab.h> 1985f6df14SAxel Lin #include <linux/err.h> 206b96c722SZubair Lutfullah Kakakhel #include <linux/of.h> 21*6d532143SPaul Cercueil #include <linux/regmap.h> 22f865c352SPaul Cercueil 23f865c352SPaul Cercueil #define DEFAULT_HEARTBEAT 5 24f865c352SPaul Cercueil #define MAX_HEARTBEAT 2048 25f865c352SPaul Cercueil 2685f6df14SAxel Lin static bool nowayout = WATCHDOG_NOWAYOUT; 2785f6df14SAxel Lin module_param(nowayout, bool, 0); 2885f6df14SAxel Lin MODULE_PARM_DESC(nowayout, 2985f6df14SAxel Lin "Watchdog cannot be stopped once started (default=" 3085f6df14SAxel Lin __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 3185f6df14SAxel Lin 3285f6df14SAxel Lin static unsigned int heartbeat = DEFAULT_HEARTBEAT; 3385f6df14SAxel Lin module_param(heartbeat, uint, 0); 3485f6df14SAxel Lin MODULE_PARM_DESC(heartbeat, 3585f6df14SAxel Lin "Watchdog heartbeat period in seconds from 1 to " 3685f6df14SAxel Lin __MODULE_STRING(MAX_HEARTBEAT) ", default " 3785f6df14SAxel Lin __MODULE_STRING(DEFAULT_HEARTBEAT)); 3885f6df14SAxel Lin 3985f6df14SAxel Lin struct jz4740_wdt_drvdata { 4085f6df14SAxel Lin struct watchdog_device wdt; 41*6d532143SPaul Cercueil struct regmap *map; 421d9c3074SPaul Cercueil struct clk *clk; 431d9c3074SPaul Cercueil unsigned long clk_rate; 4485f6df14SAxel Lin }; 45f865c352SPaul Cercueil 4685f6df14SAxel Lin static int jz4740_wdt_ping(struct watchdog_device *wdt_dev) 47f865c352SPaul Cercueil { 4885f6df14SAxel Lin struct jz4740_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev); 4985f6df14SAxel Lin 50*6d532143SPaul Cercueil regmap_write(drvdata->map, TCU_REG_WDT_TCNT, 0); 51*6d532143SPaul Cercueil 5285f6df14SAxel Lin return 0; 53f865c352SPaul Cercueil } 54f865c352SPaul Cercueil 5585f6df14SAxel Lin static int jz4740_wdt_set_timeout(struct watchdog_device *wdt_dev, 5685f6df14SAxel Lin unsigned int new_timeout) 57f865c352SPaul Cercueil { 5885f6df14SAxel Lin struct jz4740_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev); 591d9c3074SPaul Cercueil u16 timeout_value = (u16)(drvdata->clk_rate * new_timeout); 60*6d532143SPaul Cercueil unsigned int tcer; 61f865c352SPaul Cercueil 62*6d532143SPaul Cercueil regmap_read(drvdata->map, TCU_REG_WDT_TCER, &tcer); 63*6d532143SPaul Cercueil regmap_write(drvdata->map, TCU_REG_WDT_TCER, 0); 64f865c352SPaul Cercueil 65*6d532143SPaul Cercueil regmap_write(drvdata->map, TCU_REG_WDT_TDR, timeout_value); 66*6d532143SPaul Cercueil regmap_write(drvdata->map, TCU_REG_WDT_TCNT, 0); 67f865c352SPaul Cercueil 689b346118SPaul Cercueil if (tcer & TCU_WDT_TCER_TCEN) 69*6d532143SPaul Cercueil regmap_write(drvdata->map, TCU_REG_WDT_TCER, TCU_WDT_TCER_TCEN); 7085f6df14SAxel Lin 710197c1c4SWim Van Sebroeck wdt_dev->timeout = new_timeout; 7285f6df14SAxel Lin return 0; 73f865c352SPaul Cercueil } 74f865c352SPaul Cercueil 7585f6df14SAxel Lin static int jz4740_wdt_start(struct watchdog_device *wdt_dev) 76f865c352SPaul Cercueil { 779b346118SPaul Cercueil struct jz4740_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev); 78*6d532143SPaul Cercueil unsigned int tcer; 791d9c3074SPaul Cercueil int ret; 809b346118SPaul Cercueil 811d9c3074SPaul Cercueil ret = clk_prepare_enable(drvdata->clk); 821d9c3074SPaul Cercueil if (ret) 831d9c3074SPaul Cercueil return ret; 841d9c3074SPaul Cercueil 85*6d532143SPaul Cercueil regmap_read(drvdata->map, TCU_REG_WDT_TCER, &tcer); 869b346118SPaul Cercueil 8785f6df14SAxel Lin jz4740_wdt_set_timeout(wdt_dev, wdt_dev->timeout); 8885f6df14SAxel Lin 899b346118SPaul Cercueil /* Start watchdog if it wasn't started already */ 909b346118SPaul Cercueil if (!(tcer & TCU_WDT_TCER_TCEN)) 91*6d532143SPaul Cercueil regmap_write(drvdata->map, TCU_REG_WDT_TCER, TCU_WDT_TCER_TCEN); 929b346118SPaul Cercueil 9385f6df14SAxel Lin return 0; 94f865c352SPaul Cercueil } 95f865c352SPaul Cercueil 9685f6df14SAxel Lin static int jz4740_wdt_stop(struct watchdog_device *wdt_dev) 97f865c352SPaul Cercueil { 9885f6df14SAxel Lin struct jz4740_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev); 9985f6df14SAxel Lin 100*6d532143SPaul Cercueil regmap_write(drvdata->map, TCU_REG_WDT_TCER, 0); 1011d9c3074SPaul Cercueil clk_disable_unprepare(drvdata->clk); 10285f6df14SAxel Lin 10385f6df14SAxel Lin return 0; 104f865c352SPaul Cercueil } 105f865c352SPaul Cercueil 106b4918057SPaul Cercueil static int jz4740_wdt_restart(struct watchdog_device *wdt_dev, 107b4918057SPaul Cercueil unsigned long action, void *data) 108b4918057SPaul Cercueil { 109b4918057SPaul Cercueil wdt_dev->timeout = 0; 110b4918057SPaul Cercueil jz4740_wdt_start(wdt_dev); 111b4918057SPaul Cercueil return 0; 112b4918057SPaul Cercueil } 113b4918057SPaul Cercueil 11485f6df14SAxel Lin static const struct watchdog_info jz4740_wdt_info = { 11585f6df14SAxel Lin .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, 116f865c352SPaul Cercueil .identity = "jz4740 Watchdog", 117f865c352SPaul Cercueil }; 118f865c352SPaul Cercueil 11985f6df14SAxel Lin static const struct watchdog_ops jz4740_wdt_ops = { 120f865c352SPaul Cercueil .owner = THIS_MODULE, 12185f6df14SAxel Lin .start = jz4740_wdt_start, 12285f6df14SAxel Lin .stop = jz4740_wdt_stop, 12385f6df14SAxel Lin .ping = jz4740_wdt_ping, 12485f6df14SAxel Lin .set_timeout = jz4740_wdt_set_timeout, 125b4918057SPaul Cercueil .restart = jz4740_wdt_restart, 126f865c352SPaul Cercueil }; 127f865c352SPaul Cercueil 1286b96c722SZubair Lutfullah Kakakhel #ifdef CONFIG_OF 1296b96c722SZubair Lutfullah Kakakhel static const struct of_device_id jz4740_wdt_of_matches[] = { 1306b96c722SZubair Lutfullah Kakakhel { .compatible = "ingenic,jz4740-watchdog", }, 13171246c35SMathieu Malaterre { .compatible = "ingenic,jz4780-watchdog", }, 1326b96c722SZubair Lutfullah Kakakhel { /* sentinel */ } 1336b96c722SZubair Lutfullah Kakakhel }; 13435ffa961SStephen Boyd MODULE_DEVICE_TABLE(of, jz4740_wdt_of_matches); 1356b96c722SZubair Lutfullah Kakakhel #endif 1366b96c722SZubair Lutfullah Kakakhel 1372d991a16SBill Pemberton static int jz4740_wdt_probe(struct platform_device *pdev) 138f865c352SPaul Cercueil { 13902189bb9SGuenter Roeck struct device *dev = &pdev->dev; 14085f6df14SAxel Lin struct jz4740_wdt_drvdata *drvdata; 14185f6df14SAxel Lin struct watchdog_device *jz4740_wdt; 1421d9c3074SPaul Cercueil long rate; 1431d9c3074SPaul Cercueil int ret; 14485f6df14SAxel Lin 14502189bb9SGuenter Roeck drvdata = devm_kzalloc(dev, sizeof(struct jz4740_wdt_drvdata), 14685f6df14SAxel Lin GFP_KERNEL); 147e26e74b1SColin Ian King if (!drvdata) 14885f6df14SAxel Lin return -ENOMEM; 14985f6df14SAxel Lin 1501d9c3074SPaul Cercueil drvdata->clk = devm_clk_get(&pdev->dev, "wdt"); 1511d9c3074SPaul Cercueil if (IS_ERR(drvdata->clk)) { 1521d9c3074SPaul Cercueil dev_err(&pdev->dev, "cannot find WDT clock\n"); 1531d9c3074SPaul Cercueil return PTR_ERR(drvdata->clk); 1541d9c3074SPaul Cercueil } 15585f6df14SAxel Lin 1561d9c3074SPaul Cercueil /* Set smallest clock possible */ 1571d9c3074SPaul Cercueil rate = clk_round_rate(drvdata->clk, 1); 1581d9c3074SPaul Cercueil if (rate < 0) 1591d9c3074SPaul Cercueil return rate; 1601d9c3074SPaul Cercueil 1611d9c3074SPaul Cercueil ret = clk_set_rate(drvdata->clk, rate); 1621d9c3074SPaul Cercueil if (ret) 1631d9c3074SPaul Cercueil return ret; 1641d9c3074SPaul Cercueil 1651d9c3074SPaul Cercueil drvdata->clk_rate = rate; 16685f6df14SAxel Lin jz4740_wdt = &drvdata->wdt; 16785f6df14SAxel Lin jz4740_wdt->info = &jz4740_wdt_info; 16885f6df14SAxel Lin jz4740_wdt->ops = &jz4740_wdt_ops; 16985f6df14SAxel Lin jz4740_wdt->min_timeout = 1; 1701d9c3074SPaul Cercueil jz4740_wdt->max_timeout = 0xffff / rate; 1711d9c3074SPaul Cercueil jz4740_wdt->timeout = clamp(heartbeat, 1721d9c3074SPaul Cercueil jz4740_wdt->min_timeout, 1731d9c3074SPaul Cercueil jz4740_wdt->max_timeout); 17402189bb9SGuenter Roeck jz4740_wdt->parent = dev; 17585f6df14SAxel Lin watchdog_set_nowayout(jz4740_wdt, nowayout); 17685f6df14SAxel Lin watchdog_set_drvdata(jz4740_wdt, drvdata); 177f865c352SPaul Cercueil 178*6d532143SPaul Cercueil drvdata->map = device_node_to_regmap(dev->parent->of_node); 179*6d532143SPaul Cercueil if (!drvdata->map) { 180*6d532143SPaul Cercueil dev_err(dev, "regmap not found\n"); 181*6d532143SPaul Cercueil return -EINVAL; 182*6d532143SPaul Cercueil } 183f865c352SPaul Cercueil 1849ee644c9SWolfram Sang return devm_watchdog_register_device(dev, &drvdata->wdt); 185f865c352SPaul Cercueil } 186f865c352SPaul Cercueil 187f865c352SPaul Cercueil static struct platform_driver jz4740_wdt_driver = { 188f865c352SPaul Cercueil .probe = jz4740_wdt_probe, 189f865c352SPaul Cercueil .driver = { 190f865c352SPaul Cercueil .name = "jz4740-wdt", 1916b96c722SZubair Lutfullah Kakakhel .of_match_table = of_match_ptr(jz4740_wdt_of_matches), 192f865c352SPaul Cercueil }, 193f865c352SPaul Cercueil }; 194f865c352SPaul Cercueil 195b8ec6118SAxel Lin module_platform_driver(jz4740_wdt_driver); 196f865c352SPaul Cercueil 197f865c352SPaul Cercueil MODULE_AUTHOR("Paul Cercueil <paul@crapouillou.net>"); 198f865c352SPaul Cercueil MODULE_DESCRIPTION("jz4740 Watchdog Driver"); 199f865c352SPaul Cercueil MODULE_LICENSE("GPL"); 200f865c352SPaul Cercueil MODULE_ALIAS("platform:jz4740-wdt"); 201