Implement a timer driver for LoongArch architecture driver.

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 I have to declare a DRVINFO
for it.

Signed-off-by: Jiaxun Yang <jiaxun.y...@flygoat.com>
---
 drivers/timer/Kconfig           |   8 +++
 drivers/timer/Makefile          |   1 +
 drivers/timer/loongarch_timer.c | 112 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 121 insertions(+)

diff --git a/drivers/timer/Kconfig b/drivers/timer/Kconfig
index 60519c3b536c..e85c74a537ad 100644
--- a/drivers/timer/Kconfig
+++ b/drivers/timer/Kconfig
@@ -340,4 +340,12 @@ config STARFIVE_TIMER
          Select this to enable support for the timer found on
          Starfive SoC.
 
+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 b93145e8d437..632d7ac8fd83 100644
--- a/drivers/timer/Makefile
+++ b/drivers/timer/Makefile
@@ -35,3 +35,4 @@ obj-$(CONFIG_MCHP_PIT64B_TIMER)       += mchp-pit64b-timer.o
 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_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..4b9f9307511f
--- /dev/null
+++ b/drivers/timer/loongarch_timer.c
@@ -0,0 +1,112 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2024 Jiaxun Yang <jiaxun.y...@flygoat.com>
+ */
+
+#include <dm.h>
+#include <errno.h>
+#include <timer.h>
+#include <asm/loongarch.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 unsigned int loongarch_timer_get_freq_cpucfg(void)
+{
+       unsigned int res;
+       unsigned int base_freq;
+       unsigned int cfm, cfd;
+
+       res = read_cpucfg(LOONGARCH_CPUCFG2);
+       if (!(res & CPUCFG2_LLFTP))
+               return 0;
+
+       base_freq = read_cpucfg(LOONGARCH_CPUCFG4);
+       res = read_cpucfg(LOONGARCH_CPUCFG5);
+       cfm = res & 0xffff;
+       cfd = (res >> 16) & 0xffff;
+
+       if (!base_freq || !cfm || !cfd)
+               return 0;
+
+       return (base_freq * cfm / cfd);
+}
+
+#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)
+{
+       return loongarch_timer_get_freq_cpucfg();
+}
+
+/**
+ * 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 {
+               rate = loongarch_timer_get_freq_cpucfg();
+               ticks = loongarch_timer_get_count(NULL);
+       }
+
+       /* Below is converted from time(us) = (tick / rate) * 10000000 */
+       return lldiv(ticks * 1000, (rate / 1000));
+}
+#endif
+
+static int loongarch_timer_bind(struct udevice *dev)
+{
+       struct timer_dev_priv *uc_priv = dev_get_uclass_priv(dev);
+       u32 rate;
+
+       rate = loongarch_timer_get_freq_cpucfg();
+       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_bind,
+       .ops = &loongarch_timer_ops,
+       .flags = DM_FLAG_PRE_RELOC,
+};
+
+U_BOOT_DRVINFO(loongarch_timer) = {
+       .name = "loongarch_timer",
+};

-- 
2.43.0

Reply via email to