diff options
Diffstat (limited to 'drivers/usb/phy/phy-twl4030-usb.c')
-rw-r--r-- | drivers/usb/phy/phy-twl4030-usb.c | 84 |
1 files changed, 82 insertions, 2 deletions
diff --git a/drivers/usb/phy/phy-twl4030-usb.c b/drivers/usb/phy/phy-twl4030-usb.c index 8f78d2d..ff61815 100644 --- a/drivers/usb/phy/phy-twl4030-usb.c +++ b/drivers/usb/phy/phy-twl4030-usb.c @@ -38,6 +38,7 @@ #include <linux/i2c/twl.h> #include <linux/regulator/consumer.h> #include <linux/err.h> +#include <linux/notifier.h> #include <linux/slab.h> /* Register defines */ @@ -292,8 +293,14 @@ static enum omap_musb_vbus_id_status if (status & BIT(7)) { if (twl4030_is_driving_vbus(twl)) status &= ~BIT(7); - else + else { twl->vbus_supplied = true; + /* We have VBUS so ignore ID_PRES - it + * is only meaningful as an indicator + * of an A plug when there is no VBUS. + */ + status &= ~BIT(2); + } } if (status & BIT(2)) @@ -414,11 +421,40 @@ static void twl4030_phy_power(struct twl4030_usb *twl, int on) (PHY_CLK_CTRL_CLOCKGATING_EN | PHY_CLK_CTRL_CLK32K_EN)); } else { - __twl4030_phy_power(twl, 0); regulator_disable(twl->usb1v5); regulator_disable(twl->usb1v8); regulator_disable(twl->usb3v1); + if (!regulator_is_enabled(twl->usb3v1)) + /* no-one else is requesting this + * so it is OK to power-down the + * phy. Sometimes a charger might + * hold the regulator active. + */ + __twl4030_phy_power(twl, 0); + + } +} + +static void do_notify(struct twl4030_usb *twl, + enum omap_musb_vbus_id_status status) +{ + enum usb_phy_events event; + + switch (status) { + case OMAP_MUSB_UNKNOWN: + event = USB_EVENT_NONE; break; + case OMAP_MUSB_ID_GROUND: + event = USB_EVENT_ID; break; + case OMAP_MUSB_ID_FLOAT: + event = USB_EVENT_NONE; break; + case OMAP_MUSB_VBUS_VALID: + event = USB_EVENT_VBUS; break; + case OMAP_MUSB_VBUS_OFF: + event = USB_EVENT_NONE; break; } + + atomic_notifier_call_chain(&twl->phy.notifier, event, + twl->phy.otg->gadget); } static void twl4030_phy_suspend(struct twl4030_usb *twl, int controller_off) @@ -524,6 +560,43 @@ 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; @@ -551,6 +624,7 @@ 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. */ + do_notify(twl, status); omap_musb_mailbox(status); } sysfs_notify(&twl->dev->kobj, NULL, "vbus"); @@ -577,6 +651,7 @@ static void twl4030_id_workaround_work(struct work_struct *work) if (status_changed) { dev_dbg(twl->dev, "handle missing status change to %d\n", status); + do_notify(twl, status); omap_musb_mailbox(status); } @@ -704,6 +779,10 @@ static int 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->phy.notifier); /* Our job is to use irqs and status from the power module * to keep the transceiver disabled when nothing's connected. @@ -733,6 +812,7 @@ static int twl4030_usb_remove(struct platform_device *pdev) int val; cancel_delayed_work(&twl->id_workaround_work); + device_remove_file(twl->dev, &dev_attr_id); device_remove_file(twl->dev, &dev_attr_vbus); /* set transceiver mode to power on defaults */ |