From d472b8670b4847875a6b5bcce1118b96cb725d7d Mon Sep 17 00:00:00 2001 From: Andrey Smirnov Date: Wed, 2 May 2018 12:36:53 -0700 Subject: watchdog: Port RAVE SP watchdog driver from Linux kernel Port RAVE SP watchdog driver from Linux kernel Signed-off-by: Andrey Smirnov Signed-off-by: Sascha Hauer --- drivers/watchdog/Kconfig | 5 + drivers/watchdog/Makefile | 1 + drivers/watchdog/rave-sp-wdt.c | 426 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 432 insertions(+) create mode 100644 drivers/watchdog/rave-sp-wdt.c diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 1d6b15617c..27e9f6d8b4 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -70,4 +70,9 @@ config WATCHDOG_BCM2835 help Add support for watchdog on the Broadcom BCM283X SoCs. +config RAVE_SP_WATCHDOG + bool "RAVE SP Watchdog timer" + depends on RAVE_SP_CORE + help + Support for the watchdog on RAVE SP device. endif diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index 2376401213..faf06110a3 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -8,3 +8,4 @@ obj-$(CONFIG_WATCHDOG_JZ4740) += jz4740.o obj-$(CONFIG_WATCHDOG_IMX_RESET_SOURCE) += imxwd.o obj-$(CONFIG_WATCHDOG_ORION) += orion_wdt.o obj-$(CONFIG_ARCH_BCM283X) += bcm2835_wdt.o +obj-$(CONFIG_RAVE_SP_WATCHDOG) += rave-sp-wdt.o diff --git a/drivers/watchdog/rave-sp-wdt.c b/drivers/watchdog/rave-sp-wdt.c new file mode 100644 index 0000000000..1643167851 --- /dev/null +++ b/drivers/watchdog/rave-sp-wdt.c @@ -0,0 +1,426 @@ +// SPDX-License-Identifier: GPL-2.0+ + +/* + * Driver for watchdog aspect of for Zodiac Inflight Innovations RAVE + * Supervisory Processor(SP) MCU + * + * Copyright (C) 2018 Zodiac Inflight Innovation + * + */ + +#include +#include +#include +#include +#include + +#include + +#include +#include + +enum { + RAVE_SP_RESET_BYTE = 1, + RAVE_SP_RESET_REASON_NORMAL = 0, + RAVE_SP_RESET_REASON_HW_WATCHDOG = 1, + RAVE_SP_RESET_REASON_SW_WATCHDOG = 2, + RAVE_SP_RESET_REASON_VOLTAGE = 3, + RAVE_SP_RESET_REASON_HOST_REQUEST = 4, + RAVE_SP_RESET_REASON_TEMPERATURE = 5, + RAVE_SP_RESET_REASON_BUTTON_PRESS = 6, + RAVE_SP_RESET_REASON_PIC_CM = 7, + RAVE_SP_RESET_REASON_PIC_ILL_INST = 8, + RAVE_SP_RESET_REASON_PIC_TRAP = 9, + RAVE_SP_RESET_REASON_UKNOWN_REASON = 10, + RAVE_SP_RESET_REASON_THERMAL_SENSOR = 11, + RAVE_SP_RESET_REASON_SW_VOLTAGE = 12, + RAVE_SP_RESET_REASON_CP_REQUEST = 13, + + RAVE_SP_RESET_DELAY_MS = 500, + + RAVE_SP_BOOT_SOURCE_GET = 0, + RAVE_SP_BOOT_SOURCE_SET = 1, +}; + +/** + * struct rave_sp_wdt_variant - RAVE SP watchdog variant + * + * @max_timeout: Largest possible watchdog timeout setting + * @min_timeout: Smallest possible watchdog timeout setting + * + * @configure: Function to send configuration command + * @restart: Function to send "restart" command + */ +struct rave_sp_wdt_variant { + unsigned int max_timeout; + unsigned int min_timeout; + + int (*configure)(struct watchdog *, bool); + int (*restart)(struct watchdog *); + int (*reset_reason)(struct watchdog *); +}; + +/** + * struct rave_sp_wdt - RAVE SP watchdog + * + * @wdd: Underlying watchdog device + * @sp: Pointer to parent RAVE SP device + * @variant: Device specific variant information + * @reboot_notifier: Reboot notifier implementing machine reset + */ +struct rave_sp_wdt { + struct watchdog wdd; + struct rave_sp *sp; + const struct rave_sp_wdt_variant *variant; + struct restart_handler restart; + unsigned int timeout; + unsigned int boot_source; + struct device_d dev; +}; + +static struct rave_sp_wdt *to_rave_sp_wdt(struct watchdog *wdd) +{ + return container_of(wdd, struct rave_sp_wdt, wdd); +} + +static int rave_sp_wdt_exec(struct watchdog *wdd, void *data, + size_t data_size) +{ + return rave_sp_exec(to_rave_sp_wdt(wdd)->sp, + data, data_size, NULL, 0); +} + + +static int rave_sp_wdt_access_boot_source(struct rave_sp_wdt *sp_wd, u8 set_get) +{ + u8 cmd[] = { + [0] = RAVE_SP_CMD_BOOT_SOURCE, + [1] = 0, + [2] = set_get, + [3] = sp_wd->boot_source, + }; + u8 response; + int ret; + + ret = rave_sp_exec(sp_wd->sp, cmd, sizeof(cmd), &response, + sizeof(response)); + if (ret) + return ret; + + return response; +} + +static int __rave_sp_wdt_rdu_reset_reason(struct watchdog *wdd, + uint8_t response[], + size_t response_len) +{ + u8 cmd[] = { + [0] = RAVE_SP_CMD_RESET_REASON, + [1] = 0, + }; + int ret; + + ret = rave_sp_exec(to_rave_sp_wdt(wdd)->sp, cmd, sizeof(cmd), + response, response_len); + if (ret) + return ret; + /* + * Non "legacy" watchdog variants return 2 bytes as response + * whereas "legacy" ones return only one. Both however send + * the data we need as a first byte of the response. + */ + return response[0]; +} + +static int rave_sp_wdt_rdu_reset_reason(struct watchdog *wdd) +{ + u8 response[2]; + return __rave_sp_wdt_rdu_reset_reason(wdd, response, sizeof(response)); +} + +static int rave_sp_wdt_legacy_reset_reason(struct watchdog *wdd) +{ + u8 response[1]; + return __rave_sp_wdt_rdu_reset_reason(wdd, response, sizeof(response)); +} + +static int rave_sp_wdt_legacy_configure(struct watchdog *wdd, bool on) +{ + struct rave_sp_wdt *sp_wd = to_rave_sp_wdt(wdd); + + u8 cmd[] = { + [0] = RAVE_SP_CMD_SW_WDT, + [1] = 0, + [2] = 0, + [3] = on, + [4] = on ? sp_wd->timeout : 0, + }; + + return rave_sp_wdt_exec(wdd, cmd, sizeof(cmd)); +} + +static int rave_sp_wdt_rdu_configure(struct watchdog *wdd, bool on) +{ + struct rave_sp_wdt *sp_wd = to_rave_sp_wdt(wdd); + + u8 cmd[] = { + [0] = RAVE_SP_CMD_SW_WDT, + [1] = 0, + [2] = on, + [3] = sp_wd->timeout, + [4] = (u8)(sp_wd->timeout >> 8), + }; + + return rave_sp_wdt_exec(wdd, cmd, sizeof(cmd)); +} + +static int rave_sp_wdt_configure(struct watchdog *wdd, bool on) +{ + return to_rave_sp_wdt(wdd)->variant->configure(wdd, on); +} + +static int rave_sp_wdt_legacy_restart(struct watchdog *wdd) +{ + u8 cmd[] = { + [0] = RAVE_SP_CMD_RESET, + [1] = 0, + [2] = RAVE_SP_RESET_BYTE + }; + + return rave_sp_wdt_exec(wdd, cmd, sizeof(cmd)); +} + +static int rave_sp_wdt_rdu_restart(struct watchdog *wdd) +{ + u8 cmd[] = { + [0] = RAVE_SP_CMD_RESET, + [1] = 0, + [2] = RAVE_SP_RESET_BYTE, + [3] = RAVE_SP_RESET_REASON_NORMAL + }; + + return rave_sp_wdt_exec(wdd, cmd, sizeof(cmd)); +} + +static void __noreturn rave_sp_wdt_restart_handler(struct restart_handler *rst) +{ + struct rave_sp_wdt *sp_wd = + container_of(rst, struct rave_sp_wdt, restart); + + const int ret = sp_wd->variant->restart(&sp_wd->wdd); + + if (ret < 0) + dev_err(&sp_wd->wdd.dev, + "Failed to issue restart command (%d)", ret); + /* + * The actual work was done by reboot notifier above. SP + * firmware waits 500 ms before issuing reset, so let's hang + * here for twice that delay and hopefuly we'd never reach + * the return statement. + */ + mdelay(2 * RAVE_SP_RESET_DELAY_MS); + hang(); +} + +static int rave_sp_wdt_start(struct watchdog *wdd) +{ + return rave_sp_wdt_configure(wdd, true); +} + +static int rave_sp_wdt_stop(struct watchdog *wdd) +{ + return rave_sp_wdt_configure(wdd, false); +} + +static int rave_sp_wdt_set_timeout(struct watchdog *wdd, + unsigned int timeout) +{ + struct rave_sp_wdt *sp_wd = to_rave_sp_wdt(wdd); + + if (!timeout) + return rave_sp_wdt_stop(wdd); + + if (timeout < sp_wd->variant->min_timeout || + timeout > sp_wd->variant->max_timeout) + return -EINVAL; + + sp_wd->timeout = timeout; + return rave_sp_wdt_start(wdd); +} + +static const struct rave_sp_wdt_variant rave_sp_wdt_legacy = { + .max_timeout = 255, + .min_timeout = 1, + .configure = rave_sp_wdt_legacy_configure, + .restart = rave_sp_wdt_legacy_restart, + .reset_reason = rave_sp_wdt_legacy_reset_reason, +}; + +static const struct rave_sp_wdt_variant rave_sp_wdt_rdu = { + .max_timeout = 180, + .min_timeout = 60, + .configure = rave_sp_wdt_rdu_configure, + .restart = rave_sp_wdt_rdu_restart, + .reset_reason = rave_sp_wdt_rdu_reset_reason, +}; + +static const struct of_device_id rave_sp_wdt_of_match[] = { + { + .compatible = "zii,rave-sp-watchdog-legacy", + .data = &rave_sp_wdt_legacy, + }, + { + .compatible = "zii,rave-sp-watchdog", + .data = &rave_sp_wdt_rdu, + }, + { /* sentinel */ } +}; + +static int rave_sp_wdt_set_boot_source(struct param_d *param, void *priv) +{ + int ret; + + ret = rave_sp_wdt_access_boot_source(priv, RAVE_SP_BOOT_SOURCE_SET); + if (ret < 0) + return ret; + + return 0; +} + +static int rave_sp_wdt_get_boot_source(struct param_d *param, void *priv) +{ + struct rave_sp_wdt *sp_wd = priv; + int ret; + + ret = rave_sp_wdt_access_boot_source(sp_wd, RAVE_SP_BOOT_SOURCE_GET); + if (ret < 0) + return ret; + + sp_wd->boot_source = ret; + return 0; +} + +static int rave_sp_wdt_probe(struct device_d *dev) +{ + struct rave_sp_wdt *sp_wd; + const char *reset_reason; + struct nvmem_cell *cell; + struct watchdog *wdd; + __le16 timeout = 60; + struct param_d *p; + int ret; + + sp_wd = xzalloc(sizeof(*sp_wd)); + sp_wd->variant = of_device_get_match_data(dev); + sp_wd->sp = dev->parent->priv; + + cell = nvmem_cell_get(dev, "wdt-timeout"); + if (!IS_ERR(cell)) { + size_t len; + void *value = nvmem_cell_read(cell, &len); + + if (!IS_ERR(value)) { + memcpy(&timeout, value, min(len, sizeof(timeout))); + kfree(value); + } + nvmem_cell_put(cell); + } + sp_wd->timeout = le16_to_cpu(timeout); + + wdd = &sp_wd->wdd; + wdd->hwdev = dev; + wdd->set_timeout = rave_sp_wdt_set_timeout; + + ret = rave_sp_wdt_stop(wdd); + if (ret) { + dev_err(dev, "Failed to stop watchdog device\n"); + return ret; + } + + ret = watchdog_register(wdd); + if (ret) { + dev_err(dev, "Failed to register watchdog device\n"); + + return ret; + } + + sp_wd->restart.name = "rave-sp-wdt"; + sp_wd->restart.restart = rave_sp_wdt_restart_handler; + sp_wd->restart.priority = 200; + + ret = restart_handler_register(&sp_wd->restart); + if (ret) + dev_warn(dev, "Cannot register restart handler\n"); + + + p = dev_add_param_int(&wdd->dev, "boot_source", + rave_sp_wdt_set_boot_source, + rave_sp_wdt_get_boot_source, + &sp_wd->boot_source, "%u", sp_wd); + if (IS_ERR(p)) { + unregister_device(&sp_wd->dev); + return PTR_ERR(p); + } + + ret = sp_wd->variant->reset_reason(wdd); + if (ret < 0) { + dev_warn(dev, "Failed to query reset reason\n"); + return 0; + } + + switch (ret) { + case RAVE_SP_RESET_REASON_NORMAL: + reset_reason = "Normal poweroff"; + break; + case RAVE_SP_RESET_REASON_HW_WATCHDOG: + reset_reason = "PIC hardware watchdog"; + break; + case RAVE_SP_RESET_REASON_SW_WATCHDOG: + reset_reason = "PIC software watchdog"; + break; + case RAVE_SP_RESET_REASON_VOLTAGE: + reset_reason = "Input voltage out of range"; + break; + case RAVE_SP_RESET_REASON_HOST_REQUEST: + reset_reason = "Host requested"; + break; + case RAVE_SP_RESET_REASON_TEMPERATURE: + reset_reason = "Temperature out of range"; + break; + case RAVE_SP_RESET_REASON_BUTTON_PRESS: + reset_reason = "User requested"; + break; + case RAVE_SP_RESET_REASON_PIC_CM: + reset_reason = "Illegal configuration word"; + break; + case RAVE_SP_RESET_REASON_PIC_ILL_INST: + reset_reason = "Illegal instruction"; + break; + case RAVE_SP_RESET_REASON_PIC_TRAP: + reset_reason = "Illegal trap"; + break; + default: + case RAVE_SP_RESET_REASON_UKNOWN_REASON: + reset_reason = "Unknown"; + break; + case RAVE_SP_RESET_REASON_THERMAL_SENSOR: + reset_reason = "Thermal sensor"; + break; + case RAVE_SP_RESET_REASON_SW_VOLTAGE: + reset_reason = "Software detected brownout"; + break; + case RAVE_SP_RESET_REASON_CP_REQUEST: + reset_reason = "Command request"; + break; + } + + dev_info(dev, "Reset reason: %s\n", reset_reason); + return 0; +} + +static struct driver_d rave_sp_wdt_driver = { + .name = "rave-sp-wdt", + .probe = rave_sp_wdt_probe, + .of_compatible = DRV_OF_COMPAT(rave_sp_wdt_of_match), +}; +console_platform_driver(rave_sp_wdt_driver); -- cgit v1.2.3