[RFC PATCH 14/16] ARM: local timers: add msm_timer driver to driver/clocksource

Marc Zyngier marc.zyngier at arm.com
Thu Jun 16 15:06:42 EDT 2011


Add a "new" driver to driver/clocksource to support the MSM timer
hardware. This is basically a copy of arch/arm/mach-msm/timer.c
turned into a platform driver, with a CPU notifier being used
to start/stop the timer on secondary core.

Cc: David Brown <davidb at codeaurora.org>
Cc: Daniel Walker <dwalker at fifo99.com>
Cc: Bryan Huntsman <bryanh at codeaurora.org>
Cc: Stephen Boyd <sboyd at codeaurora.org>
Signed-off-by: Marc Zyngier <marc.zyngier at arm.com>
---
 drivers/clocksource/Kconfig     |    3 +
 drivers/clocksource/Makefile    |    1 +
 drivers/clocksource/msm_timer.c |  345 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 349 insertions(+), 0 deletions(-)
 create mode 100644 drivers/clocksource/msm_timer.c

diff --git a/drivers/clocksource/Kconfig b/drivers/clocksource/Kconfig
index a436dd4..c4cf8bc 100644
--- a/drivers/clocksource/Kconfig
+++ b/drivers/clocksource/Kconfig
@@ -12,3 +12,6 @@ config CLKSRC_MMIO
 
 config ARM_SMP_TWD
        bool
+
+config MSM_TIMER
+       bool
diff --git a/drivers/clocksource/Makefile b/drivers/clocksource/Makefile
index 7b30585..ffb4186 100644
--- a/drivers/clocksource/Makefile
+++ b/drivers/clocksource/Makefile
@@ -10,3 +10,4 @@ obj-$(CONFIG_CLKBLD_I8253)	+= i8253.o
 obj-$(CONFIG_CLKSRC_MMIO)	+= mmio.o
 obj-$(CONFIG_ARM_SMP_TWD)	+= arm_smp_twd.o
 obj-$(CONFIG_EXYNOS4_MCT)	+= exynos4_mct.o
+obj-$(CONFIG_MSM_TIMER)		+= msm_timer.o
diff --git a/drivers/clocksource/msm_timer.c b/drivers/clocksource/msm_timer.c
new file mode 100644
index 0000000..2ec66dd
--- /dev/null
+++ b/drivers/clocksource/msm_timer.c
@@ -0,0 +1,345 @@
+/*
+ *
+ * Copyright (C) 2007 Google, Inc.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/interrupt.h>
+#include <linux/clockchips.h>
+#include <linux/platform_device.h>
+#include <linux/cpu.h>
+
+#include <asm/hardware/gic.h>
+#include <mach/msm_iomap.h>
+#include <mach/cpu.h>
+
+#define TIMER_MATCH_VAL         0x0000
+#define TIMER_COUNT_VAL         0x0004
+#define TIMER_ENABLE            0x0008
+#define TIMER_ENABLE_CLR_ON_MATCH_EN    2
+#define TIMER_ENABLE_EN                 1
+#define TIMER_CLEAR             0x000C
+#define DGT_CLK_CTL             0x0034
+enum {
+	DGT_CLK_CTL_DIV_1 = 0,
+	DGT_CLK_CTL_DIV_2 = 1,
+	DGT_CLK_CTL_DIV_3 = 2,
+	DGT_CLK_CTL_DIV_4 = 3,
+};
+#define CSR_PROTECTION          0x0020
+#define CSR_PROTECTION_EN               1
+
+#define GPT_HZ 32768
+
+enum timer_location {
+	LOCAL_TIMER = 0,
+	GLOBAL_TIMER = 1,
+};
+
+#define MSM_GLOBAL_TIMER MSM_CLOCK_DGT
+
+/* TODO: Remove these ifdefs */
+#if defined(CONFIG_ARCH_QSD8X50)
+#define DGT_HZ (19200000 / 4) /* 19.2 MHz / 4 by default */
+#define MSM_DGT_SHIFT (0)
+#elif defined(CONFIG_ARCH_MSM7X30) || defined(CONFIG_ARCH_MSM8X60) || \
+				      defined(CONFIG_ARCH_MSM8960)
+#define DGT_HZ (24576000 / 4) /* 24.576 MHz (LPXO) / 4 by default */
+#define MSM_DGT_SHIFT (0)
+#else
+#define DGT_HZ 19200000 /* 19.2 MHz or 600 KHz after shift */
+#define MSM_DGT_SHIFT (5)
+#endif
+
+struct msm_clock {
+	struct clock_event_device   clockevent;
+	struct clocksource          clocksource;
+	unsigned int		    irq;
+	void __iomem                *regbase;
+	uint32_t                    freq;
+	uint32_t                    shift;
+	void __iomem                *global_counter;
+	void __iomem                *local_counter;
+};
+
+enum {
+	MSM_CLOCK_GPT,
+	MSM_CLOCK_DGT,
+	NR_TIMERS,
+};
+
+static struct msm_clock msm_clocks[];
+
+static irqreturn_t msm_timer_interrupt(int irq, void *dev_id)
+{
+	struct clock_event_device *evt = dev_id;
+	if (evt->event_handler == NULL)
+		return IRQ_HANDLED;
+	evt->event_handler(evt);
+	return IRQ_HANDLED;
+}
+
+static cycle_t msm_read_timer_count(struct clocksource *cs)
+{
+	struct msm_clock *clk = container_of(cs, struct msm_clock, clocksource);
+
+	return readl(clk->global_counter);
+}
+
+static struct msm_clock *clockevent_to_clock(struct clock_event_device *evt)
+{
+	return container_of(evt, struct msm_clock, clockevent);
+}
+
+static int msm_timer_set_next_event(unsigned long cycles,
+				    struct clock_event_device *evt)
+{
+	struct msm_clock *clock = clockevent_to_clock(evt);
+	uint32_t now = readl(clock->local_counter);
+	uint32_t alarm = now + (cycles << clock->shift);
+
+	writel(alarm, clock->regbase + TIMER_MATCH_VAL);
+	return 0;
+}
+
+static void msm_timer_set_mode(enum clock_event_mode mode,
+			      struct clock_event_device *evt)
+{
+	struct msm_clock *clock = clockevent_to_clock(evt);
+
+	switch (mode) {
+	case CLOCK_EVT_MODE_RESUME:
+	case CLOCK_EVT_MODE_PERIODIC:
+		break;
+	case CLOCK_EVT_MODE_ONESHOT:
+		writel(TIMER_ENABLE_EN, clock->regbase + TIMER_ENABLE);
+		break;
+	case CLOCK_EVT_MODE_UNUSED:
+		disable_irq(evt->irq);
+		/* fall through */
+	case CLOCK_EVT_MODE_SHUTDOWN:
+		writel(0, clock->regbase + TIMER_ENABLE);
+		break;
+	}
+}
+
+static struct msm_clock msm_clocks[] = {
+	[MSM_CLOCK_GPT] = {
+		.clockevent = {
+			.name           = "gp_timer",
+			.features       = CLOCK_EVT_FEAT_ONESHOT,
+			.shift          = 32,
+			.rating         = 200,
+			.set_next_event = msm_timer_set_next_event,
+			.set_mode       = msm_timer_set_mode,
+		},
+		.clocksource = {
+			.name           = "gp_timer",
+			.rating         = 200,
+			.read           = msm_read_timer_count,
+			.mask           = CLOCKSOURCE_MASK(32),
+			.flags          = CLOCK_SOURCE_IS_CONTINUOUS,
+		},
+		.irq = INT_GP_TIMER_EXP,
+		.freq = GPT_HZ,
+	},
+	[MSM_CLOCK_DGT] = {
+		.clockevent = {
+			.name           = "dg_timer",
+			.features       = CLOCK_EVT_FEAT_ONESHOT,
+			.shift          = 32 + MSM_DGT_SHIFT,
+			.rating         = 300,
+			.set_next_event = msm_timer_set_next_event,
+			.set_mode       = msm_timer_set_mode,
+		},
+		.clocksource = {
+			.name           = "dg_timer",
+			.rating         = 300,
+			.read           = msm_read_timer_count,
+			.mask           = CLOCKSOURCE_MASK((32 - MSM_DGT_SHIFT)),
+			.flags          = CLOCK_SOURCE_IS_CONTINUOUS,
+		},
+		.irq = INT_DEBUG_TIMER_EXP,
+		.freq = DGT_HZ >> MSM_DGT_SHIFT,
+		.shift = MSM_DGT_SHIFT,
+	}
+};
+
+static void __init msm_timer_primary_setup(void)
+{
+	int i;
+	int res;
+	int global_offset = 0;
+
+	if (cpu_is_msm7x01()) {
+		msm_clocks[MSM_CLOCK_GPT].regbase = MSM_CSR_BASE;
+		msm_clocks[MSM_CLOCK_DGT].regbase = MSM_CSR_BASE + 0x10;
+	} else if (cpu_is_msm7x30()) {
+		msm_clocks[MSM_CLOCK_GPT].regbase = MSM_CSR_BASE + 0x04;
+		msm_clocks[MSM_CLOCK_DGT].regbase = MSM_CSR_BASE + 0x24;
+	} else if (cpu_is_qsd8x50()) {
+		msm_clocks[MSM_CLOCK_GPT].regbase = MSM_CSR_BASE;
+		msm_clocks[MSM_CLOCK_DGT].regbase = MSM_CSR_BASE + 0x10;
+	} else if (cpu_is_msm8x60() || cpu_is_msm8960()) {
+		msm_clocks[MSM_CLOCK_GPT].regbase = MSM_TMR_BASE + 0x04;
+		msm_clocks[MSM_CLOCK_DGT].regbase = MSM_TMR_BASE + 0x24;
+
+		/* Use CPU0's timer as the global timer. */
+		global_offset = MSM_TMR0_BASE - MSM_TMR_BASE;
+	} else
+		BUG();
+
+#ifdef CONFIG_ARCH_MSM_SCORPIONMP
+	writel(DGT_CLK_CTL_DIV_4, MSM_TMR_BASE + DGT_CLK_CTL);
+#endif
+
+	for (i = 0; i < ARRAY_SIZE(msm_clocks); i++) {
+		struct msm_clock *clock = &msm_clocks[i];
+		struct clock_event_device *ce = &clock->clockevent;
+		struct clocksource *cs = &clock->clocksource;
+
+		clock->local_counter = clock->regbase + TIMER_COUNT_VAL;
+		clock->global_counter = clock->local_counter + global_offset;
+
+		writel(0, clock->regbase + TIMER_ENABLE);
+		writel(0, clock->regbase + TIMER_CLEAR);
+		writel(~0, clock->regbase + TIMER_MATCH_VAL);
+
+		ce->mult = div_sc(clock->freq, NSEC_PER_SEC, ce->shift);
+		/* allow at least 10 seconds to notice that the timer wrapped */
+		ce->max_delta_ns =
+			clockevent_delta2ns(0xf0000000 >> clock->shift, ce);
+		/* 4 gets rounded down to 3 */
+		ce->min_delta_ns = clockevent_delta2ns(4, ce);
+		ce->cpumask = cpumask_of(0);
+
+		res = clocksource_register_hz(cs, clock->freq);
+		if (res)
+			printk(KERN_ERR "msm_timer_init: clocksource_register "
+			       "failed for %s\n", cs->name);
+
+		ce->irq = gic_ppi_to_vppi(clock->irq);
+		res = request_irq(ce->irq, msm_timer_interrupt,
+				  IRQF_TIMER | IRQF_NOBALANCING | IRQF_TRIGGER_RISING,
+				  ce->name, ce);
+		if (res)
+			pr_err("msm_timer_init: request_irq failed for %s\n",
+			       ce->name);
+
+		clockevents_register_device(ce);
+	}
+}
+
+#ifdef CONFIG_SMP
+static void __cpuinit msm_timer_secondary_setup(void *data)
+{
+	static bool local_timer_inited;
+	struct clock_event_device *evt = data;
+	struct msm_clock *clock = &msm_clocks[MSM_GLOBAL_TIMER];
+	int res;
+
+	writel(DGT_CLK_CTL_DIV_4, MSM_TMR_BASE + DGT_CLK_CTL);
+
+	evt->irq = gic_ppi_to_vppi(clock->irq);
+	evt->name = "local_timer";
+	evt->features = CLOCK_EVT_FEAT_ONESHOT;
+	evt->rating = clock->clockevent.rating;
+	evt->set_mode = msm_timer_set_mode;
+	evt->set_next_event = msm_timer_set_next_event;
+	evt->shift = clock->clockevent.shift;
+	evt->mult = div_sc(clock->freq, NSEC_PER_SEC, evt->shift);
+	evt->max_delta_ns =
+		clockevent_delta2ns(0xf0000000 >> clock->shift, evt);
+	evt->min_delta_ns = clockevent_delta2ns(4, evt);
+
+	if (!local_timer_inited) {
+		writel(0, clock->regbase  + TIMER_ENABLE);
+		writel(0, clock->regbase + TIMER_CLEAR);
+		writel(~0, clock->regbase + TIMER_MATCH_VAL);
+
+		res = request_irq(evt->irq, msm_timer_interrupt,
+				  IRQF_TIMER | IRQF_NOBALANCING | IRQF_TRIGGER_RISING,
+				  clock->clockevent.name, evt);
+		if (res) {
+			pr_err("local_timer_setup: request_irq failed for %s\n",
+			       clock->clockevent.name);
+			return;
+		}
+		local_timer_inited = true;
+	} else
+		enable_irq(evt->irq);
+
+	clockevents_register_device(evt);
+}
+
+static void __cpuinit msm_timer_secondary_teardown(void *data)
+{
+	struct clock_event_device *clk = data;
+	msm_timer_set_mode(CLOCK_EVT_MODE_UNUSED, clk);
+}
+
+static int __cpuinit msm_timer_cpu_notify(struct notifier_block *self,
+				    unsigned long action, void *data)
+{
+	int cpu = (int)data;
+	struct clock_event_device *clk;
+
+	clk = &msm_clocks[MSM_GLOBAL_TIMER].clockevent;
+
+	switch (action) {
+	case CPU_STARTING:
+	case CPU_STARTING_FROZEN:
+		smp_call_function_single(cpu, msm_timer_secondary_setup,
+					 clk, 1);
+		break;
+
+	case CPU_DOWN_PREPARE:
+	case CPU_DOWN_PREPARE_FROZEN:
+		smp_call_function_single(cpu, msm_timer_secondary_teardown,
+					 clk, 1);
+		break;
+	}
+
+	return NOTIFY_OK;
+}
+
+static struct notifier_block __cpuinitdata msm_timer_cpu_nb = {
+	.notifier_call = msm_timer_cpu_notify,
+};
+#endif
+
+static int msm_timer_probe(struct platform_device *pdev)
+{
+	/* Immediately configure the timer on the boot CPU */
+	msm_timer_primary_setup();
+
+#ifdef CONFIG_SMP
+	register_cpu_notifier(&msm_timer_cpu_nb);
+#endif
+
+	return 0;
+}
+
+static int msm_timer_remove(struct platform_device *pdev)
+{
+	return -EBUSY;
+}
+
+static struct platform_driver msm_timer_driver = {
+	.probe	= msm_timer_probe,
+	.remove = __devexit_p(msm_timer_remove),
+	.driver = {
+		.name	= "msm_timer",
+	},
+};
+
+early_platform_init("localtimer", &msm_timer_driver);
-- 
1.7.0.4





More information about the linux-arm-kernel mailing list