All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH] ARM: S5PV310: Implement kernel timers using MCT
@ 2010-12-22 11:27 ` Kukjin Kim
  0 siblings, 0 replies; 14+ messages in thread
From: Kukjin Kim @ 2010-12-22 11:27 UTC (permalink / raw)
  To: linux-arm-kernel, linux-samsung-soc
  Cc: ben-linux, Changhwan Youn, Russell King, Kukjin Kim

From: Changhwan Youn <chaos.youn@samsung.com>

The Multi-Core Timer(MCT) of S5PV310 is designed for implementing
clock source timer and clock event timers. This patch implements
1 clock source timer with 64 bit free running counter of MCT and
2 clock event timers with two of 31-bit tick counters.

Signed-off-by: Changhwan Youn <chaos.youn@samsung.com>
Cc: Ben Dooks <ben-linux@fluff.org>
Cc: Russell King <rmk+kernel@arm.linux.org.uk>
Signed-off-by: Kukjin Kim <kgene.kim@samsung.com>
---
 arch/arm/mach-s5pv310/Kconfig                 |    7 +
 arch/arm/mach-s5pv310/Makefile                |    8 +-
 arch/arm/mach-s5pv310/include/mach/regs-mct.h |   47 ++++
 arch/arm/mach-s5pv310/mct.c                   |  365 +++++++++++++++++++++++++
 4 files changed, 426 insertions(+), 1 deletions(-)
 create mode 100644 arch/arm/mach-s5pv310/include/mach/regs-mct.h
 create mode 100644 arch/arm/mach-s5pv310/mct.c

diff --git a/arch/arm/mach-s5pv310/Kconfig b/arch/arm/mach-s5pv310/Kconfig
index e0cef3f..02d7b4d 100644
--- a/arch/arm/mach-s5pv310/Kconfig
+++ b/arch/arm/mach-s5pv310/Kconfig
@@ -14,6 +14,13 @@ config CPU_S5PV310
 	help
 	  Enable S5PV310 CPU support
 
+config S5PV310_MCT
+	bool "Kernel timer support by MCT"
+	depends on !LOCAL_TIMERS
+	help
+	  Use MCT (Multi Core Timer) as kernel timers
+	  NOTE: Can not choose this with LOCAL_TIMERS
+
 config S5PV310_DEV_PD
 	bool
 	help
diff --git a/arch/arm/mach-s5pv310/Makefile b/arch/arm/mach-s5pv310/Makefile
index e310609..08158e3 100644
--- a/arch/arm/mach-s5pv310/Makefile
+++ b/arch/arm/mach-s5pv310/Makefile
@@ -13,7 +13,13 @@ obj-				:=
 # Core support for S5PV310 system
 
 obj-$(CONFIG_CPU_S5PV310)	+= cpu.o init.o clock.o irq-combiner.o
-obj-$(CONFIG_CPU_S5PV310)	+= setup-i2c0.o time.o gpiolib.o irq-eint.o
+obj-$(CONFIG_CPU_S5PV310)	+= setup-i2c0.o gpiolib.o irq-eint.o
+
+ifeq ($(CONFIG_S5PV310_MCT),y)
+obj-y				+= mct.o
+else
+obj-y				+= time.o
+endif
 
 obj-$(CONFIG_SMP)		+= platsmp.o headsmp.o
 obj-$(CONFIG_LOCAL_TIMERS)	+= localtimer.o
diff --git a/arch/arm/mach-s5pv310/include/mach/regs-mct.h b/arch/arm/mach-s5pv310/include/mach/regs-mct.h
new file mode 100644
index 0000000..1480e2a
--- /dev/null
+++ b/arch/arm/mach-s5pv310/include/mach/regs-mct.h
@@ -0,0 +1,47 @@
+/* arch/arm/mach-s5pv310/include/mach/regs-mct.h
+ *
+ * Copyright (c) 2010 Samsung Electronics Co., Ltd.
+ *		http://www.samsung.com
+ *
+ * S5PV310 MCT(Multi-Core Timer) configutation
+ *
+ * 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.
+*/
+
+#ifndef __ASM_ARCH_REGS_MCT_H
+#define __ASM_ARCH_REGS_MCT_H __FILE__
+
+#include <mach/map.h>
+
+#define S5PV310_MCTREG(x)		(S5P_VA_SYSTIMER + (x))
+
+#define S5PV310_MCT_G_CNT_L		S5PV310_MCTREG(0x100)
+#define S5PV310_MCT_G_CNT_U		S5PV310_MCTREG(0x104)
+#define S5PV310_MCT_G_CNT_WSTAT		S5PV310_MCTREG(0x110)
+
+#define S5PV310_MCT_G_TCON		S5PV310_MCTREG(0x240)
+
+#define S5PV310_MCT_G_WSTAT		S5PV310_MCTREG(0x24C)
+
+#define S5PV310_MCT_L0_TCNTB		S5PV310_MCTREG(0x300)
+#define S5PV310_MCT_L0_ICNTB		S5PV310_MCTREG(0x308)
+#define S5PV310_MCT_L0_TCON		S5PV310_MCTREG(0x320)
+
+#define S5PV310_MCT_L0_INT_CSTAT	S5PV310_MCTREG(0x330)
+#define S5PV310_MCT_L0_INT_ENB		S5PV310_MCTREG(0x334)
+#define S5PV310_MCT_L0_WSTAT		S5PV310_MCTREG(0x340)
+
+#define S5PV310_MCT_L1_TCNTB		S5PV310_MCTREG(0x400)
+#define S5PV310_MCT_L1_ICNTB		S5PV310_MCTREG(0x408)
+#define S5PV310_MCT_L1_TCON		S5PV310_MCTREG(0x420)
+#define S5PV310_MCT_L1_INT_CSTAT	S5PV310_MCTREG(0x430)
+#define S5PV310_MCT_L1_INT_ENB		S5PV310_MCTREG(0x434)
+#define S5PV310_MCT_L1_WSTAT		S5PV310_MCTREG(0x440)
+
+#define MCT_L_TCON_INTERVAL_MODE	(1 << 2)
+#define MCT_L_TCON_INT_START		(1 << 1)
+#define MCT_L_TCON_TIMER_START		(1 << 0)
+
+#endif /* __ASM_ARCH_REGS_MCT_H */
diff --git a/arch/arm/mach-s5pv310/mct.c b/arch/arm/mach-s5pv310/mct.c
new file mode 100644
index 0000000..2fe189c
--- /dev/null
+++ b/arch/arm/mach-s5pv310/mct.c
@@ -0,0 +1,365 @@
+/* linux/arch/arm/mach-s5pv310/mct.c
+ *
+ * Copyright (c) 2010 Samsung Electronics Co., Ltd.
+ *		http://www.samsung.com
+ *
+ * S5PV310 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 <linux/sched.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/err.h>
+#include <linux/clk.h>
+#include <linux/clockchips.h>
+#include <linux/platform_device.h>
+#include <linux/delay.h>
+
+#include <mach/map.h>
+#include <mach/regs-mct.h>
+#include <asm/mach/time.h>
+
+static unsigned long clk_cnt_per_tick;
+static unsigned long clk_rate;
+
+enum mct_tick_type {
+	MCT_TICK0,
+	MCT_TICK1,
+};
+
+static void s5pv310_mct_write(unsigned int value, void *addr)
+{
+	void __iomem *stat_addr;
+	u32 mask;
+	u32 i;
+
+	__raw_writel(value, addr);
+
+	switch ((u32) addr) {
+	case (u32) S5PV310_MCT_G_TCON:
+		stat_addr = S5PV310_MCT_G_WSTAT;
+		mask = 1 << 16;		/* G_WSTAT_TCON_STAT */
+		break;
+
+	case (u32) S5PV310_MCT_G_CNT_L:
+		stat_addr = S5PV310_MCT_G_CNT_WSTAT;
+		mask = 1 << 0;		/* G_CNT_L_STAT */
+		break;
+
+	case (u32) S5PV310_MCT_G_CNT_U:
+		stat_addr = S5PV310_MCT_G_CNT_WSTAT;
+		mask = 1 << 1;		/* G_CNT_U_STAT */
+		break;
+
+	case (u32) S5PV310_MCT_L0_TCON:
+		stat_addr = S5PV310_MCT_L0_WSTAT;
+		mask = 1 << 3;		/* L_TCON_STAT */
+		break;
+
+	case (u32) S5PV310_MCT_L1_TCON:
+		stat_addr = S5PV310_MCT_L1_WSTAT;
+		mask = 1 << 3;		/* L_TCON_STAT */
+		break;
+
+	case (u32) S5PV310_MCT_L0_TCNTB:
+		stat_addr = S5PV310_MCT_L0_WSTAT;
+		mask = 1 << 0;		/* L_TCNTB_STAT	*/
+		break;
+
+	case (u32) S5PV310_MCT_L1_TCNTB:
+		stat_addr = S5PV310_MCT_L1_WSTAT;
+		mask = 1 << 0;		/* L_TCNTB_STAT	*/
+		break;
+
+	case (u32) S5PV310_MCT_L0_ICNTB:
+		stat_addr = S5PV310_MCT_L0_WSTAT;
+		mask = 1 << 1;		/* L_ICNTB_STAT */
+		break;
+
+	case (u32) S5PV310_MCT_L1_ICNTB:
+		stat_addr = S5PV310_MCT_L1_WSTAT;
+		mask = 1 << 1;		/* L_ICNTB_STAT */
+		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);
+}
+
+static void s5pv310_mct_tick_stop(enum mct_tick_type type)
+{
+	unsigned long tmp;
+	void __iomem *addr;
+
+	if (type == MCT_TICK0)
+		addr = S5PV310_MCT_L0_TCON;
+	else
+		addr = S5PV310_MCT_L1_TCON;
+
+	tmp = __raw_readl(addr);
+	tmp &= ~(MCT_L_TCON_INT_START | MCT_L_TCON_TIMER_START);
+	s5pv310_mct_write(tmp, addr);
+}
+
+static void s5pv310_mct_tick_start(enum mct_tick_type type,
+				   unsigned long cycles)
+{
+	unsigned long tmp;
+	unsigned int tmp1, tmp2;
+	void __iomem *addr;
+
+	s5pv310_mct_tick_stop(type);
+
+	tmp1 = (1 << 31) | cycles;	/* MCT_L_UPDATE_ICNTB */
+	tmp2 = 1 << 0;			/* L_INT_ENB_CNTIE */
+
+	if (type == MCT_TICK0) {
+		s5pv310_mct_write(tmp1, S5PV310_MCT_L0_ICNTB);
+		s5pv310_mct_write(tmp2, S5PV310_MCT_L0_INT_ENB);
+
+		addr = S5PV310_MCT_L0_TCON;
+	} else {
+		s5pv310_mct_write(tmp1, S5PV310_MCT_L1_ICNTB);
+		s5pv310_mct_write(tmp2, S5PV310_MCT_L1_INT_ENB);
+
+		addr = S5PV310_MCT_L1_TCON;
+	}
+
+	tmp = __raw_readl(addr);
+	tmp |= MCT_L_TCON_INT_START | MCT_L_TCON_TIMER_START |
+	       MCT_L_TCON_INTERVAL_MODE;
+	s5pv310_mct_write(tmp, addr);
+}
+
+static inline int s5pv310_tick_set_next_event(enum mct_tick_type type,
+					      unsigned long cycles)
+{
+	s5pv310_mct_tick_start(type, cycles);
+
+	return 0;
+}
+
+static inline void s5pv310_tick_set_mode(enum mct_tick_type type,
+					 enum clock_event_mode mode)
+{
+	s5pv310_mct_tick_stop(type);
+
+	switch (mode) {
+	case CLOCK_EVT_MODE_PERIODIC:
+		s5pv310_mct_tick_start(type, clk_cnt_per_tick);
+		break;
+
+	case CLOCK_EVT_MODE_ONESHOT:
+	case CLOCK_EVT_MODE_UNUSED:
+	case CLOCK_EVT_MODE_SHUTDOWN:
+	case CLOCK_EVT_MODE_RESUME:
+		break;
+	}
+}
+
+static int s5pv310_tick0_set_next_event(unsigned long cycles,
+					struct clock_event_device *evt)
+{
+	return s5pv310_tick_set_next_event(MCT_TICK0, cycles);
+}
+
+static int s5pv310_tick1_set_next_event(unsigned long cycles,
+					struct clock_event_device *evt)
+{
+	return s5pv310_tick_set_next_event(MCT_TICK1, cycles);
+}
+
+static void s5pv310_tick0_set_mode(enum clock_event_mode mode,
+				   struct clock_event_device *evt)
+{
+	s5pv310_tick_set_mode(MCT_TICK0, mode);
+}
+
+static void s5pv310_tick1_set_mode(enum clock_event_mode mode,
+				   struct clock_event_device *evt)
+{
+	s5pv310_tick_set_mode(MCT_TICK1, mode);
+}
+
+static struct clock_event_device mct_tick0_device = {
+	.name		= "mct-tick0",
+	.features	= CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT,
+	.rating		= 450,
+	.shift		= 20,
+	.set_next_event	= s5pv310_tick0_set_next_event,
+	.set_mode	= s5pv310_tick0_set_mode,
+};
+
+static struct clock_event_device mct_tick1_device = {
+	.name		= "mct-tick1",
+	.features	= CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT,
+	.rating		= 450,
+	.shift		= 20,
+	.set_next_event	= s5pv310_tick1_set_next_event,
+	.set_mode	= s5pv310_tick1_set_mode,
+};
+
+irqreturn_t s5pv310_mct0_event_isr(int irq, void *dev_id)
+{
+	struct clock_event_device *evt;
+
+	/* Clear the MCT tick interrupt */
+
+	evt = &mct_tick0_device;
+	if (evt->mode != CLOCK_EVT_MODE_PERIODIC)
+		s5pv310_mct_tick_stop(MCT_TICK0);
+
+	s5pv310_mct_write(0x1, S5PV310_MCT_L0_INT_CSTAT);
+
+	evt->event_handler(evt);
+
+	return IRQ_HANDLED;
+}
+
+irqreturn_t s5pv310_mct1_event_isr(int irq, void *dev_id)
+{
+	struct clock_event_device *evt;
+
+	/* Clear the MCT tick interrupt */
+
+	evt = &mct_tick1_device;
+
+	if (evt->mode != CLOCK_EVT_MODE_PERIODIC)
+		s5pv310_mct_tick_stop(MCT_TICK1);
+
+	s5pv310_mct_write(0x1, S5PV310_MCT_L1_INT_CSTAT);
+
+	evt->event_handler(evt);
+
+	return IRQ_HANDLED;
+}
+
+static struct irqaction mct_tick0_event_irq = {
+	.name		= "mct_tick0_irq",
+	.flags		= IRQF_TIMER | IRQF_IRQPOLL | IRQF_NOBALANCING,
+	.handler	= s5pv310_mct0_event_isr,
+};
+
+static struct irqaction mct_tick1_event_irq = {
+	.name		= "mct_tick1_irq",
+	.flags		= IRQF_TIMER | IRQF_NOBALANCING,
+	.handler	= s5pv310_mct1_event_isr,
+};
+
+static void s5pv310_mct_clockevent_init(struct clock_event_device *dev)
+{
+	dev->mult = div_sc(clk_rate / 2, NSEC_PER_SEC, dev->shift);
+
+	dev->max_delta_ns = clockevent_delta2ns(0xfffffff, dev);
+	dev->min_delta_ns = clockevent_delta2ns(0xf, dev);
+}
+
+static void __init s5pv310_clockevent0_init(void)
+{
+	clk_cnt_per_tick = clk_rate / 2 / HZ;
+
+	s5pv310_mct_write(0x1, S5PV310_MCT_L0_TCNTB);
+	s5pv310_mct_clockevent_init(&mct_tick0_device);
+	mct_tick0_device.cpumask = cpumask_of(0);
+	clockevents_register_device(&mct_tick0_device);
+
+	setup_irq(IRQ_MCT_L0, &mct_tick0_event_irq);
+}
+
+static void __init s5pv310_clockevent1_init(void *info)
+{
+	s5pv310_mct_write(0x1, S5PV310_MCT_L1_TCNTB);
+	s5pv310_mct_clockevent_init(&mct_tick1_device);
+	mct_tick1_device.cpumask = cpumask_of(1);
+	clockevents_register_device(&mct_tick1_device);
+
+	irq_set_affinity(IRQ_MCT1, cpumask_of(1));
+	setup_irq(IRQ_MCT_L1, &mct_tick1_event_irq);
+}
+
+static void s5pv310_mct_frc_start(unsigned int hi, unsigned int lo)
+{
+	unsigned int tmp;
+
+	s5pv310_mct_write(lo, S5PV310_MCT_G_CNT_L);
+	s5pv310_mct_write(hi, S5PV310_MCT_G_CNT_U);
+
+	tmp = __raw_readl(S5PV310_MCT_G_TCON);
+	tmp |= (1 << 8);	/* G_TCON_START */
+	s5pv310_mct_write(tmp, S5PV310_MCT_G_TCON);
+}
+
+/* Clocksource handling */
+
+static cycle_t s5pv310_frc_read(struct clocksource *cs)
+{
+	unsigned int lo, hi0, hi1;
+
+	hi0 = __raw_readl(S5PV310_MCT_G_CNT_U);
+	lo = __raw_readl(S5PV310_MCT_G_CNT_L);
+	dsb();
+	hi1 = __raw_readl(S5PV310_MCT_G_CNT_U);
+
+	if (hi0 != hi1) {
+		lo = __raw_readl(S5PV310_MCT_G_CNT_L);
+		hi1 = __raw_readl(S5PV310_MCT_G_CNT_U);
+	}
+
+	return ((cycle_t)hi1 << 32) | lo;
+}
+
+struct clocksource mct_frc = {
+	.name		= "mct-frc",
+	.rating		= 400,
+	.read		= s5pv310_frc_read,
+	.mask		= CLOCKSOURCE_MASK(64),
+	.flags		= CLOCK_SOURCE_IS_CONTINUOUS ,
+};
+
+static void __init s5pv310_clocksource_init(void)
+{
+	s5pv310_mct_frc_start(0, 0);
+
+	if (clocksource_register_hz(&mct_frc, clk_rate))
+		panic("%s: can't register clocksource\n", mct_frc.name);
+}
+
+static void __init s5pv310_timer_resources(void)
+{
+	struct clk *mct_clk;
+
+	mct_clk = clk_get(NULL, "xtal");
+
+	clk_rate = clk_get_rate(mct_clk);
+}
+
+static void __init s5pv310_timer_init(void)
+{
+	s5pv310_timer_resources();
+
+	s5pv310_clockevent0_init();
+	s5pv310_clocksource_init();
+}
+
+static int __init secondary_clockevent_init(void)
+{
+	return smp_call_function_single(1, s5pv310_clockevent1_init, NULL, 0);
+}
+arch_initcall(secondary_clockevent_init);
+
+struct sys_timer s5pv310_timer = {
+	.init		= s5pv310_timer_init,
+};
-- 
1.6.2.5

^ permalink raw reply related	[flat|nested] 14+ messages in thread

* [PATCH] ARM: S5PV310: Implement kernel timers using MCT
@ 2010-12-22 11:27 ` Kukjin Kim
  0 siblings, 0 replies; 14+ messages in thread
From: Kukjin Kim @ 2010-12-22 11:27 UTC (permalink / raw)
  To: linux-arm-kernel

From: Changhwan Youn <chaos.youn@samsung.com>

The Multi-Core Timer(MCT) of S5PV310 is designed for implementing
clock source timer and clock event timers. This patch implements
1 clock source timer with 64 bit free running counter of MCT and
2 clock event timers with two of 31-bit tick counters.

Signed-off-by: Changhwan Youn <chaos.youn@samsung.com>
Cc: Ben Dooks <ben-linux@fluff.org>
Cc: Russell King <rmk+kernel@arm.linux.org.uk>
Signed-off-by: Kukjin Kim <kgene.kim@samsung.com>
---
 arch/arm/mach-s5pv310/Kconfig                 |    7 +
 arch/arm/mach-s5pv310/Makefile                |    8 +-
 arch/arm/mach-s5pv310/include/mach/regs-mct.h |   47 ++++
 arch/arm/mach-s5pv310/mct.c                   |  365 +++++++++++++++++++++++++
 4 files changed, 426 insertions(+), 1 deletions(-)
 create mode 100644 arch/arm/mach-s5pv310/include/mach/regs-mct.h
 create mode 100644 arch/arm/mach-s5pv310/mct.c

diff --git a/arch/arm/mach-s5pv310/Kconfig b/arch/arm/mach-s5pv310/Kconfig
index e0cef3f..02d7b4d 100644
--- a/arch/arm/mach-s5pv310/Kconfig
+++ b/arch/arm/mach-s5pv310/Kconfig
@@ -14,6 +14,13 @@ config CPU_S5PV310
 	help
 	  Enable S5PV310 CPU support
 
+config S5PV310_MCT
+	bool "Kernel timer support by MCT"
+	depends on !LOCAL_TIMERS
+	help
+	  Use MCT (Multi Core Timer) as kernel timers
+	  NOTE: Can not choose this with LOCAL_TIMERS
+
 config S5PV310_DEV_PD
 	bool
 	help
diff --git a/arch/arm/mach-s5pv310/Makefile b/arch/arm/mach-s5pv310/Makefile
index e310609..08158e3 100644
--- a/arch/arm/mach-s5pv310/Makefile
+++ b/arch/arm/mach-s5pv310/Makefile
@@ -13,7 +13,13 @@ obj-				:=
 # Core support for S5PV310 system
 
 obj-$(CONFIG_CPU_S5PV310)	+= cpu.o init.o clock.o irq-combiner.o
-obj-$(CONFIG_CPU_S5PV310)	+= setup-i2c0.o time.o gpiolib.o irq-eint.o
+obj-$(CONFIG_CPU_S5PV310)	+= setup-i2c0.o gpiolib.o irq-eint.o
+
+ifeq ($(CONFIG_S5PV310_MCT),y)
+obj-y				+= mct.o
+else
+obj-y				+= time.o
+endif
 
 obj-$(CONFIG_SMP)		+= platsmp.o headsmp.o
 obj-$(CONFIG_LOCAL_TIMERS)	+= localtimer.o
diff --git a/arch/arm/mach-s5pv310/include/mach/regs-mct.h b/arch/arm/mach-s5pv310/include/mach/regs-mct.h
new file mode 100644
index 0000000..1480e2a
--- /dev/null
+++ b/arch/arm/mach-s5pv310/include/mach/regs-mct.h
@@ -0,0 +1,47 @@
+/* arch/arm/mach-s5pv310/include/mach/regs-mct.h
+ *
+ * Copyright (c) 2010 Samsung Electronics Co., Ltd.
+ *		http://www.samsung.com
+ *
+ * S5PV310 MCT(Multi-Core Timer) configutation
+ *
+ * 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.
+*/
+
+#ifndef __ASM_ARCH_REGS_MCT_H
+#define __ASM_ARCH_REGS_MCT_H __FILE__
+
+#include <mach/map.h>
+
+#define S5PV310_MCTREG(x)		(S5P_VA_SYSTIMER + (x))
+
+#define S5PV310_MCT_G_CNT_L		S5PV310_MCTREG(0x100)
+#define S5PV310_MCT_G_CNT_U		S5PV310_MCTREG(0x104)
+#define S5PV310_MCT_G_CNT_WSTAT		S5PV310_MCTREG(0x110)
+
+#define S5PV310_MCT_G_TCON		S5PV310_MCTREG(0x240)
+
+#define S5PV310_MCT_G_WSTAT		S5PV310_MCTREG(0x24C)
+
+#define S5PV310_MCT_L0_TCNTB		S5PV310_MCTREG(0x300)
+#define S5PV310_MCT_L0_ICNTB		S5PV310_MCTREG(0x308)
+#define S5PV310_MCT_L0_TCON		S5PV310_MCTREG(0x320)
+
+#define S5PV310_MCT_L0_INT_CSTAT	S5PV310_MCTREG(0x330)
+#define S5PV310_MCT_L0_INT_ENB		S5PV310_MCTREG(0x334)
+#define S5PV310_MCT_L0_WSTAT		S5PV310_MCTREG(0x340)
+
+#define S5PV310_MCT_L1_TCNTB		S5PV310_MCTREG(0x400)
+#define S5PV310_MCT_L1_ICNTB		S5PV310_MCTREG(0x408)
+#define S5PV310_MCT_L1_TCON		S5PV310_MCTREG(0x420)
+#define S5PV310_MCT_L1_INT_CSTAT	S5PV310_MCTREG(0x430)
+#define S5PV310_MCT_L1_INT_ENB		S5PV310_MCTREG(0x434)
+#define S5PV310_MCT_L1_WSTAT		S5PV310_MCTREG(0x440)
+
+#define MCT_L_TCON_INTERVAL_MODE	(1 << 2)
+#define MCT_L_TCON_INT_START		(1 << 1)
+#define MCT_L_TCON_TIMER_START		(1 << 0)
+
+#endif /* __ASM_ARCH_REGS_MCT_H */
diff --git a/arch/arm/mach-s5pv310/mct.c b/arch/arm/mach-s5pv310/mct.c
new file mode 100644
index 0000000..2fe189c
--- /dev/null
+++ b/arch/arm/mach-s5pv310/mct.c
@@ -0,0 +1,365 @@
+/* linux/arch/arm/mach-s5pv310/mct.c
+ *
+ * Copyright (c) 2010 Samsung Electronics Co., Ltd.
+ *		http://www.samsung.com
+ *
+ * S5PV310 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 <linux/sched.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/err.h>
+#include <linux/clk.h>
+#include <linux/clockchips.h>
+#include <linux/platform_device.h>
+#include <linux/delay.h>
+
+#include <mach/map.h>
+#include <mach/regs-mct.h>
+#include <asm/mach/time.h>
+
+static unsigned long clk_cnt_per_tick;
+static unsigned long clk_rate;
+
+enum mct_tick_type {
+	MCT_TICK0,
+	MCT_TICK1,
+};
+
+static void s5pv310_mct_write(unsigned int value, void *addr)
+{
+	void __iomem *stat_addr;
+	u32 mask;
+	u32 i;
+
+	__raw_writel(value, addr);
+
+	switch ((u32) addr) {
+	case (u32) S5PV310_MCT_G_TCON:
+		stat_addr = S5PV310_MCT_G_WSTAT;
+		mask = 1 << 16;		/* G_WSTAT_TCON_STAT */
+		break;
+
+	case (u32) S5PV310_MCT_G_CNT_L:
+		stat_addr = S5PV310_MCT_G_CNT_WSTAT;
+		mask = 1 << 0;		/* G_CNT_L_STAT */
+		break;
+
+	case (u32) S5PV310_MCT_G_CNT_U:
+		stat_addr = S5PV310_MCT_G_CNT_WSTAT;
+		mask = 1 << 1;		/* G_CNT_U_STAT */
+		break;
+
+	case (u32) S5PV310_MCT_L0_TCON:
+		stat_addr = S5PV310_MCT_L0_WSTAT;
+		mask = 1 << 3;		/* L_TCON_STAT */
+		break;
+
+	case (u32) S5PV310_MCT_L1_TCON:
+		stat_addr = S5PV310_MCT_L1_WSTAT;
+		mask = 1 << 3;		/* L_TCON_STAT */
+		break;
+
+	case (u32) S5PV310_MCT_L0_TCNTB:
+		stat_addr = S5PV310_MCT_L0_WSTAT;
+		mask = 1 << 0;		/* L_TCNTB_STAT	*/
+		break;
+
+	case (u32) S5PV310_MCT_L1_TCNTB:
+		stat_addr = S5PV310_MCT_L1_WSTAT;
+		mask = 1 << 0;		/* L_TCNTB_STAT	*/
+		break;
+
+	case (u32) S5PV310_MCT_L0_ICNTB:
+		stat_addr = S5PV310_MCT_L0_WSTAT;
+		mask = 1 << 1;		/* L_ICNTB_STAT */
+		break;
+
+	case (u32) S5PV310_MCT_L1_ICNTB:
+		stat_addr = S5PV310_MCT_L1_WSTAT;
+		mask = 1 << 1;		/* L_ICNTB_STAT */
+		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);
+}
+
+static void s5pv310_mct_tick_stop(enum mct_tick_type type)
+{
+	unsigned long tmp;
+	void __iomem *addr;
+
+	if (type == MCT_TICK0)
+		addr = S5PV310_MCT_L0_TCON;
+	else
+		addr = S5PV310_MCT_L1_TCON;
+
+	tmp = __raw_readl(addr);
+	tmp &= ~(MCT_L_TCON_INT_START | MCT_L_TCON_TIMER_START);
+	s5pv310_mct_write(tmp, addr);
+}
+
+static void s5pv310_mct_tick_start(enum mct_tick_type type,
+				   unsigned long cycles)
+{
+	unsigned long tmp;
+	unsigned int tmp1, tmp2;
+	void __iomem *addr;
+
+	s5pv310_mct_tick_stop(type);
+
+	tmp1 = (1 << 31) | cycles;	/* MCT_L_UPDATE_ICNTB */
+	tmp2 = 1 << 0;			/* L_INT_ENB_CNTIE */
+
+	if (type == MCT_TICK0) {
+		s5pv310_mct_write(tmp1, S5PV310_MCT_L0_ICNTB);
+		s5pv310_mct_write(tmp2, S5PV310_MCT_L0_INT_ENB);
+
+		addr = S5PV310_MCT_L0_TCON;
+	} else {
+		s5pv310_mct_write(tmp1, S5PV310_MCT_L1_ICNTB);
+		s5pv310_mct_write(tmp2, S5PV310_MCT_L1_INT_ENB);
+
+		addr = S5PV310_MCT_L1_TCON;
+	}
+
+	tmp = __raw_readl(addr);
+	tmp |= MCT_L_TCON_INT_START | MCT_L_TCON_TIMER_START |
+	       MCT_L_TCON_INTERVAL_MODE;
+	s5pv310_mct_write(tmp, addr);
+}
+
+static inline int s5pv310_tick_set_next_event(enum mct_tick_type type,
+					      unsigned long cycles)
+{
+	s5pv310_mct_tick_start(type, cycles);
+
+	return 0;
+}
+
+static inline void s5pv310_tick_set_mode(enum mct_tick_type type,
+					 enum clock_event_mode mode)
+{
+	s5pv310_mct_tick_stop(type);
+
+	switch (mode) {
+	case CLOCK_EVT_MODE_PERIODIC:
+		s5pv310_mct_tick_start(type, clk_cnt_per_tick);
+		break;
+
+	case CLOCK_EVT_MODE_ONESHOT:
+	case CLOCK_EVT_MODE_UNUSED:
+	case CLOCK_EVT_MODE_SHUTDOWN:
+	case CLOCK_EVT_MODE_RESUME:
+		break;
+	}
+}
+
+static int s5pv310_tick0_set_next_event(unsigned long cycles,
+					struct clock_event_device *evt)
+{
+	return s5pv310_tick_set_next_event(MCT_TICK0, cycles);
+}
+
+static int s5pv310_tick1_set_next_event(unsigned long cycles,
+					struct clock_event_device *evt)
+{
+	return s5pv310_tick_set_next_event(MCT_TICK1, cycles);
+}
+
+static void s5pv310_tick0_set_mode(enum clock_event_mode mode,
+				   struct clock_event_device *evt)
+{
+	s5pv310_tick_set_mode(MCT_TICK0, mode);
+}
+
+static void s5pv310_tick1_set_mode(enum clock_event_mode mode,
+				   struct clock_event_device *evt)
+{
+	s5pv310_tick_set_mode(MCT_TICK1, mode);
+}
+
+static struct clock_event_device mct_tick0_device = {
+	.name		= "mct-tick0",
+	.features	= CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT,
+	.rating		= 450,
+	.shift		= 20,
+	.set_next_event	= s5pv310_tick0_set_next_event,
+	.set_mode	= s5pv310_tick0_set_mode,
+};
+
+static struct clock_event_device mct_tick1_device = {
+	.name		= "mct-tick1",
+	.features	= CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT,
+	.rating		= 450,
+	.shift		= 20,
+	.set_next_event	= s5pv310_tick1_set_next_event,
+	.set_mode	= s5pv310_tick1_set_mode,
+};
+
+irqreturn_t s5pv310_mct0_event_isr(int irq, void *dev_id)
+{
+	struct clock_event_device *evt;
+
+	/* Clear the MCT tick interrupt */
+
+	evt = &mct_tick0_device;
+	if (evt->mode != CLOCK_EVT_MODE_PERIODIC)
+		s5pv310_mct_tick_stop(MCT_TICK0);
+
+	s5pv310_mct_write(0x1, S5PV310_MCT_L0_INT_CSTAT);
+
+	evt->event_handler(evt);
+
+	return IRQ_HANDLED;
+}
+
+irqreturn_t s5pv310_mct1_event_isr(int irq, void *dev_id)
+{
+	struct clock_event_device *evt;
+
+	/* Clear the MCT tick interrupt */
+
+	evt = &mct_tick1_device;
+
+	if (evt->mode != CLOCK_EVT_MODE_PERIODIC)
+		s5pv310_mct_tick_stop(MCT_TICK1);
+
+	s5pv310_mct_write(0x1, S5PV310_MCT_L1_INT_CSTAT);
+
+	evt->event_handler(evt);
+
+	return IRQ_HANDLED;
+}
+
+static struct irqaction mct_tick0_event_irq = {
+	.name		= "mct_tick0_irq",
+	.flags		= IRQF_TIMER | IRQF_IRQPOLL | IRQF_NOBALANCING,
+	.handler	= s5pv310_mct0_event_isr,
+};
+
+static struct irqaction mct_tick1_event_irq = {
+	.name		= "mct_tick1_irq",
+	.flags		= IRQF_TIMER | IRQF_NOBALANCING,
+	.handler	= s5pv310_mct1_event_isr,
+};
+
+static void s5pv310_mct_clockevent_init(struct clock_event_device *dev)
+{
+	dev->mult = div_sc(clk_rate / 2, NSEC_PER_SEC, dev->shift);
+
+	dev->max_delta_ns = clockevent_delta2ns(0xfffffff, dev);
+	dev->min_delta_ns = clockevent_delta2ns(0xf, dev);
+}
+
+static void __init s5pv310_clockevent0_init(void)
+{
+	clk_cnt_per_tick = clk_rate / 2 / HZ;
+
+	s5pv310_mct_write(0x1, S5PV310_MCT_L0_TCNTB);
+	s5pv310_mct_clockevent_init(&mct_tick0_device);
+	mct_tick0_device.cpumask = cpumask_of(0);
+	clockevents_register_device(&mct_tick0_device);
+
+	setup_irq(IRQ_MCT_L0, &mct_tick0_event_irq);
+}
+
+static void __init s5pv310_clockevent1_init(void *info)
+{
+	s5pv310_mct_write(0x1, S5PV310_MCT_L1_TCNTB);
+	s5pv310_mct_clockevent_init(&mct_tick1_device);
+	mct_tick1_device.cpumask = cpumask_of(1);
+	clockevents_register_device(&mct_tick1_device);
+
+	irq_set_affinity(IRQ_MCT1, cpumask_of(1));
+	setup_irq(IRQ_MCT_L1, &mct_tick1_event_irq);
+}
+
+static void s5pv310_mct_frc_start(unsigned int hi, unsigned int lo)
+{
+	unsigned int tmp;
+
+	s5pv310_mct_write(lo, S5PV310_MCT_G_CNT_L);
+	s5pv310_mct_write(hi, S5PV310_MCT_G_CNT_U);
+
+	tmp = __raw_readl(S5PV310_MCT_G_TCON);
+	tmp |= (1 << 8);	/* G_TCON_START */
+	s5pv310_mct_write(tmp, S5PV310_MCT_G_TCON);
+}
+
+/* Clocksource handling */
+
+static cycle_t s5pv310_frc_read(struct clocksource *cs)
+{
+	unsigned int lo, hi0, hi1;
+
+	hi0 = __raw_readl(S5PV310_MCT_G_CNT_U);
+	lo = __raw_readl(S5PV310_MCT_G_CNT_L);
+	dsb();
+	hi1 = __raw_readl(S5PV310_MCT_G_CNT_U);
+
+	if (hi0 != hi1) {
+		lo = __raw_readl(S5PV310_MCT_G_CNT_L);
+		hi1 = __raw_readl(S5PV310_MCT_G_CNT_U);
+	}
+
+	return ((cycle_t)hi1 << 32) | lo;
+}
+
+struct clocksource mct_frc = {
+	.name		= "mct-frc",
+	.rating		= 400,
+	.read		= s5pv310_frc_read,
+	.mask		= CLOCKSOURCE_MASK(64),
+	.flags		= CLOCK_SOURCE_IS_CONTINUOUS ,
+};
+
+static void __init s5pv310_clocksource_init(void)
+{
+	s5pv310_mct_frc_start(0, 0);
+
+	if (clocksource_register_hz(&mct_frc, clk_rate))
+		panic("%s: can't register clocksource\n", mct_frc.name);
+}
+
+static void __init s5pv310_timer_resources(void)
+{
+	struct clk *mct_clk;
+
+	mct_clk = clk_get(NULL, "xtal");
+
+	clk_rate = clk_get_rate(mct_clk);
+}
+
+static void __init s5pv310_timer_init(void)
+{
+	s5pv310_timer_resources();
+
+	s5pv310_clockevent0_init();
+	s5pv310_clocksource_init();
+}
+
+static int __init secondary_clockevent_init(void)
+{
+	return smp_call_function_single(1, s5pv310_clockevent1_init, NULL, 0);
+}
+arch_initcall(secondary_clockevent_init);
+
+struct sys_timer s5pv310_timer = {
+	.init		= s5pv310_timer_init,
+};
-- 
1.6.2.5

^ permalink raw reply related	[flat|nested] 14+ messages in thread

* Re: [PATCH] ARM: S5PV310: Implement kernel timers using MCT
  2010-12-22 11:27 ` Kukjin Kim
@ 2010-12-22 12:35   ` Jamie Iles
  -1 siblings, 0 replies; 14+ messages in thread
From: Jamie Iles @ 2010-12-22 12:35 UTC (permalink / raw)
  To: Kukjin Kim
  Cc: linux-arm-kernel, linux-samsung-soc, Russell King,
	Changhwan Youn, ben-linux

Hi Kukjin,

Just trying to learn what other people are doing in their platforms, so 
apologies if my comments aren't applicable! Looks good to me though.

On Wed, Dec 22, 2010 at 08:27:01PM +0900, Kukjin Kim wrote:
> From: Changhwan Youn <chaos.youn@samsung.com>
> 
> The Multi-Core Timer(MCT) of S5PV310 is designed for implementing
> clock source timer and clock event timers. This patch implements
> 1 clock source timer with 64 bit free running counter of MCT and
> 2 clock event timers with two of 31-bit tick counters.
> 
> Signed-off-by: Changhwan Youn <chaos.youn@samsung.com>
> Cc: Ben Dooks <ben-linux@fluff.org>
> Cc: Russell King <rmk+kernel@arm.linux.org.uk>
> Signed-off-by: Kukjin Kim <kgene.kim@samsung.com>
> ---
>  arch/arm/mach-s5pv310/Kconfig                 |    7 +
>  arch/arm/mach-s5pv310/Makefile                |    8 +-
>  arch/arm/mach-s5pv310/include/mach/regs-mct.h |   47 ++++
>  arch/arm/mach-s5pv310/mct.c                   |  365 +++++++++++++++++++++++++
>  4 files changed, 426 insertions(+), 1 deletions(-)
>  create mode 100644 arch/arm/mach-s5pv310/include/mach/regs-mct.h
>  create mode 100644 arch/arm/mach-s5pv310/mct.c
[...]
> diff --git a/arch/arm/mach-s5pv310/mct.c b/arch/arm/mach-s5pv310/mct.c
> new file mode 100644
> index 0000000..2fe189c
> --- /dev/null
> +++ b/arch/arm/mach-s5pv310/mct.c
[...]
> +static struct clock_event_device mct_tick0_device = {
> +	.name		= "mct-tick0",
> +	.features	= CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT,
> +	.rating		= 450,
> +	.shift		= 20,
> +	.set_next_event	= s5pv310_tick0_set_next_event,
> +	.set_mode	= s5pv310_tick0_set_mode,
> +};
> +
> +static struct clock_event_device mct_tick1_device = {
> +	.name		= "mct-tick1",
> +	.features	= CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT,
> +	.rating		= 450,
> +	.shift		= 20,
> +	.set_next_event	= s5pv310_tick1_set_next_event,
> +	.set_mode	= s5pv310_tick1_set_mode,
> +};

Do you need to specify .shift here? Could you use 
clockevents_calc_mult_shift() to calculate mult and shift for you?

> +irqreturn_t s5pv310_mct0_event_isr(int irq, void *dev_id)
> +{
> +	struct clock_event_device *evt;
> +
> +	/* Clear the MCT tick interrupt */
> +
> +	evt = &mct_tick0_device;
> +	if (evt->mode != CLOCK_EVT_MODE_PERIODIC)
> +		s5pv310_mct_tick_stop(MCT_TICK0);

I don't think you need to stop the timer here, the clockevents layer 
will handle this for you.

[...]
> +static void s5pv310_mct_clockevent_init(struct clock_event_device *dev)
> +{
> +	dev->mult = div_sc(clk_rate / 2, NSEC_PER_SEC, dev->shift);
> +
> +	dev->max_delta_ns = clockevent_delta2ns(0xfffffff, dev);

Is there a reason that the delta is only using 28 bits here?

Jamie

^ permalink raw reply	[flat|nested] 14+ messages in thread

* [PATCH] ARM: S5PV310: Implement kernel timers using MCT
@ 2010-12-22 12:35   ` Jamie Iles
  0 siblings, 0 replies; 14+ messages in thread
From: Jamie Iles @ 2010-12-22 12:35 UTC (permalink / raw)
  To: linux-arm-kernel

Hi Kukjin,

Just trying to learn what other people are doing in their platforms, so 
apologies if my comments aren't applicable! Looks good to me though.

On Wed, Dec 22, 2010 at 08:27:01PM +0900, Kukjin Kim wrote:
> From: Changhwan Youn <chaos.youn@samsung.com>
> 
> The Multi-Core Timer(MCT) of S5PV310 is designed for implementing
> clock source timer and clock event timers. This patch implements
> 1 clock source timer with 64 bit free running counter of MCT and
> 2 clock event timers with two of 31-bit tick counters.
> 
> Signed-off-by: Changhwan Youn <chaos.youn@samsung.com>
> Cc: Ben Dooks <ben-linux@fluff.org>
> Cc: Russell King <rmk+kernel@arm.linux.org.uk>
> Signed-off-by: Kukjin Kim <kgene.kim@samsung.com>
> ---
>  arch/arm/mach-s5pv310/Kconfig                 |    7 +
>  arch/arm/mach-s5pv310/Makefile                |    8 +-
>  arch/arm/mach-s5pv310/include/mach/regs-mct.h |   47 ++++
>  arch/arm/mach-s5pv310/mct.c                   |  365 +++++++++++++++++++++++++
>  4 files changed, 426 insertions(+), 1 deletions(-)
>  create mode 100644 arch/arm/mach-s5pv310/include/mach/regs-mct.h
>  create mode 100644 arch/arm/mach-s5pv310/mct.c
[...]
> diff --git a/arch/arm/mach-s5pv310/mct.c b/arch/arm/mach-s5pv310/mct.c
> new file mode 100644
> index 0000000..2fe189c
> --- /dev/null
> +++ b/arch/arm/mach-s5pv310/mct.c
[...]
> +static struct clock_event_device mct_tick0_device = {
> +	.name		= "mct-tick0",
> +	.features	= CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT,
> +	.rating		= 450,
> +	.shift		= 20,
> +	.set_next_event	= s5pv310_tick0_set_next_event,
> +	.set_mode	= s5pv310_tick0_set_mode,
> +};
> +
> +static struct clock_event_device mct_tick1_device = {
> +	.name		= "mct-tick1",
> +	.features	= CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT,
> +	.rating		= 450,
> +	.shift		= 20,
> +	.set_next_event	= s5pv310_tick1_set_next_event,
> +	.set_mode	= s5pv310_tick1_set_mode,
> +};

Do you need to specify .shift here? Could you use 
clockevents_calc_mult_shift() to calculate mult and shift for you?

> +irqreturn_t s5pv310_mct0_event_isr(int irq, void *dev_id)
> +{
> +	struct clock_event_device *evt;
> +
> +	/* Clear the MCT tick interrupt */
> +
> +	evt = &mct_tick0_device;
> +	if (evt->mode != CLOCK_EVT_MODE_PERIODIC)
> +		s5pv310_mct_tick_stop(MCT_TICK0);

I don't think you need to stop the timer here, the clockevents layer 
will handle this for you.

[...]
> +static void s5pv310_mct_clockevent_init(struct clock_event_device *dev)
> +{
> +	dev->mult = div_sc(clk_rate / 2, NSEC_PER_SEC, dev->shift);
> +
> +	dev->max_delta_ns = clockevent_delta2ns(0xfffffff, dev);

Is there a reason that the delta is only using 28 bits here?

Jamie

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH] ARM: S5PV310: Implement kernel timers using MCT
  2010-12-22 11:27 ` Kukjin Kim
@ 2010-12-22 22:55   ` Russell King - ARM Linux
  -1 siblings, 0 replies; 14+ messages in thread
From: Russell King - ARM Linux @ 2010-12-22 22:55 UTC (permalink / raw)
  To: Kukjin Kim; +Cc: linux-arm-kernel, linux-samsung-soc, ben-linux, Changhwan Youn

On Wed, Dec 22, 2010 at 08:27:01PM +0900, Kukjin Kim wrote:
> From: Changhwan Youn <chaos.youn@samsung.com>
> 
> The Multi-Core Timer(MCT) of S5PV310 is designed for implementing
> clock source timer and clock event timers. This patch implements
> 1 clock source timer with 64 bit free running counter of MCT and
> 2 clock event timers with two of 31-bit tick counters.
> 
> Signed-off-by: Changhwan Youn <chaos.youn@samsung.com>
> Cc: Ben Dooks <ben-linux@fluff.org>
> Cc: Russell King <rmk+kernel@arm.linux.org.uk>
> Signed-off-by: Kukjin Kim <kgene.kim@samsung.com>
> ---
>  arch/arm/mach-s5pv310/Kconfig                 |    7 +
>  arch/arm/mach-s5pv310/Makefile                |    8 +-
>  arch/arm/mach-s5pv310/include/mach/regs-mct.h |   47 ++++
>  arch/arm/mach-s5pv310/mct.c                   |  365 +++++++++++++++++++++++++
>  4 files changed, 426 insertions(+), 1 deletions(-)
>  create mode 100644 arch/arm/mach-s5pv310/include/mach/regs-mct.h
>  create mode 100644 arch/arm/mach-s5pv310/mct.c
> 
> diff --git a/arch/arm/mach-s5pv310/Kconfig b/arch/arm/mach-s5pv310/Kconfig
> index e0cef3f..02d7b4d 100644
> --- a/arch/arm/mach-s5pv310/Kconfig
> +++ b/arch/arm/mach-s5pv310/Kconfig
> @@ -14,6 +14,13 @@ config CPU_S5PV310
>  	help
>  	  Enable S5PV310 CPU support
>  
> +config S5PV310_MCT
> +	bool "Kernel timer support by MCT"
> +	depends on !LOCAL_TIMERS
> +	help
> +	  Use MCT (Multi Core Timer) as kernel timers
> +	  NOTE: Can not choose this with LOCAL_TIMERS
> +
>  config S5PV310_DEV_PD
>  	bool
>  	help
> diff --git a/arch/arm/mach-s5pv310/Makefile b/arch/arm/mach-s5pv310/Makefile
> index e310609..08158e3 100644
> --- a/arch/arm/mach-s5pv310/Makefile
> +++ b/arch/arm/mach-s5pv310/Makefile
> @@ -13,7 +13,13 @@ obj-				:=
>  # Core support for S5PV310 system
>  
>  obj-$(CONFIG_CPU_S5PV310)	+= cpu.o init.o clock.o irq-combiner.o
> -obj-$(CONFIG_CPU_S5PV310)	+= setup-i2c0.o time.o gpiolib.o irq-eint.o
> +obj-$(CONFIG_CPU_S5PV310)	+= setup-i2c0.o gpiolib.o irq-eint.o
> +
> +ifeq ($(CONFIG_S5PV310_MCT),y)
> +obj-y				+= mct.o
> +else
> +obj-y				+= time.o
> +endif
>  
>  obj-$(CONFIG_SMP)		+= platsmp.o headsmp.o
>  obj-$(CONFIG_LOCAL_TIMERS)	+= localtimer.o
> diff --git a/arch/arm/mach-s5pv310/include/mach/regs-mct.h b/arch/arm/mach-s5pv310/include/mach/regs-mct.h
> new file mode 100644
> index 0000000..1480e2a
> --- /dev/null
> +++ b/arch/arm/mach-s5pv310/include/mach/regs-mct.h
> @@ -0,0 +1,47 @@
> +/* arch/arm/mach-s5pv310/include/mach/regs-mct.h
> + *
> + * Copyright (c) 2010 Samsung Electronics Co., Ltd.
> + *		http://www.samsung.com
> + *
> + * S5PV310 MCT(Multi-Core Timer) configutation
> + *
> + * 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.
> +*/
> +
> +#ifndef __ASM_ARCH_REGS_MCT_H
> +#define __ASM_ARCH_REGS_MCT_H __FILE__
> +
> +#include <mach/map.h>
> +
> +#define S5PV310_MCTREG(x)		(S5P_VA_SYSTIMER + (x))
> +
> +#define S5PV310_MCT_G_CNT_L		S5PV310_MCTREG(0x100)
> +#define S5PV310_MCT_G_CNT_U		S5PV310_MCTREG(0x104)
> +#define S5PV310_MCT_G_CNT_WSTAT		S5PV310_MCTREG(0x110)
> +
> +#define S5PV310_MCT_G_TCON		S5PV310_MCTREG(0x240)
> +
> +#define S5PV310_MCT_G_WSTAT		S5PV310_MCTREG(0x24C)
> +
> +#define S5PV310_MCT_L0_TCNTB		S5PV310_MCTREG(0x300)
> +#define S5PV310_MCT_L0_ICNTB		S5PV310_MCTREG(0x308)
> +#define S5PV310_MCT_L0_TCON		S5PV310_MCTREG(0x320)
> +
> +#define S5PV310_MCT_L0_INT_CSTAT	S5PV310_MCTREG(0x330)
> +#define S5PV310_MCT_L0_INT_ENB		S5PV310_MCTREG(0x334)
> +#define S5PV310_MCT_L0_WSTAT		S5PV310_MCTREG(0x340)
> +
> +#define S5PV310_MCT_L1_TCNTB		S5PV310_MCTREG(0x400)
> +#define S5PV310_MCT_L1_ICNTB		S5PV310_MCTREG(0x408)
> +#define S5PV310_MCT_L1_TCON		S5PV310_MCTREG(0x420)
> +#define S5PV310_MCT_L1_INT_CSTAT	S5PV310_MCTREG(0x430)
> +#define S5PV310_MCT_L1_INT_ENB		S5PV310_MCTREG(0x434)
> +#define S5PV310_MCT_L1_WSTAT		S5PV310_MCTREG(0x440)
> +
> +#define MCT_L_TCON_INTERVAL_MODE	(1 << 2)
> +#define MCT_L_TCON_INT_START		(1 << 1)
> +#define MCT_L_TCON_TIMER_START		(1 << 0)
> +
> +#endif /* __ASM_ARCH_REGS_MCT_H */
> diff --git a/arch/arm/mach-s5pv310/mct.c b/arch/arm/mach-s5pv310/mct.c
> new file mode 100644
> index 0000000..2fe189c
> --- /dev/null
> +++ b/arch/arm/mach-s5pv310/mct.c
> @@ -0,0 +1,365 @@
> +/* linux/arch/arm/mach-s5pv310/mct.c
> + *
> + * Copyright (c) 2010 Samsung Electronics Co., Ltd.
> + *		http://www.samsung.com
> + *
> + * S5PV310 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 <linux/sched.h>
> +#include <linux/interrupt.h>
> +#include <linux/irq.h>
> +#include <linux/err.h>
> +#include <linux/clk.h>
> +#include <linux/clockchips.h>
> +#include <linux/platform_device.h>
> +#include <linux/delay.h>
> +
> +#include <mach/map.h>
> +#include <mach/regs-mct.h>
> +#include <asm/mach/time.h>
> +
> +static unsigned long clk_cnt_per_tick;
> +static unsigned long clk_rate;
> +
> +enum mct_tick_type {
> +	MCT_TICK0,
> +	MCT_TICK1,
> +};
> +
> +static void s5pv310_mct_write(unsigned int value, void *addr)
> +{
> +	void __iomem *stat_addr;
> +	u32 mask;
> +	u32 i;
> +
> +	__raw_writel(value, addr);
> +
> +	switch ((u32) addr) {
> +	case (u32) S5PV310_MCT_G_TCON:
> +		stat_addr = S5PV310_MCT_G_WSTAT;
> +		mask = 1 << 16;		/* G_WSTAT_TCON_STAT */
> +		break;
> +
> +	case (u32) S5PV310_MCT_G_CNT_L:
> +		stat_addr = S5PV310_MCT_G_CNT_WSTAT;
> +		mask = 1 << 0;		/* G_CNT_L_STAT */
> +		break;
> +
> +	case (u32) S5PV310_MCT_G_CNT_U:
> +		stat_addr = S5PV310_MCT_G_CNT_WSTAT;
> +		mask = 1 << 1;		/* G_CNT_U_STAT */
> +		break;
> +
> +	case (u32) S5PV310_MCT_L0_TCON:
> +		stat_addr = S5PV310_MCT_L0_WSTAT;
> +		mask = 1 << 3;		/* L_TCON_STAT */
> +		break;
> +
> +	case (u32) S5PV310_MCT_L1_TCON:
> +		stat_addr = S5PV310_MCT_L1_WSTAT;
> +		mask = 1 << 3;		/* L_TCON_STAT */
> +		break;
> +
> +	case (u32) S5PV310_MCT_L0_TCNTB:
> +		stat_addr = S5PV310_MCT_L0_WSTAT;
> +		mask = 1 << 0;		/* L_TCNTB_STAT	*/
> +		break;
> +
> +	case (u32) S5PV310_MCT_L1_TCNTB:
> +		stat_addr = S5PV310_MCT_L1_WSTAT;
> +		mask = 1 << 0;		/* L_TCNTB_STAT	*/
> +		break;
> +
> +	case (u32) S5PV310_MCT_L0_ICNTB:
> +		stat_addr = S5PV310_MCT_L0_WSTAT;
> +		mask = 1 << 1;		/* L_ICNTB_STAT */
> +		break;
> +
> +	case (u32) S5PV310_MCT_L1_ICNTB:
> +		stat_addr = S5PV310_MCT_L1_WSTAT;
> +		mask = 1 << 1;		/* L_ICNTB_STAT */
> +		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);
> +}
> +
> +static void s5pv310_mct_tick_stop(enum mct_tick_type type)
> +{
> +	unsigned long tmp;
> +	void __iomem *addr;
> +
> +	if (type == MCT_TICK0)
> +		addr = S5PV310_MCT_L0_TCON;
> +	else
> +		addr = S5PV310_MCT_L1_TCON;
> +
> +	tmp = __raw_readl(addr);
> +	tmp &= ~(MCT_L_TCON_INT_START | MCT_L_TCON_TIMER_START);
> +	s5pv310_mct_write(tmp, addr);
> +}
> +
> +static void s5pv310_mct_tick_start(enum mct_tick_type type,
> +				   unsigned long cycles)
> +{
> +	unsigned long tmp;
> +	unsigned int tmp1, tmp2;
> +	void __iomem *addr;
> +
> +	s5pv310_mct_tick_stop(type);
> +
> +	tmp1 = (1 << 31) | cycles;	/* MCT_L_UPDATE_ICNTB */
> +	tmp2 = 1 << 0;			/* L_INT_ENB_CNTIE */
> +
> +	if (type == MCT_TICK0) {
> +		s5pv310_mct_write(tmp1, S5PV310_MCT_L0_ICNTB);
> +		s5pv310_mct_write(tmp2, S5PV310_MCT_L0_INT_ENB);
> +
> +		addr = S5PV310_MCT_L0_TCON;
> +	} else {
> +		s5pv310_mct_write(tmp1, S5PV310_MCT_L1_ICNTB);
> +		s5pv310_mct_write(tmp2, S5PV310_MCT_L1_INT_ENB);
> +
> +		addr = S5PV310_MCT_L1_TCON;
> +	}
> +
> +	tmp = __raw_readl(addr);
> +	tmp |= MCT_L_TCON_INT_START | MCT_L_TCON_TIMER_START |
> +	       MCT_L_TCON_INTERVAL_MODE;
> +	s5pv310_mct_write(tmp, addr);
> +}
> +
> +static inline int s5pv310_tick_set_next_event(enum mct_tick_type type,
> +					      unsigned long cycles)
> +{
> +	s5pv310_mct_tick_start(type, cycles);
> +
> +	return 0;
> +}
> +
> +static inline void s5pv310_tick_set_mode(enum mct_tick_type type,
> +					 enum clock_event_mode mode)
> +{
> +	s5pv310_mct_tick_stop(type);
> +
> +	switch (mode) {
> +	case CLOCK_EVT_MODE_PERIODIC:
> +		s5pv310_mct_tick_start(type, clk_cnt_per_tick);
> +		break;
> +
> +	case CLOCK_EVT_MODE_ONESHOT:
> +	case CLOCK_EVT_MODE_UNUSED:
> +	case CLOCK_EVT_MODE_SHUTDOWN:
> +	case CLOCK_EVT_MODE_RESUME:
> +		break;
> +	}
> +}
> +
> +static int s5pv310_tick0_set_next_event(unsigned long cycles,
> +					struct clock_event_device *evt)
> +{
> +	return s5pv310_tick_set_next_event(MCT_TICK0, cycles);
> +}
> +
> +static int s5pv310_tick1_set_next_event(unsigned long cycles,
> +					struct clock_event_device *evt)
> +{
> +	return s5pv310_tick_set_next_event(MCT_TICK1, cycles);
> +}
> +
> +static void s5pv310_tick0_set_mode(enum clock_event_mode mode,
> +				   struct clock_event_device *evt)
> +{
> +	s5pv310_tick_set_mode(MCT_TICK0, mode);
> +}
> +
> +static void s5pv310_tick1_set_mode(enum clock_event_mode mode,
> +				   struct clock_event_device *evt)
> +{
> +	s5pv310_tick_set_mode(MCT_TICK1, mode);
> +}
> +
> +static struct clock_event_device mct_tick0_device = {
> +	.name		= "mct-tick0",
> +	.features	= CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT,
> +	.rating		= 450,
> +	.shift		= 20,
> +	.set_next_event	= s5pv310_tick0_set_next_event,
> +	.set_mode	= s5pv310_tick0_set_mode,
> +};
> +
> +static struct clock_event_device mct_tick1_device = {
> +	.name		= "mct-tick1",
> +	.features	= CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT,
> +	.rating		= 450,
> +	.shift		= 20,
> +	.set_next_event	= s5pv310_tick1_set_next_event,
> +	.set_mode	= s5pv310_tick1_set_mode,
> +};
> +
> +irqreturn_t s5pv310_mct0_event_isr(int irq, void *dev_id)
> +{
> +	struct clock_event_device *evt;
> +
> +	/* Clear the MCT tick interrupt */
> +
> +	evt = &mct_tick0_device;
> +	if (evt->mode != CLOCK_EVT_MODE_PERIODIC)
> +		s5pv310_mct_tick_stop(MCT_TICK0);
> +
> +	s5pv310_mct_write(0x1, S5PV310_MCT_L0_INT_CSTAT);
> +
> +	evt->event_handler(evt);
> +
> +	return IRQ_HANDLED;
> +}
> +
> +irqreturn_t s5pv310_mct1_event_isr(int irq, void *dev_id)
> +{
> +	struct clock_event_device *evt;
> +
> +	/* Clear the MCT tick interrupt */
> +
> +	evt = &mct_tick1_device;
> +
> +	if (evt->mode != CLOCK_EVT_MODE_PERIODIC)
> +		s5pv310_mct_tick_stop(MCT_TICK1);
> +
> +	s5pv310_mct_write(0x1, S5PV310_MCT_L1_INT_CSTAT);
> +
> +	evt->event_handler(evt);
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static struct irqaction mct_tick0_event_irq = {
> +	.name		= "mct_tick0_irq",
> +	.flags		= IRQF_TIMER | IRQF_IRQPOLL | IRQF_NOBALANCING,
> +	.handler	= s5pv310_mct0_event_isr,
> +};
> +
> +static struct irqaction mct_tick1_event_irq = {
> +	.name		= "mct_tick1_irq",
> +	.flags		= IRQF_TIMER | IRQF_NOBALANCING,
> +	.handler	= s5pv310_mct1_event_isr,
> +};
> +
> +static void s5pv310_mct_clockevent_init(struct clock_event_device *dev)
> +{
> +	dev->mult = div_sc(clk_rate / 2, NSEC_PER_SEC, dev->shift);

Best use the helper function to calculate both the multipler and shift,
rather than hard-coding the shift.  As the shift is defined to be '20'
I guess this was copied rather than calculated for the optimal value.

> +
> +	dev->max_delta_ns = clockevent_delta2ns(0xfffffff, dev);
> +	dev->min_delta_ns = clockevent_delta2ns(0xf, dev);
> +}
> +
> +static void __init s5pv310_clockevent0_init(void)
> +{
> +	clk_cnt_per_tick = clk_rate / 2 / HZ;
> +
> +	s5pv310_mct_write(0x1, S5PV310_MCT_L0_TCNTB);
> +	s5pv310_mct_clockevent_init(&mct_tick0_device);
> +	mct_tick0_device.cpumask = cpumask_of(0);
> +	clockevents_register_device(&mct_tick0_device);
> +
> +	setup_irq(IRQ_MCT_L0, &mct_tick0_event_irq);
> +}
> +
> +static void __init s5pv310_clockevent1_init(void *info)
> +{
> +	s5pv310_mct_write(0x1, S5PV310_MCT_L1_TCNTB);
> +	s5pv310_mct_clockevent_init(&mct_tick1_device);
> +	mct_tick1_device.cpumask = cpumask_of(1);
> +	clockevents_register_device(&mct_tick1_device);
> +
> +	irq_set_affinity(IRQ_MCT1, cpumask_of(1));
> +	setup_irq(IRQ_MCT_L1, &mct_tick1_event_irq);

This looks like it's supporting SMP - is there some reason not to use
the localtimer support built into SMP, which also supports hotplug CPU
(the above doesn't because the local clockevents will be automatically
unregistered.)

If you don't have a global timer, then register your boot CPU's clock
event timer as the global timer for the time being - the SMP code needs
tweaking so that a local timer can be registered early for those without
global timers.  Until then, if you have a global timer, I strongly
suggest you register that as a clock event too (it'll automatically
get shutdown when not needed.)

> +}
> +
> +static void s5pv310_mct_frc_start(unsigned int hi, unsigned int lo)
> +{
> +	unsigned int tmp;
> +
> +	s5pv310_mct_write(lo, S5PV310_MCT_G_CNT_L);
> +	s5pv310_mct_write(hi, S5PV310_MCT_G_CNT_U);
> +
> +	tmp = __raw_readl(S5PV310_MCT_G_TCON);
> +	tmp |= (1 << 8);	/* G_TCON_START */
> +	s5pv310_mct_write(tmp, S5PV310_MCT_G_TCON);
> +}
> +
> +/* Clocksource handling */
> +
> +static cycle_t s5pv310_frc_read(struct clocksource *cs)
> +{
> +	unsigned int lo, hi0, hi1;
> +
> +	hi0 = __raw_readl(S5PV310_MCT_G_CNT_U);
> +	lo = __raw_readl(S5PV310_MCT_G_CNT_L);
> +	dsb();
> +	hi1 = __raw_readl(S5PV310_MCT_G_CNT_U);
> +
> +	if (hi0 != hi1) {
> +		lo = __raw_readl(S5PV310_MCT_G_CNT_L);
> +		hi1 = __raw_readl(S5PV310_MCT_G_CNT_U);
> +	}

Are you sure this is safe?  Would it not be better to do this as:

	hi1 = __raw_readl(S5PV310_MCT_G_CNT_U);
	do {
		hi0 = hi1;
		lo = __raw_readl(S5PV310_MCT_G_CNT_L);
		hi1 = __raw_readl(S5PV310_MCT_G_CNT_U);
	} while (hi0 != hi1);

Note that I don't believe you need a dsb() in there either, as device
accesses are ordered with respect to themselves.  I know it's highly
unlikely that we'll see two updates, but I think having it obviously
robust is a good idea.

^ permalink raw reply	[flat|nested] 14+ messages in thread

* [PATCH] ARM: S5PV310: Implement kernel timers using MCT
@ 2010-12-22 22:55   ` Russell King - ARM Linux
  0 siblings, 0 replies; 14+ messages in thread
From: Russell King - ARM Linux @ 2010-12-22 22:55 UTC (permalink / raw)
  To: linux-arm-kernel

On Wed, Dec 22, 2010 at 08:27:01PM +0900, Kukjin Kim wrote:
> From: Changhwan Youn <chaos.youn@samsung.com>
> 
> The Multi-Core Timer(MCT) of S5PV310 is designed for implementing
> clock source timer and clock event timers. This patch implements
> 1 clock source timer with 64 bit free running counter of MCT and
> 2 clock event timers with two of 31-bit tick counters.
> 
> Signed-off-by: Changhwan Youn <chaos.youn@samsung.com>
> Cc: Ben Dooks <ben-linux@fluff.org>
> Cc: Russell King <rmk+kernel@arm.linux.org.uk>
> Signed-off-by: Kukjin Kim <kgene.kim@samsung.com>
> ---
>  arch/arm/mach-s5pv310/Kconfig                 |    7 +
>  arch/arm/mach-s5pv310/Makefile                |    8 +-
>  arch/arm/mach-s5pv310/include/mach/regs-mct.h |   47 ++++
>  arch/arm/mach-s5pv310/mct.c                   |  365 +++++++++++++++++++++++++
>  4 files changed, 426 insertions(+), 1 deletions(-)
>  create mode 100644 arch/arm/mach-s5pv310/include/mach/regs-mct.h
>  create mode 100644 arch/arm/mach-s5pv310/mct.c
> 
> diff --git a/arch/arm/mach-s5pv310/Kconfig b/arch/arm/mach-s5pv310/Kconfig
> index e0cef3f..02d7b4d 100644
> --- a/arch/arm/mach-s5pv310/Kconfig
> +++ b/arch/arm/mach-s5pv310/Kconfig
> @@ -14,6 +14,13 @@ config CPU_S5PV310
>  	help
>  	  Enable S5PV310 CPU support
>  
> +config S5PV310_MCT
> +	bool "Kernel timer support by MCT"
> +	depends on !LOCAL_TIMERS
> +	help
> +	  Use MCT (Multi Core Timer) as kernel timers
> +	  NOTE: Can not choose this with LOCAL_TIMERS
> +
>  config S5PV310_DEV_PD
>  	bool
>  	help
> diff --git a/arch/arm/mach-s5pv310/Makefile b/arch/arm/mach-s5pv310/Makefile
> index e310609..08158e3 100644
> --- a/arch/arm/mach-s5pv310/Makefile
> +++ b/arch/arm/mach-s5pv310/Makefile
> @@ -13,7 +13,13 @@ obj-				:=
>  # Core support for S5PV310 system
>  
>  obj-$(CONFIG_CPU_S5PV310)	+= cpu.o init.o clock.o irq-combiner.o
> -obj-$(CONFIG_CPU_S5PV310)	+= setup-i2c0.o time.o gpiolib.o irq-eint.o
> +obj-$(CONFIG_CPU_S5PV310)	+= setup-i2c0.o gpiolib.o irq-eint.o
> +
> +ifeq ($(CONFIG_S5PV310_MCT),y)
> +obj-y				+= mct.o
> +else
> +obj-y				+= time.o
> +endif
>  
>  obj-$(CONFIG_SMP)		+= platsmp.o headsmp.o
>  obj-$(CONFIG_LOCAL_TIMERS)	+= localtimer.o
> diff --git a/arch/arm/mach-s5pv310/include/mach/regs-mct.h b/arch/arm/mach-s5pv310/include/mach/regs-mct.h
> new file mode 100644
> index 0000000..1480e2a
> --- /dev/null
> +++ b/arch/arm/mach-s5pv310/include/mach/regs-mct.h
> @@ -0,0 +1,47 @@
> +/* arch/arm/mach-s5pv310/include/mach/regs-mct.h
> + *
> + * Copyright (c) 2010 Samsung Electronics Co., Ltd.
> + *		http://www.samsung.com
> + *
> + * S5PV310 MCT(Multi-Core Timer) configutation
> + *
> + * 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.
> +*/
> +
> +#ifndef __ASM_ARCH_REGS_MCT_H
> +#define __ASM_ARCH_REGS_MCT_H __FILE__
> +
> +#include <mach/map.h>
> +
> +#define S5PV310_MCTREG(x)		(S5P_VA_SYSTIMER + (x))
> +
> +#define S5PV310_MCT_G_CNT_L		S5PV310_MCTREG(0x100)
> +#define S5PV310_MCT_G_CNT_U		S5PV310_MCTREG(0x104)
> +#define S5PV310_MCT_G_CNT_WSTAT		S5PV310_MCTREG(0x110)
> +
> +#define S5PV310_MCT_G_TCON		S5PV310_MCTREG(0x240)
> +
> +#define S5PV310_MCT_G_WSTAT		S5PV310_MCTREG(0x24C)
> +
> +#define S5PV310_MCT_L0_TCNTB		S5PV310_MCTREG(0x300)
> +#define S5PV310_MCT_L0_ICNTB		S5PV310_MCTREG(0x308)
> +#define S5PV310_MCT_L0_TCON		S5PV310_MCTREG(0x320)
> +
> +#define S5PV310_MCT_L0_INT_CSTAT	S5PV310_MCTREG(0x330)
> +#define S5PV310_MCT_L0_INT_ENB		S5PV310_MCTREG(0x334)
> +#define S5PV310_MCT_L0_WSTAT		S5PV310_MCTREG(0x340)
> +
> +#define S5PV310_MCT_L1_TCNTB		S5PV310_MCTREG(0x400)
> +#define S5PV310_MCT_L1_ICNTB		S5PV310_MCTREG(0x408)
> +#define S5PV310_MCT_L1_TCON		S5PV310_MCTREG(0x420)
> +#define S5PV310_MCT_L1_INT_CSTAT	S5PV310_MCTREG(0x430)
> +#define S5PV310_MCT_L1_INT_ENB		S5PV310_MCTREG(0x434)
> +#define S5PV310_MCT_L1_WSTAT		S5PV310_MCTREG(0x440)
> +
> +#define MCT_L_TCON_INTERVAL_MODE	(1 << 2)
> +#define MCT_L_TCON_INT_START		(1 << 1)
> +#define MCT_L_TCON_TIMER_START		(1 << 0)
> +
> +#endif /* __ASM_ARCH_REGS_MCT_H */
> diff --git a/arch/arm/mach-s5pv310/mct.c b/arch/arm/mach-s5pv310/mct.c
> new file mode 100644
> index 0000000..2fe189c
> --- /dev/null
> +++ b/arch/arm/mach-s5pv310/mct.c
> @@ -0,0 +1,365 @@
> +/* linux/arch/arm/mach-s5pv310/mct.c
> + *
> + * Copyright (c) 2010 Samsung Electronics Co., Ltd.
> + *		http://www.samsung.com
> + *
> + * S5PV310 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 <linux/sched.h>
> +#include <linux/interrupt.h>
> +#include <linux/irq.h>
> +#include <linux/err.h>
> +#include <linux/clk.h>
> +#include <linux/clockchips.h>
> +#include <linux/platform_device.h>
> +#include <linux/delay.h>
> +
> +#include <mach/map.h>
> +#include <mach/regs-mct.h>
> +#include <asm/mach/time.h>
> +
> +static unsigned long clk_cnt_per_tick;
> +static unsigned long clk_rate;
> +
> +enum mct_tick_type {
> +	MCT_TICK0,
> +	MCT_TICK1,
> +};
> +
> +static void s5pv310_mct_write(unsigned int value, void *addr)
> +{
> +	void __iomem *stat_addr;
> +	u32 mask;
> +	u32 i;
> +
> +	__raw_writel(value, addr);
> +
> +	switch ((u32) addr) {
> +	case (u32) S5PV310_MCT_G_TCON:
> +		stat_addr = S5PV310_MCT_G_WSTAT;
> +		mask = 1 << 16;		/* G_WSTAT_TCON_STAT */
> +		break;
> +
> +	case (u32) S5PV310_MCT_G_CNT_L:
> +		stat_addr = S5PV310_MCT_G_CNT_WSTAT;
> +		mask = 1 << 0;		/* G_CNT_L_STAT */
> +		break;
> +
> +	case (u32) S5PV310_MCT_G_CNT_U:
> +		stat_addr = S5PV310_MCT_G_CNT_WSTAT;
> +		mask = 1 << 1;		/* G_CNT_U_STAT */
> +		break;
> +
> +	case (u32) S5PV310_MCT_L0_TCON:
> +		stat_addr = S5PV310_MCT_L0_WSTAT;
> +		mask = 1 << 3;		/* L_TCON_STAT */
> +		break;
> +
> +	case (u32) S5PV310_MCT_L1_TCON:
> +		stat_addr = S5PV310_MCT_L1_WSTAT;
> +		mask = 1 << 3;		/* L_TCON_STAT */
> +		break;
> +
> +	case (u32) S5PV310_MCT_L0_TCNTB:
> +		stat_addr = S5PV310_MCT_L0_WSTAT;
> +		mask = 1 << 0;		/* L_TCNTB_STAT	*/
> +		break;
> +
> +	case (u32) S5PV310_MCT_L1_TCNTB:
> +		stat_addr = S5PV310_MCT_L1_WSTAT;
> +		mask = 1 << 0;		/* L_TCNTB_STAT	*/
> +		break;
> +
> +	case (u32) S5PV310_MCT_L0_ICNTB:
> +		stat_addr = S5PV310_MCT_L0_WSTAT;
> +		mask = 1 << 1;		/* L_ICNTB_STAT */
> +		break;
> +
> +	case (u32) S5PV310_MCT_L1_ICNTB:
> +		stat_addr = S5PV310_MCT_L1_WSTAT;
> +		mask = 1 << 1;		/* L_ICNTB_STAT */
> +		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);
> +}
> +
> +static void s5pv310_mct_tick_stop(enum mct_tick_type type)
> +{
> +	unsigned long tmp;
> +	void __iomem *addr;
> +
> +	if (type == MCT_TICK0)
> +		addr = S5PV310_MCT_L0_TCON;
> +	else
> +		addr = S5PV310_MCT_L1_TCON;
> +
> +	tmp = __raw_readl(addr);
> +	tmp &= ~(MCT_L_TCON_INT_START | MCT_L_TCON_TIMER_START);
> +	s5pv310_mct_write(tmp, addr);
> +}
> +
> +static void s5pv310_mct_tick_start(enum mct_tick_type type,
> +				   unsigned long cycles)
> +{
> +	unsigned long tmp;
> +	unsigned int tmp1, tmp2;
> +	void __iomem *addr;
> +
> +	s5pv310_mct_tick_stop(type);
> +
> +	tmp1 = (1 << 31) | cycles;	/* MCT_L_UPDATE_ICNTB */
> +	tmp2 = 1 << 0;			/* L_INT_ENB_CNTIE */
> +
> +	if (type == MCT_TICK0) {
> +		s5pv310_mct_write(tmp1, S5PV310_MCT_L0_ICNTB);
> +		s5pv310_mct_write(tmp2, S5PV310_MCT_L0_INT_ENB);
> +
> +		addr = S5PV310_MCT_L0_TCON;
> +	} else {
> +		s5pv310_mct_write(tmp1, S5PV310_MCT_L1_ICNTB);
> +		s5pv310_mct_write(tmp2, S5PV310_MCT_L1_INT_ENB);
> +
> +		addr = S5PV310_MCT_L1_TCON;
> +	}
> +
> +	tmp = __raw_readl(addr);
> +	tmp |= MCT_L_TCON_INT_START | MCT_L_TCON_TIMER_START |
> +	       MCT_L_TCON_INTERVAL_MODE;
> +	s5pv310_mct_write(tmp, addr);
> +}
> +
> +static inline int s5pv310_tick_set_next_event(enum mct_tick_type type,
> +					      unsigned long cycles)
> +{
> +	s5pv310_mct_tick_start(type, cycles);
> +
> +	return 0;
> +}
> +
> +static inline void s5pv310_tick_set_mode(enum mct_tick_type type,
> +					 enum clock_event_mode mode)
> +{
> +	s5pv310_mct_tick_stop(type);
> +
> +	switch (mode) {
> +	case CLOCK_EVT_MODE_PERIODIC:
> +		s5pv310_mct_tick_start(type, clk_cnt_per_tick);
> +		break;
> +
> +	case CLOCK_EVT_MODE_ONESHOT:
> +	case CLOCK_EVT_MODE_UNUSED:
> +	case CLOCK_EVT_MODE_SHUTDOWN:
> +	case CLOCK_EVT_MODE_RESUME:
> +		break;
> +	}
> +}
> +
> +static int s5pv310_tick0_set_next_event(unsigned long cycles,
> +					struct clock_event_device *evt)
> +{
> +	return s5pv310_tick_set_next_event(MCT_TICK0, cycles);
> +}
> +
> +static int s5pv310_tick1_set_next_event(unsigned long cycles,
> +					struct clock_event_device *evt)
> +{
> +	return s5pv310_tick_set_next_event(MCT_TICK1, cycles);
> +}
> +
> +static void s5pv310_tick0_set_mode(enum clock_event_mode mode,
> +				   struct clock_event_device *evt)
> +{
> +	s5pv310_tick_set_mode(MCT_TICK0, mode);
> +}
> +
> +static void s5pv310_tick1_set_mode(enum clock_event_mode mode,
> +				   struct clock_event_device *evt)
> +{
> +	s5pv310_tick_set_mode(MCT_TICK1, mode);
> +}
> +
> +static struct clock_event_device mct_tick0_device = {
> +	.name		= "mct-tick0",
> +	.features	= CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT,
> +	.rating		= 450,
> +	.shift		= 20,
> +	.set_next_event	= s5pv310_tick0_set_next_event,
> +	.set_mode	= s5pv310_tick0_set_mode,
> +};
> +
> +static struct clock_event_device mct_tick1_device = {
> +	.name		= "mct-tick1",
> +	.features	= CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT,
> +	.rating		= 450,
> +	.shift		= 20,
> +	.set_next_event	= s5pv310_tick1_set_next_event,
> +	.set_mode	= s5pv310_tick1_set_mode,
> +};
> +
> +irqreturn_t s5pv310_mct0_event_isr(int irq, void *dev_id)
> +{
> +	struct clock_event_device *evt;
> +
> +	/* Clear the MCT tick interrupt */
> +
> +	evt = &mct_tick0_device;
> +	if (evt->mode != CLOCK_EVT_MODE_PERIODIC)
> +		s5pv310_mct_tick_stop(MCT_TICK0);
> +
> +	s5pv310_mct_write(0x1, S5PV310_MCT_L0_INT_CSTAT);
> +
> +	evt->event_handler(evt);
> +
> +	return IRQ_HANDLED;
> +}
> +
> +irqreturn_t s5pv310_mct1_event_isr(int irq, void *dev_id)
> +{
> +	struct clock_event_device *evt;
> +
> +	/* Clear the MCT tick interrupt */
> +
> +	evt = &mct_tick1_device;
> +
> +	if (evt->mode != CLOCK_EVT_MODE_PERIODIC)
> +		s5pv310_mct_tick_stop(MCT_TICK1);
> +
> +	s5pv310_mct_write(0x1, S5PV310_MCT_L1_INT_CSTAT);
> +
> +	evt->event_handler(evt);
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static struct irqaction mct_tick0_event_irq = {
> +	.name		= "mct_tick0_irq",
> +	.flags		= IRQF_TIMER | IRQF_IRQPOLL | IRQF_NOBALANCING,
> +	.handler	= s5pv310_mct0_event_isr,
> +};
> +
> +static struct irqaction mct_tick1_event_irq = {
> +	.name		= "mct_tick1_irq",
> +	.flags		= IRQF_TIMER | IRQF_NOBALANCING,
> +	.handler	= s5pv310_mct1_event_isr,
> +};
> +
> +static void s5pv310_mct_clockevent_init(struct clock_event_device *dev)
> +{
> +	dev->mult = div_sc(clk_rate / 2, NSEC_PER_SEC, dev->shift);

Best use the helper function to calculate both the multipler and shift,
rather than hard-coding the shift.  As the shift is defined to be '20'
I guess this was copied rather than calculated for the optimal value.

> +
> +	dev->max_delta_ns = clockevent_delta2ns(0xfffffff, dev);
> +	dev->min_delta_ns = clockevent_delta2ns(0xf, dev);
> +}
> +
> +static void __init s5pv310_clockevent0_init(void)
> +{
> +	clk_cnt_per_tick = clk_rate / 2 / HZ;
> +
> +	s5pv310_mct_write(0x1, S5PV310_MCT_L0_TCNTB);
> +	s5pv310_mct_clockevent_init(&mct_tick0_device);
> +	mct_tick0_device.cpumask = cpumask_of(0);
> +	clockevents_register_device(&mct_tick0_device);
> +
> +	setup_irq(IRQ_MCT_L0, &mct_tick0_event_irq);
> +}
> +
> +static void __init s5pv310_clockevent1_init(void *info)
> +{
> +	s5pv310_mct_write(0x1, S5PV310_MCT_L1_TCNTB);
> +	s5pv310_mct_clockevent_init(&mct_tick1_device);
> +	mct_tick1_device.cpumask = cpumask_of(1);
> +	clockevents_register_device(&mct_tick1_device);
> +
> +	irq_set_affinity(IRQ_MCT1, cpumask_of(1));
> +	setup_irq(IRQ_MCT_L1, &mct_tick1_event_irq);

This looks like it's supporting SMP - is there some reason not to use
the localtimer support built into SMP, which also supports hotplug CPU
(the above doesn't because the local clockevents will be automatically
unregistered.)

If you don't have a global timer, then register your boot CPU's clock
event timer as the global timer for the time being - the SMP code needs
tweaking so that a local timer can be registered early for those without
global timers.  Until then, if you have a global timer, I strongly
suggest you register that as a clock event too (it'll automatically
get shutdown when not needed.)

> +}
> +
> +static void s5pv310_mct_frc_start(unsigned int hi, unsigned int lo)
> +{
> +	unsigned int tmp;
> +
> +	s5pv310_mct_write(lo, S5PV310_MCT_G_CNT_L);
> +	s5pv310_mct_write(hi, S5PV310_MCT_G_CNT_U);
> +
> +	tmp = __raw_readl(S5PV310_MCT_G_TCON);
> +	tmp |= (1 << 8);	/* G_TCON_START */
> +	s5pv310_mct_write(tmp, S5PV310_MCT_G_TCON);
> +}
> +
> +/* Clocksource handling */
> +
> +static cycle_t s5pv310_frc_read(struct clocksource *cs)
> +{
> +	unsigned int lo, hi0, hi1;
> +
> +	hi0 = __raw_readl(S5PV310_MCT_G_CNT_U);
> +	lo = __raw_readl(S5PV310_MCT_G_CNT_L);
> +	dsb();
> +	hi1 = __raw_readl(S5PV310_MCT_G_CNT_U);
> +
> +	if (hi0 != hi1) {
> +		lo = __raw_readl(S5PV310_MCT_G_CNT_L);
> +		hi1 = __raw_readl(S5PV310_MCT_G_CNT_U);
> +	}

Are you sure this is safe?  Would it not be better to do this as:

	hi1 = __raw_readl(S5PV310_MCT_G_CNT_U);
	do {
		hi0 = hi1;
		lo = __raw_readl(S5PV310_MCT_G_CNT_L);
		hi1 = __raw_readl(S5PV310_MCT_G_CNT_U);
	} while (hi0 != hi1);

Note that I don't believe you need a dsb() in there either, as device
accesses are ordered with respect to themselves.  I know it's highly
unlikely that we'll see two updates, but I think having it obviously
robust is a good idea.

^ permalink raw reply	[flat|nested] 14+ messages in thread

* RE: [PATCH] ARM: S5PV310: Implement kernel timers using MCT
  2010-12-22 12:35   ` Jamie Iles
@ 2010-12-23 10:24     ` Kukjin Kim
  -1 siblings, 0 replies; 14+ messages in thread
From: Kukjin Kim @ 2010-12-23 10:24 UTC (permalink / raw)
  To: 'Jamie Iles'
  Cc: linux-arm-kernel, linux-samsung-soc, 'Russell King',
	'Changhwan Youn',
	ben-linux

Jamie Iles wrote:
> 
> Hi Kukjin,
> 
Hi Jamie :-)

> Just trying to learn what other people are doing in their platforms, so
> apologies if my comments aren't applicable! Looks good to me though.
> 
No problem, thanks for your comments and always welcome to review ;-)

(snip)

> > +
> > +static struct clock_event_device mct_tick1_device = {
> > +	.name		= "mct-tick1",
> > +	.features	= CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT,
> > +	.rating		= 450,
> > +	.shift		= 20,
> > +	.set_next_event	= s5pv310_tick1_set_next_event,
> > +	.set_mode	= s5pv310_tick1_set_mode,
> > +};
> 
> Do you need to specify .shift here? Could you use
> clockevents_calc_mult_shift() to calculate mult and shift for you?
> 
Oh, yeah...Mr. Youn and I didn't notice the existence of it...will fix.

> > +irqreturn_t s5pv310_mct0_event_isr(int irq, void *dev_id)
> > +{
> > +	struct clock_event_device *evt;
> > +
> > +	/* Clear the MCT tick interrupt */
> > +
> > +	evt = &mct_tick0_device;
> > +	if (evt->mode != CLOCK_EVT_MODE_PERIODIC)
> > +		s5pv310_mct_tick_stop(MCT_TICK0);
> 
> I don't think you need to stop the timer here, the clockevents layer
> will handle this for you.
> 
This is to support One-shot mode using MCT. Because basically MCT local
timer only supports periodic mode.
But will be added comment.

> [...]
> > +static void s5pv310_mct_clockevent_init(struct clock_event_device *dev)
> > +{
> > +	dev->mult = div_sc(clk_rate / 2, NSEC_PER_SEC, dev->shift);
> > +
> > +	dev->max_delta_ns = clockevent_delta2ns(0xfffffff, dev);
> 
> Is there a reason that the delta is only using 28 bits here?
> 
Oh, your pointing out is right...Its max delta should be 31 bits.
Will fix it...and correct value is 0x7fffffff.

Thanks.

Best regards,
Kgene.
--
Kukjin Kim <kgene.kim@samsung.com>, Senior Engineer,
SW Solution Development Team, Samsung Electronics Co., Ltd.

^ permalink raw reply	[flat|nested] 14+ messages in thread

* [PATCH] ARM: S5PV310: Implement kernel timers using MCT
@ 2010-12-23 10:24     ` Kukjin Kim
  0 siblings, 0 replies; 14+ messages in thread
From: Kukjin Kim @ 2010-12-23 10:24 UTC (permalink / raw)
  To: linux-arm-kernel

Jamie Iles wrote:
> 
> Hi Kukjin,
> 
Hi Jamie :-)

> Just trying to learn what other people are doing in their platforms, so
> apologies if my comments aren't applicable! Looks good to me though.
> 
No problem, thanks for your comments and always welcome to review ;-)

(snip)

> > +
> > +static struct clock_event_device mct_tick1_device = {
> > +	.name		= "mct-tick1",
> > +	.features	= CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT,
> > +	.rating		= 450,
> > +	.shift		= 20,
> > +	.set_next_event	= s5pv310_tick1_set_next_event,
> > +	.set_mode	= s5pv310_tick1_set_mode,
> > +};
> 
> Do you need to specify .shift here? Could you use
> clockevents_calc_mult_shift() to calculate mult and shift for you?
> 
Oh, yeah...Mr. Youn and I didn't notice the existence of it...will fix.

> > +irqreturn_t s5pv310_mct0_event_isr(int irq, void *dev_id)
> > +{
> > +	struct clock_event_device *evt;
> > +
> > +	/* Clear the MCT tick interrupt */
> > +
> > +	evt = &mct_tick0_device;
> > +	if (evt->mode != CLOCK_EVT_MODE_PERIODIC)
> > +		s5pv310_mct_tick_stop(MCT_TICK0);
> 
> I don't think you need to stop the timer here, the clockevents layer
> will handle this for you.
> 
This is to support One-shot mode using MCT. Because basically MCT local
timer only supports periodic mode.
But will be added comment.

> [...]
> > +static void s5pv310_mct_clockevent_init(struct clock_event_device *dev)
> > +{
> > +	dev->mult = div_sc(clk_rate / 2, NSEC_PER_SEC, dev->shift);
> > +
> > +	dev->max_delta_ns = clockevent_delta2ns(0xfffffff, dev);
> 
> Is there a reason that the delta is only using 28 bits here?
> 
Oh, your pointing out is right...Its max delta should be 31 bits.
Will fix it...and correct value is 0x7fffffff.

Thanks.

Best regards,
Kgene.
--
Kukjin Kim <kgene.kim@samsung.com>, Senior Engineer,
SW Solution Development Team, Samsung Electronics Co., Ltd.

^ permalink raw reply	[flat|nested] 14+ messages in thread

* RE: [PATCH] ARM: S5PV310: Implement kernel timers using MCT
  2010-12-22 22:55   ` Russell King - ARM Linux
@ 2010-12-23 10:29     ` Kukjin Kim
  -1 siblings, 0 replies; 14+ messages in thread
From: Kukjin Kim @ 2010-12-23 10:29 UTC (permalink / raw)
  To: 'Russell King - ARM Linux'
  Cc: linux-arm-kernel, linux-samsung-soc, ben-linux, 'Changhwan Youn'

Russell King - ARM Linux wrote:
> 
> On Wed, Dec 22, 2010 at 08:27:01PM +0900, Kukjin Kim wrote:
> > From: Changhwan Youn <chaos.youn@samsung.com>

(snip)

> > +static void s5pv310_mct_clockevent_init(struct clock_event_device *dev)
> > +{
> > +	dev->mult = div_sc(clk_rate / 2, NSEC_PER_SEC, dev->shift);
> 
> Best use the helper function to calculate both the multipler and shift,
> rather than hard-coding the shift.  As the shift is defined to be '20'
> I guess this was copied rather than calculated for the optimal value.
> 
You're right...will fix it.

(snip)

> > +static void __init s5pv310_clockevent1_init(void *info)
> > +{
> > +	s5pv310_mct_write(0x1, S5PV310_MCT_L1_TCNTB);
> > +	s5pv310_mct_clockevent_init(&mct_tick1_device);
> > +	mct_tick1_device.cpumask = cpumask_of(1);
> > +	clockevents_register_device(&mct_tick1_device);
> > +
> > +	irq_set_affinity(IRQ_MCT1, cpumask_of(1));
> > +	setup_irq(IRQ_MCT_L1, &mct_tick1_event_irq);
> 
> This looks like it's supporting SMP - is there some reason not to use
> the localtimer support built into SMP, which also supports hotplug CPU
> (the above doesn't because the local clockevents will be automatically
> unregistered.)
> 
As I know, to use the localtimer support, it needs to support PPI.
Unfortunately the MCT of S5PV310 supports SPI. For hotplug CPU, I'm looking
for the methods to register clockevents again at CPU hotplug-in.

> If you don't have a global timer, then register your boot CPU's clock
> event timer as the global timer for the time being - the SMP code needs
> tweaking so that a local timer can be registered early for those without
> global timers.  Until then, if you have a global timer, I strongly
> suggest you register that as a clock event too (it'll automatically
> get shutdown when not needed.)
> 
Ok...will add another timer for the global timer.

(snip)

> > +static cycle_t s5pv310_frc_read(struct clocksource *cs)
> > +{
> > +	unsigned int lo, hi0, hi1;
> > +
> > +	hi0 = __raw_readl(S5PV310_MCT_G_CNT_U);
> > +	lo = __raw_readl(S5PV310_MCT_G_CNT_L);
> > +	dsb();
> > +	hi1 = __raw_readl(S5PV310_MCT_G_CNT_U);
> > +
> > +	if (hi0 != hi1) {
> > +		lo = __raw_readl(S5PV310_MCT_G_CNT_L);
> > +		hi1 = __raw_readl(S5PV310_MCT_G_CNT_U);
> > +	}
> 
> Are you sure this is safe?  Would it not be better to do this as:
> 
> 	hi1 = __raw_readl(S5PV310_MCT_G_CNT_U);
> 	do {
> 		hi0 = hi1;
> 		lo = __raw_readl(S5PV310_MCT_G_CNT_L);
> 		hi1 = __raw_readl(S5PV310_MCT_G_CNT_U);
> 	} while (hi0 != hi1);
> 
> Note that I don't believe you need a dsb() in there either, as device
> accesses are ordered with respect to themselves.  I know it's highly
> unlikely that we'll see two updates, but I think having it obviously
> robust is a good idea.

Yes, would be better..will change as your suggestion.

Merry Xmas :-)
Thanks.

Best regards,
Kgene.
--
Kukjin Kim <kgene.kim@samsung.com>, Senior Engineer,
SW Solution Development Team, Samsung Electronics Co., Ltd.

^ permalink raw reply	[flat|nested] 14+ messages in thread

* [PATCH] ARM: S5PV310: Implement kernel timers using MCT
@ 2010-12-23 10:29     ` Kukjin Kim
  0 siblings, 0 replies; 14+ messages in thread
From: Kukjin Kim @ 2010-12-23 10:29 UTC (permalink / raw)
  To: linux-arm-kernel

Russell King - ARM Linux wrote:
> 
> On Wed, Dec 22, 2010 at 08:27:01PM +0900, Kukjin Kim wrote:
> > From: Changhwan Youn <chaos.youn@samsung.com>

(snip)

> > +static void s5pv310_mct_clockevent_init(struct clock_event_device *dev)
> > +{
> > +	dev->mult = div_sc(clk_rate / 2, NSEC_PER_SEC, dev->shift);
> 
> Best use the helper function to calculate both the multipler and shift,
> rather than hard-coding the shift.  As the shift is defined to be '20'
> I guess this was copied rather than calculated for the optimal value.
> 
You're right...will fix it.

(snip)

> > +static void __init s5pv310_clockevent1_init(void *info)
> > +{
> > +	s5pv310_mct_write(0x1, S5PV310_MCT_L1_TCNTB);
> > +	s5pv310_mct_clockevent_init(&mct_tick1_device);
> > +	mct_tick1_device.cpumask = cpumask_of(1);
> > +	clockevents_register_device(&mct_tick1_device);
> > +
> > +	irq_set_affinity(IRQ_MCT1, cpumask_of(1));
> > +	setup_irq(IRQ_MCT_L1, &mct_tick1_event_irq);
> 
> This looks like it's supporting SMP - is there some reason not to use
> the localtimer support built into SMP, which also supports hotplug CPU
> (the above doesn't because the local clockevents will be automatically
> unregistered.)
> 
As I know, to use the localtimer support, it needs to support PPI.
Unfortunately the MCT of S5PV310 supports SPI. For hotplug CPU, I'm looking
for the methods to register clockevents again at CPU hotplug-in.

> If you don't have a global timer, then register your boot CPU's clock
> event timer as the global timer for the time being - the SMP code needs
> tweaking so that a local timer can be registered early for those without
> global timers.  Until then, if you have a global timer, I strongly
> suggest you register that as a clock event too (it'll automatically
> get shutdown when not needed.)
> 
Ok...will add another timer for the global timer.

(snip)

> > +static cycle_t s5pv310_frc_read(struct clocksource *cs)
> > +{
> > +	unsigned int lo, hi0, hi1;
> > +
> > +	hi0 = __raw_readl(S5PV310_MCT_G_CNT_U);
> > +	lo = __raw_readl(S5PV310_MCT_G_CNT_L);
> > +	dsb();
> > +	hi1 = __raw_readl(S5PV310_MCT_G_CNT_U);
> > +
> > +	if (hi0 != hi1) {
> > +		lo = __raw_readl(S5PV310_MCT_G_CNT_L);
> > +		hi1 = __raw_readl(S5PV310_MCT_G_CNT_U);
> > +	}
> 
> Are you sure this is safe?  Would it not be better to do this as:
> 
> 	hi1 = __raw_readl(S5PV310_MCT_G_CNT_U);
> 	do {
> 		hi0 = hi1;
> 		lo = __raw_readl(S5PV310_MCT_G_CNT_L);
> 		hi1 = __raw_readl(S5PV310_MCT_G_CNT_U);
> 	} while (hi0 != hi1);
> 
> Note that I don't believe you need a dsb() in there either, as device
> accesses are ordered with respect to themselves.  I know it's highly
> unlikely that we'll see two updates, but I think having it obviously
> robust is a good idea.

Yes, would be better..will change as your suggestion.

Merry Xmas :-)
Thanks.

Best regards,
Kgene.
--
Kukjin Kim <kgene.kim@samsung.com>, Senior Engineer,
SW Solution Development Team, Samsung Electronics Co., Ltd.

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH] ARM: S5PV310: Implement kernel timers using MCT
  2010-12-23 10:29     ` Kukjin Kim
@ 2010-12-23 12:22       ` Russell King - ARM Linux
  -1 siblings, 0 replies; 14+ messages in thread
From: Russell King - ARM Linux @ 2010-12-23 12:22 UTC (permalink / raw)
  To: Kukjin Kim
  Cc: linux-arm-kernel, linux-samsung-soc, ben-linux, 'Changhwan Youn'

On Thu, Dec 23, 2010 at 07:29:48PM +0900, Kukjin Kim wrote:
> Russell King - ARM Linux wrote:
> > This looks like it's supporting SMP - is there some reason not to use
> > the localtimer support built into SMP, which also supports hotplug CPU
> > (the above doesn't because the local clockevents will be automatically
> > unregistered.)
> > 
> As I know, to use the localtimer support, it needs to support PPI.
> Unfortunately the MCT of S5PV310 supports SPI. For hotplug CPU, I'm looking
> for the methods to register clockevents again at CPU hotplug-in.

We can avoid that - nothing forces you to use the PPI interrupt.
Just register your interrupt handler in the normal way.

There's really no need to reinvent the wheel.

^ permalink raw reply	[flat|nested] 14+ messages in thread

* [PATCH] ARM: S5PV310: Implement kernel timers using MCT
@ 2010-12-23 12:22       ` Russell King - ARM Linux
  0 siblings, 0 replies; 14+ messages in thread
From: Russell King - ARM Linux @ 2010-12-23 12:22 UTC (permalink / raw)
  To: linux-arm-kernel

On Thu, Dec 23, 2010 at 07:29:48PM +0900, Kukjin Kim wrote:
> Russell King - ARM Linux wrote:
> > This looks like it's supporting SMP - is there some reason not to use
> > the localtimer support built into SMP, which also supports hotplug CPU
> > (the above doesn't because the local clockevents will be automatically
> > unregistered.)
> > 
> As I know, to use the localtimer support, it needs to support PPI.
> Unfortunately the MCT of S5PV310 supports SPI. For hotplug CPU, I'm looking
> for the methods to register clockevents again at CPU hotplug-in.

We can avoid that - nothing forces you to use the PPI interrupt.
Just register your interrupt handler in the normal way.

There's really no need to reinvent the wheel.

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH] ARM: S5PV310: Implement kernel timers using MCT
  2010-12-23 10:24     ` Kukjin Kim
@ 2010-12-28  0:57       ` Jamie Iles
  -1 siblings, 0 replies; 14+ messages in thread
From: Jamie Iles @ 2010-12-28  0:57 UTC (permalink / raw)
  To: Kukjin Kim
  Cc: 'Jamie Iles',
	linux-arm-kernel, linux-samsung-soc, 'Russell King',
	'Changhwan Youn',
	ben-linux

On Thu, Dec 23, 2010 at 07:24:48PM +0900, Kukjin Kim wrote:
> Jamie Iles wrote:
> > > +irqreturn_t s5pv310_mct0_event_isr(int irq, void *dev_id)
> > > +{
> > > +	struct clock_event_device *evt;
> > > +
> > > +	/* Clear the MCT tick interrupt */
> > > +
> > > +	evt = &mct_tick0_device;
> > > +	if (evt->mode != CLOCK_EVT_MODE_PERIODIC)
> > > +		s5pv310_mct_tick_stop(MCT_TICK0);
> > 
> > I don't think you need to stop the timer here, the clockevents layer
> > will handle this for you.
> > 
> This is to support One-shot mode using MCT. Because basically MCT local
> timer only supports periodic mode.
> But will be added comment.

Actually I think you can simply remove the s5pv310_mct_tick_stop() 
altogether. The onestop event handlers should schedule the next expiry 
and will respect the min and max deltas so after the event the timer 
will be correctly programmed for the next event.

I had to try this on hardware to convince myself (the timers on my 
platform have the same behaviour) but it does work!

Jamie

^ permalink raw reply	[flat|nested] 14+ messages in thread

* [PATCH] ARM: S5PV310: Implement kernel timers using MCT
@ 2010-12-28  0:57       ` Jamie Iles
  0 siblings, 0 replies; 14+ messages in thread
From: Jamie Iles @ 2010-12-28  0:57 UTC (permalink / raw)
  To: linux-arm-kernel

On Thu, Dec 23, 2010 at 07:24:48PM +0900, Kukjin Kim wrote:
> Jamie Iles wrote:
> > > +irqreturn_t s5pv310_mct0_event_isr(int irq, void *dev_id)
> > > +{
> > > +	struct clock_event_device *evt;
> > > +
> > > +	/* Clear the MCT tick interrupt */
> > > +
> > > +	evt = &mct_tick0_device;
> > > +	if (evt->mode != CLOCK_EVT_MODE_PERIODIC)
> > > +		s5pv310_mct_tick_stop(MCT_TICK0);
> > 
> > I don't think you need to stop the timer here, the clockevents layer
> > will handle this for you.
> > 
> This is to support One-shot mode using MCT. Because basically MCT local
> timer only supports periodic mode.
> But will be added comment.

Actually I think you can simply remove the s5pv310_mct_tick_stop() 
altogether. The onestop event handlers should schedule the next expiry 
and will respect the min and max deltas so after the event the timer 
will be correctly programmed for the next event.

I had to try this on hardware to convince myself (the timers on my 
platform have the same behaviour) but it does work!

Jamie

^ permalink raw reply	[flat|nested] 14+ messages in thread

end of thread, other threads:[~2010-12-28  0:57 UTC | newest]

Thread overview: 14+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2010-12-22 11:27 [PATCH] ARM: S5PV310: Implement kernel timers using MCT Kukjin Kim
2010-12-22 11:27 ` Kukjin Kim
2010-12-22 12:35 ` Jamie Iles
2010-12-22 12:35   ` Jamie Iles
2010-12-23 10:24   ` Kukjin Kim
2010-12-23 10:24     ` Kukjin Kim
2010-12-28  0:57     ` Jamie Iles
2010-12-28  0:57       ` Jamie Iles
2010-12-22 22:55 ` Russell King - ARM Linux
2010-12-22 22:55   ` Russell King - ARM Linux
2010-12-23 10:29   ` Kukjin Kim
2010-12-23 10:29     ` Kukjin Kim
2010-12-23 12:22     ` Russell King - ARM Linux
2010-12-23 12:22       ` Russell King - ARM Linux

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.