bool vbus_supplied;
u8 asleep;
bool irq_enabled;
+
+ struct delayed_work id_workaround_work;
};
/* internal define on top of container_of */
/*-------------------------------------------------------------------------*/
+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;
* 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;
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)
}
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);
* 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");
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");
}
/* 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");
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);
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");
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 */