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>
267 lines
6.7 KiB
C
267 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));
|
|
}
|
|
}
|
|
}
|
|
}
|