armbian_build/patch/kernel/archive/spacemit-6.1/008-drivers-clocksource.patch
2024-07-01 19:15:00 +02:00

939 lines
25 KiB
Diff

From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Patrick Yavitz <pyavitz@armbian.com>
Date: Fri, 21 Jun 2024 11:54:06 -0400
Subject: add spacemit patch set
source: https://gitee.com/bianbu-linux/linux-6.1
Signed-off-by: Patrick Yavitz <pyavitz@armbian.com>
---
drivers/clocksource/Kconfig | 7 +
drivers/clocksource/Makefile | 1 +
drivers/clocksource/dw_apb_timer.c | 14 +-
drivers/clocksource/timer-k1x.c | 703 ++++++++++
4 files changed, 723 insertions(+), 2 deletions(-)
diff --git a/drivers/clocksource/Kconfig b/drivers/clocksource/Kconfig
index 111111111111..222222222222 100644
--- a/drivers/clocksource/Kconfig
+++ b/drivers/clocksource/Kconfig
@@ -134,6 +134,13 @@ config RDA_TIMER
help
Enables the support for the RDA Micro timer driver.
+config SPACEMIT_K1X_TIMER
+ bool "Spacemit k1x timer driver" if COMPILE_TEST
+ select CLKSRC_MMIO
+ select TIMER_OF
+ help
+ Enables the support for the spacemit k1x timer driver.
+
config SUN4I_TIMER
bool "Sun4i timer driver" if COMPILE_TEST
depends on HAS_IOMEM
diff --git a/drivers/clocksource/Makefile b/drivers/clocksource/Makefile
index 111111111111..222222222222 100644
--- a/drivers/clocksource/Makefile
+++ b/drivers/clocksource/Makefile
@@ -59,6 +59,7 @@ obj-$(CONFIG_MILBEAUT_TIMER) += timer-milbeaut.o
obj-$(CONFIG_SPRD_TIMER) += timer-sprd.o
obj-$(CONFIG_NPCM7XX_TIMER) += timer-npcm7xx.o
obj-$(CONFIG_RDA_TIMER) += timer-rda.o
+obj-$(CONFIG_SPACEMIT_K1X_TIMER) += timer-k1x.o
obj-$(CONFIG_ARC_TIMERS) += arc_timer.o
obj-$(CONFIG_ARM_ARCH_TIMER) += arm_arch_timer.o
diff --git a/drivers/clocksource/dw_apb_timer.c b/drivers/clocksource/dw_apb_timer.c
index 111111111111..222222222222 100644
--- a/drivers/clocksource/dw_apb_timer.c
+++ b/drivers/clocksource/dw_apb_timer.c
@@ -34,6 +34,8 @@
#define APBTMR_CONTROL_MODE_PERIODIC (1 << 1)
#define APBTMR_CONTROL_INT (1 << 2)
+static raw_spinlock_t dw_apb_timer_lock;
+
static inline struct dw_apb_clock_event_device *
ced_to_dw_apb_ced(struct clock_event_device *evt)
{
@@ -102,8 +104,10 @@ static irqreturn_t dw_apb_clockevent_irq(int irq, void *data)
return IRQ_NONE;
}
+ raw_spin_lock(&dw_ced->timer_lock);
if (dw_ced->eoi)
dw_ced->eoi(&dw_ced->timer);
+ raw_spin_unlock(&dw_ced->timer_lock);
evt->event_handler(evt);
return IRQ_HANDLED;
@@ -207,15 +211,20 @@ static int apbt_next_event(unsigned long delta,
u32 ctrl;
struct dw_apb_clock_event_device *dw_ced = ced_to_dw_apb_ced(evt);
+ raw_spin_lock(&dw_ced->timer_lock);
+
/* Disable timer */
ctrl = apbt_readl_relaxed(&dw_ced->timer, APBTMR_N_CONTROL);
ctrl &= ~APBTMR_CONTROL_ENABLE;
apbt_writel_relaxed(&dw_ced->timer, ctrl, APBTMR_N_CONTROL);
+
/* write new count */
apbt_writel_relaxed(&dw_ced->timer, delta, APBTMR_N_LOAD_COUNT);
ctrl |= APBTMR_CONTROL_ENABLE;
apbt_writel_relaxed(&dw_ced->timer, ctrl, APBTMR_N_CONTROL);
+ raw_spin_unlock(&dw_ced->timer_lock);
+
return 0;
}
@@ -248,6 +257,8 @@ dw_apb_clockevent_init(int cpu, const char *name, unsigned rating,
if (!dw_ced)
return NULL;
+ raw_spin_lock_init(&dw_ced->timer_lock);
+
dw_ced->timer.base = base;
dw_ced->timer.irq = irq;
dw_ced->timer.freq = freq;
@@ -272,8 +283,7 @@ dw_apb_clockevent_init(int cpu, const char *name, unsigned rating,
dw_ced->ced.name = name;
dw_ced->eoi = apbt_eoi;
- err = request_irq(irq, dw_apb_clockevent_irq,
- IRQF_TIMER | IRQF_IRQPOLL | IRQF_NOBALANCING,
+ err = request_irq(irq, dw_apb_clockevent_irq, IRQF_ONESHOT,
dw_ced->ced.name, &dw_ced->ced);
if (err) {
pr_err("failed to request timer irq\n");
diff --git a/drivers/clocksource/timer-k1x.c b/drivers/clocksource/timer-k1x.c
new file mode 100644
index 000000000000..111111111111
--- /dev/null
+++ b/drivers/clocksource/timer-k1x.c
@@ -0,0 +1,703 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * spacemit-k1x timer driver
+ *
+ * Copyright (C) 2023 Spacemit
+ */
+
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/interrupt.h>
+#include <linux/clockchips.h>
+
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_irq.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/cpu.h>
+#include <linux/clockchips.h>
+#include <linux/sched_clock.h>
+#include <linux/stat.h>
+#include <linux/clk.h>
+#include <linux/reset.h>
+
+#define TMR_CCR (0x000c)
+#define TMR_TN_MM(n, m) (0x0010 + ((n) << 4) + ((m) << 2))
+#define TMR_CR(n) (0x0090 + ((n) << 2))
+#define TMR_SR(n) (0x0080 + ((n) << 2))
+#define TMR_IER(n) (0x0060 + ((n) << 2))
+#define TMR_PLVR(n) (0x0040 + ((n) << 2))
+#define TMR_PLCR(n) (0x0050 + ((n) << 2))
+#define TMR_WMER (0x0068)
+#define TMR_WMR (0x006c)
+#define TMR_WVR (0x00cc)
+#define TMR_WSR (0x00c0)
+#define TMR_ICR(n) (0x0070 + ((n) << 2))
+#define TMR_WICR (0x00c4)
+#define TMR_CER (0x0000)
+#define TMR_CMR (0x0004)
+#define TMR_WCR (0x00c8)
+#define TMR_WFAR (0x00b0)
+#define TMR_WSAR (0x00b4)
+#define TMR_CRSR (0x0008)
+
+#define TMR_CCR_CS_0(x) (((x) & 0x3) << 0)
+#define TMR_CCR_CS_1(x) (((x) & 0x3) << 2)
+#define TMR_CCR_CS_2(x) (((x) & 0x3) << 5)
+
+#define MAX_EVT_NUM 5
+
+#define MAX_DELTA (0xfffffffe)
+#define MIN_DELTA (5)
+
+#define SPACEMIT_MAX_COUNTER 3
+#define SPACEMIT_MAX_TIMER 3
+
+#define TMR_CER_COUNTER(cid) (1 << (cid))
+#define SPACEMIT_ALL_COUNTERS ((1 << SPACEMIT_MAX_COUNTER) - 1)
+
+#define SPACEMIT_TIMER_CLOCK_32KHZ 32768
+
+#define SPACEMIT_TIMER_COUNTER_CLKSRC (1 << 0)
+#define SPACEMIT_TIMER_COUNTER_CLKEVT (1 << 1)
+#define SPACEMIT_TIMER_COUNTER_DELAY (1 << 2)
+
+#define SPACEMIT_TIMER_ALL_CPU (0xFFFFFFFF)
+
+struct spacemit_timer;
+
+struct spacemit_timer_evt {
+ struct clock_event_device ced;
+ struct irqaction irqa;
+ unsigned int freq;
+ unsigned int irq;
+ unsigned int cid;
+ unsigned int tid;
+ int cpu;
+ bool timer_enabled;
+ /* 0: timer set; 1: timer timeout(irq comes) */
+ int timer_status;
+ unsigned int timeout;
+ struct spacemit_timer *timer;
+};
+
+struct spacemit_timer {
+ unsigned int id;
+ void __iomem *base;
+ struct spacemit_timer_evt evt[SPACEMIT_MAX_COUNTER];
+ unsigned int flag;
+ int loop_delay_fastclk;
+ unsigned int fc_freq;
+ unsigned int freq;
+ struct clk *clk;
+ /* lock to protect hw operation. */
+ spinlock_t tm_lock;
+};
+
+struct timer_werror_info {
+ u32 reg;
+ u32 target;
+ u32 val;
+ u32 mask;
+};
+
+/* record the last x write failures */
+#define TIMER_ERR_NUM 10
+static struct timer_werror_info werr_info[TIMER_ERR_NUM];
+static int werr_index;
+
+static struct spacemit_timer *spacemit_timers[SPACEMIT_MAX_TIMER];
+static int timer_counter_switch_clock(struct spacemit_timer *tm, unsigned int freq);
+
+void timer_dump_hwinfo(int tid)
+{
+ struct spacemit_timer_evt *t_evt = &spacemit_timers[tid]->evt[0];
+ void __iomem *base = spacemit_timers[tid]->base;
+ unsigned int sr, cid, cer, cmr, ccr, mr, ier, cr;
+
+ cid = t_evt->cid;
+
+ cer = __raw_readl(base + TMR_CER);
+ cmr = __raw_readl(base + TMR_CMR);
+ ccr = __raw_readl(base + TMR_CCR);
+ mr = __raw_readl(base + TMR_TN_MM(cid, 0));
+ ier = __raw_readl(base + TMR_IER(cid));
+ sr = __raw_readl(base + TMR_SR(cid));
+ cr = __raw_readl(base + TMR_CR(cid));
+
+ pr_err("timer enable: %d. timeout: %d cycles. next event: %lld\n", !t_evt->timer_status, t_evt->timeout, t_evt->ced.next_event);
+
+ pr_err("cer/cmr/ccr/mr/ier/sr/cr: (0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x)\n", cer, cmr, ccr, mr, ier, sr, cr);
+
+ return;
+}
+
+static void timer_write_error(u32 reg, u32 target, u32 val, u32 mask)
+{
+ werr_info[werr_index].reg = reg;
+ werr_info[werr_index].target = target;
+ werr_info[werr_index].val = val;
+ werr_info[werr_index].mask = mask;
+ werr_index = (werr_index+1) % TIMER_ERR_NUM;
+
+ pr_err("timer write fail: register = 0x%x: (0x%x, 0x%x, 0x%x)\n", reg, target, val, mask);
+}
+
+static void timer_write_check(struct spacemit_timer *tm, u32 reg, u32 val, u32 mask, bool clr, bool clk_switch)
+{
+ int loop = 3, retry = 100;
+ u32 t_read, t_check = clr ? !val : val;
+
+reg_re_write:
+ __raw_writel(val, tm->base + reg);
+
+ if (clk_switch)
+ timer_counter_switch_clock(tm, tm->fc_freq);
+
+ t_read = __raw_readl(tm->base + reg);
+
+ while (((t_read & mask) != (t_check & mask)) && loop) {
+ /* avoid trying frequently to worsen bus contention */
+ udelay(30);
+ t_read = __raw_readl(tm->base + reg);
+ loop--;
+
+ if (!loop) {
+ timer_write_error(reg, t_check, t_read, mask);
+ loop = 3;
+ if (--retry)
+ goto reg_re_write;
+ else
+ return;
+ }
+ }
+}
+
+static int timer_counter_switch_clock(struct spacemit_timer *tm, unsigned int freq)
+{
+ u32 ccr, val, mask, tid;
+
+ tid = tm->id;
+
+ ccr = __raw_readl(tm->base + TMR_CCR);
+
+ switch (tid) {
+ case 0:
+ mask = TMR_CCR_CS_0(3);
+ break;
+ case 1:
+ mask = TMR_CCR_CS_1(3);
+ break;
+ case 2:
+ mask = TMR_CCR_CS_2(3);
+ break;
+ default:
+ pr_err("wrong timer id: 0x%x\n", tid);
+ return -EINVAL;
+ }
+
+ ccr &= ~mask;
+
+ if (freq == tm->fc_freq)
+ val = 0;
+ else if (freq == SPACEMIT_TIMER_CLOCK_32KHZ)
+ val = 1;
+ else {
+ pr_err("Timer %d: invalid clock rate %d\n", tid, freq);
+ return -EINVAL;
+ }
+
+ switch (tid) {
+ case 0:
+ ccr |= TMR_CCR_CS_0(val);
+ break;
+ case 1:
+ ccr |= TMR_CCR_CS_1(val);
+ break;
+ case 2:
+ ccr |= TMR_CCR_CS_2(val);
+ break;
+ }
+
+ timer_write_check(tm, TMR_CCR, ccr, mask, false, false);
+
+ return 0;
+}
+
+static void timer_counter_disable(struct spacemit_timer_evt *evt)
+{
+ struct spacemit_timer *tm = evt->timer;
+ u32 cer;
+ bool clk_switch = false;
+
+ if (evt->freq != tm->fc_freq)
+ clk_switch = true;
+ /*
+ * Stop the counter will need multiple timer clock to take effect.
+ * Some operations can only be done when counter is disabled. So
+ * add delay here.
+ */
+ /* Step1: disable counter */
+ cer = __raw_readl(tm->base + TMR_CER);
+ timer_write_check(tm, TMR_CER, (cer & ~(1 << evt->cid)), (1 << evt->cid), false, clk_switch);
+
+ /* remove unnecesary write, check explicitly: 2 cycles (32k) */
+
+ evt->timer_status = 1;
+}
+
+static void timer_counter_enable(struct spacemit_timer_evt *evt)
+{
+ struct spacemit_timer *tm = evt->timer;
+ u32 cer;
+
+ /* Switch to original clock */
+ if (evt->freq != tm->fc_freq)
+ timer_counter_switch_clock(tm, evt->freq);
+
+ /* Enable timer */
+ cer = __raw_readl(tm->base + TMR_CER);
+
+ timer_write_check(tm, TMR_CER, (cer | (1 << evt->cid)), (1 << evt->cid), false, false);
+
+ evt->timer_status = 0;
+}
+
+static irqreturn_t timer_interrupt(int irq, void *dev_id)
+{
+ struct clock_event_device *c = dev_id;
+ struct spacemit_timer_evt *evt;
+ unsigned int cnt;
+ unsigned long flags;
+ void __iomem *base;
+
+ evt = container_of(c, struct spacemit_timer_evt, ced);
+ cnt = evt->cid;
+ base = evt->timer->base;
+
+ spin_lock_irqsave(&(evt->timer->tm_lock), flags);
+ /* We only use match #0 for the counter. */
+ if (__raw_readl(base + TMR_SR(cnt)) & 0x1) {
+ timer_counter_disable(evt);
+
+ /* Disable the interrupt. */
+ timer_write_check(evt->timer, TMR_IER(cnt), 0, 0x7, false, false);
+ /* Clear interrupt status */
+ timer_write_check(evt->timer, TMR_ICR(cnt), 0x1, 0x7, true, false);
+
+ spin_unlock_irqrestore(&(evt->timer->tm_lock), flags);
+
+ c->event_handler(c);
+
+ return IRQ_HANDLED;
+ }
+
+ spin_unlock_irqrestore(&(evt->timer->tm_lock), flags);
+ return IRQ_NONE;
+}
+
+static int timer_shutdown(struct clock_event_device *dev)
+{
+ struct spacemit_timer_evt *evt;
+ unsigned long flags;
+
+ evt = container_of(dev, struct spacemit_timer_evt, ced);
+
+ spin_lock_irqsave(&(evt->timer->tm_lock), flags);
+
+ evt->timer_enabled = !evt->timer_status;
+
+ /* disable counter */
+ timer_counter_disable(evt);
+
+ spin_unlock_irqrestore(&(evt->timer->tm_lock), flags);
+
+ return 0;
+}
+
+static int timer_resume(struct clock_event_device *dev)
+{
+ struct spacemit_timer_evt *evt;
+ unsigned long flags;
+
+ evt = container_of(dev, struct spacemit_timer_evt, ced);
+
+ spin_lock_irqsave(&(evt->timer->tm_lock), flags);
+
+ /* check whether need to enable timer */
+ if (evt->timer_enabled)
+ timer_counter_enable(evt);
+
+ spin_unlock_irqrestore(&(evt->timer->tm_lock), flags);
+
+ return 0;
+}
+
+static int timer_set_next_event(unsigned long delta,
+ struct clock_event_device *dev)
+{
+ struct spacemit_timer_evt *evt;
+ unsigned int cid;
+ unsigned long flags;
+ u32 cer;
+ void __iomem *base;
+
+ evt = container_of(dev, struct spacemit_timer_evt, ced);
+ cid = evt->cid;
+ base = evt->timer->base;
+
+ spin_lock_irqsave(&(evt->timer->tm_lock), flags);
+
+ cer = __raw_readl(base + TMR_CER);
+
+ /* If the timer counter is enabled, first disable it. */
+ if (cer & (1 << cid))
+ timer_counter_disable(evt);
+
+ /* Setup new counter value */
+ timer_write_check(evt->timer, TMR_TN_MM(cid, 0), (delta - 1), (u32)(-1), false, false);
+
+ /* enable the matching interrupt */
+ timer_write_check(evt->timer, TMR_IER(cid), 0x1, 0x1, false, false);
+
+ timer_counter_enable(evt);
+
+ evt->timeout = delta - 1;
+
+ spin_unlock_irqrestore(&(evt->timer->tm_lock), flags);
+ return 0;
+}
+
+int __init spacemit_timer_init(struct device_node *np, int tid, void __iomem *base,
+ unsigned int flag, unsigned int fc_freq,
+ unsigned int apb_freq, unsigned int freq)
+{
+ struct spacemit_timer *tm = spacemit_timers[tid];
+ struct clk *clk;
+ struct reset_control *resets;
+ u32 tmp, delay;
+
+ if (tm)
+ return -EINVAL;
+
+ tm = kzalloc(sizeof(*tm), GFP_KERNEL);
+ if (!tm)
+ return -ENOMEM;
+
+ clk = of_clk_get(np, 0);
+ if (!clk) {
+ pr_err("%s: get clk failed! %s\n", __func__, np->name);
+ goto out;
+ }
+
+ if (IS_ERR(clk)) {
+ pr_err("Timer %d: fail to get clock!\n", tid);
+ goto out;
+ }
+
+ if (clk_prepare_enable(clk)) {
+ pr_err("Timer %d: fail to enable clock!\n", tid);
+ goto out;
+ }
+
+ if (clk_set_rate(clk, fc_freq)) {
+ pr_err("Timer %d: fail to set clock rate to %uHz!\n", tid, fc_freq);
+ goto out;
+ }
+
+ resets = of_reset_control_get(np, 0);
+ if(IS_ERR(resets)) {
+ clk_disable_unprepare(clk);
+ return PTR_ERR(resets);
+ }
+ reset_control_deassert(resets);
+ /*
+ * The calculation formula for the loop cycle is:
+ *
+ * (1) need wait for 2 timer's clock cycle:
+ * 1 2
+ * ------- x 2 = -------
+ * fc_freq fc_freq
+ *
+ * (2) convert to apb clock cycle:
+ * 2 1 apb_freq * 2
+ * ------- / -------- = ----------------
+ * fc_freq apb_freq fc_freq
+ *
+ * (3) every apb register's accessing will take 8 apb clock cycle,
+ * also consider add extral one more time for safe way;
+ * so finally need loop times for the apb register accessing:
+ *
+ * (apb_freq * 2)
+ * ------------------ / 8 + 1
+ * fc_freq
+ */
+ delay = ((apb_freq * 2) / fc_freq / 8) + 1;
+ pr_err("Timer %d: loop_delay_fastclk is %d\n", tid, delay);
+
+ tm->id = tid;
+ tm->base = base;
+ tm->flag = flag;
+ tm->loop_delay_fastclk = delay;
+ tm->fc_freq = fc_freq;
+ tm->freq = freq;
+ spin_lock_init(&(tm->tm_lock));
+
+ spacemit_timers[tid] = tm;
+
+ /* We will disable all counters. Switch to fastclk first. */
+ timer_counter_switch_clock(tm, fc_freq);
+
+ /* disalbe all counters */
+ tmp = __raw_readl(base + TMR_CER) & ~SPACEMIT_ALL_COUNTERS;
+ __raw_writel(tmp, base + TMR_CER);
+
+ /* disable matching interrupt */
+ __raw_writel(0x00, base + TMR_IER(0));
+ __raw_writel(0x00, base + TMR_IER(1));
+ __raw_writel(0x00, base + TMR_IER(2));
+
+ while (delay--) {
+ /* Clear pending interrupt status */
+ __raw_writel(0x1, base + TMR_ICR(0));
+ __raw_writel(0x1, base + TMR_ICR(1));
+ __raw_writel(0x1, base + TMR_ICR(2));
+ __raw_writel(tmp, base + TMR_CER);
+ }
+
+ return 0;
+out:
+ kfree(tm);
+ return -EINVAL;
+}
+
+static int __init spacemit_timer_hw_init(struct spacemit_timer_evt *evt)
+{
+ struct spacemit_timer *tm = evt->timer;
+ unsigned int tmp, delay, freq, cid, ratio;
+ int ret;
+
+ cid = evt->cid;
+ freq = evt->freq;
+
+ ret = timer_counter_switch_clock(tm, freq);
+ if (ret)
+ return ret;
+
+ ratio = tm->fc_freq / freq;
+ delay = tm->loop_delay_fastclk * ratio;
+
+ /* set timer to free-running mode */
+ tmp = __raw_readl(tm->base + TMR_CMR) | TMR_CER_COUNTER(cid);
+ __raw_writel(tmp, tm->base + TMR_CMR);
+
+ /* free-running */
+ __raw_writel(0x0, tm->base + TMR_PLCR(cid));
+ /* clear status */
+ __raw_writel(0x7, tm->base + TMR_ICR(cid));
+
+ /* enable counter */
+ tmp = __raw_readl(tm->base + TMR_CER) | TMR_CER_COUNTER(cid);
+ __raw_writel(tmp, tm->base + TMR_CER);
+
+ while (delay--)
+ __raw_writel(tmp, tm->base + TMR_CER);
+
+ return 0;
+}
+
+
+int __init spacemit_timer_setup(struct spacemit_timer_evt *evt)
+{
+ int broadcast = 0;
+ int ret;
+
+ if (evt->cpu == SPACEMIT_TIMER_ALL_CPU)
+ broadcast = 1;
+ else if (evt->cpu >= num_possible_cpus())
+ return -EINVAL;
+
+ evt->ced.name = "timer-spacemit";
+ evt->ced.features = CLOCK_EVT_FEAT_ONESHOT;
+ evt->ced.rating = 200;
+ evt->ced.set_next_event = timer_set_next_event;
+ evt->ced.set_state_shutdown = timer_shutdown;
+ evt->ced.tick_resume = timer_resume;
+ evt->ced.irq = evt->irq;
+
+ evt->irqa.flags = IRQF_TIMER | IRQF_IRQPOLL;
+ evt->irqa.handler = timer_interrupt;
+ evt->irqa.dev_id = &(evt->ced);
+
+ ret = spacemit_timer_hw_init(evt);
+ if (ret)
+ return ret;
+
+ if (broadcast) {
+ evt->irqa.name = "broadcast-timer";
+ /* evt->ced.features |= CLOCK_EVT_FEAT_DYNIRQ; */
+ evt->ced.cpumask = cpu_possible_mask;
+ ret = request_irq(evt->ced.irq, timer_interrupt, IRQF_TIMER | IRQF_IRQPOLL | IRQF_ONESHOT, "broadcast-timer", evt->irqa.dev_id);
+ if (ret < 0)
+ return ret;
+ clockevents_config_and_register(&evt->ced,
+ evt->freq, MIN_DELTA, MAX_DELTA);
+ } else {
+ evt->irqa.name = "local-timer";
+ evt->ced.cpumask = cpumask_of(evt->cpu);
+ evt->irqa.flags |= IRQF_PERCPU;
+ ret = request_irq(evt->ced.irq, timer_interrupt, IRQF_TIMER | IRQF_IRQPOLL, "local-timer", evt->irqa.dev_id);
+ if (ret < 0)
+ return ret;
+ /* Enable clock event device for boot CPU. */
+ if (evt->cpu == smp_processor_id()) {
+ clockevents_config_and_register(&evt->ced,
+ evt->freq, MIN_DELTA,
+ MAX_DELTA);
+ /* Only online CPU can be set affinity. */
+ irq_set_affinity_hint(evt->ced.irq, cpumask_of(evt->cpu));
+ } else {
+ /* disable none boot CPU's irq at first */
+ disable_irq(evt->ced.irq);
+ }
+ }
+
+ return 0;
+}
+
+#ifdef CONFIG_OF
+
+const struct of_device_id spacemit_counter_of_id[] = {
+ {
+ .compatible = "spacemit,timer-match",
+ },
+ { },
+};
+
+static int __init spacemit_of_counter_init(struct device_node *np, int tid)
+{
+ int irq, ret;
+ unsigned int cid, cpu;
+ struct spacemit_timer_evt *evt;
+
+ if (!np)
+ return -EINVAL;
+
+ ret = of_property_read_u32(np, "spacemit,timer-counter-id", &cid);
+ if (ret || cid >= SPACEMIT_MAX_TIMER) {
+ pr_err("Timer %d: fail to get counter id 0x%x\n", tid, cid);
+ return ret;
+ }
+
+ if (of_property_read_bool(np, "spacemit,timer-broadcast"))
+ cpu = SPACEMIT_TIMER_ALL_CPU;
+ else {
+ ret = of_property_read_u32(np,
+ "spacemit,timer-counter-cpu",
+ &cpu);
+ if (ret) {
+ pr_err("Timer %d:%d: fail to get cpu\n",
+ tid, cid);
+ return ret;
+ }
+ }
+ irq = irq_of_parse_and_map(np, 0);
+ evt = &spacemit_timers[tid]->evt[cid];
+ evt->timer = spacemit_timers[tid];
+ evt->freq = spacemit_timers[tid]->freq;
+ evt->irq = irq;
+ evt->cpu = cpu;
+ evt->cid = cid;
+ evt->tid = tid;
+ ret = spacemit_timer_setup(evt);
+ if (ret) {
+ pr_err("Timer %d:%d: fail to create clkevt\n",
+ tid, cid);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int __init spacemit_of_timer_init(struct device_node *np)
+{
+ unsigned int flag, tid, fc_freq, apb_freq, freq;
+ void __iomem *base;
+ struct device_node *child_np;
+ const struct of_device_id *match;
+ int ret = 0;
+
+ /* timer initialization */
+ base = of_iomap(np, 0);
+ if (!base) {
+ pr_err("Timer: fail to map register space\n");
+ ret = -EINVAL;
+ goto out;
+ }
+
+ flag = 0;
+
+ /* get timer id */
+ ret = of_property_read_u32(np, "spacemit,timer-id", &tid);
+ if (ret || tid >= SPACEMIT_MAX_TIMER) {
+ pr_err("Timer %d: fail to get timer-id with err %d\n", tid, ret);
+ goto out;
+ }
+
+ /* timer's fast clock and apb frequency */
+ ret = of_property_read_u32(np, "spacemit,timer-fastclk-frequency", &fc_freq);
+ if (ret) {
+ pr_err("Timer %d: fail to get fastclk-frequency with err %d\n",
+ tid, ret);
+ goto out;
+ }
+
+ ret = of_property_read_u32(np, "spacemit,timer-apb-frequency", &apb_freq);
+ if (ret) {
+ pr_err("Timer %d: fail to get apb-frequency with err %d\n",
+ tid, ret);
+ goto out;
+ }
+
+ ret = of_property_read_u32(np, "spacemit,timer-frequency", &freq);
+ if (ret) {
+ pr_err("Timer %d: fail to get timer frequency with err %d\n",
+ tid, ret);
+ goto out;
+ }
+
+ /*
+ * Need use loop for more safe register's accessing,
+ * so at here dynamically calculate the loop time.
+ */
+ if (!fc_freq || !apb_freq) {
+ pr_err("mmp timer's fast clock or apb freq are incorrect!\n");
+ ret = -EINVAL;
+ goto out;
+ }
+
+ ret = spacemit_timer_init(np, tid, base, flag, fc_freq, apb_freq, freq);
+ if (ret)
+ goto out;
+
+ /* counter initialization */
+ for_each_child_of_node(np, child_np) {
+ match = of_match_node(spacemit_counter_of_id, child_np);
+ if (!of_device_is_available(child_np))
+ continue;
+ ret = spacemit_of_counter_init(child_np, tid);
+ if (ret)
+ goto out;
+ }
+ return 0;
+out:
+ if (ret)
+ pr_err("Failed to get timer from dtb with error:%d\n", ret);
+ return ret;
+}
+
+TIMER_OF_DECLARE(spacemit_timer, "spacemit,soc-timer", spacemit_of_timer_init);
+#endif
--
Armbian
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Patrick Yavitz <pyavitz@xxxxx.com>
Date: Sat, 22 Jun 2024 09:59:12 -0400
Subject: drivers: clocksource: timer-riscv.c: fixups
Signed-off-by: Patrick Yavitz <pyavitz@xxxxx.com>
---
drivers/clocksource/timer-riscv.c | 61 +++++++++-
1 file changed, 55 insertions(+), 6 deletions(-)
diff --git a/drivers/clocksource/timer-riscv.c b/drivers/clocksource/timer-riscv.c
index 111111111111..222222222222 100644
--- a/drivers/clocksource/timer-riscv.c
+++ b/drivers/clocksource/timer-riscv.c
@@ -33,8 +33,46 @@ static int riscv_clock_next_event(unsigned long delta,
struct clock_event_device *ce)
{
u64 next_tval = get_cycles64() + delta;
+ csr_set(CSR_IE, IE_TIE);
+
+ if (static_branch_likely(&riscv_sstc_available)) {
+#if defined(CONFIG_32BIT)
+ csr_write(CSR_STIMECMP, next_tval & 0xFFFFFFFF);
+ csr_write(CSR_STIMECMPH, next_tval >> 32);
+#else
+ csr_write(CSR_STIMECMP, next_tval);
+#endif
+ } else
+ sbi_set_timer(next_tval);
+
+ return 0;
+}
+
+static int riscv_set_state_shutdown(struct clock_event_device *ce)
+{
+ u64 next_tval = 0xffffffffffffffff;
+
+ csr_clear(CSR_IE, IE_TIE);
+
+ if (static_branch_likely(&riscv_sstc_available)) {
+#if defined(CONFIG_32BIT)
+ csr_write(CSR_STIMECMP, next_tval & 0xFFFFFFFF);
+ csr_write(CSR_STIMECMPH, next_tval >> 32);
+#else
+ csr_write(CSR_STIMECMP, next_tval);
+#endif
+ } else
+ sbi_set_timer(next_tval);
+
+ return 0;
+}
+
+static int riscv_set_state_oneshot(struct clock_event_device *ce)
+{
+ u64 next_tval = 0xffffffffffffffff;
csr_set(CSR_IE, IE_TIE);
+
if (static_branch_likely(&riscv_sstc_available)) {
#if defined(CONFIG_32BIT)
csr_write(CSR_STIMECMP, next_tval & 0xFFFFFFFF);
@@ -51,9 +89,12 @@ static int riscv_clock_next_event(unsigned long delta,
static unsigned int riscv_clock_event_irq;
static DEFINE_PER_CPU(struct clock_event_device, riscv_clock_event) = {
.name = "riscv_timer_clockevent",
- .features = CLOCK_EVT_FEAT_ONESHOT,
+ .features = CLOCK_EVT_FEAT_ONESHOT | CLOCK_EVT_FEAT_C3STOP,
.rating = 100,
.set_next_event = riscv_clock_next_event,
+ .set_state_shutdown = riscv_set_state_shutdown,
+ .set_state_oneshot_stopped = riscv_set_state_shutdown,
+ .set_state_oneshot = riscv_set_state_oneshot,
};
/*
@@ -158,6 +199,13 @@ static int __init riscv_timer_init_dt(struct device_node *n)
return -ENODEV;
}
+#ifdef CONFIG_SOC_SPACEMIT
+ if (riscv_isa_extension_available(NULL, SSTC)) {
+ pr_info("Timer interrupt in S-mode is available via sstc extension\n");
+ static_branch_enable(&riscv_sstc_available);
+ }
+#endif
+
pr_info("%s: Registering clocksource cpuid [%d] hartid [%lu]\n",
__func__, cpuid, hartid);
error = clocksource_register_hz(&riscv_clocksource, riscv_timebase);
@@ -177,11 +225,6 @@ static int __init riscv_timer_init_dt(struct device_node *n)
return error;
}
- if (riscv_isa_extension_available(NULL, SSTC)) {
- pr_info("Timer interrupt in S-mode is available via sstc extension\n");
- static_branch_enable(&riscv_sstc_available);
- }
-
error = cpuhp_setup_state(CPUHP_AP_RISCV_TIMER_STARTING,
"clockevents/riscv/timer:starting",
riscv_timer_starting_cpu, riscv_timer_dying_cpu);
@@ -189,6 +232,12 @@ static int __init riscv_timer_init_dt(struct device_node *n)
pr_err("cpu hp setup state failed for RISCV timer [%d]\n",
error);
+#ifndef CONFIG_SOC_SPACEMIT
+ if (riscv_isa_extension_available(NULL, SSTC)) {
+ pr_info("Timer interrupt in S-mode is available via sstc extension\n");
+ static_branch_enable(&riscv_sstc_available);
+ }
+#endif
return error;
}
--
Armbian