1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * Watchdog driver for Faraday Technology FTWDT010 4 * 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/of_device.h> 18 #include <linux/platform_device.h> 19 #include <linux/slab.h> 20 #include <linux/watchdog.h> 21 22 #define FTWDT010_WDCOUNTER 0x0 23 #define FTWDT010_WDLOAD 0x4 24 #define FTWDT010_WDRESTART 0x8 25 #define FTWDT010_WDCR 0xC 26 27 #define WDRESTART_MAGIC 0x5AB9 28 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 37 struct ftwdt010_wdt { 38 struct watchdog_device wdd; 39 struct device *dev; 40 void __iomem *base; 41 bool has_irq; 42 }; 43 44 static inline 45 struct ftwdt010_wdt *to_ftwdt010_wdt(struct watchdog_device *wdd) 46 { 47 return container_of(wdd, struct ftwdt010_wdt, wdd); 48 } 49 50 static 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 67 static 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 75 static 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 84 static 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 93 static 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 103 static 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 110 static 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 119 static 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 128 static const struct watchdog_info ftwdt010_wdt_info = { 129 .options = WDIOF_KEEPALIVEPING 130 | WDIOF_MAGICCLOSE 131 | WDIOF_SETTIMEOUT, 132 .identity = KBUILD_MODNAME, 133 }; 134 135 136 static 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 second 161 * 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 193 static 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 205 static 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 219 static 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 224 #ifdef CONFIG_OF 225 static const struct of_device_id ftwdt010_wdt_match[] = { 226 { .compatible = "faraday,ftwdt010" }, 227 { .compatible = "cortina,gemini-watchdog" }, 228 {}, 229 }; 230 MODULE_DEVICE_TABLE(of, ftwdt010_wdt_match); 231 #endif 232 233 static struct platform_driver ftwdt010_wdt_driver = { 234 .probe = ftwdt010_wdt_probe, 235 .driver = { 236 .name = "ftwdt010-wdt", 237 .of_match_table = of_match_ptr(ftwdt010_wdt_match), 238 .pm = &ftwdt010_wdt_dev_pm_ops, 239 }, 240 }; 241 module_platform_driver(ftwdt010_wdt_driver); 242 MODULE_AUTHOR("Linus Walleij"); 243 MODULE_DESCRIPTION("Watchdog driver for Faraday Technology FTWDT010"); 244 MODULE_LICENSE("GPL"); 245