From patchwork Thu Jun 16 19:06:42 2011 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Marc Zyngier X-Patchwork-Id: 888382 Received: from merlin.infradead.org (merlin.infradead.org [205.233.59.134]) by demeter1.kernel.org (8.14.4/8.14.4) with ESMTP id p5GJAMsj014865 (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA bits=256 verify=NO) for ; Thu, 16 Jun 2011 19:10:44 GMT Received: from canuck.infradead.org ([2001:4978:20e::1]) by merlin.infradead.org with esmtps (Exim 4.76 #1 (Red Hat Linux)) id 1QXHwu-0004er-Iq; Thu, 16 Jun 2011 19:09:53 +0000 Received: from localhost ([127.0.0.1] helo=canuck.infradead.org) by canuck.infradead.org with esmtp (Exim 4.76 #1 (Red Hat Linux)) id 1QXHwq-0004Ge-Nl; Thu, 16 Jun 2011 19:09:48 +0000 Received: from casper.infradead.org ([2001:770:15f::2]) by canuck.infradead.org with esmtps (Exim 4.76 #1 (Red Hat Linux)) id 1QXHuv-0003pg-ST for linux-arm-kernel@canuck.infradead.org; Thu, 16 Jun 2011 19:07:50 +0000 Received: from service87.mimecast.com ([94.185.240.25]) by casper.infradead.org with smtp (Exim 4.76 #1 (Red Hat Linux)) id 1QXHut-0001J8-6M for linux-arm-kernel@lists.infradead.org; Thu, 16 Jun 2011 19:07:48 +0000 Received: from cam-owa2.Emea.Arm.com (fw-tnat.cambridge.arm.com [217.140.96.21]) by service87.mimecast.com; Thu, 16 Jun 2011 20:07:21 +0100 Received: from localhost.localdomain ([10.1.255.212]) by cam-owa2.Emea.Arm.com with Microsoft SMTPSVC(6.0.3790.3959); Thu, 16 Jun 2011 20:06:56 +0100 From: Marc Zyngier To: linux-arm-kernel@lists.infradead.org Subject: [RFC PATCH 14/16] ARM: local timers: add msm_timer driver to driver/clocksource Date: Thu, 16 Jun 2011 20:06:42 +0100 Message-Id: <1308251204-16719-15-git-send-email-marc.zyngier@arm.com> X-Mailer: git-send-email 1.7.0.4 In-Reply-To: <1308251204-16719-1-git-send-email-marc.zyngier@arm.com> References: <1308251204-16719-1-git-send-email-marc.zyngier@arm.com> X-OriginalArrivalTime: 16 Jun 2011 19:06:56.0530 (UTC) FILETIME=[8FA06720:01CC2C58] X-MC-Unique: 111061620072100101 X-CRM114-Version: 20090807-BlameThorstenAndJenny ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20110616_200747_378717_7ADE725B X-CRM114-Status: GOOD ( 21.82 ) X-Spam-Score: -2.6 (--) X-Spam-Report: SpamAssassin version 3.3.2-r929478 on casper.infradead.org summary: Content analysis details: (-2.6 points, 5.0 required) pts rule name description ---- ---------------------- -------------------------------------------------- -0.7 RCVD_IN_DNSWL_LOW RBL: Sender listed at http://www.dnswl.org/, low trust [94.185.240.25 listed in list.dnswl.org] -0.0 SPF_PASS SPF: sender matches SPF record -1.9 BAYES_00 BODY: Bayes spam probability is 0 to 1% [score: 0.0000] Cc: Arnd Bergmann , Stephen Boyd , Bryan Huntsman , Daniel Walker , Thomas Gleixner , David Brown X-BeenThere: linux-arm-kernel@lists.infradead.org X-Mailman-Version: 2.1.12 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , MIME-Version: 1.0 Sender: linux-arm-kernel-bounces@lists.infradead.org Errors-To: linux-arm-kernel-bounces+patchwork-linux-arm=patchwork.kernel.org@lists.infradead.org X-Greylist: IP, sender and recipient auto-whitelisted, not delayed by milter-greylist-4.2.6 (demeter1.kernel.org [140.211.167.41]); Thu, 16 Jun 2011 19:11:01 +0000 (UTC) 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 Cc: Daniel Walker Cc: Bryan Huntsman Cc: Stephen Boyd Signed-off-by: Marc Zyngier --- 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 +#include +#include +#include + +#include +#include +#include + +#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);