diff --git a/Documentation/devicetree/bindings/usb/atmel-usb.txt b/Documentation/devicetree/bindings/usb/atmel-usb.txt index f4262ed60582d3..20d366a05be638 100644 --- a/Documentation/devicetree/bindings/usb/atmel-usb.txt +++ b/Documentation/devicetree/bindings/usb/atmel-usb.txt @@ -18,6 +18,12 @@ Required properties: - atmel,oc-gpio: If present, specifies a gpio that needs to be activated for the overcurrent detection. +Optional properties: + - id-gpio: If present, specifies a gpio used for OTG "like" detection. + This is only applicable to devices that share a transceiver between + host port A and the device port. Both OHCI and UDC nodes should have + this property. + usb0: ohci@00500000 { compatible = "atmel,at91rm9200-ohci", "usb-ohci"; reg = <0x00500000 0x100000>; @@ -25,6 +31,7 @@ usb0: ohci@00500000 { clock-names = "ohci_clk", "hclk", "uhpck"; interrupts = <20 4>; num-ports = <2>; + id-gpio = <&pioE, 26, 0>; }; EHCI @@ -93,6 +100,10 @@ Required properties: Optional properties: - atmel,vbus-gpio: If present, specifies a gpio that allows to detect whether vbus is present (USB is connected). + - id-gpio: If present, specifies a gpio used for OTG "like" detection. + This is only applicable to devices that share a transceiver between + host port A and the device port. Both OHCI and UDC nodes should have + this property. Required child node properties: - name: Name of the endpoint. @@ -112,6 +123,7 @@ usb2: gadget@fff78000 { clocks = <&utmi>, <&udphs_clk>; clock-names = "hclk", "pclk"; atmel,vbus-gpio = <&pioB 19 0>; + id-gpio = <&pioE, 26, 0>; ep@0 { reg = <0>; diff --git a/drivers/usb/gadget/udc/atmel_usba_udc.c b/drivers/usb/gadget/udc/atmel_usba_udc.c index b21a23f73fabc9..c41ec54d58fd66 100644 --- a/drivers/usb/gadget/udc/atmel_usba_udc.c +++ b/drivers/usb/gadget/udc/atmel_usba_udc.c @@ -1945,6 +1945,20 @@ static irqreturn_t usba_vbus_irq_thread(int irq, void *devid) /* debounce */ udelay(10); + /* If ID pin is pulled low, configure USB port as host */ + if (gpio_is_valid(udc->id_pin)) { + if (!(gpio_get_value(udc->id_pin))) { + udc->id_prev = 0; + usba_writel(udc, CTRL, USBA_DISABLE_MASK); + return IRQ_HANDLED; + } + + if (udc->id_prev != gpio_get_value(udc->id_pin)) { + udc->id_prev = 1; + return IRQ_HANDLED; + } + } + mutex_lock(&udc->vbus_mutex); vbus = vbus_is_present(udc); @@ -1983,10 +1997,19 @@ static int atmel_usba_start(struct usb_gadget *gadget, /* If Vbus is present, enable the controller and wait for reset */ udc->vbus_prev = vbus_is_present(udc); - if (udc->vbus_prev) { + + if (gpio_is_valid(udc->id_pin)) { + if ((udc->vbus_prev) && gpio_get_value(udc->id_pin)) { ret = usba_start(udc); - if (ret) - goto err; + if (ret) + goto err; + } + } else { + if (udc->vbus_prev) { + ret = usba_start(udc); + if (ret) + goto err; + } } mutex_unlock(&udc->vbus_mutex); @@ -2082,6 +2105,8 @@ static struct usba_ep * atmel_udc_of_init(struct platform_device *pdev, &flags); udc->vbus_pin_inverted = (flags & OF_GPIO_ACTIVE_LOW) ? 1 : 0; + udc->id_pin = of_get_named_gpio_flags(np, "id-gpio", 0, &flags); + if (fifo_mode == 0) { pp = NULL; while ((pp = of_get_next_child(np, pp))) @@ -2234,6 +2259,7 @@ static struct usba_ep * usba_udc_pdata(struct platform_device *pdev, udc->vbus_pin = pdata->vbus_pin; udc->vbus_pin_inverted = pdata->vbus_pin_inverted; + udc->id_pin = pdata->id_pin; udc->num_ep = pdata->num_ep; INIT_LIST_HEAD(&eps[0].ep.ep_list); @@ -2309,6 +2335,7 @@ static int usba_udc_probe(struct platform_device *pdev) udc->pclk = pclk; udc->hclk = hclk; udc->vbus_pin = -ENODEV; + udc->id_pin = -ENODEV; ret = -ENOMEM; udc->regs = devm_ioremap(&pdev->dev, regs->start, resource_size(regs)); diff --git a/drivers/usb/gadget/udc/atmel_usba_udc.h b/drivers/usb/gadget/udc/atmel_usba_udc.h index 9551b704bfd3e7..06756047d43179 100644 --- a/drivers/usb/gadget/udc/atmel_usba_udc.h +++ b/drivers/usb/gadget/udc/atmel_usba_udc.h @@ -356,6 +356,8 @@ struct usba_udc { u16 test_mode; int vbus_prev; + int id_prev; + int id_pin; u32 int_enb_cache; diff --git a/drivers/usb/host/ohci-at91.c b/drivers/usb/host/ohci-at91.c index af0566da77e7f6..174ada5fd10b6e 100644 --- a/drivers/usb/host/ohci-at91.c +++ b/drivers/usb/host/ohci-at91.c @@ -46,6 +46,7 @@ struct at91_usbh_data { u8 vbus_pin_active_low[AT91_MAX_USBH_PORTS]; u8 overcurrent_status[AT91_MAX_USBH_PORTS]; u8 overcurrent_changed[AT91_MAX_USBH_PORTS]; + int id_gpio; }; struct ohci_at91_priv { @@ -508,6 +509,29 @@ static irqreturn_t ohci_hcd_at91_overcurrent_irq(int irq, void *data) return IRQ_HANDLED; } +static irqreturn_t ohci_at91_otg_irq(int irq, void *data) +{ + struct platform_device *pdev = data; + struct at91_usbh_data *pdata = dev_get_platdata(&pdev->dev); + + /* debounce */ + mdelay(10); + + /* OTG "like" connector can only be on port A as it shares a + transceiver with the UDP, so vbus_pin index is 0 */ + if (gpio_is_valid(pdata->id_gpio)) { + if (gpio_get_value(pdata->id_gpio)) { + /* id pin is high, we are not a host so set VBUS low */ + gpio_direction_output(pdata->vbus_pin[0], 1); + } else { + /* id pin is low, we are now a host, set VBUS hi */ + gpio_direction_output(pdata->vbus_pin[0], 0); + } + } + + return IRQ_HANDLED; +} + static const struct of_device_id at91_ohci_dt_ids[] = { { .compatible = "atmel,at91rm9200-ohci" }, { /* sentinel */ } @@ -557,6 +581,7 @@ static int ohci_hcd_at91_drv_probe(struct platform_device *pdev) gpio = of_get_named_gpio_flags(np, "atmel,vbus-gpio", i, &flags); + pdata->vbus_pin[i] = gpio; if (!gpio_is_valid(gpio)) continue; @@ -619,6 +644,34 @@ static int ohci_hcd_at91_drv_probe(struct platform_device *pdev) } } + /* Get ID pin (if present) for OTG "like" detection */ + pdata->id_gpio = of_get_named_gpio_flags(np, "id-gpio", 0, &flags); + + if (gpio_is_valid(pdata->id_gpio)) { + ret = gpio_request(pdata->id_gpio, "otg_id_pin"); + if (ret) { + dev_err(&pdev->dev, "can't request ID pin %d\n", pdata->id_gpio); + } + + ret = gpio_direction_input(pdata->id_gpio); + + if (ret) { + dev_err(&pdev->dev, "can't configure ID pin %d as input\n", pdata->id_gpio); + gpio_free(pdata->id_gpio); + } + + ret = request_irq(gpio_to_irq(pdata->id_gpio), ohci_at91_otg_irq, 0, "otg_irq", pdev); + + if (ret) { + gpio_free(pdata->id_gpio); + dev_err(&pdev->dev, "OTG IRQ request failed. id_gpio: %d\n", pdata->id_gpio); + } + + if (!gpio_get_value(pdata->id_gpio)) { + gpio_direction_output(pdata->vbus_pin[0], 0); + } + } + device_init_wakeup(&pdev->dev, 1); return usb_hcd_at91_probe(&ohci_at91_hc_driver, pdev); } @@ -642,6 +695,11 @@ static int ohci_hcd_at91_drv_remove(struct platform_device *pdev) free_irq(gpio_to_irq(pdata->overcurrent_pin[i]), pdev); gpio_free(pdata->overcurrent_pin[i]); } + + if (gpio_is_valid(pdata->id_gpio)) { + free_irq(gpio_to_irq(pdata->id_gpio), pdev); + gpio_free(pdata->id_gpio); + } } device_init_wakeup(&pdev->dev, 0); diff --git a/include/linux/usb/atmel_usba_udc.h b/include/linux/usb/atmel_usba_udc.h index ba99af275a31fa..61dccd272567a9 100644 --- a/include/linux/usb/atmel_usba_udc.h +++ b/include/linux/usb/atmel_usba_udc.h @@ -16,6 +16,7 @@ struct usba_ep_data { struct usba_platform_data { int vbus_pin; int vbus_pin_inverted; + int id_pin; int num_ep; struct usba_ep_data ep[0]; };