1 // SPDX-License-Identifier: GPL-2.0-or-later 2 /* 3 * Watchdog driver for Alphascale ASM9260. 4 * 5 * Copyright (c) 2014 Oleksij Rempel <linux@rempel-privat.de> 6 */ 7 8 #include <linux/bitops.h> 9 #include <linux/clk.h> 10 #include <linux/delay.h> 11 #include <linux/interrupt.h> 12 #include <linux/io.h> 13 #include <linux/module.h> 14 #include <linux/of.h> 15 #include <linux/platform_device.h> 16 #include <linux/reset.h> 17 #include <linux/watchdog.h> 18 19 #define CLOCK_FREQ 1000000 20 21 /* Watchdog Mode register */ 22 #define HW_WDMOD 0x00 23 /* Wake interrupt. Set by HW, can't be cleared. */ 24 #define BM_MOD_WDINT BIT(3) 25 /* This bit set if timeout reached. Cleared by SW. */ 26 #define BM_MOD_WDTOF BIT(2) 27 /* HW Reset on timeout */ 28 #define BM_MOD_WDRESET BIT(1) 29 /* WD enable */ 30 #define BM_MOD_WDEN BIT(0) 31 32 /* 33 * Watchdog Timer Constant register 34 * Minimal value is 0xff, the meaning of this value 35 * depends on used clock: T = WDCLK * (0xff + 1) * 4 36 */ 37 #define HW_WDTC 0x04 38 #define BM_WDTC_MAX(freq) (0x7fffffff / (freq)) 39 40 /* Watchdog Feed register */ 41 #define HW_WDFEED 0x08 42 43 /* Watchdog Timer Value register */ 44 #define HW_WDTV 0x0c 45 46 #define ASM9260_WDT_DEFAULT_TIMEOUT 30 47 48 enum asm9260_wdt_mode { 49 HW_RESET, 50 SW_RESET, 51 DEBUG, 52 }; 53 54 struct asm9260_wdt_priv { 55 struct device *dev; 56 struct watchdog_device wdd; 57 struct clk *clk; 58 struct clk *clk_ahb; 59 struct reset_control *rst; 60 61 void __iomem *iobase; 62 int irq; 63 unsigned long wdt_freq; 64 enum asm9260_wdt_mode mode; 65 }; 66 67 static int asm9260_wdt_feed(struct watchdog_device *wdd) 68 { 69 struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd); 70 71 iowrite32(0xaa, priv->iobase + HW_WDFEED); 72 iowrite32(0x55, priv->iobase + HW_WDFEED); 73 74 return 0; 75 } 76 77 static unsigned int asm9260_wdt_gettimeleft(struct watchdog_device *wdd) 78 { 79 struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd); 80 u32 counter; 81 82 counter = ioread32(priv->iobase + HW_WDTV); 83 84 return counter / priv->wdt_freq; 85 } 86 87 static int asm9260_wdt_updatetimeout(struct watchdog_device *wdd) 88 { 89 struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd); 90 u32 counter; 91 92 counter = wdd->timeout * priv->wdt_freq; 93 94 iowrite32(counter, priv->iobase + HW_WDTC); 95 96 return 0; 97 } 98 99 static int asm9260_wdt_enable(struct watchdog_device *wdd) 100 { 101 struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd); 102 u32 mode = 0; 103 104 if (priv->mode == HW_RESET) 105 mode = BM_MOD_WDRESET; 106 107 iowrite32(BM_MOD_WDEN | mode, priv->iobase + HW_WDMOD); 108 109 asm9260_wdt_updatetimeout(wdd); 110 111 asm9260_wdt_feed(wdd); 112 113 return 0; 114 } 115 116 static int asm9260_wdt_disable(struct watchdog_device *wdd) 117 { 118 struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd); 119 120 /* The only way to disable WD is to reset it. */ 121 reset_control_assert(priv->rst); 122 reset_control_deassert(priv->rst); 123 124 return 0; 125 } 126 127 static int asm9260_wdt_settimeout(struct watchdog_device *wdd, unsigned int to) 128 { 129 wdd->timeout = to; 130 asm9260_wdt_updatetimeout(wdd); 131 132 return 0; 133 } 134 135 static void asm9260_wdt_sys_reset(struct asm9260_wdt_priv *priv) 136 { 137 /* init WD if it was not started */ 138 139 iowrite32(BM_MOD_WDEN | BM_MOD_WDRESET, priv->iobase + HW_WDMOD); 140 141 iowrite32(0xff, priv->iobase + HW_WDTC); 142 /* first pass correct sequence */ 143 asm9260_wdt_feed(&priv->wdd); 144 /* 145 * Then write wrong pattern to the feed to trigger reset 146 * ASAP. 147 */ 148 iowrite32(0xff, priv->iobase + HW_WDFEED); 149 150 mdelay(1000); 151 } 152 153 static irqreturn_t asm9260_wdt_irq(int irq, void *devid) 154 { 155 struct asm9260_wdt_priv *priv = devid; 156 u32 stat; 157 158 stat = ioread32(priv->iobase + HW_WDMOD); 159 if (!(stat & BM_MOD_WDINT)) 160 return IRQ_NONE; 161 162 if (priv->mode == DEBUG) { 163 dev_info(priv->dev, "Watchdog Timeout. Do nothing.\n"); 164 } else { 165 dev_info(priv->dev, "Watchdog Timeout. Doing SW Reset.\n"); 166 asm9260_wdt_sys_reset(priv); 167 } 168 169 return IRQ_HANDLED; 170 } 171 172 static int asm9260_restart(struct watchdog_device *wdd, unsigned long action, 173 void *data) 174 { 175 struct asm9260_wdt_priv *priv = watchdog_get_drvdata(wdd); 176 177 asm9260_wdt_sys_reset(priv); 178 179 return 0; 180 } 181 182 static const struct watchdog_info asm9260_wdt_ident = { 183 .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING 184 | WDIOF_MAGICCLOSE, 185 .identity = "Alphascale asm9260 Watchdog", 186 }; 187 188 static const struct watchdog_ops asm9260_wdt_ops = { 189 .owner = THIS_MODULE, 190 .start = asm9260_wdt_enable, 191 .stop = asm9260_wdt_disable, 192 .get_timeleft = asm9260_wdt_gettimeleft, 193 .ping = asm9260_wdt_feed, 194 .set_timeout = asm9260_wdt_settimeout, 195 .restart = asm9260_restart, 196 }; 197 198 static void asm9260_clk_disable_unprepare(void *data) 199 { 200 clk_disable_unprepare(data); 201 } 202 203 static int asm9260_wdt_get_dt_clks(struct asm9260_wdt_priv *priv) 204 { 205 int err; 206 unsigned long clk; 207 208 priv->clk = devm_clk_get(priv->dev, "mod"); 209 if (IS_ERR(priv->clk)) { 210 dev_err(priv->dev, "Failed to get \"mod\" clk\n"); 211 return PTR_ERR(priv->clk); 212 } 213 214 /* configure AHB clock */ 215 priv->clk_ahb = devm_clk_get(priv->dev, "ahb"); 216 if (IS_ERR(priv->clk_ahb)) { 217 dev_err(priv->dev, "Failed to get \"ahb\" clk\n"); 218 return PTR_ERR(priv->clk_ahb); 219 } 220 221 err = clk_prepare_enable(priv->clk_ahb); 222 if (err) { 223 dev_err(priv->dev, "Failed to enable ahb_clk!\n"); 224 return err; 225 } 226 err = devm_add_action_or_reset(priv->dev, 227 asm9260_clk_disable_unprepare, 228 priv->clk_ahb); 229 if (err) 230 return err; 231 232 err = clk_set_rate(priv->clk, CLOCK_FREQ); 233 if (err) { 234 dev_err(priv->dev, "Failed to set rate!\n"); 235 return err; 236 } 237 238 err = clk_prepare_enable(priv->clk); 239 if (err) { 240 dev_err(priv->dev, "Failed to enable clk!\n"); 241 return err; 242 } 243 err = devm_add_action_or_reset(priv->dev, 244 asm9260_clk_disable_unprepare, 245 priv->clk); 246 if (err) 247 return err; 248 249 /* wdt has internal divider */ 250 clk = clk_get_rate(priv->clk); 251 if (!clk) { 252 dev_err(priv->dev, "Failed, clk is 0!\n"); 253 return -EINVAL; 254 } 255 256 priv->wdt_freq = clk / 2; 257 258 return 0; 259 } 260 261 static void asm9260_wdt_get_dt_mode(struct asm9260_wdt_priv *priv) 262 { 263 const char *tmp; 264 int ret; 265 266 /* default mode */ 267 priv->mode = HW_RESET; 268 269 ret = of_property_read_string(priv->dev->of_node, 270 "alphascale,mode", &tmp); 271 if (ret < 0) 272 return; 273 274 if (!strcmp(tmp, "hw")) 275 priv->mode = HW_RESET; 276 else if (!strcmp(tmp, "sw")) 277 priv->mode = SW_RESET; 278 else if (!strcmp(tmp, "debug")) 279 priv->mode = DEBUG; 280 else 281 dev_warn(priv->dev, "unknown reset-type: %s. Using default \"hw\" mode.", 282 tmp); 283 } 284 285 static int asm9260_wdt_probe(struct platform_device *pdev) 286 { 287 struct device *dev = &pdev->dev; 288 struct asm9260_wdt_priv *priv; 289 struct watchdog_device *wdd; 290 int ret; 291 static const char * const mode_name[] = { "hw", "sw", "debug", }; 292 293 priv = devm_kzalloc(dev, sizeof(struct asm9260_wdt_priv), GFP_KERNEL); 294 if (!priv) 295 return -ENOMEM; 296 297 priv->dev = dev; 298 299 priv->iobase = devm_platform_ioremap_resource(pdev, 0); 300 if (IS_ERR(priv->iobase)) 301 return PTR_ERR(priv->iobase); 302 303 priv->rst = devm_reset_control_get_exclusive(dev, "wdt_rst"); 304 if (IS_ERR(priv->rst)) 305 return PTR_ERR(priv->rst); 306 307 ret = asm9260_wdt_get_dt_clks(priv); 308 if (ret) 309 return ret; 310 311 wdd = &priv->wdd; 312 wdd->info = &asm9260_wdt_ident; 313 wdd->ops = &asm9260_wdt_ops; 314 wdd->min_timeout = 1; 315 wdd->max_timeout = BM_WDTC_MAX(priv->wdt_freq); 316 wdd->parent = dev; 317 318 watchdog_set_drvdata(wdd, priv); 319 320 /* 321 * If 'timeout-sec' unspecified in devicetree, assume a 30 second 322 * default, unless the max timeout is less than 30 seconds, then use 323 * the max instead. 324 */ 325 wdd->timeout = ASM9260_WDT_DEFAULT_TIMEOUT; 326 watchdog_init_timeout(wdd, 0, dev); 327 328 asm9260_wdt_get_dt_mode(priv); 329 330 if (priv->mode != HW_RESET) 331 priv->irq = platform_get_irq(pdev, 0); 332 333 if (priv->irq > 0) { 334 /* 335 * Not all supported platforms specify an interrupt for the 336 * watchdog, so let's make it optional. 337 */ 338 ret = devm_request_irq(dev, priv->irq, asm9260_wdt_irq, 0, 339 pdev->name, priv); 340 if (ret < 0) 341 dev_warn(dev, "failed to request IRQ\n"); 342 } 343 344 watchdog_set_restart_priority(wdd, 128); 345 346 watchdog_stop_on_reboot(wdd); 347 watchdog_stop_on_unregister(wdd); 348 ret = devm_watchdog_register_device(dev, wdd); 349 if (ret) 350 return ret; 351 352 platform_set_drvdata(pdev, priv); 353 354 dev_info(dev, "Watchdog enabled (timeout: %d sec, mode: %s)\n", 355 wdd->timeout, mode_name[priv->mode]); 356 return 0; 357 } 358 359 static const struct of_device_id asm9260_wdt_of_match[] = { 360 { .compatible = "alphascale,asm9260-wdt"}, 361 {}, 362 }; 363 MODULE_DEVICE_TABLE(of, asm9260_wdt_of_match); 364 365 static struct platform_driver asm9260_wdt_driver = { 366 .driver = { 367 .name = "asm9260-wdt", 368 .of_match_table = asm9260_wdt_of_match, 369 }, 370 .probe = asm9260_wdt_probe, 371 }; 372 module_platform_driver(asm9260_wdt_driver); 373 374 MODULE_DESCRIPTION("asm9260 WatchDog Timer Driver"); 375 MODULE_AUTHOR("Oleksij Rempel <linux@rempel-privat.de>"); 376 MODULE_LICENSE("GPL"); 377