diff options
Diffstat (limited to 'arch/arm/mach-imx')
-rw-r--r-- | arch/arm/mach-imx/generic.c | 118 | ||||
-rw-r--r-- | arch/arm/mach-imx/time.c | 122 |
2 files changed, 226 insertions, 14 deletions
diff --git a/arch/arm/mach-imx/generic.c b/arch/arm/mach-imx/generic.c index 1c474cf709ca..a58b678006df 100644 --- a/arch/arm/mach-imx/generic.c +++ b/arch/arm/mach-imx/generic.c @@ -28,12 +28,16 @@ #include <linux/module.h> #include <linux/string.h> +#include <asm/errno.h> #include <asm/arch/imxfb.h> #include <asm/hardware.h> #include <asm/arch/imx-regs.h> #include <asm/mach/map.h> #include <asm/arch/mmc.h> +#include <asm/arch/gpio.h> + +unsigned long imx_gpio_alloc_map[(GPIO_PORT_MAX + 1) * 32 / BITS_PER_LONG]; void imx_gpio_mode(int gpio_mode) { @@ -95,6 +99,120 @@ void imx_gpio_mode(int gpio_mode) EXPORT_SYMBOL(imx_gpio_mode); +int imx_gpio_request(unsigned gpio, const char *label) +{ + if(gpio >= (GPIO_PORT_MAX + 1) * 32) + printk(KERN_ERR "imx_gpio: Attempt to request nonexistent GPIO %d for \"%s\"\n", + gpio, label ? label : "?"); + return -EINVAL; + + if(test_and_set_bit(gpio, imx_gpio_alloc_map)) { + printk(KERN_ERR "imx_gpio: GPIO %d already used. Allocation for \"%s\" failed\n", + gpio, label ? label : "?"); + return -EBUSY; + } + + return 0; +} + +EXPORT_SYMBOL(imx_gpio_request); + +void imx_gpio_free(unsigned gpio) +{ + if(gpio >= (GPIO_PORT_MAX + 1) * 32) + return; + + clear_bit(gpio, imx_gpio_alloc_map); +} + +EXPORT_SYMBOL(imx_gpio_free); + +int imx_gpio_direction_input(unsigned gpio) +{ + imx_gpio_mode(gpio| GPIO_IN); + return 0; +} + +EXPORT_SYMBOL(imx_gpio_direction_input); + +int imx_gpio_direction_output(unsigned gpio, int value) +{ + imx_gpio_set_value(gpio, value); + imx_gpio_mode(gpio| GPIO_OUT); + return 0; +} + +EXPORT_SYMBOL(imx_gpio_direction_output); + +int imx_gpio_setup_multiple_pins(const int *pin_list, unsigned count, + int alloc_mode, const char *label) +{ + const int *p = pin_list; + int i; + unsigned gpio; + unsigned mode; + + for (i = 0; i < count; i++) { + gpio = *p & (GPIO_PIN_MASK | GPIO_PORT_MASK); + mode = *p & ~(GPIO_PIN_MASK | GPIO_PORT_MASK); + + if (gpio >= (GPIO_PORT_MAX + 1) * 32) + goto setup_error; + + if (alloc_mode & IMX_GPIO_ALLOC_MODE_RELEASE) + imx_gpio_free(gpio); + else if (!(alloc_mode & IMX_GPIO_ALLOC_MODE_NO_ALLOC)) + if (imx_gpio_request(gpio, label)) + if (!(alloc_mode & IMX_GPIO_ALLOC_MODE_TRY_ALLOC)) + goto setup_error; + + if (!(alloc_mode & (IMX_GPIO_ALLOC_MODE_ALLOC_ONLY | + IMX_GPIO_ALLOC_MODE_RELEASE))) + imx_gpio_mode(gpio | mode); + + p++; + } + return 0; + +setup_error: + if(alloc_mode & (IMX_GPIO_ALLOC_MODE_NO_ALLOC | + IMX_GPIO_ALLOC_MODE_TRY_ALLOC)) + return -EINVAL; + + while (p != pin_list) { + p--; + gpio = *p & (GPIO_PIN_MASK | GPIO_PORT_MASK); + imx_gpio_free(gpio); + } + + return -EINVAL; +} + +EXPORT_SYMBOL(imx_gpio_setup_multiple_pins); + +void __imx_gpio_set_value(unsigned gpio, int value) +{ + imx_gpio_set_value_inline(gpio, value); +} + +EXPORT_SYMBOL(__imx_gpio_set_value); + +int imx_gpio_to_irq(unsigned gpio) +{ + return IRQ_GPIOA(0) + gpio; +} + +EXPORT_SYMBOL(imx_gpio_to_irq); + +int imx_irq_to_gpio(unsigned irq) +{ + if (irq < IRQ_GPIOA(0)) + return -EINVAL; + return irq - IRQ_GPIOA(0); +} + +EXPORT_SYMBOL(imx_irq_to_gpio); + /* * get the system pll clock in Hz * diff --git a/arch/arm/mach-imx/time.c b/arch/arm/mach-imx/time.c index 6960a9d04217..d86d124aea22 100644 --- a/arch/arm/mach-imx/time.c +++ b/arch/arm/mach-imx/time.c @@ -3,6 +3,7 @@ * * Copyright (C) 2000-2001 Deep Blue Solutions * Copyright (C) 2002 Shane Nay (shane@minirl.com) + * Copyright (C) 2006-2007 Pavel Pisa (ppisa@pikron.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 @@ -15,6 +16,7 @@ #include <linux/irq.h> #include <linux/time.h> #include <linux/clocksource.h> +#include <linux/clockchips.h> #include <asm/hardware.h> #include <asm/io.h> @@ -25,7 +27,8 @@ /* Use timer 1 as system timer */ #define TIMER_BASE IMX_TIM1_BASE -static unsigned long evt_diff; +static struct clock_event_device clockevent_imx; +static enum clock_event_mode clockevent_mode = CLOCK_EVT_MODE_UNUSED; /* * IRQ handler for the timer @@ -33,25 +36,20 @@ static unsigned long evt_diff; static irqreturn_t imx_timer_interrupt(int irq, void *dev_id) { + struct clock_event_device *evt = &clockevent_imx; uint32_t tstat; + irqreturn_t ret = IRQ_NONE; /* clear the interrupt */ tstat = IMX_TSTAT(TIMER_BASE); IMX_TSTAT(TIMER_BASE) = 0; if (tstat & TSTAT_COMP) { - do { - - write_seqlock(&xtime_lock); - timer_tick(); - write_sequnlock(&xtime_lock); - IMX_TCMP(TIMER_BASE) += evt_diff; - - } while (unlikely((int32_t)(IMX_TCMP(TIMER_BASE) - - IMX_TCN(TIMER_BASE)) < 0)); + evt->event_handler(evt); + ret = IRQ_HANDLED; } - return IRQ_HANDLED; + return ret; } static struct irqaction imx_timer_irq = { @@ -70,10 +68,8 @@ static void __init imx_timer_hardware_init(void) */ IMX_TCTL(TIMER_BASE) = 0; IMX_TPRER(TIMER_BASE) = 0; - IMX_TCMP(TIMER_BASE) = LATCH - 1; - IMX_TCTL(TIMER_BASE) = TCTL_FRR | TCTL_CLK_PCLK1 | TCTL_IRQEN | TCTL_TEN; - evt_diff = LATCH; + IMX_TCTL(TIMER_BASE) = TCTL_FRR | TCTL_CLK_PCLK1 | TCTL_TEN; } cycle_t imx_get_cycles(void) @@ -99,11 +95,109 @@ static int __init imx_clocksource_init(void) return 0; } +static int imx_set_next_event(unsigned long evt, + struct clock_event_device *unused) +{ + unsigned long tcmp; + + tcmp = IMX_TCN(TIMER_BASE) + evt; + IMX_TCMP(TIMER_BASE) = tcmp; + + return (int32_t)(tcmp - IMX_TCN(TIMER_BASE)) < 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" +}; +#endif /*DEBUG*/ + +static void imx_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 imx_set_next_event() + */ + local_irq_save(flags); + /* Disable interrupt in GPT module */ + IMX_TCTL(TIMER_BASE) &= ~TCTL_IRQEN; + if (mode != clockevent_mode) { + /* Set event time into far-far future */ + IMX_TCMP(TIMER_BASE) = IMX_TCN(TIMER_BASE) - 3; + /* Clear pending interrupt */ + IMX_TSTAT(TIMER_BASE) &= ~TSTAT_COMP; + } + +#ifdef DEBUG + printk(KERN_INFO "imx_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 "imx_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 + * imx_set_next_event(), the core has about 4 minutes + * to call imx_set_next_event() or shutdown clock after + * mode switching + */ + local_irq_save(flags); + IMX_TCTL(TIMER_BASE) |= TCTL_IRQEN; + 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 appears */ + break; + } +} + +static struct clock_event_device clockevent_imx = { + .name = "imx_timer1", + .features = CLOCK_EVT_FEAT_ONESHOT, + .shift = 32, + .set_mode = imx_set_mode, + .set_next_event = imx_set_next_event, + .rating = 200, +}; + +static int __init imx_clockevent_init(void) +{ + clockevent_imx.mult = div_sc(imx_get_perclk1(), NSEC_PER_SEC, + clockevent_imx.shift); + clockevent_imx.max_delta_ns = + clockevent_delta2ns(0xfffffffe, &clockevent_imx); + clockevent_imx.min_delta_ns = + clockevent_delta2ns(0xf, &clockevent_imx); + + clockevent_imx.cpumask = cpumask_of_cpu(0); + + clockevents_register_device(&clockevent_imx); + + return 0; +} + + static void __init imx_timer_init(void) { imx_timer_hardware_init(); imx_clocksource_init(); + imx_clockevent_init(); + /* * Make irqs happen for the system timer */ |