[PATCH 0/3] timer: add SP804 DM_TIMER driver (and use it)

Some boards were using an ARM SP804 timer IP, but were driving that using hardcoded addresses and input frequencies, using the CONFIG_SYS_TIMER_* macros. This is deprecated and scheduled for removal.
This series adds a DM_TIMER compliant SP804 driver in patch 1, and lets the Highbank board use that (patch 3). Since the Highbank DT somewhat hides the timer base fixed-clock inside a subnode, we need some simple-bus like driver in patch 2 to help the driver find the timer frequency.
The SP804 driver should be usable by other SoCs using the SP804 timer, but I don't have any to test it there.
Cheers, Andre
Andre Przywara (3): timer: add SP804 UCLASS timer driver highbank: scan into hb_sregs DT subnodes highbank: switch to use the Arm SP804 DM_TIMER driver
arch/arm/Kconfig | 2 + board/highbank/Makefile | 2 +- board/highbank/hb_sregs.c | 45 +++++++++++++++ drivers/timer/Kconfig | 6 ++ drivers/timer/Makefile | 1 + drivers/timer/sp804_timer.c | 108 ++++++++++++++++++++++++++++++++++++ include/configs/highbank.h | 4 -- 7 files changed, 163 insertions(+), 5 deletions(-) create mode 100644 board/highbank/hb_sregs.c create mode 100644 drivers/timer/sp804_timer.c

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 | 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, +};

On Thu, Oct 20, 2022 at 11:10:23PM +0100, Andre Przywara wrote:
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
Applied to u-boot/master, thanks!

The DT used for Calxeda Highbank and Midway systems exposes a "system registers" block, modeled as a DT subnode. This includes several clocks, including the two fixed clocks for the main oscillator and timer.
So far U-Boot was ignorant of this special construct (a "clocks" node within the "hb-sregs" node), as it didn't need the PLL clocks in there. But that also meant we lost the fixed clocks, which form the base for the UART baudrate generator and also the SP804 timer.
To allow the generic PL011 and SP804 driver to read the clock rate, add a simple bus driver, which triggers the DT node discovery inside this special node. As we only care about the fixed clocks (we don't have drivers for the PLLs anyway), just ignore the address translation (for now).
The binding is described in bindings/arm/calxeda/hb-sregs.yaml, the DT snippet in question looks like:
======================= sregs@fff3c000 { compatible = "calxeda,hb-sregs"; reg = <0xfff3c000 0x1000>;
clocks { #address-cells = <1>; #size-cells = <0>;
osc: oscillator { #clock-cells = <0>; compatible = "fixed-clock"; clock-frequency = <33333000>; }; .... }; }; =======================
Signed-off-by: Andre Przywara andre.przywara@arm.com --- board/highbank/Makefile | 2 +- board/highbank/hb_sregs.c | 45 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 board/highbank/hb_sregs.c
diff --git a/board/highbank/Makefile b/board/highbank/Makefile index 57f7f2e2a65..9e432119849 100644 --- a/board/highbank/Makefile +++ b/board/highbank/Makefile @@ -3,4 +3,4 @@ # (C) Copyright 2000-2006 # Wolfgang Denk, DENX Software Engineering, wd@denx.de.
-obj-y := highbank.o ahci.o +obj-y := highbank.o ahci.o hb_sregs.o diff --git a/board/highbank/hb_sregs.c b/board/highbank/hb_sregs.c new file mode 100644 index 00000000000..d9dd2c2bf67 --- /dev/null +++ b/board/highbank/hb_sregs.c @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Calxeda Highbank/Midway "system registers" bus driver + * + * There is a "clocks" subnode inside the top node, which groups all clocks, + * both programmable PLLs as well as fixed clocks. + * Simple allow the DT enumeration to look inside this node, so that we can + * read the fixed clock frequencies using the DM clock framework. + * + * Copyright (C) 2019 Arm Ltd. + */ + +#include <common.h> +#include <dm.h> +#include <dm/lists.h> + +static int hb_sregs_scan_fdt_dev(struct udevice *dev) +{ + ofnode clock_node, node; + + /* Search for subnode called "clocks". */ + ofnode_for_each_subnode(clock_node, dev_ofnode(dev)) { + if (!ofnode_name_eq(clock_node, "clocks")) + continue; + + /* Enumerate all nodes inside this "clocks" subnode. */ + ofnode_for_each_subnode(node, clock_node) + lists_bind_fdt(dev, node, NULL, NULL, false); + return 0; + } + + return -ENOENT; +} + +static const struct udevice_id highbank_sreg_ids[] = { + { .compatible = "calxeda,hb-sregs" }, + {} +}; + +U_BOOT_DRIVER(hb_sregs) = { + .name = "hb-sregs", + .id = UCLASS_SIMPLE_BUS, + .bind = hb_sregs_scan_fdt_dev, + .of_match = highbank_sreg_ids, +};

On Thu, Oct 20, 2022 at 11:10:24PM +0100, Andre Przywara wrote:
The DT used for Calxeda Highbank and Midway systems exposes a "system registers" block, modeled as a DT subnode. This includes several clocks, including the two fixed clocks for the main oscillator and timer.
So far U-Boot was ignorant of this special construct (a "clocks" node within the "hb-sregs" node), as it didn't need the PLL clocks in there. But that also meant we lost the fixed clocks, which form the base for the UART baudrate generator and also the SP804 timer.
To allow the generic PL011 and SP804 driver to read the clock rate, add a simple bus driver, which triggers the DT node discovery inside this special node. As we only care about the fixed clocks (we don't have drivers for the PLLs anyway), just ignore the address translation (for now).
The binding is described in bindings/arm/calxeda/hb-sregs.yaml, the DT snippet in question looks like:
======================= sregs@fff3c000 { compatible = "calxeda,hb-sregs"; reg = <0xfff3c000 0x1000>;
clocks { #address-cells = <1>; #size-cells = <0>; osc: oscillator { #clock-cells = <0>; compatible = "fixed-clock"; clock-frequency = <33333000>; }; .... };
};
Signed-off-by: Andre Przywara andre.przywara@arm.com
Applied to u-boot/master, thanks!

So far the Calxeda machines were using the CONFIG_SYS_TIMER_* macros to simply hardcode the address of the counter register of the SP804 timer. This method is deprecated and scheduled for removal.
Use the newly introduced SP804 DM_TIMER driver to provide timer functionality on Highbank and Midway machines. The base address and base frequency are taken from the devicetree.
Signed-off-by: Andre Przywara andre.przywara@arm.com --- arch/arm/Kconfig | 2 ++ include/configs/highbank.h | 4 ---- 2 files changed, 2 insertions(+), 4 deletions(-)
diff --git a/arch/arm/Kconfig b/arch/arm/Kconfig index 2e833940525..3269d0c5196 100644 --- a/arch/arm/Kconfig +++ b/arch/arm/Kconfig @@ -793,6 +793,8 @@ config ARCH_HIGHBANK select AHCI select DM_ETH select PHYS_64BIT + select TIMER + select SP804_TIMER imply OF_HAS_PRIOR_STAGE
config ARCH_INTEGRATOR diff --git a/include/configs/highbank.h b/include/configs/highbank.h index bb6cc957261..5e2b50bbac1 100644 --- a/include/configs/highbank.h +++ b/include/configs/highbank.h @@ -8,10 +8,6 @@
#define CONFIG_SYS_BOOTMAPSZ (16 << 20)
-#define CONFIG_SYS_TIMER_RATE (150000000/256) -#define CONFIG_SYS_TIMER_COUNTER (0xFFF34000 + 0x4) -#define CONFIG_SYS_TIMER_COUNTS_DOWN - #define CONFIG_PL011_CLOCK 150000000
/*

On Thu, Oct 20, 2022 at 11:10:25PM +0100, Andre Przywara wrote:
So far the Calxeda machines were using the CONFIG_SYS_TIMER_* macros to simply hardcode the address of the counter register of the SP804 timer. This method is deprecated and scheduled for removal.
Use the newly introduced SP804 DM_TIMER driver to provide timer functionality on Highbank and Midway machines. The base address and base frequency are taken from the devicetree.
Signed-off-by: Andre Przywara andre.przywara@arm.com
Applied to u-boot/master, thanks!
participants (2)
-
Andre Przywara
-
Tom Rini