aboutsummaryrefslogtreecommitdiff
path: root/drivers/usb
diff options
context:
space:
mode:
authorSakethram Bommisetti <sakethram.bommisetti@stericsson.com>2011-01-25 14:53:59 +0530
committerJonas ABERG <jonas.aberg@stericsson.com>2011-02-04 12:46:58 +0100
commit28327e018d157f3445d34e76a9f55ed010ef9327 (patch)
tree0bdaee168fcf56aec81f5edb638ac8a4799466f6 /drivers/usb
parent0be4ac3a0d1caf8a17f738d13749082c8e0711ba (diff)
USB: core: OTG Supplement Revision 2.0 updates
OTG supplement revision 2.0 spec introduces Attach Detection Protocol(ADP) for detecting peripheral connection without applying power on VBUS. ADP is optional and is included in the OTG descriptor along with SRP and HNP. HNP polling is introduced for peripheral to notify its wish to become host. Host polls (GET_STATUS on DEVICE) peripheral for host_request and suspend the bus when peripheral returns host_request TRUE. The spec insists the polling frequency to be in 1-2 sec range and bus should be suspended with in 2 sec from host_request is set. a_alt_hnp_support feature is obsolete and a_hnp_support feature is limited to only legacy OTG B-device. The newly introduced bcdOTG field in the OTG descriptor is used for identifying the 2.0 compliant B-device. Signed-off-by: Pavankumar Kondeti <pkondeti@...> Change-Id: I437110def5a1c40a857bae9ffb739ee27f1e3fc9 Reviewed-on: http://gerrit.lud.stericsson.com/gerrit/14266 Tested-by: Sakethram BOMMISETTI <sakethram.bommisetti@stericsson.com> Reviewed-by: Praveena NADAHALLY <praveen.nadahally@stericsson.com> Reviewed-by: QATOOLS
Diffstat (limited to 'drivers/usb')
-rw-r--r--drivers/usb/core/driver.c56
-rw-r--r--drivers/usb/core/hcd.c3
-rw-r--r--drivers/usb/core/hub.c98
-rw-r--r--drivers/usb/core/usb.h5
4 files changed, 140 insertions, 22 deletions
diff --git a/drivers/usb/core/driver.c b/drivers/usb/core/driver.c
index a6bd53ace03..ddb7b47d3c9 100644
--- a/drivers/usb/core/driver.c
+++ b/drivers/usb/core/driver.c
@@ -1208,6 +1208,19 @@ static int usb_suspend_both(struct usb_device *udev, pm_message_t msg)
* and flush any outstanding URBs.
*/
} else {
+#ifdef CONFIG_USB_OTG
+ /* According to OTG supplement Rev 2.0 section 6.3
+ * Unless an A-device enables b_hnp_enable before entering
+ * suspend it shall also continue polling while the bus is
+ * suspended.
+ *
+ * We don't have to perform HNP polling, as we are going to
+ * enable b_hnp_enable before suspending.
+ */
+ if (udev->bus->hnp_support &&
+ udev->portnum == udev->bus->otg_port)
+ cancel_delayed_work(&udev->bus->hnp_polling);
+#endif
udev->can_submit = 0;
for (i = 0; i < 16; ++i) {
usb_hcd_flush_endpoint(udev, udev->ep_out[i]);
@@ -1270,6 +1283,49 @@ static int usb_resume_both(struct usb_device *udev, pm_message_t msg)
return status;
}
+#ifdef CONFIG_USB_OTG
+void usb_hnp_polling_work(struct work_struct *work)
+{
+ int ret;
+ struct usb_bus *bus =
+ container_of(work, struct usb_bus, hnp_polling.work);
+ struct usb_device *udev =
+ bus->root_hub->children[bus->otg_port - 1];
+ u8 *status = kmalloc(sizeof(*status), GFP_KERNEL);
+
+ if (!status) {
+ schedule_delayed_work(&bus->hnp_polling,
+ msecs_to_jiffies(THOST_REQ_POLL));
+ return;
+ }
+
+ ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0),
+ USB_REQ_GET_STATUS, USB_DIR_IN | USB_RECIP_DEVICE,
+ 0, OTG_STATUS_SELECTOR, status, sizeof(*status),
+ USB_CTRL_GET_TIMEOUT);
+ if (ret < 0) {
+ /* Peripheral may not be supporting HNP polling */
+ dev_vdbg(&udev->dev, "HNP polling failed."
+ "status %d\n", ret);
+ goto out;
+ }
+
+ /* Spec says host must suspend the bus with in 2 sec. */
+ if (*status & (1 << HOST_REQUEST_FLAG)) {
+ do_unbind_rebind(udev, DO_UNBIND);
+ udev->do_remote_wakeup = 0;
+ ret = usb_suspend_both(udev, PMSG_USER_SUSPEND);
+ if (ret)
+ dev_info(&udev->dev, "suspend failed\n");
+ } else {
+ schedule_delayed_work(&bus->hnp_polling,
+ msecs_to_jiffies(THOST_REQ_POLL));
+ }
+out:
+ kfree(status);
+}
+#endif
+
static void choose_wakeup(struct usb_device *udev, pm_message_t msg)
{
int w;
diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c
index 12742f152f4..9524c8c6146 100644
--- a/drivers/usb/core/hcd.c
+++ b/drivers/usb/core/hcd.c
@@ -870,6 +870,9 @@ static void usb_bus_init (struct usb_bus *bus)
bus->bandwidth_isoc_reqs = 0;
INIT_LIST_HEAD (&bus->bus_list);
+#ifdef CONFIG_USB_OTG
+ INIT_DELAYED_WORK(&bus->hnp_polling, usb_hnp_polling_work);
+#endif
}
/*-------------------------------------------------------------------------*/
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
index 906abe7a071..3e248eb72a3 100644
--- a/drivers/usb/core/hub.c
+++ b/drivers/usb/core/hub.c
@@ -593,7 +593,7 @@ static int hub_hub_status(struct usb_hub *hub,
"%s failed (err = %d)\n", __func__, ret);
else {
*status = le16_to_cpu(hub->status->hub.wHubStatus);
- *change = le16_to_cpu(hub->status->hub.wHubChange);
+ *change = le16_to_cpu(hub->status->hub.wHubChange);
ret = 0;
}
mutex_unlock(&hub->status_mutex);
@@ -1580,6 +1580,12 @@ void usb_disconnect(struct usb_device **pdev)
usb_set_device_state(udev, USB_STATE_NOTATTACHED);
dev_info (&udev->dev, "USB disconnect, address %d\n", udev->devnum);
+#ifdef CONFIG_USB_OTG
+ if (udev->bus->hnp_support && udev->portnum == udev->bus->otg_port) {
+ cancel_delayed_work_sync(&udev->bus->hnp_polling);
+ udev->bus->hnp_support = 0;
+ }
+#endif
usb_lock_device(udev);
/* Free up all the children before we remove this device */
@@ -1684,15 +1690,33 @@ static int usb_enumerate_device_otg(struct usb_device *udev)
(port1 == bus->otg_port)
? "" : "non-");
- /* enable HNP before suspend, it's simpler */
- if (port1 == bus->otg_port)
- bus->b_hnp_enable = 1;
+
+ /* a_alt_hnp_support is obsoleted */
+ if (port1 != bus->otg_port)
+ goto fail;
+
+ bus->hnp_support = 1;
+
+ /* a_hnp_support is not required for devices
+ * compliant to revision 2.0 or subsequent
+ * versions.
+ */
+ if (desc->bLength == sizeof(*desc) &&
+ le16_to_cpu(desc->bcdOTG) >= 0x0200)
+ goto out;
+
+ /* Legacy B-device i.e compliant to spec
+ * revision 1.3 expect A-device to set
+ * a_hnp_support or b_hnp_enable before
+ * selecting configuration. b_hnp_enable
+ * is set before suspending the port.
+ */
+
err = usb_control_msg(udev,
usb_sndctrlpipe(udev, 0),
USB_REQ_SET_FEATURE, 0,
- bus->b_hnp_enable
- ? USB_DEVICE_B_HNP_ENABLE
- : USB_DEVICE_A_ALT_HNP_SUPPORT,
+ USB_DEVICE_A_HNP_SUPPORT,
+
0, NULL, 0, USB_CTRL_SET_TIMEOUT);
if (err < 0) {
/* OTG MESSAGE: report errors here,
@@ -1701,24 +1725,35 @@ static int usb_enumerate_device_otg(struct usb_device *udev)
dev_info(&udev->dev,
"can't set HNP mode: %d\n",
err);
- bus->b_hnp_enable = 0;
+ bus->hnp_support = 0;
+
}
}
}
}
-
+out:
if (!is_targeted(udev)) {
/* Maybe it can talk to us, though we can't talk to it.
* (Includes HNP test device.)
*/
- if (udev->bus->b_hnp_enable || udev->bus->is_b_host) {
+ if (udev->bus->hnp_support) {
err = usb_port_suspend(udev, PMSG_SUSPEND);
if (err < 0)
dev_dbg(&udev->dev, "HNP fail, %d\n", err);
- }
+
err = -ENOTSUPP;
- goto fail;
+
+ } else if (udev->bus->hnp_support &&
+ udev->portnum == udev->bus->otg_port) {
+ /* HNP polling is introduced in OTG supplement Rev 2.0
+ * and older devices does not support. Work is not
+ * re-armed if device returns STALL. B-Host also
+ * performs HNP polling.
+ */
+ schedule_delayed_work(&udev->bus->hnp_polling,
+ msecs_to_jiffies(THOST_REQ_POLL));
+ }
}
fail:
#endif
@@ -2199,6 +2234,20 @@ int usb_port_suspend(struct usb_device *udev, pm_message_t msg)
return status;
}
}
+#ifdef CONFIG_USB_OTG
+ if (!udev->bus->is_b_host && udev->bus->hnp_support &&
+ udev->portnum == udev->bus->otg_port) {
+ status = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
+ USB_REQ_SET_FEATURE, 0,
+ USB_DEVICE_B_HNP_ENABLE,
+ 0, NULL, 0, USB_CTRL_SET_TIMEOUT);
+ if (status < 0)
+ dev_dbg(&udev->dev, "can't enable HNP on port %d, "
+ "status %d\n", port1, status);
+ else
+ udev->bus->b_hnp_enable = 1;
+ }
+#endif
/* see 7.1.7.6 */
status = set_port_feature(hub->hdev, port1, USB_PORT_FEAT_SUSPEND);
@@ -2342,6 +2391,11 @@ int usb_port_resume(struct usb_device *udev, pm_message_t msg)
int status;
u16 portchange, portstatus;
+#ifdef CONFIG_USB_OTG
+ if (!udev->bus->is_b_host && udev->bus->hnp_support &&
+ udev->portnum == udev->bus->otg_port)
+ udev->bus->b_hnp_enable = 0;
+#endif
/* Skip the initial Clear-Suspend step for a remote wakeup */
status = hub_port_status(hub, port1, &portstatus, &portchange);
if (status == 0 && !(portstatus & USB_PORT_STAT_SUSPEND))
@@ -2518,7 +2572,7 @@ EXPORT_SYMBOL_GPL(usb_root_hub_lost_power);
* Between connect detection and reset signaling there must be a delay
* of 100ms at least for debounce and power-settling. The corresponding
* timer shall restart whenever the downstream port detects a disconnect.
- *
+ *
* Apparently there are some bluetooth and irda-dongles and a number of
* low-speed devices for which this debounce period may last over a second.
* Not covered by the spec - but easy to deal with.
@@ -2696,7 +2750,7 @@ hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1,
default:
goto fail;
}
-
+
type = "";
switch (udev->speed) {
case USB_SPEED_LOW: speed = "low"; break;
@@ -2726,7 +2780,7 @@ hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1,
udev->tt = &hub->tt;
udev->ttport = port1;
}
-
+
/* Why interleave GET_DESCRIPTOR and SET_ADDRESS this way?
* Because device hardware and firmware is sometimes buggy in
* this area, and this is how Linux has done it for ages.
@@ -2871,7 +2925,7 @@ hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1,
udev->ep0.desc.wMaxPacketSize = cpu_to_le16(i);
usb_ep0_reinit(udev);
}
-
+
retval = usb_get_device_descriptor(udev, USB_DT_DEVICE_SIZE);
if (retval < (signed)sizeof(udev->descriptor)) {
dev_err(&udev->dev, "device descriptor read/all, error %d\n",
@@ -3147,7 +3201,7 @@ static void hub_port_connect_change(struct usb_hub *hub, int port1,
goto loop_disable;
}
}
-
+
/* check for devices running slower than they could */
if (le16_to_cpu(udev->descriptor.bcdUSB) >= 0x0200
&& udev->speed == USB_SPEED_FULL
@@ -3205,7 +3259,7 @@ loop:
!(hcd->driver->port_handed_over)(hcd, port1))
dev_err(hub_dev, "unable to enumerate USB device on port %d\n",
port1);
-
+
done:
hub_port_disable(hub, port1, 1);
if (hcd->driver->relinquish_port && !hub->hdev->parent)
@@ -3331,7 +3385,7 @@ static void hub_events(void)
* EM interference sometimes causes badly
* shielded USB devices to be shutdown by
* the hub, this hack enables them again.
- * Works at least with mouse driver.
+ * Works at least with mouse driver.
*/
if (!(portstatus & USB_PORT_STAT_ENABLE)
&& !connect_change
@@ -3369,7 +3423,7 @@ static void hub_events(void)
"resume on port %d, status %d\n",
i, ret);
}
-
+
if (portchange & USB_PORT_STAT_C_OVERCURRENT) {
dev_err (hub_dev,
"over-current change on port %d\n",
@@ -3648,7 +3702,7 @@ static int usb_reset_and_verify_device(struct usb_device *udev)
if (ret < 0)
goto re_enumerate;
-
+
/* Device might have changed firmware (DFU or similar) */
if (descriptors_changed(udev, &descriptor)) {
dev_info(&udev->dev, "device firmware changed\n");
@@ -3721,7 +3775,7 @@ static int usb_reset_and_verify_device(struct usb_device *udev)
done:
return 0;
-
+
re_enumerate:
hub_port_logical_disconnect(parent_hub, port1);
return -ENODEV;
diff --git a/drivers/usb/core/usb.h b/drivers/usb/core/usb.h
index cd882203ad3..56423909dff 100644
--- a/drivers/usb/core/usb.h
+++ b/drivers/usb/core/usb.h
@@ -72,6 +72,11 @@ static inline int usb_port_resume(struct usb_device *udev, pm_message_t msg)
#endif
+#ifdef CONFIG_USB_OTG
+extern void usb_hnp_polling_work(struct work_struct *work);
+#endif
+
+
#ifdef CONFIG_USB_SUSPEND
extern void usb_autosuspend_device(struct usb_device *udev);