1/* 2 * arch/arm/mach-s5pc100/irq-gpio.c 3 * 4 * Copyright (C) 2009 Samsung Electronics 5 * 6 * S5PC100 - Interrupt handling for IRQ_GPIO${group}(x) 7 * 8 * This program is free software; you can redistribute it and/or modify 9 * it under the terms of the GNU General Public License version 2 as 10 * published by the Free Software Foundation. 11 */ 12 13#include <linux/kernel.h> 14#include <linux/interrupt.h> 15#include <linux/irq.h> 16#include <linux/io.h> 17#include <linux/gpio.h> 18 19#include <mach/map.h> 20#include <plat/gpio-cfg.h> 21 22#define S5P_GPIOREG(x) (S5P_VA_GPIO + (x)) 23 24#define CON_OFFSET 0x700 25#define MASK_OFFSET 0x900 26#define PEND_OFFSET 0xA00 27#define CON_OFFSET_2 0xE00 28#define MASK_OFFSET_2 0xF00 29#define PEND_OFFSET_2 0xF40 30 31#define GPIOINT_LEVEL_LOW 0x0 32#define GPIOINT_LEVEL_HIGH 0x1 33#define GPIOINT_EDGE_FALLING 0x2 34#define GPIOINT_EDGE_RISING 0x3 35#define GPIOINT_EDGE_BOTH 0x4 36 37static int group_to_con_offset(int group) 38{ 39 return group << 2; 40} 41 42static int group_to_mask_offset(int group) 43{ 44 return group << 2; 45} 46 47static int group_to_pend_offset(int group) 48{ 49 return group << 2; 50} 51 52static int s5pc100_get_start(unsigned int group) 53{ 54 switch (group) { 55 case 0: return S5PC100_GPIO_A0_START; 56 case 1: return S5PC100_GPIO_A1_START; 57 case 2: return S5PC100_GPIO_B_START; 58 case 3: return S5PC100_GPIO_C_START; 59 case 4: return S5PC100_GPIO_D_START; 60 case 5: return S5PC100_GPIO_E0_START; 61 case 6: return S5PC100_GPIO_E1_START; 62 case 7: return S5PC100_GPIO_F0_START; 63 case 8: return S5PC100_GPIO_F1_START; 64 case 9: return S5PC100_GPIO_F2_START; 65 case 10: return S5PC100_GPIO_F3_START; 66 case 11: return S5PC100_GPIO_G0_START; 67 case 12: return S5PC100_GPIO_G1_START; 68 case 13: return S5PC100_GPIO_G2_START; 69 case 14: return S5PC100_GPIO_G3_START; 70 case 15: return S5PC100_GPIO_I_START; 71 case 16: return S5PC100_GPIO_J0_START; 72 case 17: return S5PC100_GPIO_J1_START; 73 case 18: return S5PC100_GPIO_J2_START; 74 case 19: return S5PC100_GPIO_J3_START; 75 case 20: return S5PC100_GPIO_J4_START; 76 default: 77 BUG(); 78 } 79 80 return -EINVAL; 81} 82 83static int s5pc100_get_group(unsigned int irq) 84{ 85 irq -= S3C_IRQ_GPIO(0); 86 87 switch (irq) { 88 case S5PC100_GPIO_A0_START ... S5PC100_GPIO_A1_START - 1: 89 return 0; 90 case S5PC100_GPIO_A1_START ... S5PC100_GPIO_B_START - 1: 91 return 1; 92 case S5PC100_GPIO_B_START ... S5PC100_GPIO_C_START - 1: 93 return 2; 94 case S5PC100_GPIO_C_START ... S5PC100_GPIO_D_START - 1: 95 return 3; 96 case S5PC100_GPIO_D_START ... S5PC100_GPIO_E0_START - 1: 97 return 4; 98 case S5PC100_GPIO_E0_START ... S5PC100_GPIO_E1_START - 1: 99 return 5; 100 case S5PC100_GPIO_E1_START ... S5PC100_GPIO_F0_START - 1: 101 return 6; 102 case S5PC100_GPIO_F0_START ... S5PC100_GPIO_F1_START - 1: 103 return 7; 104 case S5PC100_GPIO_F1_START ... S5PC100_GPIO_F2_START - 1: 105 return 8; 106 case S5PC100_GPIO_F2_START ... S5PC100_GPIO_F3_START - 1: 107 return 9; 108 case S5PC100_GPIO_F3_START ... S5PC100_GPIO_G0_START - 1: 109 return 10; 110 case S5PC100_GPIO_G0_START ... S5PC100_GPIO_G1_START - 1: 111 return 11; 112 case S5PC100_GPIO_G1_START ... S5PC100_GPIO_G2_START - 1: 113 return 12; 114 case S5PC100_GPIO_G2_START ... S5PC100_GPIO_G3_START - 1: 115 return 13; 116 case S5PC100_GPIO_G3_START ... S5PC100_GPIO_H0_START - 1: 117 return 14; 118 case S5PC100_GPIO_I_START ... S5PC100_GPIO_J0_START - 1: 119 return 15; 120 case S5PC100_GPIO_J0_START ... S5PC100_GPIO_J1_START - 1: 121 return 16; 122 case S5PC100_GPIO_J1_START ... S5PC100_GPIO_J2_START - 1: 123 return 17; 124 case S5PC100_GPIO_J2_START ... S5PC100_GPIO_J3_START - 1: 125 return 18; 126 case S5PC100_GPIO_J3_START ... S5PC100_GPIO_J4_START - 1: 127 return 19; 128 case S5PC100_GPIO_J4_START ... S5PC100_GPIO_K0_START - 1: 129 return 20; 130 default: 131 BUG(); 132 } 133 134 return -EINVAL; 135} 136 137static int s5pc100_get_offset(unsigned int irq) 138{ 139 struct gpio_chip *chip = get_irq_data(irq); 140 return irq - S3C_IRQ_GPIO(chip->base); 141} 142 143static void s5pc100_gpioint_ack(unsigned int irq) 144{ 145 int group, offset, pend_offset; 146 unsigned int value; 147 148 group = s5pc100_get_group(irq); 149 offset = s5pc100_get_offset(irq); 150 pend_offset = group_to_pend_offset(group); 151 152 value = __raw_readl(S5P_GPIOREG(PEND_OFFSET) + pend_offset); 153 value |= 1 << offset; 154 __raw_writel(value, S5P_GPIOREG(PEND_OFFSET) + pend_offset); 155} 156 157static void s5pc100_gpioint_mask(unsigned int irq) 158{ 159 int group, offset, mask_offset; 160 unsigned int value; 161 162 group = s5pc100_get_group(irq); 163 offset = s5pc100_get_offset(irq); 164 mask_offset = group_to_mask_offset(group); 165 166 value = __raw_readl(S5P_GPIOREG(MASK_OFFSET) + mask_offset); 167 value |= 1 << offset; 168 __raw_writel(value, S5P_GPIOREG(MASK_OFFSET) + mask_offset); 169} 170 171static void s5pc100_gpioint_unmask(unsigned int irq) 172{ 173 int group, offset, mask_offset; 174 unsigned int value; 175 176 group = s5pc100_get_group(irq); 177 offset = s5pc100_get_offset(irq); 178 mask_offset = group_to_mask_offset(group); 179 180 value = __raw_readl(S5P_GPIOREG(MASK_OFFSET) + mask_offset); 181 value &= ~(1 << offset); 182 __raw_writel(value, S5P_GPIOREG(MASK_OFFSET) + mask_offset); 183} 184 185static void s5pc100_gpioint_mask_ack(unsigned int irq) 186{ 187 s5pc100_gpioint_mask(irq); 188 s5pc100_gpioint_ack(irq); 189} 190 191static int s5pc100_gpioint_set_type(unsigned int irq, unsigned int type) 192{ 193 int group, offset, con_offset; 194 unsigned int value; 195 196 group = s5pc100_get_group(irq); 197 offset = s5pc100_get_offset(irq); 198 con_offset = group_to_con_offset(group); 199 200 switch (type) { 201 case IRQ_TYPE_NONE: 202 printk(KERN_WARNING "No irq type\n"); 203 return -EINVAL; 204 case IRQ_TYPE_EDGE_RISING: 205 type = GPIOINT_EDGE_RISING; 206 break; 207 case IRQ_TYPE_EDGE_FALLING: 208 type = GPIOINT_EDGE_FALLING; 209 break; 210 case IRQ_TYPE_EDGE_BOTH: 211 type = GPIOINT_EDGE_BOTH; 212 break; 213 case IRQ_TYPE_LEVEL_HIGH: 214 type = GPIOINT_LEVEL_HIGH; 215 break; 216 case IRQ_TYPE_LEVEL_LOW: 217 type = GPIOINT_LEVEL_LOW; 218 break; 219 default: 220 BUG(); 221 } 222 223 224 value = __raw_readl(S5P_GPIOREG(CON_OFFSET) + con_offset); 225 value &= ~(0xf << (offset * 0x4)); 226 value |= (type << (offset * 0x4)); 227 __raw_writel(value, S5P_GPIOREG(CON_OFFSET) + con_offset); 228 229 return 0; 230} 231 232struct irq_chip s5pc100_gpioint = { 233 .name = "GPIO", 234 .ack = s5pc100_gpioint_ack, 235 .mask = s5pc100_gpioint_mask, 236 .mask_ack = s5pc100_gpioint_mask_ack, 237 .unmask = s5pc100_gpioint_unmask, 238 .set_type = s5pc100_gpioint_set_type, 239}; 240 241void s5pc100_irq_gpioint_handler(unsigned int irq, struct irq_desc *desc) 242{ 243 int group, offset, pend_offset, mask_offset; 244 int real_irq, group_end; 245 unsigned int pend, mask; 246 247 group_end = 21; 248 249 for (group = 0; group < group_end; group++) { 250 pend_offset = group_to_pend_offset(group); 251 pend = __raw_readl(S5P_GPIOREG(PEND_OFFSET) + pend_offset); 252 if (!pend) 253 continue; 254 255 mask_offset = group_to_mask_offset(group); 256 mask = __raw_readl(S5P_GPIOREG(MASK_OFFSET) + mask_offset); 257 pend &= ~mask; 258 259 for (offset = 0; offset < 8; offset++) { 260 if (pend & (1 << offset)) { 261 real_irq = s5pc100_get_start(group) + offset; 262 generic_handle_irq(S3C_IRQ_GPIO(real_irq)); 263 } 264 } 265 } 266} 267