mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-11-01 17:08:10 +00:00
8d84981e39
Clockevent cleanup series from Shawn Guo. Resolved move/change conflict in mach-pxa/time.c due to the sys_timer cleanup. * clocksource/cleanup: clocksource: use clockevents_config_and_register() where possible ARM: use clockevents_config_and_register() where possible clockevents: export clockevents_config_and_register for module use + sync to Linux 3.8-rc3 Signed-off-by: Olof Johansson <olof@lixom.net> Conflicts: arch/arm/mach-pxa/time.c
156 lines
4.7 KiB
C
156 lines
4.7 KiB
C
/*
|
|
* arch/arm/mach-lpc32xx/timer.c
|
|
*
|
|
* Author: Kevin Wells <kevin.wells@nxp.com>
|
|
*
|
|
* Copyright (C) 2009 - 2010 NXP Semiconductors
|
|
* Copyright (C) 2009 Fontys University of Applied Sciences, Eindhoven
|
|
* Ed Schouten <e.schouten@fontys.nl>
|
|
* Laurens Timmermans <l.timmermans@fontys.nl>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*/
|
|
|
|
#include <linux/interrupt.h>
|
|
#include <linux/irq.h>
|
|
#include <linux/time.h>
|
|
#include <linux/err.h>
|
|
#include <linux/clockchips.h>
|
|
|
|
#include <asm/mach/time.h>
|
|
|
|
#include <mach/hardware.h>
|
|
#include <mach/platform.h>
|
|
#include "common.h"
|
|
|
|
static int lpc32xx_clkevt_next_event(unsigned long delta,
|
|
struct clock_event_device *dev)
|
|
{
|
|
__raw_writel(LPC32XX_TIMER_CNTR_TCR_RESET,
|
|
LPC32XX_TIMER_TCR(LPC32XX_TIMER0_BASE));
|
|
__raw_writel(delta, LPC32XX_TIMER_PR(LPC32XX_TIMER0_BASE));
|
|
__raw_writel(LPC32XX_TIMER_CNTR_TCR_EN,
|
|
LPC32XX_TIMER_TCR(LPC32XX_TIMER0_BASE));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void lpc32xx_clkevt_mode(enum clock_event_mode mode,
|
|
struct clock_event_device *dev)
|
|
{
|
|
switch (mode) {
|
|
case CLOCK_EVT_MODE_PERIODIC:
|
|
WARN_ON(1);
|
|
break;
|
|
|
|
case CLOCK_EVT_MODE_ONESHOT:
|
|
case CLOCK_EVT_MODE_SHUTDOWN:
|
|
/*
|
|
* Disable the timer. When using oneshot, we must also
|
|
* disable the timer to wait for the first call to
|
|
* set_next_event().
|
|
*/
|
|
__raw_writel(0, LPC32XX_TIMER_TCR(LPC32XX_TIMER0_BASE));
|
|
break;
|
|
|
|
case CLOCK_EVT_MODE_UNUSED:
|
|
case CLOCK_EVT_MODE_RESUME:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static struct clock_event_device lpc32xx_clkevt = {
|
|
.name = "lpc32xx_clkevt",
|
|
.features = CLOCK_EVT_FEAT_ONESHOT,
|
|
.rating = 300,
|
|
.set_next_event = lpc32xx_clkevt_next_event,
|
|
.set_mode = lpc32xx_clkevt_mode,
|
|
};
|
|
|
|
static irqreturn_t lpc32xx_timer_interrupt(int irq, void *dev_id)
|
|
{
|
|
struct clock_event_device *evt = &lpc32xx_clkevt;
|
|
|
|
/* Clear match */
|
|
__raw_writel(LPC32XX_TIMER_CNTR_MTCH_BIT(0),
|
|
LPC32XX_TIMER_IR(LPC32XX_TIMER0_BASE));
|
|
|
|
evt->event_handler(evt);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static struct irqaction lpc32xx_timer_irq = {
|
|
.name = "LPC32XX Timer Tick",
|
|
.flags = IRQF_DISABLED | IRQF_TIMER | IRQF_IRQPOLL,
|
|
.handler = lpc32xx_timer_interrupt,
|
|
};
|
|
|
|
/*
|
|
* The clock management driver isn't initialized at this point, so the
|
|
* clocks need to be enabled here manually and then tagged as used in
|
|
* the clock driver initialization
|
|
*/
|
|
void __init lpc32xx_timer_init(void)
|
|
{
|
|
u32 clkrate, pllreg;
|
|
|
|
/* Enable timer clock */
|
|
__raw_writel(LPC32XX_CLKPWR_TMRPWMCLK_TIMER0_EN |
|
|
LPC32XX_CLKPWR_TMRPWMCLK_TIMER1_EN,
|
|
LPC32XX_CLKPWR_TIMERS_PWMS_CLK_CTRL_1);
|
|
|
|
/*
|
|
* The clock driver isn't initialized at this point. So determine if
|
|
* the SYSCLK is driven from the PLL397 or main oscillator and then use
|
|
* it to compute the PLL frequency and the PCLK divider to get the base
|
|
* timer rates. This rate is needed to compute the tick rate.
|
|
*/
|
|
if (clk_is_sysclk_mainosc() != 0)
|
|
clkrate = LPC32XX_MAIN_OSC_FREQ;
|
|
else
|
|
clkrate = 397 * LPC32XX_CLOCK_OSC_FREQ;
|
|
|
|
/* Get ARM HCLKPLL register and convert it into a frequency */
|
|
pllreg = __raw_readl(LPC32XX_CLKPWR_HCLKPLL_CTRL) & 0x1FFFF;
|
|
clkrate = clk_get_pllrate_from_reg(clkrate, pllreg);
|
|
|
|
/* Get PCLK divider and divide ARM PLL clock by it to get timer rate */
|
|
clkrate = clkrate / clk_get_pclk_div();
|
|
|
|
/* Initial timer setup */
|
|
__raw_writel(0, LPC32XX_TIMER_TCR(LPC32XX_TIMER0_BASE));
|
|
__raw_writel(LPC32XX_TIMER_CNTR_MTCH_BIT(0),
|
|
LPC32XX_TIMER_IR(LPC32XX_TIMER0_BASE));
|
|
__raw_writel(1, LPC32XX_TIMER_MR0(LPC32XX_TIMER0_BASE));
|
|
__raw_writel(LPC32XX_TIMER_CNTR_MCR_MTCH(0) |
|
|
LPC32XX_TIMER_CNTR_MCR_STOP(0) |
|
|
LPC32XX_TIMER_CNTR_MCR_RESET(0),
|
|
LPC32XX_TIMER_MCR(LPC32XX_TIMER0_BASE));
|
|
|
|
/* Setup tick interrupt */
|
|
setup_irq(IRQ_LPC32XX_TIMER0, &lpc32xx_timer_irq);
|
|
|
|
/* Setup the clockevent structure. */
|
|
lpc32xx_clkevt.cpumask = cpumask_of(0);
|
|
clockevents_config_and_register(&lpc32xx_clkevt, clkrate, 1, -1);
|
|
|
|
/* Use timer1 as clock source. */
|
|
__raw_writel(LPC32XX_TIMER_CNTR_TCR_RESET,
|
|
LPC32XX_TIMER_TCR(LPC32XX_TIMER1_BASE));
|
|
__raw_writel(0, LPC32XX_TIMER_PR(LPC32XX_TIMER1_BASE));
|
|
__raw_writel(0, LPC32XX_TIMER_MCR(LPC32XX_TIMER1_BASE));
|
|
__raw_writel(LPC32XX_TIMER_CNTR_TCR_EN,
|
|
LPC32XX_TIMER_TCR(LPC32XX_TIMER1_BASE));
|
|
|
|
clocksource_mmio_init(LPC32XX_TIMER_TC(LPC32XX_TIMER1_BASE),
|
|
"lpc32xx_clksrc", clkrate, 300, 32, clocksource_mmio_readl_up);
|
|
}
|