clocksource: add starfive hw-timer driver
authorSamin Guo <samin.guo@starfivetech.com>
Wed, 1 Sep 2021 09:42:14 +0000 (17:42 +0800)
committersamin <samin.guo@starfivetech.com>
Fri, 26 Nov 2021 03:09:14 +0000 (11:09 +0800)
This driver applies to JH7100|JH7110

Signed-off-by: samin <samin.guo@starfivetech.com>
arch/riscv/configs/starfive_jh7110_defconfig
drivers/clocksource/Kconfig
drivers/clocksource/Makefile
drivers/clocksource/timer-starfive.c [new file with mode: 0644]
drivers/clocksource/timer-starfive.h [new file with mode: 0644]

index 5cd5959..90c3450 100644 (file)
@@ -187,6 +187,7 @@ CONFIG_SYNC_FILE=y
 # CONFIG_VIRTIO_MENU is not set
 # CONFIG_VHOST_MENU is not set
 CONFIG_GOLDFISH=y
+CONFIG_STARFIVE_TIMER=y
 # CONFIG_IOMMU_SUPPORT is not set
 CONFIG_RPMSG_CHAR=y
 CONFIG_RPMSG_VIRTIO=y
index 0f5e398..34af7e0 100644 (file)
@@ -632,6 +632,17 @@ config RISCV_TIMER
          is accessed via both the SBI and the rdcycle instruction.  This is
          required for all RISC-V systems.
 
+config STARFIVE_TIMER
+        bool "Timer for the STARFIVE SOCS"
+        depends on RISCV && OF
+        select TIMER_OF
+        select CLKSRC_MMIO
+        help
+          This enables the starfive timers for jh7xx socs. On RISC-V platform,
+          the system has started RISCV_TIMER. But you can also use these timers
+          to do a lot more on StarFive JH71xx soc. These timers can provide much
+          higher precision than RISCV_TIMER.
+
 config CLINT_TIMER
        bool "CLINT Timer for the RISC-V platform" if COMPILE_TEST
        depends on GENERIC_SCHED_CLOCK && RISCV
index c17ee32..8e4d3cf 100644 (file)
@@ -88,3 +88,4 @@ obj-$(CONFIG_CSKY_MP_TIMER)           += timer-mp-csky.o
 obj-$(CONFIG_GX6605S_TIMER)            += timer-gx6605s.o
 obj-$(CONFIG_HYPERV_TIMER)             += hyperv_timer.o
 obj-$(CONFIG_MICROCHIP_PIT64B)         += timer-microchip-pit64b.o
+obj-$(CONFIG_STARFIVE_TIMER)           += timer-starfive.o
diff --git a/drivers/clocksource/timer-starfive.c b/drivers/clocksource/timer-starfive.c
new file mode 100644 (file)
index 0000000..1f507b2
--- /dev/null
@@ -0,0 +1,410 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2021 StarFive, Inc <samin.guo@starfivetech.com>
+ *
+ * THE PRESENT SOFTWARE WHICH IS FOR GUIDANCE ONLY AIMS AT PROVIDING
+ * CUSTOMERS WITH CODING INFORMATION REGARDING THEIR PRODUCTS IN ORDER
+ * FOR THEM TO SAVE TIME. AS A RESULT, STARFIVE SHALL NOT BE HELD LIABLE
+ * FOR ANY DIRECT, INDIRECT OR CONSEQUENTIAL DAMAGES WITH RESPECT TO ANY
+ * CLAIMS ARISING FROM THE CONTENT OF SUCH SOFTWARE AND/OR THE USE MADE
+ * BY CUSTOMERS OF THE CODING INFORMATION CONTAINED HEREIN IN CONNECTION
+ * WITH THEIR PRODUCTS.
+ */
+
+#include <linux/clk.h>
+#include <linux/clocksource.h>
+#include <linux/clockchips.h>
+#include <linux/err.h>
+#include <linux/kernel.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_clk.h>
+#include <linux/of_irq.h>
+#include <linux/sched_clock.h>
+
+#include "timer-starfive.h"
+
+struct starfive_timer __initdata jh7100_starfive_timer = {
+       .ctrl           = STF_TIMER_CTL,
+       .load           = STF_TIMER_LOAD,
+       .enable         = STF_TIMER_ENABLE,
+       .reload         = STF_TIMER_RELOAD,
+       .value          = STF_TIMER_VALUE,
+       .intclr         = STF_TIMER_INT_CLR,
+       .intmask        = STF_TIMER_INT_MASK,
+       .timer_base     = {TIMER_BASE(0), TIMER_BASE(1), TIMER_BASE(2), TIMER_BASE(3),
+                       TIMER_BASE(4), TIMER_BASE(5), TIMER_BASE(6), TIMER_BASE(7)},
+};
+
+static struct starfive_clkevt starfive_jh7100_clkevt[NR_TIMERS];
+
+static inline struct starfive_clkevt *
+to_starfive_clkevt(struct clock_event_device *evt)
+{
+       return container_of(evt, struct starfive_clkevt, evt);
+}
+
+static inline void timer_set_mod(struct starfive_clkevt *clkevt, int mod)
+{
+       writel(mod, clkevt->ctrl);
+}
+
+/*
+ * After disable timer, then enable, the timer will start
+ * from the reload count value(0x08[31:0]).
+ */
+static inline void timer_int_enable(struct starfive_clkevt *clkevt)
+{
+       writel(INTMASK_ENABLE_DIS, clkevt->intmask);
+}
+
+static inline void timer_int_disable(struct starfive_clkevt *clkevt)
+{
+       writel(INTMASK_ENABLE, clkevt->intmask);
+}
+
+static inline void timer_int_clear(struct starfive_clkevt *clkevt)
+{
+       /* waiting interrupt can be to clearing */
+       do {
+
+       } while (readl(clkevt->intclr) & INT_STATUS_CLR_AVA);
+
+       writel(1, clkevt->intclr);
+}
+
+/*
+ * The initial value to be loaded into the
+ * counter and is also used as the reload value.
+ */
+static inline void timer_set_val(struct starfive_clkevt *clkevt, u32 val)
+{
+       writel(val, clkevt->load);
+}
+
+static inline u32 timer_get_val(struct starfive_clkevt *clkevt)
+{
+       return readl(clkevt->value);
+}
+
+/*
+ * Write RELOAD register to reload preset value to counter.
+ * (Write 0 and write 1 are both ok)
+ */
+static inline void
+timer_set_reload(struct starfive_clkevt *clkevt)
+{
+       writel(1, clkevt->reload);
+}
+
+static inline void timer_enable(struct starfive_clkevt *clkevt)
+{
+       writel(TIMER_ENA, clkevt->enable);
+}
+
+static inline void timer_disable(struct starfive_clkevt *clkevt)
+{
+       writel(TIMER_ENA_DIS, clkevt->enable);
+}
+
+static void timer_shutdown(struct starfive_clkevt *clkevt)
+{
+       timer_int_disable(clkevt);
+       timer_disable(clkevt);
+       timer_int_clear(clkevt);
+}
+
+static void starfive_timer_suspend(struct clock_event_device *evt)
+{
+       struct starfive_clkevt *clkevt;
+
+       clkevt = to_starfive_clkevt(evt);
+
+       clkevt->reload_val = timer_get_val(clkevt);
+
+       timer_disable(clkevt);
+       timer_int_disable(clkevt);
+       timer_int_clear(clkevt);
+}
+
+static void starfive_timer_resume(struct clock_event_device *evt)
+{
+       struct starfive_clkevt *clkevt;
+
+       clkevt = to_starfive_clkevt(evt);
+       timer_set_val(clkevt, clkevt->reload_val);
+       timer_set_reload(clkevt);
+       timer_int_enable(clkevt);
+       timer_enable(clkevt);
+}
+
+static int starfive_timer_tick_resume(struct clock_event_device *evt)
+{
+       starfive_timer_resume(evt);
+
+       return 0;
+}
+
+static int starfive_timer_shutdown(struct clock_event_device *evt)
+{
+       struct starfive_clkevt *clkevt;
+
+       clkevt = to_starfive_clkevt(evt);
+       timer_shutdown(clkevt);
+
+       return 0;
+}
+
+static int
+starfive_get_clock_rate(struct starfive_clkevt *clkevt, struct device_node *np)
+{
+       int ret;
+       u32 rate;
+
+       if (clkevt->clk) {
+               clkevt->rate = clk_get_rate(clkevt->clk);
+               if (clkevt->rate > 0) {
+                       pr_debug("clk_get_rate clkevt->rate: %lld\n", clkevt->rate);
+                       return 0;
+               }
+       }
+
+       /* Next we try to get clock-frequency from dts.*/
+       ret = of_property_read_u32(np, "clock-frequency", &rate);
+       if (!ret) {
+               pr_debug("Timer: try get clock-frequency:%d MHz\n", rate);
+               clkevt->rate = (u64)rate;
+               return 0;
+       } else {
+               pr_err("Timer: get rate failed, need clock-frequency define in dts.\n");
+       }
+
+       return -ENOENT;
+}
+
+static int starfive_clocksource_init(struct starfive_clkevt *clkevt,
+                               const char *name, struct device_node *np)
+{
+       timer_set_mod(clkevt, MOD_CONTIN);
+       timer_set_val(clkevt, 0xffffffff);  /* val = rate --> 1s */
+       timer_int_disable(clkevt);
+       timer_int_clear(clkevt);
+       timer_int_enable(clkevt);
+       timer_enable(clkevt);
+
+       clocksource_mmio_init(clkevt->value, name,
+               clkevt->rate, 200, 32, clocksource_mmio_readl_down);
+
+       return 0;
+}
+
+/*
+ * IRQ handler for the timer
+ */
+static irqreturn_t starfive_timer_interrupt(int irq, void *priv)
+{
+       struct clock_event_device *evt = (struct clock_event_device  *)priv;
+       struct starfive_clkevt *clkevt = to_starfive_clkevt(evt);
+
+       timer_int_clear(clkevt);
+
+       if (evt->event_handler)
+               evt->event_handler(evt);
+
+       return IRQ_HANDLED;
+}
+
+static int starfive_timer_set_periodic(struct clock_event_device *evt)
+{
+       struct starfive_clkevt *clkevt;
+
+       clkevt = to_starfive_clkevt(evt);
+
+       timer_disable(clkevt);
+       timer_set_mod(clkevt, MOD_CONTIN);
+       timer_set_val(clkevt, clkevt->periodic);
+       timer_int_disable(clkevt);
+       timer_int_clear(clkevt);
+       timer_int_enable(clkevt);
+       timer_enable(clkevt);
+
+       return 0;
+}
+
+static int starfive_timer_set_oneshot(struct clock_event_device *evt)
+{
+       struct starfive_clkevt *clkevt;
+
+       clkevt = to_starfive_clkevt(evt);
+
+       timer_disable(clkevt);
+       timer_set_mod(clkevt, MOD_SINGLE);
+       timer_set_val(clkevt, 0xffffffff);
+       timer_int_disable(clkevt);
+       timer_int_clear(clkevt);
+       timer_int_enable(clkevt);
+       timer_enable(clkevt);
+
+       return 0;
+}
+
+static int starfive_timer_set_next_event(unsigned long next,
+                                       struct clock_event_device *evt)
+{
+       struct starfive_clkevt *clkevt;
+
+       clkevt = to_starfive_clkevt(evt);
+
+       timer_disable(clkevt);
+       timer_set_mod(clkevt, MOD_SINGLE);
+       timer_set_val(clkevt, next);
+       timer_enable(clkevt);
+
+       return 0;
+}
+
+static void starfive_set_clockevent(struct clock_event_device *evt)
+{
+       evt->features   = CLOCK_EVT_FEAT_PERIODIC |
+                       CLOCK_EVT_FEAT_ONESHOT |
+                       CLOCK_EVT_FEAT_DYNIRQ;
+       evt->set_state_shutdown = starfive_timer_shutdown;
+       evt->set_state_periodic = starfive_timer_set_periodic;
+       evt->set_state_oneshot  = starfive_timer_set_oneshot;
+       evt->set_state_oneshot_stopped = starfive_timer_shutdown;
+       evt->tick_resume        = starfive_timer_tick_resume;
+       evt->set_next_event     = starfive_timer_set_next_event;
+       evt->suspend            = starfive_timer_suspend;
+       evt->resume             = starfive_timer_resume;
+       evt->rating             = 300;
+}
+
+static int starfive_clockevents_register(struct starfive_clkevt *clkevt, unsigned int irq,
+                               struct device_node *np, const char *name)
+{
+       int ret = 0;
+
+       ret = starfive_get_clock_rate(clkevt, np);
+       if (ret)
+               return -EINVAL;
+
+       clkevt->periodic = DIV_ROUND_CLOSEST(clkevt->rate, HZ);
+
+       starfive_set_clockevent(&clkevt->evt);
+       clkevt->evt.name = name;
+       clkevt->evt.irq = irq;
+       clkevt->evt.cpumask = cpu_possible_mask;
+
+       ret = request_irq(irq, starfive_timer_interrupt,
+                       IRQF_TIMER | IRQF_IRQPOLL, name, &clkevt->evt);
+       if (ret)
+               pr_err("%s: request_irq failed\n", name);
+
+       clockevents_config_and_register(&clkevt->evt, clkevt->rate, 0xf, 0xffffffff);
+
+       return ret;
+}
+
+static void __init starfive_clkevt_init(struct starfive_timer *timer,
+                                       void __iomem *base)
+{
+       struct starfive_clkevt *clkevt;
+       void __iomem *timer_base;
+       int i;
+
+       for (i = 0; i < NR_TIMERS; i++) {
+               timer_base = base + timer->timer_base[i];
+               clkevt = &starfive_jh7100_clkevt[i];
+               clkevt->base    = timer_base;
+               clkevt->ctrl    = timer_base + timer->ctrl;
+               clkevt->load    = timer_base + timer->load;
+               clkevt->enable  = timer_base + timer->enable;
+               clkevt->reload  = timer_base + timer->reload;
+               clkevt->value   = timer_base + timer->value;
+               clkevt->intclr  = timer_base + timer->intclr;
+               clkevt->intmask = timer_base + timer->intmask;
+       }
+}
+
+static int __init do_starfive_timer_of_init(struct device_node *np,
+                                       struct starfive_timer *timer)
+{
+       int index, count, irq, ret = -EINVAL;
+       const char *name = NULL;
+       struct clk *clk;
+       struct clk *pclk;
+       struct starfive_clkevt *clkevt;
+       void __iomem *base;
+
+       base = of_iomap(np, 0);
+       if (!base)
+               return -ENXIO;
+
+       if (!of_device_is_available(np)) {
+               ret = -EINVAL;
+               goto err;
+       }
+
+       starfive_clkevt_init(timer, base);
+
+       pclk = of_clk_get_by_name(np, "apb_clk");
+       if (!IS_ERR(pclk))
+               if (clk_prepare_enable(pclk))
+                       pr_warn("pclk for %pOFn is present, but could not be activated\n", np);
+
+       count = of_irq_count(np);
+       if (count > NR_TIMERS || count <= 0) {
+               ret = -EINVAL;
+               goto err;
+       }
+
+       for (index = 0; index < count; index++) {
+               /* one of timer is wdog-timer, skip...*/
+               of_property_read_string_index(np, "clock-names", index, &name);
+               if (strncmp(name, "timer", strlen("timer")))
+                       continue;
+
+               clkevt = &starfive_jh7100_clkevt[index];
+               /* Ensure timers are disabled */
+               timer_disable(clkevt);
+
+               clk = of_clk_get_by_name(np, name);
+               if (!IS_ERR(clk)) {
+                       clkevt->clk = clk;
+                       if (clk_prepare_enable(clk))
+                               pr_warn("clk for %pOFn is present, but could not be activated\n",
+                               np);
+
+               }
+
+               irq = irq_of_parse_and_map(np, index);
+               if (irq < 0) {
+                       ret = -EINVAL;
+                       goto err;
+               }
+
+               ret = starfive_clockevents_register(clkevt, irq, np, name);
+               if (ret) {
+                       pr_err("%s: init clockevents failed.\n", name);
+                       goto err;
+               }
+
+               ret = starfive_clocksource_init(clkevt, name, np);
+               if (ret)
+                       goto err;
+       }
+
+       return 0;
+err:
+       iounmap(base);
+       return ret;
+}
+
+static int __init starfive_timer_of_init(struct device_node *np)
+{
+       return do_starfive_timer_of_init(np, &jh7100_starfive_timer);
+}
+TIMER_OF_DECLARE(starfive_timer, "starfive,si5-timers", starfive_timer_of_init);
diff --git a/drivers/clocksource/timer-starfive.h b/drivers/clocksource/timer-starfive.h
new file mode 100644 (file)
index 0000000..82e375d
--- /dev/null
@@ -0,0 +1,98 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright 2021 StarFive, Inc <samin.guo@starfivetech.com>
+ *
+ * THE PRESENT SOFTWARE WHICH IS FOR GUIDANCE ONLY AIMS AT PROVIDING
+ * CUSTOMERS WITH CODING INFORMATION REGARDING THEIR PRODUCTS IN ORDER
+ * FOR THEM TO SAVE TIME. AS A RESULT, STARFIVE SHALL NOT BE HELD LIABLE
+ * FOR ANY DIRECT, INDIRECT OR CONSEQUENTIAL DAMAGES WITH RESPECT TO ANY
+ * CLAIMS ARISING FROM THE CONTENT OF SUCH SOFTWARE AND/OR THE USE MADE
+ * BY CUSTOMERS OF THE CODING INFORMATION CONTAINED HEREIN IN CONNECTION
+ * WITH THEIR PRODUCTS.
+ */
+#ifndef STARFIVE_TIMER_H
+#define STARFIVE_TIMER_H
+enum STF_TIMERS {
+       TIMER_0 = 0,
+       TIMER_1,
+       TIMER_2,
+       TIMER_3,
+       TIMER_4,  /*WDT*/
+       TIMER_5,
+       TIMER_6,
+       TIMER_7,
+       TIMERS_MAX
+};
+
+#define NR_TIMERS              TIMERS_MAX
+#define PER_TIMER_LEN          0x40
+#define TIMER_BASE(x)          ((TIMER_##x)*PER_TIMER_LEN)
+
+/*
+ * JH7100 timwer TIMER_INT_STATUS:
+ * ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ * |     Bits     | 08~31 | 7 | 6 | 5 |  4  | 3 | 2 | 1 | 0 |
+ * ----------------------------------------------------------
+ * | timer(n)_int |  res  | 6 | 5 | 4 | Wdt | 3 | 2 | 1 | 0 |
+ * ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+ *
+ * Software can read this register to know which interrupt is occurred.
+ */
+#define        STF_TIMER_INT_STATUS    0x00
+#define STF_TIMER_CTL          0x04
+#define STF_TIMER_LOAD         0x08
+#define STF_TIMER_ENABLE       0x10
+#define STF_TIMER_RELOAD       0x14
+#define STF_TIMER_VALUE                0x18
+#define STF_TIMER_INT_CLR      0x20
+#define STF_TIMER_INT_MASK     0x24
+
+enum TIMERI_INTMASK {
+       INTMASK_ENABLE_DIS = 0,
+       INTMASK_ENABLE = 1
+};
+
+enum TIMER_MOD {
+       MOD_CONTIN = 0,
+       MOD_SINGLE = 1
+};
+
+enum TIMER_CTL_EN {
+       TIMER_ENA_DIS   = 0,
+       TIMER_ENA       = 1
+};
+
+#define INT_STATUS_CLR_AVA     BIT(1)
+enum {
+       INT_CLR_AVAILABLE = 0,
+       INT_CLR_NOT_AVAILABLE = 1
+};
+
+struct starfive_timer {
+       u32 ctrl;
+       u32 load;
+       u32 enable;
+       u32 reload;
+       u32 value;
+       u32 intclr;
+       u32 intmask;
+       u32 wdt_lock;   /* 0x3c+i*0x40 watchdog use ONLY */
+       u32 timer_base[NR_TIMERS];
+};
+
+struct starfive_clkevt {
+       struct clock_event_device evt;
+       struct clk *clk;
+       u64 periodic;
+       u64 rate;
+       u32 reload_val;
+       void __iomem *base;
+       void __iomem *ctrl;
+       void __iomem *load;
+       void __iomem *enable;
+       void __iomem *reload;
+       void __iomem *value;
+       void __iomem *intclr;
+       void __iomem *intmask;
+};
+#endif /* STARFIVE_TIMER_H */