From 731d108dd0706e76addc6945e8c8709a05cb4728 Mon Sep 17 00:00:00 2001 From: Andre Przywara Date: Thu, 20 Oct 2022 23:10:23 +0100 Subject: [PATCH] timer: add SP804 UCLASS timer driver The "Arm Ltd. Dual-Timer Module (SP804)" is a simple 32-bit count-down timer IP with interrupt functionality, and is used in some SoCs from various vendors. Add a simple DM compliant timer driver, to allow users of the SP804 to switch to DM_TIMER. This relies on the input clock to be accessible via the DM clock framework, which should be fine as we probably look at fixed-clock's here anyway. We re-program the control register in the probe() function, but keep the divider in place, in case this has been set to something on purpose before. The TRM for the timer IP can be found here: https://developer.arm.com/documentation/ddi0271/latest Signed-off-by: Andre Przywara --- drivers/timer/Kconfig | 6 ++ drivers/timer/Makefile | 1 + drivers/timer/sp804_timer.c | 108 ++++++++++++++++++++++++++++++++++++ 3 files changed, 115 insertions(+) create mode 100644 drivers/timer/sp804_timer.c diff --git a/drivers/timer/Kconfig b/drivers/timer/Kconfig index 3b2fa2489b..6d6665005c 100644 --- a/drivers/timer/Kconfig +++ b/drivers/timer/Kconfig @@ -239,6 +239,12 @@ config ARM_GLOBAL_TIMER Select this to enable global timer found on ARM Cortex A9 based devices. +config SP804_TIMER + bool "ARM SP804 timer support" + depends on TIMER + help + ARM SP804 dual timer IP support + config STM32_TIMER bool "STM32 timer support" depends on TIMER diff --git a/drivers/timer/Makefile b/drivers/timer/Makefile index bc8b2c0bd0..6470fd5426 100644 --- a/drivers/timer/Makefile +++ b/drivers/timer/Makefile @@ -23,6 +23,7 @@ obj-$(CONFIG_RENESAS_OSTM_TIMER) += ostm_timer.o obj-$(CONFIG_RISCV_TIMER) += riscv_timer.o obj-$(CONFIG_ROCKCHIP_TIMER) += rockchip_timer.o obj-$(CONFIG_SANDBOX_TIMER) += sandbox_timer.o +obj-$(CONFIG_SP804_TIMER) += sp804_timer.o obj-$(CONFIG_$(SPL_)SIFIVE_CLINT) += sifive_clint_timer.o obj-$(CONFIG_ARM_GLOBAL_TIMER) += arm_global_timer.o obj-$(CONFIG_STM32_TIMER) += stm32_timer.o diff --git a/drivers/timer/sp804_timer.c b/drivers/timer/sp804_timer.c new file mode 100644 index 0000000000..8fd4afb15a --- /dev/null +++ b/drivers/timer/sp804_timer.c @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * ARM PrimeCell Dual-Timer Module (SP804) driver + * Copyright (C) 2022 Arm Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +DECLARE_GLOBAL_DATA_PTR; + +#define SP804_TIMERX_LOAD 0x00 +#define SP804_TIMERX_VALUE 0x04 +#define SP804_TIMERX_CONTROL 0x08 + +#define SP804_CTRL_TIMER_ENABLE (1U << 7) +#define SP804_CTRL_TIMER_PERIODIC (1U << 6) +#define SP804_CTRL_INT_ENABLE (1U << 5) +#define SP804_CTRL_TIMER_PRESCALE_SHIFT 2 +#define SP804_CTRL_TIMER_PRESCALE_MASK (3U << SP804_CTRL_TIMER_PRESCALE_SHIFT) +#define SP804_CTRL_TIMER_32BIT (1U << 1) +#define SP804_CTRL_ONESHOT (1U << 0) + + +struct sp804_timer_plat { + uintptr_t base; +}; + +static u64 sp804_timer_get_count(struct udevice *dev) +{ + struct sp804_timer_plat *plat = dev_get_plat(dev); + uint32_t cntr = readl(plat->base + SP804_TIMERX_VALUE); + + /* timers are down-counting */ + return ~0u - cntr; +} + +static int sp804_clk_of_to_plat(struct udevice *dev) +{ + struct sp804_timer_plat *plat = dev_get_plat(dev); + + plat->base = dev_read_addr(dev); + if (!plat->base) + return -ENOENT; + + return 0; +} + +static int sp804_timer_probe(struct udevice *dev) +{ + struct sp804_timer_plat *plat = dev_get_plat(dev); + struct timer_dev_priv *uc_priv = dev_get_uclass_priv(dev); + struct clk base_clk; + unsigned int divider = 1; + uint32_t ctlr; + int ret; + + ctlr = readl(plat->base + SP804_TIMERX_CONTROL); + ctlr &= SP804_CTRL_TIMER_PRESCALE_MASK; + switch (ctlr >> SP804_CTRL_TIMER_PRESCALE_SHIFT) { + case 0x0: divider = 1; break; + case 0x1: divider = 16; break; + case 0x2: divider = 256; break; + case 0x3: printf("illegal!\n"); break; + } + + ret = clk_get_by_index(dev, 0, &base_clk); + if (ret) { + printf("could not find SP804 timer base clock in DT\n"); + return ret; + } + + uc_priv->clock_rate = clk_get_rate(&base_clk) / divider; + + /* keep divider, free-running, wrapping, no IRQs, 32-bit mode */ + ctlr |= SP804_CTRL_TIMER_32BIT | SP804_CTRL_TIMER_ENABLE; + writel(ctlr, plat->base + SP804_TIMERX_CONTROL); + + return 0; +} + +static const struct timer_ops sp804_timer_ops = { + .get_count = sp804_timer_get_count, +}; + +static const struct udevice_id sp804_timer_ids[] = { + { .compatible = "arm,sp804" }, + {} +}; + +U_BOOT_DRIVER(arm_sp804_timer) = { + .name = "arm_sp804_timer", + .id = UCLASS_TIMER, + .of_match = sp804_timer_ids, + .probe = sp804_timer_probe, + .ops = &sp804_timer_ops, + .plat_auto = sizeof(struct sp804_timer_plat), + .of_to_plat = sp804_clk_of_to_plat, +}; -- 2.39.5