[PATCH 2/2] clocksource/drivers: Add Realtek Otto timer driver

From: Sander Vanheule
Date: Sun Jan 16 2022 - 16:40:01 EST


Driver to provide initial support for the timer/counter blocks found on
Realtek Otto MIPS SoCs.

This implementation hard enables the derived clock with a divisor of 2,
to obtain the highest possible timer resolution. A real derived and
reconfigurable timer clock can be implemented later, should the need
arise for longer timeouts.

Since this platform has other clocksources (CSRC_R4K or MIPS GIC), the
timers are only added as clockevent devices.

Signed-off-by: Sander Vanheule <sander@xxxxxxxxxxxxx>
---
MAINTAINERS | 6 +
drivers/clocksource/Kconfig | 8 +
drivers/clocksource/Makefile | 1 +
drivers/clocksource/timer-realtek-otto.c | 216 +++++++++++++++++++++++
4 files changed, 231 insertions(+)
create mode 100644 drivers/clocksource/timer-realtek-otto.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 6d9dafd09de0..ad7c9b10fcd8 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -16342,6 +16342,12 @@ S: Maintained
F: include/sound/rt*.h
F: sound/soc/codecs/rt*

+REALTEK OTTO TIMER
+M: Sander Vanheule <sander@xxxxxxxxxxxxx>
+S: Maintained
+F: Documentation/devicetree/bindings/timer/realtek,otto-tc.yaml
+F: drivers/clocksource/timer-realtek-otto.c
+
REALTEK OTTO WATCHDOG
M: Sander Vanheule <sander@xxxxxxxxxxxxx>
L: linux-watchdog@xxxxxxxxxxxxxxx
diff --git a/drivers/clocksource/Kconfig b/drivers/clocksource/Kconfig
index cfb8ea0df3b1..03557568182d 100644
--- a/drivers/clocksource/Kconfig
+++ b/drivers/clocksource/Kconfig
@@ -84,6 +84,14 @@ config IXP4XX_TIMER
help
Enables support for the Intel XScale IXP4xx SoC timer.

+config REALTEK_OTTO_TIMER
+ bool "Realtek Otto timer driver"
+ depends on MACH_REALTEK_RTL || COMPILE_TEST
+ select TIMER_OF
+ help
+ Enables support for the timer/counter found on Realtek's networking
+ SoC series RTL838x, RTL839x, RTL930x, RTL931x.
+
config ROCKCHIP_TIMER
bool "Rockchip timer driver" if COMPILE_TEST
depends on ARM || ARM64
diff --git a/drivers/clocksource/Makefile b/drivers/clocksource/Makefile
index fa5f624eadb6..43c792e4a574 100644
--- a/drivers/clocksource/Makefile
+++ b/drivers/clocksource/Makefile
@@ -23,6 +23,7 @@ obj-$(CONFIG_DW_APB_TIMER) += dw_apb_timer.o
obj-$(CONFIG_DW_APB_TIMER_OF) += dw_apb_timer_of.o
obj-$(CONFIG_FTTMR010_TIMER) += timer-fttmr010.o
obj-$(CONFIG_IXP4XX_TIMER) += timer-ixp4xx.o
+obj-$(CONFIG_REALTEK_OTTO_TIMER) += timer-realtek-otto.o
obj-$(CONFIG_ROCKCHIP_TIMER) += timer-rockchip.o
obj-$(CONFIG_CLKSRC_NOMADIK_MTU) += nomadik-mtu.o
obj-$(CONFIG_CLKSRC_DBX500_PRCMU) += clksrc-dbx500-prcmu.o
diff --git a/drivers/clocksource/timer-realtek-otto.c b/drivers/clocksource/timer-realtek-otto.c
new file mode 100644
index 000000000000..2c07be042614
--- /dev/null
+++ b/drivers/clocksource/timer-realtek-otto.c
@@ -0,0 +1,216 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Realtek Otto platform timer/counter
+ *
+ * Copyright
+ * Sander Vanheule - 2022
+ *
+ * Up-counting 28-bit timer that can operate in oneshot or repeat mode,
+ * providing interrupts at roll-over. The maximum value is written to the DATA
+ * register, while the current timer value can be read from the CNT register.
+ *
+ * A divided clock is used to drive the timer, derived from the bus clock. The
+ * clock divisor is configurable from 2 to 65535. Divisor values of 0 and 1
+ * disable the timer clock. The timer can also be enabled independently from
+ * the clock selection with the ENABLE flag.
+ *
+ * The SoC's interrupt controller can configure the CPU affinity of the timer
+ * interrupts to any subset of the available CPUs.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/clocksource.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+
+#include "timer-of.h"
+
+struct otto_tc_ctrl {
+ struct timer_of to;
+ spinlock_t lock;
+};
+
+#define OTTO_TC_REG_OFFSET(to, offset) (timer_of_base(to) + offset)
+
+#define OTTO_TC_REG_DATA(to) OTTO_TC_REG_OFFSET(to, 0x0)
+#define OTTO_TC_REG_CNT(to) OTTO_TC_REG_OFFSET(to, 0x4)
+#define OTTO_TC_WIDTH 28
+#define OTTO_TC_MAX_PERIOD BIT(OTTO_TC_WIDTH)
+
+#define OTTO_TC_REG_CTL(to) OTTO_TC_REG_OFFSET(to, 0x8)
+#define OTTO_TC_CTL_ENABLE BIT(28)
+#define OTTO_TC_CTL_MODE BIT(24)
+#define OTTO_TC_MODE_ONESHOT FIELD_PREP(OTTO_TC_CTL_MODE, 0)
+#define OTTO_TC_MODE_REPEAT FIELD_PREP(OTTO_TC_CTL_MODE, 1)
+#define OTTO_TC_CTL_DIVISOR GENMASK(15, 0)
+
+#define OTTO_TC_MIN_DIVISOR 2
+#define OTTO_TC_MAX_DIVISOR OTTO_TC_CTL_DIVISOR
+
+#define OTTO_TC_REG_ICTL(to) OTTO_TC_REG_OFFSET(to, 0xC)
+#define OTTO_TC_ICTL_ENABLE BIT(20)
+#define OTTO_TC_ICTL_STATUS BIT(16)
+
+static inline void otto_tc_irq_enable_clear(struct timer_of *to)
+{
+ writel(OTTO_TC_ICTL_ENABLE | OTTO_TC_ICTL_STATUS, OTTO_TC_REG_ICTL(to));
+}
+
+static inline struct otto_tc_ctrl *otto_tc_timer_to_ctrl(struct timer_of *to)
+{
+ return container_of(to, struct otto_tc_ctrl, to);
+}
+
+static irqreturn_t otto_tc_handler(int irq, void *dev_id)
+{
+ struct clock_event_device *ced = dev_id;
+ struct timer_of *to = to_timer_of(ced);
+
+ otto_tc_irq_enable_clear(to);
+ ced->event_handler(ced);
+
+ return IRQ_HANDLED;
+}
+
+static void otto_tc_set_divisor(struct otto_tc_ctrl *ctrl, u16 divisor)
+{
+ struct timer_of *to = &ctrl->to;
+ unsigned long flags;
+ u32 val;
+
+ spin_lock_irqsave(&ctrl->lock, flags);
+ val = readl(OTTO_TC_REG_CTL(to)) & ~OTTO_TC_CTL_DIVISOR;
+ val |= FIELD_PREP(OTTO_TC_CTL_DIVISOR, divisor);
+ writel(val, OTTO_TC_REG_CTL(to));
+ spin_unlock_irqrestore(&ctrl->lock, flags);
+}
+
+static void otto_tc_set_timeout(struct clock_event_device *ced, u32 value)
+{
+ struct timer_of *to = to_timer_of(ced);
+ struct otto_tc_ctrl *ctrl = otto_tc_timer_to_ctrl(to);
+ unsigned long flags;
+
+ spin_lock_irqsave(&ctrl->lock, flags);
+ writel(value, OTTO_TC_REG_DATA(to));
+ spin_unlock_irqrestore(&ctrl->lock, flags);
+}
+
+/*
+ * Timers can only be (re)started from the disabled state. The clkevt state
+ * machine is expected perform the necessary disables (shutdown) before moving
+ * a timer into an enabled state through .set_oneshot() or .set_periodic().
+ */
+static void otto_tc_set_mode(struct clock_event_device *ced, bool started, u32 mode)
+{
+ struct timer_of *to = to_timer_of(ced);
+ struct otto_tc_ctrl *ctrl = otto_tc_timer_to_ctrl(to);
+ unsigned long flags;
+ u32 val;
+
+ spin_lock_irqsave(&ctrl->lock, flags);
+ val = readl(OTTO_TC_REG_CTL(to));
+ val = (val & ~OTTO_TC_CTL_MODE) | mode;
+ if (started)
+ val |= OTTO_TC_CTL_ENABLE;
+ else
+ val &= ~OTTO_TC_CTL_ENABLE;
+ writel(val, OTTO_TC_REG_CTL(to));
+ spin_unlock_irqrestore(&ctrl->lock, flags);
+}
+
+static int otto_tc_set_next_event(unsigned long evt, struct clock_event_device *ced)
+{
+ otto_tc_set_timeout(ced, evt);
+
+ return 0;
+}
+
+static int otto_tc_set_periodic(struct clock_event_device *ced)
+{
+ struct timer_of *to = to_timer_of(ced);
+
+ otto_tc_set_timeout(ced, timer_of_period(to));
+ otto_tc_set_mode(ced, true, OTTO_TC_MODE_REPEAT);
+
+ return 0;
+}
+
+static int otto_tc_set_oneshot(struct clock_event_device *ced)
+{
+ otto_tc_set_mode(ced, true, OTTO_TC_MODE_ONESHOT);
+
+ return 0;
+}
+
+static int otto_tc_set_oneshot_stopped(struct clock_event_device *ced)
+{
+ otto_tc_set_mode(ced, false, OTTO_TC_MODE_ONESHOT);
+
+ return 0;
+}
+
+static int otto_tc_set_shutdown(struct clock_event_device *ced)
+{
+ otto_tc_set_mode(ced, false, 0);
+
+ return 0;
+}
+
+static void __init otto_tc_init_clkevt(struct timer_of *to)
+{
+ struct clock_event_device *ced = &to->clkevt;
+
+ ced->features = CLOCK_EVT_FEAT_ONESHOT | CLOCK_EVT_FEAT_PERIODIC |
+ CLOCK_EVT_FEAT_DYNIRQ;
+ ced->set_next_event = otto_tc_set_next_event;
+ ced->set_state_periodic = otto_tc_set_periodic;
+ ced->set_state_oneshot = otto_tc_set_oneshot;
+ ced->set_state_oneshot_stopped = otto_tc_set_oneshot_stopped;
+ ced->set_state_shutdown = otto_tc_set_shutdown;
+ ced->cpumask = cpumask_of(0);
+ ced->rating = 300;
+
+ clockevents_config_and_register(ced, timer_of_rate(to), 1, OTTO_TC_MAX_PERIOD);
+}
+
+static int __init otto_tc_init(struct device_node *node)
+{
+ struct otto_tc_ctrl *ctrl;
+ int err;
+
+ ctrl = kzalloc(sizeof(*ctrl), GFP_KERNEL);
+ if (!ctrl)
+ return -ENOMEM;
+
+ spin_lock_init(&ctrl->lock);
+
+ ctrl->to.flags = TIMER_OF_BASE | TIMER_OF_IRQ | TIMER_OF_CLOCK;
+ ctrl->to.of_clk.name = "bus";
+ ctrl->to.of_irq.flags = IRQF_TIMER;
+ ctrl->to.of_irq.handler = otto_tc_handler;
+
+ err = timer_of_init(node, &ctrl->to);
+ if (err)
+ goto err_out;
+
+ /* Reset timer state */
+ writel(0, OTTO_TC_REG_CTL(&ctrl->to));
+ writel(0, OTTO_TC_REG_DATA(&ctrl->to));
+
+ /* TODO Replace by a real derived clock */
+ otto_tc_set_divisor(ctrl, OTTO_TC_MIN_DIVISOR);
+ ctrl->to.of_clk.rate /= OTTO_TC_MIN_DIVISOR;
+ ctrl->to.of_clk.period /= OTTO_TC_MIN_DIVISOR;
+
+ otto_tc_irq_enable_clear(&ctrl->to);
+ otto_tc_init_clkevt(&ctrl->to);
+
+ return 0;
+
+err_out:
+ kfree(ctrl);
+
+ return err;
+}
+TIMER_OF_DECLARE(otto_tc, "realtek,otto-tc", otto_tc_init);
--
2.34.1