Commit 1703be8f authored by David Brownell's avatar David Brownell Committed by Tony Lindgren

replace twl4030-gpio irq_chip

Replace twl4030 GPIO IRQ handling with generic SIH support.
No change in functionality; but there's now one less kernel
thread tied up supporting this chip.
Signed-off-by: default avatarDavid Brownell <dbrownell@users.sourceforge.net>
Signed-off-by: default avatarTony Lindgren <tony@atomide.com>
parent 8243a8a9
...@@ -51,22 +51,8 @@ ...@@ -51,22 +51,8 @@
*/ */
static inline void activate_irq(int irq)
{
#ifdef CONFIG_ARM
/* ARM requires an extra step to clear IRQ_NOREQUEST, which it
* sets on behalf of every irq_chip. Also sets IRQ_NOPROBE.
*/
set_irq_flags(irq, IRQF_VALID);
#else
/* same effect on other architectures */
set_irq_noprobe(irq);
#endif
}
static struct gpio_chip twl_gpiochip; static struct gpio_chip twl_gpiochip;
static int twl4030_gpio_irq_base; static int twl4030_gpio_irq_base;
static int twl4030_gpio_irq_end;
/* genirq interfaces are not available to modules */ /* genirq interfaces are not available to modules */
#ifdef MODULE #ifdef MODULE
...@@ -89,133 +75,7 @@ static DEFINE_MUTEX(gpio_lock); ...@@ -89,133 +75,7 @@ static DEFINE_MUTEX(gpio_lock);
/* store usage of each GPIO. - each bit represents one GPIO */ /* store usage of each GPIO. - each bit represents one GPIO */
static unsigned int gpio_usage_count; static unsigned int gpio_usage_count;
/* protect what irq_chip modifies through its helper task */ /*----------------------------------------------------------------------*/
static DEFINE_SPINLOCK(gpio_spinlock);
/* shadow the imr register; updates are delayed */
static u32 gpio_imr_shadow;
static bool gpio_pending_mask;
/* bitmask of requests to set gpio irq trigger type */
static unsigned int gpio_pending_trigger;
/* pointer to helper thread */
static struct task_struct *gpio_helper_thread;
/*
* Helper functions to read and write the GPIO ISR and IMR registers as
* 32-bit integers. Functions return 0 on success, non-zero otherwise.
* The caller must hold gpio_lock.
*/
/* NOTE: only the IRQ dispatcher may read ISR; reading it has
* side effects, because of the clear-on-read mechanism (COR=1).
*/
static int gpio_read_isr(unsigned int *isr)
{
int ret;
*isr = 0;
ret = twl4030_i2c_read(TWL4030_MODULE_GPIO, (u8 *) isr,
REG_GPIO_ISR1A, 3);
le32_to_cpup(isr);
*isr &= GPIO_32_MASK;
return ret;
}
/* IMR is written only during initialization and
* by request of the irq_chip code.
*/
static int gpio_write_imr(unsigned int imr)
{
imr &= GPIO_32_MASK;
/*
* The buffer passed to the twl4030_i2c_write() routine must have an
* extra byte at the beginning reserved for its internal use.
*/
imr <<= 8;
imr = cpu_to_le32(imr);
return twl4030_i2c_write(TWL4030_MODULE_GPIO, (u8 *) &imr,
REG_GPIO_IMR1A, 3);
}
/*
* These are the irqchip methods for the TWL4030 GPIO interrupts.
* Our IRQ handle method doesn't call these, but they will be called by
* other routines such as setup_irq() and enable_irq(). They are called
* with cpu interrupts disabled and with a lock on the irq_desc.lock
* spinlock. This complicates matters, because accessing the TWL4030 GPIO
* interrupt controller requires I2C bus transactions that can't be initiated
* in this context. Our solution is to defer accessing the interrupt
* controller to a kernel thread.
*/
static void twl4030_gpio_irq_ack(unsigned int irq)
{
/* NOP -- dispatcher acks when it reads ISR */
}
static void twl4030_gpio_irq_mask(unsigned int irq)
{
int gpio = irq - twl4030_gpio_irq_base;
u32 mask = 1 << gpio;
unsigned long flags;
spin_lock_irqsave(&gpio_spinlock, flags);
gpio_pending_mask = true;
gpio_imr_shadow |= mask;
if (gpio_helper_thread && gpio_helper_thread->state != TASK_RUNNING)
wake_up_process(gpio_helper_thread);
spin_unlock_irqrestore(&gpio_spinlock, flags);
}
static void twl4030_gpio_irq_unmask(unsigned int irq)
{
int gpio = irq - twl4030_gpio_irq_base;
u32 mask = 1 << gpio;
unsigned long flags;
spin_lock_irqsave(&gpio_spinlock, flags);
gpio_pending_mask = true;
gpio_imr_shadow &= ~mask;
if (gpio_helper_thread && gpio_helper_thread->state != TASK_RUNNING)
wake_up_process(gpio_helper_thread);
spin_unlock_irqrestore(&gpio_spinlock, flags);
}
static int twl4030_gpio_irq_set_type(unsigned int irq, unsigned trigger)
{
struct irq_desc *desc = irq_desc + irq;
int gpio = irq - twl4030_gpio_irq_base;
unsigned long flags;
trigger &= IRQ_TYPE_SENSE_MASK;
if (trigger & ~IRQ_TYPE_EDGE_BOTH)
return -EINVAL;
if ((desc->status & IRQ_TYPE_SENSE_MASK) == trigger)
return 0;
desc->status &= ~IRQ_TYPE_SENSE_MASK;
desc->status |= trigger;
spin_lock_irqsave(&gpio_spinlock, flags);
gpio_pending_trigger |= (1 << gpio);
if (gpio_helper_thread && gpio_helper_thread->state != TASK_RUNNING)
wake_up_process(gpio_helper_thread);
spin_unlock_irqrestore(&gpio_spinlock, flags);
return 0;
}
static struct irq_chip twl4030_gpio_irq_chip = {
.name = "twl4030",
.ack = twl4030_gpio_irq_ack,
.mask = twl4030_gpio_irq_mask,
.unmask = twl4030_gpio_irq_unmask,
.set_type = twl4030_gpio_irq_set_type,
};
/* /*
* To configure TWL4030 GPIO module registers * To configure TWL4030 GPIO module registers
...@@ -355,35 +215,6 @@ int twl4030_get_gpio_datain(int gpio) ...@@ -355,35 +215,6 @@ int twl4030_get_gpio_datain(int gpio)
} }
EXPORT_SYMBOL(twl4030_get_gpio_datain); EXPORT_SYMBOL(twl4030_get_gpio_datain);
static int twl4030_set_gpio_edge_ctrl(int gpio, int edge)
{
u8 c_bnk = gpio >> 2;
u8 c_off = (gpio & 0x3) * 2;
u8 c_msk = 0;
u8 reg = 0;
u8 base = 0;
int ret = 0;
base = REG_GPIO_EDR1 + c_bnk;
if (edge & IRQ_TYPE_EDGE_RISING)
c_msk |= BIT(c_off + 1);
if (edge & IRQ_TYPE_EDGE_FALLING)
c_msk |= BIT(c_off);
mutex_lock(&gpio_lock);
ret = gpio_twl4030_read(base);
if (ret >= 0) {
/* clear the previous rising/falling values */
reg = (u8) ret;
reg &= ~(0x3 << c_off);
reg |= c_msk;
ret = gpio_twl4030_write(base, reg);
}
mutex_unlock(&gpio_lock);
return ret;
}
/* /*
* Configure debounce timing value for a GPIO pin on TWL4030 * Configure debounce timing value for a GPIO pin on TWL4030
*/ */
...@@ -450,105 +281,6 @@ int twl4030_set_gpio_card_detect(int gpio, int enable) ...@@ -450,105 +281,6 @@ int twl4030_set_gpio_card_detect(int gpio, int enable)
} }
#endif #endif
/* MODULE FUNCTIONS */
/*
* gpio_helper_thread() is a kernel thread that is awakened by irq_chip
* methods to update IRQ masks and trigger modes.
*
* We must do this in a thread rather than in irq_chip methods because of the
* need to access the TWL4030 via the I2C bus. Note that we don't need to be
* concerned about race conditions where the request to unmask a GPIO interrupt
* has already been cancelled before this thread does the unmasking. If a GPIO
* interrupt is improperly unmasked, then the IRQ handler for it will mask it
* when an interrupt occurs.
*/
static int twl4030_gpio_helper_thread(void *dev)
{
current->flags |= PF_NOFREEZE;
while (!kthread_should_stop()) {
int irq;
bool do_mask;
u32 local_imr;
unsigned int gpio_trigger;
spin_lock_irq(&gpio_spinlock);
do_mask = gpio_pending_mask;
gpio_pending_mask = false;
local_imr = gpio_imr_shadow;
gpio_trigger = gpio_pending_trigger;
gpio_pending_trigger = 0;
spin_unlock_irq(&gpio_spinlock);
if (do_mask) {
int status = gpio_write_imr(local_imr);
if (status)
dev_err(dev, "write IMR err %d\n", status);
}
for (irq = twl4030_gpio_irq_base;
gpio_trigger;
gpio_trigger >>= 1, irq++) {
struct irq_desc *desc;
unsigned type;
if (!(gpio_trigger & 0x1))
continue;
desc = irq_desc + irq;
spin_lock_irq(&desc->lock);
type = desc->status & IRQ_TYPE_SENSE_MASK;
spin_unlock_irq(&desc->lock);
twl4030_set_gpio_edge_ctrl(irq - twl4030_gpio_irq_base,
type);
}
spin_lock_irq(&gpio_spinlock);
if (!gpio_pending_mask && !gpio_pending_trigger)
set_current_state(TASK_INTERRUPTIBLE);
spin_unlock_irq(&gpio_spinlock);
schedule();
}
set_current_state(TASK_RUNNING);
return 0;
}
/*
* do_twl4030_gpio_module_irq() is the desc->handle method for the twl4030 gpio
* module interrupt. It executes in kernel thread context.
* This is a chained interrupt, so there is no desc->action method for it.
* We query the gpio module interrupt controller in the twl4030 to determine
* which gpio lines are generating interrupt requests, and then call the
* desc->handle method for each gpio that needs service.
* On entry, cpu interrupts are disabled.
*/
static void do_twl4030_gpio_module_irq(unsigned int irq, irq_desc_t *desc)
{
int gpio_irq;
unsigned int gpio_isr;
/* PIH irqs like this can't be individually masked or acked ...
* so we don't even call the PIH irq_chip stub methods.
*/
local_irq_enable();
if (gpio_read_isr(&gpio_isr))
gpio_isr = 0;
local_irq_disable();
for (gpio_irq = twl4030_gpio_irq_base; 0 != gpio_isr;
gpio_isr >>= 1, gpio_irq++) {
if (gpio_isr & 0x1)
generic_handle_irq(gpio_irq);
}
}
/*----------------------------------------------------------------------*/ /*----------------------------------------------------------------------*/
static int twl_direction_in(struct gpio_chip *chip, unsigned offset) static int twl_direction_in(struct gpio_chip *chip, unsigned offset)
...@@ -576,8 +308,9 @@ static void twl_set(struct gpio_chip *chip, unsigned offset, int value) ...@@ -576,8 +308,9 @@ static void twl_set(struct gpio_chip *chip, unsigned offset, int value)
static int twl_to_irq(struct gpio_chip *chip, unsigned offset) static int twl_to_irq(struct gpio_chip *chip, unsigned offset)
{ {
/* NOTE: assumes IRQs are set up ... */ return twl4030_gpio_irq_base
return twl4030_gpio_irq_base + offset; ? (twl4030_gpio_irq_base + offset)
: -EINVAL;
} }
static struct gpio_chip twl_gpiochip = { static struct gpio_chip twl_gpiochip = {
...@@ -624,80 +357,22 @@ static int __devinit gpio_twl4030_probe(struct platform_device *pdev) ...@@ -624,80 +357,22 @@ static int __devinit gpio_twl4030_probe(struct platform_device *pdev)
{ {
struct twl4030_gpio_platform_data *pdata = pdev->dev.platform_data; struct twl4030_gpio_platform_data *pdata = pdev->dev.platform_data;
int ret; int ret;
int irq = 0;
/* All GPIO interrupts are initially masked */
gpio_pending_mask = false;
gpio_imr_shadow = GPIO_32_MASK;
ret = gpio_write_imr(gpio_imr_shadow);
twl4030_gpio_irq_base = pdata->irq_base;
twl4030_gpio_irq_end = pdata->irq_end;
if ((twl4030_gpio_irq_end - twl4030_gpio_irq_base) > 0) { /* maybe setup IRQs */
if (pdata->irq_base) {
if (is_module()) { if (is_module()) {
dev_err(&pdev->dev, dev_err(&pdev->dev,
"can't dispatch IRQs from modules\n"); "can't dispatch IRQs from modules\n");
goto no_irqs; goto no_irqs;
} }
if (twl4030_gpio_irq_end > NR_IRQS) { ret = twl4030_sih_setup(TWL4030_MODULE_GPIO);
dev_err(&pdev->dev, if (ret < 0)
"last IRQ is too large: %d\n", return ret;
twl4030_gpio_irq_end); WARN_ON(ret != pdata->irq_base);
return -EINVAL; twl4030_gpio_irq_base = ret;
}
} else {
dev_notice(&pdev->dev,
"no IRQs being dispatched\n");
goto no_irqs;
}
if (!ret) {
/*
* Create a kernel thread to handle deferred operations on gpio
* interrupts.
*/
gpio_helper_thread = kthread_create(twl4030_gpio_helper_thread,
&pdev->dev, "twl4030 gpio");
if (!gpio_helper_thread) {
dev_err(&pdev->dev,
"could not create helper thread!\n");
ret = -ENOMEM;
}
}
if (!ret) {
/* install an irq handler for each of the gpio interrupts */
for (irq = twl4030_gpio_irq_base; irq < twl4030_gpio_irq_end;
irq++) {
set_irq_chip_and_handler(irq, &twl4030_gpio_irq_chip,
handle_edge_irq);
activate_irq(irq);
}
/* gpio module IRQ */
irq = platform_get_irq(pdev, 0);
/* COR=1 means irqs are acked by reading ISR
* PENDDIS=0 means pending events enabled
* EXCLEN=0 means route to both irq lines
*/
gpio_twl4030_write(REG_GPIO_SIH_CTRL,
TWL4030_SIH_CTRL_COR_MASK);
/*
* Install an irq handler to demultiplex the gpio module
* interrupt.
*/
set_irq_chained_handler(irq, do_twl4030_gpio_module_irq);
wake_up_process(gpio_helper_thread);
dev_info(&pdev->dev, "IRQ %d chains IRQs %d..%d\n", irq,
twl4030_gpio_irq_base, twl4030_gpio_irq_end - 1);
} }
no_irqs: no_irqs:
if (!ret) {
/* /*
* NOTE: boards may waste power if they don't set pullups * NOTE: boards may waste power if they don't set pullups
* and pulldowns correctly ... default for non-ULPI pins is * and pulldowns correctly ... default for non-ULPI pins is
...@@ -729,7 +404,6 @@ no_irqs: ...@@ -729,7 +404,6 @@ no_irqs:
if (status) if (status)
dev_dbg(&pdev->dev, "setup --> %d\n", status); dev_dbg(&pdev->dev, "setup --> %d\n", status);
} }
}
return ret; return ret;
} }
...@@ -738,7 +412,6 @@ static int __devexit gpio_twl4030_remove(struct platform_device *pdev) ...@@ -738,7 +412,6 @@ static int __devexit gpio_twl4030_remove(struct platform_device *pdev)
{ {
struct twl4030_gpio_platform_data *pdata = pdev->dev.platform_data; struct twl4030_gpio_platform_data *pdata = pdev->dev.platform_data;
int status; int status;
int irq;
if (pdata->teardown) { if (pdata->teardown) {
status = pdata->teardown(&pdev->dev, status = pdata->teardown(&pdev->dev,
...@@ -753,24 +426,12 @@ static int __devexit gpio_twl4030_remove(struct platform_device *pdev) ...@@ -753,24 +426,12 @@ static int __devexit gpio_twl4030_remove(struct platform_device *pdev)
if (status < 0) if (status < 0)
return status; return status;
if (is_module() || (twl4030_gpio_irq_end - twl4030_gpio_irq_base) <= 0) if (is_module())
return 0; return 0;
/* uninstall the gpio demultiplexing interrupt handler */ /* REVISIT no support yet for deregistering all the IRQs */
irq = platform_get_irq(pdev, 0); WARN_ON(1);
set_irq_handler(irq, NULL); return -EIO;
/* uninstall the irq handler for each of the gpio interrupts */
for (irq = twl4030_gpio_irq_base; irq < twl4030_gpio_irq_end; irq++)
set_irq_handler(irq, NULL);
/* stop the gpio helper kernel thread */
if (gpio_helper_thread) {
kthread_stop(gpio_helper_thread);
gpio_helper_thread = NULL;
}
return 0;
} }
/* Note: this hardware lives inside an I2C-based multi-function device. */ /* Note: this hardware lives inside an I2C-based multi-function device. */
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment