diff options
Diffstat (limited to 'arch/arm/plat-mxc/gpio.c')
-rw-r--r-- | arch/arm/plat-mxc/gpio.c | 185 |
1 files changed, 174 insertions, 11 deletions
diff --git a/arch/arm/plat-mxc/gpio.c b/arch/arm/plat-mxc/gpio.c index 7506d963be4b..0ad808ed9c7b 100644 --- a/arch/arm/plat-mxc/gpio.c +++ b/arch/arm/plat-mxc/gpio.c @@ -3,7 +3,7 @@ * Copyright 2008 Juergen Beisert, kernel@pengutronix.de * * Based on code from Freescale, - * Copyright 2004-2006 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright 2004-2010 Freescale Semiconductor, Inc. All Rights Reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -20,12 +20,32 @@ */ #include <linux/init.h> +#include <linux/interrupt.h> #include <linux/io.h> #include <linux/irq.h> #include <linux/gpio.h> +#include <linux/sysdev.h> +#include <mach/gpio.h> #include <mach/hardware.h> #include <asm-generic/bug.h> +#if defined(CONFIG_ARCH_MX2) +#else +/* gpio and gpio based interrupt handling */ +#define GPIO_DR 0x00 +#define GPIO_GDIR 0x04 +#define GPIO_PSR 0x08 +#define GPIO_ICR1 0x0C +#define GPIO_ICR2 0x10 +#define GPIO_IMR 0x14 +#define GPIO_ISR 0x18 +#define GPIO_INT_LOW_LEV 0x0 +#define GPIO_INT_HIGH_LEV 0x1 +#define GPIO_INT_RISE_EDGE 0x2 +#define GPIO_INT_FALL_EDGE 0x3 +#define GPIO_INT_NONE 0x4 +#endif + static struct mxc_gpio_port *mxc_gpio_ports; static int gpio_table_size; @@ -103,6 +123,12 @@ static int gpio_set_irq_type(u32 irq, u32 type) return -EINVAL; } + /* set the correct irq handler */ + if (type & IRQ_TYPE_EDGE_BOTH) + set_irq_handler(irq, handle_edge_irq); + else if (type & (IRQ_TYPE_LEVEL_LOW | IRQ_TYPE_LEVEL_HIGH)) + set_irq_handler(irq, handle_level_irq); + reg += GPIO_ICR1 + ((gpio & 0x10) >> 2); /* lower or upper register */ bit = gpio & 0xf; val = __raw_readl(reg) & ~(0x3 << (bit << 1)); @@ -162,16 +188,23 @@ static void mxc_gpio_irq_handler(struct mxc_gpio_port *port, u32 irq_stat) } } -#if defined(CONFIG_ARCH_MX3) || defined(CONFIG_ARCH_MX1) -/* MX1 and MX3 has one interrupt *per* gpio port */ -static void mx3_gpio_irq_handler(u32 irq, struct irq_desc *desc) +#ifndef CONFIG_ARCH_MX2 +/* one interrupt *per* gpio port */ +static void gpio_irq_handler(u32 irq, struct irq_desc *desc) { u32 irq_stat; + u32 mask = 0xFFFFFFFF; struct mxc_gpio_port *port = (struct mxc_gpio_port *)get_irq_data(irq); - irq_stat = __raw_readl(port->base + GPIO_ISR) & - __raw_readl(port->base + GPIO_IMR); +#ifdef MXC_GPIO_SPLIT_IRQ_2 + if (irq == port->irq) + mask = 0x0000FFFF; + else + mask = 0xFFFF0000; +#endif + irq_stat = __raw_readl(port->base + GPIO_ISR) & + (__raw_readl(port->base + GPIO_IMR) & mask); mxc_gpio_irq_handler(port, irq_stat); } #endif @@ -197,11 +230,44 @@ static void mx2_gpio_irq_handler(u32 irq, struct irq_desc *desc) } #endif +/* + * Set interrupt number "irq" in the GPIO as a wake-up source. + * While system is running all registered GPIO interrupts need to have + * wake-up enabled. When system is suspended, only selected GPIO interrupts + * need to have wake-up enabled. + * @param irq interrupt source number + * @param enable enable as wake-up if equal to non-zero + * @return This function returns 0 on success. + */ +static int gpio_set_wake_irq(u32 irq, u32 enable) +{ + u32 gpio = irq_to_gpio(irq); + u32 gpio_idx = gpio & 0x1F; + struct mxc_gpio_port *port = &mxc_gpio_ports[gpio / 32]; + + if (enable) { + port->suspend_wakeup |= (1 << gpio_idx); + if (port->irq_high && (gpio_idx >= 16)) + enable_irq_wake(port->irq_high); + else + enable_irq_wake(port->irq); + } else { + port->suspend_wakeup &= ~(1 << gpio_idx); + if (port->irq_high && (gpio_idx >= 16)) + disable_irq_wake(port->irq_high); + else + disable_irq_wake(port->irq); + } + + return 0; +} + static struct irq_chip gpio_irq_chip = { .ack = gpio_ack_irq, .mask = gpio_mask_irq, .unmask = gpio_unmask_irq, .set_type = gpio_set_irq_type, + .set_wake = gpio_set_wake_irq, }; static void _set_gpio_direction(struct gpio_chip *chip, unsigned offset, @@ -252,9 +318,98 @@ static int mxc_gpio_direction_output(struct gpio_chip *chip, return 0; } +#ifdef CONFIG_PM +/*! + * This function puts the GPIO in low-power mode/state. + * All the interrupts that are enabled are first saved. + * Only those interrupts which registers as a wake source by calling + * enable_irq_wake are enabled. All other interrupts are disabled. + * + * @param dev the system device structure used to give information + * on GPIO to suspend + * @param mesg the power state the device is entering + * + * @return The function always returns 0. + */ +static int mxc_gpio_suspend(struct sys_device *dev, pm_message_t mesg) +{ + int i; + struct mxc_gpio_port *port = mxc_gpio_ports; + + for (i = 0; i < gpio_table_size; i++) { + void __iomem *isr_reg; + void __iomem *imr_reg; + + isr_reg = port[i].base + GPIO_ISR; + imr_reg = port[i].base + GPIO_IMR; + + if (__raw_readl(isr_reg) & port[i].suspend_wakeup) + return -EPERM; + + port[i].saved_wakeup = __raw_readl(imr_reg); + __raw_writel(port[i].suspend_wakeup, imr_reg); + } + + return 0; +} + +/*! + * This function brings the GPIO back from low-power state. + * All the interrupts enabled before suspension are re-enabled from + * the saved information. + * + * @param dev the system device structure used to give information + * on GPIO to resume + * + * @return The function always returns 0. + */ +static int mxc_gpio_resume(struct sys_device *dev) +{ + int i; + struct mxc_gpio_port *port = mxc_gpio_ports; + + for (i = 0; i < gpio_table_size; i++) { + void __iomem *isr_reg; + void __iomem *imr_reg; + + isr_reg = port[i].base + GPIO_ISR; + imr_reg = port[i].base + GPIO_IMR; + + __raw_writel(port[i].saved_wakeup, imr_reg); + } + + return 0; +} +#else +#define mxc_gpio_suspend NULL +#define mxc_gpio_resume NULL +#endif /* CONFIG_PM */ + +/*! + * This structure contains pointers to the power management callback functions. + */ +static struct sysdev_class mxc_gpio_sysclass = { + .name = "mxc_gpio", + .suspend = mxc_gpio_suspend, + .resume = mxc_gpio_resume, +}; + +/*! + * This structure represents GPIO as a system device. + * System devices follow a slightly different driver model. + * They don't need to do dynammic driver binding, can't be probed, + * and don't reside on any type of peripheral bus. + * So, it is represented and treated a little differently. + */ +static struct sys_device mxc_gpio_device = { + .id = 0, + .cls = &mxc_gpio_sysclass, +}; + int __init mxc_gpio_init(struct mxc_gpio_port *port, int cnt) { int i, j; + int ret = 0; /* save for local usage */ mxc_gpio_ports = port; @@ -269,7 +424,7 @@ int __init mxc_gpio_init(struct mxc_gpio_port *port, int cnt) for (j = port[i].virtual_irq_start; j < port[i].virtual_irq_start + 32; j++) { set_irq_chip(j, &gpio_irq_chip); - set_irq_handler(j, handle_edge_irq); + set_irq_handler(j, handle_level_irq); set_irq_flags(j, IRQF_VALID); } @@ -284,10 +439,13 @@ int __init mxc_gpio_init(struct mxc_gpio_port *port, int cnt) /* its a serious configuration bug when it fails */ BUG_ON( gpiochip_add(&port[i].chip) < 0 ); -#if defined(CONFIG_ARCH_MX3) || defined(CONFIG_ARCH_MX1) - /* setup one handler for each entry */ - set_irq_chained_handler(port[i].irq, mx3_gpio_irq_handler); +#ifndef CONFIG_ARCH_MX2 + set_irq_chained_handler(port[i].irq, gpio_irq_handler); set_irq_data(port[i].irq, &port[i]); + if (port[i].irq_high) { + set_irq_chained_handler(port[i].irq_high, gpio_irq_handler); + set_irq_data(port[i].irq_high, &port[i]); + } #endif } @@ -296,5 +454,10 @@ int __init mxc_gpio_init(struct mxc_gpio_port *port, int cnt) set_irq_chained_handler(port[0].irq, mx2_gpio_irq_handler); set_irq_data(port[0].irq, port); #endif - return 0; + + ret = sysdev_class_register(&mxc_gpio_sysclass); + if (ret == 0) + ret = sysdev_register(&mxc_gpio_device); + + return ret; } |