aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/usb/phy/phy-twl4030-usb.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/phy/phy-twl4030-usb.c')
-rw-r--r--drivers/usb/phy/phy-twl4030-usb.c84
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 */