File Index
Files and directories
Sign in
FILE

lenovo_se30_wdt.c

File preview with syntax highlighting.

linux-6.19.14/drivers/watchdog/lenovo_se30_wdt.c C
  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
 27static int timeout; /* in seconds */
 28module_param(timeout, int, 0);
 29MODULE_PARM_DESC(timeout,
 30		 "Watchdog timeout in seconds. 1 <= timeout <= 255, default="
 31		 __MODULE_STRING(WATCHDOG_TIMEOUT) ".");
 32
 33static bool nowayout = WATCHDOG_NOWAYOUT;
 34module_param(nowayout, bool, 0);
 35MODULE_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
 67struct 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 */
 75struct 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
 82static inline void superio_outb(int ioreg, int reg, int val)
 83{
 84	outb(reg, ioreg);
 85	outb(val, ioreg + 1);
 86}
 87
 88static inline int superio_inb(int ioreg, int reg)
 89{
 90	outb(reg, ioreg);
 91	return inb(ioreg + 1);
 92}
 93
 94static 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
106static inline void superio_exit(int key, int addr)
107{
108	outb(key, addr); /* Leave extended function mode */
109	release_region(addr, 2);
110}
111
112static 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
136static 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
149static 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
164static 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
175static 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
182static 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
189static 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
201static 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
221static const struct watchdog_info lenovo_se30_wdt_info = {
222	.options	= WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING |
223			  WDIOF_MAGICCLOSE,
224	.identity	= "Lenovo SE30 watchdog",
225};
226
227static 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
235static 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
298static struct platform_device *pdev;
299
300static struct platform_driver lenovo_se30_wdt_driver = {
301	.driver = {
302		.name = LNV_SE30_NAME,
303	},
304	.probe  = lenovo_se30_wdt_probe,
305};
306
307static 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
322static 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};
373MODULE_DEVICE_TABLE(dmi, lenovo_se30_wdt_dmi_table);
374
375static 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
383static 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
390module_init(lenovo_se30_wdt_init);
391module_exit(lenovo_se30_wdt_exit);
392
393MODULE_AUTHOR("Mark Pearson <mpearson-lenovo@squebb.ca>");
394MODULE_AUTHOR("David Ober <dober@lenovo.com>");
395MODULE_DESCRIPTION("Lenovo SE30 watchdog driver");
396MODULE_LICENSE("GPL");
~/ Read-only file preview.