jz4740_wdt.c (5238B)
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 26static bool nowayout = WATCHDOG_NOWAYOUT; 27module_param(nowayout, bool, 0); 28MODULE_PARM_DESC(nowayout, 29 "Watchdog cannot be stopped once started (default=" 30 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 31 32static unsigned int heartbeat = DEFAULT_HEARTBEAT; 33module_param(heartbeat, uint, 0); 34MODULE_PARM_DESC(heartbeat, 35 "Watchdog heartbeat period in seconds from 1 to " 36 __MODULE_STRING(MAX_HEARTBEAT) ", default " 37 __MODULE_STRING(DEFAULT_HEARTBEAT)); 38 39struct jz4740_wdt_drvdata { 40 struct watchdog_device wdt; 41 struct regmap *map; 42 struct clk *clk; 43 unsigned long clk_rate; 44}; 45 46static 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 55static 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 75static 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 96static 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 106static 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 114static const struct watchdog_info jz4740_wdt_info = { 115 .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, 116 .identity = "jz4740 Watchdog", 117}; 118 119static 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 129static const struct of_device_id jz4740_wdt_of_matches[] = { 130 { .compatible = "ingenic,jz4740-watchdog", }, 131 { .compatible = "ingenic,jz4780-watchdog", }, 132 { /* sentinel */ } 133}; 134MODULE_DEVICE_TABLE(of, jz4740_wdt_of_matches); 135#endif 136 137static 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 (IS_ERR(drvdata->map)) { 180 dev_err(dev, "regmap not found\n"); 181 return PTR_ERR(drvdata->map); 182 } 183 184 return devm_watchdog_register_device(dev, &drvdata->wdt); 185} 186 187static 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 195module_platform_driver(jz4740_wdt_driver); 196 197MODULE_AUTHOR("Paul Cercueil <paul@crapouillou.net>"); 198MODULE_DESCRIPTION("jz4740 Watchdog Driver"); 199MODULE_LICENSE("GPL"); 200MODULE_ALIAS("platform:jz4740-wdt");