msm: timer: SMP timer support for msm

The msm provides timer hardware that is private to each core. Each
timer has separate counter and match registers, so we create separate
clock_event_devices for each core. For the global clocksource, use
cpu 0's counter.

Signed-off-by: Jeff Ohlstein <johlstei@codeaurora.org>
Signed-off-by: David Brown <davidb@codeaurora.org>
This commit is contained in:
Jeff Ohlstein 2010-12-02 12:05:12 -08:00 committed by David Brown
parent 7b181446c6
commit 94790ec25f
3 changed files with 108 additions and 24 deletions

View File

@ -60,7 +60,11 @@
#define MSM_TMR_BASE IOMEM(0xF0200000) #define MSM_TMR_BASE IOMEM(0xF0200000)
#define MSM_TMR_PHYS 0x02000000 #define MSM_TMR_PHYS 0x02000000
#define MSM_TMR_SIZE (SZ_1M) #define MSM_TMR_SIZE SZ_4K
#define MSM_TMR0_BASE IOMEM(0xF0201000)
#define MSM_TMR0_PHYS 0x02040000
#define MSM_TMR0_SIZE SZ_4K
#define MSM_GPT_BASE (MSM_TMR_BASE + 0x4) #define MSM_GPT_BASE (MSM_TMR_BASE + 0x4)
#define MSM_DGT_BASE (MSM_TMR_BASE + 0x24) #define MSM_DGT_BASE (MSM_TMR_BASE + 0x24)

View File

@ -105,6 +105,7 @@ static struct map_desc msm8x60_io_desc[] __initdata = {
MSM_DEVICE(QGIC_DIST), MSM_DEVICE(QGIC_DIST),
MSM_DEVICE(QGIC_CPU), MSM_DEVICE(QGIC_CPU),
MSM_DEVICE(TMR), MSM_DEVICE(TMR),
MSM_DEVICE(TMR0),
MSM_DEVICE(ACC), MSM_DEVICE(ACC),
MSM_DEVICE(GCC), MSM_DEVICE(GCC),
}; };

View File

@ -47,6 +47,19 @@ enum {
#define GPT_HZ 32768 #define GPT_HZ 32768
enum timer_location {
LOCAL_TIMER = 0,
GLOBAL_TIMER = 1,
};
#ifdef MSM_TMR0_BASE
#define MSM_TMR_GLOBAL (MSM_TMR0_BASE - MSM_TMR_BASE)
#else
#define MSM_TMR_GLOBAL 0
#endif
#define MSM_GLOBAL_TIMER MSM_CLOCK_DGT
#if defined(CONFIG_ARCH_QSD8X50) #if defined(CONFIG_ARCH_QSD8X50)
#define DGT_HZ (19200000 / 4) /* 19.2 MHz / 4 by default */ #define DGT_HZ (19200000 / 4) /* 19.2 MHz / 4 by default */
#define MSM_DGT_SHIFT (0) #define MSM_DGT_SHIFT (0)
@ -65,49 +78,67 @@ struct msm_clock {
void __iomem *regbase; void __iomem *regbase;
uint32_t freq; uint32_t freq;
uint32_t shift; uint32_t shift;
void __iomem *global_counter;
void __iomem *local_counter;
}; };
enum {
MSM_CLOCK_GPT,
MSM_CLOCK_DGT,
NR_TIMERS,
};
static struct msm_clock msm_clocks[];
static struct clock_event_device *local_clock_event;
static irqreturn_t msm_timer_interrupt(int irq, void *dev_id) static irqreturn_t msm_timer_interrupt(int irq, void *dev_id)
{ {
struct clock_event_device *evt = dev_id; struct clock_event_device *evt = dev_id;
if (smp_processor_id() != 0)
evt = local_clock_event;
if (evt->event_handler == NULL)
return IRQ_HANDLED;
evt->event_handler(evt); evt->event_handler(evt);
return IRQ_HANDLED; return IRQ_HANDLED;
} }
static cycle_t msm_gpt_read(struct clocksource *cs) static cycle_t msm_read_timer_count(struct clocksource *cs)
{ {
return readl(MSM_GPT_BASE + TIMER_COUNT_VAL); struct msm_clock *clk = container_of(cs, struct msm_clock, clocksource);
return readl(clk->global_counter);
} }
static cycle_t msm_dgt_read(struct clocksource *cs) static struct msm_clock *clockevent_to_clock(struct clock_event_device *evt)
{ {
return readl(MSM_DGT_BASE + TIMER_COUNT_VAL) >> MSM_DGT_SHIFT; #ifdef CONFIG_SMP
int i;
for (i = 0; i < NR_TIMERS; i++)
if (evt == &(msm_clocks[i].clockevent))
return &msm_clocks[i];
return &msm_clocks[MSM_GLOBAL_TIMER];
#else
return container_of(evt, struct msm_clock, clockevent);
#endif
} }
static int msm_timer_set_next_event(unsigned long cycles, static int msm_timer_set_next_event(unsigned long cycles,
struct clock_event_device *evt) struct clock_event_device *evt)
{ {
struct msm_clock *clock = container_of(evt, struct msm_clock, clockevent); struct msm_clock *clock = clockevent_to_clock(evt);
uint32_t now = readl(clock->regbase + TIMER_COUNT_VAL); uint32_t now = readl(clock->local_counter);
uint32_t alarm = now + (cycles << clock->shift); uint32_t alarm = now + (cycles << clock->shift);
int late;
writel(alarm, clock->regbase + TIMER_MATCH_VAL); writel(alarm, clock->regbase + TIMER_MATCH_VAL);
now = readl(clock->regbase + TIMER_COUNT_VAL);
late = now - alarm;
if (late >= (-2 << clock->shift) && late < DGT_HZ*5) {
printk(KERN_NOTICE "msm_timer_set_next_event(%lu) clock %s, "
"alarm already expired, now %x, alarm %x, late %d\n",
cycles, clock->clockevent.name, now, alarm, late);
return -ETIME;
}
return 0; return 0;
} }
static void msm_timer_set_mode(enum clock_event_mode mode, static void msm_timer_set_mode(enum clock_event_mode mode,
struct clock_event_device *evt) struct clock_event_device *evt)
{ {
struct msm_clock *clock = container_of(evt, struct msm_clock, clockevent); struct msm_clock *clock = clockevent_to_clock(evt);
switch (mode) { switch (mode) {
case CLOCK_EVT_MODE_RESUME: case CLOCK_EVT_MODE_RESUME:
case CLOCK_EVT_MODE_PERIODIC: case CLOCK_EVT_MODE_PERIODIC:
@ -123,7 +154,7 @@ static void msm_timer_set_mode(enum clock_event_mode mode,
} }
static struct msm_clock msm_clocks[] = { static struct msm_clock msm_clocks[] = {
{ [MSM_CLOCK_GPT] = {
.clockevent = { .clockevent = {
.name = "gp_timer", .name = "gp_timer",
.features = CLOCK_EVT_FEAT_ONESHOT, .features = CLOCK_EVT_FEAT_ONESHOT,
@ -135,7 +166,7 @@ static struct msm_clock msm_clocks[] = {
.clocksource = { .clocksource = {
.name = "gp_timer", .name = "gp_timer",
.rating = 200, .rating = 200,
.read = msm_gpt_read, .read = msm_read_timer_count,
.mask = CLOCKSOURCE_MASK(32), .mask = CLOCKSOURCE_MASK(32),
.flags = CLOCK_SOURCE_IS_CONTINUOUS, .flags = CLOCK_SOURCE_IS_CONTINUOUS,
}, },
@ -147,9 +178,12 @@ static struct msm_clock msm_clocks[] = {
.irq = INT_GP_TIMER_EXP .irq = INT_GP_TIMER_EXP
}, },
.regbase = MSM_GPT_BASE, .regbase = MSM_GPT_BASE,
.freq = GPT_HZ .freq = GPT_HZ,
.local_counter = MSM_GPT_BASE + TIMER_COUNT_VAL,
.global_counter = MSM_GPT_BASE + TIMER_COUNT_VAL +
MSM_TMR_GLOBAL,
}, },
{ [MSM_CLOCK_DGT] = {
.clockevent = { .clockevent = {
.name = "dg_timer", .name = "dg_timer",
.features = CLOCK_EVT_FEAT_ONESHOT, .features = CLOCK_EVT_FEAT_ONESHOT,
@ -161,7 +195,7 @@ static struct msm_clock msm_clocks[] = {
.clocksource = { .clocksource = {
.name = "dg_timer", .name = "dg_timer",
.rating = 300, .rating = 300,
.read = msm_dgt_read, .read = msm_read_timer_count,
.mask = CLOCKSOURCE_MASK((32 - MSM_DGT_SHIFT)), .mask = CLOCKSOURCE_MASK((32 - MSM_DGT_SHIFT)),
.flags = CLOCK_SOURCE_IS_CONTINUOUS, .flags = CLOCK_SOURCE_IS_CONTINUOUS,
}, },
@ -174,7 +208,10 @@ static struct msm_clock msm_clocks[] = {
}, },
.regbase = MSM_DGT_BASE, .regbase = MSM_DGT_BASE,
.freq = DGT_HZ >> MSM_DGT_SHIFT, .freq = DGT_HZ >> MSM_DGT_SHIFT,
.shift = MSM_DGT_SHIFT .shift = MSM_DGT_SHIFT,
.local_counter = MSM_DGT_BASE + TIMER_COUNT_VAL,
.global_counter = MSM_DGT_BASE + TIMER_COUNT_VAL +
MSM_TMR_GLOBAL,
} }
}; };
@ -183,7 +220,7 @@ static void __init msm_timer_init(void)
int i; int i;
int res; int res;
#ifdef CONFIG_ARCH_MSM8X60 #ifdef CONFIG_ARCH_MSM_SCORPIONMP
writel(DGT_CLK_CTL_DIV_4, MSM_TMR_BASE + DGT_CLK_CTL); writel(DGT_CLK_CTL_DIV_4, MSM_TMR_BASE + DGT_CLK_CTL);
#endif #endif
@ -217,6 +254,48 @@ static void __init msm_timer_init(void)
} }
} }
#ifdef CONFIG_SMP
void __cpuinit local_timer_setup(struct clock_event_device *evt)
{
struct msm_clock *clock = &msm_clocks[MSM_GLOBAL_TIMER];
/* Use existing clock_event for cpu 0 */
if (!smp_processor_id())
return;
writel(DGT_CLK_CTL_DIV_4, MSM_TMR_BASE + DGT_CLK_CTL);
if (!local_clock_event) {
writel(0, clock->regbase + TIMER_ENABLE);
writel(0, clock->regbase + TIMER_CLEAR);
writel(~0, clock->regbase + TIMER_MATCH_VAL);
}
evt->irq = clock->irq.irq;
evt->name = "local_timer";
evt->features = CLOCK_EVT_FEAT_ONESHOT;
evt->rating = clock->clockevent.rating;
evt->set_mode = msm_timer_set_mode;
evt->set_next_event = msm_timer_set_next_event;
evt->shift = clock->clockevent.shift;
evt->mult = div_sc(clock->freq, NSEC_PER_SEC, evt->shift);
evt->max_delta_ns =
clockevent_delta2ns(0xf0000000 >> clock->shift, evt);
evt->min_delta_ns = clockevent_delta2ns(4, evt);
local_clock_event = evt;
gic_enable_ppi(clock->irq.irq);
clockevents_register_device(evt);
}
inline int local_timer_ack(void)
{
return 1;
}
#endif
struct sys_timer msm_timer = { struct sys_timer msm_timer = {
.init = msm_timer_init .init = msm_timer_init
}; };