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>
This commit is contained in:
Yoshihiro Shimoda 2018-07-27 10:50:40 +09:00 committed by Felipe Balbi
parent 342ccce173
commit 39facfa01c
2 changed files with 84 additions and 1 deletions

View File

@ -193,6 +193,7 @@ config USB_RENESAS_USB3
tristate 'Renesas USB3.0 Peripheral controller'
depends on ARCH_RENESAS || COMPILE_TEST
depends on EXTCON
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.

View File

@ -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);
@ -2562,6 +2624,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;
@ -2647,6 +2715,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);