Re: [PATCH v10 2/7] usb: mux: add generic code for dual role port mux

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Thu, Jun 02, 2016 at 09:37:24AM +0800, Lu Baolu wrote:
> Several Intel platforms implement USB dual role by having completely
> separate xHCI and dwc3 IPs in PCH or SOC silicons. These two IPs share
> a single USB port. There is another external port mux which controls
> where the data lines should go. While the USB controllers are part of
> the silicon, the port mux design are platform specific.
> 
> This patch adds the generic code to handle such multiple roles of a
> usb port. It exports the necessary interfaces for other components to
> register or unregister a usb mux device, and to control its role.
> It registers the mux device with sysfs as well, so that users are able
> to control the port role from user space.
> 
> Some other archs (e.g. Renesas R-Car gen2 SoCs) need an external mux to
> swap usb roles as well. This code could also be leveraged for those archs.
> 

Sorry to review this so late, from my point, it is a dual-role switch
driver too, we are reviewing USB OTG/dual-role framework [1], it is
not necessary to create another framework to do it. And USB OTG framework
has already tested at Renesas's platform [2].

[1] http://www.spinics.net/lists/linux-usb/msg140835.html
[2] http://www.spinics.net/lists/linux-usb/msg140827.html

Peter

> Signed-off-by: Lu Baolu <baolu.lu@xxxxxxxxxxxxxxx>
> Reviewed-by: Heikki Krogerus <heikki.krogerus@xxxxxxxxxxxxxxx>
> Reviewed-by: Felipe Balbi <balbi@xxxxxxxxxx>
> ---
>  Documentation/ABI/testing/sysfs-bus-platform |  18 +++
>  drivers/usb/Kconfig                          |   2 +
>  drivers/usb/Makefile                         |   1 +
>  drivers/usb/mux/Kconfig                      |   8 ++
>  drivers/usb/mux/Makefile                     |   4 +
>  drivers/usb/mux/portmux-core.c               | 202 +++++++++++++++++++++++++++
>  include/linux/usb/portmux.h                  |  90 ++++++++++++
>  7 files changed, 325 insertions(+)
>  create mode 100644 drivers/usb/mux/Kconfig
>  create mode 100644 drivers/usb/mux/Makefile
>  create mode 100644 drivers/usb/mux/portmux-core.c
>  create mode 100644 include/linux/usb/portmux.h
> 
> diff --git a/Documentation/ABI/testing/sysfs-bus-platform b/Documentation/ABI/testing/sysfs-bus-platform
> index 5172a61..b994e4e 100644
> --- a/Documentation/ABI/testing/sysfs-bus-platform
> +++ b/Documentation/ABI/testing/sysfs-bus-platform
> @@ -18,3 +18,21 @@ Description:
>  		devices to opt-out of driver binding using a driver_override
>  		name such as "none".  Only a single driver may be specified in
>  		the override, there is no support for parsing delimiters.
> +
> +What:		/sys/bus/platform/devices/.../portmux.N/name
> +		/sys/bus/platform/devices/.../portmux.N/state
> +Date:		April 2016
> +Contact:	Lu Baolu <baolu.lu@xxxxxxxxxxxxxxx>
> +Description:
> +		In some platforms, a single USB port is shared between a USB host
> +		controller and a device controller. A USB mux driver is needed to
> +		handle the port mux. Read-only attribute "name" shows the name of
> +		the port mux device. "state" attribute shows and stores the mux
> +		state.
> +		For read:
> +		'unknown'    - the mux hasn't been set yet;
> +		'peripheral' - mux has been switched to PERIPHERAL controller;
> +		'host'       - mux has been switched to HOST controller.
> +		For write:
> +		'peripheral' - mux will be switched to PERIPHERAL controller;
> +		'host'       - mux will be switched to HOST controller.
> diff --git a/drivers/usb/Kconfig b/drivers/usb/Kconfig
> index 8689dcb..328916e 100644
> --- a/drivers/usb/Kconfig
> +++ b/drivers/usb/Kconfig
> @@ -148,6 +148,8 @@ endif # USB
>  
>  source "drivers/usb/phy/Kconfig"
>  
> +source "drivers/usb/mux/Kconfig"
> +
>  source "drivers/usb/gadget/Kconfig"
>  
>  config USB_LED_TRIG
> diff --git a/drivers/usb/Makefile b/drivers/usb/Makefile
> index dca7856..9a92338 100644
> --- a/drivers/usb/Makefile
> +++ b/drivers/usb/Makefile
> @@ -6,6 +6,7 @@
>  
>  obj-$(CONFIG_USB)		+= core/
>  obj-$(CONFIG_USB_SUPPORT)	+= phy/
> +obj-$(CONFIG_USB_SUPPORT)	+= mux/
>  
>  obj-$(CONFIG_USB_DWC3)		+= dwc3/
>  obj-$(CONFIG_USB_DWC2)		+= dwc2/
> diff --git a/drivers/usb/mux/Kconfig b/drivers/usb/mux/Kconfig
> new file mode 100644
> index 0000000..ba778f2
> --- /dev/null
> +++ b/drivers/usb/mux/Kconfig
> @@ -0,0 +1,8 @@
> +#
> +# USB port mux driver configuration
> +#
> +
> +menuconfig USB_PORTMUX
> +	bool "USB dual role port MUX support"
> +	help
> +	  Generic USB dual role port mux support.
> diff --git a/drivers/usb/mux/Makefile b/drivers/usb/mux/Makefile
> new file mode 100644
> index 0000000..f85df92
> --- /dev/null
> +++ b/drivers/usb/mux/Makefile
> @@ -0,0 +1,4 @@
> +#
> +# Makefile for USB port mux drivers
> +#
> +obj-$(CONFIG_USB_PORTMUX)		+= portmux-core.o
> diff --git a/drivers/usb/mux/portmux-core.c b/drivers/usb/mux/portmux-core.c
> new file mode 100644
> index 0000000..75fbb45
> --- /dev/null
> +++ b/drivers/usb/mux/portmux-core.c
> @@ -0,0 +1,202 @@
> +/**
> + * portmux-core.c - USB Port Mux support
> + *
> + * Copyright (C) 2016 Intel Corporation
> + *
> + * Author: Lu Baolu <baolu.lu@xxxxxxxxxxxxxxx>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + */
> +
> +#include <linux/slab.h>
> +#include <linux/err.h>
> +#include <linux/usb/portmux.h>
> +
> +static int usb_mux_change_state(struct portmux_dev *pdev,
> +				enum portmux_role role)
> +{
> +	struct device *dev = &pdev->dev;
> +	int ret = -EINVAL;
> +
> +	dev_WARN_ONCE(dev,
> +		      !mutex_is_locked(&pdev->mux_mutex),
> +		      "mutex is unlocked\n");
> +
> +	switch (role) {
> +	case PORTMUX_HOST:
> +		if (pdev->desc->ops->set_host_cb)
> +			ret = pdev->desc->ops->set_host_cb(pdev->dev.parent);
> +		break;
> +	case PORTMUX_DEVICE:
> +		if (pdev->desc->ops->set_device_cb)
> +			ret = pdev->desc->ops->set_device_cb(pdev->dev.parent);
> +		break;
> +	default:
> +		break;
> +	}
> +
> +	if (!ret)
> +		pdev->mux_state = role;
> +
> +	return ret;
> +}
> +
> +static const char * const role_name[] = {
> +	"unknown",	/* PORTMUX_UNKNOWN */
> +	"host",		/* PORTMUX_HOST */
> +	"peripheral"	/* PORTMUX_DEVICE */
> +};
> +
> +static ssize_t state_show(struct device *dev,
> +			  struct device_attribute *attr,
> +			  char *buf)
> +{
> +	struct portmux_dev *pdev = dev_get_drvdata(dev);
> +
> +	return sprintf(buf, "%s\n", role_name[pdev->mux_state]);
> +}
> +
> +static ssize_t state_store(struct device *dev,
> +			   struct device_attribute *attr,
> +			   const char *buf, size_t count)
> +{
> +	struct portmux_dev *pdev = dev_get_drvdata(dev);
> +	enum portmux_role role;
> +
> +	if (sysfs_streq(buf, "peripheral"))
> +		role = PORTMUX_DEVICE;
> +	else if (sysfs_streq(buf, "host"))
> +		role = PORTMUX_HOST;
> +	else
> +		return -EINVAL;
> +
> +	mutex_lock(&pdev->mux_mutex);
> +	usb_mux_change_state(pdev, role);
> +	mutex_unlock(&pdev->mux_mutex);
> +
> +	return count;
> +}
> +static DEVICE_ATTR_RW(state);
> +
> +static ssize_t name_show(struct device *dev,
> +			 struct device_attribute *attr,
> +			 char *buf)
> +{
> +	struct portmux_dev *pdev = dev_get_drvdata(dev);
> +
> +	return sprintf(buf, "%s\n", pdev->desc->name);
> +}
> +static DEVICE_ATTR_RO(name);
> +
> +static struct attribute *portmux_attrs[] = {
> +	&dev_attr_state.attr,
> +	&dev_attr_name.attr,
> +	NULL,
> +};
> +
> +static struct attribute_group portmux_attr_grp = {
> +	.attrs = portmux_attrs,
> +};
> +
> +static const struct attribute_group *portmux_group[] = {
> +	&portmux_attr_grp,
> +	NULL,
> +};
> +
> +static void portmux_dev_release(struct device *dev)
> +{
> +	dev_vdbg(dev, "%s\n", __func__);
> +}
> +
> +/**
> + * portmux_register - register a port mux
> + * @dev: device the mux belongs to
> + * @desc: the descriptor of this port mux
> + *
> + * Called by port mux drivers to register a mux. Returns a valid
> + * pointer to struct portmux_dev on success or an ERR_PTR() on
> + * error.
> + */
> +struct portmux_dev *portmux_register(struct portmux_desc *desc)
> +{
> +	static atomic_t portmux_no = ATOMIC_INIT(-1);
> +	struct portmux_dev *pdev;
> +	int ret;
> +
> +	/* parameter sanity check */
> +	if (!desc || !desc->name || !desc->ops || !desc->dev)
> +		return ERR_PTR(-EINVAL);
> +
> +	pdev = kzalloc(sizeof(*pdev), GFP_KERNEL);
> +	if (!pdev)
> +		return ERR_PTR(-ENOMEM);
> +
> +	mutex_init(&pdev->mux_mutex);
> +	pdev->desc = desc;
> +	pdev->dev.parent = desc->dev;
> +	pdev->dev.release = portmux_dev_release;
> +	dev_set_name(&pdev->dev, "portmux.%lu",
> +		     (unsigned long)atomic_inc_return(&portmux_no));
> +	pdev->dev.groups = portmux_group;
> +	ret = device_register(&pdev->dev);
> +	if (ret) {
> +		kfree(pdev);
> +		return ERR_PTR(ret);
> +	}
> +
> +	dev_set_drvdata(&pdev->dev, pdev);
> +
> +	return pdev;
> +}
> +EXPORT_SYMBOL_GPL(portmux_register);
> +
> +/**
> + * portmux_unregister - unregister a port mux
> + * @pdev: the port mux device
> + *
> + * Called by port mux drivers to release a mux.
> + */
> +void portmux_unregister(struct portmux_dev *pdev)
> +{
> +	device_unregister(&pdev->dev);
> +	kfree(pdev);
> +}
> +EXPORT_SYMBOL_GPL(portmux_unregister);
> +
> +/**
> + * portmux_switch - switch the port role
> + * @pdev: the port mux device
> + * @role: the target role
> + *
> + * Called by other components to switch the port role.
> + */
> +int portmux_switch(struct portmux_dev *pdev, enum portmux_role role)
> +{
> +	int ret;
> +
> +	mutex_lock(&pdev->mux_mutex);
> +	ret = usb_mux_change_state(pdev, role);
> +	mutex_unlock(&pdev->mux_mutex);
> +
> +	return ret;
> +}
> +EXPORT_SYMBOL_GPL(portmux_switch);
> +
> +#ifdef CONFIG_PM_SLEEP
> +/**
> + * portmux_complete - refresh port state during system resumes back
> + * @pdev: the port mux device
> + *
> + * Called by port mux drivers to refresh port state during system
> + * resumes back.
> + */
> +void portmux_complete(struct portmux_dev *pdev)
> +{
> +	mutex_lock(&pdev->mux_mutex);
> +	usb_mux_change_state(pdev, pdev->mux_state);
> +	mutex_unlock(&pdev->mux_mutex);
> +}
> +EXPORT_SYMBOL_GPL(portmux_complete);
> +#endif
> diff --git a/include/linux/usb/portmux.h b/include/linux/usb/portmux.h
> new file mode 100644
> index 0000000..093620a
> --- /dev/null
> +++ b/include/linux/usb/portmux.h
> @@ -0,0 +1,90 @@
> +/**
> + * portmux.h - USB Port Mux definitions
> + *
> + * Copyright (C) 2016 Intel Corporation
> + *
> + * Author: Lu Baolu <baolu.lu@xxxxxxxxxxxxxxx>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + */
> +
> +#ifndef __LINUX_USB_PORTMUX_H
> +#define __LINUX_USB_PORTMUX_H
> +
> +#include <linux/device.h>
> +
> +/**
> + * struct portmux_ops - ops two switch the port
> + *
> + * @set_host_cb: callback for switching port to host
> + * @set_device_cb: callback for switching port to device
> + */
> +struct portmux_ops {
> +	int (*set_host_cb)(struct device *dev);
> +	int (*set_device_cb)(struct device *dev);
> +};
> +
> +/**
> + * struct portmux_desc - port mux device descriptor
> + *
> + * @name: the name of the mux device
> + * @dev: the parent of the mux device
> + * @ops: ops to switch the port role
> + */
> +struct portmux_desc {
> +	const char *name;
> +	struct device *dev;
> +	const struct portmux_ops *ops;
> +};
> +
> +/**
> + * enum portmux_role - role of the port
> + */
> +enum portmux_role {
> +	PORTMUX_UNKNOWN,
> +	PORTMUX_HOST,
> +	PORTMUX_DEVICE,
> +};
> +
> +/**
> + * struct portmux_dev - A mux device
> + *
> + * @desc: the descriptor of the mux
> + * @dev: device of this mux
> + * @mux_mutex: lock to serialize port switch operation
> + * @mux_state: state of the mux
> + */
> +struct portmux_dev {
> +	const struct portmux_desc *desc;
> +	struct device dev;
> +
> +	/* lock for mux_state */
> +	struct mutex mux_mutex;
> +	enum portmux_role mux_state;
> +};
> +
> +/*
> + * Functions for mux driver
> + */
> +struct portmux_dev *portmux_register(struct portmux_desc *desc);
> +void portmux_unregister(struct portmux_dev *pdev);
> +#ifdef CONFIG_PM_SLEEP
> +void portmux_complete(struct portmux_dev *pdev);
> +#endif
> +
> +/*
> + * Functions for mux consumer
> + */
> +#if defined(CONFIG_USB_PORTMUX)
> +int portmux_switch(struct portmux_dev *pdev, enum portmux_role role);
> +#else
> +static inline int portmux_switch(struct portmux_dev *pdev,
> +				 enum portmux_role role)
> +{
> +	return 0;
> +}
> +#endif
> +
> +#endif /* __LINUX_USB_PORTMUX_H */
> -- 
> 2.1.4
> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-usb" in
> the body of a message to majordomo@xxxxxxxxxxxxxxx
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

-- 

Best Regards,
Peter Chen
--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html



[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux