Message ID | 20230713105834.65751-1-dober6023@gmail.com |
---|---|
State | New |
Headers | show |
Series | Watchdog: New module for ITE 5632 watchdog | expand |
On 7/13/23 03:58, David Ober wrote: > This modules is to allow for the new ITE 5632 EC chip > to support the watchdog for initial use in the Lenovo SE10 > > Signed-off-by: David Ober <dober6023@gmail.com> > --- > drivers/watchdog/Kconfig | 10 ++ > drivers/watchdog/Makefile | 1 + > drivers/watchdog/ite5632_wdt.c | 279 +++++++++++++++++++++++++++++++++ > 3 files changed, 290 insertions(+) > create mode 100644 drivers/watchdog/ite5632_wdt.c > > diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig > index ee97d89dfc11..861cc0eff468 100644 > --- a/drivers/watchdog/Kconfig > +++ b/drivers/watchdog/Kconfig > @@ -264,6 +264,16 @@ config MENZ069_WATCHDOG > This driver can also be built as a module. If so the module > will be called menz069_wdt. > > +config ITE5632_WDT > + tristate "ITE 5632" > + select WATCHDOG_CORE > + help > + If you say yes here you get support for the watchdog > + functionality of the ITE 5632 eSIO chip. > + > + This driver can also be built as a module. If so, the module > + will be called ite5632_wdt. > + > config WDAT_WDT > tristate "ACPI Watchdog Action Table (WDAT)" > depends on ACPI > diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile > index 3633f5b98236..32c8340f3175 100644 > --- a/drivers/watchdog/Makefile > +++ b/drivers/watchdog/Makefile > @@ -119,6 +119,7 @@ obj-$(CONFIG_WAFER_WDT) += wafer5823wdt.o > obj-$(CONFIG_I6300ESB_WDT) += i6300esb.o > obj-$(CONFIG_IE6XX_WDT) += ie6xx_wdt.o > obj-$(CONFIG_ITCO_WDT) += iTCO_wdt.o > +obj-$(CONFIG_ITE5632_WDT) += ite5632_wdt.o > ifeq ($(CONFIG_ITCO_VENDOR_SUPPORT),y) > obj-$(CONFIG_ITCO_WDT) += iTCO_vendor_support.o > endif > diff --git a/drivers/watchdog/ite5632_wdt.c b/drivers/watchdog/ite5632_wdt.c > new file mode 100644 > index 000000000000..32a68f16674f > --- /dev/null > +++ b/drivers/watchdog/ite5632_wdt.c > @@ -0,0 +1,279 @@ > +// SPDX-License-Identifier: GPL-2.0-or-later > +/* > + * Customized ITE5632 WDT driver for Lenovo SE10. > + * > + */ > + > +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt > + > +#include <linux/delay.h> > +#include <linux/init.h> > +#include <linux/io.h> > +#include <linux/ioport.h> > +#include <linux/module.h> > +#include <linux/moduleparam.h> > +#include <linux/platform_device.h> > +#include <linux/string.h> > +#include <linux/types.h> > +#include <linux/watchdog.h> > + > +#define EC_STATUS_port 0x6C > +#define EC_CMD_port 0x6C > +#define EC_DATA_port 0x68 > +#define EC_OBF 0x01 > +#define EC_IBF 0x02 > +#define CFG_LDN 0x07 > +#define CFG_BRAM_LDN 0x10 /* for BRAM Base */ > +#define CFG_PORT 0x2E > + > +#define CUS_WDT_SWI 0x1A > +#define CUS_WDT_CFG 0x1B > +#define CUS_WDT_FEED 0xB0 > +#define CUS_WDT_CNT 0xB1 > + > +#define DRVNAME "ite5632" > + > +/*The timeout range is 1-255 seconds*/ > +#define MIN_TIMEOUT 1 > +#define MAX_TIMEOUT 255 > + > +#define WATCHDOG_TIMEOUT 60 /* 60 sec default timeout */ > +static unsigned short bram_base; > + > +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) ")"); > + > +struct ite5632_data_t { > + struct watchdog_device wdt; > +}; That structure seems unnecessary since it only has a single object. > + > +/* Kernel methods. */ > +static void set_bram(unsigned char offset, unsigned char val) > +{ > + outb(offset, bram_base); > + outb(val, bram_base + 1); > +} > + > +/* wait EC output buffer full */ > +static void wait_EC_OBF(void) > +{ > + while (1) { > + if (inb(EC_STATUS_port) & EC_OBF) > + break; > + udelay(10); > + } This would hang forever if the EC doesn't respond. Please select a sensible timeout and return an error if that happens. If the EC often takes longer than, say, 100uS to reply, please use usleep_range(). > +} > + > +/* wait EC input buffer empty */ > +static void wait_EC_IBE(void) > +{ > + while (1) { > + if (!(inb(EC_STATUS_port) & EC_IBF)) > + break; > + udelay(10); > + } Same as above. > +} > + > +static void send_EC_cmd(unsigned char EcCmd) > +{ > + wait_EC_IBE(); > + outb(EcCmd, EC_CMD_port); > + wait_EC_IBE(); > +} > + > +static unsigned char Read_EC_data(void) > +{ > + wait_EC_OBF(); > + return inb(EC_DATA_port); > +} > + > +static void LPC_Write_Byte(unsigned char index, unsigned char data) > +{ > + outb(index, CFG_PORT); > + outb(data, CFG_PORT + 1); > +} > + > +static unsigned char LPC_Read_Byte(unsigned char index) > +{ > + outb(index, CFG_PORT); > + return inb(CFG_PORT + 1); > +} > + > +static int GetChipID(void) > +{ > + unsigned short wPortAddr = 0x2E; > + unsigned char MSB, LSB; > + unsigned char cmd = (CFG_PORT == 0x2E) ? (0x55) : (0xAA); Please no unnecessary ( ) around constants. Besides, CFG_PORT is a constant and the check is pointless. > + > + outb(0x87, CFG_PORT); > + outb(0x01, CFG_PORT); > + outb(0x55, CFG_PORT); > + outb(cmd, CFG_PORT); > + outb(0x20, wPortAddr); What is the point of this ? First use CFG_PORT and then suddenly wPortAddr which has the same value ? Why ? And why not call LPC_Read_Byte() ? > + MSB = inb(wPortAddr + 1); > + outb(0x21, wPortAddr); > + LSB = inb(wPortAddr + 1); > + return (MSB * 256 + LSB); > +} > + > +static int wdt_start(struct watchdog_device *wdog) > +{ > + set_bram(CUS_WDT_SWI, 0x80); > + return 0; > +} > + > +static int wdt_set_timeout(struct watchdog_device *wdog, unsigned int timeout) > +{ > + wdog->timeout = timeout; > + set_bram(CUS_WDT_CFG, wdog->timeout); > + return 0; > +} > + > +static int wdt_stop(struct watchdog_device *wdog) > +{ > + wdt_set_timeout(wdog, 0); > + return 0; > +} > + > +static unsigned int wdt_get_time(struct watchdog_device *wdog) > +{ > + unsigned int timeleft; > + struct ite5632_data_t *data = watchdog_get_drvdata(wdog); > + > + if (!request_region(EC_DATA_port, 5, "EC")) { > + dev_err(data->wdt.parent, ":request fail\n"); > + return 0; Logging noise but ignore the error ? That is unacceptable. Please drop the log message and return the error to the caller. If you absolutely want log messages, make it dev_dbg(). Same everywhere else where errors are ignored. > + } > + send_EC_cmd(CUS_WDT_CNT); > + > + timeleft = (int)Read_EC_data(); Unnecessary typecast. > + release_region(EC_DATA_port, 5); > + return timeleft; > +} > + > +static int wdt_ping(struct watchdog_device *wdog) > +{ > + struct ite5632_data_t *data = watchdog_get_drvdata(wdog); > + > + if (!request_region(EC_DATA_port, 5, "EC")) { Should this be request_muxed_region() ? Overall, requesting and releasing the memory region for each ping is expensive. Is this really necessary ? Why ? > + dev_err(data->wdt.parent, ":request fail\n"); > + return 0; > + } > + send_EC_cmd(CUS_WDT_FEED); > + release_region(EC_DATA_port, 5); > + return 0; > +} > + > +/* Kernel Interfaces */ > +static const struct watchdog_info wdt_info = { > + .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, > + .identity = "5632 Watchdog", > +}; > + > +static const struct watchdog_ops wdt_ops = { > + .owner = THIS_MODULE, > + .start = wdt_start, > + .stop = wdt_stop, > + .ping = wdt_ping, > + .set_timeout = wdt_set_timeout, > + .get_timeleft = wdt_get_time, > +}; > + > +static int ite5632_probe(struct platform_device *pdev) > +{ > + struct device *dev = &pdev->dev; > + struct ite5632_data_t *data = NULL; > + > + dev_info(&pdev->dev, "Probe ITE5632 called\n"); > + > + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); > + if (!data) > + return -ENOMEM; > + > + data->wdt.info = &wdt_info, > + data->wdt.ops = &wdt_ops, > + data->wdt.timeout = WATCHDOG_TIMEOUT; /* Set default timeout */ > + data->wdt.min_timeout = MIN_TIMEOUT; > + data->wdt.max_timeout = MAX_TIMEOUT; > + data->wdt.parent = &pdev->dev; > + > + watchdog_init_timeout(&data->wdt, timeout, &pdev->dev); > + watchdog_set_drvdata(&data->wdt, data); > + > + watchdog_set_nowayout(&data->wdt, nowayout); > + watchdog_stop_on_reboot(&data->wdt); > + watchdog_stop_on_unregister(&data->wdt); > + > + dev_info(&pdev->dev, "initialized. timeout=%d sec (nowayout=%d)\n", > + data->wdt.timeout, nowayout); > + > + return devm_watchdog_register_device(dev, &data->wdt); > +} > + > +static struct platform_driver ite5632_driver = { > + .driver = { > + .name = DRVNAME, > + }, > + .probe = ite5632_probe, > +}; > + > +static struct platform_device *pdev; > + > +static int __init wdt_init(void) > +{ > + int ret; > + int chip; > + > + chip = GetChipID(); > + > + if (chip == 0x5632) > + pr_info("Found ITE ChipID= %4X\n", chip); > + else { > + pr_info("ITE ChipID 5632 not found\n"); Completely unacceptable. That would create noise on each system which doesn't have this chip. > + return -ENODEV; > + } > + Please run checkpatch --strict and fix what it reports. > + LPC_Write_Byte(CFG_LDN, CFG_BRAM_LDN); > + bram_base = ((LPC_Read_Byte(0x60) << 8) | LPC_Read_Byte(0x61)); > + > + platform_driver_register(&ite5632_driver); > + > + pdev = platform_device_alloc(DRVNAME, bram_base); > + > + dev_info(&pdev->dev, "ITE5632 device found\n"); More than one message is just as unacceptable. > + > + /* platform_device_add calls probe() */ > + ret = platform_device_add(pdev); > + if (ret) { > + platform_device_put(pdev); > + if (pdev) > + platform_device_unregister(pdev); > + platform_driver_unregister(&ite5632_driver); > + } > + return ret; > +} > + > +static void __exit wdt_exit(void) > +{ > + platform_device_unregister(pdev); > + platform_driver_unregister(&ite5632_driver); > + > + outb(0x02, 0x2E); > + outb(0x02, 0x2E + 1); What does this do ? And, again, why not call the helper function ? > +} > + > +module_init(wdt_init); > +module_exit(wdt_exit); > + > +MODULE_LICENSE("GPL"); > +MODULE_AUTHOR("David Ober<dober@lenovo.com>"); > +MODULE_DESCRIPTION("WDT driver for ITE5632");
diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index ee97d89dfc11..861cc0eff468 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -264,6 +264,16 @@ config MENZ069_WATCHDOG This driver can also be built as a module. If so the module will be called menz069_wdt. +config ITE5632_WDT + tristate "ITE 5632" + select WATCHDOG_CORE + help + If you say yes here you get support for the watchdog + functionality of the ITE 5632 eSIO chip. + + This driver can also be built as a module. If so, the module + will be called ite5632_wdt. + config WDAT_WDT tristate "ACPI Watchdog Action Table (WDAT)" depends on ACPI diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index 3633f5b98236..32c8340f3175 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -119,6 +119,7 @@ obj-$(CONFIG_WAFER_WDT) += wafer5823wdt.o obj-$(CONFIG_I6300ESB_WDT) += i6300esb.o obj-$(CONFIG_IE6XX_WDT) += ie6xx_wdt.o obj-$(CONFIG_ITCO_WDT) += iTCO_wdt.o +obj-$(CONFIG_ITE5632_WDT) += ite5632_wdt.o ifeq ($(CONFIG_ITCO_VENDOR_SUPPORT),y) obj-$(CONFIG_ITCO_WDT) += iTCO_vendor_support.o endif diff --git a/drivers/watchdog/ite5632_wdt.c b/drivers/watchdog/ite5632_wdt.c new file mode 100644 index 000000000000..32a68f16674f --- /dev/null +++ b/drivers/watchdog/ite5632_wdt.c @@ -0,0 +1,279 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Customized ITE5632 WDT driver for Lenovo SE10. + * + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/ioport.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/platform_device.h> +#include <linux/string.h> +#include <linux/types.h> +#include <linux/watchdog.h> + +#define EC_STATUS_port 0x6C +#define EC_CMD_port 0x6C +#define EC_DATA_port 0x68 +#define EC_OBF 0x01 +#define EC_IBF 0x02 +#define CFG_LDN 0x07 +#define CFG_BRAM_LDN 0x10 /* for BRAM Base */ +#define CFG_PORT 0x2E + +#define CUS_WDT_SWI 0x1A +#define CUS_WDT_CFG 0x1B +#define CUS_WDT_FEED 0xB0 +#define CUS_WDT_CNT 0xB1 + +#define DRVNAME "ite5632" + +/*The timeout range is 1-255 seconds*/ +#define MIN_TIMEOUT 1 +#define MAX_TIMEOUT 255 + +#define WATCHDOG_TIMEOUT 60 /* 60 sec default timeout */ +static unsigned short bram_base; + +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) ")"); + +struct ite5632_data_t { + struct watchdog_device wdt; +}; + +/* Kernel methods. */ +static void set_bram(unsigned char offset, unsigned char val) +{ + outb(offset, bram_base); + outb(val, bram_base + 1); +} + +/* wait EC output buffer full */ +static void wait_EC_OBF(void) +{ + while (1) { + if (inb(EC_STATUS_port) & EC_OBF) + break; + udelay(10); + } +} + +/* wait EC input buffer empty */ +static void wait_EC_IBE(void) +{ + while (1) { + if (!(inb(EC_STATUS_port) & EC_IBF)) + break; + udelay(10); + } +} + +static void send_EC_cmd(unsigned char EcCmd) +{ + wait_EC_IBE(); + outb(EcCmd, EC_CMD_port); + wait_EC_IBE(); +} + +static unsigned char Read_EC_data(void) +{ + wait_EC_OBF(); + return inb(EC_DATA_port); +} + +static void LPC_Write_Byte(unsigned char index, unsigned char data) +{ + outb(index, CFG_PORT); + outb(data, CFG_PORT + 1); +} + +static unsigned char LPC_Read_Byte(unsigned char index) +{ + outb(index, CFG_PORT); + return inb(CFG_PORT + 1); +} + +static int GetChipID(void) +{ + unsigned short wPortAddr = 0x2E; + unsigned char MSB, LSB; + unsigned char cmd = (CFG_PORT == 0x2E) ? (0x55) : (0xAA); + + outb(0x87, CFG_PORT); + outb(0x01, CFG_PORT); + outb(0x55, CFG_PORT); + outb(cmd, CFG_PORT); + outb(0x20, wPortAddr); + MSB = inb(wPortAddr + 1); + outb(0x21, wPortAddr); + LSB = inb(wPortAddr + 1); + return (MSB * 256 + LSB); +} + +static int wdt_start(struct watchdog_device *wdog) +{ + set_bram(CUS_WDT_SWI, 0x80); + return 0; +} + +static int wdt_set_timeout(struct watchdog_device *wdog, unsigned int timeout) +{ + wdog->timeout = timeout; + set_bram(CUS_WDT_CFG, wdog->timeout); + return 0; +} + +static int wdt_stop(struct watchdog_device *wdog) +{ + wdt_set_timeout(wdog, 0); + return 0; +} + +static unsigned int wdt_get_time(struct watchdog_device *wdog) +{ + unsigned int timeleft; + struct ite5632_data_t *data = watchdog_get_drvdata(wdog); + + if (!request_region(EC_DATA_port, 5, "EC")) { + dev_err(data->wdt.parent, ":request fail\n"); + return 0; + } + send_EC_cmd(CUS_WDT_CNT); + + timeleft = (int)Read_EC_data(); + release_region(EC_DATA_port, 5); + return timeleft; +} + +static int wdt_ping(struct watchdog_device *wdog) +{ + struct ite5632_data_t *data = watchdog_get_drvdata(wdog); + + if (!request_region(EC_DATA_port, 5, "EC")) { + dev_err(data->wdt.parent, ":request fail\n"); + return 0; + } + send_EC_cmd(CUS_WDT_FEED); + release_region(EC_DATA_port, 5); + return 0; +} + +/* Kernel Interfaces */ +static const struct watchdog_info wdt_info = { + .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, + .identity = "5632 Watchdog", +}; + +static const struct watchdog_ops wdt_ops = { + .owner = THIS_MODULE, + .start = wdt_start, + .stop = wdt_stop, + .ping = wdt_ping, + .set_timeout = wdt_set_timeout, + .get_timeleft = wdt_get_time, +}; + +static int ite5632_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct ite5632_data_t *data = NULL; + + dev_info(&pdev->dev, "Probe ITE5632 called\n"); + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->wdt.info = &wdt_info, + data->wdt.ops = &wdt_ops, + data->wdt.timeout = WATCHDOG_TIMEOUT; /* Set default timeout */ + data->wdt.min_timeout = MIN_TIMEOUT; + data->wdt.max_timeout = MAX_TIMEOUT; + data->wdt.parent = &pdev->dev; + + watchdog_init_timeout(&data->wdt, timeout, &pdev->dev); + watchdog_set_drvdata(&data->wdt, data); + + watchdog_set_nowayout(&data->wdt, nowayout); + watchdog_stop_on_reboot(&data->wdt); + watchdog_stop_on_unregister(&data->wdt); + + dev_info(&pdev->dev, "initialized. timeout=%d sec (nowayout=%d)\n", + data->wdt.timeout, nowayout); + + return devm_watchdog_register_device(dev, &data->wdt); +} + +static struct platform_driver ite5632_driver = { + .driver = { + .name = DRVNAME, + }, + .probe = ite5632_probe, +}; + +static struct platform_device *pdev; + +static int __init wdt_init(void) +{ + int ret; + int chip; + + chip = GetChipID(); + + if (chip == 0x5632) + pr_info("Found ITE ChipID= %4X\n", chip); + else { + pr_info("ITE ChipID 5632 not found\n"); + return -ENODEV; + } + + LPC_Write_Byte(CFG_LDN, CFG_BRAM_LDN); + bram_base = ((LPC_Read_Byte(0x60) << 8) | LPC_Read_Byte(0x61)); + + platform_driver_register(&ite5632_driver); + + pdev = platform_device_alloc(DRVNAME, bram_base); + + dev_info(&pdev->dev, "ITE5632 device found\n"); + + /* platform_device_add calls probe() */ + ret = platform_device_add(pdev); + if (ret) { + platform_device_put(pdev); + if (pdev) + platform_device_unregister(pdev); + platform_driver_unregister(&ite5632_driver); + } + return ret; +} + +static void __exit wdt_exit(void) +{ + platform_device_unregister(pdev); + platform_driver_unregister(&ite5632_driver); + + outb(0x02, 0x2E); + outb(0x02, 0x2E + 1); +} + +module_init(wdt_init); +module_exit(wdt_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("David Ober<dober@lenovo.com>"); +MODULE_DESCRIPTION("WDT driver for ITE5632");
This modules is to allow for the new ITE 5632 EC chip to support the watchdog for initial use in the Lenovo SE10 Signed-off-by: David Ober <dober6023@gmail.com> --- drivers/watchdog/Kconfig | 10 ++ drivers/watchdog/Makefile | 1 + drivers/watchdog/ite5632_wdt.c | 279 +++++++++++++++++++++++++++++++++ 3 files changed, 290 insertions(+) create mode 100644 drivers/watchdog/ite5632_wdt.c