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