377 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*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 100000020 21/* Watchdog Mode register */22#define HW_WDMOD 0x0023/* 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 register34 * Minimal value is 0xff, the meaning of this value35 * depends on used clock: T = WDCLK * (0xff + 1) * 436 */37#define HW_WDTC 0x0438#define BM_WDTC_MAX(freq) (0x7fffffff / (freq))39 40/* Watchdog Feed register */41#define HW_WDFEED 0x0842 43/* Watchdog Timer Value register */44#define HW_WDTV 0x0c45 46#define ASM9260_WDT_DEFAULT_TIMEOUT 3047 48enum asm9260_wdt_mode {49 HW_RESET,50 SW_RESET,51 DEBUG,52};53 54struct 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 67static 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 77static 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 87static 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 99static 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 116static 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 127static 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 135static 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 reset146 * ASAP.147 */148 iowrite32(0xff, priv->iobase + HW_WDFEED);149 150 mdelay(1000);151}152 153static 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 172static 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 182static const struct watchdog_info asm9260_wdt_ident = {183 .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING184 | WDIOF_MAGICCLOSE,185 .identity = "Alphascale asm9260 Watchdog",186};187 188static 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 198static void asm9260_clk_disable_unprepare(void *data)199{200 clk_disable_unprepare(data);201}202 203static 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 261static 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 else281 dev_warn(priv->dev, "unknown reset-type: %s. Using default \"hw\" mode.",282 tmp);283}284 285static 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 second322 * default, unless the max timeout is less than 30 seconds, then use323 * 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 the336 * 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 359static const struct of_device_id asm9260_wdt_of_match[] = {360 { .compatible = "alphascale,asm9260-wdt"},361 {},362};363MODULE_DEVICE_TABLE(of, asm9260_wdt_of_match);364 365static 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};372module_platform_driver(asm9260_wdt_driver);373 374MODULE_DESCRIPTION("asm9260 WatchDog Timer Driver");375MODULE_AUTHOR("Oleksij Rempel <linux@rempel-privat.de>");376MODULE_LICENSE("GPL");377