// SPDX-License-Identifier: GPL-2.0 /* * Renesas RZ/N1 Watchdog timer. * This is a 12-bit timer driver from a (62.5/16384) MHz clock. It can't even * cope with 2 seconds. * * Copyright 2018 Renesas Electronics Europe Ltd. * * Derived from Ralink RT288x watchdog timer. */ #include #include #include #include #include #include #include #include #include #define DEFAULT_TIMEOUT 60 #define RZN1_WDT_RETRIGGER 0x0 #define RZN1_WDT_RETRIGGER_RELOAD_VAL 0 #define RZN1_WDT_RETRIGGER_RELOAD_VAL_MASK 0xfff #define RZN1_WDT_RETRIGGER_PRESCALE BIT(12) #define RZN1_WDT_RETRIGGER_ENABLE BIT(13) #define RZN1_WDT_RETRIGGER_WDSI (0x2 << 14) #define RZN1_WDT_PRESCALER 16384 #define RZN1_WDT_MAX 4095 struct rzn1_watchdog { struct watchdog_device wdtdev; void __iomem *base; unsigned long clk_rate_khz; }; static inline uint32_t max_heart_beat_ms(unsigned long clk_rate_khz) { return (RZN1_WDT_MAX * RZN1_WDT_PRESCALER) / clk_rate_khz; } static inline uint32_t compute_reload_value(uint32_t tick_ms, unsigned long clk_rate_khz) { return (tick_ms * clk_rate_khz) / RZN1_WDT_PRESCALER; } static int rzn1_wdt_ping(struct watchdog_device *w) { struct rzn1_watchdog *wdt = watchdog_get_drvdata(w); /* Any value retrigggers the watchdog */ writel(0, wdt->base + RZN1_WDT_RETRIGGER); return 0; } static int rzn1_wdt_start(struct watchdog_device *w) { struct rzn1_watchdog *wdt = watchdog_get_drvdata(w); u32 val; /* * The hardware allows you to write to this reg only once. * Since this includes the reload value, there is no way to change the * timeout once started. Also note that the WDT clock is half the bus * fabric clock rate, so if the bus fabric clock rate is changed after * the WDT is started, the WDT interval will be wrong. */ val = RZN1_WDT_RETRIGGER_WDSI; val |= RZN1_WDT_RETRIGGER_ENABLE; val |= RZN1_WDT_RETRIGGER_PRESCALE; val |= compute_reload_value(w->max_hw_heartbeat_ms, wdt->clk_rate_khz); writel(val, wdt->base + RZN1_WDT_RETRIGGER); return 0; } static irqreturn_t rzn1_wdt_irq(int irq, void *_wdt) { pr_crit("RZN1 Watchdog. Initiating system reboot\n"); emergency_restart(); return IRQ_HANDLED; } static struct watchdog_info rzn1_wdt_info = { .identity = "RZ/N1 Watchdog", .options = WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING, }; static const struct watchdog_ops rzn1_wdt_ops = { .owner = THIS_MODULE, .start = rzn1_wdt_start, .ping = rzn1_wdt_ping, }; static int rzn1_wdt_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct rzn1_watchdog *wdt; struct device_node *np = dev->of_node; struct clk *clk; unsigned long clk_rate; int ret; int irq; wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL); if (!wdt) return -ENOMEM; wdt->base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(wdt->base)) return PTR_ERR(wdt->base); irq = platform_get_irq(pdev, 0); if (irq < 0) return irq; ret = devm_request_irq(dev, irq, rzn1_wdt_irq, 0, np->name, wdt); if (ret) { dev_err(dev, "failed to request irq %d\n", irq); return ret; } clk = devm_clk_get_enabled(dev, NULL); if (IS_ERR(clk)) { dev_err(dev, "failed to get the clock\n"); return PTR_ERR(clk); } clk_rate = clk_get_rate(clk); if (!clk_rate) { dev_err(dev, "failed to get the clock rate\n"); return -EINVAL; } wdt->clk_rate_khz = clk_rate / 1000; wdt->wdtdev.info = &rzn1_wdt_info, wdt->wdtdev.ops = &rzn1_wdt_ops, wdt->wdtdev.status = WATCHDOG_NOWAYOUT_INIT_STATUS, wdt->wdtdev.parent = dev; /* * The period of the watchdog cannot be changed once set * and is limited to a very short period. * Configure it for a 1s period once and for all, and * rely on the heart-beat provided by the watchdog core * to make this usable by the user-space. */ wdt->wdtdev.max_hw_heartbeat_ms = max_heart_beat_ms(wdt->clk_rate_khz); if (wdt->wdtdev.max_hw_heartbeat_ms > 1000) wdt->wdtdev.max_hw_heartbeat_ms = 1000; wdt->wdtdev.timeout = DEFAULT_TIMEOUT; ret = watchdog_init_timeout(&wdt->wdtdev, 0, dev); if (ret) return ret; watchdog_set_drvdata(&wdt->wdtdev, wdt); return devm_watchdog_register_device(dev, &wdt->wdtdev); } static const struct of_device_id rzn1_wdt_match[] = { { .compatible = "renesas,rzn1-wdt" }, {}, }; MODULE_DEVICE_TABLE(of, rzn1_wdt_match); static struct platform_driver rzn1_wdt_driver = { .probe = rzn1_wdt_probe, .driver = { .name = KBUILD_MODNAME, .of_match_table = rzn1_wdt_match, }, }; module_platform_driver(rzn1_wdt_driver); MODULE_DESCRIPTION("Renesas RZ/N1 hardware watchdog"); MODULE_AUTHOR("Phil Edworthy "); MODULE_LICENSE("GPL");