From f0ec6f69555f44c3861723b1aef0ce214c7cccc0 Mon Sep 17 00:00:00 2001 From: Mohamad Noor Alim Hussin Date: Thu, 21 Aug 2025 09:47:51 +0800 Subject: [PATCH] drivers: add efinix watchdog driver --- drivers/watchdog/Kconfig | 9 ++ drivers/watchdog/Makefile | 1 + drivers/watchdog/efx_wdt.c | 298 +++++++++++++++++++++++++++++++++++++ 3 files changed, 308 insertions(+) create mode 100644 drivers/watchdog/efx_wdt.c diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 01ce3f41c..152c191e7 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -351,6 +351,15 @@ config SL28CPLD_WATCHDOG To compile this driver as a module, choose M here: the module will be called sl28cpld_wdt. +config EFINIX_WATCHDOG + tristate "Efinix Watchdog" + select WATCHDOG_CORE + help + Say Y here to include suport for the watchdog timer on Efinix boards. + + To compile this driver as a module, choose M here: the module will be + called efx_wdt. + # ALPHA Architecture # ARM Architecture diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index 071a2e50b..8e8dba19d 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -227,3 +227,4 @@ obj-$(CONFIG_MENZ069_WATCHDOG) += menz69_wdt.o obj-$(CONFIG_RAVE_SP_WATCHDOG) += rave-sp-wdt.o obj-$(CONFIG_STPMIC1_WATCHDOG) += stpmic1_wdt.o obj-$(CONFIG_SL28CPLD_WATCHDOG) += sl28cpld_wdt.o +obj-$(CONFIG_EFINIX_WATCHDOG) += efx_wdt.o diff --git a/drivers/watchdog/efx_wdt.c b/drivers/watchdog/efx_wdt.c new file mode 100644 index 000000000..75e111821 --- /dev/null +++ b/drivers/watchdog/efx_wdt.c @@ -0,0 +1,298 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Support for watchdog on Efinix Inc SoC + * + * Copyright (C) 2025 Efinix Inc + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRIVER_NAME "efx-watchdog" +#define EFX_WDT_DEFAULT_TIME 60 /* in seconds */ +#define EFX_WDT_MAX_TIME 65U /* in seconds */ +#define EFX_WDT_RESET 0xAD68E70D /* Magic number to reset the watchdog */ +#define EFX_WDT_UNLOCK 0x3C21B925 /* Magic number to unlock the watchdog */ +#define EFX_WDT_LOCK 0x3C21B924 /* Magic number to lock the watchdog */ +#define EFX_WDT_ENA_COUNTER_0 (1 << 0) +#define EFX_WDT_ENA_COUNTER_1 (1 << 1) + +#define EFX_WDT_HEARTBEAT 0x00 +#define EFX_WDT_ENABLE 0x04 +#define EFX_WDT_DISABLED 0x08 +#define EFX_WDT_PRESCALER 0x40 +#define EFX_WDT_COUNTER_0 0x80 +#define EFX_WDT_COUNTER_1 0x84 +#define EFX_WDT_COUNTER_0_VAL 0xc0 +#define EFX_WDT_COUNTER_1_VAL 0xc4 + +struct efx_wdt_dev { + void __iomem *base; + struct watchdog_device wdog; + struct clk *clk; +}; + +static unsigned timeout; +module_param(timeout, uint, 0); +MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds (default=" + __MODULE_STRING(EFX_WDT_DEFAULT_TIME) ")"); + +static bool nowayout = WATCHDOG_NOWAYOUT; +module_param(nowayout, bool, 0); +MODULE_PARM_DESC(nowayout, "Watchdog cannot bbe sttopped once started (default=" + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); + +static const struct watchdog_info efx_wdt_info = { + .identity = "efx-watchdog", + .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE, +}; + +static const struct watchdog_info efx_wdt_pretimeout_info = { + .identity = "efx-watchdog-pretimeout", + .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE | + WDIOF_PRETIMEOUT, +}; + +static void efx_wdt_unlock(struct efx_wdt_dev *wdev) +{ + writel(EFX_WDT_UNLOCK, wdev->base + EFX_WDT_HEARTBEAT); +} + +static void efx_wdt_lock(struct efx_wdt_dev *wdev) +{ + writel(EFX_WDT_LOCK, wdev->base + EFX_WDT_HEARTBEAT); +} + +static void efx_wdt_set_counter_limit(struct efx_wdt_dev *wdev, unsigned int limit, int counter) +{ + unsigned int value; + int counter_reg; + + if (counter == 0) + counter_reg = EFX_WDT_COUNTER_0; + else if (counter == 1) + counter_reg = EFX_WDT_COUNTER_1; + + value = (limit * 1000) + 1; + efx_wdt_unlock(wdev); + writel(value, wdev->base + counter_reg); + efx_wdt_lock(wdev); +} + +static int efx_wdt_start(struct watchdog_device *wdog) +{ + struct efx_wdt_dev *wdev = (struct efx_wdt_dev *)watchdog_get_drvdata(wdog); + + efx_wdt_unlock(wdev); + writel(EFX_WDT_ENA_COUNTER_0 | EFX_WDT_ENA_COUNTER_1 , wdev->base + EFX_WDT_ENABLE); + efx_wdt_lock(wdev); + set_bit(WDOG_HW_RUNNING | WDOG_ACTIVE, &wdog->status); + return 0; +} + +static int efx_wdt_ping(struct watchdog_device *wdog) +{ + struct efx_wdt_dev *wdev = (struct efx_wdt_dev *)watchdog_get_drvdata(wdog); + + writel(EFX_WDT_RESET, wdev->base + EFX_WDT_HEARTBEAT); + return 0; +} + +static int efx_wdt_stop(struct watchdog_device *wdog) +{ + struct efx_wdt_dev *wdev = (struct efx_wdt_dev *)watchdog_get_drvdata(wdog); + + efx_wdt_unlock(wdev); + writel(EFX_WDT_ENA_COUNTER_0 | EFX_WDT_ENA_COUNTER_1, wdev->base + EFX_WDT_DISABLED); + efx_wdt_lock(wdev); + set_bit(WDOG_STOP_ON_UNREGISTER, &wdog->status); + return 0; +} + +static int efx_wdt_set_pretimeout(struct watchdog_device *wdog, unsigned int pretimeout) +{ + struct efx_wdt_dev *wdev = (struct efx_wdt_dev *)watchdog_get_drvdata(wdog); + unsigned int actual; + + actual = min(pretimeout, wdog->timeout); + wdog->pretimeout = actual; + efx_wdt_set_counter_limit(wdev, wdog->pretimeout, 0); + return 0; +} + +static int efx_wdt_set_timeout(struct watchdog_device *wdog, unsigned int new_timeout) +{ + struct efx_wdt_dev *wdev = (struct efx_wdt_dev *)watchdog_get_drvdata(wdog); + unsigned int actual; + + actual = min(new_timeout, EFX_WDT_MAX_TIME); + wdog->timeout = actual; + efx_wdt_set_counter_limit(wdev, wdog->timeout, 1); + efx_wdt_set_pretimeout(wdog, wdog->timeout / 2); + return 0; +} + +static unsigned int efx_wdt_get_timeleft(struct watchdog_device *wdog) +{ + struct efx_wdt_dev *wdev = (struct efx_wdt_dev *)watchdog_get_drvdata(wdog); + unsigned int value; + + value = readl(wdev->base + EFX_WDT_COUNTER_1_VAL); + + return (wdog->timeout * 1000) - value; +} + +static int efx_wdt_restart(struct watchdog_device *wdog, unsigned long action, + void *data) +{ + struct efx_wdt_dev *wdev = (struct efx_wdt_dev *)watchdog_get_drvdata(wdog); + efx_wdt_stop(wdog); + efx_wdt_ping(wdog); + efx_wdt_set_counter_limit(wdev, wdog->min_timeout, 1); + efx_wdt_unlock(wdev); + writel(EFX_WDT_ENA_COUNTER_1, wdev->base + EFX_WDT_ENABLE); + efx_wdt_lock(wdev); + return 0; +} + +static irqreturn_t efx_wdt_isr(int irq, void *dev) +{ + struct watchdog_device *wdog = (struct watchdog_device *)dev; + struct efx_wdt_dev *wdev = (struct efx_wdt_dev *)watchdog_get_drvdata(wdog); + + writel(~(EFX_WDT_ENA_COUNTER_0), wdev->base + EFX_WDT_ENABLE); + watchdog_notify_pretimeout(wdog); + + return IRQ_HANDLED; +} + +static const struct watchdog_ops efx_wdt_ops = { + .start = efx_wdt_start, + .stop = efx_wdt_stop, + .ping = efx_wdt_ping, + .set_timeout = efx_wdt_set_timeout, + .set_pretimeout = efx_wdt_set_pretimeout, + .get_timeleft = efx_wdt_get_timeleft, + .restart = efx_wdt_restart, +}; + +static int __init efx_wdt_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct efx_wdt_dev *wdev; + struct watchdog_device *wdog; + int irq, ret; + unsigned int clk_rate, prescaler; + + wdev = devm_kzalloc(dev, sizeof(*wdev), GFP_KERNEL); + if (!wdev) + return -ENOMEM; + + wdev->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(wdev->base)) + return PTR_ERR(wdev->base); + + wdev->clk = devm_clk_get(dev, NULL); + if (IS_ERR(wdev->clk)) { + dev_err(dev, "can't get watchdog clock\n"); + return PTR_ERR(wdev->clk); + } + + wdog = &wdev->wdog; + wdog->info = &efx_wdt_info; + wdog->ops = &efx_wdt_ops; + wdog->min_timeout = 1; + wdog->timeout = EFX_WDT_DEFAULT_TIME; + wdog->max_hw_heartbeat_ms = EFX_WDT_MAX_TIME * 1000; + wdog->parent = dev; + + /* Set the prescaler */ + clk_rate = clk_get_rate(wdev->clk); + if (!clk_rate) { + dev_err(dev, "Missing 'clock-frequency' for watchdog in device tree.\n"); + return -EINVAL; + } + + if (clk_rate == 0) { + dev_err(dev, "'clock-frequency' for watchdog cannot be 0\n"); + return -EINVAL; + } + + prescaler = (clk_rate / 1000) - 1; + writel(prescaler, wdev->base + EFX_WDT_PRESCALER); + + irq = platform_get_irq(pdev, 0); + if (irq > 0) { + ret = devm_request_irq(&pdev->dev, irq, efx_wdt_isr, 0, DRIVER_NAME, wdog); + if (ret) + return ret; + + wdog->info = &efx_wdt_pretimeout_info; + } + + platform_set_drvdata(pdev, wdog); + watchdog_set_drvdata(wdog, wdev); + watchdog_set_nowayout(wdog, nowayout); + watchdog_set_restart_priority(wdog, 128); + watchdog_init_timeout(wdog, timeout, dev); + + efx_wdt_set_timeout(wdog, wdog->timeout); + efx_wdt_set_pretimeout(wdog, wdog->timeout / 2); + + efx_wdt_stop(wdog); + + ret = devm_watchdog_register_device(dev, wdog); + if (ret) { + dev_err(dev, "Failed to register watchdog driver\n"); + return ret; + } + + dev_info(dev, "Successfully register watchdog driver\n"); + + efx_wdt_lock(wdev); + + return 0; +} + +static void efx_wdt_shutdown(struct platform_device *pdev) +{ + struct watchdog_device *wdog = (struct watchdog_device *)platform_get_drvdata(pdev); + struct efx_wdt_dev *wdev = (struct efx_wdt_dev *)watchdog_get_drvdata(wdog); + efx_wdt_stop(wdog); + efx_wdt_ping(wdog); + efx_wdt_set_counter_limit(wdev, wdog->min_timeout, 1); + efx_wdt_unlock(wdev); + writel(EFX_WDT_ENA_COUNTER_1, wdev->base + EFX_WDT_ENABLE); + efx_wdt_lock(wdev); +} + +static const struct of_device_id efx_wdt_ids[] = { + { .compatible = "efx,efx-wdt" }, + { } +}; +MODULE_DEVICE_TABLE(of, efx_wdt_ids); + +static struct platform_driver efx_wdt_driver = { + .shutdown = efx_wdt_shutdown, + .driver = { + .name = DRIVER_NAME, + .of_match_table = efx_wdt_ids, + } +}; + +module_platform_driver_probe(efx_wdt_driver, efx_wdt_probe); + +MODULE_AUTHOR("Mohamad Noor Alim Hussin "); +MODULE_DESCRIPTION("Watchdog driver for Efinix SoC"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" DRIVER_NAME);