From d1cd3da95e191a4303551f5dc8e508a472a99849 Mon Sep 17 00:00:00 2001 From: Ahmad Fatoum Date: Fri, 11 Oct 2019 18:27:52 +0200 Subject: watchdog: add support for Fintek F718xx and, F818xx Super I/O This is an adaptation of the Linux v5.3 f71808e_wdt driver for the watchdog component of the Fintek Super I/O chips. Signed-off-by: Ahmad Fatoum Signed-off-by: Sascha Hauer --- drivers/watchdog/Kconfig | 9 + drivers/watchdog/Makefile | 1 + drivers/watchdog/f71808e_wdt.c | 379 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 389 insertions(+) create mode 100644 drivers/watchdog/f71808e_wdt.c (limited to 'drivers/watchdog') diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index fbaab896d4..45dd41a2a2 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -96,4 +96,13 @@ config STPMIC1_WATCHDOG help Enable to support configuration of the stpmic1's built-in watchdog. +config F71808E_WDT + bool "Fintek F718xx, F818xx Super I/O Watchdog" + depends on X86 + depends on FINTEK_SUPERIO + help + This is the driver for the hardware watchdog on the Fintek F71808E, + F71862FG, F71868, F71869, F71882FG, F71889FG, F81865 and F81866 + Super I/O controllers. + endif diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index 1fbd780885..63efc2a87e 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -13,3 +13,4 @@ obj-$(CONFIG_ARCH_BCM283X) += bcm2835_wdt.o obj-$(CONFIG_RAVE_SP_WATCHDOG) += rave-sp-wdt.o obj-$(CONFIG_STM32_IWDG_WATCHDOG) += stm32_iwdg.o obj-$(CONFIG_STPMIC1_WATCHDOG) += stpmic1_wdt.o +obj-$(CONFIG_F71808E_WDT) += f71808e_wdt.o diff --git a/drivers/watchdog/f71808e_wdt.c b/drivers/watchdog/f71808e_wdt.c new file mode 100644 index 0000000000..4f881a1d02 --- /dev/null +++ b/drivers/watchdog/f71808e_wdt.c @@ -0,0 +1,379 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/*************************************************************************** + * Copyright (C) 2006 by Hans Edgington * + * Copyright (C) 2007-2009 Hans de Goede * + * Copyright (C) 2010 Giel van Schijndel * + * Copyright (C) 2019 Ahmad Fatoum * + * * + ***************************************************************************/ + +#define pr_fmt(fmt) "f71808e_wdt: " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SIO_F71808FG_LD_WDT 0x07 /* Watchdog timer logical device */ +#define SIO_UNLOCK_KEY 0x87 /* Key to enable Super-I/O */ +#define SIO_LOCK_KEY 0xAA /* Key to disable Super-I/O */ + +#define SIO_REG_LDSEL 0x07 /* Logical device select */ +#define SIO_REG_DEVREV 0x22 /* Device revision */ +#define SIO_REG_ROM_ADDR_SEL 0x27 /* ROM address select */ +#define SIO_F81866_REG_PORT_SEL 0x27 /* F81866 Multi-Function Register */ +#define SIO_REG_MFUNCT1 0x29 /* Multi function select 1 */ +#define SIO_REG_MFUNCT2 0x2a /* Multi function select 2 */ +#define SIO_REG_MFUNCT3 0x2b /* Multi function select 3 */ +#define SIO_F81866_REG_GPIO1 0x2c /* F81866 GPIO1 Enable Register */ +#define SIO_REG_ENABLE 0x30 /* Logical device enable */ +#define SIO_REG_ADDR 0x60 /* Logical device address (2 bytes) */ + +#define F71808FG_REG_WDO_CONF 0xf0 +#define F71808FG_REG_WDT_CONF 0xf5 +#define F71808FG_REG_WD_TIME 0xf6 + +#define F71808FG_FLAG_WDOUT_EN 7 + +#define F71808FG_FLAG_WDTMOUT_STS 6 +#define F71808FG_FLAG_WD_EN 5 +#define F71808FG_FLAG_WD_PULSE 4 +#define F71808FG_FLAG_WD_UNIT 3 + +#define F81865_REG_WDO_CONF 0xfa +#define F81865_FLAG_WDOUT_EN 0 + +/* Default values */ +#define WATCHDOG_MAX_TIMEOUT (60 * 255) + +enum pulse_width { + PULSE_WIDTH_LEVEL, PULSE_WIDTH_1MS, + PULSE_WIDTH_LOW, PULSE_WIDTH_MID, PULSE_WIDTH_HIGH +}; + +const char *pulse_width_names[] = { "level", "1", "25", "125", "5000" }; +const char *pulse_width_names_f71868[] = { "level", "1", "30", "150", "6000" }; + +enum wdtrst_pin { + WDTRST_PIN_56, WDTRST_PIN_63, +}; + +const char *f71862fg_pin_names[] = { "56", "63" }; + +enum chips { f71808fg, f71858fg, f71862fg, f71868, f71869, f71882fg, f71889fg, + f81865, f81866}; + +struct f71808e_wdt; + +struct f71808e_variant_data { + enum chips type; + void (*pinconf)(struct f71808e_wdt *wd); +}; + +struct f71808e_wdt { + struct watchdog wdd; + u16 sioaddr; + const struct f71808e_variant_data *variant; + unsigned int timeout; + u8 timer_val; /* content for the wd_time register */ + char minutes_mode; + int pulse_width; + int f71862fg_pin; +}; + +static inline struct f71808e_wdt *to_f71808e_wdt(struct watchdog *wdd) +{ + return container_of(wdd, struct f71808e_wdt, wdd); +} + +static inline bool has_f81865_wdo_conf(struct f71808e_wdt *wd) +{ + return wd->variant->type == f81865 || wd->variant->type == f81866; +} + +static inline void superio_enter(u16 base) +{ + /* according to the datasheet the key must be sent twice! */ + outb(SIO_UNLOCK_KEY, base); + outb(SIO_UNLOCK_KEY, base); +} + +static inline void superio_select(u16 base, int ld) +{ + outb(SIO_REG_LDSEL, base); + outb(ld, base + 1); +} + +static inline void superio_exit(u16 base) +{ + outb(SIO_LOCK_KEY, base); +} + +static void f71808e_wdt_keepalive(struct f71808e_wdt *wd) +{ + superio_enter(wd->sioaddr); + + superio_select(wd->sioaddr, SIO_F71808FG_LD_WDT); + + if (wd->minutes_mode) + /* select minutes for timer units */ + superio_set_bit(wd->sioaddr, F71808FG_REG_WDT_CONF, + F71808FG_FLAG_WD_UNIT); + else + /* select seconds for timer units */ + superio_clear_bit(wd->sioaddr, F71808FG_REG_WDT_CONF, + F71808FG_FLAG_WD_UNIT); + + /* Set timer value */ + superio_outb(wd->sioaddr, F71808FG_REG_WD_TIME, + wd->timer_val); + + superio_exit(wd->sioaddr); +} + +static void f71808e_wdt_start(struct f71808e_wdt *wd) +{ + /* Make sure we don't die as soon as the watchdog is enabled below */ + f71808e_wdt_keepalive(wd); + + superio_enter(wd->sioaddr); + + superio_select(wd->sioaddr, SIO_F71808FG_LD_WDT); + + /* Watchdog pin configuration */ + wd->variant->pinconf(wd); + + superio_select(wd->sioaddr, SIO_F71808FG_LD_WDT); + superio_set_bit(wd->sioaddr, SIO_REG_ENABLE, 0); + + if (has_f81865_wdo_conf(wd)) + superio_set_bit(wd->sioaddr, F81865_REG_WDO_CONF, + F81865_FLAG_WDOUT_EN); + else + superio_set_bit(wd->sioaddr, F71808FG_REG_WDO_CONF, + F71808FG_FLAG_WDOUT_EN); + + superio_set_bit(wd->sioaddr, F71808FG_REG_WDT_CONF, + F71808FG_FLAG_WD_EN); + + if (wd->pulse_width > 0) { + /* Select "pulse" output mode with given duration */ + u8 wdt_conf = superio_inb(wd->sioaddr, F71808FG_REG_WDT_CONF); + + /* Set WD_PSWIDTH bits (1:0) */ + wdt_conf = (wdt_conf & 0xfc) | (wd->pulse_width & 0x03); + /* Set WD_PULSE to "pulse" mode */ + wdt_conf |= BIT(F71808FG_FLAG_WD_PULSE); + + superio_outb(wd->sioaddr, F71808FG_REG_WDT_CONF, wdt_conf); + } else { + /* Select "level" output mode */ + superio_clear_bit(wd->sioaddr, F71808FG_REG_WDT_CONF, + F71808FG_FLAG_WD_PULSE); + } + + superio_exit(wd->sioaddr); +} + +static void f71808e_wdt_stop(struct f71808e_wdt *wd) +{ + superio_enter(wd->sioaddr); + + superio_select(wd->sioaddr, SIO_F71808FG_LD_WDT); + + superio_clear_bit(wd->sioaddr, F71808FG_REG_WDT_CONF, + F71808FG_FLAG_WD_EN); + + superio_exit(wd->sioaddr); +} + +static int f71808e_wdt_set_timeout(struct watchdog *wdd, unsigned int new_timeout) +{ + struct f71808e_wdt *wd = to_f71808e_wdt(wdd); + + if (!new_timeout) { + f71808e_wdt_stop(wd); + return 0; + } + + if (wd->timeout != new_timeout) { + if (new_timeout > 0xff) { + wd->timer_val = DIV_ROUND_UP(new_timeout, 60); + wd->minutes_mode = true; + } else { + wd->timer_val = new_timeout; + wd->minutes_mode = false; + } + + f71808e_wdt_start(wd); + wd->timeout = new_timeout; + } + + f71808e_wdt_keepalive(wd); + return 0; +} + +static int f71808e_wdt_init(struct f71808e_wdt *wd, struct device_d *dev) +{ + struct watchdog *wdd = &wd->wdd; + const char * const *names = pulse_width_names; + int wdt_conf; + int ret; + + superio_enter(wd->sioaddr); + + superio_select(wd->sioaddr, SIO_F71808FG_LD_WDT); + + wdt_conf = superio_inb(wd->sioaddr, F71808FG_REG_WDT_CONF); + + superio_exit(wd->sioaddr); + + if (wd->variant->type == f71868) + names = pulse_width_names_f71868; + + wd->pulse_width = PULSE_WIDTH_MID; /* either 125ms or 150ms */ + + dev_add_param_enum(dev, "pulse_width_ms", NULL, NULL, + &wd->pulse_width, names, + ARRAY_SIZE(pulse_width_names), + wd); + + if (wd->variant->type == f71862fg) { + wd->f71862fg_pin = WDTRST_PIN_63; + + dev_add_param_enum(dev, "wdtrst_pin", NULL, NULL, + &wd->f71862fg_pin, f71862fg_pin_names, + ARRAY_SIZE(f71862fg_pin_names), + wd); + } + + wdd->hwdev = dev; + wdd->set_timeout = &f71808e_wdt_set_timeout; + wdd->timeout_max = WATCHDOG_MAX_TIMEOUT; + + if (wdt_conf & BIT(F71808FG_FLAG_WDTMOUT_STS)) + reset_source_set_priority(RESET_WDG, + RESET_SOURCE_DEFAULT_PRIORITY); + + dev_info(dev, "reset reason: %s\n", reset_source_name()); + + ret = watchdog_register(wdd); + if (ret) + return ret; + + superio_enter(wd->sioaddr); + dev_info(dev, "revision %d probed.\n", + superio_inb(wd->sioaddr, SIO_REG_DEVREV)); + superio_exit(wd->sioaddr); + + return 0; +} + +static void f71808fg_pinconf(struct f71808e_wdt *wd) +{ + /* Set pin 21 to GPIO23/WDTRST#, then to WDTRST# */ + superio_clear_bit(wd->sioaddr, SIO_REG_MFUNCT2, 3); + superio_clear_bit(wd->sioaddr, SIO_REG_MFUNCT3, 3); +} +static void f71862fg_pinconf(struct f71808e_wdt *wd) +{ + u16 ioaddr = wd->sioaddr; + + if (wd->f71862fg_pin == WDTRST_PIN_63) { + /* SPI must be disabled first to use this pin! */ + superio_clear_bit(ioaddr, SIO_REG_ROM_ADDR_SEL, 6); + superio_set_bit(ioaddr, SIO_REG_MFUNCT3, 4); + } else if (wd->f71862fg_pin == WDTRST_PIN_56) { + superio_set_bit(ioaddr, SIO_REG_MFUNCT1, 1); + } +} +static void f71868_pinconf(struct f71808e_wdt *wd) +{ + /* GPIO14 --> WDTRST# */ + superio_clear_bit(wd->sioaddr, SIO_REG_MFUNCT1, 4); +} +static void f71882fg_pinconf(struct f71808e_wdt *wd) +{ + /* Set pin 56 to WDTRST# */ + superio_set_bit(wd->sioaddr, SIO_REG_MFUNCT1, 1); +} +static void f71889fg_pinconf(struct f71808e_wdt *wd) +{ + /* set pin 40 to WDTRST# */ + superio_outb(wd->sioaddr, SIO_REG_MFUNCT3, + superio_inb(wd->sioaddr, SIO_REG_MFUNCT3) & 0xcf); +} +static void f81865_pinconf(struct f71808e_wdt *wd) +{ + /* Set pin 70 to WDTRST# */ + superio_clear_bit(wd->sioaddr, SIO_REG_MFUNCT3, 5); +} +static void f81866_pinconf(struct f71808e_wdt *wd) +{ + /* + * GPIO1 Control Register when 27h BIT3:2 = 01 & BIT0 = 0. + * The PIN 70(GPIO15/WDTRST) is controlled by 2Ch: + * BIT5: 0 -> WDTRST# + * 1 -> GPIO15 + */ + u8 tmp = superio_inb(wd->sioaddr, SIO_F81866_REG_PORT_SEL); + tmp &= ~(BIT(3) | BIT(0)); + tmp |= BIT(2); + superio_outb(wd->sioaddr, SIO_F81866_REG_PORT_SEL, tmp); + + superio_clear_bit(wd->sioaddr, SIO_F81866_REG_GPIO1, 5); +} + +static struct f71808e_variant_data f71808fg_data = { .type = f71808fg, .pinconf = f71808fg_pinconf }; +static struct f71808e_variant_data f71862fg_data = { .type = f71862fg, .pinconf = f71862fg_pinconf }; +static struct f71808e_variant_data f71868_data = { .type = f71868, .pinconf = f71868_pinconf }; +static struct f71808e_variant_data f71869_data = { .type = f71869, .pinconf = f71868_pinconf }; +static struct f71808e_variant_data f71882fg_data = { .type = f71882fg, .pinconf = f71882fg_pinconf }; +static struct f71808e_variant_data f71889fg_data = { .type = f71889fg, .pinconf = f71889fg_pinconf }; +static struct f71808e_variant_data f81865_data = { .type = f81865, .pinconf = f81865_pinconf }; +static struct f71808e_variant_data f81866_data = { .type = f81866, .pinconf = f81866_pinconf }; + +static struct platform_device_id f71808e_wdt_ids[] = { + { .name = "f71808fg_wdt", .driver_data = (unsigned long)&f71808fg_data }, + { .name = "f71862fg_wdt", .driver_data = (unsigned long)&f71862fg_data }, + { .name = "f71868_wdt", .driver_data = (unsigned long)&f71868_data }, + { .name = "f71869_wdt", .driver_data = (unsigned long)&f71869_data }, + { .name = "f71882fg_wdt", .driver_data = (unsigned long)&f71882fg_data }, + { .name = "f71889fg_wdt", .driver_data = (unsigned long)&f71889fg_data }, + { .name = "f81865_wdt", .driver_data = (unsigned long)&f81865_data }, + { .name = "f81866_wdt", .driver_data = (unsigned long)&f81866_data }, + { /* sentinel */ }, +}; + +static int f71808e_probe(struct device_d *dev) +{ + struct f71808e_wdt *wd; + struct resource *res; + int ret; + + wd = xzalloc(sizeof(*wd)); + + ret = dev_get_drvdata(dev, (const void **)&wd->variant); + if (ret) + return ret; + + res = dev_get_resource(dev->parent, IORESOURCE_IO, 0); + if (IS_ERR(res)) + return PTR_ERR(res); + wd->sioaddr = res->start; + + return f71808e_wdt_init(wd, dev); +} + +static struct driver_d f71808e_wdt_driver = { + .probe = f71808e_probe, + .name = "f71808e_wdt", + .id_table = f71808e_wdt_ids, +}; + +device_platform_driver(f71808e_wdt_driver); -- cgit v1.2.3