diff --git a/drivers/usb/phy/phy-twl4030-usb.c b/drivers/usb/phy/phy-twl4030-usb.c index f4ec53a581035d9f7d2c1c163b76e5052e7e3fb7..61fe7e29c9c3a16bb648c2ec9657761bd4dd09a8 100644 --- a/drivers/usb/phy/phy-twl4030-usb.c +++ b/drivers/usb/phy/phy-twl4030-usb.c @@ -163,6 +163,8 @@ struct twl4030_usb { bool vbus_supplied; u8 asleep; bool irq_enabled; + + struct delayed_work id_workaround_work; }; /* internal define on top of container_of */ @@ -287,10 +289,6 @@ static enum omap_musb_vbus_id_status * are registered, and that both are active... */ - spin_lock_irq(&twl->lock); - twl->linkstat = linkstat; - spin_unlock_irq(&twl->lock); - return linkstat; } @@ -412,6 +410,16 @@ static void twl4030_phy_resume(struct twl4030_usb *twl) __twl4030_phy_resume(twl); twl->asleep = 0; dev_dbg(twl->dev, "%s\n", __func__); + + /* + * XXX When VBUS gets driven after musb goes to A mode, + * ID_PRES related interrupts no longer arrive, why? + * Register itself is updated fine though, so we must poll. + */ + if (twl->linkstat == OMAP_MUSB_ID_GROUND) { + cancel_delayed_work(&twl->id_workaround_work); + schedule_delayed_work(&twl->id_workaround_work, HZ); + } } static int twl4030_usb_ldo_init(struct twl4030_usb *twl) @@ -483,10 +491,18 @@ static irqreturn_t twl4030_usb_irq(int irq, void *_twl) { struct twl4030_usb *twl = _twl; enum omap_musb_vbus_id_status status; - enum omap_musb_vbus_id_status status_prev = twl->linkstat; + bool status_changed = false; status = twl4030_usb_linkstat(twl); - if (status > 0 && status != status_prev) { + + spin_lock_irq(&twl->lock); + if (status >= 0 && status != twl->linkstat) { + twl->linkstat = status; + status_changed = true; + } + spin_unlock_irq(&twl->lock); + + if (status_changed) { /* 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 @@ -498,13 +514,42 @@ static irqreturn_t twl4030_usb_irq(int irq, void *_twl) * USB_LINK_VBUS state. musb_hdrc won't care until it * starts to handle softconnect right. */ - omap_musb_mailbox(twl->linkstat); + omap_musb_mailbox(status); } sysfs_notify(&twl->dev->kobj, NULL, "vbus"); return IRQ_HANDLED; } +static void twl4030_id_workaround_work(struct work_struct *work) +{ + struct twl4030_usb *twl = container_of(work, struct twl4030_usb, + id_workaround_work.work); + enum omap_musb_vbus_id_status status; + bool status_changed = false; + + status = twl4030_usb_linkstat(twl); + + spin_lock_irq(&twl->lock); + if (status >= 0 && status != twl->linkstat) { + twl->linkstat = status; + status_changed = true; + } + spin_unlock_irq(&twl->lock); + + if (status_changed) { + dev_dbg(twl->dev, "handle missing status change to %d\n", + status); + omap_musb_mailbox(status); + } + + /* don't schedule during sleep - irq works right then */ + if (status == OMAP_MUSB_ID_GROUND && !twl->asleep) { + cancel_delayed_work(&twl->id_workaround_work); + schedule_delayed_work(&twl->id_workaround_work, HZ); + } +} + static int twl4030_usb_phy_init(struct usb_phy *phy) { struct twl4030_usb *twl = phy_to_twl(phy); @@ -518,6 +563,8 @@ static int twl4030_usb_phy_init(struct usb_phy *phy) twl->asleep = 1; status = twl4030_usb_linkstat(twl); + twl->linkstat = status; + if (status == OMAP_MUSB_ID_GROUND || status == OMAP_MUSB_VBUS_VALID) omap_musb_mailbox(twl->linkstat); @@ -608,6 +655,8 @@ static int twl4030_usb_probe(struct platform_device *pdev) /* init spinlock for workqueue */ spin_lock_init(&twl->lock); + INIT_DELAYED_WORK(&twl->id_workaround_work, twl4030_id_workaround_work); + err = twl4030_usb_ldo_init(twl); if (err) { dev_err(&pdev->dev, "ldo init failed\n"); @@ -646,6 +695,7 @@ static int __exit twl4030_usb_remove(struct platform_device *pdev) struct twl4030_usb *twl = platform_get_drvdata(pdev); int val; + cancel_delayed_work(&twl->id_workaround_work); device_remove_file(twl->dev, &dev_attr_vbus); /* set transceiver mode to power on defaults */