 b0d5217cfb
			
		
	
	
	b0d5217cfb
	
	
	
		
			
			Add support for gpiolib calls. This is based on the gpiolib implementation from plat-s3c64xx tree. Add support for external interrupts for GPIO H banks. Add support for GPIO interrupts for all banks. Signed-off-by: Kyungmin Park <kyungmin.park@samsung.com> Signed-off-by: Marek Szyprowski <m.szyprowski@samsung.com> Signed-off-by: Ben Dooks <ben-linux@fluff.org>
		
			
				
	
	
		
			266 lines
		
	
	
	
		
			6.7 KiB
			
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			266 lines
		
	
	
	
		
			6.7 KiB
			
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|  * 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));
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| }
 |