Message ID | 20250115140510.2017-1-mpearson-lenovo@squebb.ca (mailing list archive) |
---|---|
State | New |
Headers | show |
Series | watchdog: lenovo_se30_wdt: Watchdog driver for Lenovo SE30 platform | expand |
On 1/15/25 06:05, Mark Pearson wrote: > Watchdog driver implementation for Lenovo SE30 platform. > > Signed-off-by: Mark Pearson <mpearson-lenovo@squebb.ca> > --- > drivers/watchdog/Kconfig | 12 + > drivers/watchdog/Makefile | 1 + > drivers/watchdog/lenovo_se30_wdt.c | 435 +++++++++++++++++++++++++++++ > 3 files changed, 448 insertions(+) > create mode 100644 drivers/watchdog/lenovo_se30_wdt.c > > diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig > index f81705f8539a..c73e8f0e436c 100644 > --- a/drivers/watchdog/Kconfig > +++ b/drivers/watchdog/Kconfig > @@ -279,6 +279,18 @@ config LENOVO_SE10_WDT > This driver can also be built as a module. If so, the module > will be called lenovo-se10-wdt. > > +config LENOVO_SE30_WDT > + tristate "Lenovo SE30 Watchdog" > + depends on (X86 && DMI) || COMPILE_TEST > + depends on HAS_IOPORT > + select WATCHDOG_CORE > + help > + If you say yes here you get support for the watchdog > + functionality for the Lenovo SE30 platform. > + > + This driver can also be built as a module. If so, the module > + will be called lenovo-se30-wdt. > + > config MENF21BMC_WATCHDOG > tristate "MEN 14F021P00 BMC Watchdog" > depends on MFD_MENF21BMC || COMPILE_TEST > diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile > index 8411626fa162..c9482904bf87 100644 > --- a/drivers/watchdog/Makefile > +++ b/drivers/watchdog/Makefile > @@ -124,6 +124,7 @@ obj-$(CONFIG_I6300ESB_WDT) += i6300esb.o > obj-$(CONFIG_IE6XX_WDT) += ie6xx_wdt.o > obj-$(CONFIG_ITCO_WDT) += iTCO_wdt.o > obj-$(CONFIG_LENOVO_SE10_WDT) += lenovo_se10_wdt.o > +obj-$(CONFIG_LENOVO_SE30_WDT) += lenovo_se30_wdt.o > ifeq ($(CONFIG_ITCO_VENDOR_SUPPORT),y) > obj-$(CONFIG_ITCO_WDT) += iTCO_vendor_support.o > endif > diff --git a/drivers/watchdog/lenovo_se30_wdt.c b/drivers/watchdog/lenovo_se30_wdt.c > new file mode 100644 > index 000000000000..d119f6fe870a > --- /dev/null > +++ b/drivers/watchdog/lenovo_se30_wdt.c > @@ -0,0 +1,435 @@ > +// SPDX-License-Identifier: GPL-2.0-or-later > +/* > + * WDT driver for Lenovo SE30 device > + */ > + > +#define dev_fmt(fmt) KBUILD_MODNAME ": " fmt > + > +#include <linux/acpi.h> I don't see any ACPI code. > +#include <linux/dmi.h> > +#include <linux/delay.h> > +#include <linux/iommu.h> > +#include <linux/kernel.h> > +#include <linux/module.h> > +#include <linux/moduleparam.h> > +#include <linux/platform_device.h> > +#include <linux/watchdog.h> > + > +#define IOREGION_OFFSET 4 /* Use EC port 1 */ > +#define IOREGION_LENGTH 4 > + > +#define WATCHDOG_TIMEOUT 60 > +#define MIN_TIMEOUT 1 What is the point of a 1-second timeout if a ping can take up to 6 seconds ? > +#define MAX_TIMEOUT 255 > + Please use #define<space>NAME<tab>value for defines, and align the value. > +static int timeout; /* in seconds */ > +module_param(timeout, int, 0); > +MODULE_PARM_DESC(timeout, > + "Watchdog timeout in seconds. 1 <= timeout <= 255, default=" > + __MODULE_STRING(WATCHDOG_TIMEOUT) "."); > + > +static bool nowayout = WATCHDOG_NOWAYOUT; > +module_param(nowayout, bool, 0); > +MODULE_PARM_DESC(nowayout, > + "Watchdog cannot be stopped once started (default=" > + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); > + > +#define LNV_SE30_NAME "lenovo-se30-wdt" > +#define LNV_SE30_ID 0x0110 > +#define CHIPID_MASK 0xFFF0 > + > +#define LNV_SE30_MAX_IO_RETRY_NUM 100 > + > +#define CHIPID_REG 0x20 > +#define SIO_REG 0x2e > +#define LDN_REG 0x07 > +#define UNLOCK_KEY 0x87 > +#define LOCK_KEY 0xAA > +#define LD_NUM_SHM 0x0F > +#define LD_BASE_ADDR 0xF8 > + > +#define WDT_MODULE 0x10 > +#define WDT_CFG_INDEX 0x15 /* WD configuration register */ > +#define WDT_CNT_INDEX 0x16 /* WD timer count register */ > +#define WDT_CFG_RESET 0x2 > + > +/* Host Interface WIN2 offset definition */ > +#define SHM_WIN_SIZE 0xFF > +#define SHM_WIN_MOD_OFFSET 0x01 > +#define SHM_WIN_CMD_OFFSET 0x02 > +#define SHM_WIN_SEL_OFFSET 0x03 > +#define SHM_WIN_CTL_OFFSET 0x04 > +#define VAL_SHM_WIN_CTRL_WR 0x40 > +#define VAL_SHM_WIN_CTRL_RD 0x80 > +#define SHM_WIN_ID_OFFSET 0x08 > +#define SHM_WIN_DAT_OFFSET 0x10 > + > +struct nct6692_shm { > + unsigned char __iomem *base_addr; > + unsigned long base_phys; > +}; > + > +struct nct6692_sio { > + unsigned long base_phys; > + int sioreg; > +}; > + > +struct nct6692_reg { > + unsigned char mod; > + unsigned char cmd; > + unsigned char sel; > + unsigned int idx; > +}; > + > +/* Watchdog is based on NCT6692 device */ > +struct lenovo_se30_wdt { > + struct nct6692_shm shm; > + struct nct6692_reg wdt_cfg; > + struct nct6692_reg wdt_cnt; > + struct nct6692_sio sio; > + struct watchdog_device wdt; > +}; > + > +static inline void superio_outb(int ioreg, int reg, int val) > +{ > + outb(reg, ioreg); > + outb(val, ioreg + 1); > +} > + > +static inline int superio_inb(int ioreg, int reg) > +{ > + outb(reg, ioreg); > + return inb(ioreg + 1); > +} > + > +static inline int superio_enter(int key, int addr, const char *name) > +{ > + if (!request_muxed_region(addr, 2, name)) { > + pr_err("I/O address 0x%04x already in use\n", addr); > + return -EBUSY; > + } > + outb(key, addr); /* Enter extended function mode */ > + outb(key, addr); /* Again according to manual */ > + > + return 0; > +} > + > +static inline void superio_exit(int key, int addr) > +{ > + outb(key, addr); /* Leave extended function mode */ > + release_region(addr, 2); > +} > + > +static int shm_get_ready(const struct nct6692_shm *shm, > + const struct nct6692_reg *reg) > +{ > + unsigned char pre_id, new_id; > + int i; > + > + iowrite8(reg->mod, shm->base_addr + SHM_WIN_MOD_OFFSET); > + iowrite8(reg->cmd, shm->base_addr + SHM_WIN_CMD_OFFSET); > + iowrite8(reg->sel, shm->base_addr + SHM_WIN_SEL_OFFSET); > + > + pre_id = ioread8(shm->base_addr + SHM_WIN_ID_OFFSET); > + iowrite8(VAL_SHM_WIN_CTRL_RD, shm->base_addr + SHM_WIN_CTL_OFFSET); > + > + /* Loop checking when interface is ready */ > + for (i = 0; i < LNV_SE30_MAX_IO_RETRY_NUM; i++) { > + new_id = ioread8(shm->base_addr + SHM_WIN_ID_OFFSET); > + if (new_id != pre_id) > + return 0; > + msleep(20); Can it really take up to 100 * 20 = 2,000 ms for the chip to become ready ? It seems to me that this huge timeout makes the driver pretty much pointless. > + } > + return -ETIMEDOUT; > +} > + > +static int read_shm_win(const struct nct6692_shm *shm, > + const struct nct6692_reg *reg, > + unsigned char idx_offset, > + unsigned char *data) > +{ > + int err = shm_get_ready(shm, reg); > + > + if (err) > + return err; > + *data = ioread8(shm->base_addr + SHM_WIN_DAT_OFFSET + reg->idx + idx_offset); > + return 0; > +} > + > +static int write_shm_win(const struct nct6692_shm *shm, > + const struct nct6692_reg *reg, > + unsigned char idx_offset, > + unsigned char val) > +{ > + int err = shm_get_ready(shm, reg); > + > + if (err) > + return err; > + iowrite8(val, shm->base_addr + SHM_WIN_DAT_OFFSET + reg->idx + idx_offset); > + iowrite8(VAL_SHM_WIN_CTRL_WR, shm->base_addr + SHM_WIN_CTL_OFFSET); > + err = shm_get_ready(shm, reg); > + return err; > +} > + > +static int lenovo_se30_wdt_enable(struct lenovo_se30_wdt *data, unsigned int timeout) > +{ > + if (timeout) { > + int err = write_shm_win(&data->shm, &data->wdt_cfg, 0, WDT_CFG_RESET); > + > + if (err) > + return err; > + } > + return write_shm_win(&data->shm, &data->wdt_cnt, 0, timeout); > +} > + > +static int lenovo_se30_wdt_start(struct watchdog_device *wdog) > +{ > + struct lenovo_se30_wdt *data = watchdog_get_drvdata(wdog); > + const struct nct6692_shm *shm = &data->shm; > + int err; > + > + if (!request_mem_region(shm->base_phys, SHM_WIN_SIZE, LNV_SE30_NAME)) > + return -EBUSY; > + Are any other drivers using this region ? If not, it would be better to request the region once in the probe function. If there are other drivers using it, why not use request_muxed_region() ? > + err = lenovo_se30_wdt_enable(data, wdog->timeout); > + release_mem_region(shm->base_phys, SHM_WIN_SIZE); > + return err; > +} > + > +static int lenovo_se30_wdt_stop(struct watchdog_device *wdog) > +{ > + struct lenovo_se30_wdt *data = watchdog_get_drvdata(wdog); > + const struct nct6692_shm *shm = &data->shm; > + int err; > + > + if (!request_mem_region(shm->base_phys, SHM_WIN_SIZE, LNV_SE30_NAME)) > + return -EBUSY; > + > + err = lenovo_se30_wdt_enable(data, 0); > + release_mem_region(shm->base_phys, SHM_WIN_SIZE); > + return err; > +} > + > +static int lenovo_se30_wdt_set_timeout(struct watchdog_device *wdog, > + unsigned int timeout) > +{ > + wdog->timeout = timeout; > + return 0; > +} > + > +static unsigned int lenovo_se30_wdt_get_timeleft(struct watchdog_device *wdog) > +{ > + struct lenovo_se30_wdt *data = watchdog_get_drvdata(wdog); > + const struct nct6692_shm *shm = &data->shm; > + unsigned char timeleft; > + int err; > + > + if (!request_mem_region(shm->base_phys, SHM_WIN_SIZE, LNV_SE30_NAME)) > + return -EBUSY; > + > + err = read_shm_win(&data->shm, &data->wdt_cnt, 0, &timeleft); > + release_mem_region(shm->base_phys, SHM_WIN_SIZE); > + if (err) > + return 0; > + return timeleft; > +} > + > +static int lenovo_se30_wdt_ping(struct watchdog_device *wdt) > +{ > + struct lenovo_se30_wdt *data = watchdog_get_drvdata(wdt); > + const struct nct6692_shm *shm = &data->shm; > + int err = 0; > + > + if (!request_mem_region(shm->base_phys, SHM_WIN_SIZE, LNV_SE30_NAME)) > + return -EBUSY; > + > + /* > + * Device does not support refreshing WDT_TIMER_REG register when > + * the watchdog is active. Need to disable, feed and enable again > + */ Seriously ? With the huge timeouts associated with each write, that can cause the watchdog to be disabled for each ping for several seconds. Diasabling can cause a delay of 2 seconds, plus four more seconds for re-enabling it. Is that even worth the trouble of providing a watchdog ? > + err = lenovo_se30_wdt_enable(data, 0); > + if (err) > + return err; This leaves the memory region unreleased. > + > + err = write_shm_win(&data->shm, &data->wdt_cnt, 0, wdt->timeout); > + if (!err) > + err = lenovo_se30_wdt_enable(data, wdt->timeout); > + > + release_mem_region(shm->base_phys, SHM_WIN_SIZE); > + return err; > +} > + > +static const struct watchdog_info lenovo_se30_wdt_info = { > + .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | > + WDIOF_MAGICCLOSE, > + .identity = "Lenovo SE30 watchdog", > +}; > + > +static const struct watchdog_ops lenovo_se30_wdt_ops = { > + .owner = THIS_MODULE, > + .start = lenovo_se30_wdt_start, > + .stop = lenovo_se30_wdt_stop, > + .ping = lenovo_se30_wdt_ping, > + .set_timeout = lenovo_se30_wdt_set_timeout, > + .get_timeleft = lenovo_se30_wdt_get_timeleft, > +}; > + > +static int lenovo_se30_wdt_probe(struct platform_device *pdev) > +{ > + struct device *dev = &pdev->dev; > + struct lenovo_se30_wdt *priv; > + unsigned long base_phys; > + unsigned short val; > + int err; > + > + err = superio_enter(UNLOCK_KEY, SIO_REG, LNV_SE30_NAME); > + if (err) > + return err; > + > + val = superio_inb(SIO_REG, CHIPID_REG) << 8; > + val |= superio_inb(SIO_REG, CHIPID_REG + 1); > + > + if ((val & CHIPID_MASK) != LNV_SE30_ID) { > + superio_exit(LOCK_KEY, SIO_REG); > + return -ENODEV; > + } > + > + superio_outb(SIO_REG, LDN_REG, LD_NUM_SHM); > + base_phys = (superio_inb(SIO_REG, LD_BASE_ADDR) | > + (superio_inb(SIO_REG, LD_BASE_ADDR + 1) << 8) | > + (superio_inb(SIO_REG, LD_BASE_ADDR + 2) << 16) | > + (superio_inb(SIO_REG, LD_BASE_ADDR + 3) << 24)) & > + 0xFFFFFFFF; > + > + superio_exit(LOCK_KEY, SIO_REG); > + if (base_phys == 0xFFFFFFFF || base_phys == 0) > + return -ENODEV; > + > + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); > + if (!priv) > + return -ENOMEM; > + > + priv->sio.base_phys = base_phys; > + priv->shm.base_phys = base_phys; > + priv->shm.base_addr = ioremap_cache(priv->shm.base_phys, SHM_WIN_SIZE); > + > + priv->wdt_cfg.mod = WDT_MODULE; > + priv->wdt_cfg.idx = WDT_CFG_INDEX; > + priv->wdt_cnt.mod = WDT_MODULE; > + priv->wdt_cnt.idx = WDT_CNT_INDEX; > + > + priv->wdt.ops = &lenovo_se30_wdt_ops; > + priv->wdt.info = &lenovo_se30_wdt_info; > + priv->wdt.timeout = WATCHDOG_TIMEOUT; /* Set default timeout */ > + priv->wdt.min_timeout = MIN_TIMEOUT; > + priv->wdt.max_timeout = MAX_TIMEOUT; > + priv->wdt.parent = dev; > + > + watchdog_init_timeout(&priv->wdt, timeout, dev); > + watchdog_set_drvdata(&priv->wdt, priv); > + watchdog_set_nowayout(&priv->wdt, nowayout); > + watchdog_stop_on_reboot(&priv->wdt); > + watchdog_stop_on_unregister(&priv->wdt); > + > + return devm_watchdog_register_device(dev, &priv->wdt); > +} > + > +static struct platform_device *pdev; > + > +static struct platform_driver lenovo_se30_wdt_driver = { > + .driver = { > + .name = LNV_SE30_NAME, > + }, > + .probe = lenovo_se30_wdt_probe, > +}; > + > +static int lenovo_se30_create_platform_device(const struct dmi_system_id *id) > +{ > + int err; > + > + pdev = platform_device_alloc(LNV_SE30_NAME, -1); > + if (!pdev) > + return -ENOMEM; > + > + err = platform_device_add(pdev); > + if (err) > + platform_device_put(pdev); > + > + return err; > +} > + > +static const struct dmi_system_id lenovo_se30_wdt_dmi_table[] __initconst = { > + { > + .ident = "LENOVO-SE30", > + .matches = { > + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), > + DMI_MATCH(DMI_PRODUCT_NAME, "11NA"), > + }, > + .callback = lenovo_se30_create_platform_device, > + }, > + { > + .ident = "LENOVO-SE30", > + .matches = { > + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), > + DMI_MATCH(DMI_PRODUCT_NAME, "11NB"), > + }, > + .callback = lenovo_se30_create_platform_device, > + }, > + { > + .ident = "LENOVO-SE30", > + .matches = { > + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), > + DMI_MATCH(DMI_PRODUCT_NAME, "11NC"), > + }, > + .callback = lenovo_se30_create_platform_device, > + }, > + { > + .ident = "LENOVO-SE30", > + .matches = { > + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), > + DMI_MATCH(DMI_PRODUCT_NAME, "11NH"), > + }, > + .callback = lenovo_se30_create_platform_device, > + }, > + { > + .ident = "LENOVO-SE30", > + .matches = { > + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), > + DMI_MATCH(DMI_PRODUCT_NAME, "11NJ"), > + }, > + .callback = lenovo_se30_create_platform_device, > + }, > + { > + .ident = "LENOVO-SE30", > + .matches = { > + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), > + DMI_MATCH(DMI_PRODUCT_NAME, "11NK"), > + }, > + .callback = lenovo_se30_create_platform_device, > + }, > + {} > +}; > +MODULE_DEVICE_TABLE(dmi, lenovo_se30_wdt_dmi_table); > + > +static int __init lenovo_se30_wdt_init(void) > +{ > + if (!dmi_check_system(lenovo_se30_wdt_dmi_table)) > + return -ENODEV; > + > + return platform_driver_register(&lenovo_se30_wdt_driver); > +} > + > +static void __exit lenovo_se30_wdt_exit(void) > +{ > + if (pdev) > + platform_device_unregister(pdev); > + platform_driver_unregister(&lenovo_se30_wdt_driver); > +} > + > +module_init(lenovo_se30_wdt_init); > +module_exit(lenovo_se30_wdt_exit); > + > +MODULE_AUTHOR("Mark Pearson <mpearson-lenovo@squebb.ca>"); > +MODULE_AUTHOR("David Ober <dober@lenovo.com>"); > +MODULE_DESCRIPTION("Lenovo SE30 watchdog driver"); > +MODULE_LICENSE("GPL");
Hi Mark, kernel test robot noticed the following build warnings: [auto build test WARNING on groeck-staging/hwmon-next] [also build test WARNING on linus/master v6.13-rc7 next-20250116] [If your patch is applied to the wrong git tree, kindly drop us a note. And when submitting patch, we suggest to use '--base' as documented in https://git-scm.com/docs/git-format-patch#_base_tree_information] url: https://github.com/intel-lab-lkp/linux/commits/Mark-Pearson/watchdog-lenovo_se30_wdt-Watchdog-driver-for-Lenovo-SE30-platform/20250115-220703 base: https://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging.git hwmon-next patch link: https://lore.kernel.org/r/20250115140510.2017-1-mpearson-lenovo%40squebb.ca patch subject: [PATCH] watchdog: lenovo_se30_wdt: Watchdog driver for Lenovo SE30 platform config: um-allyesconfig (https://download.01.org/0day-ci/archive/20250116/202501162358.vgAIFntg-lkp@intel.com/config) compiler: gcc-12 (Debian 12.2.0-14) 12.2.0 reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20250116/202501162358.vgAIFntg-lkp@intel.com/reproduce) If you fix the issue in a separate patch/commit (i.e. not just a new version of the same patch/commit), kindly add following tags | Reported-by: kernel test robot <lkp@intel.com> | Closes: https://lore.kernel.org/oe-kbuild-all/202501162358.vgAIFntg-lkp@intel.com/ All warnings (new ones prefixed by >>): drivers/watchdog/lenovo_se30_wdt.c: In function 'lenovo_se30_wdt_probe': drivers/watchdog/lenovo_se30_wdt.c:314:31: error: implicit declaration of function 'ioremap_cache'; did you mean 'ioremap_uc'? [-Werror=implicit-function-declaration] 314 | priv->shm.base_addr = ioremap_cache(priv->shm.base_phys, SHM_WIN_SIZE); | ^~~~~~~~~~~~~ | ioremap_uc >> drivers/watchdog/lenovo_se30_wdt.c:314:29: warning: assignment to 'unsigned char *' from 'int' makes pointer from integer without a cast [-Wint-conversion] 314 | priv->shm.base_addr = ioremap_cache(priv->shm.base_phys, SHM_WIN_SIZE); | ^ cc1: some warnings being treated as errors vim +314 drivers/watchdog/lenovo_se30_wdt.c 276 277 static int lenovo_se30_wdt_probe(struct platform_device *pdev) 278 { 279 struct device *dev = &pdev->dev; 280 struct lenovo_se30_wdt *priv; 281 unsigned long base_phys; 282 unsigned short val; 283 int err; 284 285 err = superio_enter(UNLOCK_KEY, SIO_REG, LNV_SE30_NAME); 286 if (err) 287 return err; 288 289 val = superio_inb(SIO_REG, CHIPID_REG) << 8; 290 val |= superio_inb(SIO_REG, CHIPID_REG + 1); 291 292 if ((val & CHIPID_MASK) != LNV_SE30_ID) { 293 superio_exit(LOCK_KEY, SIO_REG); 294 return -ENODEV; 295 } 296 297 superio_outb(SIO_REG, LDN_REG, LD_NUM_SHM); 298 base_phys = (superio_inb(SIO_REG, LD_BASE_ADDR) | 299 (superio_inb(SIO_REG, LD_BASE_ADDR + 1) << 8) | 300 (superio_inb(SIO_REG, LD_BASE_ADDR + 2) << 16) | 301 (superio_inb(SIO_REG, LD_BASE_ADDR + 3) << 24)) & 302 0xFFFFFFFF; 303 304 superio_exit(LOCK_KEY, SIO_REG); 305 if (base_phys == 0xFFFFFFFF || base_phys == 0) 306 return -ENODEV; 307 308 priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); 309 if (!priv) 310 return -ENOMEM; 311 312 priv->sio.base_phys = base_phys; 313 priv->shm.base_phys = base_phys; > 314 priv->shm.base_addr = ioremap_cache(priv->shm.base_phys, SHM_WIN_SIZE); 315 316 priv->wdt_cfg.mod = WDT_MODULE; 317 priv->wdt_cfg.idx = WDT_CFG_INDEX; 318 priv->wdt_cnt.mod = WDT_MODULE; 319 priv->wdt_cnt.idx = WDT_CNT_INDEX; 320 321 priv->wdt.ops = &lenovo_se30_wdt_ops; 322 priv->wdt.info = &lenovo_se30_wdt_info; 323 priv->wdt.timeout = WATCHDOG_TIMEOUT; /* Set default timeout */ 324 priv->wdt.min_timeout = MIN_TIMEOUT; 325 priv->wdt.max_timeout = MAX_TIMEOUT; 326 priv->wdt.parent = dev; 327 328 watchdog_init_timeout(&priv->wdt, timeout, dev); 329 watchdog_set_drvdata(&priv->wdt, priv); 330 watchdog_set_nowayout(&priv->wdt, nowayout); 331 watchdog_stop_on_reboot(&priv->wdt); 332 watchdog_stop_on_unregister(&priv->wdt); 333 334 return devm_watchdog_register_device(dev, &priv->wdt); 335 } 336
Hi Mark, kernel test robot noticed the following build errors: [auto build test ERROR on groeck-staging/hwmon-next] [also build test ERROR on linus/master v6.13-rc7 next-20250117] [If your patch is applied to the wrong git tree, kindly drop us a note. And when submitting patch, we suggest to use '--base' as documented in https://git-scm.com/docs/git-format-patch#_base_tree_information] url: https://github.com/intel-lab-lkp/linux/commits/Mark-Pearson/watchdog-lenovo_se30_wdt-Watchdog-driver-for-Lenovo-SE30-platform/20250115-220703 base: https://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging.git hwmon-next patch link: https://lore.kernel.org/r/20250115140510.2017-1-mpearson-lenovo%40squebb.ca patch subject: [PATCH] watchdog: lenovo_se30_wdt: Watchdog driver for Lenovo SE30 platform config: riscv-allmodconfig (https://download.01.org/0day-ci/archive/20250118/202501180914.vD2HyOZ2-lkp@intel.com/config) compiler: clang version 20.0.0git (https://github.com/llvm/llvm-project f5cd181ffbb7cb61d582fe130d46580d5969d47a) reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20250118/202501180914.vD2HyOZ2-lkp@intel.com/reproduce) If you fix the issue in a separate patch/commit (i.e. not just a new version of the same patch/commit), kindly add following tags | Reported-by: kernel test robot <lkp@intel.com> | Closes: https://lore.kernel.org/oe-kbuild-all/202501180914.vD2HyOZ2-lkp@intel.com/ All errors (new ones prefixed by >>): In file included from drivers/watchdog/lenovo_se30_wdt.c:11: In file included from include/linux/iommu.h:10: In file included from include/linux/scatterlist.h:8: In file included from include/linux/mm.h:2223: include/linux/vmstat.h:504:43: warning: arithmetic between different enumeration types ('enum zone_stat_item' and 'enum numa_stat_item') [-Wenum-enum-conversion] 504 | return vmstat_text[NR_VM_ZONE_STAT_ITEMS + | ~~~~~~~~~~~~~~~~~~~~~ ^ 505 | item]; | ~~~~ include/linux/vmstat.h:511:43: warning: arithmetic between different enumeration types ('enum zone_stat_item' and 'enum numa_stat_item') [-Wenum-enum-conversion] 511 | return vmstat_text[NR_VM_ZONE_STAT_ITEMS + | ~~~~~~~~~~~~~~~~~~~~~ ^ 512 | NR_VM_NUMA_EVENT_ITEMS + | ~~~~~~~~~~~~~~~~~~~~~~ include/linux/vmstat.h:518:36: warning: arithmetic between different enumeration types ('enum node_stat_item' and 'enum lru_list') [-Wenum-enum-conversion] 518 | return node_stat_name(NR_LRU_BASE + lru) + 3; // skip "nr_" | ~~~~~~~~~~~ ^ ~~~ include/linux/vmstat.h:524:43: warning: arithmetic between different enumeration types ('enum zone_stat_item' and 'enum numa_stat_item') [-Wenum-enum-conversion] 524 | return vmstat_text[NR_VM_ZONE_STAT_ITEMS + | ~~~~~~~~~~~~~~~~~~~~~ ^ 525 | NR_VM_NUMA_EVENT_ITEMS + | ~~~~~~~~~~~~~~~~~~~~~~ >> drivers/watchdog/lenovo_se30_wdt.c:314:24: error: call to undeclared function 'ioremap_cache'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration] 314 | priv->shm.base_addr = ioremap_cache(priv->shm.base_phys, SHM_WIN_SIZE); | ^ drivers/watchdog/lenovo_se30_wdt.c:314:24: note: did you mean 'ioremap_uc'? include/asm-generic/io.h:1145:29: note: 'ioremap_uc' declared here 1145 | static inline void __iomem *ioremap_uc(phys_addr_t offset, size_t size) | ^ include/asm-generic/io.h:1144:20: note: expanded from macro 'ioremap_uc' 1144 | #define ioremap_uc ioremap_uc | ^ >> drivers/watchdog/lenovo_se30_wdt.c:314:22: error: incompatible integer to pointer conversion assigning to 'unsigned char *' from 'int' [-Wint-conversion] 314 | priv->shm.base_addr = ioremap_cache(priv->shm.base_phys, SHM_WIN_SIZE); | ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 4 warnings and 2 errors generated. vim +/ioremap_cache +314 drivers/watchdog/lenovo_se30_wdt.c 276 277 static int lenovo_se30_wdt_probe(struct platform_device *pdev) 278 { 279 struct device *dev = &pdev->dev; 280 struct lenovo_se30_wdt *priv; 281 unsigned long base_phys; 282 unsigned short val; 283 int err; 284 285 err = superio_enter(UNLOCK_KEY, SIO_REG, LNV_SE30_NAME); 286 if (err) 287 return err; 288 289 val = superio_inb(SIO_REG, CHIPID_REG) << 8; 290 val |= superio_inb(SIO_REG, CHIPID_REG + 1); 291 292 if ((val & CHIPID_MASK) != LNV_SE30_ID) { 293 superio_exit(LOCK_KEY, SIO_REG); 294 return -ENODEV; 295 } 296 297 superio_outb(SIO_REG, LDN_REG, LD_NUM_SHM); 298 base_phys = (superio_inb(SIO_REG, LD_BASE_ADDR) | 299 (superio_inb(SIO_REG, LD_BASE_ADDR + 1) << 8) | 300 (superio_inb(SIO_REG, LD_BASE_ADDR + 2) << 16) | 301 (superio_inb(SIO_REG, LD_BASE_ADDR + 3) << 24)) & 302 0xFFFFFFFF; 303 304 superio_exit(LOCK_KEY, SIO_REG); 305 if (base_phys == 0xFFFFFFFF || base_phys == 0) 306 return -ENODEV; 307 308 priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); 309 if (!priv) 310 return -ENOMEM; 311 312 priv->sio.base_phys = base_phys; 313 priv->shm.base_phys = base_phys; > 314 priv->shm.base_addr = ioremap_cache(priv->shm.base_phys, SHM_WIN_SIZE); 315 316 priv->wdt_cfg.mod = WDT_MODULE; 317 priv->wdt_cfg.idx = WDT_CFG_INDEX; 318 priv->wdt_cnt.mod = WDT_MODULE; 319 priv->wdt_cnt.idx = WDT_CNT_INDEX; 320 321 priv->wdt.ops = &lenovo_se30_wdt_ops; 322 priv->wdt.info = &lenovo_se30_wdt_info; 323 priv->wdt.timeout = WATCHDOG_TIMEOUT; /* Set default timeout */ 324 priv->wdt.min_timeout = MIN_TIMEOUT; 325 priv->wdt.max_timeout = MAX_TIMEOUT; 326 priv->wdt.parent = dev; 327 328 watchdog_init_timeout(&priv->wdt, timeout, dev); 329 watchdog_set_drvdata(&priv->wdt, priv); 330 watchdog_set_nowayout(&priv->wdt, nowayout); 331 watchdog_stop_on_reboot(&priv->wdt); 332 watchdog_stop_on_unregister(&priv->wdt); 333 334 return devm_watchdog_register_device(dev, &priv->wdt); 335 } 336
Hi Mark, kernel test robot noticed the following build errors: [auto build test ERROR on groeck-staging/hwmon-next] [also build test ERROR on linus/master v6.13-rc7 next-20250117] [If your patch is applied to the wrong git tree, kindly drop us a note. And when submitting patch, we suggest to use '--base' as documented in https://git-scm.com/docs/git-format-patch#_base_tree_information] url: https://github.com/intel-lab-lkp/linux/commits/Mark-Pearson/watchdog-lenovo_se30_wdt-Watchdog-driver-for-Lenovo-SE30-platform/20250115-220703 base: https://git.kernel.org/pub/scm/linux/kernel/git/groeck/linux-staging.git hwmon-next patch link: https://lore.kernel.org/r/20250115140510.2017-1-mpearson-lenovo%40squebb.ca patch subject: [PATCH] watchdog: lenovo_se30_wdt: Watchdog driver for Lenovo SE30 platform config: m68k-allmodconfig (https://download.01.org/0day-ci/archive/20250118/202501181145.cTfnETL4-lkp@intel.com/config) compiler: m68k-linux-gcc (GCC) 14.2.0 reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20250118/202501181145.cTfnETL4-lkp@intel.com/reproduce) If you fix the issue in a separate patch/commit (i.e. not just a new version of the same patch/commit), kindly add following tags | Reported-by: kernel test robot <lkp@intel.com> | Closes: https://lore.kernel.org/oe-kbuild-all/202501181145.cTfnETL4-lkp@intel.com/ All errors (new ones prefixed by >>): drivers/watchdog/lenovo_se30_wdt.c: In function 'lenovo_se30_wdt_probe': >> drivers/watchdog/lenovo_se30_wdt.c:314:31: error: implicit declaration of function 'ioremap_cache'; did you mean 'ioremap_uc'? [-Wimplicit-function-declaration] 314 | priv->shm.base_addr = ioremap_cache(priv->shm.base_phys, SHM_WIN_SIZE); | ^~~~~~~~~~~~~ | ioremap_uc >> drivers/watchdog/lenovo_se30_wdt.c:314:29: error: assignment to 'unsigned char *' from 'int' makes pointer from integer without a cast [-Wint-conversion] 314 | priv->shm.base_addr = ioremap_cache(priv->shm.base_phys, SHM_WIN_SIZE); | ^ vim +314 drivers/watchdog/lenovo_se30_wdt.c 276 277 static int lenovo_se30_wdt_probe(struct platform_device *pdev) 278 { 279 struct device *dev = &pdev->dev; 280 struct lenovo_se30_wdt *priv; 281 unsigned long base_phys; 282 unsigned short val; 283 int err; 284 285 err = superio_enter(UNLOCK_KEY, SIO_REG, LNV_SE30_NAME); 286 if (err) 287 return err; 288 289 val = superio_inb(SIO_REG, CHIPID_REG) << 8; 290 val |= superio_inb(SIO_REG, CHIPID_REG + 1); 291 292 if ((val & CHIPID_MASK) != LNV_SE30_ID) { 293 superio_exit(LOCK_KEY, SIO_REG); 294 return -ENODEV; 295 } 296 297 superio_outb(SIO_REG, LDN_REG, LD_NUM_SHM); 298 base_phys = (superio_inb(SIO_REG, LD_BASE_ADDR) | 299 (superio_inb(SIO_REG, LD_BASE_ADDR + 1) << 8) | 300 (superio_inb(SIO_REG, LD_BASE_ADDR + 2) << 16) | 301 (superio_inb(SIO_REG, LD_BASE_ADDR + 3) << 24)) & 302 0xFFFFFFFF; 303 304 superio_exit(LOCK_KEY, SIO_REG); 305 if (base_phys == 0xFFFFFFFF || base_phys == 0) 306 return -ENODEV; 307 308 priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); 309 if (!priv) 310 return -ENOMEM; 311 312 priv->sio.base_phys = base_phys; 313 priv->shm.base_phys = base_phys; > 314 priv->shm.base_addr = ioremap_cache(priv->shm.base_phys, SHM_WIN_SIZE); 315 316 priv->wdt_cfg.mod = WDT_MODULE; 317 priv->wdt_cfg.idx = WDT_CFG_INDEX; 318 priv->wdt_cnt.mod = WDT_MODULE; 319 priv->wdt_cnt.idx = WDT_CNT_INDEX; 320 321 priv->wdt.ops = &lenovo_se30_wdt_ops; 322 priv->wdt.info = &lenovo_se30_wdt_info; 323 priv->wdt.timeout = WATCHDOG_TIMEOUT; /* Set default timeout */ 324 priv->wdt.min_timeout = MIN_TIMEOUT; 325 priv->wdt.max_timeout = MAX_TIMEOUT; 326 priv->wdt.parent = dev; 327 328 watchdog_init_timeout(&priv->wdt, timeout, dev); 329 watchdog_set_drvdata(&priv->wdt, priv); 330 watchdog_set_nowayout(&priv->wdt, nowayout); 331 watchdog_stop_on_reboot(&priv->wdt); 332 watchdog_stop_on_unregister(&priv->wdt); 333 334 return devm_watchdog_register_device(dev, &priv->wdt); 335 } 336
diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index f81705f8539a..c73e8f0e436c 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -279,6 +279,18 @@ config LENOVO_SE10_WDT This driver can also be built as a module. If so, the module will be called lenovo-se10-wdt. +config LENOVO_SE30_WDT + tristate "Lenovo SE30 Watchdog" + depends on (X86 && DMI) || COMPILE_TEST + depends on HAS_IOPORT + select WATCHDOG_CORE + help + If you say yes here you get support for the watchdog + functionality for the Lenovo SE30 platform. + + This driver can also be built as a module. If so, the module + will be called lenovo-se30-wdt. + config MENF21BMC_WATCHDOG tristate "MEN 14F021P00 BMC Watchdog" depends on MFD_MENF21BMC || COMPILE_TEST diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index 8411626fa162..c9482904bf87 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -124,6 +124,7 @@ obj-$(CONFIG_I6300ESB_WDT) += i6300esb.o obj-$(CONFIG_IE6XX_WDT) += ie6xx_wdt.o obj-$(CONFIG_ITCO_WDT) += iTCO_wdt.o obj-$(CONFIG_LENOVO_SE10_WDT) += lenovo_se10_wdt.o +obj-$(CONFIG_LENOVO_SE30_WDT) += lenovo_se30_wdt.o ifeq ($(CONFIG_ITCO_VENDOR_SUPPORT),y) obj-$(CONFIG_ITCO_WDT) += iTCO_vendor_support.o endif diff --git a/drivers/watchdog/lenovo_se30_wdt.c b/drivers/watchdog/lenovo_se30_wdt.c new file mode 100644 index 000000000000..d119f6fe870a --- /dev/null +++ b/drivers/watchdog/lenovo_se30_wdt.c @@ -0,0 +1,435 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * WDT driver for Lenovo SE30 device + */ + +#define dev_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/acpi.h> +#include <linux/dmi.h> +#include <linux/delay.h> +#include <linux/iommu.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/platform_device.h> +#include <linux/watchdog.h> + +#define IOREGION_OFFSET 4 /* Use EC port 1 */ +#define IOREGION_LENGTH 4 + +#define WATCHDOG_TIMEOUT 60 +#define MIN_TIMEOUT 1 +#define MAX_TIMEOUT 255 + +static int timeout; /* in seconds */ +module_param(timeout, int, 0); +MODULE_PARM_DESC(timeout, + "Watchdog timeout in seconds. 1 <= timeout <= 255, default=" + __MODULE_STRING(WATCHDOG_TIMEOUT) "."); + +static bool nowayout = WATCHDOG_NOWAYOUT; +module_param(nowayout, bool, 0); +MODULE_PARM_DESC(nowayout, + "Watchdog cannot be stopped once started (default=" + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); + +#define LNV_SE30_NAME "lenovo-se30-wdt" +#define LNV_SE30_ID 0x0110 +#define CHIPID_MASK 0xFFF0 + +#define LNV_SE30_MAX_IO_RETRY_NUM 100 + +#define CHIPID_REG 0x20 +#define SIO_REG 0x2e +#define LDN_REG 0x07 +#define UNLOCK_KEY 0x87 +#define LOCK_KEY 0xAA +#define LD_NUM_SHM 0x0F +#define LD_BASE_ADDR 0xF8 + +#define WDT_MODULE 0x10 +#define WDT_CFG_INDEX 0x15 /* WD configuration register */ +#define WDT_CNT_INDEX 0x16 /* WD timer count register */ +#define WDT_CFG_RESET 0x2 + +/* Host Interface WIN2 offset definition */ +#define SHM_WIN_SIZE 0xFF +#define SHM_WIN_MOD_OFFSET 0x01 +#define SHM_WIN_CMD_OFFSET 0x02 +#define SHM_WIN_SEL_OFFSET 0x03 +#define SHM_WIN_CTL_OFFSET 0x04 +#define VAL_SHM_WIN_CTRL_WR 0x40 +#define VAL_SHM_WIN_CTRL_RD 0x80 +#define SHM_WIN_ID_OFFSET 0x08 +#define SHM_WIN_DAT_OFFSET 0x10 + +struct nct6692_shm { + unsigned char __iomem *base_addr; + unsigned long base_phys; +}; + +struct nct6692_sio { + unsigned long base_phys; + int sioreg; +}; + +struct nct6692_reg { + unsigned char mod; + unsigned char cmd; + unsigned char sel; + unsigned int idx; +}; + +/* Watchdog is based on NCT6692 device */ +struct lenovo_se30_wdt { + struct nct6692_shm shm; + struct nct6692_reg wdt_cfg; + struct nct6692_reg wdt_cnt; + struct nct6692_sio sio; + struct watchdog_device wdt; +}; + +static inline void superio_outb(int ioreg, int reg, int val) +{ + outb(reg, ioreg); + outb(val, ioreg + 1); +} + +static inline int superio_inb(int ioreg, int reg) +{ + outb(reg, ioreg); + return inb(ioreg + 1); +} + +static inline int superio_enter(int key, int addr, const char *name) +{ + if (!request_muxed_region(addr, 2, name)) { + pr_err("I/O address 0x%04x already in use\n", addr); + return -EBUSY; + } + outb(key, addr); /* Enter extended function mode */ + outb(key, addr); /* Again according to manual */ + + return 0; +} + +static inline void superio_exit(int key, int addr) +{ + outb(key, addr); /* Leave extended function mode */ + release_region(addr, 2); +} + +static int shm_get_ready(const struct nct6692_shm *shm, + const struct nct6692_reg *reg) +{ + unsigned char pre_id, new_id; + int i; + + iowrite8(reg->mod, shm->base_addr + SHM_WIN_MOD_OFFSET); + iowrite8(reg->cmd, shm->base_addr + SHM_WIN_CMD_OFFSET); + iowrite8(reg->sel, shm->base_addr + SHM_WIN_SEL_OFFSET); + + pre_id = ioread8(shm->base_addr + SHM_WIN_ID_OFFSET); + iowrite8(VAL_SHM_WIN_CTRL_RD, shm->base_addr + SHM_WIN_CTL_OFFSET); + + /* Loop checking when interface is ready */ + for (i = 0; i < LNV_SE30_MAX_IO_RETRY_NUM; i++) { + new_id = ioread8(shm->base_addr + SHM_WIN_ID_OFFSET); + if (new_id != pre_id) + return 0; + msleep(20); + } + return -ETIMEDOUT; +} + +static int read_shm_win(const struct nct6692_shm *shm, + const struct nct6692_reg *reg, + unsigned char idx_offset, + unsigned char *data) +{ + int err = shm_get_ready(shm, reg); + + if (err) + return err; + *data = ioread8(shm->base_addr + SHM_WIN_DAT_OFFSET + reg->idx + idx_offset); + return 0; +} + +static int write_shm_win(const struct nct6692_shm *shm, + const struct nct6692_reg *reg, + unsigned char idx_offset, + unsigned char val) +{ + int err = shm_get_ready(shm, reg); + + if (err) + return err; + iowrite8(val, shm->base_addr + SHM_WIN_DAT_OFFSET + reg->idx + idx_offset); + iowrite8(VAL_SHM_WIN_CTRL_WR, shm->base_addr + SHM_WIN_CTL_OFFSET); + err = shm_get_ready(shm, reg); + return err; +} + +static int lenovo_se30_wdt_enable(struct lenovo_se30_wdt *data, unsigned int timeout) +{ + if (timeout) { + int err = write_shm_win(&data->shm, &data->wdt_cfg, 0, WDT_CFG_RESET); + + if (err) + return err; + } + return write_shm_win(&data->shm, &data->wdt_cnt, 0, timeout); +} + +static int lenovo_se30_wdt_start(struct watchdog_device *wdog) +{ + struct lenovo_se30_wdt *data = watchdog_get_drvdata(wdog); + const struct nct6692_shm *shm = &data->shm; + int err; + + if (!request_mem_region(shm->base_phys, SHM_WIN_SIZE, LNV_SE30_NAME)) + return -EBUSY; + + err = lenovo_se30_wdt_enable(data, wdog->timeout); + release_mem_region(shm->base_phys, SHM_WIN_SIZE); + return err; +} + +static int lenovo_se30_wdt_stop(struct watchdog_device *wdog) +{ + struct lenovo_se30_wdt *data = watchdog_get_drvdata(wdog); + const struct nct6692_shm *shm = &data->shm; + int err; + + if (!request_mem_region(shm->base_phys, SHM_WIN_SIZE, LNV_SE30_NAME)) + return -EBUSY; + + err = lenovo_se30_wdt_enable(data, 0); + release_mem_region(shm->base_phys, SHM_WIN_SIZE); + return err; +} + +static int lenovo_se30_wdt_set_timeout(struct watchdog_device *wdog, + unsigned int timeout) +{ + wdog->timeout = timeout; + return 0; +} + +static unsigned int lenovo_se30_wdt_get_timeleft(struct watchdog_device *wdog) +{ + struct lenovo_se30_wdt *data = watchdog_get_drvdata(wdog); + const struct nct6692_shm *shm = &data->shm; + unsigned char timeleft; + int err; + + if (!request_mem_region(shm->base_phys, SHM_WIN_SIZE, LNV_SE30_NAME)) + return -EBUSY; + + err = read_shm_win(&data->shm, &data->wdt_cnt, 0, &timeleft); + release_mem_region(shm->base_phys, SHM_WIN_SIZE); + if (err) + return 0; + return timeleft; +} + +static int lenovo_se30_wdt_ping(struct watchdog_device *wdt) +{ + struct lenovo_se30_wdt *data = watchdog_get_drvdata(wdt); + const struct nct6692_shm *shm = &data->shm; + int err = 0; + + if (!request_mem_region(shm->base_phys, SHM_WIN_SIZE, LNV_SE30_NAME)) + return -EBUSY; + + /* + * Device does not support refreshing WDT_TIMER_REG register when + * the watchdog is active. Need to disable, feed and enable again + */ + err = lenovo_se30_wdt_enable(data, 0); + if (err) + return err; + + err = write_shm_win(&data->shm, &data->wdt_cnt, 0, wdt->timeout); + if (!err) + err = lenovo_se30_wdt_enable(data, wdt->timeout); + + release_mem_region(shm->base_phys, SHM_WIN_SIZE); + return err; +} + +static const struct watchdog_info lenovo_se30_wdt_info = { + .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | + WDIOF_MAGICCLOSE, + .identity = "Lenovo SE30 watchdog", +}; + +static const struct watchdog_ops lenovo_se30_wdt_ops = { + .owner = THIS_MODULE, + .start = lenovo_se30_wdt_start, + .stop = lenovo_se30_wdt_stop, + .ping = lenovo_se30_wdt_ping, + .set_timeout = lenovo_se30_wdt_set_timeout, + .get_timeleft = lenovo_se30_wdt_get_timeleft, +}; + +static int lenovo_se30_wdt_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct lenovo_se30_wdt *priv; + unsigned long base_phys; + unsigned short val; + int err; + + err = superio_enter(UNLOCK_KEY, SIO_REG, LNV_SE30_NAME); + if (err) + return err; + + val = superio_inb(SIO_REG, CHIPID_REG) << 8; + val |= superio_inb(SIO_REG, CHIPID_REG + 1); + + if ((val & CHIPID_MASK) != LNV_SE30_ID) { + superio_exit(LOCK_KEY, SIO_REG); + return -ENODEV; + } + + superio_outb(SIO_REG, LDN_REG, LD_NUM_SHM); + base_phys = (superio_inb(SIO_REG, LD_BASE_ADDR) | + (superio_inb(SIO_REG, LD_BASE_ADDR + 1) << 8) | + (superio_inb(SIO_REG, LD_BASE_ADDR + 2) << 16) | + (superio_inb(SIO_REG, LD_BASE_ADDR + 3) << 24)) & + 0xFFFFFFFF; + + superio_exit(LOCK_KEY, SIO_REG); + if (base_phys == 0xFFFFFFFF || base_phys == 0) + return -ENODEV; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->sio.base_phys = base_phys; + priv->shm.base_phys = base_phys; + priv->shm.base_addr = ioremap_cache(priv->shm.base_phys, SHM_WIN_SIZE); + + priv->wdt_cfg.mod = WDT_MODULE; + priv->wdt_cfg.idx = WDT_CFG_INDEX; + priv->wdt_cnt.mod = WDT_MODULE; + priv->wdt_cnt.idx = WDT_CNT_INDEX; + + priv->wdt.ops = &lenovo_se30_wdt_ops; + priv->wdt.info = &lenovo_se30_wdt_info; + priv->wdt.timeout = WATCHDOG_TIMEOUT; /* Set default timeout */ + priv->wdt.min_timeout = MIN_TIMEOUT; + priv->wdt.max_timeout = MAX_TIMEOUT; + priv->wdt.parent = dev; + + watchdog_init_timeout(&priv->wdt, timeout, dev); + watchdog_set_drvdata(&priv->wdt, priv); + watchdog_set_nowayout(&priv->wdt, nowayout); + watchdog_stop_on_reboot(&priv->wdt); + watchdog_stop_on_unregister(&priv->wdt); + + return devm_watchdog_register_device(dev, &priv->wdt); +} + +static struct platform_device *pdev; + +static struct platform_driver lenovo_se30_wdt_driver = { + .driver = { + .name = LNV_SE30_NAME, + }, + .probe = lenovo_se30_wdt_probe, +}; + +static int lenovo_se30_create_platform_device(const struct dmi_system_id *id) +{ + int err; + + pdev = platform_device_alloc(LNV_SE30_NAME, -1); + if (!pdev) + return -ENOMEM; + + err = platform_device_add(pdev); + if (err) + platform_device_put(pdev); + + return err; +} + +static const struct dmi_system_id lenovo_se30_wdt_dmi_table[] __initconst = { + { + .ident = "LENOVO-SE30", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "11NA"), + }, + .callback = lenovo_se30_create_platform_device, + }, + { + .ident = "LENOVO-SE30", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "11NB"), + }, + .callback = lenovo_se30_create_platform_device, + }, + { + .ident = "LENOVO-SE30", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "11NC"), + }, + .callback = lenovo_se30_create_platform_device, + }, + { + .ident = "LENOVO-SE30", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "11NH"), + }, + .callback = lenovo_se30_create_platform_device, + }, + { + .ident = "LENOVO-SE30", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "11NJ"), + }, + .callback = lenovo_se30_create_platform_device, + }, + { + .ident = "LENOVO-SE30", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), + DMI_MATCH(DMI_PRODUCT_NAME, "11NK"), + }, + .callback = lenovo_se30_create_platform_device, + }, + {} +}; +MODULE_DEVICE_TABLE(dmi, lenovo_se30_wdt_dmi_table); + +static int __init lenovo_se30_wdt_init(void) +{ + if (!dmi_check_system(lenovo_se30_wdt_dmi_table)) + return -ENODEV; + + return platform_driver_register(&lenovo_se30_wdt_driver); +} + +static void __exit lenovo_se30_wdt_exit(void) +{ + if (pdev) + platform_device_unregister(pdev); + platform_driver_unregister(&lenovo_se30_wdt_driver); +} + +module_init(lenovo_se30_wdt_init); +module_exit(lenovo_se30_wdt_exit); + +MODULE_AUTHOR("Mark Pearson <mpearson-lenovo@squebb.ca>"); +MODULE_AUTHOR("David Ober <dober@lenovo.com>"); +MODULE_DESCRIPTION("Lenovo SE30 watchdog driver"); +MODULE_LICENSE("GPL");
Watchdog driver implementation for Lenovo SE30 platform. Signed-off-by: Mark Pearson <mpearson-lenovo@squebb.ca> --- drivers/watchdog/Kconfig | 12 + drivers/watchdog/Makefile | 1 + drivers/watchdog/lenovo_se30_wdt.c | 435 +++++++++++++++++++++++++++++ 3 files changed, 448 insertions(+) create mode 100644 drivers/watchdog/lenovo_se30_wdt.c