* David Brownell <david-b@xxxxxxxxxxx> [081016 12:34]: > From: David Brownell <dbrownell@xxxxxxxxxxxxxxxxxxxxx> > > Simplify handling of VBUS and ID status and the USB_PRES interrupt. > This seems to resolve the problems I previously saw on Beagle and > Overo boards. > > - Read the status directly, instead of trying to infer it from > dodgey side effects of IRQ handling and trigger tweaking. > > - Stop trying to arrange those dodgey side effects; just leave > the IRQ enabled at all times. > > - Check that status as part of driver probe, so the PHY can > always be active by the time it's needed. (BUGFIX!) > > - Re-sequence probe() to be less racey, and to avoid duplicating > logic found elsewhere (notably irq handling and PHY activation). > > - Make the irq_chip be the exclusive owner of PWR_EDR (finally). > > - Get rid of needless work_struct ... in this irq handling thread, > it's safe and cheap to call sysfs_notify() directly. > > Stop enabling IRQs on the USB subchip. There's no IRQ handler for > them, and since ALT_INT_REROUTE isn't being set they'll never > arrive ... so there's no point to enabling them. > > Also add some comments about other things such an otg_transciever > driver should be doing, fixing minor omissions; and remove some > unnecessary file inclusions. Pushing today, I guess that's it for the USB problems for now. Tony > > Signed-off-by: David Brownell <dbrownell@xxxxxxxxxxxxxxxxxxxxx> > --- > NOTE: applies on top of the previous patches updating IRQ > handling and creating drivers/mfd/twl4030-irq.c ... > > drivers/i2c/chips/twl4030-usb.c | 209 ++++++++++++++++++-------------------- > 1 file changed, 104 insertions(+), 105 deletions(-) > > --- a/drivers/i2c/chips/twl4030-usb.c > +++ b/drivers/i2c/chips/twl4030-usb.c > @@ -26,16 +26,12 @@ > > #include <linux/module.h> > #include <linux/init.h> > -#include <linux/time.h> > #include <linux/interrupt.h> > -#include <linux/irq.h> > #include <linux/platform_device.h> > #include <linux/spinlock.h> > #include <linux/workqueue.h> > #include <linux/io.h> > -#include <linux/usb.h> > -#include <linux/usb/ch9.h> > -#include <linux/usb/gadget.h> > +#include <linux/delay.h> > #include <linux/usb/otg.h> > #include <linux/i2c/twl4030.h> > #include <mach/usb.h> > @@ -237,10 +233,6 @@ > #define PMBR1 0x0D > #define GPIO_USB_4PIN_ULPI_2430C (3 << 0) > > -/* In module TWL4030_MODULE_INT */ > -#define REG_PWR_EDR1 0x05 > -#define USB_PRES_FALLING (1 << 4) > -#define USB_PRES_RISING (1 << 5) > > /* bits in OTG_CTRL */ > #define OTG_XCEIV_OUTPUTS \ > @@ -256,8 +248,14 @@ > OTG_CTRL_BITS) > > > +enum linkstat { > + USB_LINK_UNKNOWN = 0, > + USB_LINK_NONE, > + USB_LINK_VBUS, > + USB_LINK_ID, > +}; > + > struct twl4030_usb { > - struct work_struct irq_work; > struct otg_transceiver otg; > struct device *dev; > > @@ -267,8 +265,8 @@ struct twl4030_usb { > /* pin configuration */ > enum twl4030_usb_mode usb_mode; > > - unsigned vbus:1; > int irq; > + u8 linkstat; > u8 asleep; > bool irq_enabled; > }; > @@ -326,21 +324,27 @@ static inline int twl4030_usb_write(stru > return ret; > } > > -static inline int twl4030_usb_read(struct twl4030_usb *twl, u8 address) > +static inline int twl4030_readb(struct twl4030_usb *twl, u8 module, u8 address) > { > u8 data; > int ret = 0; > > - ret = twl4030_i2c_read_u8(TWL4030_MODULE_USB, &data, address); > + ret = twl4030_i2c_read_u8(module, &data, address); > if (ret >= 0) > ret = data; > else > dev_warn(twl->dev, > - "TWL4030:USB:Read[0x%x] Error %d\n", address, ret); > + "TWL4030:readb[0x%x,0x%x] Error %d\n", > + module, address, ret); > > return ret; > } > > +static inline int twl4030_usb_read(struct twl4030_usb *twl, u8 address) > +{ > + return twl4030_readb(twl, TWL4030_MODULE_USB, address); > +} > + > /*-------------------------------------------------------------------------*/ > > static inline int > @@ -357,6 +361,39 @@ twl4030_usb_clear_bits(struct twl4030_us > > /*-------------------------------------------------------------------------*/ > > +static enum linkstat twl4030_usb_linkstat(struct twl4030_usb *twl) > +{ > + int status; > + int linkstat = USB_LINK_UNKNOWN; > + > + /* STS_HW_CONDITIONS */ > + status = twl4030_readb(twl, TWL4030_MODULE_PM_MASTER, 0x0f); > + if (status < 0) > + dev_err(twl->dev, "USB link status err %d\n", status); > + else if (status & BIT(7)) > + linkstat = USB_LINK_VBUS; > + else if (status & BIT(2)) > + linkstat = USB_LINK_ID; > + else > + linkstat = USB_LINK_NONE; > + > + dev_dbg(twl->dev, "HW_CONDITIONS 0x%02x/%d; link %d\n", > + status, status, linkstat); > + > + spin_lock_irq(&twl->lock); > + twl->linkstat = linkstat; > + if (linkstat == USB_LINK_ID) { > + twl->otg.default_a = true; > + twl->otg.state = OTG_STATE_A_IDLE; > + } else { > + twl->otg.default_a = false; > + twl->otg.state = OTG_STATE_B_IDLE; > + } > + spin_unlock_irq(&twl->lock); > + > + return linkstat; > +} > + > static void twl4030_usb_set_mode(struct twl4030_usb *twl, int mode) > { > twl->usb_mode = mode; > @@ -410,24 +447,6 @@ static void twl4030_i2c_access(struct tw > } > } > > -static void usb_irq_enable(struct twl4030_usb *twl, int trigger) > -{ > - set_irq_type(twl->irq, trigger); > - > - if (!twl->irq_enabled) { > - enable_irq(twl->irq); > - twl->irq_enabled = true; > - } > -} > - > -static void usb_irq_disable(struct twl4030_usb *twl) > -{ > - if (twl->irq_enabled) { > - disable_irq(twl->irq); > - twl->irq_enabled = false; > - } > -} > - > static void twl4030_phy_power(struct twl4030_usb *twl, int on) > { > u8 pwr; > @@ -448,16 +467,9 @@ static void twl4030_phy_power(struct twl > > static void twl4030_phy_suspend(struct twl4030_usb *twl, int controller_off) > { > - if (controller_off) > - usb_irq_disable(twl); > - > if (twl->asleep) > return; > > - if (!controller_off) > - /* enable rising edge interrupt to detect cable attach */ > - usb_irq_enable(twl, IRQ_TYPE_EDGE_RISING); > - > twl4030_phy_power(twl, 0); > twl->asleep = 1; > } > @@ -467,9 +479,6 @@ static void twl4030_phy_resume(struct tw > if (!twl->asleep) > return; > > - /* enable falling edge interrupt to detect cable detach */ > - usb_irq_enable(twl, IRQ_TYPE_EDGE_FALLING); > - > twl4030_phy_power(twl, 1); > twl4030_i2c_access(twl, 1); > twl4030_usb_set_mode(twl, twl->usb_mode); > @@ -514,25 +523,18 @@ static ssize_t twl4030_usb_vbus_show(str > int ret = -EINVAL; > > spin_lock_irqsave(&twl->lock, flags); > - ret = sprintf(buf, "%s\n", twl->vbus ? "on" : "off"); > + ret = sprintf(buf, "%s\n", > + (twl->linkstat == USB_LINK_VBUS) ? "on" : "off"); > spin_unlock_irqrestore(&twl->lock, flags); > > return ret; > } > static DEVICE_ATTR(vbus, 0444, twl4030_usb_vbus_show, NULL); > > -static void twl4030_usb_irq_work(struct work_struct *work) > -{ > - struct twl4030_usb *twl = container_of(work, > - struct twl4030_usb, irq_work); > - > - sysfs_notify(&twl->dev->kobj, NULL, "vbus"); > -} > - > static irqreturn_t twl4030_usb_irq(int irq, void *_twl) > { > struct twl4030_usb *twl = _twl; > - u8 val; > + int status; > > #ifdef CONFIG_LOCKDEP > /* WORKAROUND for lockdep forcing IRQF_DISABLED on us, which > @@ -542,27 +544,28 @@ static irqreturn_t twl4030_usb_irq(int i > 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. > - */ > + status = twl4030_usb_linkstat(twl); > + if (status != USB_LINK_UNKNOWN) { > > - /* action based on cable attach or detach */ > - WARN_ON(twl4030_i2c_read_u8(TWL4030_MODULE_INT, > - &val, REG_PWR_EDR1) < 0); > + /* FIXME add a set_power() method so that B-devices can > + * configure the charger appropriately. It's not always > + * correct to consume VBUS power, and how much current to > + * consume is a function of the USB configuration chosen > + * by the host. > + * > + * REVISIT usb_gadget_vbus_connect(...) as needed, ditto > + * its disconnect() sibling, when changing to/from the > + * USB_LINK_VBUS state. musb_hdrc won't care until it > + * starts to handle softconnect right. > + */ > + twl4030charger_usb_en(status == USB_LINK_VBUS); > > - if (val & USB_PRES_RISING) { > - twl4030_phy_resume(twl); > - twl4030charger_usb_en(1); > - twl->vbus = 1; > - } else { > - twl4030charger_usb_en(0); > - twl->vbus = 0; > - twl4030_phy_suspend(twl, 0); > + if (status == USB_LINK_NONE) > + twl4030_phy_suspend(twl, 0); > + else > + twl4030_phy_resume(twl); > } > - schedule_work(&twl->irq_work); > + sysfs_notify(&twl->dev->kobj, NULL, "vbus"); > > return IRQ_HANDLED; > } > @@ -608,11 +611,6 @@ static int twl4030_set_peripheral(struct > > twl->otg.state = OTG_STATE_B_IDLE; > > - twl4030_usb_set_bits(twl, USB_INT_EN_RISE, > - USB_INT_SESSVALID | USB_INT_VBUSVALID); > - twl4030_usb_set_bits(twl, USB_INT_EN_FALL, > - USB_INT_SESSVALID | USB_INT_VBUSVALID); > - > return 0; > } > > @@ -639,8 +637,7 @@ static int twl4030_set_host(struct otg_t > twl4030_usb_set_bits(twl, TWL4030_OTG_CTRL, > TWL4030_OTG_CTRL_DMPULLDOWN > | TWL4030_OTG_CTRL_DPPULLDOWN); > - twl4030_usb_set_bits(twl, USB_INT_EN_RISE, USB_INT_IDGND); > - twl4030_usb_set_bits(twl, USB_INT_EN_FALL, USB_INT_IDGND); > + > twl4030_usb_set_bits(twl, FUNC_CTRL, FUNC_CTRL_SUSPENDM); > twl4030_usb_set_bits(twl, TWL4030_OTG_CTRL, TWL4030_OTG_CTRL_DRVVBUS); > > @@ -651,8 +648,7 @@ static int __init twl4030_usb_probe(stru > { > struct twl4030_usb_data *pdata = pdev->dev.platform_data; > struct twl4030_usb *twl; > - int status; > - u8 vbus; > + int status; > > twl = kzalloc(sizeof *twl, GFP_KERNEL); > if (!twl) > @@ -663,26 +659,38 @@ static int __init twl4030_usb_probe(stru > return -EINVAL; > } > > - WARN_ON(twl4030_i2c_read_u8(TWL4030_MODULE_INT, > - &vbus, REG_PWR_EDR1) < 0); > - vbus &= USB_PRES_RISING; > - > twl->dev = &pdev->dev; > twl->irq = platform_get_irq(pdev, 0); > + twl->otg.dev = twl->dev; > + twl->otg.label = "twl4030"; > twl->otg.set_host = twl4030_set_host; > twl->otg.set_peripheral = twl4030_set_peripheral; > twl->otg.set_suspend = twl4030_set_suspend; > twl->usb_mode = pdata->usb_mode; > - twl->vbus = vbus ? 1 : 0; > + twl->asleep = 1; > > /* init spinlock for workqueue */ > spin_lock_init(&twl->lock); > > - /* init irq workqueue before request_irq */ > - INIT_WORK(&twl->irq_work, twl4030_usb_irq_work); > + twl4030_usb_ldo_init(twl); > + otg_set_transceiver(&twl->otg); > + > + platform_set_drvdata(pdev, twl); > + if (device_create_file(&pdev->dev, &dev_attr_vbus)) > + dev_warn(&pdev->dev, "could not create sysfs file\n"); > > + /* Our job is to use irqs and status from the power module > + * to keep the transceiver disabled when nothing's connected. > + * > + * FIXME we actually shouldn't start enabling it until the > + * USB controller drivers have said they're ready, by calling > + * set_host() and/or set_peripheral() ... OTG_capable boards > + * need both handles, otherwise just one suffices. > + */ > twl->irq_enabled = true; > - status = request_irq(twl->irq, twl4030_usb_irq, 0, "twl4030_usb", twl); > + status = request_irq(twl->irq, twl4030_usb_irq, > + IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, > + "twl4030_usb", twl); > if (status < 0) { > dev_dbg(&pdev->dev, "can't get IRQ %d, err %d\n", > twl->irq, status); > @@ -690,25 +698,17 @@ static int __init twl4030_usb_probe(stru > return status; > } > > - twl4030_usb_ldo_init(twl); > - twl4030_phy_power(twl, 1); > - twl4030_i2c_access(twl, 1); > - twl4030_usb_set_mode(twl, twl->usb_mode); > - > - twl->asleep = 0; > - > - if (twl->usb_mode == T2_USB_MODE_ULPI) { > - twl4030_i2c_access(twl, 0); > - twl4030_phy_suspend(twl, 0); > - } > + /* The IRQ handler just handles changes from the previous states > + * of the ID and VBUS pins ... in probe() we must initialize that > + * previous state. The easy way: fake an IRQ. > + * > + * REVISIT: a real IRQ might have happened already, if PREEMPT is > + * enabled. Else the IRQ may not yet be configured or enabled, > + * because of scheduling delays. > + */ > + twl4030_usb_irq(twl->irq, twl); > > - otg_set_transceiver(&twl->otg); > - platform_set_drvdata(pdev, twl); > dev_info(&pdev->dev, "Initialized TWL4030 USB module\n"); > - > - if (device_create_file(&pdev->dev, &dev_attr_vbus)) > - dev_warn(&pdev->dev, "could not create sysfs file\n"); > - > return 0; > } > > @@ -717,7 +717,6 @@ static int __exit twl4030_usb_remove(str > struct twl4030_usb *twl = platform_get_drvdata(pdev); > int val; > > - usb_irq_disable(twl); > free_irq(twl->irq, twl); > device_remove_file(twl->dev, &dev_attr_vbus); > > -- > To unsubscribe from this list: send the line "unsubscribe linux-omap" in > the body of a message to majordomo@xxxxxxxxxxxxxxx > More majordomo info at http://vger.kernel.org/majordomo-info.html -- To unsubscribe from this list: send the line "unsubscribe linux-omap" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html