| xj | b04a402 | 2021-11-25 15:01:52 +0800 | [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 |  | 
|  | 106 |  | 
|  | 107 | static int npcm_wdt_set_timeout(struct watchdog_device *wdd, | 
|  | 108 | unsigned int timeout) | 
|  | 109 | { | 
|  | 110 | if (timeout < 2) | 
|  | 111 | wdd->timeout = 1; | 
|  | 112 | else if (timeout < 3) | 
|  | 113 | wdd->timeout = 2; | 
|  | 114 | else if (timeout < 6) | 
|  | 115 | wdd->timeout = 5; | 
|  | 116 | else if (timeout < 11) | 
|  | 117 | wdd->timeout = 10; | 
|  | 118 | else if (timeout < 22) | 
|  | 119 | wdd->timeout = 21; | 
|  | 120 | else if (timeout < 44) | 
|  | 121 | wdd->timeout = 43; | 
|  | 122 | else if (timeout < 87) | 
|  | 123 | wdd->timeout = 86; | 
|  | 124 | else if (timeout < 173) | 
|  | 125 | wdd->timeout = 172; | 
|  | 126 | else if (timeout < 688) | 
|  | 127 | wdd->timeout = 687; | 
|  | 128 | else | 
|  | 129 | wdd->timeout = 2750; | 
|  | 130 |  | 
|  | 131 | if (watchdog_active(wdd)) | 
|  | 132 | npcm_wdt_start(wdd); | 
|  | 133 |  | 
|  | 134 | return 0; | 
|  | 135 | } | 
|  | 136 |  | 
|  | 137 | static irqreturn_t npcm_wdt_interrupt(int irq, void *data) | 
|  | 138 | { | 
|  | 139 | struct npcm_wdt *wdt = data; | 
|  | 140 |  | 
|  | 141 | watchdog_notify_pretimeout(&wdt->wdd); | 
|  | 142 |  | 
|  | 143 | return IRQ_HANDLED; | 
|  | 144 | } | 
|  | 145 |  | 
|  | 146 | static int npcm_wdt_restart(struct watchdog_device *wdd, | 
|  | 147 | unsigned long action, void *data) | 
|  | 148 | { | 
|  | 149 | struct npcm_wdt *wdt = to_npcm_wdt(wdd); | 
|  | 150 |  | 
|  | 151 | writel(NPCM_WTR | NPCM_WTRE | NPCM_WTE, wdt->reg); | 
|  | 152 | udelay(1000); | 
|  | 153 |  | 
|  | 154 | return 0; | 
|  | 155 | } | 
|  | 156 |  | 
|  | 157 | static bool npcm_is_running(struct watchdog_device *wdd) | 
|  | 158 | { | 
|  | 159 | struct npcm_wdt *wdt = to_npcm_wdt(wdd); | 
|  | 160 |  | 
|  | 161 | return readl(wdt->reg) & NPCM_WTE; | 
|  | 162 | } | 
|  | 163 |  | 
|  | 164 | static const struct watchdog_info npcm_wdt_info = { | 
|  | 165 | .identity	= KBUILD_MODNAME, | 
|  | 166 | .options	= WDIOF_SETTIMEOUT | 
|  | 167 | | WDIOF_KEEPALIVEPING | 
|  | 168 | | WDIOF_MAGICCLOSE, | 
|  | 169 | }; | 
|  | 170 |  | 
|  | 171 | static const struct watchdog_ops npcm_wdt_ops = { | 
|  | 172 | .owner = THIS_MODULE, | 
|  | 173 | .start = npcm_wdt_start, | 
|  | 174 | .stop = npcm_wdt_stop, | 
|  | 175 | .ping = npcm_wdt_ping, | 
|  | 176 | .set_timeout = npcm_wdt_set_timeout, | 
|  | 177 | .restart = npcm_wdt_restart, | 
|  | 178 | }; | 
|  | 179 |  | 
|  | 180 | static int npcm_wdt_probe(struct platform_device *pdev) | 
|  | 181 | { | 
|  | 182 | struct device *dev = &pdev->dev; | 
|  | 183 | struct npcm_wdt *wdt; | 
|  | 184 | struct resource *res; | 
|  | 185 | int irq; | 
|  | 186 | int ret; | 
|  | 187 |  | 
|  | 188 | wdt = devm_kzalloc(&pdev->dev, sizeof(*wdt), GFP_KERNEL); | 
|  | 189 | if (!wdt) | 
|  | 190 | return -ENOMEM; | 
|  | 191 |  | 
|  | 192 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | 
|  | 193 | wdt->reg = devm_ioremap_resource(dev, res); | 
|  | 194 | if (IS_ERR(wdt->reg)) | 
|  | 195 | return PTR_ERR(wdt->reg); | 
|  | 196 |  | 
|  | 197 | irq = platform_get_irq(pdev, 0); | 
|  | 198 | if (irq < 0) | 
|  | 199 | return irq; | 
|  | 200 |  | 
|  | 201 | wdt->wdd.info = &npcm_wdt_info; | 
|  | 202 | wdt->wdd.ops = &npcm_wdt_ops; | 
|  | 203 | wdt->wdd.min_timeout = 1; | 
|  | 204 | wdt->wdd.max_timeout = 2750; | 
|  | 205 | wdt->wdd.parent = dev; | 
|  | 206 |  | 
|  | 207 | wdt->wdd.timeout = 86; | 
|  | 208 | watchdog_init_timeout(&wdt->wdd, 0, dev); | 
|  | 209 |  | 
|  | 210 | /* Ensure timeout is able to be represented by the hardware */ | 
|  | 211 | npcm_wdt_set_timeout(&wdt->wdd, wdt->wdd.timeout); | 
|  | 212 |  | 
|  | 213 | if (npcm_is_running(&wdt->wdd)) { | 
|  | 214 | /* Restart with the default or device-tree specified timeout */ | 
|  | 215 | npcm_wdt_start(&wdt->wdd); | 
|  | 216 | set_bit(WDOG_HW_RUNNING, &wdt->wdd.status); | 
|  | 217 | } | 
|  | 218 |  | 
|  | 219 | ret = devm_request_irq(dev, irq, npcm_wdt_interrupt, 0, | 
|  | 220 | "watchdog", wdt); | 
|  | 221 | if (ret) | 
|  | 222 | return ret; | 
|  | 223 |  | 
|  | 224 | ret = devm_watchdog_register_device(dev, &wdt->wdd); | 
|  | 225 | if (ret) { | 
|  | 226 | dev_err(dev, "failed to register watchdog\n"); | 
|  | 227 | return ret; | 
|  | 228 | } | 
|  | 229 |  | 
|  | 230 | dev_info(dev, "NPCM watchdog driver enabled\n"); | 
|  | 231 |  | 
|  | 232 | return 0; | 
|  | 233 | } | 
|  | 234 |  | 
|  | 235 | #ifdef CONFIG_OF | 
|  | 236 | static const struct of_device_id npcm_wdt_match[] = { | 
|  | 237 | {.compatible = "nuvoton,npcm750-wdt"}, | 
|  | 238 | {}, | 
|  | 239 | }; | 
|  | 240 | MODULE_DEVICE_TABLE(of, npcm_wdt_match); | 
|  | 241 | #endif | 
|  | 242 |  | 
|  | 243 | static struct platform_driver npcm_wdt_driver = { | 
|  | 244 | .probe		= npcm_wdt_probe, | 
|  | 245 | .driver		= { | 
|  | 246 | .name	= "npcm-wdt", | 
|  | 247 | .of_match_table = of_match_ptr(npcm_wdt_match), | 
|  | 248 | }, | 
|  | 249 | }; | 
|  | 250 | module_platform_driver(npcm_wdt_driver); | 
|  | 251 |  | 
|  | 252 | MODULE_AUTHOR("Joel Stanley"); | 
|  | 253 | MODULE_DESCRIPTION("Watchdog driver for NPCM"); | 
|  | 254 | MODULE_LICENSE("GPL v2"); |