clocksource/drivers: Add a goldfish-timer clocksource
authorLaurent Vivier <laurent@vivier.eu>
Wed, 6 Apr 2022 20:15:22 +0000 (22:15 +0200)
committerGeert Uytterhoeven <geert@linux-m68k.org>
Mon, 11 Apr 2022 09:48:01 +0000 (11:48 +0200)
Add a clocksource based on the goldfish-rtc device.

Move the timer register definition to <clocksource/timer-goldfish.h>

This kernel implementation is based on the QEMU upstream implementation:

   https://git.qemu.org/?p=qemu.git;a=blob_plain;f=hw/rtc/goldfish_rtc.c

goldfish-timer is a high-precision signed 64-bit nanosecond timer.
It is part of the 'goldfish' virtual hardware platform used to run
some emulated Android systems under QEMU.
This timer only supports oneshot event.

Signed-off-by: Laurent Vivier <laurent@vivier.eu>
Acked-by: Daniel Lezcano <daniel.lezcano@linaro.org>
Link: https://lore.kernel.org/r/20220406201523.243733-4-laurent@vivier.eu
Signed-off-by: Geert Uytterhoeven <geert@linux-m68k.org>
drivers/clocksource/Kconfig
drivers/clocksource/Makefile
drivers/clocksource/timer-goldfish.c [new file with mode: 0644]
drivers/rtc/rtc-goldfish.c
include/clocksource/timer-goldfish.h [new file with mode: 0644]

index 1589ae7d5abb632cee3f866893414f462334da28..06866bfa1826907c3456119b350485ee99e0839d 100644 (file)
@@ -711,4 +711,11 @@ config MICROCHIP_PIT64B
          modes and high resolution. It is used as a clocksource
          and a clockevent.
 
+config GOLDFISH_TIMER
+       bool "Clocksource using goldfish-rtc"
+       depends on M68K || COMPILE_TEST
+       depends on RTC_DRV_GOLDFISH
+       help
+         Support for the timer/counter of goldfish-rtc
+
 endmenu
index 9c85ee2bb373505d03370af1f275944f6a1ceaf3..b839beb6ea539f8cd133543e0ebed81720f81111 100644 (file)
@@ -88,3 +88,4 @@ obj-$(CONFIG_GX6605S_TIMER)           += timer-gx6605s.o
 obj-$(CONFIG_HYPERV_TIMER)             += hyperv_timer.o
 obj-$(CONFIG_MICROCHIP_PIT64B)         += timer-microchip-pit64b.o
 obj-$(CONFIG_MSC313E_TIMER)            += timer-msc313e.o
+obj-$(CONFIG_GOLDFISH_TIMER)           += timer-goldfish.o
diff --git a/drivers/clocksource/timer-goldfish.c b/drivers/clocksource/timer-goldfish.c
new file mode 100644 (file)
index 0000000..0512d5e
--- /dev/null
@@ -0,0 +1,153 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/interrupt.h>
+#include <linux/ioport.h>
+#include <linux/clocksource.h>
+#include <linux/clockchips.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/goldfish.h>
+#include <clocksource/timer-goldfish.h>
+
+struct goldfish_timer {
+       struct clocksource cs;
+       struct clock_event_device ced;
+       struct resource res;
+       void __iomem *base;
+};
+
+static struct goldfish_timer *ced_to_gf(struct clock_event_device *ced)
+{
+       return container_of(ced, struct goldfish_timer, ced);
+}
+
+static struct goldfish_timer *cs_to_gf(struct clocksource *cs)
+{
+       return container_of(cs, struct goldfish_timer, cs);
+}
+
+static u64 goldfish_timer_read(struct clocksource *cs)
+{
+       struct goldfish_timer *timerdrv = cs_to_gf(cs);
+       void __iomem *base = timerdrv->base;
+       u32 time_low, time_high;
+       u64 ticks;
+
+       /*
+        * time_low: get low bits of current time and update time_high
+        * time_high: get high bits of time at last time_low read
+        */
+       time_low = gf_ioread32(base + TIMER_TIME_LOW);
+       time_high = gf_ioread32(base + TIMER_TIME_HIGH);
+
+       ticks = ((u64)time_high << 32) | time_low;
+
+       return ticks;
+}
+
+static int goldfish_timer_set_oneshot(struct clock_event_device *evt)
+{
+       struct goldfish_timer *timerdrv = ced_to_gf(evt);
+       void __iomem *base = timerdrv->base;
+
+       gf_iowrite32(0, base + TIMER_ALARM_HIGH);
+       gf_iowrite32(0, base + TIMER_ALARM_LOW);
+       gf_iowrite32(1, base + TIMER_IRQ_ENABLED);
+
+       return 0;
+}
+
+static int goldfish_timer_shutdown(struct clock_event_device *evt)
+{
+       struct goldfish_timer *timerdrv = ced_to_gf(evt);
+       void __iomem *base = timerdrv->base;
+
+       gf_iowrite32(0, base + TIMER_IRQ_ENABLED);
+
+       return 0;
+}
+
+static int goldfish_timer_next_event(unsigned long delta,
+                                    struct clock_event_device *evt)
+{
+       struct goldfish_timer *timerdrv = ced_to_gf(evt);
+       void __iomem *base = timerdrv->base;
+       u64 now;
+
+       now = goldfish_timer_read(&timerdrv->cs);
+
+       now += delta;
+
+       gf_iowrite32(upper_32_bits(now), base + TIMER_ALARM_HIGH);
+       gf_iowrite32(lower_32_bits(now), base + TIMER_ALARM_LOW);
+
+       return 0;
+}
+
+static irqreturn_t goldfish_timer_irq(int irq, void *dev_id)
+{
+       struct goldfish_timer *timerdrv = dev_id;
+       struct clock_event_device *evt = &timerdrv->ced;
+       void __iomem *base = timerdrv->base;
+
+       gf_iowrite32(1, base + TIMER_CLEAR_INTERRUPT);
+
+       evt->event_handler(evt);
+
+       return IRQ_HANDLED;
+}
+
+int __init goldfish_timer_init(int irq, void __iomem *base)
+{
+       struct goldfish_timer *timerdrv;
+       int ret;
+
+       timerdrv = kzalloc(sizeof(*timerdrv), GFP_KERNEL);
+       if (!timerdrv)
+               return -ENOMEM;
+
+       timerdrv->base = base;
+
+       timerdrv->ced = (struct clock_event_device){
+               .name                   = "goldfish_timer",
+               .features               = CLOCK_EVT_FEAT_ONESHOT,
+               .set_state_shutdown     = goldfish_timer_shutdown,
+               .set_state_oneshot      = goldfish_timer_set_oneshot,
+               .set_next_event         = goldfish_timer_next_event,
+       };
+
+       timerdrv->res = (struct resource){
+               .name  = "goldfish_timer",
+               .start = (unsigned long)base,
+               .end   = (unsigned long)base + 0xfff,
+       };
+
+       ret = request_resource(&iomem_resource, &timerdrv->res);
+       if (ret) {
+               pr_err("Cannot allocate '%s' resource\n", timerdrv->res.name);
+               return ret;
+       }
+
+       timerdrv->cs = (struct clocksource){
+               .name           = "goldfish_timer",
+               .rating         = 400,
+               .read           = goldfish_timer_read,
+               .mask           = CLOCKSOURCE_MASK(64),
+               .flags          = 0,
+               .max_idle_ns    = LONG_MAX,
+       };
+
+       clocksource_register_hz(&timerdrv->cs, NSEC_PER_SEC);
+
+       ret = request_irq(irq, goldfish_timer_irq, IRQF_TIMER,
+                         "goldfish_timer", timerdrv);
+       if (ret) {
+               pr_err("Couldn't register goldfish-timer interrupt\n");
+               return ret;
+       }
+
+       clockevents_config_and_register(&timerdrv->ced, NSEC_PER_SEC,
+                                       1, 0xffffffff);
+
+       return 0;
+}
index eb1929b0cbb6018310c06faab112b37c7928ce33..59c0f38cc08dfe9ac69317ca6514362b027f1a7f 100644 (file)
 #include <linux/platform_device.h>
 #include <linux/rtc.h>
 #include <linux/goldfish.h>
-
-#define TIMER_TIME_LOW         0x00    /* get low bits of current time  */
-                                       /*   and update TIMER_TIME_HIGH  */
-#define TIMER_TIME_HIGH        0x04    /* get high bits of time at last */
-                                       /*   TIMER_TIME_LOW read         */
-#define TIMER_ALARM_LOW        0x08    /* set low bits of alarm and     */
-                                       /*   activate it                 */
-#define TIMER_ALARM_HIGH       0x0c    /* set high bits of next alarm   */
-#define TIMER_IRQ_ENABLED      0x10
-#define TIMER_CLEAR_ALARM      0x14
-#define TIMER_ALARM_STATUS     0x18
-#define TIMER_CLEAR_INTERRUPT  0x1c
+#include <clocksource/timer-goldfish.h>
 
 struct goldfish_rtc {
        void __iomem *base;
diff --git a/include/clocksource/timer-goldfish.h b/include/clocksource/timer-goldfish.h
new file mode 100644 (file)
index 0000000..05a3a4f
--- /dev/null
@@ -0,0 +1,31 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * goldfish-timer clocksource
+ * Registers definition for the goldfish-timer device
+ */
+
+#ifndef _CLOCKSOURCE_TIMER_GOLDFISH_H
+#define _CLOCKSOURCE_TIMER_GOLDFISH_H
+
+/*
+ * TIMER_TIME_LOW       get low bits of current time and update TIMER_TIME_HIGH
+ * TIMER_TIME_HIGH      get high bits of time at last TIMER_TIME_LOW read
+ * TIMER_ALARM_LOW      set low bits of alarm and activate it
+ * TIMER_ALARM_HIGH     set high bits of next alarm
+ * TIMER_IRQ_ENABLED    enable alarm interrupt
+ * TIMER_CLEAR_ALARM    disarm an existing alarm
+ * TIMER_ALARM_STATUS   alarm status (running or not)
+ * TIMER_CLEAR_INTERRUPT clear interrupt
+ */
+#define TIMER_TIME_LOW         0x00
+#define TIMER_TIME_HIGH                0x04
+#define TIMER_ALARM_LOW                0x08
+#define TIMER_ALARM_HIGH       0x0c
+#define TIMER_IRQ_ENABLED      0x10
+#define TIMER_CLEAR_ALARM      0x14
+#define TIMER_ALARM_STATUS     0x18
+#define TIMER_CLEAR_INTERRUPT  0x1c
+
+extern int goldfish_timer_init(int irq, void __iomem *base);
+
+#endif /* _CLOCKSOURCE_TIMER_GOLDFISH_H */