From patchwork Thu Jun 16 19:06:38 2011 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Marc Zyngier X-Patchwork-Id: 888342 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 p5GJ9Xlh009746 (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-SHA bits=256 verify=NO) for ; Thu, 16 Jun 2011 19:09:55 GMT Received: from canuck.infradead.org ([2001:4978:20e::1]) by merlin.infradead.org with esmtps (Exim 4.76 #1 (Red Hat Linux)) id 1QXHwH-0003au-OW; Thu, 16 Jun 2011 19:09:14 +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 1QXHwG-00045O-S8; Thu, 16 Jun 2011 19:09:12 +0000 Received: from casper.infradead.org ([2001:770:15f::2]) by canuck.infradead.org with esmtps (Exim 4.76 #1 (Red Hat Linux)) id 1QXHus-0003p8-WE for linux-arm-kernel@canuck.infradead.org; Thu, 16 Jun 2011 19:07:47 +0000 Received: from service87.mimecast.com ([94.185.240.25]) by casper.infradead.org with smtp (Exim 4.76 #1 (Red Hat Linux)) id 1QXHup-0001IN-No for linux-arm-kernel@lists.infradead.org; Thu, 16 Jun 2011 19:07:45 +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:34 +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:54 +0100 From: Marc Zyngier To: linux-arm-kernel@lists.infradead.org Subject: [RFC PATCH 10/16] ARM: local timers: add exynos_mct driver to driver/clocksource Date: Thu, 16 Jun 2011 20:06:38 +0100 Message-Id: <1308251204-16719-11-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:54.0967 (UTC) FILETIME=[8EB1E870:01CC2C58] X-MC-Unique: 111061620073400701 X-CRM114-Version: 20090807-BlameThorstenAndJenny ( TRE 0.8.0 (BSD) ) MR-646709E3 X-CRM114-CacheID: sfid-20110616_200743_835145_B030AAE4 X-CRM114-Status: GOOD ( 19.22 ) 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 , Kukjin Kim , 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:55 +0000 (UTC) Add a "new" driver to driver/clocksource to support the Samsung MCT hardware. This is basically the local timer part of arch/arm/mach-exynos4/mct.c, turned into a platform driver, and with a CPU notifier being used to start/stop timers on secondary cores. Cc: Kukjin Kim Signed-off-by: Marc Zyngier --- drivers/clocksource/Makefile | 1 + drivers/clocksource/exynos4_mct.c | 283 +++++++++++++++++++++++++++++++++++++ 2 files changed, 284 insertions(+), 0 deletions(-) create mode 100644 drivers/clocksource/exynos4_mct.c diff --git a/drivers/clocksource/Makefile b/drivers/clocksource/Makefile index b7e6397..7b30585 100644 --- a/drivers/clocksource/Makefile +++ b/drivers/clocksource/Makefile @@ -9,3 +9,4 @@ 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 +obj-$(CONFIG_EXYNOS4_MCT) += exynos4_mct.o diff --git a/drivers/clocksource/exynos4_mct.c b/drivers/clocksource/exynos4_mct.c new file mode 100644 index 0000000..b07a9a9 --- /dev/null +++ b/drivers/clocksource/exynos4_mct.c @@ -0,0 +1,283 @@ +/* + * + * Copyright (c) 2011 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * EXYNOS4 MCT(Multi-Core Timer) support + * + * 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 + +static unsigned long clk_cnt_per_tick; +static unsigned long clk_rate; + +struct mct_clock_event_device { + struct clock_event_device evt; + void __iomem *base; +}; + +static struct mct_clock_event_device __percpu *mct_tick; + +static void exynos4_mct_write(unsigned int value, void *addr) +{ + void __iomem *stat_addr; + u32 mask; + u32 i; + + __raw_writel(value, addr); + + switch ((u32) addr) { + case (u32)(EXYNOS4_MCT_L0_BASE + MCT_L_TCON_OFFSET): + stat_addr = EXYNOS4_MCT_L0_BASE + MCT_L_WSTAT_OFFSET; + mask = 1 << 3; /* L0_TCON write status */ + break; + case (u32)(EXYNOS4_MCT_L1_BASE + MCT_L_TCON_OFFSET): + stat_addr = EXYNOS4_MCT_L1_BASE + MCT_L_WSTAT_OFFSET; + mask = 1 << 3; /* L1_TCON write status */ + break; + case (u32)(EXYNOS4_MCT_L0_BASE + MCT_L_TCNTB_OFFSET): + stat_addr = EXYNOS4_MCT_L0_BASE + MCT_L_WSTAT_OFFSET; + mask = 1 << 0; /* L0_TCNTB write status */ + break; + case (u32)(EXYNOS4_MCT_L1_BASE + MCT_L_TCNTB_OFFSET): + stat_addr = EXYNOS4_MCT_L1_BASE + MCT_L_WSTAT_OFFSET; + mask = 1 << 0; /* L1_TCNTB write status */ + break; + case (u32)(EXYNOS4_MCT_L0_BASE + MCT_L_ICNTB_OFFSET): + stat_addr = EXYNOS4_MCT_L0_BASE + MCT_L_WSTAT_OFFSET; + mask = 1 << 1; /* L0_ICNTB write status */ + break; + case (u32)(EXYNOS4_MCT_L1_BASE + MCT_L_ICNTB_OFFSET): + stat_addr = EXYNOS4_MCT_L1_BASE + MCT_L_WSTAT_OFFSET; + mask = 1 << 1; /* L1_ICNTB write status */ + break; + default: + return; + } + + /* Wait maximum 1 ms until written values are applied */ + for (i = 0; i < loops_per_jiffy / 1000 * HZ; i++) + if (__raw_readl(stat_addr) & mask) { + __raw_writel(mask, stat_addr); + return; + } + + panic("MCT hangs after writing %d (addr:0x%08x)\n", value, (u32)addr); +} + +/* Clock event handling */ +static void exynos4_mct_tick_stop(void *data) +{ + struct mct_clock_event_device *mevt = data; + unsigned long tmp; + unsigned long mask = MCT_L_TCON_INT_START | MCT_L_TCON_TIMER_START; + void __iomem *addr = mevt->base + MCT_L_TCON_OFFSET; + + tmp = __raw_readl(addr); + if (tmp & mask) { + tmp &= ~mask; + exynos4_mct_write(tmp, addr); + } +} + +static void exynos4_mct_tick_start(unsigned long cycles, + struct mct_clock_event_device *mevt) +{ + unsigned long tmp; + + exynos4_mct_tick_stop(mevt); + + tmp = (1 << 31) | cycles; /* MCT_L_UPDATE_ICNTB */ + + /* update interrupt count buffer */ + exynos4_mct_write(tmp, mevt->base + MCT_L_ICNTB_OFFSET); + + /* enable MCT tick interrupt */ + exynos4_mct_write(0x1, mevt->base + MCT_L_INT_ENB_OFFSET); + + tmp = __raw_readl(mevt->base + MCT_L_TCON_OFFSET); + tmp |= MCT_L_TCON_INT_START | MCT_L_TCON_TIMER_START | + MCT_L_TCON_INTERVAL_MODE; + exynos4_mct_write(tmp, mevt->base + MCT_L_TCON_OFFSET); +} + +static int exynos4_tick_set_next_event(unsigned long cycles, + struct clock_event_device *evt) +{ + struct mct_clock_event_device *mevt; + + mevt = container_of(evt, struct mct_clock_event_device, evt); + exynos4_mct_tick_start(cycles, mevt); + + return 0; +} + +static inline void exynos4_tick_set_mode(enum clock_event_mode mode, + struct clock_event_device *evt) +{ + struct mct_clock_event_device *mevt; + + mevt = container_of(evt, struct mct_clock_event_device, evt); + exynos4_mct_tick_stop(mevt); + + switch (mode) { + case CLOCK_EVT_MODE_PERIODIC: + exynos4_mct_tick_start(clk_cnt_per_tick, mevt); + break; + + case CLOCK_EVT_MODE_ONESHOT: + case CLOCK_EVT_MODE_UNUSED: + case CLOCK_EVT_MODE_SHUTDOWN: + case CLOCK_EVT_MODE_RESUME: + break; + } +} + +static irqreturn_t exynos4_mct_tick_isr(int irq, void *dev_id) +{ + struct mct_clock_event_device *mevt = dev_id; + struct clock_event_device *evt = &mevt->evt; + + /* + * This is for supporting oneshot mode. + * Mct would generate interrupt periodically + * without explicit stopping. + */ + if (evt->mode != CLOCK_EVT_MODE_PERIODIC) + exynos4_mct_tick_stop(mevt); + + /* Clear the MCT tick interrupt */ + exynos4_mct_write(0x1, mevt->base + MCT_L_INT_CSTAT_OFFSET); + + evt->event_handler(evt); + + return IRQ_HANDLED; +} + +static struct irqaction mct_tick0_event_irq = { + .name = "mct_tick0_irq", + .flags = IRQF_TIMER | IRQF_NOBALANCING, + .handler = exynos4_mct_tick_isr, +}; + +static struct irqaction mct_tick1_event_irq = { + .name = "mct_tick1_irq", + .flags = IRQF_TIMER | IRQF_NOBALANCING, + .handler = exynos4_mct_tick_isr, +}; + +static void exynos4_mct_tick_init(void *data) +{ + struct mct_clock_event_device *mevt = data; + struct clock_event_device *evt = &mevt->evt; + unsigned int cpu = smp_processor_id(); + + if (cpu == 0) { + mevt->base = EXYNOS4_MCT_L0_BASE; + evt->name = "mct_tick0"; + } else { + mevt->base = EXYNOS4_MCT_L1_BASE; + evt->name = "mct_tick1"; + } + + evt->cpumask = cpumask_of(cpu); + evt->set_next_event = exynos4_tick_set_next_event; + evt->set_mode = exynos4_tick_set_mode; + evt->features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT; + evt->rating = 450; + + clockevents_calc_mult_shift(evt, clk_rate / 2, 5); + evt->max_delta_ns = + clockevent_delta2ns(0x7fffffff, evt); + evt->min_delta_ns = + clockevent_delta2ns(0xf, evt); + + clockevents_register_device(evt); + + exynos4_mct_write(0x1, mevt->base + MCT_L_TCNTB_OFFSET); + + if (cpu == 0) { + mct_tick0_event_irq.dev_id = mevt; + setup_irq(IRQ_MCT_L0, &mct_tick0_event_irq); + } else { + mct_tick1_event_irq.dev_id = mevt; + irq_set_affinity(IRQ_MCT1, cpumask_of(1)); + setup_irq(IRQ_MCT_L1, &mct_tick1_event_irq); + } +} + +static int __cpuinit exynos4_mct_cpu_notify(struct notifier_block *self, + unsigned long action, void *data) +{ + int cpu = (int)data; + struct mct_clock_event_device *mevt = per_cpu_ptr(mct_tick, cpu); + + switch (action) { + case CPU_STARTING: + case CPU_STARTING_FROZEN: + smp_call_function_single(cpu, exynos4_mct_tick_init, mevt, 1); + break; + + case CPU_DOWN_PREPARE: + case CPU_DOWN_PREPARE_FROZEN: + smp_call_function_single(cpu, exynos4_mct_tick_stop, mevt, 1); + break; + } + + return NOTIFY_OK; +} + +static struct notifier_block __cpuinitdata exynos4_mct_cpu_nb = { + .notifier_call = exynos4_mct_cpu_notify, +}; + +static int exynos4_mct_probe(struct platform_device *pdev) +{ + struct clk *mct_clk; + struct mct_clock_event_device *mevt; + + if (mct_tick) + return -EBUSY; + + mct_tick = alloc_percpu(struct mct_clock_event_device); + if (!mct_tick) + return -ENOMEM; + + mct_clk = clk_get(NULL, "xtal"); + clk_rate = clk_get_rate(mct_clk); + + /* Immediately configure the timer on the boot CPU */ + mevt = per_cpu_ptr(mct_tick, smp_processor_id()); + exynos4_mct_tick_init(mevt); + + register_cpu_notifier(&exynos4_mct_cpu_nb); + + return 0; +} + +static int exynos4_mct_remove(struct platform_device *pdev) +{ + return -EBUSY; +} + +static struct platform_driver exynos4_mct_driver = { + .probe = exynos4_mct_probe, + .remove = __devexit_p(exynos4_mct_remove), + .driver = { + .name = "exynos4_mct", + }, +}; + +early_platform_init("localtimer", &exynos4_mct_driver);