| From 22b0fb1431f5378ad37927c3cf7027017679a73a Mon Sep 17 00:00:00 2001 |
| From: Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com> |
| Date: Fri, 27 Jul 2018 10:50:40 +0900 |
| Subject: [PATCH 1622/1795] usb: gadget: udc: renesas_usb3: Add register of usb |
| role switch |
| |
| This patch adds role switch support for R-Car SoCs into the USB 3.0 |
| peripheral driver. Some R-Car SoCs (e.g. R-Car H3) have USB 3.0 |
| dual-role device controller which has the USB 3.0 xHCI host and |
| Renesas USB 3.0 peripheral. |
| |
| Unfortunately, the mode change register (DRD_CON) contains |
| the USB 3.0 peripheral controller side only. So, this renesas_usb3 |
| driver manages the DRD_CON now. However, in peripheral mode, the host |
| should stop. Also the host hardware needs to reinitialize its own |
| registers when the mode changes from peripheral to host mode. |
| Otherwise, the host cannot work correctly (e.g. detect a device |
| as high-speed). |
| |
| To achieve this reinitialization by a driver, this driver also |
| registers a role switch driver to manage the DRD_CON and get |
| a device pointer of usb 3.0 host from "companion" property of OF. |
| Then, when the usb role is changed, renesas_usb3_role_switch_set() |
| will attach/release the xhci-plat driver to reinitialize the host |
| hardware. |
| |
| Signed-off-by: Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com> |
| Reviewed-by: Heikki Krogerus <heikki.krogerus@linux.intel.com> |
| Signed-off-by: Felipe Balbi <felipe.balbi@linux.intel.com> |
| (cherry picked from commit 39facfa01c9fc64f90233d1734882f0a0cafe36a) |
| Signed-off-by: Simon Horman <horms+renesas@verge.net.au> |
| Signed-off-by: Geert Uytterhoeven <geert+renesas@glider.be> |
| |
| Conflicts: |
| drivers/usb/gadget/udc/Kconfig |
| --- |
| drivers/usb/gadget/udc/Kconfig | 1 + |
| drivers/usb/gadget/udc/renesas_usb3.c | 84 ++++++++++++++++++++++++++- |
| 2 files changed, 84 insertions(+), 1 deletion(-) |
| |
| diff --git a/drivers/usb/gadget/udc/Kconfig b/drivers/usb/gadget/udc/Kconfig |
| index 1e9567091d86..1d0ff704bfa8 100644 |
| --- a/drivers/usb/gadget/udc/Kconfig |
| +++ b/drivers/usb/gadget/udc/Kconfig |
| @@ -193,6 +193,7 @@ config USB_RENESAS_USB3 |
| tristate 'Renesas USB3.0 Peripheral controller' |
| depends on ARCH_RENESAS || COMPILE_TEST |
| depends on EXTCON && HAS_DMA |
| + select USB_ROLE_SWITCH |
| help |
| Renesas USB3.0 Peripheral controller is a USB peripheral controller |
| that supports super, high, and full speed USB 3.0 data transfers. |
| diff --git a/drivers/usb/gadget/udc/renesas_usb3.c b/drivers/usb/gadget/udc/renesas_usb3.c |
| index 977ea1a02cf9..a94fd09ee6f4 100644 |
| --- a/drivers/usb/gadget/udc/renesas_usb3.c |
| +++ b/drivers/usb/gadget/udc/renesas_usb3.c |
| @@ -23,6 +23,8 @@ |
| #include <linux/uaccess.h> |
| #include <linux/usb/ch9.h> |
| #include <linux/usb/gadget.h> |
| +#include <linux/usb/of.h> |
| +#include <linux/usb/role.h> |
| |
| /* register definitions */ |
| #define USB3_AXI_INT_STA 0x008 |
| @@ -335,6 +337,11 @@ struct renesas_usb3 { |
| struct phy *phy; |
| struct dentry *dentry; |
| |
| + struct usb_role_switch *role_sw; |
| + struct device *host_dev; |
| + struct work_struct role_work; |
| + enum usb_role role; |
| + |
| struct renesas_usb3_ep *usb3_ep; |
| int num_usb3_eps; |
| |
| @@ -651,6 +658,14 @@ static void usb3_check_vbus(struct renesas_usb3 *usb3) |
| } |
| } |
| |
| +static void renesas_usb3_role_work(struct work_struct *work) |
| +{ |
| + struct renesas_usb3 *usb3 = |
| + container_of(work, struct renesas_usb3, role_work); |
| + |
| + usb_role_switch_set_role(usb3->role_sw, usb3->role); |
| +} |
| + |
| static void usb3_set_mode(struct renesas_usb3 *usb3, bool host) |
| { |
| if (host) |
| @@ -659,6 +674,16 @@ static void usb3_set_mode(struct renesas_usb3 *usb3, bool host) |
| usb3_set_bit(usb3, DRD_CON_PERI_CON, USB3_DRD_CON); |
| } |
| |
| +static void usb3_set_mode_by_role_sw(struct renesas_usb3 *usb3, bool host) |
| +{ |
| + if (usb3->role_sw) { |
| + usb3->role = host ? USB_ROLE_HOST : USB_ROLE_DEVICE; |
| + schedule_work(&usb3->role_work); |
| + } else { |
| + usb3_set_mode(usb3, host); |
| + } |
| +} |
| + |
| static void usb3_vbus_out(struct renesas_usb3 *usb3, bool enable) |
| { |
| if (enable) |
| @@ -672,7 +697,7 @@ static void usb3_mode_config(struct renesas_usb3 *usb3, bool host, bool a_dev) |
| unsigned long flags; |
| |
| spin_lock_irqsave(&usb3->lock, flags); |
| - usb3_set_mode(usb3, host); |
| + usb3_set_mode_by_role_sw(usb3, host); |
| usb3_vbus_out(usb3, a_dev); |
| /* for A-Peripheral or forced B-device mode */ |
| if ((!host && a_dev) || |
| @@ -2302,6 +2327,41 @@ static const struct usb_gadget_ops renesas_usb3_gadget_ops = { |
| .set_selfpowered = renesas_usb3_set_selfpowered, |
| }; |
| |
| +static enum usb_role renesas_usb3_role_switch_get(struct device *dev) |
| +{ |
| + struct renesas_usb3 *usb3 = dev_get_drvdata(dev); |
| + enum usb_role cur_role; |
| + |
| + pm_runtime_get_sync(dev); |
| + cur_role = usb3_is_host(usb3) ? USB_ROLE_HOST : USB_ROLE_DEVICE; |
| + pm_runtime_put(dev); |
| + |
| + return cur_role; |
| +} |
| + |
| +static int renesas_usb3_role_switch_set(struct device *dev, |
| + enum usb_role role) |
| +{ |
| + struct renesas_usb3 *usb3 = dev_get_drvdata(dev); |
| + struct device *host = usb3->host_dev; |
| + enum usb_role cur_role = renesas_usb3_role_switch_get(dev); |
| + |
| + pm_runtime_get_sync(dev); |
| + if (cur_role == USB_ROLE_HOST && role == USB_ROLE_DEVICE) { |
| + device_release_driver(host); |
| + usb3_set_mode(usb3, false); |
| + } else if (cur_role == USB_ROLE_DEVICE && role == USB_ROLE_HOST) { |
| + /* Must set the mode before device_attach of the host */ |
| + usb3_set_mode(usb3, true); |
| + /* This device_attach() might sleep */ |
| + if (device_attach(host) < 0) |
| + dev_err(dev, "device_attach(host) failed\n"); |
| + } |
| + pm_runtime_put(dev); |
| + |
| + return 0; |
| +} |
| + |
| static ssize_t role_store(struct device *dev, struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| @@ -2405,6 +2465,8 @@ static int renesas_usb3_remove(struct platform_device *pdev) |
| debugfs_remove_recursive(usb3->dentry); |
| device_remove_file(&pdev->dev, &dev_attr_role); |
| |
| + usb_role_switch_unregister(usb3->role_sw); |
| + |
| usb_del_gadget_udc(&usb3->gadget); |
| renesas_usb3_dma_free_prd(usb3, &pdev->dev); |
| |
| @@ -2561,6 +2623,12 @@ static const unsigned int renesas_usb3_cable[] = { |
| EXTCON_NONE, |
| }; |
| |
| +static const struct usb_role_switch_desc renesas_usb3_role_switch_desc = { |
| + .set = renesas_usb3_role_switch_set, |
| + .get = renesas_usb3_role_switch_get, |
| + .allow_userspace_control = true, |
| +}; |
| + |
| static int renesas_usb3_probe(struct platform_device *pdev) |
| { |
| struct renesas_usb3 *usb3; |
| @@ -2646,6 +2714,20 @@ static int renesas_usb3_probe(struct platform_device *pdev) |
| if (ret < 0) |
| goto err_dev_create; |
| |
| + INIT_WORK(&usb3->role_work, renesas_usb3_role_work); |
| + usb3->role_sw = usb_role_switch_register(&pdev->dev, |
| + &renesas_usb3_role_switch_desc); |
| + if (!IS_ERR(usb3->role_sw)) { |
| + usb3->host_dev = usb_of_get_companion_dev(&pdev->dev); |
| + if (!usb3->host_dev) { |
| + /* If not found, this driver will not use a role sw */ |
| + usb_role_switch_unregister(usb3->role_sw); |
| + usb3->role_sw = NULL; |
| + } |
| + } else { |
| + usb3->role_sw = NULL; |
| + } |
| + |
| usb3->workaround_for_vbus = priv->workaround_for_vbus; |
| |
| renesas_usb3_debugfs_init(usb3, &pdev->dev); |
| -- |
| 2.19.0 |
| |