X-Git-Url: https://git.openpandora.org/cgi-bin/gitweb.cgi?a=blobdiff_plain;f=drivers%2Fusb%2Fotg%2Ftwl4030-usb.c;h=296e5537c76e085d5b752a62d921751718738374;hb=a592f46075d79ff7348e624e7d871dca730e70f2;hp=14f66c35862938adc5817c123233521c9ca1f925;hpb=8c285645ab3b05942124020b5f0b89d3b539823a;p=pandora-kernel.git diff --git a/drivers/usb/otg/twl4030-usb.c b/drivers/usb/otg/twl4030-usb.c index 14f66c358629..296e5537c76e 100644 --- a/drivers/usb/otg/twl4030-usb.c +++ b/drivers/usb/otg/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 */ @@ -246,10 +248,30 @@ twl4030_usb_clear_bits(struct twl4030_usb *twl, u8 reg, u8 bits) /*-------------------------------------------------------------------------*/ +static bool twl4030_is_driving_vbus(struct twl4030_usb *twl) +{ + int ret; + + ret = twl4030_usb_read(twl, PHY_CLK_CTRL_STS); + if (ret < 0 || !(ret & PHY_DPLL_CLK)) + /* + * if clocks are off, registers are not updated, + * but we can assume we don't drive VBUS in this case + */ + return false; + + ret = twl4030_usb_read(twl, ULPI_OTG_CTRL); + if (ret < 0) + return false; + + return (ret & (ULPI_OTG_DRVVBUS | ULPI_OTG_CHRGVBUS)) ? true : false; +} + static enum usb_xceiv_events twl4030_usb_linkstat(struct twl4030_usb *twl) { int status; int linkstat = USB_EVENT_NONE; + bool driving_vbus = false; twl->vbus_supplied = false; @@ -263,23 +285,31 @@ static enum usb_xceiv_events twl4030_usb_linkstat(struct twl4030_usb *twl) * signal is active, the OTG module is activated, and * its interrupt may be raised (may wake the system). */ + msleep(50); status = twl4030_readb(twl, TWL4030_MODULE_PM_MASTER, STS_HW_CONDITIONS); if (status < 0) dev_err(twl->dev, "USB link status err %d\n", status); else if (status & (BIT(7) | BIT(2))) { - if (status & (BIT(7))) - twl->vbus_supplied = true; + if (status & BIT(7)) { + driving_vbus = twl4030_is_driving_vbus(twl); + if (driving_vbus) + status &= ~BIT(7); + } if (status & BIT(2)) linkstat = USB_EVENT_ID; - else + else if (status & BIT(7)) { linkstat = USB_EVENT_VBUS; - } else - linkstat = USB_EVENT_NONE; + twl->vbus_supplied = true; + } + } + + dev_dbg(twl->dev, "HW_CONDITIONS 0x%02x; link %d, driving_vbus %d\n", + status, linkstat, driving_vbus); - dev_dbg(twl->dev, "HW_CONDITIONS 0x%02x/%d; link %d\n", - status, status, linkstat); + if (twl->otg.last_event == linkstat) + return linkstat; twl->otg.last_event = linkstat; @@ -419,7 +449,16 @@ static void twl4030_phy_resume(struct twl4030_usb *twl) return; __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->otg.last_event == USB_EVENT_ID) { + 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) @@ -497,9 +536,47 @@ static ssize_t twl4030_usb_vbus_show(struct device *dev, } static DEVICE_ATTR(vbus, 0444, twl4030_usb_vbus_show, NULL); +static ssize_t twl4030_usb_id_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int ret; + int n = 0; + struct twl4030_usb *twl = dev_get_drvdata(dev); + twl4030_i2c_access(twl, 1); + ret = twl4030_usb_read(twl, ULPI_OTG_CTRL); + if ((ret < 0) || (!(ret & ULPI_OTG_ID_PULLUP))) { + /* + * enable ID pullup so that the id pin state can be measured, + * seems to be disabled sometimes for some reasons + */ + dev_dbg(dev, "ULPI_OTG_ID_PULLUP not set (%x)\n", ret); + twl4030_usb_set_bits(twl, ULPI_OTG_CTRL, ULPI_OTG_ID_PULLUP); + mdelay(100); + } + ret = twl4030_usb_read(twl, ID_STATUS); + twl4030_i2c_access(twl, 0); + if (ret < 0) + return ret; + if (ret & ID_RES_FLOAT) + n = scnprintf(buf, PAGE_SIZE, "%s\n", "floating"); + else if (ret & ID_RES_440K) + n = scnprintf(buf, PAGE_SIZE, "%s\n", "440k"); + else if (ret & ID_RES_200K) + n = scnprintf(buf, PAGE_SIZE, "%s\n", "200k"); + else if (ret & ID_RES_102K) + n = scnprintf(buf, PAGE_SIZE, "%s\n", "102k"); + else if (ret & ID_RES_GND) + n = scnprintf(buf, PAGE_SIZE, "%s\n", "GND"); + else + n = scnprintf(buf, PAGE_SIZE, "unknown: id=0x%x\n", ret); + return n; +} +static DEVICE_ATTR(id, 0444, twl4030_usb_id_show, NULL); + static irqreturn_t twl4030_usb_irq(int irq, void *_twl) { struct twl4030_usb *twl = _twl; + int status_old = twl->otg.last_event; int status; status = twl4030_usb_linkstat(twl); @@ -515,12 +592,8 @@ 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. */ - if (status == USB_EVENT_NONE) - twl4030_phy_suspend(twl, 0); - else - twl4030_phy_resume(twl); - - atomic_notifier_call_chain(&twl->otg.notifier, status, + if (status != status_old) + atomic_notifier_call_chain(&twl->otg.notifier, status, twl->otg.gadget); } sysfs_notify(&twl->dev->kobj, NULL, "vbus"); @@ -528,23 +601,44 @@ static irqreturn_t twl4030_usb_irq(int irq, void *_twl) return IRQ_HANDLED; } -static void twl4030_usb_phy_init(struct twl4030_usb *twl) +static void twl4030_id_workaround_work(struct work_struct *work) { + struct twl4030_usb *twl = container_of(work, struct twl4030_usb, + id_workaround_work.work); + int status_old = twl->otg.last_event; int status; status = twl4030_usb_linkstat(twl); - if (status >= 0) { - if (status == USB_EVENT_NONE) { - __twl4030_phy_power(twl, 0); - twl->asleep = 1; - } else { - __twl4030_phy_resume(twl); - twl->asleep = 0; - } + if (status != status_old) { + dev_dbg(twl->dev, "handle missing status change: %d->%d\n", + status_old, status); + twl->otg.last_event = status_old; + twl4030_usb_irq(0, twl); + } - atomic_notifier_call_chain(&twl->otg.notifier, status, - twl->otg.gadget); + /* don't schedule during sleep - irq works right then */ + if (status == USB_EVENT_ID && !twl->asleep) { + cancel_delayed_work(&twl->id_workaround_work); + schedule_delayed_work(&twl->id_workaround_work, HZ); } +} + +static void twl4030_usb_phy_init(struct twl4030_usb *twl) +{ + int status; + + /* + * Start in sleep state, we'll get otg.set_suspend(false) call + * and power up when musb runtime_pm enable kicks in. + */ + __twl4030_phy_power(twl, 0); + twl->asleep = 1; + + status = twl4030_usb_linkstat(twl); + if (status >= 0 && status != USB_EVENT_NONE) + atomic_notifier_call_chain(&twl->otg.notifier, status, + twl->otg.gadget); + sysfs_notify(&twl->dev->kobj, NULL, "vbus"); } @@ -620,6 +714,8 @@ static int __devinit 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"); @@ -631,6 +727,8 @@ static int __devinit twl4030_usb_probe(struct platform_device *pdev) platform_set_drvdata(pdev, twl); if (device_create_file(&pdev->dev, &dev_attr_vbus)) dev_warn(&pdev->dev, "could not create sysfs file\n"); + if (device_create_file(&pdev->dev, &dev_attr_id)) + dev_warn(&pdev->dev, "could not create sysfs file\n"); ATOMIC_INIT_NOTIFIER_HEAD(&twl->otg.notifier); @@ -653,9 +751,6 @@ static int __devinit twl4030_usb_probe(struct platform_device *pdev) return status; } - /* Power down phy or make it work according to - * current link state. - */ twl4030_usb_phy_init(twl); dev_info(&pdev->dev, "Initialized TWL4030 USB module\n"); @@ -667,7 +762,9 @@ 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); free_irq(twl->irq, twl); + device_remove_file(twl->dev, &dev_attr_id); device_remove_file(twl->dev, &dev_attr_vbus); /* set transceiver mode to power on defaults */