Joel Stanley | 975b7f0 | 2018-03-13 16:47:26 +1030 | [diff] [blame] | 1 | // SPDX-License-Identifier: GPL-2.0 |
| 2 | // Copyright (c) 2018 Nuvoton Technology corporation. |
| 3 | // Copyright (c) 2018 IBM Corp. |
| 4 | |
| 5 | #include <linux/bitops.h> |
| 6 | #include <linux/delay.h> |
| 7 | #include <linux/interrupt.h> |
| 8 | #include <linux/kernel.h> |
| 9 | #include <linux/module.h> |
| 10 | #include <linux/of_irq.h> |
| 11 | #include <linux/platform_device.h> |
| 12 | #include <linux/slab.h> |
| 13 | #include <linux/watchdog.h> |
| 14 | |
| 15 | #define NPCM_WTCR 0x1C |
| 16 | |
| 17 | #define NPCM_WTCLK (BIT(10) | BIT(11)) /* Clock divider */ |
| 18 | #define NPCM_WTE BIT(7) /* Enable */ |
| 19 | #define NPCM_WTIE BIT(6) /* Enable irq */ |
| 20 | #define NPCM_WTIS (BIT(4) | BIT(5)) /* Interval selection */ |
| 21 | #define NPCM_WTIF BIT(3) /* Interrupt flag*/ |
| 22 | #define NPCM_WTRF BIT(2) /* Reset flag */ |
| 23 | #define NPCM_WTRE BIT(1) /* Reset enable */ |
| 24 | #define NPCM_WTR BIT(0) /* Reset counter */ |
| 25 | |
| 26 | /* |
| 27 | * Watchdog timeouts |
| 28 | * |
| 29 | * 170 msec: WTCLK=01 WTIS=00 VAL= 0x400 |
| 30 | * 670 msec: WTCLK=01 WTIS=01 VAL= 0x410 |
| 31 | * 1360 msec: WTCLK=10 WTIS=00 VAL= 0x800 |
| 32 | * 2700 msec: WTCLK=01 WTIS=10 VAL= 0x420 |
| 33 | * 5360 msec: WTCLK=10 WTIS=01 VAL= 0x810 |
| 34 | * 10700 msec: WTCLK=01 WTIS=11 VAL= 0x430 |
| 35 | * 21600 msec: WTCLK=10 WTIS=10 VAL= 0x820 |
| 36 | * 43000 msec: WTCLK=11 WTIS=00 VAL= 0xC00 |
| 37 | * 85600 msec: WTCLK=10 WTIS=11 VAL= 0x830 |
| 38 | * 172000 msec: WTCLK=11 WTIS=01 VAL= 0xC10 |
| 39 | * 687000 msec: WTCLK=11 WTIS=10 VAL= 0xC20 |
| 40 | * 2750000 msec: WTCLK=11 WTIS=11 VAL= 0xC30 |
| 41 | */ |
| 42 | |
| 43 | struct npcm_wdt { |
| 44 | struct watchdog_device wdd; |
| 45 | void __iomem *reg; |
| 46 | }; |
| 47 | |
| 48 | static inline struct npcm_wdt *to_npcm_wdt(struct watchdog_device *wdd) |
| 49 | { |
| 50 | return container_of(wdd, struct npcm_wdt, wdd); |
| 51 | } |
| 52 | |
| 53 | static int npcm_wdt_ping(struct watchdog_device *wdd) |
| 54 | { |
| 55 | struct npcm_wdt *wdt = to_npcm_wdt(wdd); |
| 56 | u32 val; |
| 57 | |
| 58 | val = readl(wdt->reg); |
| 59 | writel(val | NPCM_WTR, wdt->reg); |
| 60 | |
| 61 | return 0; |
| 62 | } |
| 63 | |
| 64 | static int npcm_wdt_start(struct watchdog_device *wdd) |
| 65 | { |
| 66 | struct npcm_wdt *wdt = to_npcm_wdt(wdd); |
| 67 | u32 val; |
| 68 | |
| 69 | if (wdd->timeout < 2) |
| 70 | val = 0x800; |
| 71 | else if (wdd->timeout < 3) |
| 72 | val = 0x420; |
| 73 | else if (wdd->timeout < 6) |
| 74 | val = 0x810; |
| 75 | else if (wdd->timeout < 11) |
| 76 | val = 0x430; |
| 77 | else if (wdd->timeout < 22) |
| 78 | val = 0x820; |
| 79 | else if (wdd->timeout < 44) |
| 80 | val = 0xC00; |
| 81 | else if (wdd->timeout < 87) |
| 82 | val = 0x830; |
| 83 | else if (wdd->timeout < 173) |
| 84 | val = 0xC10; |
| 85 | else if (wdd->timeout < 688) |
| 86 | val = 0xC20; |
| 87 | else |
| 88 | val = 0xC30; |
| 89 | |
| 90 | val |= NPCM_WTRE | NPCM_WTE | NPCM_WTR | NPCM_WTIE; |
| 91 | |
| 92 | writel(val, wdt->reg); |
| 93 | |
| 94 | return 0; |
| 95 | } |
| 96 | |
| 97 | static int npcm_wdt_stop(struct watchdog_device *wdd) |
| 98 | { |
| 99 | struct npcm_wdt *wdt = to_npcm_wdt(wdd); |
| 100 | |
| 101 | writel(0, wdt->reg); |
| 102 | |
| 103 | return 0; |
| 104 | } |
| 105 | |
Joel Stanley | 975b7f0 | 2018-03-13 16:47:26 +1030 | [diff] [blame] | 106 | static int npcm_wdt_set_timeout(struct watchdog_device *wdd, |
| 107 | unsigned int timeout) |
| 108 | { |
| 109 | if (timeout < 2) |
| 110 | wdd->timeout = 1; |
| 111 | else if (timeout < 3) |
Tomer Maimon | f667421 | 2020-03-03 12:01:14 +0200 | [diff] [blame] | 112 | wdd->timeout = 2; |
Joel Stanley | 975b7f0 | 2018-03-13 16:47:26 +1030 | [diff] [blame] | 113 | else if (timeout < 6) |
Tomer Maimon | f667421 | 2020-03-03 12:01:14 +0200 | [diff] [blame] | 114 | wdd->timeout = 5; |
Joel Stanley | 975b7f0 | 2018-03-13 16:47:26 +1030 | [diff] [blame] | 115 | else if (timeout < 11) |
Tomer Maimon | f667421 | 2020-03-03 12:01:14 +0200 | [diff] [blame] | 116 | wdd->timeout = 10; |
Joel Stanley | 975b7f0 | 2018-03-13 16:47:26 +1030 | [diff] [blame] | 117 | else if (timeout < 22) |
Tomer Maimon | f667421 | 2020-03-03 12:01:14 +0200 | [diff] [blame] | 118 | wdd->timeout = 21; |
Joel Stanley | 975b7f0 | 2018-03-13 16:47:26 +1030 | [diff] [blame] | 119 | else if (timeout < 44) |
Tomer Maimon | f667421 | 2020-03-03 12:01:14 +0200 | [diff] [blame] | 120 | wdd->timeout = 43; |
Joel Stanley | 975b7f0 | 2018-03-13 16:47:26 +1030 | [diff] [blame] | 121 | else if (timeout < 87) |
Tomer Maimon | f667421 | 2020-03-03 12:01:14 +0200 | [diff] [blame] | 122 | wdd->timeout = 86; |
Joel Stanley | 975b7f0 | 2018-03-13 16:47:26 +1030 | [diff] [blame] | 123 | else if (timeout < 173) |
Tomer Maimon | f667421 | 2020-03-03 12:01:14 +0200 | [diff] [blame] | 124 | wdd->timeout = 172; |
Joel Stanley | 975b7f0 | 2018-03-13 16:47:26 +1030 | [diff] [blame] | 125 | else if (timeout < 688) |
Tomer Maimon | f667421 | 2020-03-03 12:01:14 +0200 | [diff] [blame] | 126 | wdd->timeout = 687; |
Joel Stanley | 975b7f0 | 2018-03-13 16:47:26 +1030 | [diff] [blame] | 127 | else |
Tomer Maimon | f667421 | 2020-03-03 12:01:14 +0200 | [diff] [blame] | 128 | wdd->timeout = 2750; |
Joel Stanley | 975b7f0 | 2018-03-13 16:47:26 +1030 | [diff] [blame] | 129 | |
| 130 | if (watchdog_active(wdd)) |
| 131 | npcm_wdt_start(wdd); |
| 132 | |
| 133 | return 0; |
| 134 | } |
| 135 | |
| 136 | static irqreturn_t npcm_wdt_interrupt(int irq, void *data) |
| 137 | { |
| 138 | struct npcm_wdt *wdt = data; |
| 139 | |
| 140 | watchdog_notify_pretimeout(&wdt->wdd); |
| 141 | |
| 142 | return IRQ_HANDLED; |
| 143 | } |
| 144 | |
| 145 | static int npcm_wdt_restart(struct watchdog_device *wdd, |
| 146 | unsigned long action, void *data) |
| 147 | { |
| 148 | struct npcm_wdt *wdt = to_npcm_wdt(wdd); |
| 149 | |
| 150 | writel(NPCM_WTR | NPCM_WTRE | NPCM_WTE, wdt->reg); |
| 151 | udelay(1000); |
| 152 | |
| 153 | return 0; |
| 154 | } |
| 155 | |
| 156 | static bool npcm_is_running(struct watchdog_device *wdd) |
| 157 | { |
| 158 | struct npcm_wdt *wdt = to_npcm_wdt(wdd); |
| 159 | |
| 160 | return readl(wdt->reg) & NPCM_WTE; |
| 161 | } |
| 162 | |
| 163 | static const struct watchdog_info npcm_wdt_info = { |
| 164 | .identity = KBUILD_MODNAME, |
| 165 | .options = WDIOF_SETTIMEOUT |
| 166 | | WDIOF_KEEPALIVEPING |
| 167 | | WDIOF_MAGICCLOSE, |
| 168 | }; |
| 169 | |
| 170 | static const struct watchdog_ops npcm_wdt_ops = { |
| 171 | .owner = THIS_MODULE, |
| 172 | .start = npcm_wdt_start, |
| 173 | .stop = npcm_wdt_stop, |
| 174 | .ping = npcm_wdt_ping, |
| 175 | .set_timeout = npcm_wdt_set_timeout, |
| 176 | .restart = npcm_wdt_restart, |
| 177 | }; |
| 178 | |
| 179 | static int npcm_wdt_probe(struct platform_device *pdev) |
| 180 | { |
| 181 | struct device *dev = &pdev->dev; |
| 182 | struct npcm_wdt *wdt; |
Joel Stanley | 975b7f0 | 2018-03-13 16:47:26 +1030 | [diff] [blame] | 183 | int irq; |
| 184 | int ret; |
| 185 | |
Guenter Roeck | 7e6437e | 2019-04-09 10:23:47 -0700 | [diff] [blame] | 186 | wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL); |
Joel Stanley | 975b7f0 | 2018-03-13 16:47:26 +1030 | [diff] [blame] | 187 | if (!wdt) |
| 188 | return -ENOMEM; |
| 189 | |
Guenter Roeck | 0f0a6a2 | 2019-04-02 12:01:53 -0700 | [diff] [blame] | 190 | wdt->reg = devm_platform_ioremap_resource(pdev, 0); |
Joel Stanley | 975b7f0 | 2018-03-13 16:47:26 +1030 | [diff] [blame] | 191 | if (IS_ERR(wdt->reg)) |
| 192 | return PTR_ERR(wdt->reg); |
| 193 | |
| 194 | irq = platform_get_irq(pdev, 0); |
| 195 | if (irq < 0) |
| 196 | return irq; |
| 197 | |
| 198 | wdt->wdd.info = &npcm_wdt_info; |
| 199 | wdt->wdd.ops = &npcm_wdt_ops; |
| 200 | wdt->wdd.min_timeout = 1; |
| 201 | wdt->wdd.max_timeout = 2750; |
| 202 | wdt->wdd.parent = dev; |
| 203 | |
| 204 | wdt->wdd.timeout = 86; |
| 205 | watchdog_init_timeout(&wdt->wdd, 0, dev); |
| 206 | |
| 207 | /* Ensure timeout is able to be represented by the hardware */ |
| 208 | npcm_wdt_set_timeout(&wdt->wdd, wdt->wdd.timeout); |
| 209 | |
| 210 | if (npcm_is_running(&wdt->wdd)) { |
| 211 | /* Restart with the default or device-tree specified timeout */ |
| 212 | npcm_wdt_start(&wdt->wdd); |
| 213 | set_bit(WDOG_HW_RUNNING, &wdt->wdd.status); |
| 214 | } |
| 215 | |
Guenter Roeck | 7e6437e | 2019-04-09 10:23:47 -0700 | [diff] [blame] | 216 | ret = devm_request_irq(dev, irq, npcm_wdt_interrupt, 0, "watchdog", |
| 217 | wdt); |
Joel Stanley | 975b7f0 | 2018-03-13 16:47:26 +1030 | [diff] [blame] | 218 | if (ret) |
| 219 | return ret; |
| 220 | |
| 221 | ret = devm_watchdog_register_device(dev, &wdt->wdd); |
Wolfram Sang | ab9113d0 | 2019-05-18 23:27:43 +0200 | [diff] [blame] | 222 | if (ret) |
Joel Stanley | 975b7f0 | 2018-03-13 16:47:26 +1030 | [diff] [blame] | 223 | return ret; |
Joel Stanley | 975b7f0 | 2018-03-13 16:47:26 +1030 | [diff] [blame] | 224 | |
| 225 | dev_info(dev, "NPCM watchdog driver enabled\n"); |
| 226 | |
| 227 | return 0; |
| 228 | } |
| 229 | |
| 230 | #ifdef CONFIG_OF |
| 231 | static const struct of_device_id npcm_wdt_match[] = { |
Jonathan Neuschäfer | 328d1c1 | 2021-04-06 14:09:18 +0200 | [diff] [blame] | 232 | {.compatible = "nuvoton,wpcm450-wdt"}, |
Joel Stanley | 975b7f0 | 2018-03-13 16:47:26 +1030 | [diff] [blame] | 233 | {.compatible = "nuvoton,npcm750-wdt"}, |
| 234 | {}, |
| 235 | }; |
| 236 | MODULE_DEVICE_TABLE(of, npcm_wdt_match); |
| 237 | #endif |
| 238 | |
| 239 | static struct platform_driver npcm_wdt_driver = { |
| 240 | .probe = npcm_wdt_probe, |
| 241 | .driver = { |
| 242 | .name = "npcm-wdt", |
| 243 | .of_match_table = of_match_ptr(npcm_wdt_match), |
| 244 | }, |
| 245 | }; |
| 246 | module_platform_driver(npcm_wdt_driver); |
| 247 | |
| 248 | MODULE_AUTHOR("Joel Stanley"); |
| 249 | MODULE_DESCRIPTION("Watchdog driver for NPCM"); |
| 250 | MODULE_LICENSE("GPL v2"); |