[PATCH v3 13/19] timer: Add loongarch_timer driver
Yao Zi <[email protected]>
| Newsgroups | org.u-boot-project.lists.u-boot |
|---|---|
| Message-ID | <[email protected]> |
From: Jiaxun Yang <[email protected]> Implement a timer driver for LoongArch architecture timer. It's synced in hardware for every core in a system, and frequency information can be gathered from CPUCFG instruction. It is not described in fdt, thus a DRVINFO is declared for it. During probing, stability of the timer frequency is checked through CPUCFG, and the probing would fail if it isn't constant. Signed-off-by: Jiaxun Yang <[email protected]> Signed-off-by: Yao Zi <[email protected]> --- Changed from v2 - include div64.h for definition of lldiv() - Rename loongarch_timer_bind() to loongarch_timer_probe(), to correctly reflect that it's used in the probe() callback - Bail out with -ENODEV when missing LLFTP support, which means there isn't a timer with stable frequency No changes from v1 drivers/timer/Kconfig | 8 ++ drivers/timer/Makefile | 1 + drivers/timer/loongarch_timer.c | 132 ++++++++++++++++++++++++++++++++ 3 files changed, 141 insertions(+) create mode 100644 drivers/timer/loongarch_timer.c diff --git a/drivers/timer/Kconfig b/drivers/timer/Kconfig index 500a25638a96..6feb53328147 100644 --- a/drivers/timer/Kconfig +++ b/drivers/timer/Kconfig @@ -348,4 +348,12 @@ config GOLDFISH_TIMER It uses the Goldfish RTC hardware to provide a nanosecond-resolution timer, commonly found in QEMU virt machines. +config LOONGARCH_TIMER + bool "LoongArch CPU timer support" + depends on TIMER + depends on LOONGARCH + help + Select this to enable support for the timer found on + LoongArch CPUs. + endmenu diff --git a/drivers/timer/Makefile b/drivers/timer/Makefile index d8b3f2b65d4c..0b70b915bfeb 100644 --- a/drivers/timer/Makefile +++ b/drivers/timer/Makefile @@ -37,3 +37,4 @@ obj-$(CONFIG_IMX_GPT_TIMER) += imx-gpt-timer.o obj-$(CONFIG_XILINX_TIMER) += xilinx-timer.o obj-$(CONFIG_STARFIVE_TIMER) += starfive-timer.o obj-$(CONFIG_GOLDFISH_TIMER) += goldfish_timer.o +obj-$(CONFIG_LOONGARCH_TIMER) += loongarch_timer.o diff --git a/drivers/timer/loongarch_timer.c b/drivers/timer/loongarch_timer.c new file mode 100644 index 000000000000..c294112abe19 --- /dev/null +++ b/drivers/timer/loongarch_timer.c @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2024 Jiaxun Yang <[email protected]> + */ + +#include <asm/loongarch.h> +#include <div64.h> +#include <dm.h> +#include <dm/device_compat.h> +#include <errno.h> +#include <timer.h> + +static u64 notrace loongarch_timer_get_count(struct udevice *dev) +{ + u32 hi, lo; + + if (IS_ENABLED(CONFIG_64BIT)) + return drdtime(); + + do { + hi = rdtimeh(); + lo = rdtimel(); + } while (hi != rdtimeh()); + + return ((u64)hi << 32) | lo; +} + +static int loongarch_timer_get_freq_cpucfg(unsigned int *freq) +{ + unsigned int res; + unsigned int base_freq; + unsigned int cfm, cfd; + + res = read_cpucfg(LOONGARCH_CPUCFG2); + if (!(res & CPUCFG2_LLFTP)) + return -ENODEV; + + base_freq = read_cpucfg(LOONGARCH_CPUCFG4); + res = read_cpucfg(LOONGARCH_CPUCFG5); + cfm = res & 0xffff; + cfd = (res >> 16) & 0xffff; + + if (!base_freq || !cfm || !cfd) + return -EINVAL; + + *freq = base_freq * cfm / cfd; + + return 0; +} + +#if IS_ENABLED(CONFIG_TIMER_EARLY) +/** + * timer_early_get_rate() - Get the timer rate before driver model + */ +unsigned long notrace timer_early_get_rate(void) +{ + unsigned int freq; + int ret = loongarch_timer_get_freq_cpucfg(&freq); + + if (ret) + panic("Failed to read timer frequency from cpucfg: %d\n", ret); + + return freq; +} + +/** + * timer_early_get_count() - Get the timer count before driver model + * + */ +u64 notrace timer_early_get_count(void) +{ + return loongarch_timer_get_count(NULL); +} +#endif + +#if CONFIG_IS_ENABLED(BOOTSTAGE) +ulong timer_get_boot_us(void) +{ + int ret; + u64 ticks = 0; + u32 rate; + + ret = dm_timer_init(); + if (!ret) { + rate = timer_get_rate(gd->timer); + timer_get_count(gd->timer, &ticks); + } else { + ret = loongarch_timer_get_freq_cpucfg(&rate); + if (ret) + panic("failed to read timer frequency from cpucfg: %d\n", ret); + + ticks = loongarch_timer_get_count(NULL); + } + + /* Below is converted from time(us) = (tick / rate) * 1000000 */ + return lldiv(ticks * 1000, (rate / 1000)); +} +#endif + +static int loongarch_timer_probe(struct udevice *dev) +{ + struct timer_dev_priv *uc_priv = dev_get_uclass_priv(dev); + unsigned int rate; + int ret; + + ret = loongarch_timer_get_freq_cpucfg(&rate); + if (ret < 0) { + dev_err(dev, "failed to read timer frequency from cpucfg: %d\n", + ret); + return ret; + } + + uc_priv->clock_rate = rate; + + return 0; +} + +static const struct timer_ops loongarch_timer_ops = { + .get_count = loongarch_timer_get_count, +}; + +U_BOOT_DRIVER(loongarch_timer) = { + .name = "loongarch_timer", + .id = UCLASS_TIMER, + .probe = loongarch_timer_probe, + .ops = &loongarch_timer_ops, + .flags = DM_FLAG_PRE_RELOC, +}; + +U_BOOT_DRVINFO(loongarch_timer) = { + .name = "loongarch_timer", +}; -- 2.55.0