diff options
Diffstat (limited to 'arch/arm/plat-s5pc1xx/irq-gpio.c')
-rw-r--r-- | arch/arm/plat-s5pc1xx/irq-gpio.c | 266 |
1 files changed, 266 insertions, 0 deletions
diff --git a/arch/arm/plat-s5pc1xx/irq-gpio.c b/arch/arm/plat-s5pc1xx/irq-gpio.c new file mode 100644 index 00000000000..fecca7a679b --- /dev/null +++ b/arch/arm/plat-s5pc1xx/irq-gpio.c @@ -0,0 +1,266 @@ +/* + * arch/arm/plat-s5pc1xx/irq-gpio.c + * + * Copyright (C) 2009 Samsung Electronics + * + * S5PC1XX - Interrupt handling for IRQ_GPIO${group}(x) + * + * 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/kernel.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/io.h> +#include <linux/gpio.h> + +#include <mach/map.h> +#include <plat/gpio-cfg.h> + +#define S5PC1XX_GPIOREG(x) (S5PC1XX_VA_GPIO + (x)) + +#define CON_OFFSET 0x700 +#define MASK_OFFSET 0x900 +#define PEND_OFFSET 0xA00 +#define CON_OFFSET_2 0xE00 +#define MASK_OFFSET_2 0xF00 +#define PEND_OFFSET_2 0xF40 + +#define GPIOINT_LEVEL_LOW 0x0 +#define GPIOINT_LEVEL_HIGH 0x1 +#define GPIOINT_EDGE_FALLING 0x2 +#define GPIOINT_EDGE_RISING 0x3 +#define GPIOINT_EDGE_BOTH 0x4 + +static int group_to_con_offset(int group) +{ + return group << 2; +} + +static int group_to_mask_offset(int group) +{ + return group << 2; +} + +static int group_to_pend_offset(int group) +{ + return group << 2; +} + +static int s5pc1xx_get_start(unsigned int group) +{ + switch (group) { + case 0: return S5PC100_GPIO_A0_START; + case 1: return S5PC100_GPIO_A1_START; + case 2: return S5PC100_GPIO_B_START; + case 3: return S5PC100_GPIO_C_START; + case 4: return S5PC100_GPIO_D_START; + case 5: return S5PC100_GPIO_E0_START; + case 6: return S5PC100_GPIO_E1_START; + case 7: return S5PC100_GPIO_F0_START; + case 8: return S5PC100_GPIO_F1_START; + case 9: return S5PC100_GPIO_F2_START; + case 10: return S5PC100_GPIO_F3_START; + case 11: return S5PC100_GPIO_G0_START; + case 12: return S5PC100_GPIO_G1_START; + case 13: return S5PC100_GPIO_G2_START; + case 14: return S5PC100_GPIO_G3_START; + case 15: return S5PC100_GPIO_I_START; + case 16: return S5PC100_GPIO_J0_START; + case 17: return S5PC100_GPIO_J1_START; + case 18: return S5PC100_GPIO_J2_START; + case 19: return S5PC100_GPIO_J3_START; + case 20: return S5PC100_GPIO_J4_START; + default: + BUG(); + } + + return -EINVAL; +} + +static int s5pc1xx_get_group(unsigned int irq) +{ + irq -= S3C_IRQ_GPIO(0); + + switch (irq) { + case S5PC100_GPIO_A0_START ... S5PC100_GPIO_A1_START - 1: + return 0; + case S5PC100_GPIO_A1_START ... S5PC100_GPIO_B_START - 1: + return 1; + case S5PC100_GPIO_B_START ... S5PC100_GPIO_C_START - 1: + return 2; + case S5PC100_GPIO_C_START ... S5PC100_GPIO_D_START - 1: + return 3; + case S5PC100_GPIO_D_START ... S5PC100_GPIO_E0_START - 1: + return 4; + case S5PC100_GPIO_E0_START ... S5PC100_GPIO_E1_START - 1: + return 5; + case S5PC100_GPIO_E1_START ... S5PC100_GPIO_F0_START - 1: + return 6; + case S5PC100_GPIO_F0_START ... S5PC100_GPIO_F1_START - 1: + return 7; + case S5PC100_GPIO_F1_START ... S5PC100_GPIO_F2_START - 1: + return 8; + case S5PC100_GPIO_F2_START ... S5PC100_GPIO_F3_START - 1: + return 9; + case S5PC100_GPIO_F3_START ... S5PC100_GPIO_G0_START - 1: + return 10; + case S5PC100_GPIO_G0_START ... S5PC100_GPIO_G1_START - 1: + return 11; + case S5PC100_GPIO_G1_START ... S5PC100_GPIO_G2_START - 1: + return 12; + case S5PC100_GPIO_G2_START ... S5PC100_GPIO_G3_START - 1: + return 13; + case S5PC100_GPIO_G3_START ... S5PC100_GPIO_H0_START - 1: + return 14; + case S5PC100_GPIO_I_START ... S5PC100_GPIO_J0_START - 1: + return 15; + case S5PC100_GPIO_J0_START ... S5PC100_GPIO_J1_START - 1: + return 16; + case S5PC100_GPIO_J1_START ... S5PC100_GPIO_J2_START - 1: + return 17; + case S5PC100_GPIO_J2_START ... S5PC100_GPIO_J3_START - 1: + return 18; + case S5PC100_GPIO_J3_START ... S5PC100_GPIO_J4_START - 1: + return 19; + case S5PC100_GPIO_J4_START ... S5PC100_GPIO_K0_START - 1: + return 20; + default: + BUG(); + } + + return -EINVAL; +} + +static int s5pc1xx_get_offset(unsigned int irq) +{ + struct gpio_chip *chip = get_irq_data(irq); + return irq - S3C_IRQ_GPIO(chip->base); +} + +static void s5pc1xx_gpioint_ack(unsigned int irq) +{ + int group, offset, pend_offset; + unsigned int value; + + group = s5pc1xx_get_group(irq); + offset = s5pc1xx_get_offset(irq); + pend_offset = group_to_pend_offset(group); + + value = __raw_readl(S5PC1XX_GPIOREG(PEND_OFFSET) + pend_offset); + value |= 1 << offset; + __raw_writel(value, S5PC1XX_GPIOREG(PEND_OFFSET) + pend_offset); +} + +static void s5pc1xx_gpioint_mask(unsigned int irq) +{ + int group, offset, mask_offset; + unsigned int value; + + group = s5pc1xx_get_group(irq); + offset = s5pc1xx_get_offset(irq); + mask_offset = group_to_mask_offset(group); + + value = __raw_readl(S5PC1XX_GPIOREG(MASK_OFFSET) + mask_offset); + value |= 1 << offset; + __raw_writel(value, S5PC1XX_GPIOREG(MASK_OFFSET) + mask_offset); +} + +static void s5pc1xx_gpioint_unmask(unsigned int irq) +{ + int group, offset, mask_offset; + unsigned int value; + + group = s5pc1xx_get_group(irq); + offset = s5pc1xx_get_offset(irq); + mask_offset = group_to_mask_offset(group); + + value = __raw_readl(S5PC1XX_GPIOREG(MASK_OFFSET) + mask_offset); + value &= ~(1 << offset); + __raw_writel(value, S5PC1XX_GPIOREG(MASK_OFFSET) + mask_offset); +} + +static void s5pc1xx_gpioint_mask_ack(unsigned int irq) +{ + s5pc1xx_gpioint_mask(irq); + s5pc1xx_gpioint_ack(irq); +} + +static int s5pc1xx_gpioint_set_type(unsigned int irq, unsigned int type) +{ + int group, offset, con_offset; + unsigned int value; + + group = s5pc1xx_get_group(irq); + offset = s5pc1xx_get_offset(irq); + con_offset = group_to_con_offset(group); + + switch (type) { + case IRQ_TYPE_NONE: + printk(KERN_WARNING "No irq type\n"); + return -EINVAL; + case IRQ_TYPE_EDGE_RISING: + type = GPIOINT_EDGE_RISING; + break; + case IRQ_TYPE_EDGE_FALLING: + type = GPIOINT_EDGE_FALLING; + break; + case IRQ_TYPE_EDGE_BOTH: + type = GPIOINT_EDGE_BOTH; + break; + case IRQ_TYPE_LEVEL_HIGH: + type = GPIOINT_LEVEL_HIGH; + break; + case IRQ_TYPE_LEVEL_LOW: + type = GPIOINT_LEVEL_LOW; + break; + default: + BUG(); + } + + + value = __raw_readl(S5PC1XX_GPIOREG(CON_OFFSET) + con_offset); + value &= ~(0xf << (offset * 0x4)); + value |= (type << (offset * 0x4)); + __raw_writel(value, S5PC1XX_GPIOREG(CON_OFFSET) + con_offset); + + return 0; +} + +struct irq_chip s5pc1xx_gpioint = { + .name = "GPIO", + .ack = s5pc1xx_gpioint_ack, + .mask = s5pc1xx_gpioint_mask, + .mask_ack = s5pc1xx_gpioint_mask_ack, + .unmask = s5pc1xx_gpioint_unmask, + .set_type = s5pc1xx_gpioint_set_type, +}; + +void s5pc1xx_irq_gpioint_handler(unsigned int irq, struct irq_desc *desc) +{ + int group, offset, pend_offset, mask_offset; + int real_irq, group_end; + unsigned int pend, mask; + + group_end = 21; + + for (group = 0; group < group_end; group++) { + pend_offset = group_to_pend_offset(group); + pend = __raw_readl(S5PC1XX_GPIOREG(PEND_OFFSET) + pend_offset); + if (!pend) + continue; + + mask_offset = group_to_mask_offset(group); + mask = __raw_readl(S5PC1XX_GPIOREG(MASK_OFFSET) + mask_offset); + pend &= ~mask; + + for (offset = 0; offset < 8; offset++) { + if (pend & (1 << offset)) { + real_irq = s5pc1xx_get_start(group) + offset; + generic_handle_irq(S3C_IRQ_GPIO(real_irq)); + } + } + } +} |