timer: add SP804 UCLASS timer driver
authorAndre Przywara <andre.przywara@arm.com>
Thu, 20 Oct 2022 22:10:23 +0000 (23:10 +0100)
committerTom Rini <trini@konsulko.com>
Wed, 2 Nov 2022 17:58:17 +0000 (13:58 -0400)
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 <andre.przywara@arm.com>
drivers/timer/Kconfig
drivers/timer/Makefile
drivers/timer/sp804_timer.c [new file with mode: 0644]

index 3b2fa24..6d66650 100644 (file)
@@ -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
index bc8b2c0..6470fd5 100644 (file)
@@ -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 (file)
index 0000000..8fd4afb
--- /dev/null
@@ -0,0 +1,108 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * ARM PrimeCell Dual-Timer Module (SP804) driver
+ * Copyright (C) 2022 Arm Ltd.
+ */
+
+#include <common.h>
+#include <clk.h>
+#include <dm.h>
+#include <init.h>
+#include <log.h>
+#include <asm/global_data.h>
+#include <dm/ofnode.h>
+#include <mapmem.h>
+#include <dt-structs.h>
+#include <timer.h>
+#include <asm/io.h>
+
+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,
+};