| // 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 <linux/clk.h> | 
 | #include <linux/interrupt.h> | 
 | #include <linux/kernel.h> | 
 | #include <linux/module.h> | 
 | #include <linux/of_address.h> | 
 | #include <linux/of_irq.h> | 
 | #include <linux/platform_device.h> | 
 | #include <linux/reboot.h> | 
 | #include <linux/watchdog.h> | 
 |  | 
 | #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 <phil.edworthy@renesas.com>"); | 
 | MODULE_LICENSE("GPL"); |