From patchwork Thu Jun 16 19:06:30 2011 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Marc Zyngier X-Patchwork-Id: 888322 Received: from merlin.infradead.org (merlin.infradead.org [205.233.59.134]) by demeter2.kernel.org (8.14.4/8.14.4) with ESMTP id p5GJ8u3g008980 (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA bits=256 verify=NO) for ; Thu, 16 Jun 2011 19:09:17 GMT Received: from canuck.infradead.org ([2001:4978:20e::1]) by merlin.infradead.org with esmtps (Exim 4.76 #1 (Red Hat Linux)) id 1QXHvj-00032H-7m; Thu, 16 Jun 2011 19:08:39 +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 1QXHvi-0003yB-RX; Thu, 16 Jun 2011 19:08:38 +0000 Received: from casper.infradead.org ([2001:770:15f::2]) by canuck.infradead.org with esmtps (Exim 4.76 #1 (Red Hat Linux)) id 1QXHum-0003o4-VN for linux-arm-kernel@canuck.infradead.org; Thu, 16 Jun 2011 19:07:41 +0000 Received: from service87.mimecast.com ([94.185.240.25]) by casper.infradead.org with smtp (Exim 4.76 #1 (Red Hat Linux)) id 1QXHuk-0001I4-J9 for linux-arm-kernel@lists.infradead.org; Thu, 16 Jun 2011 19:07:39 +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:32 +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:52 +0100 From: Marc Zyngier To: linux-arm-kernel@lists.infradead.org Subject: [RFC PATCH 02/16] ARM: local timers: add arm_smp_twd driver to driver/clocksource Date: Thu, 16 Jun 2011 20:06:30 +0100 Message-Id: <1308251204-16719-3-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:52.0092 (UTC) FILETIME=[8CFB37C0:01CC2C58] X-MC-Unique: 111061620073200201 X-CRM114-Version: 20090807-BlameThorstenAndJenny ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20110616_200738_777819_6269A09A X-CRM114-Status: GOOD ( 20.07 ) 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: Thomas Gleixner , Arnd Bergmann 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 (demeter2.kernel.org [140.211.167.43]); Thu, 16 Jun 2011 19:09:17 +0000 (UTC) Add a "new" driver to driver/clocksource to support the ARM TWD hardware. This is basically a copy of arch/arm/kernel/smp_twd.c, turned into a platform driver, with a CPU notifier being used to start/stop timers on secondary cores. Signed-off-by: Marc Zyngier --- drivers/clocksource/Kconfig | 3 + drivers/clocksource/Makefile | 1 + drivers/clocksource/arm_smp_twd.c | 265 +++++++++++++++++++++++++++++++++++++ 3 files changed, 269 insertions(+), 0 deletions(-) create mode 100644 drivers/clocksource/arm_smp_twd.c diff --git a/drivers/clocksource/Kconfig b/drivers/clocksource/Kconfig index 330343b..a436dd4 100644 --- a/drivers/clocksource/Kconfig +++ b/drivers/clocksource/Kconfig @@ -9,3 +9,6 @@ config CLKBLD_I8253 config CLKSRC_MMIO bool + +config ARM_SMP_TWD + bool diff --git a/drivers/clocksource/Makefile b/drivers/clocksource/Makefile index 7922a0c..b7e6397 100644 --- a/drivers/clocksource/Makefile +++ b/drivers/clocksource/Makefile @@ -8,3 +8,4 @@ obj-$(CONFIG_SH_TIMER_MTU2) += sh_mtu2.o obj-$(CONFIG_SH_TIMER_TMU) += sh_tmu.o obj-$(CONFIG_CLKBLD_I8253) += i8253.o obj-$(CONFIG_CLKSRC_MMIO) += mmio.o +obj-$(CONFIG_ARM_SMP_TWD) += arm_smp_twd.o diff --git a/drivers/clocksource/arm_smp_twd.c b/drivers/clocksource/arm_smp_twd.c new file mode 100644 index 0000000..5e2e8cc --- /dev/null +++ b/drivers/clocksource/arm_smp_twd.c @@ -0,0 +1,265 @@ +/* + * linux/arch/arm/kernel/smp_twd.c + * + * Copyright (C) 2002 ARM Ltd. + * All Rights Reserved + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define TWD_TIMER_LOAD 0x00 +#define TWD_TIMER_COUNTER 0x04 +#define TWD_TIMER_CONTROL 0x08 +#define TWD_TIMER_INTSTAT 0x0C + +#define TWD_TIMER_CONTROL_ENABLE (1 << 0) +#define TWD_TIMER_CONTROL_ONESHOT (0 << 1) +#define TWD_TIMER_CONTROL_PERIODIC (1 << 1) +#define TWD_TIMER_CONTROL_IT_ENABLE (1 << 2) + +static void __iomem *twd_base; +static int twd_ppi; + +static unsigned long twd_timer_rate; +static DEFINE_PER_CPU(bool, irq_reqd); +static struct clock_event_device __percpu *twd_evt; + +static void twd_set_mode(enum clock_event_mode mode, + struct clock_event_device *clk) +{ + unsigned long ctrl; + + switch (mode) { + case CLOCK_EVT_MODE_PERIODIC: + /* timer load already set up */ + ctrl = TWD_TIMER_CONTROL_ENABLE | TWD_TIMER_CONTROL_IT_ENABLE + | TWD_TIMER_CONTROL_PERIODIC; + __raw_writel(twd_timer_rate / HZ, twd_base + TWD_TIMER_LOAD); + break; + case CLOCK_EVT_MODE_ONESHOT: + /* period set, and timer enabled in 'next_event' hook */ + ctrl = TWD_TIMER_CONTROL_IT_ENABLE | TWD_TIMER_CONTROL_ONESHOT; + break; + case CLOCK_EVT_MODE_UNUSED: + case CLOCK_EVT_MODE_SHUTDOWN: + default: + ctrl = 0; + } + + __raw_writel(ctrl, twd_base + TWD_TIMER_CONTROL); +} + +static int twd_set_next_event(unsigned long evt, + struct clock_event_device *unused) +{ + unsigned long ctrl = __raw_readl(twd_base + TWD_TIMER_CONTROL); + + ctrl |= TWD_TIMER_CONTROL_ENABLE; + + __raw_writel(evt, twd_base + TWD_TIMER_COUNTER); + __raw_writel(ctrl, twd_base + TWD_TIMER_CONTROL); + + return 0; +} + +static irqreturn_t twd_handler(int irq, void *dev_id) +{ + struct clock_event_device *evt = dev_id; + + if (__raw_readl(twd_base + TWD_TIMER_INTSTAT)) { + __raw_writel(1, twd_base + TWD_TIMER_INTSTAT); + evt->event_handler(evt); + return IRQ_HANDLED; + } + + return IRQ_NONE; +} + +static void __cpuinit twd_calibrate_rate(void) +{ + unsigned long count; + u64 waitjiffies; + + /* + * If this is the first time round, we need to work out how fast + * the timer ticks + */ + if (twd_timer_rate == 0) { + printk(KERN_INFO "Calibrating local timer... "); + + /* Wait for a tick to start */ + waitjiffies = get_jiffies_64() + 1; + + while (get_jiffies_64() < waitjiffies) + udelay(10); + + /* OK, now the tick has started, let's get the timer going */ + waitjiffies += 5; + + /* enable, no interrupt or reload */ + __raw_writel(0x1, twd_base + TWD_TIMER_CONTROL); + + /* maximum value */ + __raw_writel(0xFFFFFFFFU, twd_base + TWD_TIMER_COUNTER); + + while (get_jiffies_64() < waitjiffies) + udelay(10); + + count = __raw_readl(twd_base + TWD_TIMER_COUNTER); + + twd_timer_rate = (0xFFFFFFFFU - count) * (HZ / 5); + + printk("%lu.%02luMHz.\n", twd_timer_rate / 1000000, + (twd_timer_rate / 1000000) % 100); + } +} + +/* + * Setup the local clock events for a CPU. + */ +static void __cpuinit twd_setup(void *data) +{ + struct clock_event_device *clk = data; + int err; + + twd_calibrate_rate(); + + clk->name = "arm_smp_twd"; + clk->features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT | + CLOCK_EVT_FEAT_C3STOP; + clk->rating = 450; + clk->set_mode = twd_set_mode; + clk->set_next_event = twd_set_next_event; + clk->shift = 20; + clk->mult = div_sc(twd_timer_rate, NSEC_PER_SEC, clk->shift); + clk->max_delta_ns = clockevent_delta2ns(0xffffffff, clk); + clk->min_delta_ns = clockevent_delta2ns(0xf, clk); + clk->irq = gic_ppi_to_vppi(twd_ppi); + clk->cpumask = cpumask_of(smp_processor_id()); + + pr_debug("Configuring %s on cpu #%d\n", clk->name, smp_processor_id()); + + err = request_irq(clk->irq, twd_handler, + IRQF_PERCPU | IRQF_NOBALANCING | IRQF_TIMER, + clk->name, clk); + if (err) { + pr_err("%s: can't register interrupt %d on cpu %d (%d)\n", + clk->name, clk->irq, smp_processor_id(), err); + return; + } + + clockevents_register_device(clk); +} + +static void __cpuinit twd_teardown(void *data) +{ + struct clock_event_device *clk = data; + pr_debug("twd_teardown disable IRQ%d cpu #%d\n", + clk->irq, smp_processor_id()); + disable_irq(clk->irq); + twd_set_mode(CLOCK_EVT_MODE_UNUSED, clk); +} + +static void __cpuinit twd_restart(void *data) +{ + struct clock_event_device *clk = data; + pr_debug("twd_restart enable IRQ%d cpu #%d\n", + clk->irq, smp_processor_id()); + enable_irq(clk->irq); + clockevents_register_device(clk); +} + +static int __cpuinit twd_cpu_notify(struct notifier_block *self, + unsigned long action, void *data) +{ + int cpu = (int)data; + struct clock_event_device *clk = per_cpu_ptr(twd_evt, cpu); + bool *reqd = &per_cpu(irq_reqd, cpu); + + switch (action) { + case CPU_STARTING: + case CPU_STARTING_FROZEN: + if (!*reqd) { + smp_call_function_single(cpu, twd_setup, clk, 1); + *reqd = true; + } else { + smp_call_function_single(cpu, twd_restart, clk, 1); + } + break; + + case CPU_DOWN_PREPARE: + case CPU_DOWN_PREPARE_FROZEN: + smp_call_function_single(cpu, twd_teardown, clk, 1); + break; + } + + return NOTIFY_OK; +} + +static struct notifier_block __cpuinitdata twd_cpu_nb = { + .notifier_call = twd_cpu_notify, +}; + +static int twd_probe(struct platform_device *pdev) +{ + struct resource *mem; + struct clock_event_device *clk; + int irq; + + if (twd_base) + return -EBUSY; + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + irq = platform_get_irq(pdev, 0); + if (!mem || irq < 0) + return -EINVAL; + + twd_base = ioremap(mem->start, resource_size(mem)); + twd_evt = alloc_percpu(struct clock_event_device); + if (!twd_base || !twd_evt) { + iounmap(twd_base); + twd_base = NULL; + free_percpu(twd_evt); + return -ENOMEM; + } + + twd_ppi = irq; + + /* Immediately configure the timer on the boot CPU */ + clk = per_cpu_ptr(twd_evt, smp_processor_id()); + twd_setup(clk); + + register_cpu_notifier(&twd_cpu_nb); + + return 0; +} + +static int twd_remove(struct platform_device *pdev) +{ + return -EBUSY; +} + +static struct platform_driver twd_driver = { + .probe = twd_probe, + .remove = __devexit_p(twd_remove), + .driver = { + .name = "arm_smp_twd", + }, +}; + +early_platform_init("localtimer", &twd_driver);