Files
Kernel/arch/mips/kernel/cevt-r4k-bcm-pwr.c

245 lines
7.4 KiB
C

/***********************************************************
*
* Copyright (c) 2009 Broadcom Corporation
* All Rights Reserved
*
* <:label-BRCM:2009:DUAL/GPL:standard
*
* Unless you and Broadcom execute a separate written software license
* agreement governing use of this software, this software is licensed
* to you under the terms of the GNU General Public License version 2
* (the "GPL"), available at http://www.broadcom.com/licenses/GPLv2.php,
* with the following added to such license:
*
* As a special exception, the copyright holders of this software give
* you permission to link this software with independent modules, and
* to copy and distribute the resulting executable under terms of your
* choice, provided that you also meet, for each linked independent
* module, the terms and conditions of the license of that module.
* An independent module is a module which is not derived from this
* software. The special exception does not apply to any modifications
* of the software.
*
* Not withstanding the above, under no circumstances may you combine
* this software in any way with any other Broadcom software provided
* under a license other than the GPL, without Broadcom's express prior
* written consent.
*
* :>
*
************************************************************/
/***********************************************************
*
* This file implements clock events for the Broadcom DSL and GPON CPE
* when the power management feature is enabled. When the processor
* is found to be mostly idle, the main CPU clock is slowed down to
* save power. By slowing down the clock, the C0 counter unfortunately
* also slows down. This file replaces the (typical) 1 msec clock tick
* interrupt processing with a reliable timer source which is unaffected
* by the change in MIPS clock changes.
*
* The timer available to replace the C0 timer works differently.
* The design needs to be adjusted accordingly. The C0 counter is a free
* running counter which wraps at 0xFFFFFFFF and which runs at different
* frequencies depending on the MIPS frequency. The C0 compare register
* requires to be programmed to stay ahead of the C0 counter, to generate
* an interrupt in the future.
*
* The peripheral timers (there are 3 of them) wrap at 0x3fffffff and
* run at 50 MHz. When the timer reaches a programmed value, it can generate
* and interrupt and then either stops counting or restarts at 0.
* This difference in behavior between the C0 counter and the peripheral timers
* required to use 2 timers for power management. One to generate the periodic
* interrupts required by the clock events (Timer 0), and one to keep an accurate
* reference when the clock is slowed down for saving power (Timer 2). Timer 1
* is planned to be used by the second processor to support SMP.
*
************************************************************/
#include <linux/clockchips.h>
#include <linux/interrupt.h>
#include <linux/percpu.h>
#include <linux/smp.h>
#include <asm/smtc_ipi.h>
#include <asm/time.h>
#include <asm/cevt-r4k.h>
#include <bcm_map_part.h>
#include <bcm_intr.h>
extern void BcmPwrMngtCheckWaitCount(void);
extern unsigned int TimerC0Snapshot0;
#if defined(CONFIG_SMP)
extern unsigned int TimerC0Snapshot1;
extern unsigned int C0divider, C0multiplier, C0ratio;
#endif
DEFINE_PER_CPU(struct clock_event_device, bcm_mips_clockevent_device);
int bcm_timer_irq_installed;
static int bcm_mips_next_event0(unsigned long delta,
struct clock_event_device *evt)
{
// Timer may be reprogrammed while it is already running, so clear it first
TIMER->TimerCtl0 = 0;
TIMER->TimerCnt0 = 0;
TIMER->TimerCtl0 = TIMERENABLE | RSTCNTCLR | delta;
return 0;
}
#if defined(CONFIG_SMP)
static int bcm_mips_next_event1(unsigned long delta,
struct clock_event_device *evt)
{
// Timer may be reprogrammed while it is already running, so clear it first
TIMER->TimerCtl1 = 0;
TIMER->TimerCnt1 = 0;
TIMER->TimerCtl1 = TIMERENABLE | RSTCNTCLR | delta;
return 0;
}
#endif
void bcm_mips_set_clock_mode(enum clock_event_mode mode,
struct clock_event_device *evt)
{
}
void bcm_mips_event_handler(struct clock_event_device *dev)
{
}
#if defined(CONFIG_SMP)
extern struct plat_smp_ops *mp_ops;
#endif
irqreturn_t bcm_timer_interrupt_handler_TP0(int irq, void *dev_id)
{
struct clock_event_device *cd;
irqreturn_t rc = IRQ_NONE;
byte timer_ints = TIMER->TimerInts & (TIMER0|TIMER1);
if (timer_ints & TIMER0) {
TIMER->TimerCtl0 = 0;
}
if (timer_ints & TIMER1) {
TIMER->TimerCtl1 = 0;
}
TIMER->TimerInts = timer_ints;
if (timer_ints & TIMER0) {
// Turn off timer
TIMER->TimerCtl0 = 0;
cd = &per_cpu(bcm_mips_clockevent_device, 0);
cd->event_handler(cd);
BcmPwrMngtCheckWaitCount();
rc = IRQ_HANDLED;
}
#if defined(CONFIG_SMP)
if (timer_ints & TIMER1) {
// Turn off timer
TIMER->TimerCtl1 = 0;
mp_ops->send_ipi_single(1, SMP_BCM_PWRSAVE_TIMER);
rc = IRQ_HANDLED;
}
#endif
return rc;
}
struct irqaction perf_timer_irqaction = {
.handler = bcm_timer_interrupt_handler_TP0,
.flags = IRQF_DISABLED|IRQF_SHARED,
.name = "Periph Timer",
};
#if defined(CONFIG_SMP)
void bcm_timer_interrupt_handler_TP1(void)
{
struct clock_event_device *cd;
cd = &per_cpu(bcm_mips_clockevent_device, 1);
cd->event_handler(cd);
BcmPwrMngtCheckWaitCount();
return;
}
#endif
int __cpuinit r4k_clockevent_init(void)
{
unsigned int cpu = smp_processor_id();
struct clock_event_device *cd;
cd = &per_cpu(bcm_mips_clockevent_device, cpu);
cd->name = "BCM Periph Timer";
cd->features = CLOCK_EVT_FEAT_ONESHOT;
/* Calculate the min / max delta */
cd->mult = div_sc((unsigned long) 50000000, NSEC_PER_SEC, 32);
cd->shift = 32;
cd->max_delta_ns = clockevent_delta2ns(0x3fffffff, cd);
cd->min_delta_ns = clockevent_delta2ns(0x300, cd);
cd->rating = 300;
cd->irq = INTERRUPT_ID_TIMER;
cd->cpumask = cpumask_of(cpu);
if (cpu == 0)
cd->set_next_event = bcm_mips_next_event0;
#if defined(CONFIG_SMP)
else
cd->set_next_event = bcm_mips_next_event1;
#endif
cd->set_mode = bcm_mips_set_clock_mode;
cd->event_handler = bcm_mips_event_handler;
clockevents_register_device(cd);
if (cpu == 0) {
// Start the BCM Timer interrupt
irq_set_affinity(INTERRUPT_ID_TIMER, cpumask_of(0));
setup_irq(INTERRUPT_ID_TIMER, &perf_timer_irqaction);
// Start the BCM Timer0 - keep accurate 1 msec tick count
TIMER->TimerCtl0 = TIMERENABLE | RSTCNTCLR | (50000-1);
TIMER->TimerMask |= TIMER0EN;
// Take a snapshot of the C0 timer when Timer2 was started
// This will be needed later when having to make adjustments
TimerC0Snapshot0 = read_c0_count();
// Start the BCM Timer2
// to keep an accurate free running high precision counter
// Count up to its maximum value so it can be used by csrc-r4k-bcm-pwr.c
TIMER->TimerCtl2 = TIMERENABLE | 0x3fffffff;
}
#if defined(CONFIG_SMP)
else {
unsigned int newTimerCnt, mult, rem, result;
// Start the BCM Timer1 - keep accurate 1 msec tick count
TIMER->TimerCtl1 = TIMERENABLE | RSTCNTCLR | (50000-1);
TIMER->TimerMask |= TIMER1EN;
// Take a snapshot of the C0 timer when Timer1 was started
// This will be needed later when having to make adjustments
TimerC0Snapshot1 = read_c0_count();
newTimerCnt = TIMER->TimerCnt2 & 0x3fffffff;
mult = newTimerCnt/C0divider;
rem = newTimerCnt%C0divider;
result = mult*C0multiplier + ((rem*C0ratio)>>10);
TimerC0Snapshot1 -= result;
}
#endif
return 0;
}