[PATCH 1/3] timer: add SP804 UCLASS timer driver

Andre Przywara andre.przywara at arm.com
Fri Oct 21 00:10:23 CEST 2022


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 at arm.com>
---
 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 fd8745ffc2e..22da516f045 100644
--- a/drivers/timer/Kconfig
+++ b/drivers/timer/Kconfig
@@ -230,6 +230,12 @@ config SANDBOX_TIMER
 	  Select this to enable an emulated timer for sandbox. It gets
 	  time from host os.
 
+config SP804_TIMER
+	bool "ARM SP804 timer support"
+	depends on TIMER
+	help
+	  ARM SP804 dual timer IP support
+
 config STI_TIMER
 	bool "STi timer support"
 	depends on TIMER
diff --git a/drivers/timer/Makefile b/drivers/timer/Makefile
index 7bfb7749e97..ec332943876 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_STI_TIMER)		+= sti-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 00000000000..8fd4afb15a5
--- /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 <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,
+};
-- 
2.25.1



More information about the U-Boot mailing list