1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * sl28cpld watchdog driver 4 * 5 * Copyright 2020 Kontron Europe GmbH 6 */ 7 8 #include <linux/kernel.h> 9 #include <linux/mod_devicetable.h> 10 #include <linux/module.h> 11 #include <linux/platform_device.h> 12 #include <linux/property.h> 13 #include <linux/regmap.h> 14 #include <linux/watchdog.h> 15 16 /* 17 * Watchdog timer block registers. 18 */ 19 #define WDT_CTRL 0x00 20 #define WDT_CTRL_EN BIT(0) 21 #define WDT_CTRL_LOCK BIT(2) 22 #define WDT_CTRL_ASSERT_SYS_RESET BIT(6) 23 #define WDT_CTRL_ASSERT_WDT_TIMEOUT BIT(7) 24 #define WDT_TIMEOUT 0x01 25 #define WDT_KICK 0x02 26 #define WDT_KICK_VALUE 0x6b 27 #define WDT_COUNT 0x03 28 29 #define WDT_DEFAULT_TIMEOUT 10 30 31 static bool nowayout = WATCHDOG_NOWAYOUT; 32 module_param(nowayout, bool, 0); 33 MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" 34 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); 35 36 static int timeout; 37 module_param(timeout, int, 0); 38 MODULE_PARM_DESC(timeout, "Initial watchdog timeout in seconds"); 39 40 struct sl28cpld_wdt { 41 struct watchdog_device wdd; 42 struct regmap *regmap; 43 u32 offset; 44 bool assert_wdt_timeout; 45 }; 46 47 static int sl28cpld_wdt_ping(struct watchdog_device *wdd) 48 { 49 struct sl28cpld_wdt *wdt = watchdog_get_drvdata(wdd); 50 51 return regmap_write(wdt->regmap, wdt->offset + WDT_KICK, 52 WDT_KICK_VALUE); 53 } 54 55 static int sl28cpld_wdt_start(struct watchdog_device *wdd) 56 { 57 struct sl28cpld_wdt *wdt = watchdog_get_drvdata(wdd); 58 unsigned int val; 59 60 val = WDT_CTRL_EN | WDT_CTRL_ASSERT_SYS_RESET; 61 if (wdt->assert_wdt_timeout) 62 val |= WDT_CTRL_ASSERT_WDT_TIMEOUT; 63 if (nowayout) 64 val |= WDT_CTRL_LOCK; 65 66 return regmap_update_bits(wdt->regmap, wdt->offset + WDT_CTRL, 67 val, val); 68 } 69 70 static int sl28cpld_wdt_stop(struct watchdog_device *wdd) 71 { 72 struct sl28cpld_wdt *wdt = watchdog_get_drvdata(wdd); 73 74 return regmap_update_bits(wdt->regmap, wdt->offset + WDT_CTRL, 75 WDT_CTRL_EN, 0); 76 } 77 78 static unsigned int sl28cpld_wdt_get_timeleft(struct watchdog_device *wdd) 79 { 80 struct sl28cpld_wdt *wdt = watchdog_get_drvdata(wdd); 81 unsigned int val; 82 int ret; 83 84 ret = regmap_read(wdt->regmap, wdt->offset + WDT_COUNT, &val); 85 if (ret) 86 return 0; 87 88 return val; 89 } 90 91 static int sl28cpld_wdt_set_timeout(struct watchdog_device *wdd, 92 unsigned int timeout) 93 { 94 struct sl28cpld_wdt *wdt = watchdog_get_drvdata(wdd); 95 int ret; 96 97 ret = regmap_write(wdt->regmap, wdt->offset + WDT_TIMEOUT, timeout); 98 if (ret) 99 return ret; 100 101 wdd->timeout = timeout; 102 103 return 0; 104 } 105 106 static const struct watchdog_info sl28cpld_wdt_info = { 107 .options = WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING, 108 .identity = "sl28cpld watchdog", 109 }; 110 111 static struct watchdog_ops sl28cpld_wdt_ops = { 112 .owner = THIS_MODULE, 113 .start = sl28cpld_wdt_start, 114 .stop = sl28cpld_wdt_stop, 115 .ping = sl28cpld_wdt_ping, 116 .set_timeout = sl28cpld_wdt_set_timeout, 117 .get_timeleft = sl28cpld_wdt_get_timeleft, 118 }; 119 120 static int sl28cpld_wdt_probe(struct platform_device *pdev) 121 { 122 struct watchdog_device *wdd; 123 struct sl28cpld_wdt *wdt; 124 unsigned int status; 125 unsigned int val; 126 int ret; 127 128 if (!pdev->dev.parent) 129 return -ENODEV; 130 131 wdt = devm_kzalloc(&pdev->dev, sizeof(*wdt), GFP_KERNEL); 132 if (!wdt) 133 return -ENOMEM; 134 135 wdt->regmap = dev_get_regmap(pdev->dev.parent, NULL); 136 if (!wdt->regmap) 137 return -ENODEV; 138 139 ret = device_property_read_u32(&pdev->dev, "reg", &wdt->offset); 140 if (ret) 141 return -EINVAL; 142 143 wdt->assert_wdt_timeout = device_property_read_bool(&pdev->dev, 144 "kontron,assert-wdt-timeout-pin"); 145 146 /* initialize struct watchdog_device */ 147 wdd = &wdt->wdd; 148 wdd->parent = &pdev->dev; 149 wdd->info = &sl28cpld_wdt_info; 150 wdd->ops = &sl28cpld_wdt_ops; 151 wdd->min_timeout = 1; 152 wdd->max_timeout = 255; 153 154 watchdog_set_drvdata(wdd, wdt); 155 watchdog_stop_on_reboot(wdd); 156 157 /* 158 * Read the status early, in case of an error, we haven't modified the 159 * hardware. 160 */ 161 ret = regmap_read(wdt->regmap, wdt->offset + WDT_CTRL, &status); 162 if (ret) 163 return ret; 164 165 /* 166 * Initial timeout value, may be overwritten by device tree or module 167 * parmeter in watchdog_init_timeout(). 168 * 169 * Reading a zero here means that either the hardware has a default 170 * value of zero (which is very unlikely and definitely a hardware 171 * bug) or the bootloader set it to zero. In any case, we handle 172 * this case gracefully and set out own timeout. 173 */ 174 ret = regmap_read(wdt->regmap, wdt->offset + WDT_TIMEOUT, &val); 175 if (ret) 176 return ret; 177 178 if (val) 179 wdd->timeout = val; 180 else 181 wdd->timeout = WDT_DEFAULT_TIMEOUT; 182 183 watchdog_init_timeout(wdd, timeout, &pdev->dev); 184 sl28cpld_wdt_set_timeout(wdd, wdd->timeout); 185 186 /* if the watchdog is locked, we set nowayout */ 187 if (status & WDT_CTRL_LOCK) 188 nowayout = true; 189 watchdog_set_nowayout(wdd, nowayout); 190 191 /* 192 * If watchdog is already running, keep it enabled, but make 193 * sure its mode is set correctly. 194 */ 195 if (status & WDT_CTRL_EN) { 196 sl28cpld_wdt_start(wdd); 197 set_bit(WDOG_HW_RUNNING, &wdd->status); 198 } 199 200 ret = devm_watchdog_register_device(&pdev->dev, wdd); 201 if (ret < 0) { 202 dev_err(&pdev->dev, "failed to register watchdog device\n"); 203 return ret; 204 } 205 206 dev_info(&pdev->dev, "initial timeout %d sec%s\n", 207 wdd->timeout, nowayout ? ", nowayout" : ""); 208 209 return 0; 210 } 211 212 static const struct of_device_id sl28cpld_wdt_of_match[] = { 213 { .compatible = "kontron,sl28cpld-wdt" }, 214 {} 215 }; 216 MODULE_DEVICE_TABLE(of, sl28cpld_wdt_of_match); 217 218 static struct platform_driver sl28cpld_wdt_driver = { 219 .probe = sl28cpld_wdt_probe, 220 .driver = { 221 .name = "sl28cpld-wdt", 222 .of_match_table = sl28cpld_wdt_of_match, 223 }, 224 }; 225 module_platform_driver(sl28cpld_wdt_driver); 226 227 MODULE_DESCRIPTION("sl28cpld Watchdog Driver"); 228 MODULE_AUTHOR("Michael Walle <michael@walle.cc>"); 229 MODULE_LICENSE("GPL"); 230