I can't test the pwrbutton stuff, but rtc and musb behave...
With one issue: the usb transceiver glue misbehaves on
startup if the device is connected. (Again.) I looked at
this a bit, and I think the issue is probably caused by
not actually using key USB registers ... IRQ trigger
mode state transitions are at best a fragile proxy for the
real state.
(This is similar to the GPIO patch I just sent, but simpler
except for the impact on the few drivers thinking oddly
about IRQs. Those patches cover the key SIH modules, and
a similar one affects the PIH in twl4030-core.)
- Dave
================================
Streamline the "power irq" code, and some of the mechanisms
it uses:
- Support IRQ masking, not just unmasking; simpler code.
- Use the standard handle_edge_irq() handler for these edge IRQs.
- Use generic_handle_irq() instead of a manual expansion thereof.
- Provide a missing spinlock for the shared data.
In short, use more of the standard genirq code ... more correctly.
Also, update the drivers using the "power IRQ" accordingly:
- Don't request IRQF_DISABLED if the handler makes I2C calls;
and defend against lockdep forcing it on us.
- Let the irq_chip handle IRQ mask/unmask; it handles mutual
exclusion between drivers, among other things.
- (Unrelated) remove useless MODULE_ALIAS in pwrbutton.
The USB transceiver driver still places some dodgey games with IRQ
enable/disable, and IRQ trigger flags. At least some of them seem
like they'd be simplified by using USB transceiver registers ...
notably, startup code, which doesn't seem to check state before
it enters an irq-driven mode.
For the moment, these drivers still (wrongly) try to configure IRQ
trigger modes themselves ... again, that's something that an irq_chip
handles (but not yet, for this one).
NOTE: tested (MUSB and RTC only) along with the init/retry hack
to make twl4030-pwrirq work around the i2c-omap timeout problems.
int err;
u8 value;
+#ifdef CONFIG_LOCKDEP
+ /* WORKAROUND for lockdep forcing IRQF_DISABLED on us, which
+ * we don't want and can't tolerate. Although it might be
+ * friendlier not to borrow this thread context...
+ */
+ local_irq_enable();
+#endif
+
err = twl4030_i2c_read_u8(TWL4030_MODULE_PM_MASTER, &value,
STS_HW_CONDITIONS);
if (!err) {
int err = 0;
u8 value;
+ /* PWRBTN == PWRON */
if (request_irq(TWL4030_PWRIRQ_PWRBTN, powerbutton_irq, 0,
"PwrButton", NULL) < 0) {
printk(KERN_ERR "Unable to allocate IRQ for power button\n");
goto free_irq_and_out;
}
- err = twl4030_i2c_read_u8(TWL4030_MODULE_INT, &value, PWR_IMR1);
- if (err) {
- printk(KERN_WARNING "I2C error %d while reading TWL4030"
- " INT PWR_IMR1 register\n", err);
-
- goto free_input_dev;
- }
-
- err = twl4030_i2c_write_u8(TWL4030_MODULE_INT,
- value & (~PWR_PWRON_IRQ), PWR_IMR1);
- if (err) {
- printk(KERN_WARNING "I2C error %d while writing TWL4030"
- " INT PWR_IMR1 register\n", err);
- goto free_input_dev;
- }
-
+ /* FIXME just pass IRQF_EDGE_FALLING | IRQF_EDGE_RISING
+ * to request_irq(), once MODULE_INT supports them...
+ */
err = twl4030_i2c_read_u8(TWL4030_MODULE_INT, &value, PWR_EDR1);
if (err) {
printk(KERN_WARNING "I2C error %d while reading TWL4030"
out:
return err;
}
+module_init(twl4030_pwrbutton_init);
static void __exit twl4030_pwrbutton_exit(void)
{
free_irq(TWL4030_PWRIRQ_PWRBTN, NULL);
input_unregister_device(powerbutton_dev);
input_free_device(powerbutton_dev);
-
}
-
-module_init(twl4030_pwrbutton_init);
module_exit(twl4030_pwrbutton_exit);
-MODULE_ALIAS("i2c:twl4030-pwrbutton");
MODULE_DESCRIPTION("Triton2 Power Button");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Peter De Schrijver");
#include <linux/i2c/twl4030.h>
+static DEFINE_SPINLOCK(pwr_lock);
static u8 twl4030_pwrirq_mask;
-static u8 twl4030_pwrirq_pending_unmask;
static struct task_struct *twl4030_pwrirq_unmask_thread;
static void twl4030_pwrirq_ack(unsigned int irq) {}
-static void twl4030_pwrirq_disableint(unsigned int irq) {}
+static void twl4030_pwrirq_disableint(unsigned int irq)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&pwr_lock, flags);
+ twl4030_pwrirq_mask |= 1 << (irq - TWL4030_PWR_IRQ_BASE);
+ if (twl4030_pwrirq_unmask_thread
+ && twl4030_pwrirq_unmask_thread->state != TASK_RUNNING)
+ wake_up_process(twl4030_pwrirq_unmask_thread);
+ spin_unlock_irqrestore(&pwr_lock, flags);
+}
static void twl4030_pwrirq_enableint(unsigned int irq)
{
- twl4030_pwrirq_pending_unmask |= 1 << (irq - TWL4030_PWR_IRQ_BASE);
- if (twl4030_pwrirq_unmask_thread &&
- twl4030_pwrirq_unmask_thread->state != TASK_RUNNING)
+ unsigned long flags;
+
+ spin_lock_irqsave(&pwr_lock, flags);
+ twl4030_pwrirq_mask &= ~(1 << (irq - TWL4030_PWR_IRQ_BASE));
+ if (twl4030_pwrirq_unmask_thread
+ && twl4030_pwrirq_unmask_thread->state != TASK_RUNNING)
wake_up_process(twl4030_pwrirq_unmask_thread);
+ spin_unlock_irqrestore(&pwr_lock, flags);
}
static struct irq_chip twl4030_pwrirq_chip = {
.unmask = twl4030_pwrirq_enableint,
};
-static void do_twl4030_pwrmodule_irq(unsigned int irq, irq_desc_t *desc)
-{
- struct irqaction *action;
- const unsigned int cpu = smp_processor_id();
-
- desc->status |= IRQ_LEVEL;
-
- if (!desc->depth) {
- kstat_cpu(cpu).irqs[irq]++;
-
- action = desc->action;
- if (action) {
- int ret;
- int status = 0;
- int retval = 0;
-
- do {
- ret = action->handler(irq, action->dev_id);
- if (ret == IRQ_HANDLED)
- status |= action->flags;
- retval |= ret;
- action = action->next;
- } while (action);
-
- if (status & IRQF_SAMPLE_RANDOM)
- add_interrupt_randomness(irq);
-
- if (retval != IRQ_HANDLED)
- printk(KERN_ERR "ISR for TWL4030 power module"
- " irq %d can't handle interrupt\n",
- irq);
- } else {
- local_irq_disable();
- twl4030_pwrirq_mask |= 1 << (irq - TWL4030_PWR_IRQ_BASE);
- local_irq_enable();
- twl4030_i2c_write_u8(TWL4030_MODULE_INT,
- twl4030_pwrirq_mask,
- TWL4030_INT_PWR_IMR1);
- }
- }
-}
-
static void do_twl4030_pwrirq(unsigned int irq, irq_desc_t *desc)
{
const unsigned int cpu = smp_processor_id();
local_irq_enable();
ret = twl4030_i2c_read_u8(TWL4030_MODULE_INT, &pwr_isr,
TWL4030_INT_PWR_ISR1);
+ local_irq_disable();
if (ret) {
printk(KERN_WARNING
"I2C error %d while reading TWL4030"
for (module_irq = TWL4030_PWR_IRQ_BASE; pwr_isr != 0;
module_irq++, pwr_isr >>= 1) {
- if (pwr_isr & 1) {
- irq_desc_t *d = irq_desc + module_irq;
- d->handle_irq(module_irq, d);
- }
+ if (pwr_isr & 1)
+ generic_handle_irq(module_irq);
}
- local_irq_disable();
desc->chip->unmask(irq);
}
}
current->flags |= PF_NOFREEZE;
while (!kthread_should_stop()) {
- u8 local_unmask;
-
- local_irq_disable();
- local_unmask = twl4030_pwrirq_pending_unmask;
- twl4030_pwrirq_pending_unmask = 0;
- local_irq_enable();
+ u8 local_mask;
- twl4030_pwrirq_mask &= ~local_unmask;
+ spin_lock_irq(&pwr_lock);
+ local_mask = twl4030_pwrirq_mask;
+ spin_unlock_irq(&pwr_lock);
- twl4030_i2c_write_u8(TWL4030_MODULE_INT, twl4030_pwrirq_mask,
+ twl4030_i2c_write_u8(TWL4030_MODULE_INT, local_mask,
TWL4030_INT_PWR_IMR1);
- local_irq_disable();
- if (!twl4030_pwrirq_pending_unmask)
+ spin_lock_irq(&pwr_lock);
+ if (local_mask == twl4030_pwrirq_mask)
set_current_state(TASK_INTERRUPTIBLE);
- local_irq_enable();
+ spin_unlock_irq(&pwr_lock);
schedule();
}
int i, err;
twl4030_pwrirq_mask = 0xff;
- twl4030_pwrirq_pending_unmask = 0;
err = twl4030_i2c_write_u8(TWL4030_MODULE_INT, twl4030_pwrirq_mask,
TWL4030_INT_PWR_IMR1);
}
for (i = TWL4030_PWR_IRQ_BASE; i < TWL4030_PWR_IRQ_END; i++) {
- set_irq_chip(i, &twl4030_pwrirq_chip);
- set_irq_handler(i, do_twl4030_pwrmodule_irq);
+ set_irq_chip_and_handler(i, &twl4030_pwrirq_chip,
+ handle_edge_irq);
set_irq_flags(i, IRQF_VALID);
}
#define GPIO_USB_4PIN_ULPI_2430C (3 << 0)
/* In module TWL4030_MODULE_INT */
-#define REG_PWR_ISR1 0x00
-#define REG_PWR_IMR1 0x01
-#define USB_PRES (1 << 2)
#define REG_PWR_EDR1 0x05
#define USB_PRES_FALLING (1 << 4)
#define USB_PRES_RISING (1 << 5)
-#define REG_PWR_SIH_CTRL 0x07
-#define COR (1 << 2)
/* bits in OTG_CTRL */
#define OTG_XCEIV_OUTPUTS \
unsigned vbus:1;
int irq;
u8 asleep;
+ bool irq_enabled;
};
/* internal define on top of container_of */
{
u8 val;
+ /* FIXME use set_irq_type(...) when that (soon) works */
+
/* edge setup */
WARN_ON(twl4030_i2c_read_u8(TWL4030_MODULE_INT,
&val, REG_PWR_EDR1) < 0);
WARN_ON(twl4030_i2c_write_u8_verify(twl, TWL4030_MODULE_INT,
val, REG_PWR_EDR1) < 0);
- /* un-mask interrupt */
- WARN_ON(twl4030_i2c_read_u8(TWL4030_MODULE_INT,
- &val, REG_PWR_IMR1) < 0);
-
- val &= ~USB_PRES;
-
- WARN_ON(twl4030_i2c_write_u8_verify(twl, TWL4030_MODULE_INT,
- val, REG_PWR_IMR1) < 0);
+ if (!twl->irq_enabled) {
+ enable_irq(twl->irq);
+ twl->irq_enabled = true;
+ }
}
static void usb_irq_disable(struct twl4030_usb *twl)
{
- u8 val;
-
- /* undo edge setup */
- WARN_ON(twl4030_i2c_read_u8(TWL4030_MODULE_INT,
- &val, REG_PWR_EDR1) < 0);
- val &= ~(USB_PRES_RISING | USB_PRES_FALLING);
- WARN_ON(twl4030_i2c_write_u8_verify(twl, TWL4030_MODULE_INT,
- val, REG_PWR_EDR1) < 0);
-
- /* mask interrupt */
- WARN_ON(twl4030_i2c_read_u8(TWL4030_MODULE_INT,
- &val, REG_PWR_IMR1) < 0);
- val |= USB_PRES;
-
- WARN_ON(twl4030_i2c_write_u8_verify(twl, TWL4030_MODULE_INT,
- val, REG_PWR_IMR1) < 0);
+ if (twl->irq_enabled) {
+ disable_irq(twl->irq);
+ twl->irq_enabled = false;
+ }
}
static void twl4030_phy_power(struct twl4030_usb *twl, int on)
struct twl4030_usb *twl = _twl;
u8 val;
+#ifdef CONFIG_LOCKDEP
+ /* WORKAROUND for lockdep forcing IRQF_DISABLED on us, which
+ * we don't want and can't tolerate. Although it might be
+ * friendlier not to borrow this thread context...
+ */
+ local_irq_enable();
+#endif
+
+ /* FIXME stop accessing PWR_EDR1 ... if nothing else, we
+ * know which edges we told the IRQ to trigger on. And
+ * there seem to be OTG_specific registers and irqs that
+ * provide the right info without guessing like this:
+ * USB_INT_STS, ID_STATUS, STS_HW_CONDITIONS, etc.
+ */
+
/* action based on cable attach or detach */
WARN_ON(twl4030_i2c_read_u8(TWL4030_MODULE_INT,
&val, REG_PWR_EDR1) < 0);
/* init irq workqueue before request_irq */
INIT_WORK(&twl->irq_work, twl4030_usb_irq_work);
- usb_irq_disable(twl);
+ twl->irq_enabled = true;
status = request_irq(twl->irq, twl4030_usb_irq, 0, "twl4030_usb", twl);
if (status < 0) {
dev_dbg(&pdev->dev, "can't get IRQ %d, err %d\n",
int res;
u8 rd_reg;
+#ifdef CONFIG_LOCKDEP
+ /* WORKAROUND for lockdep forcing IRQF_DISABLED on us, which
+ * we don't want and can't tolerate. Although it might be
+ * friendlier not to borrow this thread context...
+ */
+ local_irq_enable();
+#endif
+
res = twl4030_rtc_read_u8(&rd_reg, REG_RTC_STATUS_REG);
if (res)
goto out;