243 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * Watchdog driver for Faraday Technology FTWDT0104 *5 * Copyright (C) 2017 Linus Walleij <linus.walleij@linaro.org>6 *7 * Inspired by the out-of-tree drivers from OpenWRT:8 * Copyright (C) 2009 Paulius Zaleckas <paulius.zaleckas@teltonika.lt>9 */10 11#include <linux/bitops.h>12#include <linux/init.h>13#include <linux/interrupt.h>14#include <linux/io.h>15#include <linux/kernel.h>16#include <linux/module.h>17#include <linux/mod_devicetable.h>18#include <linux/platform_device.h>19#include <linux/slab.h>20#include <linux/watchdog.h>21 22#define FTWDT010_WDCOUNTER 0x023#define FTWDT010_WDLOAD 0x424#define FTWDT010_WDRESTART 0x825#define FTWDT010_WDCR 0xC26 27#define WDRESTART_MAGIC 0x5AB928 29#define WDCR_CLOCK_5MHZ BIT(4)30#define WDCR_WDEXT BIT(3)31#define WDCR_WDINTR BIT(2)32#define WDCR_SYS_RST BIT(1)33#define WDCR_ENABLE BIT(0)34 35#define WDT_CLOCK 5000000 /* 5 MHz */36 37struct ftwdt010_wdt {38 struct watchdog_device wdd;39 struct device *dev;40 void __iomem *base;41 bool has_irq;42};43 44static inline45struct ftwdt010_wdt *to_ftwdt010_wdt(struct watchdog_device *wdd)46{47 return container_of(wdd, struct ftwdt010_wdt, wdd);48}49 50static void ftwdt010_enable(struct ftwdt010_wdt *gwdt,51 unsigned int timeout,52 bool need_irq)53{54 u32 enable;55 56 writel(timeout * WDT_CLOCK, gwdt->base + FTWDT010_WDLOAD);57 writel(WDRESTART_MAGIC, gwdt->base + FTWDT010_WDRESTART);58 /* set clock before enabling */59 enable = WDCR_CLOCK_5MHZ | WDCR_SYS_RST;60 writel(enable, gwdt->base + FTWDT010_WDCR);61 if (need_irq)62 enable |= WDCR_WDINTR;63 enable |= WDCR_ENABLE;64 writel(enable, gwdt->base + FTWDT010_WDCR);65}66 67static int ftwdt010_wdt_start(struct watchdog_device *wdd)68{69 struct ftwdt010_wdt *gwdt = to_ftwdt010_wdt(wdd);70 71 ftwdt010_enable(gwdt, wdd->timeout, gwdt->has_irq);72 return 0;73}74 75static int ftwdt010_wdt_stop(struct watchdog_device *wdd)76{77 struct ftwdt010_wdt *gwdt = to_ftwdt010_wdt(wdd);78 79 writel(0, gwdt->base + FTWDT010_WDCR);80 81 return 0;82}83 84static int ftwdt010_wdt_ping(struct watchdog_device *wdd)85{86 struct ftwdt010_wdt *gwdt = to_ftwdt010_wdt(wdd);87 88 writel(WDRESTART_MAGIC, gwdt->base + FTWDT010_WDRESTART);89 90 return 0;91}92 93static int ftwdt010_wdt_set_timeout(struct watchdog_device *wdd,94 unsigned int timeout)95{96 wdd->timeout = timeout;97 if (watchdog_active(wdd))98 ftwdt010_wdt_start(wdd);99 100 return 0;101}102 103static int ftwdt010_wdt_restart(struct watchdog_device *wdd,104 unsigned long action, void *data)105{106 ftwdt010_enable(to_ftwdt010_wdt(wdd), 0, false);107 return 0;108}109 110static irqreturn_t ftwdt010_wdt_interrupt(int irq, void *data)111{112 struct ftwdt010_wdt *gwdt = data;113 114 watchdog_notify_pretimeout(&gwdt->wdd);115 116 return IRQ_HANDLED;117}118 119static const struct watchdog_ops ftwdt010_wdt_ops = {120 .start = ftwdt010_wdt_start,121 .stop = ftwdt010_wdt_stop,122 .ping = ftwdt010_wdt_ping,123 .set_timeout = ftwdt010_wdt_set_timeout,124 .restart = ftwdt010_wdt_restart,125 .owner = THIS_MODULE,126};127 128static const struct watchdog_info ftwdt010_wdt_info = {129 .options = WDIOF_KEEPALIVEPING130 | WDIOF_MAGICCLOSE131 | WDIOF_SETTIMEOUT,132 .identity = KBUILD_MODNAME,133};134 135 136static int ftwdt010_wdt_probe(struct platform_device *pdev)137{138 struct device *dev = &pdev->dev;139 struct ftwdt010_wdt *gwdt;140 unsigned int reg;141 int irq;142 int ret;143 144 gwdt = devm_kzalloc(dev, sizeof(*gwdt), GFP_KERNEL);145 if (!gwdt)146 return -ENOMEM;147 148 gwdt->base = devm_platform_ioremap_resource(pdev, 0);149 if (IS_ERR(gwdt->base))150 return PTR_ERR(gwdt->base);151 152 gwdt->dev = dev;153 gwdt->wdd.info = &ftwdt010_wdt_info;154 gwdt->wdd.ops = &ftwdt010_wdt_ops;155 gwdt->wdd.min_timeout = 1;156 gwdt->wdd.max_timeout = 0xFFFFFFFF / WDT_CLOCK;157 gwdt->wdd.parent = dev;158 159 /*160 * If 'timeout-sec' unspecified in devicetree, assume a 13 second161 * default.162 */163 gwdt->wdd.timeout = 13U;164 watchdog_init_timeout(&gwdt->wdd, 0, dev);165 166 reg = readw(gwdt->base + FTWDT010_WDCR);167 if (reg & WDCR_ENABLE) {168 /* Watchdog was enabled by the bootloader, disable it. */169 reg &= ~WDCR_ENABLE;170 writel(reg, gwdt->base + FTWDT010_WDCR);171 }172 173 irq = platform_get_irq(pdev, 0);174 if (irq > 0) {175 ret = devm_request_irq(dev, irq, ftwdt010_wdt_interrupt, 0,176 "watchdog bark", gwdt);177 if (ret)178 return ret;179 gwdt->has_irq = true;180 }181 182 ret = devm_watchdog_register_device(dev, &gwdt->wdd);183 if (ret)184 return ret;185 186 /* Set up platform driver data */187 platform_set_drvdata(pdev, gwdt);188 dev_info(dev, "FTWDT010 watchdog driver enabled\n");189 190 return 0;191}192 193static int __maybe_unused ftwdt010_wdt_suspend(struct device *dev)194{195 struct ftwdt010_wdt *gwdt = dev_get_drvdata(dev);196 unsigned int reg;197 198 reg = readw(gwdt->base + FTWDT010_WDCR);199 reg &= ~WDCR_ENABLE;200 writel(reg, gwdt->base + FTWDT010_WDCR);201 202 return 0;203}204 205static int __maybe_unused ftwdt010_wdt_resume(struct device *dev)206{207 struct ftwdt010_wdt *gwdt = dev_get_drvdata(dev);208 unsigned int reg;209 210 if (watchdog_active(&gwdt->wdd)) {211 reg = readw(gwdt->base + FTWDT010_WDCR);212 reg |= WDCR_ENABLE;213 writel(reg, gwdt->base + FTWDT010_WDCR);214 }215 216 return 0;217}218 219static const struct dev_pm_ops ftwdt010_wdt_dev_pm_ops = {220 SET_SYSTEM_SLEEP_PM_OPS(ftwdt010_wdt_suspend,221 ftwdt010_wdt_resume)222};223 224static const struct of_device_id ftwdt010_wdt_match[] = {225 { .compatible = "faraday,ftwdt010" },226 { .compatible = "cortina,gemini-watchdog" },227 {},228};229MODULE_DEVICE_TABLE(of, ftwdt010_wdt_match);230 231static struct platform_driver ftwdt010_wdt_driver = {232 .probe = ftwdt010_wdt_probe,233 .driver = {234 .name = "ftwdt010-wdt",235 .of_match_table = ftwdt010_wdt_match,236 .pm = &ftwdt010_wdt_dev_pm_ops,237 },238};239module_platform_driver(ftwdt010_wdt_driver);240MODULE_AUTHOR("Linus Walleij");241MODULE_DESCRIPTION("Watchdog driver for Faraday Technology FTWDT010");242MODULE_LICENSE("GPL");243