Re: [PATCH v2] clocksource: Add node counter timer driver for MIPS/Loongson64

From: Philippe Mathieu-Daudé
Date: Mon May 27 2024 - 08:21:25 EST


Hi Jiaxun,

On 17/5/24 19:13, Jiaxun Yang wrote:
Node counter is a timer presents on many Loongson-3 series CPUs.
It is maintained on every node in system. To avoid synchronisation
complexity we only access the copy from first node in system.

It also has many ways to be accessed, on latest Loongson-3 CPU with
IOCSR instruction support it should be accessed with a IOCSR request,
while on earlier Loongson-3 CPUs it is attached to a 32 bits MMIO bus.
For QEMU's Loongson-3 virt system it is mapped to a 64 bit MMIO location.

On some rare case the counter is disabled by firmware or not present
on chip, so we need to perform a lightweight test to ensure it is
running before actually use it.

Signed-off-by: Jiaxun Yang <jiaxun.yang@xxxxxxxxxxx>
---
Changes in v2:
- Fix build failure when it's not enabled.
- Link to v1: https://lore.kernel.org/r/20240512-loongson_nodecnt-v1-1-2157b92ef8f8@xxxxxxxxxxx
---
MAINTAINERS | 1 +
arch/mips/include/asm/mach-loongson64/loongson.h | 3 +
arch/mips/loongson64/time.c | 3 +
drivers/clocksource/Kconfig | 8 ++
drivers/clocksource/loongson-nodecnt.c | 112 +++++++++++++++++++++++
5 files changed, 127 insertions(+)


diff --git a/drivers/clocksource/loongson-nodecnt.c b/drivers/clocksource/loongson-nodecnt.c
new file mode 100644
index 000000000000..3cea4045ce75
--- /dev/null
+++ b/drivers/clocksource/loongson-nodecnt.c


+#define NODECNT_REGBASE 0x3ff00408
+
+static void __iomem *nodecnt_reg;
+static u64 (*nodecnt_read_fn)(void);
+
+static u64 notrace nodecnt_read_2x32(void)
+{
+ unsigned int hi, hi2, lo;
+
+ do {
+ hi = readl_relaxed(nodecnt_reg + 4);
+ lo = readl_relaxed(nodecnt_reg);
+ hi2 = readl_relaxed(nodecnt_reg + 4);
+ } while (hi2 != hi);
+
+ return (((u64) hi) << 32) + lo;
+}
+
+static u64 notrace nodecnt_read_64(void)
+{
+ return readq_relaxed(nodecnt_reg);
+}


+int __init nodecnt_clocksource_init(void)
+{
+ int err;
+ uint64_t delta;
+
+ if (!cpu_clock_freq)
+ return -ENODEV;
+
+ if (cpu_has_csr() && csr_readl(LOONGSON_CSR_FEATURES) & LOONGSON_CSRF_NODECNT) {
+ nodecnt_read_fn = nodecnt_read_csr;
+ } else if (loongson_sysconf.bridgetype == VIRTUAL) {
+ nodecnt_reg = ioremap(NODECNT_REGBASE, 8);
+ if (!nodecnt_reg)
+ return -ENOMEM;
+ nodecnt_read_fn = nodecnt_read_64;
+ } else {
+ switch (boot_cpu_data.processor_id & (PRID_IMP_MASK | PRID_REV_MASK)) {
+ case PRID_IMP_LOONGSON_64C | PRID_REV_LOONGSON3A_R2_0:
+ case PRID_IMP_LOONGSON_64C | PRID_REV_LOONGSON3A_R2_1:
+ case PRID_IMP_LOONGSON_64C | PRID_REV_LOONGSON3A_R3_0:
+ case PRID_IMP_LOONGSON_64C | PRID_REV_LOONGSON3A_R3_1:
+ break;
+ default:
+ return -ENODEV;
+ }
+ nodecnt_reg = ioremap(NODECNT_REGBASE, 8);
+ if (!nodecnt_reg)
+ return -ENOMEM;
+ nodecnt_read_fn = nodecnt_read_2x32;
+ }
+
+ /* Test if nodecnt is usable */
+ delta = nodecnt_read_fn();
+ udelay(10);
+ delta = nodecnt_read_fn() - delta;
+
+ if (!delta) {
+ pr_info("nodecnt: clocksource unusable\n");
+ err = -ENODEV;
+ goto out;
+ }
+
+ err = clocksource_register_hz(&nodecnt_clocksource, cpu_clock_freq);
+ if (err) {
+ pr_err("nodecnt: clocksource register failed\n");
+ goto out;
+ }
+
+ /* It fits for sched_clock if we don't suffer from cross node access */
+ if (loongson_sysconf.bridgetype == VIRTUAL || loongson_sysconf.nr_nodes <= 1)
+ sched_clock_register(nodecnt_read_fn, 64, cpu_clock_freq);

return 0; ? ...

+
+out:

.. or:

if (err) ?

+ if (nodecnt_reg)
+ iounmap(nodecnt_reg);
+ return err;
+}

---
base-commit: 75fa778d74b786a1608d55d655d42b480a6fa8bd
change-id: 20240512-loongson_nodecnt-0704f76bc959

Best regards,