diff options
author | Abhishek Shah <abhishek.shah@broadcom.com> | 2019-05-30 16:01:03 +0530 |
---|---|---|
committer | Jerome Forissier <jerome.forissier@linaro.org> | 2019-08-13 09:43:56 +0200 |
commit | cbb41c9147d0c4c2a2a31f80425a8df07835f0a1 (patch) | |
tree | 9d1f6c6c3b8c9fc2c8801298922606184a5aa09f | |
parent | f5df167c2ffb2b26de30bebe6e7a0674924b4de7 (diff) |
drivers: wdt: Add arm SP805 watchdog driver
Add sp805 watchdog driver with following functionality:
- start/reload watchdog with specified timeout
- stop watchdog
- ping watchdog (clear watchdog interrupt and reload it)
- register watchdog interrupt handler
Signed-off-by: Abhishek Shah <abhishek.shah@broadcom.com>
Reviewed-by: Sandeep Tripathy <sandeep.tripathy@broadcom.com>
Acked-by: Jens Wiklander <jens.wiklander@linaro.org>
Reviewed-by: Etienne Carriere <etienne.carriere@linaro.org>
-rw-r--r-- | core/arch/arm/plat-bcm/conf.mk | 1 | ||||
-rw-r--r-- | core/drivers/sp805_wdt.c | 142 | ||||
-rw-r--r-- | core/drivers/sub.mk | 1 | ||||
-rw-r--r-- | core/include/drivers/sp805_wdt.h | 68 | ||||
-rw-r--r-- | core/include/drivers/wdt.h | 36 |
5 files changed, 248 insertions, 0 deletions
diff --git a/core/arch/arm/plat-bcm/conf.mk b/core/arch/arm/plat-bcm/conf.mk index b6d2ca5d..73d9d545 100644 --- a/core/arch/arm/plat-bcm/conf.mk +++ b/core/arch/arm/plat-bcm/conf.mk @@ -23,6 +23,7 @@ $(call force,CFG_SECURE_TIME_SOURCE_CNTPCT,y) ifeq ($(PLATFORM_FLAVOR),ns3) $(call force,CFG_PL022,y) +$(call force,CFG_SP805_WDT,y) $(call force,CFG_BCM_HWRNG,y) $(call force,CFG_BCM_SOTP,y) $(call force,CFG_BCM_GPIO,y) diff --git a/core/drivers/sp805_wdt.c b/core/drivers/sp805_wdt.c new file mode 100644 index 00000000..1d034c21 --- /dev/null +++ b/core/drivers/sp805_wdt.c @@ -0,0 +1,142 @@ +// SPDX-License-Identifier: BSD-2-Clause +/* + * Copyright (c) 2019, Broadcom + */ + +#include <drivers/sp805_wdt.h> +#include <initcall.h> +#include <io.h> +#include <keep.h> +#include <kernel/interrupt.h> +#include <mm/core_memprot.h> +#include <stdint.h> +#include <trace.h> + +static vaddr_t chip_to_base(struct wdt_chip *chip) +{ + struct sp805_wdt_data *pd = + container_of(chip, struct sp805_wdt_data, chip); + + return io_pa_or_va(&pd->base); +} + +static TEE_Result sp805_setload(struct wdt_chip *chip, unsigned long timeout) +{ + struct sp805_wdt_data *pd = + container_of(chip, struct sp805_wdt_data, chip); + uint32_t load = 0; + + /* + * sp805 runs counter with given value twice, after the end of first + * counter it gives an interrupt and then starts counter again. If + * interrupt already occurred then it resets the system. This is why + * load is half of what should be required. + */ + if (MUL_OVERFLOW(timeout, pd->clk_rate, &load)) + return TEE_ERROR_SECURITY; + + load = (load / 2) - 1; + if (load < WDT_LOAD_MIN) + load = WDT_LOAD_MIN; + + pd->load_val = load; + return TEE_SUCCESS; +} + +static void sp805_config(struct wdt_chip *chip, bool enable) +{ + struct sp805_wdt_data *pd = + container_of(chip, struct sp805_wdt_data, chip); + vaddr_t base = chip_to_base(chip); + + io_write32(base + WDT_LOCK_OFFSET, WDT_UNLOCK_KEY); + io_write32(base + WDT_LOAD_OFFSET, pd->load_val); + io_write32(base + WDT_INTCLR_OFFSET, WDT_INT_CLR); + + if (enable) + io_write32(base + WDT_CONTROL_OFFSET, + WDT_INT_EN | WDT_RESET_EN); + + io_write32(base + WDT_LOCK_OFFSET, WDT_LOCK_KEY); + + /* Flush posted writes. */ + (void)io_read32(base + WDT_LOCK_OFFSET); +} + +static void sp805_ping(struct wdt_chip *chip) +{ + sp805_config(chip, false); +} + +static void sp805_enable(struct wdt_chip *chip) +{ + sp805_config(chip, true); +} + +static void sp805_disable(struct wdt_chip *chip) +{ + vaddr_t base = chip_to_base(chip); + + io_write32(base + WDT_LOCK_OFFSET, WDT_UNLOCK_KEY); + io_write32(base + WDT_CONTROL_OFFSET, 0); + io_write32(base + WDT_LOCK_OFFSET, WDT_LOCK_KEY); + + /* Flush posted writes. */ + (void)io_read32(base + WDT_LOCK_OFFSET); +} + +static enum itr_return wdt_itr_cb(struct itr_handler *h) +{ + struct wdt_chip *chip = h->data; + struct sp805_wdt_data *pd = + container_of(chip, struct sp805_wdt_data, chip); + + if (pd->itr_handler) + pd->itr_handler(chip); + + return ITRR_HANDLED; +} +KEEP_PAGER(wdt_itr_cb); + +TEE_Result sp805_register_itr_handler(struct sp805_wdt_data *pd, + uint32_t itr_num, uint32_t itr_flags, + sp805_itr_handler_func_t itr_handler) +{ + struct itr_handler *wdt_itr; + + assert(!pd->chip.wdt_itr); + + wdt_itr = calloc(1, sizeof(*wdt_itr)); + if (!wdt_itr) + return TEE_ERROR_OUT_OF_MEMORY; + + wdt_itr->it = itr_num; + wdt_itr->flags = itr_flags; + wdt_itr->handler = wdt_itr_cb; + wdt_itr->data = &pd->chip; + pd->itr_handler = itr_handler; + pd->chip.wdt_itr = wdt_itr; + + itr_add(wdt_itr); + itr_enable(wdt_itr->it); + + return TEE_SUCCESS; +} + +static const struct wdt_ops sp805_wdt_ops = { + .start = sp805_enable, + .stop = sp805_disable, + .ping = sp805_ping, + .set_timeout = sp805_setload, +}; +KEEP_PAGER(sp805_wdt_ops); + +TEE_Result sp805_wdt_init(struct sp805_wdt_data *pd, paddr_t base, + uint32_t clk_rate, uint32_t timeout) +{ + assert(pd); + pd->base.pa = base; + pd->clk_rate = clk_rate; + pd->chip.ops = &sp805_wdt_ops; + return sp805_setload(&pd->chip, timeout); +} diff --git a/core/drivers/sub.mk b/core/drivers/sub.mk index 4504b5ed..22e14291 100644 --- a/core/drivers/sub.mk +++ b/core/drivers/sub.mk @@ -5,6 +5,7 @@ srcs-$(CFG_TZC380) += tzc380.c srcs-$(CFG_GIC) += gic.c srcs-$(CFG_PL061) += pl061_gpio.c srcs-$(CFG_PL022) += pl022_spi.c +srcs-$(CFG_SP805_WDT) += sp805_wdt.c srcs-$(CFG_8250_UART) += serial8250_uart.c srcs-$(CFG_16550_UART) += ns16550.c srcs-$(CFG_IMX_SNVS) += imx_snvs.c diff --git a/core/include/drivers/sp805_wdt.h b/core/include/drivers/sp805_wdt.h new file mode 100644 index 00000000..2edf7d42 --- /dev/null +++ b/core/include/drivers/sp805_wdt.h @@ -0,0 +1,68 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * Copyright 2019 Broadcom. + */ + +#ifndef SP805_WDT_H +#define SP805_WDT_H + +#include <drivers/wdt.h> +#include <kernel/interrupt.h> +#include <mm/core_memprot.h> +#include <types_ext.h> + +/* SP805 register offset */ +#define WDT_LOAD_OFFSET 0x000 +#define WDT_CONTROL_OFFSET 0x008 +#define WDT_INTCLR_OFFSET 0x00c +#define WDT_LOCK_OFFSET 0xc00 + +/* Magic word to unlock the wd registers */ +#define WDT_UNLOCK_KEY 0x1ACCE551 +#define WDT_LOCK_KEY 0x1 + +/* Register field definitions */ +#define WDT_INT_EN BIT(0) +#define WDT_RESET_EN BIT(1) +#define WDT_INT_CLR BIT(0) + +#define WDT_LOAD_MIN 0x1 + +typedef void (*sp805_itr_handler_func_t)(struct wdt_chip *chip); + +struct sp805_wdt_data { + struct io_pa_va base; + struct wdt_chip chip; + uint32_t clk_rate; + uint32_t load_val; + uint32_t itr_num; + sp805_itr_handler_func_t itr_handler; +}; + +/* + * Initialize sp805 watchdog timer + * + * @pd: allocated sp805 watchdog timer platform data + * @base: physical base address of sp805 watchdog timer + * @clk_rate: rate of the clock driving the watchdog timer hardware + * @timeout: watchdog timer timeout in seconds + * Return a TEE_Result compliant status + */ +TEE_Result sp805_wdt_init(struct sp805_wdt_data *pd, paddr_t base, + uint32_t clk_rate, uint32_t timeout); + +/* + * Optionally register sp805 watchdog timer interrupt handler + * + * @pd: platform data of sp805 watchdog timer for which interrupt handler + * is to be registered + * @itr_num: sp805 watchdog timer interrupt id + * @itr_flag: interrupt attributes + * @itr_handler: Optional interrupt handler callback + * Return a TEE_Result compliant status + */ +TEE_Result sp805_register_itr_handler(struct sp805_wdt_data *pd, + uint32_t itr_num, uint32_t itr_flag, + sp805_itr_handler_func_t itr_handler); + +#endif /* SP805_WDT_H */ diff --git a/core/include/drivers/wdt.h b/core/include/drivers/wdt.h new file mode 100644 index 00000000..5f736dcb --- /dev/null +++ b/core/include/drivers/wdt.h @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * Copyright 2019 Broadcom. + */ + +#ifndef DRIVERS_WDT_H +#define DRIVERS_WDT_H + +#include <kernel/interrupt.h> +#include <tee_api_types.h> + +struct wdt_chip { + const struct wdt_ops *ops; + struct itr_handler *wdt_itr; +}; + +/* + * struct wdt_ops - The watchdog device operations + * + * @start: The routine for starting the watchdog device. + * @stop: The routine for stopping the watchdog device. + * @ping: The routine that sends a keepalive ping to the watchdog device. + * @set_timeout:The routine that finds the load value that will reset system in + * required timeout (in seconds). + * + * The wdt_ops structure contains a list of low-level operations + * that control a watchdog device. + */ +struct wdt_ops { + void (*start)(struct wdt_chip *chip); + void (*stop)(struct wdt_chip *chip); + void (*ping)(struct wdt_chip *chip); + TEE_Result (*set_timeout)(struct wdt_chip *chip, unsigned long timeout); +}; + +#endif /* DRIVERS_WDT_H */ |