Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 | // SPDX-License-Identifier: GPL-2.0-only /* * Imagination Technologies PowerDown Controller Watchdog Timer. * * Copyright (c) 2014 Imagination Technologies Ltd. * * Based on drivers/watchdog/sunxi_wdt.c Copyright (c) 2013 Carlo Caione * 2012 Henrik Nordstrom * * Notes * ----- * The timeout value is rounded to the next power of two clock cycles. * This is configured using the PDC_WDT_CONFIG register, according to this * formula: * * timeout = 2^(delay + 1) clock cycles * * Where 'delay' is the value written in PDC_WDT_CONFIG register. * * Therefore, the hardware only allows to program watchdog timeouts, expressed * as a power of two number of watchdog clock cycles. The current implementation * guarantees that the actual watchdog timeout will be _at least_ the value * programmed in the imgpdg_wdt driver. * * The following table shows how the user-configured timeout relates * to the actual hardware timeout (watchdog clock @ 40000 Hz): * * input timeout | WD_DELAY | actual timeout * ----------------------------------- * 10 | 18 | 13 seconds * 20 | 19 | 26 seconds * 30 | 20 | 52 seconds * 60 | 21 | 104 seconds * * Albeit coarse, this granularity would suffice most watchdog uses. * If the platform allows it, the user should be able to change the watchdog * clock rate and achieve a finer timeout granularity. */ #include <linux/clk.h> #include <linux/io.h> #include <linux/log2.h> #include <linux/module.h> #include <linux/mod_devicetable.h> #include <linux/platform_device.h> #include <linux/slab.h> #include <linux/watchdog.h> /* registers */ #define PDC_WDT_SOFT_RESET 0x00 #define PDC_WDT_CONFIG 0x04 #define PDC_WDT_CONFIG_ENABLE BIT(31) #define PDC_WDT_CONFIG_DELAY_MASK 0x1f #define PDC_WDT_TICKLE1 0x08 #define PDC_WDT_TICKLE1_MAGIC 0xabcd1234 #define PDC_WDT_TICKLE2 0x0c #define PDC_WDT_TICKLE2_MAGIC 0x4321dcba #define PDC_WDT_TICKLE_STATUS_MASK 0x7 #define PDC_WDT_TICKLE_STATUS_SHIFT 0 #define PDC_WDT_TICKLE_STATUS_HRESET 0x0 /* Hard reset */ #define PDC_WDT_TICKLE_STATUS_TIMEOUT 0x1 /* Timeout */ #define PDC_WDT_TICKLE_STATUS_TICKLE 0x2 /* Tickled incorrectly */ #define PDC_WDT_TICKLE_STATUS_SRESET 0x3 /* Soft reset */ #define PDC_WDT_TICKLE_STATUS_USER 0x4 /* User reset */ /* Timeout values are in seconds */ #define PDC_WDT_MIN_TIMEOUT 1 #define PDC_WDT_DEF_TIMEOUT 64 static int heartbeat; module_param(heartbeat, int, 0); MODULE_PARM_DESC(heartbeat, "Watchdog heartbeats in seconds " "(default=" __MODULE_STRING(PDC_WDT_DEF_TIMEOUT) ")"); static bool nowayout = WATCHDOG_NOWAYOUT; module_param(nowayout, bool, 0); MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started " "(default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); struct pdc_wdt_dev { struct watchdog_device wdt_dev; struct clk *wdt_clk; struct clk *sys_clk; void __iomem *base; }; static int pdc_wdt_keepalive(struct watchdog_device *wdt_dev) { struct pdc_wdt_dev *wdt = watchdog_get_drvdata(wdt_dev); writel(PDC_WDT_TICKLE1_MAGIC, wdt->base + PDC_WDT_TICKLE1); writel(PDC_WDT_TICKLE2_MAGIC, wdt->base + PDC_WDT_TICKLE2); return 0; } static int pdc_wdt_stop(struct watchdog_device *wdt_dev) { unsigned int val; struct pdc_wdt_dev *wdt = watchdog_get_drvdata(wdt_dev); val = readl(wdt->base + PDC_WDT_CONFIG); val &= ~PDC_WDT_CONFIG_ENABLE; writel(val, wdt->base + PDC_WDT_CONFIG); /* Must tickle to finish the stop */ pdc_wdt_keepalive(wdt_dev); return 0; } static void __pdc_wdt_set_timeout(struct pdc_wdt_dev *wdt) { unsigned long clk_rate = clk_get_rate(wdt->wdt_clk); unsigned int val; val = readl(wdt->base + PDC_WDT_CONFIG) & ~PDC_WDT_CONFIG_DELAY_MASK; val |= order_base_2(wdt->wdt_dev.timeout * clk_rate) - 1; writel(val, wdt->base + PDC_WDT_CONFIG); } static int pdc_wdt_set_timeout(struct watchdog_device *wdt_dev, unsigned int new_timeout) { struct pdc_wdt_dev *wdt = watchdog_get_drvdata(wdt_dev); wdt->wdt_dev.timeout = new_timeout; __pdc_wdt_set_timeout(wdt); return 0; } /* Start the watchdog timer (delay should already be set) */ static int pdc_wdt_start(struct watchdog_device *wdt_dev) { unsigned int val; struct pdc_wdt_dev *wdt = watchdog_get_drvdata(wdt_dev); __pdc_wdt_set_timeout(wdt); val = readl(wdt->base + PDC_WDT_CONFIG); val |= PDC_WDT_CONFIG_ENABLE; writel(val, wdt->base + PDC_WDT_CONFIG); return 0; } static int pdc_wdt_restart(struct watchdog_device *wdt_dev, unsigned long action, void *data) { struct pdc_wdt_dev *wdt = watchdog_get_drvdata(wdt_dev); /* Assert SOFT_RESET */ writel(0x1, wdt->base + PDC_WDT_SOFT_RESET); return 0; } static const struct watchdog_info pdc_wdt_info = { .identity = "IMG PDC Watchdog", .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, }; static const struct watchdog_ops pdc_wdt_ops = { .owner = THIS_MODULE, .start = pdc_wdt_start, .stop = pdc_wdt_stop, .ping = pdc_wdt_keepalive, .set_timeout = pdc_wdt_set_timeout, .restart = pdc_wdt_restart, }; static void pdc_clk_disable_unprepare(void *data) { clk_disable_unprepare(data); } static int pdc_wdt_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; u64 div; int ret, val; unsigned long clk_rate; struct pdc_wdt_dev *pdc_wdt; pdc_wdt = devm_kzalloc(dev, sizeof(*pdc_wdt), GFP_KERNEL); if (!pdc_wdt) return -ENOMEM; pdc_wdt->base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(pdc_wdt->base)) return PTR_ERR(pdc_wdt->base); pdc_wdt->sys_clk = devm_clk_get(dev, "sys"); if (IS_ERR(pdc_wdt->sys_clk)) { dev_err(dev, "failed to get the sys clock\n"); return PTR_ERR(pdc_wdt->sys_clk); } pdc_wdt->wdt_clk = devm_clk_get(dev, "wdt"); if (IS_ERR(pdc_wdt->wdt_clk)) { dev_err(dev, "failed to get the wdt clock\n"); return PTR_ERR(pdc_wdt->wdt_clk); } ret = clk_prepare_enable(pdc_wdt->sys_clk); if (ret) { dev_err(dev, "could not prepare or enable sys clock\n"); return ret; } ret = devm_add_action_or_reset(dev, pdc_clk_disable_unprepare, pdc_wdt->sys_clk); if (ret) return ret; ret = clk_prepare_enable(pdc_wdt->wdt_clk); if (ret) { dev_err(dev, "could not prepare or enable wdt clock\n"); return ret; } ret = devm_add_action_or_reset(dev, pdc_clk_disable_unprepare, pdc_wdt->wdt_clk); if (ret) return ret; /* We use the clock rate to calculate the max timeout */ clk_rate = clk_get_rate(pdc_wdt->wdt_clk); if (clk_rate == 0) { dev_err(dev, "failed to get clock rate\n"); return -EINVAL; } if (order_base_2(clk_rate) > PDC_WDT_CONFIG_DELAY_MASK + 1) { dev_err(dev, "invalid clock rate\n"); return -EINVAL; } if (order_base_2(clk_rate) == 0) pdc_wdt->wdt_dev.min_timeout = PDC_WDT_MIN_TIMEOUT + 1; else pdc_wdt->wdt_dev.min_timeout = PDC_WDT_MIN_TIMEOUT; pdc_wdt->wdt_dev.info = &pdc_wdt_info; pdc_wdt->wdt_dev.ops = &pdc_wdt_ops; div = 1ULL << (PDC_WDT_CONFIG_DELAY_MASK + 1); do_div(div, clk_rate); pdc_wdt->wdt_dev.max_timeout = div; pdc_wdt->wdt_dev.timeout = PDC_WDT_DEF_TIMEOUT; pdc_wdt->wdt_dev.parent = dev; watchdog_set_drvdata(&pdc_wdt->wdt_dev, pdc_wdt); watchdog_init_timeout(&pdc_wdt->wdt_dev, heartbeat, dev); pdc_wdt_stop(&pdc_wdt->wdt_dev); /* Find what caused the last reset */ val = readl(pdc_wdt->base + PDC_WDT_TICKLE1); val = (val & PDC_WDT_TICKLE_STATUS_MASK) >> PDC_WDT_TICKLE_STATUS_SHIFT; switch (val) { case PDC_WDT_TICKLE_STATUS_TICKLE: case PDC_WDT_TICKLE_STATUS_TIMEOUT: pdc_wdt->wdt_dev.bootstatus |= WDIOF_CARDRESET; dev_info(dev, "watchdog module last reset due to timeout\n"); break; case PDC_WDT_TICKLE_STATUS_HRESET: dev_info(dev, "watchdog module last reset due to hard reset\n"); break; case PDC_WDT_TICKLE_STATUS_SRESET: dev_info(dev, "watchdog module last reset due to soft reset\n"); break; case PDC_WDT_TICKLE_STATUS_USER: dev_info(dev, "watchdog module last reset due to user reset\n"); break; default: dev_info(dev, "contains an illegal status code (%08x)\n", val); break; } watchdog_set_nowayout(&pdc_wdt->wdt_dev, nowayout); watchdog_set_restart_priority(&pdc_wdt->wdt_dev, 128); platform_set_drvdata(pdev, pdc_wdt); watchdog_stop_on_reboot(&pdc_wdt->wdt_dev); watchdog_stop_on_unregister(&pdc_wdt->wdt_dev); return devm_watchdog_register_device(dev, &pdc_wdt->wdt_dev); } static const struct of_device_id pdc_wdt_match[] = { { .compatible = "img,pdc-wdt" }, {} }; MODULE_DEVICE_TABLE(of, pdc_wdt_match); static struct platform_driver pdc_wdt_driver = { .driver = { .name = "imgpdc-wdt", .of_match_table = pdc_wdt_match, }, .probe = pdc_wdt_probe, }; module_platform_driver(pdc_wdt_driver); MODULE_AUTHOR("Jude Abraham <Jude.Abraham@imgtec.com>"); MODULE_AUTHOR("Naidu Tellapati <Naidu.Tellapati@imgtec.com>"); MODULE_DESCRIPTION("Imagination Technologies PDC Watchdog Timer Driver"); MODULE_LICENSE("GPL v2"); |