xref: /linux/drivers/watchdog/lenovo_se30_wdt.c (revision bb1556ec94647060c6b52bf434b9fd824724a6f4)
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * WDT driver for Lenovo SE30 device
4  */
5 
6 #define dev_fmt(fmt) KBUILD_MODNAME ": " fmt
7 
8 #include <linux/io.h>
9 #include <linux/dmi.h>
10 #include <linux/delay.h>
11 #include <linux/iommu.h>
12 #include <linux/kernel.h>
13 #include <linux/module.h>
14 #include <linux/moduleparam.h>
15 #include <linux/platform_device.h>
16 #include <linux/watchdog.h>
17 
18 #define IOREGION_OFFSET	4 /* Use EC port 1 */
19 #define IOREGION_LENGTH	4
20 
21 #define WATCHDOG_TIMEOUT	60
22 
23 #define MIN_TIMEOUT	1
24 #define MAX_TIMEOUT	255
25 #define MAX_WAIT	10
26 
27 static int timeout; /* in seconds */
28 module_param(timeout, int, 0);
29 MODULE_PARM_DESC(timeout,
30 		 "Watchdog timeout in seconds. 1 <= timeout <= 255, default="
31 		 __MODULE_STRING(WATCHDOG_TIMEOUT) ".");
32 
33 static bool nowayout = WATCHDOG_NOWAYOUT;
34 module_param(nowayout, bool, 0);
35 MODULE_PARM_DESC(nowayout,
36 		 "Watchdog cannot be stopped once started (default="
37 		 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
38 
39 #define LNV_SE30_NAME	"lenovo-se30-wdt"
40 #define LNV_SE30_ID	0x0110
41 #define CHIPID_MASK	0xFFF0
42 
43 #define CHIPID_REG	0x20
44 #define SIO_REG		0x2e
45 #define LDN_REG		0x07
46 #define UNLOCK_KEY	0x87
47 #define LOCK_KEY	0xAA
48 #define LD_NUM_SHM	0x0F
49 #define LD_BASE_ADDR	0xF8
50 
51 #define WDT_MODULE	0x10
52 #define WDT_CFG_INDEX	0x15 /* WD configuration register */
53 #define WDT_CNT_INDEX	0x16 /* WD timer count register */
54 #define WDT_CFG_RESET	0x2
55 
56 /* Host Interface WIN2 offset definition */
57 #define SHM_WIN_SIZE		0xFF
58 #define SHM_WIN_MOD_OFFSET	0x01
59 #define SHM_WIN_CMD_OFFSET	0x02
60 #define SHM_WIN_SEL_OFFSET	0x03
61 #define SHM_WIN_CTL_OFFSET	0x04
62 #define VAL_SHM_WIN_CTRL_WR	0x40
63 #define VAL_SHM_WIN_CTRL_RD	0x80
64 #define SHM_WIN_ID_OFFSET	0x08
65 #define SHM_WIN_DAT_OFFSET	0x10
66 
67 struct nct6692_reg {
68 	unsigned char mod;
69 	unsigned char cmd;
70 	unsigned char sel;
71 	unsigned int idx;
72 };
73 
74 /* Watchdog is based on NCT6692 device */
75 struct lenovo_se30_wdt {
76 	unsigned char __iomem *shm_base_addr;
77 	struct nct6692_reg wdt_cfg;
78 	struct nct6692_reg wdt_cnt;
79 	struct watchdog_device wdt;
80 };
81 
superio_outb(int ioreg,int reg,int val)82 static inline void superio_outb(int ioreg, int reg, int val)
83 {
84 	outb(reg, ioreg);
85 	outb(val, ioreg + 1);
86 }
87 
superio_inb(int ioreg,int reg)88 static inline int superio_inb(int ioreg, int reg)
89 {
90 	outb(reg, ioreg);
91 	return inb(ioreg + 1);
92 }
93 
superio_enter(int key,int addr,const char * name)94 static inline int superio_enter(int key, int addr, const char *name)
95 {
96 	if (!request_muxed_region(addr, 2, name)) {
97 		pr_err("I/O address 0x%04x already in use\n", addr);
98 		return -EBUSY;
99 	}
100 	outb(key, addr); /* Enter extended function mode */
101 	outb(key, addr); /* Again according to manual */
102 
103 	return 0;
104 }
105 
superio_exit(int key,int addr)106 static inline void superio_exit(int key, int addr)
107 {
108 	outb(key, addr); /* Leave extended function mode */
109 	release_region(addr, 2);
110 }
111 
shm_get_ready(unsigned char __iomem * shm_base_addr,const struct nct6692_reg * reg)112 static int shm_get_ready(unsigned char __iomem *shm_base_addr,
113 			 const struct nct6692_reg *reg)
114 {
115 	unsigned char pre_id, new_id;
116 	int loop = 0;
117 
118 	iowrite8(reg->mod, shm_base_addr + SHM_WIN_MOD_OFFSET);
119 	iowrite8(reg->cmd, shm_base_addr + SHM_WIN_CMD_OFFSET);
120 	iowrite8(reg->sel, shm_base_addr + SHM_WIN_SEL_OFFSET);
121 
122 	pre_id = ioread8(shm_base_addr + SHM_WIN_ID_OFFSET);
123 	iowrite8(VAL_SHM_WIN_CTRL_RD, shm_base_addr + SHM_WIN_CTL_OFFSET);
124 
125 	/* Loop checking when interface is ready */
126 	while (loop < MAX_WAIT) {
127 		new_id = ioread8(shm_base_addr + SHM_WIN_ID_OFFSET);
128 		if (new_id != pre_id)
129 			return 0;
130 		loop++;
131 		usleep_range(10, 125);
132 	}
133 	return -ETIMEDOUT;
134 }
135 
read_shm_win(unsigned char __iomem * shm_base_addr,const struct nct6692_reg * reg,unsigned char idx_offset,unsigned char * data)136 static int read_shm_win(unsigned char __iomem *shm_base_addr,
137 			const struct nct6692_reg *reg,
138 			unsigned char idx_offset,
139 			unsigned char *data)
140 {
141 	int err = shm_get_ready(shm_base_addr, reg);
142 
143 	if (err)
144 		return err;
145 	*data = ioread8(shm_base_addr + SHM_WIN_DAT_OFFSET + reg->idx + idx_offset);
146 	return 0;
147 }
148 
write_shm_win(unsigned char __iomem * shm_base_addr,const struct nct6692_reg * reg,unsigned char idx_offset,unsigned char val)149 static int write_shm_win(unsigned char __iomem *shm_base_addr,
150 			 const struct nct6692_reg *reg,
151 			 unsigned char idx_offset,
152 			 unsigned char val)
153 {
154 	int err = shm_get_ready(shm_base_addr, reg);
155 
156 	if (err)
157 		return err;
158 	iowrite8(val, shm_base_addr + SHM_WIN_DAT_OFFSET + reg->idx + idx_offset);
159 	iowrite8(VAL_SHM_WIN_CTRL_WR, shm_base_addr + SHM_WIN_CTL_OFFSET);
160 	err = shm_get_ready(shm_base_addr, reg);
161 	return err;
162 }
163 
lenovo_se30_wdt_enable(struct lenovo_se30_wdt * data,unsigned int timeout)164 static int lenovo_se30_wdt_enable(struct lenovo_se30_wdt *data, unsigned int timeout)
165 {
166 	if (timeout) {
167 		int err = write_shm_win(data->shm_base_addr, &data->wdt_cfg, 0, WDT_CFG_RESET);
168 
169 		if (err)
170 			return err;
171 	}
172 	return write_shm_win(data->shm_base_addr, &data->wdt_cnt, 0, timeout);
173 }
174 
lenovo_se30_wdt_start(struct watchdog_device * wdog)175 static int lenovo_se30_wdt_start(struct watchdog_device *wdog)
176 {
177 	struct lenovo_se30_wdt *data = watchdog_get_drvdata(wdog);
178 
179 	return lenovo_se30_wdt_enable(data, wdog->timeout);
180 }
181 
lenovo_se30_wdt_stop(struct watchdog_device * wdog)182 static int lenovo_se30_wdt_stop(struct watchdog_device *wdog)
183 {
184 	struct lenovo_se30_wdt *data = watchdog_get_drvdata(wdog);
185 
186 	return lenovo_se30_wdt_enable(data, 0);
187 }
188 
lenovo_se30_wdt_get_timeleft(struct watchdog_device * wdog)189 static unsigned int lenovo_se30_wdt_get_timeleft(struct watchdog_device *wdog)
190 {
191 	struct lenovo_se30_wdt *data = watchdog_get_drvdata(wdog);
192 	unsigned char timeleft;
193 	int err;
194 
195 	err = read_shm_win(data->shm_base_addr, &data->wdt_cnt, 0, &timeleft);
196 	if (err)
197 		return 0;
198 	return timeleft;
199 }
200 
lenovo_se30_wdt_ping(struct watchdog_device * wdt)201 static int lenovo_se30_wdt_ping(struct watchdog_device *wdt)
202 {
203 	struct lenovo_se30_wdt *data = watchdog_get_drvdata(wdt);
204 	int err = 0;
205 
206 	/*
207 	 * Device does not support refreshing WDT_TIMER_REG register when
208 	 * the watchdog is active.  Need to disable, feed and enable again
209 	 */
210 	err = lenovo_se30_wdt_enable(data, 0);
211 	if (err)
212 		return err;
213 
214 	err = write_shm_win(data->shm_base_addr, &data->wdt_cnt, 0, wdt->timeout);
215 	if (!err)
216 		err = lenovo_se30_wdt_enable(data, wdt->timeout);
217 
218 	return err;
219 }
220 
221 static const struct watchdog_info lenovo_se30_wdt_info = {
222 	.options	= WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING |
223 			  WDIOF_MAGICCLOSE,
224 	.identity	= "Lenovo SE30 watchdog",
225 };
226 
227 static const struct watchdog_ops lenovo_se30_wdt_ops = {
228 	.owner		= THIS_MODULE,
229 	.start		= lenovo_se30_wdt_start,
230 	.stop		= lenovo_se30_wdt_stop,
231 	.ping		= lenovo_se30_wdt_ping,
232 	.get_timeleft	= lenovo_se30_wdt_get_timeleft,
233 };
234 
lenovo_se30_wdt_probe(struct platform_device * pdev)235 static int lenovo_se30_wdt_probe(struct platform_device *pdev)
236 {
237 	struct device *dev = &pdev->dev;
238 	struct lenovo_se30_wdt *priv;
239 	unsigned long base_phys;
240 	unsigned short val;
241 	int err;
242 
243 	err = superio_enter(UNLOCK_KEY, SIO_REG, LNV_SE30_NAME);
244 	if (err)
245 		return err;
246 
247 	val = superio_inb(SIO_REG, CHIPID_REG) << 8;
248 	val |= superio_inb(SIO_REG, CHIPID_REG + 1);
249 
250 	if ((val & CHIPID_MASK) != LNV_SE30_ID) {
251 		superio_exit(LOCK_KEY, SIO_REG);
252 		return -ENODEV;
253 	}
254 
255 	superio_outb(SIO_REG, LDN_REG, LD_NUM_SHM);
256 	base_phys = (superio_inb(SIO_REG, LD_BASE_ADDR) |
257 			 (superio_inb(SIO_REG, LD_BASE_ADDR + 1) << 8) |
258 			 (superio_inb(SIO_REG, LD_BASE_ADDR + 2) << 16) |
259 			 (superio_inb(SIO_REG, LD_BASE_ADDR + 3) << 24)) &
260 			0xFFFFFFFF;
261 
262 	superio_exit(LOCK_KEY, SIO_REG);
263 	if (base_phys == 0xFFFFFFFF || base_phys == 0)
264 		return -ENODEV;
265 
266 	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
267 	if (!priv)
268 		return -ENOMEM;
269 
270 	if (!devm_request_mem_region(dev, base_phys, SHM_WIN_SIZE, LNV_SE30_NAME))
271 		return -EBUSY;
272 
273 	priv->shm_base_addr = devm_ioremap(dev, base_phys, SHM_WIN_SIZE);
274 	if (!priv->shm_base_addr)
275 		return -ENOMEM;
276 
277 	priv->wdt_cfg.mod = WDT_MODULE;
278 	priv->wdt_cfg.idx = WDT_CFG_INDEX;
279 	priv->wdt_cnt.mod = WDT_MODULE;
280 	priv->wdt_cnt.idx = WDT_CNT_INDEX;
281 
282 	priv->wdt.ops = &lenovo_se30_wdt_ops;
283 	priv->wdt.info = &lenovo_se30_wdt_info;
284 	priv->wdt.timeout = WATCHDOG_TIMEOUT; /* Set default timeout */
285 	priv->wdt.min_timeout = MIN_TIMEOUT;
286 	priv->wdt.max_timeout = MAX_TIMEOUT;
287 	priv->wdt.parent = dev;
288 
289 	watchdog_init_timeout(&priv->wdt, timeout, dev);
290 	watchdog_set_drvdata(&priv->wdt, priv);
291 	watchdog_set_nowayout(&priv->wdt, nowayout);
292 	watchdog_stop_on_reboot(&priv->wdt);
293 	watchdog_stop_on_unregister(&priv->wdt);
294 
295 	return devm_watchdog_register_device(dev, &priv->wdt);
296 }
297 
298 static struct platform_device *pdev;
299 
300 static struct platform_driver lenovo_se30_wdt_driver = {
301 	.driver = {
302 		.name = LNV_SE30_NAME,
303 	},
304 	.probe  = lenovo_se30_wdt_probe,
305 };
306 
lenovo_se30_create_platform_device(const struct dmi_system_id * id)307 static int lenovo_se30_create_platform_device(const struct dmi_system_id *id)
308 {
309 	int err;
310 
311 	pdev = platform_device_alloc(LNV_SE30_NAME, -1);
312 	if (!pdev)
313 		return -ENOMEM;
314 
315 	err = platform_device_add(pdev);
316 	if (err)
317 		platform_device_put(pdev);
318 
319 	return err;
320 }
321 
322 static const struct dmi_system_id lenovo_se30_wdt_dmi_table[] __initconst = {
323 	{
324 		.ident = "LENOVO-SE30",
325 		.matches = {
326 			DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
327 			DMI_MATCH(DMI_PRODUCT_NAME, "11NA"),
328 		},
329 		.callback = lenovo_se30_create_platform_device,
330 	},
331 	{
332 		.ident = "LENOVO-SE30",
333 		.matches = {
334 			DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
335 			DMI_MATCH(DMI_PRODUCT_NAME, "11NB"),
336 		},
337 		.callback = lenovo_se30_create_platform_device,
338 	},
339 	{
340 		.ident = "LENOVO-SE30",
341 		.matches = {
342 			DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
343 			DMI_MATCH(DMI_PRODUCT_NAME, "11NC"),
344 		},
345 		.callback = lenovo_se30_create_platform_device,
346 	},
347 	{
348 		.ident = "LENOVO-SE30",
349 		.matches = {
350 			DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
351 			DMI_MATCH(DMI_PRODUCT_NAME, "11NH"),
352 		},
353 		.callback = lenovo_se30_create_platform_device,
354 	},
355 	{
356 		.ident = "LENOVO-SE30",
357 		.matches = {
358 			DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
359 			DMI_MATCH(DMI_PRODUCT_NAME, "11NJ"),
360 		},
361 		.callback = lenovo_se30_create_platform_device,
362 	},
363 	{
364 		.ident = "LENOVO-SE30",
365 		.matches = {
366 			DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
367 			DMI_MATCH(DMI_PRODUCT_NAME, "11NK"),
368 		},
369 		.callback = lenovo_se30_create_platform_device,
370 	},
371 	{}
372 };
373 MODULE_DEVICE_TABLE(dmi, lenovo_se30_wdt_dmi_table);
374 
lenovo_se30_wdt_init(void)375 static int __init lenovo_se30_wdt_init(void)
376 {
377 	if (!dmi_check_system(lenovo_se30_wdt_dmi_table))
378 		return -ENODEV;
379 
380 	return platform_driver_register(&lenovo_se30_wdt_driver);
381 }
382 
lenovo_se30_wdt_exit(void)383 static void __exit lenovo_se30_wdt_exit(void)
384 {
385 	if (pdev)
386 		platform_device_unregister(pdev);
387 	platform_driver_unregister(&lenovo_se30_wdt_driver);
388 }
389 
390 module_init(lenovo_se30_wdt_init);
391 module_exit(lenovo_se30_wdt_exit);
392 
393 MODULE_AUTHOR("Mark Pearson <mpearson-lenovo@squebb.ca>");
394 MODULE_AUTHOR("David Ober <dober@lenovo.com>");
395 MODULE_DESCRIPTION("Lenovo SE30 watchdog driver");
396 MODULE_LICENSE("GPL");
397