|
@@ -218,6 +218,156 @@ ohci_at91_start (struct usb_hcd *hcd)
|
|
return 0;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+static void ohci_at91_usb_set_power(struct at91_usbh_data *pdata, int port, int enable)
|
|
|
|
+{
|
|
|
|
+ if (port < 0 || port >= 2)
|
|
|
|
+ return;
|
|
|
|
+
|
|
|
|
+ gpio_set_value(pdata->vbus_pin[port], !pdata->vbus_pin_inverted ^ enable);
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int ohci_at91_usb_get_power(struct at91_usbh_data *pdata, int port)
|
|
|
|
+{
|
|
|
|
+ if (port < 0 || port >= 2)
|
|
|
|
+ return -EINVAL;
|
|
|
|
+
|
|
|
|
+ return gpio_get_value(pdata->vbus_pin[port]) ^ !pdata->vbus_pin_inverted;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/*
|
|
|
|
+ * Update the status data from the hub with the over-current indicator change.
|
|
|
|
+ */
|
|
|
|
+static int ohci_at91_hub_status_data(struct usb_hcd *hcd, char *buf)
|
|
|
|
+{
|
|
|
|
+ struct at91_usbh_data *pdata = hcd->self.controller->platform_data;
|
|
|
|
+ int length = ohci_hub_status_data(hcd, buf);
|
|
|
|
+ int port;
|
|
|
|
+
|
|
|
|
+ for (port = 0; port < ARRAY_SIZE(pdata->overcurrent_pin); port++) {
|
|
|
|
+ if (pdata->overcurrent_changed[port]) {
|
|
|
|
+ if (! length)
|
|
|
|
+ length = 1;
|
|
|
|
+ buf[0] |= 1 << (port + 1);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return length;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/*
|
|
|
|
+ * Look at the control requests to the root hub and see if we need to override.
|
|
|
|
+ */
|
|
|
|
+static int ohci_at91_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
|
|
|
|
+ u16 wIndex, char *buf, u16 wLength)
|
|
|
|
+{
|
|
|
|
+ struct at91_usbh_data *pdata = hcd->self.controller->platform_data;
|
|
|
|
+ struct usb_hub_descriptor *desc;
|
|
|
|
+ int ret = -EINVAL;
|
|
|
|
+ u32 *data = (u32 *)buf;
|
|
|
|
+
|
|
|
|
+ dev_dbg(hcd->self.controller,
|
|
|
|
+ "ohci_at91_hub_control(%p,0x%04x,0x%04x,0x%04x,%p,%04x)\n",
|
|
|
|
+ hcd, typeReq, wValue, wIndex, buf, wLength);
|
|
|
|
+
|
|
|
|
+ switch (typeReq) {
|
|
|
|
+ case SetPortFeature:
|
|
|
|
+ if (wValue == USB_PORT_FEAT_POWER) {
|
|
|
|
+ dev_dbg(hcd->self.controller, "SetPortFeat: POWER\n");
|
|
|
|
+ ohci_at91_usb_set_power(pdata, wIndex - 1, 1);
|
|
|
|
+ goto out;
|
|
|
|
+ }
|
|
|
|
+ break;
|
|
|
|
+
|
|
|
|
+ case ClearPortFeature:
|
|
|
|
+ switch (wValue) {
|
|
|
|
+ case USB_PORT_FEAT_C_OVER_CURRENT:
|
|
|
|
+ dev_dbg(hcd->self.controller,
|
|
|
|
+ "ClearPortFeature: C_OVER_CURRENT\n");
|
|
|
|
+
|
|
|
|
+ if (wIndex == 1 || wIndex == 2) {
|
|
|
|
+ pdata->overcurrent_changed[wIndex-1] = 0;
|
|
|
|
+ pdata->overcurrent_status[wIndex-1] = 0;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ goto out;
|
|
|
|
+
|
|
|
|
+ case USB_PORT_FEAT_OVER_CURRENT:
|
|
|
|
+ dev_dbg(hcd->self.controller,
|
|
|
|
+ "ClearPortFeature: OVER_CURRENT\n");
|
|
|
|
+
|
|
|
|
+ if (wIndex == 1 || wIndex == 2) {
|
|
|
|
+ pdata->overcurrent_status[wIndex-1] = 0;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ goto out;
|
|
|
|
+
|
|
|
|
+ case USB_PORT_FEAT_POWER:
|
|
|
|
+ dev_dbg(hcd->self.controller,
|
|
|
|
+ "ClearPortFeature: POWER\n");
|
|
|
|
+
|
|
|
|
+ if (wIndex == 1 || wIndex == 2) {
|
|
|
|
+ ohci_at91_usb_set_power(pdata, wIndex - 1, 0);
|
|
|
|
+ return 0;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ ret = ohci_hub_control(hcd, typeReq, wValue, wIndex, buf, wLength);
|
|
|
|
+ if (ret)
|
|
|
|
+ goto out;
|
|
|
|
+
|
|
|
|
+ switch (typeReq) {
|
|
|
|
+ case GetHubDescriptor:
|
|
|
|
+
|
|
|
|
+ /* update the hub's descriptor */
|
|
|
|
+
|
|
|
|
+ desc = (struct usb_hub_descriptor *)buf;
|
|
|
|
+
|
|
|
|
+ dev_dbg(hcd->self.controller, "wHubCharacteristics 0x%04x\n",
|
|
|
|
+ desc->wHubCharacteristics);
|
|
|
|
+
|
|
|
|
+ /* remove the old configurations for power-switching, and
|
|
|
|
+ * over-current protection, and insert our new configuration
|
|
|
|
+ */
|
|
|
|
+
|
|
|
|
+ desc->wHubCharacteristics &= ~cpu_to_le16(HUB_CHAR_LPSM);
|
|
|
|
+ desc->wHubCharacteristics |= cpu_to_le16(0x0001);
|
|
|
|
+
|
|
|
|
+ if (pdata->overcurrent_supported) {
|
|
|
|
+ desc->wHubCharacteristics &= ~cpu_to_le16(HUB_CHAR_OCPM);
|
|
|
|
+ desc->wHubCharacteristics |= cpu_to_le16(0x0008|0x0001);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ dev_dbg(hcd->self.controller, "wHubCharacteristics after 0x%04x\n",
|
|
|
|
+ desc->wHubCharacteristics);
|
|
|
|
+
|
|
|
|
+ return ret;
|
|
|
|
+
|
|
|
|
+ case GetPortStatus:
|
|
|
|
+ /* check port status */
|
|
|
|
+
|
|
|
|
+ dev_dbg(hcd->self.controller, "GetPortStatus(%d)\n", wIndex);
|
|
|
|
+
|
|
|
|
+ if (wIndex == 1 || wIndex == 2) {
|
|
|
|
+ if (! ohci_at91_usb_get_power(pdata, wIndex-1)) {
|
|
|
|
+ *data &= ~cpu_to_le32(RH_PS_PPS);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (pdata->overcurrent_changed[wIndex-1]) {
|
|
|
|
+ *data |= cpu_to_le32(RH_PS_OCIC);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (pdata->overcurrent_status[wIndex-1]) {
|
|
|
|
+ *data |= cpu_to_le32(RH_PS_POCI);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ out:
|
|
|
|
+ return ret;
|
|
|
|
+}
|
|
|
|
+
|
|
/*-------------------------------------------------------------------------*/
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
|
|
static const struct hc_driver ohci_at91_hc_driver = {
|
|
static const struct hc_driver ohci_at91_hc_driver = {
|
|
@@ -253,8 +403,8 @@ static const struct hc_driver ohci_at91_hc_driver = {
|
|
/*
|
|
/*
|
|
* root hub support
|
|
* root hub support
|
|
*/
|
|
*/
|
|
- .hub_status_data = ohci_hub_status_data,
|
|
|
|
- .hub_control = ohci_hub_control,
|
|
|
|
|
|
+ .hub_status_data = ohci_at91_hub_status_data,
|
|
|
|
+ .hub_control = ohci_at91_hub_control,
|
|
#ifdef CONFIG_PM
|
|
#ifdef CONFIG_PM
|
|
.bus_suspend = ohci_bus_suspend,
|
|
.bus_suspend = ohci_bus_suspend,
|
|
.bus_resume = ohci_bus_resume,
|
|
.bus_resume = ohci_bus_resume,
|
|
@@ -264,22 +414,71 @@ static const struct hc_driver ohci_at91_hc_driver = {
|
|
|
|
|
|
/*-------------------------------------------------------------------------*/
|
|
/*-------------------------------------------------------------------------*/
|
|
|
|
|
|
|
|
+static irqreturn_t ohci_hcd_at91_overcurrent_irq(int irq, void *data)
|
|
|
|
+{
|
|
|
|
+ struct platform_device *pdev = data;
|
|
|
|
+ struct at91_usbh_data *pdata = pdev->dev.platform_data;
|
|
|
|
+ int val, gpio, port;
|
|
|
|
+
|
|
|
|
+ /* From the GPIO notifying the over-current situation, find
|
|
|
|
+ * out the corresponding port */
|
|
|
|
+ gpio = irq_to_gpio(irq);
|
|
|
|
+ for (port = 0; port < ARRAY_SIZE(pdata->overcurrent_pin); port++) {
|
|
|
|
+ if (pdata->overcurrent_pin[port] == gpio)
|
|
|
|
+ break;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (port == ARRAY_SIZE(pdata->overcurrent_pin)) {
|
|
|
|
+ dev_err(& pdev->dev, "overcurrent interrupt from unknown GPIO\n");
|
|
|
|
+ return IRQ_HANDLED;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ val = gpio_get_value(gpio);
|
|
|
|
+
|
|
|
|
+ /* When notified of an over-current situation, disable power
|
|
|
|
+ on the corresponding port, and mark this port in
|
|
|
|
+ over-current. */
|
|
|
|
+ if (! val) {
|
|
|
|
+ ohci_at91_usb_set_power(pdata, port, 0);
|
|
|
|
+ pdata->overcurrent_status[port] = 1;
|
|
|
|
+ pdata->overcurrent_changed[port] = 1;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ dev_dbg(& pdev->dev, "overcurrent situation %s\n",
|
|
|
|
+ val ? "exited" : "notified");
|
|
|
|
+
|
|
|
|
+ return IRQ_HANDLED;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+/*-------------------------------------------------------------------------*/
|
|
|
|
+
|
|
static int ohci_hcd_at91_drv_probe(struct platform_device *pdev)
|
|
static int ohci_hcd_at91_drv_probe(struct platform_device *pdev)
|
|
{
|
|
{
|
|
struct at91_usbh_data *pdata = pdev->dev.platform_data;
|
|
struct at91_usbh_data *pdata = pdev->dev.platform_data;
|
|
int i;
|
|
int i;
|
|
|
|
|
|
if (pdata) {
|
|
if (pdata) {
|
|
- /* REVISIT make the driver support per-port power switching,
|
|
|
|
- * and also overcurrent detection. Here we assume the ports
|
|
|
|
- * are always powered while this driver is active, and use
|
|
|
|
- * active-low power switches.
|
|
|
|
- */
|
|
|
|
for (i = 0; i < ARRAY_SIZE(pdata->vbus_pin); i++) {
|
|
for (i = 0; i < ARRAY_SIZE(pdata->vbus_pin); i++) {
|
|
if (pdata->vbus_pin[i] <= 0)
|
|
if (pdata->vbus_pin[i] <= 0)
|
|
continue;
|
|
continue;
|
|
gpio_request(pdata->vbus_pin[i], "ohci_vbus");
|
|
gpio_request(pdata->vbus_pin[i], "ohci_vbus");
|
|
- gpio_direction_output(pdata->vbus_pin[i], pdata->vbus_pin_inverted);
|
|
|
|
|
|
+ ohci_at91_usb_set_power(pdata, i, 1);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ for (i = 0; i < ARRAY_SIZE(pdata->overcurrent_pin); i++) {
|
|
|
|
+ int ret;
|
|
|
|
+
|
|
|
|
+ if (pdata->overcurrent_pin[i] <= 0)
|
|
|
|
+ continue;
|
|
|
|
+ gpio_request(pdata->overcurrent_pin[i], "ohci_overcurrent");
|
|
|
|
+
|
|
|
|
+ ret = request_irq(gpio_to_irq(pdata->overcurrent_pin[i]),
|
|
|
|
+ ohci_hcd_at91_overcurrent_irq,
|
|
|
|
+ IRQF_SHARED, "ohci_overcurrent", pdev);
|
|
|
|
+ if (ret) {
|
|
|
|
+ gpio_free(pdata->overcurrent_pin[i]);
|
|
|
|
+ dev_warn(& pdev->dev, "cannot get GPIO IRQ for overcurrent\n");
|
|
|
|
+ }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
@@ -296,9 +495,16 @@ static int ohci_hcd_at91_drv_remove(struct platform_device *pdev)
|
|
for (i = 0; i < ARRAY_SIZE(pdata->vbus_pin); i++) {
|
|
for (i = 0; i < ARRAY_SIZE(pdata->vbus_pin); i++) {
|
|
if (pdata->vbus_pin[i] <= 0)
|
|
if (pdata->vbus_pin[i] <= 0)
|
|
continue;
|
|
continue;
|
|
- gpio_direction_output(pdata->vbus_pin[i], !pdata->vbus_pin_inverted);
|
|
|
|
|
|
+ ohci_at91_usb_set_power(pdata, i, 0);
|
|
gpio_free(pdata->vbus_pin[i]);
|
|
gpio_free(pdata->vbus_pin[i]);
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ for (i = 0; i < ARRAY_SIZE(pdata->overcurrent_pin); i++) {
|
|
|
|
+ if (pdata->overcurrent_pin[i] <= 0)
|
|
|
|
+ continue;
|
|
|
|
+ free_irq(gpio_to_irq(pdata->overcurrent_pin[i]), pdev);
|
|
|
|
+ gpio_free(pdata->overcurrent_pin[i]);
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
|
|
device_init_wakeup(&pdev->dev, 0);
|
|
device_init_wakeup(&pdev->dev, 0);
|