diff options
Diffstat (limited to 'arch/c6x/platforms/timer64.c')
| -rw-r--r-- | arch/c6x/platforms/timer64.c | 244 | 
1 files changed, 244 insertions, 0 deletions
diff --git a/arch/c6x/platforms/timer64.c b/arch/c6x/platforms/timer64.c new file mode 100644 index 00000000000..03c03c24919 --- /dev/null +++ b/arch/c6x/platforms/timer64.c @@ -0,0 +1,244 @@ +/* + *  Copyright (C) 2010, 2011 Texas Instruments Incorporated + *  Contributed by: Mark Salter (msalter@redhat.com) + * + *  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/clockchips.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/of.h> +#include <linux/of_irq.h> +#include <linux/of_address.h> +#include <asm/soc.h> +#include <asm/dscr.h> +#include <asm/timer64.h> + +struct timer_regs { +	u32	reserved0; +	u32	emumgt; +	u32	reserved1; +	u32	reserved2; +	u32	cntlo; +	u32	cnthi; +	u32	prdlo; +	u32	prdhi; +	u32	tcr; +	u32	tgcr; +	u32	wdtcr; +}; + +static struct timer_regs __iomem *timer; + +#define TCR_TSTATLO	     0x001 +#define TCR_INVOUTPLO	     0x002 +#define TCR_INVINPLO	     0x004 +#define TCR_CPLO	     0x008 +#define TCR_ENAMODELO_ONCE   0x040 +#define TCR_ENAMODELO_CONT   0x080 +#define TCR_ENAMODELO_MASK   0x0c0 +#define TCR_PWIDLO_MASK      0x030 +#define TCR_CLKSRCLO	     0x100 +#define TCR_TIENLO	     0x200 +#define TCR_TSTATHI	     (0x001 << 16) +#define TCR_INVOUTPHI	     (0x002 << 16) +#define TCR_CPHI	     (0x008 << 16) +#define TCR_PWIDHI_MASK      (0x030 << 16) +#define TCR_ENAMODEHI_ONCE   (0x040 << 16) +#define TCR_ENAMODEHI_CONT   (0x080 << 16) +#define TCR_ENAMODEHI_MASK   (0x0c0 << 16) + +#define TGCR_TIMLORS	     0x001 +#define TGCR_TIMHIRS	     0x002 +#define TGCR_TIMMODE_UD32    0x004 +#define TGCR_TIMMODE_WDT64   0x008 +#define TGCR_TIMMODE_CD32    0x00c +#define TGCR_TIMMODE_MASK    0x00c +#define TGCR_PSCHI_MASK      (0x00f << 8) +#define TGCR_TDDRHI_MASK     (0x00f << 12) + +/* + * Timer clocks are divided down from the CPU clock + * The divisor is in the EMUMGTCLKSPD register + */ +#define TIMER_DIVISOR \ +	((soc_readl(&timer->emumgt) & (0xf << 16)) >> 16) + +#define TIMER64_RATE (c6x_core_freq / TIMER_DIVISOR) + +#define TIMER64_MODE_DISABLED 0 +#define TIMER64_MODE_ONE_SHOT TCR_ENAMODELO_ONCE +#define TIMER64_MODE_PERIODIC TCR_ENAMODELO_CONT + +static int timer64_mode; +static int timer64_devstate_id = -1; + +static void timer64_config(unsigned long period) +{ +	u32 tcr = soc_readl(&timer->tcr) & ~TCR_ENAMODELO_MASK; + +	soc_writel(tcr, &timer->tcr); +	soc_writel(period - 1, &timer->prdlo); +	soc_writel(0, &timer->cntlo); +	tcr |= timer64_mode; +	soc_writel(tcr, &timer->tcr); +} + +static void timer64_enable(void) +{ +	u32 val; + +	if (timer64_devstate_id >= 0) +		dscr_set_devstate(timer64_devstate_id, DSCR_DEVSTATE_ENABLED); + +	/* disable timer, reset count */ +	soc_writel(soc_readl(&timer->tcr) & ~TCR_ENAMODELO_MASK, &timer->tcr); +	soc_writel(0, &timer->prdlo); + +	/* use internal clock and 1 cycle pulse width */ +	val = soc_readl(&timer->tcr); +	soc_writel(val & ~(TCR_CLKSRCLO | TCR_PWIDLO_MASK), &timer->tcr); + +	/* dual 32-bit unchained mode */ +	val = soc_readl(&timer->tgcr) & ~TGCR_TIMMODE_MASK; +	soc_writel(val, &timer->tgcr); +	soc_writel(val | (TGCR_TIMLORS | TGCR_TIMMODE_UD32), &timer->tgcr); +} + +static void timer64_disable(void) +{ +	/* disable timer, reset count */ +	soc_writel(soc_readl(&timer->tcr) & ~TCR_ENAMODELO_MASK, &timer->tcr); +	soc_writel(0, &timer->prdlo); + +	if (timer64_devstate_id >= 0) +		dscr_set_devstate(timer64_devstate_id, DSCR_DEVSTATE_DISABLED); +} + +static int next_event(unsigned long delta, +		      struct clock_event_device *evt) +{ +	timer64_config(delta); +	return 0; +} + +static void set_clock_mode(enum clock_event_mode mode, +			   struct clock_event_device *evt) +{ +	switch (mode) { +	case CLOCK_EVT_MODE_PERIODIC: +		timer64_enable(); +		timer64_mode = TIMER64_MODE_PERIODIC; +		timer64_config(TIMER64_RATE / HZ); +		break; +	case CLOCK_EVT_MODE_ONESHOT: +		timer64_enable(); +		timer64_mode = TIMER64_MODE_ONE_SHOT; +		break; +	case CLOCK_EVT_MODE_UNUSED: +	case CLOCK_EVT_MODE_SHUTDOWN: +		timer64_mode = TIMER64_MODE_DISABLED; +		timer64_disable(); +		break; +	case CLOCK_EVT_MODE_RESUME: +		break; +	} +} + +static struct clock_event_device t64_clockevent_device = { +	.name		= "TIMER64_EVT32_TIMER", +	.features	= CLOCK_EVT_FEAT_ONESHOT | CLOCK_EVT_FEAT_PERIODIC, +	.rating		= 200, +	.set_mode	= set_clock_mode, +	.set_next_event	= next_event, +}; + +static irqreturn_t timer_interrupt(int irq, void *dev_id) +{ +	struct clock_event_device *cd = &t64_clockevent_device; + +	cd->event_handler(cd); + +	return IRQ_HANDLED; +} + +static struct irqaction timer_iact = { +	.name		= "timer", +	.flags		= IRQF_TIMER, +	.handler	= timer_interrupt, +	.dev_id		= &t64_clockevent_device, +}; + +void __init timer64_init(void) +{ +	struct clock_event_device *cd = &t64_clockevent_device; +	struct device_node *np, *first = NULL; +	u32 val; +	int err, found = 0; + +	for_each_compatible_node(np, NULL, "ti,c64x+timer64") { +		err = of_property_read_u32(np, "ti,core-mask", &val); +		if (!err) { +			if (val & (1 << get_coreid())) { +				found = 1; +				break; +			} +		} else if (!first) +			first = np; +	} +	if (!found) { +		/* try first one with no core-mask */ +		if (first) +			np = of_node_get(first); +		else { +			pr_debug("Cannot find ti,c64x+timer64 timer.\n"); +			return; +		} +	} + +	timer = of_iomap(np, 0); +	if (!timer) { +		pr_debug("%s: Cannot map timer registers.\n", np->full_name); +		goto out; +	} +	pr_debug("%s: Timer registers=%p.\n", np->full_name, timer); + +	cd->irq	= irq_of_parse_and_map(np, 0); +	if (cd->irq == NO_IRQ) { +		pr_debug("%s: Cannot find interrupt.\n", np->full_name); +		iounmap(timer); +		goto out; +	} + +	/* If there is a device state control, save the ID. */ +	err = of_property_read_u32(np, "ti,dscr-dev-enable", &val); +	if (!err) { +		timer64_devstate_id = val; + +		/* +		 * It is necessary to enable the timer block here because +		 * the TIMER_DIVISOR macro needs to read a timer register +		 * to get the divisor. +		 */ +		dscr_set_devstate(timer64_devstate_id, DSCR_DEVSTATE_ENABLED); +	} + +	pr_debug("%s: Timer irq=%d.\n", np->full_name, cd->irq); + +	clockevents_calc_mult_shift(cd, c6x_core_freq / TIMER_DIVISOR, 5); + +	cd->max_delta_ns	= clockevent_delta2ns(0x7fffffff, cd); +	cd->min_delta_ns	= clockevent_delta2ns(250, cd); + +	cd->cpumask		= cpumask_of(smp_processor_id()); + +	clockevents_register_device(cd); +	setup_irq(cd->irq, &timer_iact); + +out: +	of_node_put(np); +	return; +}  |