// SPDX-License-Identifier: GPL-2.0-only /* * drivers/char/watchdog/ixp4xx_wdt.c * * Watchdog driver for Intel IXP4xx network processors * * Author: Deepak Saxena * Author: Linus Walleij * * Copyright 2004 (c) MontaVista, Software, Inc. * Based on sa1100 driver, Copyright (C) 2000 Oleg Drokin */ #include #include #include #include #include #include #include #include struct ixp4xx_wdt { struct watchdog_device wdd; void __iomem *base; unsigned long rate; }; /* Fallback if we do not have a clock for this */ #define IXP4XX_TIMER_FREQ 66666000 /* Registers after the timer registers */ #define IXP4XX_OSWT_OFFSET 0x14 /* Watchdog Timer */ #define IXP4XX_OSWE_OFFSET 0x18 /* Watchdog Enable */ #define IXP4XX_OSWK_OFFSET 0x1C /* Watchdog Key */ #define IXP4XX_OSST_OFFSET 0x20 /* Timer Status */ #define IXP4XX_OSST_TIMER_WDOG_PEND 0x00000008 #define IXP4XX_OSST_TIMER_WARM_RESET 0x00000010 #define IXP4XX_WDT_KEY 0x0000482E #define IXP4XX_WDT_RESET_ENABLE 0x00000001 #define IXP4XX_WDT_IRQ_ENABLE 0x00000002 #define IXP4XX_WDT_COUNT_ENABLE 0x00000004 static inline struct ixp4xx_wdt *to_ixp4xx_wdt(struct watchdog_device *wdd) { return container_of(wdd, struct ixp4xx_wdt, wdd); } static int ixp4xx_wdt_start(struct watchdog_device *wdd) { struct ixp4xx_wdt *iwdt = to_ixp4xx_wdt(wdd); __raw_writel(IXP4XX_WDT_KEY, iwdt->base + IXP4XX_OSWK_OFFSET); __raw_writel(0, iwdt->base + IXP4XX_OSWE_OFFSET); __raw_writel(wdd->timeout * iwdt->rate, iwdt->base + IXP4XX_OSWT_OFFSET); __raw_writel(IXP4XX_WDT_COUNT_ENABLE | IXP4XX_WDT_RESET_ENABLE, iwdt->base + IXP4XX_OSWE_OFFSET); __raw_writel(0, iwdt->base + IXP4XX_OSWK_OFFSET); return 0; } static int ixp4xx_wdt_stop(struct watchdog_device *wdd) { struct ixp4xx_wdt *iwdt = to_ixp4xx_wdt(wdd); __raw_writel(IXP4XX_WDT_KEY, iwdt->base + IXP4XX_OSWK_OFFSET); __raw_writel(0, iwdt->base + IXP4XX_OSWE_OFFSET); __raw_writel(0, iwdt->base + IXP4XX_OSWK_OFFSET); return 0; } static int ixp4xx_wdt_set_timeout(struct watchdog_device *wdd, unsigned int timeout) { wdd->timeout = timeout; if (watchdog_active(wdd)) ixp4xx_wdt_start(wdd); return 0; } static const struct watchdog_ops ixp4xx_wdt_ops = { .start = ixp4xx_wdt_start, .stop = ixp4xx_wdt_stop, .set_timeout = ixp4xx_wdt_set_timeout, .owner = THIS_MODULE, }; static const struct watchdog_info ixp4xx_wdt_info = { .options = WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT, .identity = KBUILD_MODNAME, }; /* Devres-handled clock disablement */ static void ixp4xx_clock_action(void *d) { clk_disable_unprepare(d); } static int ixp4xx_wdt_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct ixp4xx_wdt *iwdt; struct clk *clk; int ret; if (!(read_cpuid_id() & 0xf) && !cpu_is_ixp46x()) { dev_err(dev, "Rev. A0 IXP42x CPU detected - watchdog disabled\n"); return -ENODEV; } iwdt = devm_kzalloc(dev, sizeof(*iwdt), GFP_KERNEL); if (!iwdt) return -ENOMEM; iwdt->base = (void __iomem *)dev->platform_data; /* * Retrieve rate from a fixed clock from the device tree if * the parent has that, else use the default clock rate. */ clk = devm_clk_get(dev->parent, NULL); if (!IS_ERR(clk)) { ret = clk_prepare_enable(clk); if (ret) return ret; ret = devm_add_action_or_reset(dev, ixp4xx_clock_action, clk); if (ret) return ret; iwdt->rate = clk_get_rate(clk); } if (!iwdt->rate) iwdt->rate = IXP4XX_TIMER_FREQ; iwdt->wdd.info = &ixp4xx_wdt_info; iwdt->wdd.ops = &ixp4xx_wdt_ops; iwdt->wdd.min_timeout = 1; iwdt->wdd.max_timeout = U32_MAX / iwdt->rate; iwdt->wdd.parent = dev; /* Default to 60 seconds */ iwdt->wdd.timeout = 60U; watchdog_init_timeout(&iwdt->wdd, 0, dev); if (__raw_readl(iwdt->base + IXP4XX_OSST_OFFSET) & IXP4XX_OSST_TIMER_WARM_RESET) iwdt->wdd.bootstatus = WDIOF_CARDRESET; ret = devm_watchdog_register_device(dev, &iwdt->wdd); if (ret) return ret; dev_info(dev, "IXP4xx watchdog available\n"); return 0; } static struct platform_driver ixp4xx_wdt_driver = { .probe = ixp4xx_wdt_probe, .driver = { .name = "ixp4xx-watchdog", }, }; module_platform_driver(ixp4xx_wdt_driver); MODULE_AUTHOR("Deepak Saxena "); MODULE_DESCRIPTION("IXP4xx Network Processor Watchdog"); MODULE_LICENSE("GPL");