| // SPDX-License-Identifier: GPL-2.0+ |
| /* |
| * Watchdog driver for TQMx86 PLD. |
| * |
| * The watchdog supports power of 2 timeouts from 1 to 4096sec. |
| * Once started, it cannot be stopped. |
| * |
| * Based on the vendor code written by Vadim V.Vlasov |
| * <vvlasov@dev.rtsoft.ru> |
| */ |
| |
| #include <linux/io.h> |
| #include <linux/log2.h> |
| #include <linux/module.h> |
| #include <linux/platform_device.h> |
| #include <linux/timer.h> |
| #include <linux/watchdog.h> |
| |
| /* default timeout (secs) */ |
| #define WDT_TIMEOUT 32 |
| |
| static unsigned int timeout; |
| module_param(timeout, uint, 0); |
| MODULE_PARM_DESC(timeout, |
| "Watchdog timeout in seconds. (1<=timeout<=4096, default=" |
| __MODULE_STRING(WDT_TIMEOUT) ")"); |
| struct tqmx86_wdt { |
| struct watchdog_device wdd; |
| void __iomem *io_base; |
| }; |
| |
| #define TQMX86_WDCFG 0x00 /* Watchdog Configuration Register */ |
| #define TQMX86_WDCS 0x01 /* Watchdog Config/Status Register */ |
| |
| static int tqmx86_wdt_start(struct watchdog_device *wdd) |
| { |
| struct tqmx86_wdt *priv = watchdog_get_drvdata(wdd); |
| |
| iowrite8(0x81, priv->io_base + TQMX86_WDCS); |
| |
| return 0; |
| } |
| |
| static int tqmx86_wdt_set_timeout(struct watchdog_device *wdd, unsigned int t) |
| { |
| struct tqmx86_wdt *priv = watchdog_get_drvdata(wdd); |
| u8 val; |
| |
| t = roundup_pow_of_two(t); |
| val = ilog2(t) | 0x90; |
| val += 3; /* values 0,1,2 correspond to 0.125,0.25,0.5s timeouts */ |
| iowrite8(val, priv->io_base + TQMX86_WDCFG); |
| |
| wdd->timeout = t; |
| |
| return 0; |
| } |
| |
| static const struct watchdog_info tqmx86_wdt_info = { |
| .options = WDIOF_SETTIMEOUT | |
| WDIOF_KEEPALIVEPING, |
| .identity = "TQMx86 Watchdog", |
| }; |
| |
| static struct watchdog_ops tqmx86_wdt_ops = { |
| .owner = THIS_MODULE, |
| .start = tqmx86_wdt_start, |
| .set_timeout = tqmx86_wdt_set_timeout, |
| }; |
| |
| static int tqmx86_wdt_probe(struct platform_device *pdev) |
| { |
| struct tqmx86_wdt *priv; |
| struct resource *res; |
| int err; |
| |
| priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); |
| if (!priv) |
| return -ENOMEM; |
| |
| res = platform_get_resource(pdev, IORESOURCE_IO, 0); |
| if (!res) |
| return -ENODEV; |
| |
| priv->io_base = devm_ioport_map(&pdev->dev, res->start, |
| resource_size(res)); |
| if (!priv->io_base) |
| return -ENOMEM; |
| |
| watchdog_set_drvdata(&priv->wdd, priv); |
| |
| priv->wdd.parent = &pdev->dev; |
| priv->wdd.info = &tqmx86_wdt_info; |
| priv->wdd.ops = &tqmx86_wdt_ops; |
| priv->wdd.min_timeout = 1; |
| priv->wdd.max_timeout = 4096; |
| priv->wdd.max_hw_heartbeat_ms = 4096*1000; |
| priv->wdd.timeout = WDT_TIMEOUT; |
| |
| watchdog_init_timeout(&priv->wdd, timeout, &pdev->dev); |
| watchdog_set_nowayout(&priv->wdd, WATCHDOG_NOWAYOUT); |
| |
| tqmx86_wdt_set_timeout(&priv->wdd, priv->wdd.timeout); |
| |
| err = devm_watchdog_register_device(&pdev->dev, &priv->wdd); |
| if (err) |
| return err; |
| |
| dev_info(&pdev->dev, "TQMx86 watchdog\n"); |
| |
| return 0; |
| } |
| |
| static struct platform_driver tqmx86_wdt_driver = { |
| .driver = { |
| .name = "tqmx86-wdt", |
| }, |
| .probe = tqmx86_wdt_probe, |
| }; |
| |
| module_platform_driver(tqmx86_wdt_driver); |
| |
| MODULE_AUTHOR("Andrew Lunn <andrew@lunn.ch>"); |
| MODULE_DESCRIPTION("TQMx86 Watchdog"); |
| MODULE_ALIAS("platform:tqmx86-wdt"); |
| MODULE_LICENSE("GPL"); |