You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
353 lines
7.2 KiB
353 lines
7.2 KiB
/*
|
|
* (C) Copyright 2017 Rockchip Electronics Co., Ltd
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0+
|
|
*/
|
|
|
|
#include <dm.h>
|
|
#include <malloc.h>
|
|
#include "irq-internal.h"
|
|
|
|
typedef enum GPIOIntType {
|
|
GPIOLevelLow = 0,
|
|
GPIOLevelHigh,
|
|
GPIOEdgelFalling,
|
|
GPIOEdgelRising
|
|
} eGPIOIntType_t;
|
|
|
|
typedef enum eGPIOPinLevel {
|
|
GPIO_LOW = 0,
|
|
GPIO_HIGH
|
|
} eGPIOPinLevel_t;
|
|
|
|
typedef enum eGPIOPinDirection {
|
|
GPIO_IN = 0,
|
|
GPIO_OUT
|
|
} eGPIOPinDirection_t;
|
|
|
|
#define GPIO_SWPORT_DR 0x00
|
|
#define GPIO_SWPORT_DDR 0x04
|
|
#define GPIO_INTEN 0x30
|
|
#define GPIO_INTMASK 0x34
|
|
#define GPIO_INTTYPE_LEVEL 0x38
|
|
#define GPIO_INT_POLARITY 0x3c
|
|
#define GPIO_INT_STATUS 0x40
|
|
#define GPIO_INT_RAWSTATUS 0x44
|
|
#define GPIO_DEBOUNCE 0x48
|
|
#define GPIO_PORTS_EOI 0x4c
|
|
#define GPIO_EXT_PORT 0x50
|
|
#define GPIO_LS_SYNC 0x60
|
|
|
|
static inline unsigned pin_to_bit(unsigned pin)
|
|
{
|
|
return (1 << pin);
|
|
}
|
|
|
|
static inline unsigned offset_to_bit(unsigned offset)
|
|
{
|
|
return (1 << offset);
|
|
}
|
|
|
|
static void gpio_bit_op(void __iomem *regbase, unsigned int offset,
|
|
u32 bit, unsigned char flag)
|
|
{
|
|
u32 val = readl(regbase + offset);
|
|
|
|
if (flag)
|
|
val |= bit;
|
|
else
|
|
val &= ~bit;
|
|
|
|
writel(val, regbase + offset);
|
|
}
|
|
|
|
static int gpio_bit_rd(void __iomem *regbase, unsigned int offset, u32 bit)
|
|
{
|
|
return readl(regbase + offset) & bit ? 1 : 0;
|
|
}
|
|
|
|
static void gpio_irq_unmask(void __iomem *regbase, unsigned int bit)
|
|
{
|
|
gpio_bit_op(regbase, GPIO_INTEN, bit, 1);
|
|
}
|
|
|
|
static void gpio_irq_mask(void __iomem *regbase, unsigned int bit)
|
|
{
|
|
gpio_bit_op(regbase, GPIO_INTEN, bit, 0);
|
|
}
|
|
|
|
static void gpio_irq_ack(void __iomem *regbase, unsigned int bit)
|
|
{
|
|
gpio_bit_op(regbase, GPIO_PORTS_EOI, bit, 1);
|
|
}
|
|
|
|
static void generic_gpio_handle_irq(int irq, void *data __always_unused)
|
|
{
|
|
struct gpio_bank *bank = gpio_id_to_bank(irq - IRQ_GPIO0);
|
|
unsigned gpio_irq, pin, unmasked = 0;
|
|
u32 isr, ilr;
|
|
|
|
isr = readl(bank->regbase + GPIO_INT_STATUS);
|
|
ilr = readl(bank->regbase + GPIO_INTTYPE_LEVEL);
|
|
|
|
gpio_irq = bank->irq_base;
|
|
|
|
while (isr) {
|
|
pin = fls(isr) - 1;
|
|
|
|
/* first mask and ack irq */
|
|
gpio_irq_mask(bank->regbase, offset_to_bit(pin));
|
|
gpio_irq_ack(bank->regbase, offset_to_bit(pin));
|
|
|
|
/*
|
|
* If gpio is edge triggered, clear condition before executing
|
|
* the handler, so that we don't miss next edges trigger.
|
|
*/
|
|
if (ilr & (1 << pin)) {
|
|
unmasked = 1;
|
|
gpio_irq_unmask(bank->regbase, offset_to_bit(pin));
|
|
}
|
|
|
|
__generic_gpio_handle_irq(gpio_irq + pin);
|
|
|
|
isr &= ~(1 << pin);
|
|
|
|
if (!unmasked)
|
|
gpio_irq_unmask(bank->regbase, offset_to_bit(pin));
|
|
}
|
|
}
|
|
|
|
static void gpio_set_intr_type(void __iomem *regbase,
|
|
unsigned int bit,
|
|
eGPIOIntType_t type)
|
|
{
|
|
switch (type) {
|
|
case GPIOLevelLow:
|
|
gpio_bit_op(regbase, GPIO_INT_POLARITY, bit, 0);
|
|
gpio_bit_op(regbase, GPIO_INTTYPE_LEVEL, bit, 0);
|
|
break;
|
|
case GPIOLevelHigh:
|
|
gpio_bit_op(regbase, GPIO_INTTYPE_LEVEL, bit, 0);
|
|
gpio_bit_op(regbase, GPIO_INT_POLARITY, bit, 1);
|
|
break;
|
|
case GPIOEdgelFalling:
|
|
gpio_bit_op(regbase, GPIO_INTTYPE_LEVEL, bit, 1);
|
|
gpio_bit_op(regbase, GPIO_INT_POLARITY, bit, 0);
|
|
break;
|
|
case GPIOEdgelRising:
|
|
gpio_bit_op(regbase, GPIO_INTTYPE_LEVEL, bit, 1);
|
|
gpio_bit_op(regbase, GPIO_INT_POLARITY, bit, 1);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static int gpio_get_intr_type(void __iomem *regbase,
|
|
unsigned int bit)
|
|
{
|
|
u32 polarity, level, magic = 0;
|
|
int type;
|
|
|
|
polarity = gpio_bit_rd(regbase, GPIO_INT_POLARITY, bit);
|
|
level = gpio_bit_rd(regbase, GPIO_INTTYPE_LEVEL, bit);
|
|
magic = (polarity << 1) | (level << 0);
|
|
|
|
switch (magic) {
|
|
case 0x00:
|
|
type = GPIOLevelLow;
|
|
break;
|
|
case 0x02:
|
|
type = GPIOLevelHigh;
|
|
break;
|
|
case 0x01:
|
|
type = GPIOEdgelFalling;
|
|
break;
|
|
case 0x03:
|
|
type = GPIOEdgelRising;
|
|
break;
|
|
default:
|
|
type = -EINVAL;
|
|
}
|
|
|
|
return type;
|
|
}
|
|
|
|
static int gpio_irq_set_type(int gpio_irq, unsigned int type)
|
|
{
|
|
int gpio = irq_to_gpio(gpio_irq);
|
|
struct gpio_bank *bank = gpio_to_bank(gpio);
|
|
eGPIOIntType_t int_type = 0;
|
|
|
|
if (!bank)
|
|
return -EINVAL;
|
|
|
|
gpio &= GPIO_PIN_MASK;
|
|
if (gpio >= bank->ngpio)
|
|
return -EINVAL;
|
|
|
|
switch (type) {
|
|
case IRQ_TYPE_EDGE_RISING:
|
|
int_type = GPIOEdgelRising;
|
|
break;
|
|
case IRQ_TYPE_EDGE_FALLING:
|
|
int_type = GPIOEdgelFalling;
|
|
break;
|
|
case IRQ_TYPE_LEVEL_HIGH:
|
|
int_type = GPIOLevelHigh;
|
|
break;
|
|
case IRQ_TYPE_LEVEL_LOW:
|
|
int_type = GPIOLevelLow;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Before set interrupt type, gpio must set input */
|
|
gpio_bit_op(bank->regbase, GPIO_SWPORT_DDR,
|
|
offset_to_bit(gpio), GPIO_IN);
|
|
gpio_set_intr_type(bank->regbase, offset_to_bit(gpio), int_type);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int gpio_irq_revert_type(int gpio_irq)
|
|
{
|
|
int gpio = irq_to_gpio(gpio_irq);
|
|
struct gpio_bank *bank = gpio_to_bank(gpio);
|
|
eGPIOIntType_t int_type = 0;
|
|
int type;
|
|
|
|
if (!bank)
|
|
return -EINVAL;
|
|
|
|
gpio &= GPIO_PIN_MASK;
|
|
if (gpio >= bank->ngpio)
|
|
return -EINVAL;
|
|
|
|
type = gpio_get_intr_type(bank->regbase, offset_to_bit(gpio));
|
|
switch (type) {
|
|
case GPIOEdgelFalling:
|
|
int_type = GPIOEdgelRising;
|
|
break;
|
|
case GPIOEdgelRising:
|
|
int_type = GPIOEdgelFalling;
|
|
break;
|
|
case GPIOLevelHigh:
|
|
int_type = GPIOLevelLow;
|
|
break;
|
|
case GPIOLevelLow:
|
|
int_type = GPIOLevelHigh;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
gpio_set_intr_type(bank->regbase, offset_to_bit(gpio), int_type);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int gpio_irq_get_gpio_level(int gpio_irq)
|
|
{
|
|
int gpio = irq_to_gpio(gpio_irq);
|
|
struct gpio_bank *bank = gpio_to_bank(gpio);
|
|
|
|
if (!bank)
|
|
return -EINVAL;
|
|
|
|
gpio &= GPIO_PIN_MASK;
|
|
if (gpio >= bank->ngpio)
|
|
return -EINVAL;
|
|
|
|
return gpio_bit_rd(bank->regbase, GPIO_EXT_PORT, offset_to_bit(gpio));
|
|
}
|
|
|
|
static int gpio_irq_enable(int gpio_irq)
|
|
{
|
|
int gpio = irq_to_gpio(gpio_irq);
|
|
struct gpio_bank *bank = gpio_to_bank(gpio);
|
|
|
|
if (!bank)
|
|
return -EINVAL;
|
|
|
|
gpio &= GPIO_PIN_MASK;
|
|
if (gpio >= bank->ngpio)
|
|
return -EINVAL;
|
|
|
|
gpio_irq_unmask(bank->regbase, offset_to_bit(gpio));
|
|
|
|
if (bank->use_count == 0)
|
|
irq_handler_enable(IRQ_GPIO0 + bank->id);
|
|
bank->use_count++;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int gpio_irq_disable(int irq)
|
|
{
|
|
int gpio = irq_to_gpio(irq);
|
|
struct gpio_bank *bank = gpio_to_bank(gpio);
|
|
|
|
if (!bank)
|
|
return -EINVAL;
|
|
|
|
if (bank->use_count <= 0)
|
|
return 0;
|
|
|
|
gpio &= GPIO_PIN_MASK;
|
|
if (gpio >= bank->ngpio)
|
|
return -EINVAL;
|
|
|
|
gpio_irq_mask(bank->regbase, offset_to_bit(gpio));
|
|
|
|
if (bank->use_count == 1)
|
|
irq_handler_disable(IRQ_GPIO0 + bank->id);
|
|
bank->use_count--;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int gpio_irq_init(void)
|
|
{
|
|
struct gpio_bank *bank = NULL;
|
|
int i = 0;
|
|
|
|
for (i = 0; i < GPIO_BANK_NUM; i++) {
|
|
struct udevice *dev;
|
|
|
|
dev = malloc(sizeof(*dev));
|
|
if (!dev)
|
|
return -ENOMEM;
|
|
|
|
bank = gpio_id_to_bank(i);
|
|
if (bank) {
|
|
dev->name = bank->name;
|
|
|
|
/* disable gpio pin interrupt */
|
|
writel(0, bank->regbase + GPIO_INTEN);
|
|
|
|
/* register gpio group irq handler */
|
|
irq_install_handler(IRQ_GPIO0 + bank->id,
|
|
(interrupt_handler_t *)generic_gpio_handle_irq, dev);
|
|
|
|
/* default disable all gpio group interrupt */
|
|
irq_handler_disable(IRQ_GPIO0 + bank->id);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct irq_chip gpio_irq_chip = {
|
|
.name = "gpio-irq-chip",
|
|
.irq_init = gpio_irq_init,
|
|
.irq_enable = gpio_irq_enable,
|
|
.irq_disable = gpio_irq_disable,
|
|
.irq_set_type = gpio_irq_set_type,
|
|
.irq_revert_type = gpio_irq_revert_type,
|
|
.irq_get_gpio_level = gpio_irq_get_gpio_level,
|
|
};
|
|
|
|
struct irq_chip *arch_gpio_get_irqchip(void)
|
|
{
|
|
return &gpio_irq_chip;
|
|
}
|
|
|