diff options
Diffstat (limited to 'drivers/usb/chipidea/udc.c')
-rw-r--r-- | drivers/usb/chipidea/udc.c | 294 |
1 files changed, 228 insertions, 66 deletions
diff --git a/drivers/usb/chipidea/udc.c b/drivers/usb/chipidea/udc.c index 6a15b7250e9c..cf24527d24eb 100644 --- a/drivers/usb/chipidea/udc.c +++ b/drivers/usb/chipidea/udc.c @@ -423,7 +423,8 @@ static int _hardware_enqueue(struct ci_hw_ep *hwep, struct ci_hw_req *hwreq) hwreq->req.status = -EALREADY; - ret = usb_gadget_map_request(&ci->gadget, &hwreq->req, hwep->dir); + ret = usb_gadget_map_request_by_dev(ci->dev->parent, + &hwreq->req, hwep->dir); if (ret) return ret; @@ -603,7 +604,8 @@ static int _hardware_dequeue(struct ci_hw_ep *hwep, struct ci_hw_req *hwreq) list_del_init(&node->td); } - usb_gadget_unmap_request(&hwep->ci->gadget, &hwreq->req, hwep->dir); + usb_gadget_unmap_request_by_dev(hwep->ci->dev->parent, + &hwreq->req, hwep->dir); hwreq->req.actual += actual; @@ -709,12 +711,6 @@ static int _gadget_stop_activity(struct usb_gadget *gadget) struct ci_hdrc *ci = container_of(gadget, struct ci_hdrc, gadget); unsigned long flags; - spin_lock_irqsave(&ci->lock, flags); - ci->gadget.speed = USB_SPEED_UNKNOWN; - ci->remote_wakeup = 0; - ci->suspended = 0; - spin_unlock_irqrestore(&ci->lock, flags); - /* flush all endpoints */ gadget_for_each_ep(ep, gadget) { usb_ep_fifo_flush(ep); @@ -732,6 +728,12 @@ static int _gadget_stop_activity(struct usb_gadget *gadget) ci->status = NULL; } + spin_lock_irqsave(&ci->lock, flags); + ci->gadget.speed = USB_SPEED_UNKNOWN; + ci->remote_wakeup = 0; + ci->suspended = 0; + spin_unlock_irqrestore(&ci->lock, flags); + return 0; } @@ -750,6 +752,11 @@ __acquires(ci->lock) { int retval; + if (ci_otg_is_fsm_mode(ci)) { + ci->fsm.otg_srp_reqd = 0; + ci->fsm.otg_hnp_reqd = 0; + } + spin_unlock(&ci->lock); if (ci->gadget.speed != USB_SPEED_UNKNOWN) usb_gadget_udc_reset(&ci->gadget, ci->driver); @@ -873,7 +880,10 @@ __acquires(hwep->lock) return -ENOMEM; req->complete = isr_get_status_complete; - req->length = 2; + if (setup->wIndex == OTG_STS_SELECTOR) + req->length = 1; + else + req->length = 2; req->buf = kzalloc(req->length, gfp_flags); if (req->buf == NULL) { retval = -ENOMEM; @@ -881,8 +891,16 @@ __acquires(hwep->lock) } if ((setup->bRequestType & USB_RECIP_MASK) == USB_RECIP_DEVICE) { - *(u16 *)req->buf = (ci->remote_wakeup << 1) | - ci->gadget.is_selfpowered; + if ((setup->wIndex == OTG_STS_SELECTOR) && + ci_otg_is_fsm_mode(ci)) { + if (ci->gadget.host_request_flag) + *(u8 *)req->buf = HOST_REQUEST_FLAG; + else + *(u8 *)req->buf = 0; + } else { + *(u16 *)req->buf = (ci->remote_wakeup << 1) | + ci->gadget.is_selfpowered; + } } else if ((setup->bRequestType & USB_RECIP_MASK) \ == USB_RECIP_ENDPOINT) { dir = (le16_to_cpu(setup->wIndex) & USB_ENDPOINT_DIR_MASK) ? @@ -1007,6 +1025,28 @@ static int otg_a_alt_hnp_support(struct ci_hdrc *ci) return isr_setup_status_phase(ci); } +static int otg_srp_reqd(struct ci_hdrc *ci) +{ + if (ci_otg_is_fsm_mode(ci)) { + ci->fsm.otg_srp_reqd = 1; + return isr_setup_status_phase(ci); + } else { + return -ENOTSUPP; + } +} + +static int otg_hnp_reqd(struct ci_hdrc *ci) +{ + if (ci_otg_is_fsm_mode(ci)) { + ci->fsm.otg_hnp_reqd = 1; + ci->fsm.b_bus_req = 1; + ci->gadget.host_request_flag = 1; + return isr_setup_status_phase(ci); + } else { + return -ENOTSUPP; + } +} + /** * isr_setup_packet_handler: setup packet handler * @ci: UDC descriptor @@ -1077,8 +1117,9 @@ __acquires(ci->lock) type != (USB_DIR_IN|USB_RECIP_ENDPOINT) && type != (USB_DIR_IN|USB_RECIP_INTERFACE)) goto delegate; - if (le16_to_cpu(req.wLength) != 2 || - le16_to_cpu(req.wValue) != 0) + if ((le16_to_cpu(req.wLength) != 2 && + le16_to_cpu(req.wLength) != 1) || + le16_to_cpu(req.wValue) != 0) break; err = isr_get_status_response(ci, &req); break; @@ -1129,6 +1170,12 @@ __acquires(ci->lock) err = isr_setup_status_phase( ci); break; + case TEST_OTG_SRP_REQD: + err = otg_srp_reqd(ci); + break; + case TEST_OTG_HNP_REQD: + err = otg_hnp_reqd(ci); + break; default: break; } @@ -1306,6 +1353,10 @@ static int ep_disable(struct usb_ep *ep) return -EBUSY; spin_lock_irqsave(hwep->lock, flags); + if (hwep->ci->gadget.speed == USB_SPEED_UNKNOWN) { + spin_unlock_irqrestore(hwep->lock, flags); + return 0; + } /* only internal SW should disable ctrl endpts */ @@ -1395,6 +1446,10 @@ static int ep_queue(struct usb_ep *ep, struct usb_request *req, return -EINVAL; spin_lock_irqsave(hwep->lock, flags); + if (hwep->ci->gadget.speed == USB_SPEED_UNKNOWN) { + spin_unlock_irqrestore(hwep->lock, flags); + return 0; + } retval = _ep_queue(ep, req, gfp_flags); spin_unlock_irqrestore(hwep->lock, flags); return retval; @@ -1418,8 +1473,8 @@ static int ep_dequeue(struct usb_ep *ep, struct usb_request *req) return -EINVAL; spin_lock_irqsave(hwep->lock, flags); - - hw_ep_flush(hwep->ci, hwep->num, hwep->dir); + if (hwep->ci->gadget.speed != USB_SPEED_UNKNOWN) + hw_ep_flush(hwep->ci, hwep->num, hwep->dir); list_for_each_entry_safe(node, tmpnode, &hwreq->tds, td) { dma_pool_free(hwep->td_pool, node->ptr, node->dma); @@ -1490,6 +1545,10 @@ static void ep_fifo_flush(struct usb_ep *ep) } spin_lock_irqsave(hwep->lock, flags); + if (hwep->ci->gadget.speed == USB_SPEED_UNKNOWN) { + spin_unlock_irqrestore(hwep->lock, flags); + return; + } hw_ep_flush(hwep->ci, hwep->num, hwep->dir); @@ -1527,27 +1586,19 @@ static int ci_udc_vbus_session(struct usb_gadget *_gadget, int is_active) gadget_ready = 1; spin_unlock_irqrestore(&ci->lock, flags); - if (gadget_ready) { - if (is_active) { - pm_runtime_get_sync(&_gadget->dev); - hw_device_reset(ci); - hw_device_state(ci, ci->ep0out->qh.dma); - usb_gadget_set_state(_gadget, USB_STATE_POWERED); - usb_udc_vbus_handler(_gadget, true); - } else { - usb_udc_vbus_handler(_gadget, false); - if (ci->driver) - ci->driver->disconnect(&ci->gadget); - hw_device_state(ci, 0); - if (ci->platdata->notify_event) - ci->platdata->notify_event(ci, - CI_HDRC_CONTROLLER_STOPPED_EVENT); - _gadget_stop_activity(&ci->gadget); - pm_runtime_put_sync(&_gadget->dev); - usb_gadget_set_state(_gadget, USB_STATE_NOTATTACHED); - } + /* Charger Detection */ + ci_usb_charger_connect(ci, is_active); + + if (ci->usb_phy) { + if (is_active) + usb_phy_set_event(ci->usb_phy, USB_EVENT_VBUS); + else + usb_phy_set_event(ci->usb_phy, USB_EVENT_NONE); } + if (gadget_ready) + ci_hdrc_gadget_connect(_gadget, is_active); + return 0; } @@ -1558,6 +1609,10 @@ static int ci_udc_wakeup(struct usb_gadget *_gadget) int ret = 0; spin_lock_irqsave(&ci->lock, flags); + if (ci->gadget.speed == USB_SPEED_UNKNOWN) { + spin_unlock_irqrestore(&ci->lock, flags); + return 0; + } if (!ci->remote_wakeup) { ret = -EOPNOTSUPP; goto out; @@ -1725,7 +1780,6 @@ static int ci_udc_start(struct usb_gadget *gadget, struct usb_gadget_driver *driver) { struct ci_hdrc *ci = container_of(gadget, struct ci_hdrc, gadget); - unsigned long flags; int retval = -ENOMEM; if (driver->disconnect == NULL) @@ -1745,25 +1799,14 @@ static int ci_udc_start(struct usb_gadget *gadget, ci->driver = driver; /* Start otg fsm for B-device */ - if (ci_otg_is_fsm_mode(ci) && ci->fsm.id) { - ci_hdrc_otg_fsm_start(ci); + if (ci_otg_is_fsm_mode(ci)) { + if (ci->fsm.id) + ci_hdrc_otg_fsm_start(ci); return retval; } - pm_runtime_get_sync(&ci->gadget.dev); - if (ci->vbus_active) { - spin_lock_irqsave(&ci->lock, flags); - hw_device_reset(ci); - } else { - usb_udc_vbus_handler(&ci->gadget, false); - pm_runtime_put_sync(&ci->gadget.dev); - return retval; - } - - retval = hw_device_state(ci, ci->ep0out->qh.dma); - spin_unlock_irqrestore(&ci->lock, flags); - if (retval) - pm_runtime_put_sync(&ci->gadget.dev); + if (ci->vbus_active) + ci_hdrc_gadget_connect(&ci->gadget, 1); return retval; } @@ -1848,27 +1891,35 @@ static irqreturn_t udc_irq(struct ci_hdrc *ci) if (USBi_PCI & intr) { ci->gadget.speed = hw_port_is_high_speed(ci) ? USB_SPEED_HIGH : USB_SPEED_FULL; - if (ci->suspended && ci->driver->resume) { - spin_unlock(&ci->lock); - ci->driver->resume(&ci->gadget); - spin_lock(&ci->lock); + if (ci->usb_phy) + usb_phy_set_event(ci->usb_phy, + USB_EVENT_ENUMERATED); + if (ci->suspended) { + if (ci->driver->resume) { + spin_unlock(&ci->lock); + ci->driver->resume(&ci->gadget); + spin_lock(&ci->lock); + } ci->suspended = 0; + usb_gadget_set_state(&ci->gadget, + ci->resume_state); } } if (USBi_UI & intr) isr_tr_complete_handler(ci); - if (USBi_SLI & intr) { + if ((USBi_SLI & intr) && !(ci->suspended)) { + ci->suspended = 1; + ci->resume_state = ci->gadget.state; if (ci->gadget.speed != USB_SPEED_UNKNOWN && ci->driver->suspend) { - ci->suspended = 1; spin_unlock(&ci->lock); ci->driver->suspend(&ci->gadget); - usb_gadget_set_state(&ci->gadget, - USB_STATE_SUSPENDED); spin_lock(&ci->lock); } + usb_gadget_set_state(&ci->gadget, + USB_STATE_SUSPENDED); } retval = IRQ_HANDLED; } else { @@ -1902,13 +1953,13 @@ static int udc_start(struct ci_hdrc *ci) INIT_LIST_HEAD(&ci->gadget.ep_list); /* alloc resources */ - ci->qh_pool = dma_pool_create("ci_hw_qh", dev, + ci->qh_pool = dma_pool_create("ci_hw_qh", dev->parent, sizeof(struct ci_hw_qh), 64, CI_HDRC_PAGE_SIZE); if (ci->qh_pool == NULL) return -ENOMEM; - ci->td_pool = dma_pool_create("ci_hw_td", dev, + ci->td_pool = dma_pool_create("ci_hw_td", dev->parent, sizeof(struct ci_hw_td), 64, CI_HDRC_PAGE_SIZE); if (ci->td_pool == NULL) { @@ -1958,10 +2009,74 @@ void ci_hdrc_gadget_destroy(struct ci_hdrc *ci) dma_pool_destroy(ci->qh_pool); } +int ci_usb_charger_connect(struct ci_hdrc *ci, int is_active) +{ + int ret = 0; + + if (is_active) + pm_runtime_get_sync(ci->dev); + + if (ci->platdata->notify_event) { + if (is_active) + hw_write(ci, OP_USBCMD, USBCMD_RS, 0); + + ret = ci->platdata->notify_event(ci, + CI_HDRC_CONTROLLER_VBUS_EVENT); + if (ret == CI_HDRC_NOTIFY_RET_DEFER_EVENT) { + hw_device_reset(ci); + /* Pull up dp */ + hw_write(ci, OP_USBCMD, USBCMD_RS, USBCMD_RS); + ci->platdata->notify_event(ci, + CI_HDRC_CONTROLLER_CHARGER_POST_EVENT); + /* Pull down dp */ + hw_write(ci, OP_USBCMD, USBCMD_RS, 0); + } + } + + if (!is_active) + pm_runtime_put_sync(ci->dev); + + return ret; +} + +/** + * ci_hdrc_gadget_connect: caller make sure gadget driver is binded + */ +void ci_hdrc_gadget_connect(struct usb_gadget *gadget, int is_active) +{ + struct ci_hdrc *ci = container_of(gadget, struct ci_hdrc, gadget); + + if (is_active) { + pm_runtime_get_sync(&gadget->dev); + hw_device_reset(ci); + hw_device_state(ci, ci->ep0out->qh.dma); + usb_gadget_set_state(gadget, USB_STATE_POWERED); + usb_udc_vbus_handler(gadget, true); + } else { + usb_udc_vbus_handler(gadget, false); + if (ci->driver) + ci->driver->disconnect(gadget); + hw_device_state(ci, 0); + if (ci->platdata->notify_event) + ci->platdata->notify_event(ci, + CI_HDRC_CONTROLLER_STOPPED_EVENT); + _gadget_stop_activity(gadget); + pm_runtime_put_sync(&gadget->dev); + usb_gadget_set_state(gadget, USB_STATE_NOTATTACHED); + } +} + static int udc_id_switch_for_device(struct ci_hdrc *ci) { - if (ci->is_otg) - /* Clear and enable BSV irq */ + if (!ci->is_otg) + return 0; + + /* + * Clear and enable BSV irq for A-device switch to B-device + * (in otg fsm mode, means A_IDLE->B_DILE) due to ID change. + */ + if (!ci_otg_is_fsm_mode(ci) || + ci->fsm.otg->state == OTG_STATE_A_IDLE) hw_write_otgsc(ci, OTGSC_BSVIS | OTGSC_BSVIE, OTGSC_BSVIS | OTGSC_BSVIE); @@ -1970,12 +2085,57 @@ static int udc_id_switch_for_device(struct ci_hdrc *ci) static void udc_id_switch_for_host(struct ci_hdrc *ci) { + if (!ci->is_otg) + return; + /* - * host doesn't care B_SESSION_VALID event - * so clear and disbale BSV irq + * Clear and disbale BSV irq for B-device switch to A-device + * (in otg fsm mode, means B_IDLE->A_IDLE) due to ID change. */ - if (ci->is_otg) + if (!ci_otg_is_fsm_mode(ci) || + ci->fsm.otg->state == OTG_STATE_B_IDLE || + ci->fsm.otg->state == OTG_STATE_UNDEFINED) hw_write_otgsc(ci, OTGSC_BSVIE | OTGSC_BSVIS, OTGSC_BSVIS); + + ci->vbus_active = 0; +} + +static void udc_suspend_for_power_lost(struct ci_hdrc *ci) +{ + /* + * Set OP_ENDPTLISTADDR to be non-zero for + * checking if controller resume from power lost + * in non-host mode. + */ + if (hw_read(ci, OP_ENDPTLISTADDR, ~0) == 0) + hw_write(ci, OP_ENDPTLISTADDR, ~0, ~0); +} + +/* Power lost with device mode */ +static void udc_resume_from_power_lost(struct ci_hdrc *ci) +{ + if (ci->is_otg) + hw_write_otgsc(ci, OTGSC_BSVIS | OTGSC_BSVIE, + OTGSC_BSVIS | OTGSC_BSVIE); +} + +static void udc_suspend(struct ci_hdrc *ci) +{ + udc_suspend_for_power_lost(ci); + + if (ci->driver && ci->vbus_active && + (ci->gadget.state != USB_STATE_SUSPENDED)) + usb_gadget_disconnect(&ci->gadget); +} + +static void udc_resume(struct ci_hdrc *ci, bool power_lost) +{ + if (power_lost) { + udc_resume_from_power_lost(ci); + } else { + if (ci->driver && ci->vbus_active) + usb_gadget_connect(&ci->gadget); + } } /** @@ -1999,6 +2159,8 @@ int ci_hdrc_gadget_init(struct ci_hdrc *ci) rdrv->start = udc_id_switch_for_device; rdrv->stop = udc_id_switch_for_host; rdrv->irq = udc_irq; + rdrv->suspend = udc_suspend; + rdrv->resume = udc_resume; rdrv->name = "gadget"; ret = udc_start(ci); |