// 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);