diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2012-12-12 11:51:39 -0800 | 
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2012-12-12 11:51:39 -0800 | 
| commit | d01e4afdbb65e030fd6f1f96c30a558e2eb0f279 (patch) | |
| tree | 02ef82b2740cf93a98199eded5ef765fa6e03052 /arch/arm/mach-imx/time.c | |
| parent | 8287361abca36504da813638310d2547469283eb (diff) | |
| parent | 794b175fc0c0c4844dbb7b137a73bbfd01f6c608 (diff) | |
| download | olio-linux-3.10-d01e4afdbb65e030fd6f1f96c30a558e2eb0f279.tar.xz olio-linux-3.10-d01e4afdbb65e030fd6f1f96c30a558e2eb0f279.zip  | |
Merge tag 'cleanup' of git://git.kernel.org/pub/scm/linux/kernel/git/arm/arm-soc
Pull ARM SoC cleanups on various subarchitectures from Olof Johansson:
 "Cleanup patches for various ARM platforms and some of their associated
  drivers.  There's also a branch in here that enables Freescale i.MX to
  be part of the multiplatform support -- the first "big" SoC that is
  moved over (more multiplatform work comes in a separate branch later
  during the merge window)."
Conflicts fixed as per Olof, including a silent semantic one in
arch/arm/mach-omap2/board-generic.c (omap_prcm_restart() was renamed to
omap3xxx_restart(), and a new user of the old name was added).
* tag 'cleanup' of git://git.kernel.org/pub/scm/linux/kernel/git/arm/arm-soc: (189 commits)
  ARM: omap: fix typo on timer cleanup
  ARM: EXYNOS: Remove unused regs-mem.h file
  ARM: EXYNOS: Remove unused non-dt support for dwmci controller
  ARM: Kirkwood: Use hw_pci.ops instead of hw_pci.scan
  ARM: OMAP3: cm-t3517: use GPTIMER for system clock
  ARM: OMAP2+: timer: remove CONFIG_OMAP_32K_TIMER
  ARM: SAMSUNG: use devm_ functions for ADC driver
  ARM: EXYNOS: no duplicate mask/unmask in eint0_15
  ARM: S3C24XX: SPI clock channel setup is fixed for S3C2443
  ARM: EXYNOS: Remove i2c0 resource information and setting of device names
  ARM: Kirkwood: checkpatch cleanups
  ARM: Kirkwood: Fix sparse warnings.
  ARM: Kirkwood: Remove unused includes
  ARM: kirkwood: cleanup lsxl board includes
  ARM: integrator: use BUG_ON where possible
  ARM: integrator: push down SC dependencies
  ARM: integrator: delete static UART1 mapping
  ARM: integrator: delete SC mapping on the CP
  ARM: integrator: remove static CP syscon mapping
  ARM: integrator: remove static AP syscon mapping
  ...
Diffstat (limited to 'arch/arm/mach-imx/time.c')
| -rw-r--r-- | arch/arm/mach-imx/time.c | 326 | 
1 files changed, 326 insertions, 0 deletions
diff --git a/arch/arm/mach-imx/time.c b/arch/arm/mach-imx/time.c new file mode 100644 index 00000000000..f017302f6d0 --- /dev/null +++ b/arch/arm/mach-imx/time.c @@ -0,0 +1,326 @@ +/* + *  linux/arch/arm/plat-mxc/time.c + * + *  Copyright (C) 2000-2001 Deep Blue Solutions + *  Copyright (C) 2002 Shane Nay (shane@minirl.com) + *  Copyright (C) 2006-2007 Pavel Pisa (ppisa@pikron.com) + *  Copyright (C) 2008 Juergen Beisert (kernel@pengutronix.de) + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ + +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/clockchips.h> +#include <linux/clk.h> +#include <linux/err.h> + +#include <asm/sched_clock.h> +#include <asm/mach/time.h> + +#include "common.h" +#include "hardware.h" + +/* + * There are 2 versions of the timer hardware on Freescale MXC hardware. + * Version 1: MX1/MXL, MX21, MX27. + * Version 2: MX25, MX31, MX35, MX37, MX51 + */ + +/* defines common for all i.MX */ +#define MXC_TCTL		0x00 +#define MXC_TCTL_TEN		(1 << 0) /* Enable module */ +#define MXC_TPRER		0x04 + +/* MX1, MX21, MX27 */ +#define MX1_2_TCTL_CLK_PCLK1	(1 << 1) +#define MX1_2_TCTL_IRQEN	(1 << 4) +#define MX1_2_TCTL_FRR		(1 << 8) +#define MX1_2_TCMP		0x08 +#define MX1_2_TCN		0x10 +#define MX1_2_TSTAT		0x14 + +/* MX21, MX27 */ +#define MX2_TSTAT_CAPT		(1 << 1) +#define MX2_TSTAT_COMP		(1 << 0) + +/* MX31, MX35, MX25, MX5 */ +#define V2_TCTL_WAITEN		(1 << 3) /* Wait enable mode */ +#define V2_TCTL_CLK_IPG		(1 << 6) +#define V2_TCTL_CLK_PER		(2 << 6) +#define V2_TCTL_FRR		(1 << 9) +#define V2_IR			0x0c +#define V2_TSTAT		0x08 +#define V2_TSTAT_OF1		(1 << 0) +#define V2_TCN			0x24 +#define V2_TCMP			0x10 + +#define timer_is_v1()	(cpu_is_mx1() || cpu_is_mx21() || cpu_is_mx27()) +#define timer_is_v2()	(!timer_is_v1()) + +static struct clock_event_device clockevent_mxc; +static enum clock_event_mode clockevent_mode = CLOCK_EVT_MODE_UNUSED; + +static void __iomem *timer_base; + +static inline void gpt_irq_disable(void) +{ +	unsigned int tmp; + +	if (timer_is_v2()) +		__raw_writel(0, timer_base + V2_IR); +	else { +		tmp = __raw_readl(timer_base + MXC_TCTL); +		__raw_writel(tmp & ~MX1_2_TCTL_IRQEN, timer_base + MXC_TCTL); +	} +} + +static inline void gpt_irq_enable(void) +{ +	if (timer_is_v2()) +		__raw_writel(1<<0, timer_base + V2_IR); +	else { +		__raw_writel(__raw_readl(timer_base + MXC_TCTL) | MX1_2_TCTL_IRQEN, +			timer_base + MXC_TCTL); +	} +} + +static void gpt_irq_acknowledge(void) +{ +	if (timer_is_v1()) { +		if (cpu_is_mx1()) +			__raw_writel(0, timer_base + MX1_2_TSTAT); +		else +			__raw_writel(MX2_TSTAT_CAPT | MX2_TSTAT_COMP, +				timer_base + MX1_2_TSTAT); +	} else if (timer_is_v2()) +		__raw_writel(V2_TSTAT_OF1, timer_base + V2_TSTAT); +} + +static void __iomem *sched_clock_reg; + +static u32 notrace mxc_read_sched_clock(void) +{ +	return sched_clock_reg ? __raw_readl(sched_clock_reg) : 0; +} + +static int __init mxc_clocksource_init(struct clk *timer_clk) +{ +	unsigned int c = clk_get_rate(timer_clk); +	void __iomem *reg = timer_base + (timer_is_v2() ? V2_TCN : MX1_2_TCN); + +	sched_clock_reg = reg; + +	setup_sched_clock(mxc_read_sched_clock, 32, c); +	return clocksource_mmio_init(reg, "mxc_timer1", c, 200, 32, +			clocksource_mmio_readl_up); +} + +/* clock event */ + +static int mx1_2_set_next_event(unsigned long evt, +			      struct clock_event_device *unused) +{ +	unsigned long tcmp; + +	tcmp = __raw_readl(timer_base + MX1_2_TCN) + evt; + +	__raw_writel(tcmp, timer_base + MX1_2_TCMP); + +	return (int)(tcmp - __raw_readl(timer_base + MX1_2_TCN)) < 0 ? +				-ETIME : 0; +} + +static int v2_set_next_event(unsigned long evt, +			      struct clock_event_device *unused) +{ +	unsigned long tcmp; + +	tcmp = __raw_readl(timer_base + V2_TCN) + evt; + +	__raw_writel(tcmp, timer_base + V2_TCMP); + +	return (int)(tcmp - __raw_readl(timer_base + V2_TCN)) < 0 ? +				-ETIME : 0; +} + +#ifdef DEBUG +static const char *clock_event_mode_label[] = { +	[CLOCK_EVT_MODE_PERIODIC] = "CLOCK_EVT_MODE_PERIODIC", +	[CLOCK_EVT_MODE_ONESHOT]  = "CLOCK_EVT_MODE_ONESHOT", +	[CLOCK_EVT_MODE_SHUTDOWN] = "CLOCK_EVT_MODE_SHUTDOWN", +	[CLOCK_EVT_MODE_UNUSED]   = "CLOCK_EVT_MODE_UNUSED", +	[CLOCK_EVT_MODE_RESUME]   = "CLOCK_EVT_MODE_RESUME", +}; +#endif /* DEBUG */ + +static void mxc_set_mode(enum clock_event_mode mode, +				struct clock_event_device *evt) +{ +	unsigned long flags; + +	/* +	 * The timer interrupt generation is disabled at least +	 * for enough time to call mxc_set_next_event() +	 */ +	local_irq_save(flags); + +	/* Disable interrupt in GPT module */ +	gpt_irq_disable(); + +	if (mode != clockevent_mode) { +		/* Set event time into far-far future */ +		if (timer_is_v2()) +			__raw_writel(__raw_readl(timer_base + V2_TCN) - 3, +					timer_base + V2_TCMP); +		else +			__raw_writel(__raw_readl(timer_base + MX1_2_TCN) - 3, +					timer_base + MX1_2_TCMP); + +		/* Clear pending interrupt */ +		gpt_irq_acknowledge(); +	} + +#ifdef DEBUG +	printk(KERN_INFO "mxc_set_mode: changing mode from %s to %s\n", +		clock_event_mode_label[clockevent_mode], +		clock_event_mode_label[mode]); +#endif /* DEBUG */ + +	/* Remember timer mode */ +	clockevent_mode = mode; +	local_irq_restore(flags); + +	switch (mode) { +	case CLOCK_EVT_MODE_PERIODIC: +		printk(KERN_ERR"mxc_set_mode: Periodic mode is not " +				"supported for i.MX\n"); +		break; +	case CLOCK_EVT_MODE_ONESHOT: +	/* +	 * Do not put overhead of interrupt enable/disable into +	 * mxc_set_next_event(), the core has about 4 minutes +	 * to call mxc_set_next_event() or shutdown clock after +	 * mode switching +	 */ +		local_irq_save(flags); +		gpt_irq_enable(); +		local_irq_restore(flags); +		break; +	case CLOCK_EVT_MODE_SHUTDOWN: +	case CLOCK_EVT_MODE_UNUSED: +	case CLOCK_EVT_MODE_RESUME: +		/* Left event sources disabled, no more interrupts appear */ +		break; +	} +} + +/* + * IRQ handler for the timer + */ +static irqreturn_t mxc_timer_interrupt(int irq, void *dev_id) +{ +	struct clock_event_device *evt = &clockevent_mxc; +	uint32_t tstat; + +	if (timer_is_v2()) +		tstat = __raw_readl(timer_base + V2_TSTAT); +	else +		tstat = __raw_readl(timer_base + MX1_2_TSTAT); + +	gpt_irq_acknowledge(); + +	evt->event_handler(evt); + +	return IRQ_HANDLED; +} + +static struct irqaction mxc_timer_irq = { +	.name		= "i.MX Timer Tick", +	.flags		= IRQF_DISABLED | IRQF_TIMER | IRQF_IRQPOLL, +	.handler	= mxc_timer_interrupt, +}; + +static struct clock_event_device clockevent_mxc = { +	.name		= "mxc_timer1", +	.features	= CLOCK_EVT_FEAT_ONESHOT, +	.shift		= 32, +	.set_mode	= mxc_set_mode, +	.set_next_event	= mx1_2_set_next_event, +	.rating		= 200, +}; + +static int __init mxc_clockevent_init(struct clk *timer_clk) +{ +	unsigned int c = clk_get_rate(timer_clk); + +	if (timer_is_v2()) +		clockevent_mxc.set_next_event = v2_set_next_event; + +	clockevent_mxc.mult = div_sc(c, NSEC_PER_SEC, +					clockevent_mxc.shift); +	clockevent_mxc.max_delta_ns = +			clockevent_delta2ns(0xfffffffe, &clockevent_mxc); +	clockevent_mxc.min_delta_ns = +			clockevent_delta2ns(0xff, &clockevent_mxc); + +	clockevent_mxc.cpumask = cpumask_of(0); + +	clockevents_register_device(&clockevent_mxc); + +	return 0; +} + +void __init mxc_timer_init(void __iomem *base, int irq) +{ +	uint32_t tctl_val; +	struct clk *timer_clk; +	struct clk *timer_ipg_clk; + +	timer_clk = clk_get_sys("imx-gpt.0", "per"); +	if (IS_ERR(timer_clk)) { +		pr_err("i.MX timer: unable to get clk\n"); +		return; +	} + +	timer_ipg_clk = clk_get_sys("imx-gpt.0", "ipg"); +	if (!IS_ERR(timer_ipg_clk)) +		clk_prepare_enable(timer_ipg_clk); + +	clk_prepare_enable(timer_clk); + +	timer_base = base; + +	/* +	 * Initialise to a known state (all timers off, and timing reset) +	 */ + +	__raw_writel(0, timer_base + MXC_TCTL); +	__raw_writel(0, timer_base + MXC_TPRER); /* see datasheet note */ + +	if (timer_is_v2()) +		tctl_val = V2_TCTL_CLK_PER | V2_TCTL_FRR | V2_TCTL_WAITEN | MXC_TCTL_TEN; +	else +		tctl_val = MX1_2_TCTL_FRR | MX1_2_TCTL_CLK_PCLK1 | MXC_TCTL_TEN; + +	__raw_writel(tctl_val, timer_base + MXC_TCTL); + +	/* init and register the timer to the framework */ +	mxc_clocksource_init(timer_clk); +	mxc_clockevent_init(timer_clk); + +	/* Make irqs happen */ +	setup_irq(irq, &mxc_timer_irq); +}  |