Commit 79901024 authored by Oliver O'Halloran's avatar Oliver O'Halloran Committed by Michael Ellerman

powerpc/timer: Large Decrementer support

Power ISAv3 adds a large decrementer (LD) mode which increases the size
of the decrementer register. The size of the enlarged decrementer
register is between 32 and 64 bits with the exact size being dependent
on the implementation. When in LD mode, reads are sign extended to 64
bits and a decrementer exception is raised when the high bit is set (i.e
the value goes below zero). Writes however are truncated to the physical
register width so some care needs to be taken to ensure that the high
bit is not set when reloading the decrementer. This patch adds support
for using the LD inside the host kernel on processors that support it.

When LD mode is supported firmware will supply the ibm,dec-bits property
for CPU nodes to allow the kernel to determine the maximum decrementer
value. Enabling LD mode is a hypervisor privileged operation so the kernel
can only enable it manually when running in hypervisor mode. Guests that
support LD mode can request it using the "ibm,client-architecture-support"
firmware call (not implemented in this patch) or some other platform
specific method. If this property is not supplied then the traditional
decrementer width of 32 bit is assumed and LD mode will not be enabled.

This patch was based on initial work by Jack Miller.
Signed-off-by: default avatarOliver O'Halloran <oohall@gmail.com>
Signed-off-by: default avatarBalbir Singh <bsingharora@gmail.com>
Acked-by: default avatarMichael Neuling <mikey@neuling.org>
Signed-off-by: default avatarMichael Ellerman <mpe@ellerman.id.au>
parent 9ddf0075
...@@ -337,6 +337,7 @@ ...@@ -337,6 +337,7 @@
#define LPCR_AIL_0 0x00000000 /* MMU off exception offset 0x0 */ #define LPCR_AIL_0 0x00000000 /* MMU off exception offset 0x0 */
#define LPCR_AIL_3 0x01800000 /* MMU on exception offset 0xc00...4xxx */ #define LPCR_AIL_3 0x01800000 /* MMU on exception offset 0xc00...4xxx */
#define LPCR_ONL 0x00040000 /* online - PURR/SPURR count */ #define LPCR_ONL 0x00040000 /* online - PURR/SPURR count */
#define LPCR_LD 0x00020000 /* large decremeter */
#define LPCR_PECE 0x0001f000 /* powersave exit cause enable */ #define LPCR_PECE 0x0001f000 /* powersave exit cause enable */
#define LPCR_PECEDP 0x00010000 /* directed priv dbells cause exit */ #define LPCR_PECEDP 0x00010000 /* directed priv dbells cause exit */
#define LPCR_PECEDH 0x00008000 /* directed hyp dbells cause exit */ #define LPCR_PECEDH 0x00008000 /* directed hyp dbells cause exit */
......
...@@ -146,7 +146,7 @@ static inline void set_tb(unsigned int upper, unsigned int lower) ...@@ -146,7 +146,7 @@ static inline void set_tb(unsigned int upper, unsigned int lower)
* in auto-reload mode. The problem is PIT stops counting when it * in auto-reload mode. The problem is PIT stops counting when it
* hits zero. If it would wrap, we could use it just like a decrementer. * hits zero. If it would wrap, we could use it just like a decrementer.
*/ */
static inline unsigned int get_dec(void) static inline u64 get_dec(void)
{ {
#if defined(CONFIG_40x) #if defined(CONFIG_40x)
return (mfspr(SPRN_PIT)); return (mfspr(SPRN_PIT));
...@@ -160,10 +160,10 @@ static inline unsigned int get_dec(void) ...@@ -160,10 +160,10 @@ static inline unsigned int get_dec(void)
* in when the decrementer generates its interrupt: on the 1 to 0 * in when the decrementer generates its interrupt: on the 1 to 0
* transition for Book E/4xx, but on the 0 to -1 transition for others. * transition for Book E/4xx, but on the 0 to -1 transition for others.
*/ */
static inline void set_dec(int val) static inline void set_dec(u64 val)
{ {
#if defined(CONFIG_40x) #if defined(CONFIG_40x)
mtspr(SPRN_PIT, val); mtspr(SPRN_PIT, (u32) val);
#else #else
#ifndef CONFIG_BOOKE #ifndef CONFIG_BOOKE
--val; --val;
......
...@@ -96,7 +96,8 @@ static struct clocksource clocksource_timebase = { ...@@ -96,7 +96,8 @@ static struct clocksource clocksource_timebase = {
.read = timebase_read, .read = timebase_read,
}; };
#define DECREMENTER_MAX 0x7fffffff #define DECREMENTER_DEFAULT_MAX 0x7FFFFFFF
u64 decrementer_max = DECREMENTER_DEFAULT_MAX;
static int decrementer_set_next_event(unsigned long evt, static int decrementer_set_next_event(unsigned long evt,
struct clock_event_device *dev); struct clock_event_device *dev);
...@@ -504,8 +505,8 @@ static void __timer_interrupt(void) ...@@ -504,8 +505,8 @@ static void __timer_interrupt(void)
__this_cpu_inc(irq_stat.timer_irqs_event); __this_cpu_inc(irq_stat.timer_irqs_event);
} else { } else {
now = *next_tb - now; now = *next_tb - now;
if (now <= DECREMENTER_MAX) if (now <= decrementer_max)
set_dec((int)now); set_dec(now);
/* We may have raced with new irq work */ /* We may have raced with new irq work */
if (test_irq_work_pending()) if (test_irq_work_pending())
set_dec(1); set_dec(1);
...@@ -535,7 +536,7 @@ void timer_interrupt(struct pt_regs * regs) ...@@ -535,7 +536,7 @@ void timer_interrupt(struct pt_regs * regs)
/* Ensure a positive value is written to the decrementer, or else /* Ensure a positive value is written to the decrementer, or else
* some CPUs will continue to take decrementer exceptions. * some CPUs will continue to take decrementer exceptions.
*/ */
set_dec(DECREMENTER_MAX); set_dec(decrementer_max);
/* Some implementations of hotplug will get timer interrupts while /* Some implementations of hotplug will get timer interrupts while
* offline, just ignore these and we also need to set * offline, just ignore these and we also need to set
...@@ -583,9 +584,9 @@ static void generic_suspend_disable_irqs(void) ...@@ -583,9 +584,9 @@ static void generic_suspend_disable_irqs(void)
* with suspending. * with suspending.
*/ */
set_dec(DECREMENTER_MAX); set_dec(decrementer_max);
local_irq_disable(); local_irq_disable();
set_dec(DECREMENTER_MAX); set_dec(decrementer_max);
} }
static void generic_suspend_enable_irqs(void) static void generic_suspend_enable_irqs(void)
...@@ -866,7 +867,7 @@ static int decrementer_set_next_event(unsigned long evt, ...@@ -866,7 +867,7 @@ static int decrementer_set_next_event(unsigned long evt,
static int decrementer_shutdown(struct clock_event_device *dev) static int decrementer_shutdown(struct clock_event_device *dev)
{ {
decrementer_set_next_event(DECREMENTER_MAX, dev); decrementer_set_next_event(decrementer_max, dev);
return 0; return 0;
} }
...@@ -892,6 +893,49 @@ static void register_decrementer_clockevent(int cpu) ...@@ -892,6 +893,49 @@ static void register_decrementer_clockevent(int cpu)
clockevents_register_device(dec); clockevents_register_device(dec);
} }
static void enable_large_decrementer(void)
{
if (!cpu_has_feature(CPU_FTR_ARCH_300))
return;
if (decrementer_max <= DECREMENTER_DEFAULT_MAX)
return;
/*
* If we're running as the hypervisor we need to enable the LD manually
* otherwise firmware should have done it for us.
*/
if (cpu_has_feature(CPU_FTR_HVMODE))
mtspr(SPRN_LPCR, mfspr(SPRN_LPCR) | LPCR_LD);
}
static void __init set_decrementer_max(void)
{
struct device_node *cpu;
u32 bits = 32;
/* Prior to ISAv3 the decrementer is always 32 bit */
if (!cpu_has_feature(CPU_FTR_ARCH_300))
return;
cpu = of_find_node_by_type(NULL, "cpu");
if (of_property_read_u32(cpu, "ibm,dec-bits", &bits) == 0) {
if (bits > 64 || bits < 32) {
pr_warn("time_init: firmware supplied invalid ibm,dec-bits");
bits = 32;
}
/* calculate the signed maximum given this many bits */
decrementer_max = (1ul << (bits - 1)) - 1;
}
of_node_put(cpu);
pr_info("time_init: %u bit decrementer (max: %llx)\n",
bits, decrementer_max);
}
static void __init init_decrementer_clockevent(void) static void __init init_decrementer_clockevent(void)
{ {
int cpu = smp_processor_id(); int cpu = smp_processor_id();
...@@ -899,7 +943,7 @@ static void __init init_decrementer_clockevent(void) ...@@ -899,7 +943,7 @@ static void __init init_decrementer_clockevent(void)
clockevents_calc_mult_shift(&decrementer_clockevent, ppc_tb_freq, 4); clockevents_calc_mult_shift(&decrementer_clockevent, ppc_tb_freq, 4);
decrementer_clockevent.max_delta_ns = decrementer_clockevent.max_delta_ns =
clockevent_delta2ns(DECREMENTER_MAX, &decrementer_clockevent); clockevent_delta2ns(decrementer_max, &decrementer_clockevent);
decrementer_clockevent.min_delta_ns = decrementer_clockevent.min_delta_ns =
clockevent_delta2ns(2, &decrementer_clockevent); clockevent_delta2ns(2, &decrementer_clockevent);
...@@ -908,6 +952,9 @@ static void __init init_decrementer_clockevent(void) ...@@ -908,6 +952,9 @@ static void __init init_decrementer_clockevent(void)
void secondary_cpu_time_init(void) void secondary_cpu_time_init(void)
{ {
/* Enable and test the large decrementer for this cpu */
enable_large_decrementer();
/* Start the decrementer on CPUs that have manual control /* Start the decrementer on CPUs that have manual control
* such as BookE * such as BookE
*/ */
...@@ -973,6 +1020,10 @@ void __init time_init(void) ...@@ -973,6 +1020,10 @@ void __init time_init(void)
vdso_data->tb_update_count = 0; vdso_data->tb_update_count = 0;
vdso_data->tb_ticks_per_sec = tb_ticks_per_sec; vdso_data->tb_ticks_per_sec = tb_ticks_per_sec;
/* initialise and enable the large decrementer (if we have one) */
set_decrementer_max();
enable_large_decrementer();
/* Start the decrementer on CPUs that have manual control /* Start the decrementer on CPUs that have manual control
* such as BookE * such as BookE
*/ */
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment