[PATCH 1/2] clocksource: Add persistent timer support
From: Baolin Wang
Date: Tue Jan 09 2018 - 04:10:17 EST
On some platforms (such as Spreadtrum sc9860 platform), they need one
persistent timer to calculate the suspended time and compensate it
for the OS time.
But now there are no method to register one persistent timer on some
architectures (such as arm64), thus this patch adds one common framework
for timer drivers to register one persistent timer and implements the
read_persistent_clock64() to compensate the OS time.
Signed-off-by: Baolin Wang <baolin.wang@xxxxxxxxxx>
---
drivers/clocksource/Kconfig | 3 +
drivers/clocksource/Makefile | 1 +
drivers/clocksource/persistent-timer.c | 142 ++++++++++++++++++++++++++++++++
drivers/clocksource/persistent-timer.h | 26 ++++++
4 files changed, 172 insertions(+)
create mode 100644 drivers/clocksource/persistent-timer.c
create mode 100644 drivers/clocksource/persistent-timer.h
diff --git a/drivers/clocksource/Kconfig b/drivers/clocksource/Kconfig
index 9a6b087..9cf3d41 100644
--- a/drivers/clocksource/Kconfig
+++ b/drivers/clocksource/Kconfig
@@ -27,6 +27,9 @@ config CLKBLD_I8253
config CLKSRC_MMIO
bool
+config PERSISTENT_TIMER
+ bool
+
config BCM2835_TIMER
bool "BCM2835 timer driver" if COMPILE_TEST
select CLKSRC_MMIO
diff --git a/drivers/clocksource/Makefile b/drivers/clocksource/Makefile
index d6dec44..42556f4 100644
--- a/drivers/clocksource/Makefile
+++ b/drivers/clocksource/Makefile
@@ -76,3 +76,4 @@ obj-$(CONFIG_H8300_TMR16) += h8300_timer16.o
obj-$(CONFIG_H8300_TPU) += h8300_tpu.o
obj-$(CONFIG_CLKSRC_ST_LPC) += clksrc_st_lpc.o
obj-$(CONFIG_X86_NUMACHIP) += numachip.o
+obj-$(CONFIG_PERSISTENT_TIMER) += persistent-timer.o
diff --git a/drivers/clocksource/persistent-timer.c b/drivers/clocksource/persistent-timer.c
new file mode 100644
index 0000000..89a631d
--- /dev/null
+++ b/drivers/clocksource/persistent-timer.c
@@ -0,0 +1,142 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2017 Linaro, Inc.
+ *
+ * Author: Baolin Wang <baolin.wang@xxxxxxxxxx>
+ */
+
+#include <linux/clk.h>
+#include <linux/clocksource.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+
+#include "persistent-timer.h"
+
+static struct persistent_timer *ptimer;
+
+void read_persistent_clock64(struct timespec64 *ts)
+{
+ u64 cycles, delta, nsecs;
+
+ if (!ptimer) {
+ ts->tv_sec = 0;
+ ts->tv_nsec = 0;
+ return;
+ }
+
+ cycles = ptimer->read(ptimer);
+ delta = (cycles - ptimer->last_cycles) & ptimer->mask;
+
+ nsecs = clocksource_cyc2ns(delta, ptimer->mult, ptimer->shift);
+ timespec64_add_ns(&ptimer->ts, nsecs);
+ *ts = ptimer->ts;
+
+ ptimer->last_cycles = cycles;
+}
+
+static __init void persistent_timer_freq_init(struct persistent_timer *p)
+{
+ u64 max_sec = p->mask;
+
+ do_div(max_sec, p->freq);
+
+ /*
+ * Some counter's mask can be larger than 32bit, so we need to limit
+ * the max suspend time to have a good conversion precision. 12 hours
+ * can be enough usually.
+ */
+ if (max_sec > 43200)
+ max_sec = 43200;
+
+ clocks_calc_mult_shift(&p->mult, &p->shift, p->freq,
+ NSEC_PER_SEC, (u32)max_sec);
+}
+
+static __init int persistent_timer_clk_init(struct device_node *np,
+ struct persistent_timer *p)
+{
+ int ret;
+
+ p->clk = of_clk_get(np, 0);
+ if (IS_ERR(p->clk)) {
+ pr_err("Can't get timer clock\n");
+ return PTR_ERR(p->clk);
+ }
+
+ ret = clk_prepare_enable(p->clk);
+ if (ret) {
+ pr_err("Failed to enable clock for %pOF\n", np);
+ clk_put(p->clk);
+ return ret;
+ }
+
+ p->freq = clk_get_rate(p->clk);
+ if (!p->freq) {
+ pr_err("Failed to get clock rate for %pOF\n", np);
+ clk_disable_unprepare(p->clk);
+ clk_put(p->clk);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static __init int persistent_timer_base_init(struct device_node *np,
+ struct persistent_timer *p)
+{
+ p->base = of_io_request_and_map(np, 0, of_node_full_name(np));
+ if (IS_ERR(p->base)) {
+ pr_err("Can't map timer registers\n");
+ return PTR_ERR(p->base);
+ }
+
+ return 0;
+}
+
+int __init persistent_timer_init_and_register(struct device_node *np,
+ struct persistent_timer *p)
+{
+ int ret;
+
+ if (!p->read || !p->mask)
+ return -EINVAL;
+
+ if (p->flags & PERSISTENT_TIMER_BASE) {
+ ret = persistent_timer_base_init(np, p);
+ if (ret)
+ return ret;
+ }
+
+ if (p->flags & PERSISTENT_TIMER_CLOCK) {
+ ret = persistent_timer_clk_init(np, p);
+ if (ret)
+ goto err_clk;
+ }
+
+ if (p->flags & PERSISTENT_TIMER_MULT_SHIFT)
+ persistent_timer_freq_init(p);
+
+ p->last_cycles = 0;
+ ptimer = p;
+ return 0;
+
+err_clk:
+ if (p->flags & PERSISTENT_TIMER_BASE)
+ iounmap(p->base);
+
+ return ret;
+}
+
+void __init persistent_timer_cleanup(struct persistent_timer *p)
+{
+ if (p->flags & PERSISTENT_TIMER_CLOCK) {
+ p->freq = 0;
+ clk_disable_unprepare(p->clk);
+ clk_put(p->clk);
+ }
+
+ if (p->flags & PERSISTENT_TIMER_BASE)
+ iounmap(p->base);
+
+ ptimer = NULL;
+}
diff --git a/drivers/clocksource/persistent-timer.h b/drivers/clocksource/persistent-timer.h
new file mode 100644
index 0000000..7705027
--- /dev/null
+++ b/drivers/clocksource/persistent-timer.h
@@ -0,0 +1,26 @@
+// SPDX-License-Identifier: GPL-2.0
+#ifndef __PERSISTENT_TIMER_H__
+#define __PERSISTENT_TIMER_H__
+
+#define PERSISTENT_TIMER_BASE BIT(0)
+#define PERSISTENT_TIMER_CLOCK BIT(1)
+#define PERSISTENT_TIMER_MULT_SHIFT BIT(2)
+
+struct persistent_timer {
+ u64 (*read)(struct persistent_timer *p);
+ void __iomem *base;
+ u64 last_cycles;
+ u64 mask;
+ u32 mult;
+ u32 shift;
+ struct clk *clk;
+ unsigned long freq;
+ unsigned int flags;
+ struct timespec64 ts;
+};
+
+extern int __init persistent_timer_init_and_register(struct device_node *np,
+ struct persistent_timer *p);
+extern void __init persistent_timer_cleanup(struct persistent_timer *p);
+
+#endif
--
1.7.9.5