123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230 |
- // SPDX-License-Identifier: GPL-2.0-only
- /*
- * Watchdog driver for Faraday Technology FTWDT010
- *
- * Copyright (C) 2017 Linus Walleij <linus.walleij@linaro.org>
- *
- * Inspired by the out-of-tree drivers from OpenWRT:
- * Copyright (C) 2009 Paulius Zaleckas <paulius.zaleckas@teltonika.lt>
- */
- #include <linux/bitops.h>
- #include <linux/init.h>
- #include <linux/interrupt.h>
- #include <linux/io.h>
- #include <linux/kernel.h>
- #include <linux/module.h>
- #include <linux/of_device.h>
- #include <linux/platform_device.h>
- #include <linux/slab.h>
- #include <linux/watchdog.h>
- #define FTWDT010_WDCOUNTER 0x0
- #define FTWDT010_WDLOAD 0x4
- #define FTWDT010_WDRESTART 0x8
- #define FTWDT010_WDCR 0xC
- #define WDRESTART_MAGIC 0x5AB9
- #define WDCR_CLOCK_5MHZ BIT(4)
- #define WDCR_WDEXT BIT(3)
- #define WDCR_WDINTR BIT(2)
- #define WDCR_SYS_RST BIT(1)
- #define WDCR_ENABLE BIT(0)
- #define WDT_CLOCK 5000000 /* 5 MHz */
- struct ftwdt010_wdt {
- struct watchdog_device wdd;
- struct device *dev;
- void __iomem *base;
- bool has_irq;
- };
- static inline
- struct ftwdt010_wdt *to_ftwdt010_wdt(struct watchdog_device *wdd)
- {
- return container_of(wdd, struct ftwdt010_wdt, wdd);
- }
- static int ftwdt010_wdt_start(struct watchdog_device *wdd)
- {
- struct ftwdt010_wdt *gwdt = to_ftwdt010_wdt(wdd);
- u32 enable;
- writel(wdd->timeout * WDT_CLOCK, gwdt->base + FTWDT010_WDLOAD);
- writel(WDRESTART_MAGIC, gwdt->base + FTWDT010_WDRESTART);
- /* set clock before enabling */
- enable = WDCR_CLOCK_5MHZ | WDCR_SYS_RST;
- writel(enable, gwdt->base + FTWDT010_WDCR);
- if (gwdt->has_irq)
- enable |= WDCR_WDINTR;
- enable |= WDCR_ENABLE;
- writel(enable, gwdt->base + FTWDT010_WDCR);
- return 0;
- }
- static int ftwdt010_wdt_stop(struct watchdog_device *wdd)
- {
- struct ftwdt010_wdt *gwdt = to_ftwdt010_wdt(wdd);
- writel(0, gwdt->base + FTWDT010_WDCR);
- return 0;
- }
- static int ftwdt010_wdt_ping(struct watchdog_device *wdd)
- {
- struct ftwdt010_wdt *gwdt = to_ftwdt010_wdt(wdd);
- writel(WDRESTART_MAGIC, gwdt->base + FTWDT010_WDRESTART);
- return 0;
- }
- static int ftwdt010_wdt_set_timeout(struct watchdog_device *wdd,
- unsigned int timeout)
- {
- wdd->timeout = timeout;
- if (watchdog_active(wdd))
- ftwdt010_wdt_start(wdd);
- return 0;
- }
- static irqreturn_t ftwdt010_wdt_interrupt(int irq, void *data)
- {
- struct ftwdt010_wdt *gwdt = data;
- watchdog_notify_pretimeout(&gwdt->wdd);
- return IRQ_HANDLED;
- }
- static const struct watchdog_ops ftwdt010_wdt_ops = {
- .start = ftwdt010_wdt_start,
- .stop = ftwdt010_wdt_stop,
- .ping = ftwdt010_wdt_ping,
- .set_timeout = ftwdt010_wdt_set_timeout,
- .owner = THIS_MODULE,
- };
- static const struct watchdog_info ftwdt010_wdt_info = {
- .options = WDIOF_KEEPALIVEPING
- | WDIOF_MAGICCLOSE
- | WDIOF_SETTIMEOUT,
- .identity = KBUILD_MODNAME,
- };
- static int ftwdt010_wdt_probe(struct platform_device *pdev)
- {
- struct device *dev = &pdev->dev;
- struct ftwdt010_wdt *gwdt;
- unsigned int reg;
- int irq;
- int ret;
- gwdt = devm_kzalloc(dev, sizeof(*gwdt), GFP_KERNEL);
- if (!gwdt)
- return -ENOMEM;
- gwdt->base = devm_platform_ioremap_resource(pdev, 0);
- if (IS_ERR(gwdt->base))
- return PTR_ERR(gwdt->base);
- gwdt->dev = dev;
- gwdt->wdd.info = &ftwdt010_wdt_info;
- gwdt->wdd.ops = &ftwdt010_wdt_ops;
- gwdt->wdd.min_timeout = 1;
- gwdt->wdd.max_timeout = 0xFFFFFFFF / WDT_CLOCK;
- gwdt->wdd.parent = dev;
- /*
- * If 'timeout-sec' unspecified in devicetree, assume a 13 second
- * default.
- */
- gwdt->wdd.timeout = 13U;
- watchdog_init_timeout(&gwdt->wdd, 0, dev);
- reg = readw(gwdt->base + FTWDT010_WDCR);
- if (reg & WDCR_ENABLE) {
- /* Watchdog was enabled by the bootloader, disable it. */
- reg &= ~WDCR_ENABLE;
- writel(reg, gwdt->base + FTWDT010_WDCR);
- }
- irq = platform_get_irq(pdev, 0);
- if (irq) {
- ret = devm_request_irq(dev, irq, ftwdt010_wdt_interrupt, 0,
- "watchdog bark", gwdt);
- if (ret)
- return ret;
- gwdt->has_irq = true;
- }
- ret = devm_watchdog_register_device(dev, &gwdt->wdd);
- if (ret)
- return ret;
- /* Set up platform driver data */
- platform_set_drvdata(pdev, gwdt);
- dev_info(dev, "FTWDT010 watchdog driver enabled\n");
- return 0;
- }
- static int __maybe_unused ftwdt010_wdt_suspend(struct device *dev)
- {
- struct ftwdt010_wdt *gwdt = dev_get_drvdata(dev);
- unsigned int reg;
- reg = readw(gwdt->base + FTWDT010_WDCR);
- reg &= ~WDCR_ENABLE;
- writel(reg, gwdt->base + FTWDT010_WDCR);
- return 0;
- }
- static int __maybe_unused ftwdt010_wdt_resume(struct device *dev)
- {
- struct ftwdt010_wdt *gwdt = dev_get_drvdata(dev);
- unsigned int reg;
- if (watchdog_active(&gwdt->wdd)) {
- reg = readw(gwdt->base + FTWDT010_WDCR);
- reg |= WDCR_ENABLE;
- writel(reg, gwdt->base + FTWDT010_WDCR);
- }
- return 0;
- }
- static const struct dev_pm_ops ftwdt010_wdt_dev_pm_ops = {
- SET_SYSTEM_SLEEP_PM_OPS(ftwdt010_wdt_suspend,
- ftwdt010_wdt_resume)
- };
- #ifdef CONFIG_OF
- static const struct of_device_id ftwdt010_wdt_match[] = {
- { .compatible = "faraday,ftwdt010" },
- { .compatible = "cortina,gemini-watchdog" },
- {},
- };
- MODULE_DEVICE_TABLE(of, ftwdt010_wdt_match);
- #endif
- static struct platform_driver ftwdt010_wdt_driver = {
- .probe = ftwdt010_wdt_probe,
- .driver = {
- .name = "ftwdt010-wdt",
- .of_match_table = of_match_ptr(ftwdt010_wdt_match),
- .pm = &ftwdt010_wdt_dev_pm_ops,
- },
- };
- module_platform_driver(ftwdt010_wdt_driver);
- MODULE_AUTHOR("Linus Walleij");
- MODULE_DESCRIPTION("Watchdog driver for Faraday Technology FTWDT010");
- MODULE_LICENSE("GPL");
|