These are cleanups and smaller changes that either depend on earlier feature branches or came in late during the development cycle. We normally try to get all cleanups early, so these are the exceptions: - A follow-up on the clocksource reworks, hopefully the last time we need to merge clocksource subsystem changes through arm-soc. A first set of patches was part of the original 3.10 arm-soc cleanup series because of interdependencies with timer drivers now moved out of arch/arm. - Migrating the SPEAr13xx platform away from using auxdata for DMA channel descriptions towards using information in device tree, based on the earlier SPEAr multiplatform series - A few follow-ups on the Atmel SAMA5 support and other changes for Atmel at91 based on the larger at91 reworks. - Moving the armada irqchip implementation to drivers/irqchip - Several OMAP cleanups following up on the larger series already merged in 3.10. -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.12 (GNU/Linux) iQIVAwUAUYj5U2CrR//JCVInAQLNIRAAvsCtYOmXTxkRBxdtNEUUbkEjx71Se7q0 h9PR8vqlkbYwONkJ8a6j8pKq/WJDmLpHQWg/moBsvlGc6uEVBPBFhCWHs1+yGUzX GhnJOaIKh3+651hIoXccS+/YZ16e1EAzdCM7+1QegPTldsRGkTOiwXgmR51kmPrz 6cZ8P5MFqMrWIy4XqWhOBbMDCY/An05IHMpniGIamUg2/uB921Z0wNFvDrnsg97u DsVEwimyCJ0j7aO4TH+fkvsjoGWnIhxPtpaIm8iff6TPRI49deRb3zYpnIONm+oG /cQrRf3BNW+aiTuRCTEjdBNGtcrYgN6CLWWjzgMhv1itSlX8swBcOhuNJRCGNQRI v3wL4aEBxUpPGGL8erc2GIW7pe29YC2UEYI2z1X/5MEzYO589zkkG2k+/3HQVUwp dnYpQxhjRMvh4mcodBJFRjzH1Z7agKUwtoKalAHRRH7r5gJDkpL3zLoMhYPTG5IZ OwU+aYf+dDxh2kKW0zs8a/qL97UTHjlTRUC9LPoumvJ7LlKeDfzEn7DHUm2gggiu dO9ye/NF/xEXoDXTl0Qp2wJ6/sbPSLyCYCIMdP/gJjWUiDDqqZ0VRaKL7vE/JWrd NJ7k5yunX8/kRgfqgRFLDdFnPj1JeYHlmexsq4l9TPbPstoIcbw8u1v9sr8aZF+Z agh9u4e7QU8= =HWfp -----END PGP SIGNATURE----- Merge tag 'cleanup-for-linus-2' of git://git.kernel.org/pub/scm/linux/kernel/git/arm/arm-soc Pull ARM SoC late cleanups from Arnd Bergmann: "These are cleanups and smaller changes that either depend on earlier feature branches or came in late during the development cycle. We normally try to get all cleanups early, so these are the exceptions: - A follow-up on the clocksource reworks, hopefully the last time we need to merge clocksource subsystem changes through arm-soc. A first set of patches was part of the original 3.10 arm-soc cleanup series because of interdependencies with timer drivers now moved out of arch/arm. - Migrating the SPEAr13xx platform away from using auxdata for DMA channel descriptions towards using information in device tree, based on the earlier SPEAr multiplatform series - A few follow-ups on the Atmel SAMA5 support and other changes for Atmel at91 based on the larger at91 reworks. - Moving the armada irqchip implementation to drivers/irqchip - Several OMAP cleanups following up on the larger series already merged in 3.10." * tag 'cleanup-for-linus-2' of git://git.kernel.org/pub/scm/linux/kernel/git/arm/arm-soc: (50 commits) ARM: OMAP4: change the device names in usb_bind_phy ARM: OMAP2+: Fix mismerge for timer.c betweenff931c82
andda4a686a
ARM: SPEAr: conditionalize SMP code ARM: arch_timer: Silence debug preempt warnings ARM: OMAP: remove unused variable serial: amba-pl011: fix !CONFIG_DMA_ENGINE case ata: arasan: remove the need for platform_data ARM: at91/sama5d34ek.dts: remove not needed compatibility string ARM: at91: dts: add MCI DMA support ARM: at91: dts: add i2c dma support ARM: at91: dts: set #dma-cells to the correct value ARM: at91: suspend both memory controllers on at91sam9263 irqchip: armada-370-xp: slightly cleanup irq controller driver irqchip: armada-370-xp: move IRQ handler to avoid forward declaration irqchip: move IRQ driver for Armada 370/XP ARM: mvebu: move L2 cache initialization in init_early() devtree: add binding documentation for sp804 ARM: integrator-cp: convert use CLKSRC_OF for timer init ARM: versatile: use OF init for sp804 timer ARM: versatile: add versatile dtbs to dtbs target ...
568 lines
14 KiB
C
568 lines
14 KiB
C
/* linux/arch/arm/mach-exynos4/mct.c
|
|
*
|
|
* Copyright (c) 2011 Samsung Electronics Co., Ltd.
|
|
* http://www.samsung.com
|
|
*
|
|
* EXYNOS4 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 <linux/percpu.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_irq.h>
|
|
#include <linux/of_address.h>
|
|
#include <linux/clocksource.h>
|
|
|
|
#include <asm/localtimer.h>
|
|
|
|
#include <plat/cpu.h>
|
|
|
|
#include <mach/map.h>
|
|
#include <mach/irqs.h>
|
|
#include <asm/mach/time.h>
|
|
|
|
#define EXYNOS4_MCTREG(x) (x)
|
|
#define EXYNOS4_MCT_G_CNT_L EXYNOS4_MCTREG(0x100)
|
|
#define EXYNOS4_MCT_G_CNT_U EXYNOS4_MCTREG(0x104)
|
|
#define EXYNOS4_MCT_G_CNT_WSTAT EXYNOS4_MCTREG(0x110)
|
|
#define EXYNOS4_MCT_G_COMP0_L EXYNOS4_MCTREG(0x200)
|
|
#define EXYNOS4_MCT_G_COMP0_U EXYNOS4_MCTREG(0x204)
|
|
#define EXYNOS4_MCT_G_COMP0_ADD_INCR EXYNOS4_MCTREG(0x208)
|
|
#define EXYNOS4_MCT_G_TCON EXYNOS4_MCTREG(0x240)
|
|
#define EXYNOS4_MCT_G_INT_CSTAT EXYNOS4_MCTREG(0x244)
|
|
#define EXYNOS4_MCT_G_INT_ENB EXYNOS4_MCTREG(0x248)
|
|
#define EXYNOS4_MCT_G_WSTAT EXYNOS4_MCTREG(0x24C)
|
|
#define _EXYNOS4_MCT_L_BASE EXYNOS4_MCTREG(0x300)
|
|
#define EXYNOS4_MCT_L_BASE(x) (_EXYNOS4_MCT_L_BASE + (0x100 * x))
|
|
#define EXYNOS4_MCT_L_MASK (0xffffff00)
|
|
|
|
#define MCT_L_TCNTB_OFFSET (0x00)
|
|
#define MCT_L_ICNTB_OFFSET (0x08)
|
|
#define MCT_L_TCON_OFFSET (0x20)
|
|
#define MCT_L_INT_CSTAT_OFFSET (0x30)
|
|
#define MCT_L_INT_ENB_OFFSET (0x34)
|
|
#define MCT_L_WSTAT_OFFSET (0x40)
|
|
#define MCT_G_TCON_START (1 << 8)
|
|
#define MCT_G_TCON_COMP0_AUTO_INC (1 << 1)
|
|
#define MCT_G_TCON_COMP0_ENABLE (1 << 0)
|
|
#define MCT_L_TCON_INTERVAL_MODE (1 << 2)
|
|
#define MCT_L_TCON_INT_START (1 << 1)
|
|
#define MCT_L_TCON_TIMER_START (1 << 0)
|
|
|
|
#define TICK_BASE_CNT 1
|
|
|
|
enum {
|
|
MCT_INT_SPI,
|
|
MCT_INT_PPI
|
|
};
|
|
|
|
enum {
|
|
MCT_G0_IRQ,
|
|
MCT_G1_IRQ,
|
|
MCT_G2_IRQ,
|
|
MCT_G3_IRQ,
|
|
MCT_L0_IRQ,
|
|
MCT_L1_IRQ,
|
|
MCT_L2_IRQ,
|
|
MCT_L3_IRQ,
|
|
MCT_NR_IRQS,
|
|
};
|
|
|
|
static void __iomem *reg_base;
|
|
static unsigned long clk_rate;
|
|
static unsigned int mct_int_type;
|
|
static int mct_irqs[MCT_NR_IRQS];
|
|
|
|
struct mct_clock_event_device {
|
|
struct clock_event_device *evt;
|
|
unsigned long base;
|
|
char name[10];
|
|
};
|
|
|
|
static void exynos4_mct_write(unsigned int value, unsigned long offset)
|
|
{
|
|
unsigned long stat_addr;
|
|
u32 mask;
|
|
u32 i;
|
|
|
|
__raw_writel(value, reg_base + offset);
|
|
|
|
if (likely(offset >= EXYNOS4_MCT_L_BASE(0))) {
|
|
stat_addr = (offset & ~EXYNOS4_MCT_L_MASK) + MCT_L_WSTAT_OFFSET;
|
|
switch (offset & EXYNOS4_MCT_L_MASK) {
|
|
case MCT_L_TCON_OFFSET:
|
|
mask = 1 << 3; /* L_TCON write status */
|
|
break;
|
|
case MCT_L_ICNTB_OFFSET:
|
|
mask = 1 << 1; /* L_ICNTB write status */
|
|
break;
|
|
case MCT_L_TCNTB_OFFSET:
|
|
mask = 1 << 0; /* L_TCNTB write status */
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
} else {
|
|
switch (offset) {
|
|
case EXYNOS4_MCT_G_TCON:
|
|
stat_addr = EXYNOS4_MCT_G_WSTAT;
|
|
mask = 1 << 16; /* G_TCON write status */
|
|
break;
|
|
case EXYNOS4_MCT_G_COMP0_L:
|
|
stat_addr = EXYNOS4_MCT_G_WSTAT;
|
|
mask = 1 << 0; /* G_COMP0_L write status */
|
|
break;
|
|
case EXYNOS4_MCT_G_COMP0_U:
|
|
stat_addr = EXYNOS4_MCT_G_WSTAT;
|
|
mask = 1 << 1; /* G_COMP0_U write status */
|
|
break;
|
|
case EXYNOS4_MCT_G_COMP0_ADD_INCR:
|
|
stat_addr = EXYNOS4_MCT_G_WSTAT;
|
|
mask = 1 << 2; /* G_COMP0_ADD_INCR w status */
|
|
break;
|
|
case EXYNOS4_MCT_G_CNT_L:
|
|
stat_addr = EXYNOS4_MCT_G_CNT_WSTAT;
|
|
mask = 1 << 0; /* G_CNT_L write status */
|
|
break;
|
|
case EXYNOS4_MCT_G_CNT_U:
|
|
stat_addr = EXYNOS4_MCT_G_CNT_WSTAT;
|
|
mask = 1 << 1; /* G_CNT_U write status */
|
|
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(reg_base + stat_addr) & mask) {
|
|
__raw_writel(mask, reg_base + stat_addr);
|
|
return;
|
|
}
|
|
|
|
panic("MCT hangs after writing %d (offset:0x%lx)\n", value, offset);
|
|
}
|
|
|
|
/* Clocksource handling */
|
|
static void exynos4_mct_frc_start(u32 hi, u32 lo)
|
|
{
|
|
u32 reg;
|
|
|
|
exynos4_mct_write(lo, EXYNOS4_MCT_G_CNT_L);
|
|
exynos4_mct_write(hi, EXYNOS4_MCT_G_CNT_U);
|
|
|
|
reg = __raw_readl(reg_base + EXYNOS4_MCT_G_TCON);
|
|
reg |= MCT_G_TCON_START;
|
|
exynos4_mct_write(reg, EXYNOS4_MCT_G_TCON);
|
|
}
|
|
|
|
static cycle_t exynos4_frc_read(struct clocksource *cs)
|
|
{
|
|
unsigned int lo, hi;
|
|
u32 hi2 = __raw_readl(reg_base + EXYNOS4_MCT_G_CNT_U);
|
|
|
|
do {
|
|
hi = hi2;
|
|
lo = __raw_readl(reg_base + EXYNOS4_MCT_G_CNT_L);
|
|
hi2 = __raw_readl(reg_base + EXYNOS4_MCT_G_CNT_U);
|
|
} while (hi != hi2);
|
|
|
|
return ((cycle_t)hi << 32) | lo;
|
|
}
|
|
|
|
static void exynos4_frc_resume(struct clocksource *cs)
|
|
{
|
|
exynos4_mct_frc_start(0, 0);
|
|
}
|
|
|
|
struct clocksource mct_frc = {
|
|
.name = "mct-frc",
|
|
.rating = 400,
|
|
.read = exynos4_frc_read,
|
|
.mask = CLOCKSOURCE_MASK(64),
|
|
.flags = CLOCK_SOURCE_IS_CONTINUOUS,
|
|
.resume = exynos4_frc_resume,
|
|
};
|
|
|
|
static void __init exynos4_clocksource_init(void)
|
|
{
|
|
exynos4_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 exynos4_mct_comp0_stop(void)
|
|
{
|
|
unsigned int tcon;
|
|
|
|
tcon = __raw_readl(reg_base + EXYNOS4_MCT_G_TCON);
|
|
tcon &= ~(MCT_G_TCON_COMP0_ENABLE | MCT_G_TCON_COMP0_AUTO_INC);
|
|
|
|
exynos4_mct_write(tcon, EXYNOS4_MCT_G_TCON);
|
|
exynos4_mct_write(0, EXYNOS4_MCT_G_INT_ENB);
|
|
}
|
|
|
|
static void exynos4_mct_comp0_start(enum clock_event_mode mode,
|
|
unsigned long cycles)
|
|
{
|
|
unsigned int tcon;
|
|
cycle_t comp_cycle;
|
|
|
|
tcon = __raw_readl(reg_base + EXYNOS4_MCT_G_TCON);
|
|
|
|
if (mode == CLOCK_EVT_MODE_PERIODIC) {
|
|
tcon |= MCT_G_TCON_COMP0_AUTO_INC;
|
|
exynos4_mct_write(cycles, EXYNOS4_MCT_G_COMP0_ADD_INCR);
|
|
}
|
|
|
|
comp_cycle = exynos4_frc_read(&mct_frc) + cycles;
|
|
exynos4_mct_write((u32)comp_cycle, EXYNOS4_MCT_G_COMP0_L);
|
|
exynos4_mct_write((u32)(comp_cycle >> 32), EXYNOS4_MCT_G_COMP0_U);
|
|
|
|
exynos4_mct_write(0x1, EXYNOS4_MCT_G_INT_ENB);
|
|
|
|
tcon |= MCT_G_TCON_COMP0_ENABLE;
|
|
exynos4_mct_write(tcon , EXYNOS4_MCT_G_TCON);
|
|
}
|
|
|
|
static int exynos4_comp_set_next_event(unsigned long cycles,
|
|
struct clock_event_device *evt)
|
|
{
|
|
exynos4_mct_comp0_start(evt->mode, cycles);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void exynos4_comp_set_mode(enum clock_event_mode mode,
|
|
struct clock_event_device *evt)
|
|
{
|
|
unsigned long cycles_per_jiffy;
|
|
exynos4_mct_comp0_stop();
|
|
|
|
switch (mode) {
|
|
case CLOCK_EVT_MODE_PERIODIC:
|
|
cycles_per_jiffy =
|
|
(((unsigned long long) NSEC_PER_SEC / HZ * evt->mult) >> evt->shift);
|
|
exynos4_mct_comp0_start(mode, cycles_per_jiffy);
|
|
break;
|
|
|
|
case CLOCK_EVT_MODE_ONESHOT:
|
|
case CLOCK_EVT_MODE_UNUSED:
|
|
case CLOCK_EVT_MODE_SHUTDOWN:
|
|
case CLOCK_EVT_MODE_RESUME:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static struct clock_event_device mct_comp_device = {
|
|
.name = "mct-comp",
|
|
.features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT,
|
|
.rating = 250,
|
|
.set_next_event = exynos4_comp_set_next_event,
|
|
.set_mode = exynos4_comp_set_mode,
|
|
};
|
|
|
|
static irqreturn_t exynos4_mct_comp_isr(int irq, void *dev_id)
|
|
{
|
|
struct clock_event_device *evt = dev_id;
|
|
|
|
exynos4_mct_write(0x1, EXYNOS4_MCT_G_INT_CSTAT);
|
|
|
|
evt->event_handler(evt);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static struct irqaction mct_comp_event_irq = {
|
|
.name = "mct_comp_irq",
|
|
.flags = IRQF_TIMER | IRQF_IRQPOLL,
|
|
.handler = exynos4_mct_comp_isr,
|
|
.dev_id = &mct_comp_device,
|
|
};
|
|
|
|
static void exynos4_clockevent_init(void)
|
|
{
|
|
mct_comp_device.cpumask = cpumask_of(0);
|
|
clockevents_config_and_register(&mct_comp_device, clk_rate,
|
|
0xf, 0xffffffff);
|
|
setup_irq(mct_irqs[MCT_G0_IRQ], &mct_comp_event_irq);
|
|
}
|
|
|
|
#ifdef CONFIG_LOCAL_TIMERS
|
|
|
|
static DEFINE_PER_CPU(struct mct_clock_event_device, percpu_mct_tick);
|
|
|
|
/* Clock event handling */
|
|
static void exynos4_mct_tick_stop(struct mct_clock_event_device *mevt)
|
|
{
|
|
unsigned long tmp;
|
|
unsigned long mask = MCT_L_TCON_INT_START | MCT_L_TCON_TIMER_START;
|
|
unsigned long offset = mevt->base + MCT_L_TCON_OFFSET;
|
|
|
|
tmp = __raw_readl(reg_base + offset);
|
|
if (tmp & mask) {
|
|
tmp &= ~mask;
|
|
exynos4_mct_write(tmp, offset);
|
|
}
|
|
}
|
|
|
|
static void exynos4_mct_tick_start(unsigned long cycles,
|
|
struct mct_clock_event_device *mevt)
|
|
{
|
|
unsigned long tmp;
|
|
|
|
exynos4_mct_tick_stop(mevt);
|
|
|
|
tmp = (1 << 31) | cycles; /* MCT_L_UPDATE_ICNTB */
|
|
|
|
/* update interrupt count buffer */
|
|
exynos4_mct_write(tmp, mevt->base + MCT_L_ICNTB_OFFSET);
|
|
|
|
/* enable MCT tick interrupt */
|
|
exynos4_mct_write(0x1, mevt->base + MCT_L_INT_ENB_OFFSET);
|
|
|
|
tmp = __raw_readl(reg_base + mevt->base + MCT_L_TCON_OFFSET);
|
|
tmp |= MCT_L_TCON_INT_START | MCT_L_TCON_TIMER_START |
|
|
MCT_L_TCON_INTERVAL_MODE;
|
|
exynos4_mct_write(tmp, mevt->base + MCT_L_TCON_OFFSET);
|
|
}
|
|
|
|
static int exynos4_tick_set_next_event(unsigned long cycles,
|
|
struct clock_event_device *evt)
|
|
{
|
|
struct mct_clock_event_device *mevt = this_cpu_ptr(&percpu_mct_tick);
|
|
|
|
exynos4_mct_tick_start(cycles, mevt);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline void exynos4_tick_set_mode(enum clock_event_mode mode,
|
|
struct clock_event_device *evt)
|
|
{
|
|
struct mct_clock_event_device *mevt = this_cpu_ptr(&percpu_mct_tick);
|
|
unsigned long cycles_per_jiffy;
|
|
|
|
exynos4_mct_tick_stop(mevt);
|
|
|
|
switch (mode) {
|
|
case CLOCK_EVT_MODE_PERIODIC:
|
|
cycles_per_jiffy =
|
|
(((unsigned long long) NSEC_PER_SEC / HZ * evt->mult) >> evt->shift);
|
|
exynos4_mct_tick_start(cycles_per_jiffy, mevt);
|
|
break;
|
|
|
|
case CLOCK_EVT_MODE_ONESHOT:
|
|
case CLOCK_EVT_MODE_UNUSED:
|
|
case CLOCK_EVT_MODE_SHUTDOWN:
|
|
case CLOCK_EVT_MODE_RESUME:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static int exynos4_mct_tick_clear(struct mct_clock_event_device *mevt)
|
|
{
|
|
struct clock_event_device *evt = mevt->evt;
|
|
|
|
/*
|
|
* This is for supporting oneshot mode.
|
|
* Mct would generate interrupt periodically
|
|
* without explicit stopping.
|
|
*/
|
|
if (evt->mode != CLOCK_EVT_MODE_PERIODIC)
|
|
exynos4_mct_tick_stop(mevt);
|
|
|
|
/* Clear the MCT tick interrupt */
|
|
if (__raw_readl(reg_base + mevt->base + MCT_L_INT_CSTAT_OFFSET) & 1) {
|
|
exynos4_mct_write(0x1, mevt->base + MCT_L_INT_CSTAT_OFFSET);
|
|
return 1;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static irqreturn_t exynos4_mct_tick_isr(int irq, void *dev_id)
|
|
{
|
|
struct mct_clock_event_device *mevt = dev_id;
|
|
struct clock_event_device *evt = mevt->evt;
|
|
|
|
exynos4_mct_tick_clear(mevt);
|
|
|
|
evt->event_handler(evt);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static struct irqaction mct_tick0_event_irq = {
|
|
.name = "mct_tick0_irq",
|
|
.flags = IRQF_TIMER | IRQF_NOBALANCING,
|
|
.handler = exynos4_mct_tick_isr,
|
|
};
|
|
|
|
static struct irqaction mct_tick1_event_irq = {
|
|
.name = "mct_tick1_irq",
|
|
.flags = IRQF_TIMER | IRQF_NOBALANCING,
|
|
.handler = exynos4_mct_tick_isr,
|
|
};
|
|
|
|
static int __cpuinit exynos4_local_timer_setup(struct clock_event_device *evt)
|
|
{
|
|
struct mct_clock_event_device *mevt;
|
|
unsigned int cpu = smp_processor_id();
|
|
|
|
mevt = this_cpu_ptr(&percpu_mct_tick);
|
|
mevt->evt = evt;
|
|
|
|
mevt->base = EXYNOS4_MCT_L_BASE(cpu);
|
|
sprintf(mevt->name, "mct_tick%d", cpu);
|
|
|
|
evt->name = mevt->name;
|
|
evt->cpumask = cpumask_of(cpu);
|
|
evt->set_next_event = exynos4_tick_set_next_event;
|
|
evt->set_mode = exynos4_tick_set_mode;
|
|
evt->features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT;
|
|
evt->rating = 450;
|
|
clockevents_config_and_register(evt, clk_rate / (TICK_BASE_CNT + 1),
|
|
0xf, 0x7fffffff);
|
|
|
|
exynos4_mct_write(TICK_BASE_CNT, mevt->base + MCT_L_TCNTB_OFFSET);
|
|
|
|
if (mct_int_type == MCT_INT_SPI) {
|
|
if (cpu == 0) {
|
|
mct_tick0_event_irq.dev_id = mevt;
|
|
evt->irq = mct_irqs[MCT_L0_IRQ];
|
|
setup_irq(evt->irq, &mct_tick0_event_irq);
|
|
} else {
|
|
mct_tick1_event_irq.dev_id = mevt;
|
|
evt->irq = mct_irqs[MCT_L1_IRQ];
|
|
setup_irq(evt->irq, &mct_tick1_event_irq);
|
|
irq_set_affinity(evt->irq, cpumask_of(1));
|
|
}
|
|
} else {
|
|
enable_percpu_irq(mct_irqs[MCT_L0_IRQ], 0);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void exynos4_local_timer_stop(struct clock_event_device *evt)
|
|
{
|
|
unsigned int cpu = smp_processor_id();
|
|
evt->set_mode(CLOCK_EVT_MODE_UNUSED, evt);
|
|
if (mct_int_type == MCT_INT_SPI)
|
|
if (cpu == 0)
|
|
remove_irq(evt->irq, &mct_tick0_event_irq);
|
|
else
|
|
remove_irq(evt->irq, &mct_tick1_event_irq);
|
|
else
|
|
disable_percpu_irq(mct_irqs[MCT_L0_IRQ]);
|
|
}
|
|
|
|
static struct local_timer_ops exynos4_mct_tick_ops __cpuinitdata = {
|
|
.setup = exynos4_local_timer_setup,
|
|
.stop = exynos4_local_timer_stop,
|
|
};
|
|
#endif /* CONFIG_LOCAL_TIMERS */
|
|
|
|
static void __init exynos4_timer_resources(struct device_node *np, void __iomem *base)
|
|
{
|
|
struct clk *mct_clk, *tick_clk;
|
|
|
|
tick_clk = np ? of_clk_get_by_name(np, "fin_pll") :
|
|
clk_get(NULL, "fin_pll");
|
|
if (IS_ERR(tick_clk))
|
|
panic("%s: unable to determine tick clock rate\n", __func__);
|
|
clk_rate = clk_get_rate(tick_clk);
|
|
|
|
mct_clk = np ? of_clk_get_by_name(np, "mct") : clk_get(NULL, "mct");
|
|
if (IS_ERR(mct_clk))
|
|
panic("%s: unable to retrieve mct clock instance\n", __func__);
|
|
clk_prepare_enable(mct_clk);
|
|
|
|
reg_base = base;
|
|
if (!reg_base)
|
|
panic("%s: unable to ioremap mct address space\n", __func__);
|
|
|
|
#ifdef CONFIG_LOCAL_TIMERS
|
|
if (mct_int_type == MCT_INT_PPI) {
|
|
int err;
|
|
|
|
err = request_percpu_irq(mct_irqs[MCT_L0_IRQ],
|
|
exynos4_mct_tick_isr, "MCT",
|
|
&percpu_mct_tick);
|
|
WARN(err, "MCT: can't request IRQ %d (%d)\n",
|
|
mct_irqs[MCT_L0_IRQ], err);
|
|
}
|
|
|
|
local_timer_register(&exynos4_mct_tick_ops);
|
|
#endif /* CONFIG_LOCAL_TIMERS */
|
|
}
|
|
|
|
void __init mct_init(void)
|
|
{
|
|
if (soc_is_exynos4210()) {
|
|
mct_irqs[MCT_G0_IRQ] = EXYNOS4_IRQ_MCT_G0;
|
|
mct_irqs[MCT_L0_IRQ] = EXYNOS4_IRQ_MCT_L0;
|
|
mct_irqs[MCT_L1_IRQ] = EXYNOS4_IRQ_MCT_L1;
|
|
mct_int_type = MCT_INT_SPI;
|
|
} else {
|
|
panic("unable to determine mct controller type\n");
|
|
}
|
|
|
|
exynos4_timer_resources(NULL, S5P_VA_SYSTIMER);
|
|
exynos4_clocksource_init();
|
|
exynos4_clockevent_init();
|
|
}
|
|
|
|
static void __init mct_init_dt(struct device_node *np, unsigned int int_type)
|
|
{
|
|
u32 nr_irqs, i;
|
|
|
|
mct_int_type = int_type;
|
|
|
|
/* This driver uses only one global timer interrupt */
|
|
mct_irqs[MCT_G0_IRQ] = irq_of_parse_and_map(np, MCT_G0_IRQ);
|
|
|
|
/*
|
|
* Find out the number of local irqs specified. The local
|
|
* timer irqs are specified after the four global timer
|
|
* irqs are specified.
|
|
*/
|
|
#ifdef CONFIG_OF
|
|
nr_irqs = of_irq_count(np);
|
|
#else
|
|
nr_irqs = 0;
|
|
#endif
|
|
for (i = MCT_L0_IRQ; i < nr_irqs; i++)
|
|
mct_irqs[i] = irq_of_parse_and_map(np, i);
|
|
|
|
exynos4_timer_resources(np, of_iomap(np, 0));
|
|
exynos4_clocksource_init();
|
|
exynos4_clockevent_init();
|
|
}
|
|
|
|
|
|
static void __init mct_init_spi(struct device_node *np)
|
|
{
|
|
return mct_init_dt(np, MCT_INT_SPI);
|
|
}
|
|
|
|
static void __init mct_init_ppi(struct device_node *np)
|
|
{
|
|
return mct_init_dt(np, MCT_INT_PPI);
|
|
}
|
|
CLOCKSOURCE_OF_DECLARE(exynos4210, "samsung,exynos4210-mct", mct_init_spi);
|
|
CLOCKSOURCE_OF_DECLARE(exynos4412, "samsung,exynos4412-mct", mct_init_ppi);
|