| /* |
| * 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)); |
| } |
| } |
| } |
| } |