1 // SPDX-License-Identifier: GPL-2.0-or-later 2 /* 3 * Copyright (C) 2010, Paul Cercueil <paul@crapouillou.net> 4 * JZ4740 Watchdog driver 5 */ 6 7 #include <linux/mfd/ingenic-tcu.h> 8 #include <linux/mfd/syscon.h> 9 #include <linux/module.h> 10 #include <linux/moduleparam.h> 11 #include <linux/types.h> 12 #include <linux/kernel.h> 13 #include <linux/watchdog.h> 14 #include <linux/platform_device.h> 15 #include <linux/io.h> 16 #include <linux/device.h> 17 #include <linux/clk.h> 18 #include <linux/slab.h> 19 #include <linux/err.h> 20 #include <linux/of.h> 21 #include <linux/regmap.h> 22 23 #define DEFAULT_HEARTBEAT 5 24 #define MAX_HEARTBEAT 2048 25 26 static bool nowayout = WATCHDOG_NOWAYOUT; 27 module_param(nowayout, bool, 0); 28 MODULE_PARM_DESC(nowayout, 29 "Watchdog cannot be stopped once started (default=" 30 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 31 32 static unsigned int heartbeat = DEFAULT_HEARTBEAT; 33 module_param(heartbeat, uint, 0); 34 MODULE_PARM_DESC(heartbeat, 35 "Watchdog heartbeat period in seconds from 1 to " 36 __MODULE_STRING(MAX_HEARTBEAT) ", default " 37 __MODULE_STRING(DEFAULT_HEARTBEAT)); 38 39 struct jz4740_wdt_drvdata { 40 struct watchdog_device wdt; 41 struct regmap *map; 42 struct clk *clk; 43 unsigned long clk_rate; 44 }; 45 46 static int jz4740_wdt_ping(struct watchdog_device *wdt_dev) 47 { 48 struct jz4740_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev); 49 50 regmap_write(drvdata->map, TCU_REG_WDT_TCNT, 0); 51 52 return 0; 53 } 54 55 static int jz4740_wdt_set_timeout(struct watchdog_device *wdt_dev, 56 unsigned int new_timeout) 57 { 58 struct jz4740_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev); 59 u16 timeout_value = (u16)(drvdata->clk_rate * new_timeout); 60 unsigned int tcer; 61 62 regmap_read(drvdata->map, TCU_REG_WDT_TCER, &tcer); 63 regmap_write(drvdata->map, TCU_REG_WDT_TCER, 0); 64 65 regmap_write(drvdata->map, TCU_REG_WDT_TDR, timeout_value); 66 regmap_write(drvdata->map, TCU_REG_WDT_TCNT, 0); 67 68 if (tcer & TCU_WDT_TCER_TCEN) 69 regmap_write(drvdata->map, TCU_REG_WDT_TCER, TCU_WDT_TCER_TCEN); 70 71 wdt_dev->timeout = new_timeout; 72 return 0; 73 } 74 75 static int jz4740_wdt_start(struct watchdog_device *wdt_dev) 76 { 77 struct jz4740_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev); 78 unsigned int tcer; 79 int ret; 80 81 ret = clk_prepare_enable(drvdata->clk); 82 if (ret) 83 return ret; 84 85 regmap_read(drvdata->map, TCU_REG_WDT_TCER, &tcer); 86 87 jz4740_wdt_set_timeout(wdt_dev, wdt_dev->timeout); 88 89 /* Start watchdog if it wasn't started already */ 90 if (!(tcer & TCU_WDT_TCER_TCEN)) 91 regmap_write(drvdata->map, TCU_REG_WDT_TCER, TCU_WDT_TCER_TCEN); 92 93 return 0; 94 } 95 96 static int jz4740_wdt_stop(struct watchdog_device *wdt_dev) 97 { 98 struct jz4740_wdt_drvdata *drvdata = watchdog_get_drvdata(wdt_dev); 99 100 regmap_write(drvdata->map, TCU_REG_WDT_TCER, 0); 101 clk_disable_unprepare(drvdata->clk); 102 103 return 0; 104 } 105 106 static int jz4740_wdt_restart(struct watchdog_device *wdt_dev, 107 unsigned long action, void *data) 108 { 109 wdt_dev->timeout = 0; 110 jz4740_wdt_start(wdt_dev); 111 return 0; 112 } 113 114 static const struct watchdog_info jz4740_wdt_info = { 115 .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, 116 .identity = "jz4740 Watchdog", 117 }; 118 119 static const struct watchdog_ops jz4740_wdt_ops = { 120 .owner = THIS_MODULE, 121 .start = jz4740_wdt_start, 122 .stop = jz4740_wdt_stop, 123 .ping = jz4740_wdt_ping, 124 .set_timeout = jz4740_wdt_set_timeout, 125 .restart = jz4740_wdt_restart, 126 }; 127 128 #ifdef CONFIG_OF 129 static const struct of_device_id jz4740_wdt_of_matches[] = { 130 { .compatible = "ingenic,jz4740-watchdog", }, 131 { .compatible = "ingenic,jz4780-watchdog", }, 132 { /* sentinel */ } 133 }; 134 MODULE_DEVICE_TABLE(of, jz4740_wdt_of_matches); 135 #endif 136 137 static int jz4740_wdt_probe(struct platform_device *pdev) 138 { 139 struct device *dev = &pdev->dev; 140 struct jz4740_wdt_drvdata *drvdata; 141 struct watchdog_device *jz4740_wdt; 142 long rate; 143 int ret; 144 145 drvdata = devm_kzalloc(dev, sizeof(struct jz4740_wdt_drvdata), 146 GFP_KERNEL); 147 if (!drvdata) 148 return -ENOMEM; 149 150 drvdata->clk = devm_clk_get(&pdev->dev, "wdt"); 151 if (IS_ERR(drvdata->clk)) { 152 dev_err(&pdev->dev, "cannot find WDT clock\n"); 153 return PTR_ERR(drvdata->clk); 154 } 155 156 /* Set smallest clock possible */ 157 rate = clk_round_rate(drvdata->clk, 1); 158 if (rate < 0) 159 return rate; 160 161 ret = clk_set_rate(drvdata->clk, rate); 162 if (ret) 163 return ret; 164 165 drvdata->clk_rate = rate; 166 jz4740_wdt = &drvdata->wdt; 167 jz4740_wdt->info = &jz4740_wdt_info; 168 jz4740_wdt->ops = &jz4740_wdt_ops; 169 jz4740_wdt->min_timeout = 1; 170 jz4740_wdt->max_timeout = 0xffff / rate; 171 jz4740_wdt->timeout = clamp(heartbeat, 172 jz4740_wdt->min_timeout, 173 jz4740_wdt->max_timeout); 174 jz4740_wdt->parent = dev; 175 watchdog_set_nowayout(jz4740_wdt, nowayout); 176 watchdog_set_drvdata(jz4740_wdt, drvdata); 177 178 drvdata->map = device_node_to_regmap(dev->parent->of_node); 179 if (!drvdata->map) { 180 dev_err(dev, "regmap not found\n"); 181 return -EINVAL; 182 } 183 184 return devm_watchdog_register_device(dev, &drvdata->wdt); 185 } 186 187 static struct platform_driver jz4740_wdt_driver = { 188 .probe = jz4740_wdt_probe, 189 .driver = { 190 .name = "jz4740-wdt", 191 .of_match_table = of_match_ptr(jz4740_wdt_of_matches), 192 }, 193 }; 194 195 module_platform_driver(jz4740_wdt_driver); 196 197 MODULE_AUTHOR("Paul Cercueil <paul@crapouillou.net>"); 198 MODULE_DESCRIPTION("jz4740 Watchdog Driver"); 199 MODULE_LICENSE("GPL"); 200 MODULE_ALIAS("platform:jz4740-wdt"); 201