All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v5 00/12] USB Type-C device-connection, mux and switch support
@ 2018-02-28 15:07 Hans de Goede
  2018-02-28 15:07   ` [v5,01/12] " Hans de Goede
                   ` (11 more replies)
  0 siblings, 12 replies; 43+ messages in thread
From: Hans de Goede @ 2018-02-28 15:07 UTC (permalink / raw)
  To: Darren Hart, Andy Shevchenko, MyungJoo Ham, Chanwoo Choi,
	Mathias Nyman, Heikki Krogerus, Greg Kroah-Hartman,
	Guenter Roeck
  Cc: Hans de Goede, platform-driver-x86, linux-kernel, linux-usb

Hi All,

Here is version 5 of Heikki's and my USB Type-C device-connection, mux and
switch support series. Versions 2 - 5 bring various small code and style
fixes based on review (no major changes).

Here is the original cover-letter of v1:

Some devices with an USB Type-C connector have a bunch of muxes
behind that connector which need to be controlled by the kernel (rather
then having them controlled by firmware as on most devices).

Quite a while back I submitted a patch-series to tie together these muxes
and the Type-C Port Manager (tcpm) code, using the then new drivers/mux
framework. But the way I used the mux framework went against what it was
designed for, so in the end that series got nowhere.

Heikki Krogerus from Intel, who maintains the USB TYPEC subsystem, has
recently been working on solving the same problem for some boards he is
doing hardware-enablement for.

Heikki has come up with a number of infrastructure patches for this.
The first one is a new device-connection framework. This solves the
problem of describing non bus device-links on x86 in what in my experience
with this problematic area is a really nice simple, clean and *generic*
way. This could for example in the near future also replace the custom
lookup code in the pwm subsys and the custom pwm_add_table() /
pwm_remove_table() functions.

The other 3 patches add a framework for the different type of Type-C /
USB "muxes".

Heikki and I have gone through a number of iterations of these patches
together and we believe these are now ready for merging. Since merging
infrastructure patches without users is not done and Heikki's own use-case
for these is not yet ready for merging, the rest of this series consists
of patches by me to make the Type-C connector found on some Cherry Trail
devices (finally) be able to actually work as an USB port and not just
a charge port.

The last patch uses the new usb-role-switch framework to also do proper
devcie / host switching on CHT devices with a USB micro AB connector.
This is also a big feature for CHT users, because before this they had
to do a reboot to get an OTG-host cable recognized (on some devices).

Part of this series is an usb-role-switch driver for the role-switch
found inside the xhci controller on e.g. CHT devices, this is currently
implemented as the generic xhci controller instantiating a platform
child-device for this, since this really is a separate chunk of HW
which happens to sit in the XHCI mmio space. This approach may not be
universally liked, given that in this new series the role-switch driver
is much smaller and does not have any external deps anymore we could
just integrate it into the xhci code if that is preferred.

About merging this series (once everything is reviewed, etc.), there are
quite some interdependencies in it esp. a lot of the patches depend on
the first patch. Luckily patches 1-10 all apply to subsystems which are
maintained by Greg (most to the USB subsys). Which just leaves patches
11 and 12 once 1-10 are merged. Greg, can you create an immutable branch
for the platform/x86 and extcon maintainers to merge once this is done?

Regards,

Hans

^ permalink raw reply	[flat|nested] 43+ messages in thread

* [PATCH v5 01/12] drivers: base: Unified device connection lookup
@ 2018-02-28 15:07   ` Hans de Goede
  0 siblings, 0 replies; 43+ messages in thread
From: Hans de Goede @ 2018-02-28 15:07 UTC (permalink / raw)
  To: Darren Hart, Andy Shevchenko, MyungJoo Ham, Chanwoo Choi,
	Mathias Nyman, Heikki Krogerus, Greg Kroah-Hartman,
	Guenter Roeck
  Cc: Hans de Goede, platform-driver-x86, linux-kernel, linux-usb

From: Heikki Krogerus <heikki.krogerus@linux.intel.com>

Several frameworks - clk, gpio, phy, pmw, etc. - maintain
lookup tables for describing connections and provide custom
API for handling them. This introduces a single generic
lookup table and API for the connections.

The motivation for this commit is centralizing the
connection lookup, but the goal is to ultimately extract the
connection descriptions also from firmware by using the
fwnode_graph_* functions and other mechanisms that are
available.

Signed-off-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Reviewed-by: Hans de Goede <hdegoede@redhat.com>
Reviewed-by: Andy Shevchenko <andy.shevchenko@gmail.com>
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
---
Changes in v5:
-Add missing documentation for @list struct devcon member

Changes in v4:
-Add Andy's Reviewed-by

Changes in v3:
-Various spelling and gramar fixes in the docs pointed out by Randy Dunlap

Changes in v2:
-Add a (struct devcon) cast to the DEVCON() macro
---
 Documentation/driver-api/device_connection.rst |  43 ++++++++
 drivers/base/Makefile                          |   3 +-
 drivers/base/devcon.c                          | 139 +++++++++++++++++++++++++
 include/linux/connection.h                     |  34 ++++++
 4 files changed, 218 insertions(+), 1 deletion(-)
 create mode 100644 Documentation/driver-api/device_connection.rst
 create mode 100644 drivers/base/devcon.c
 create mode 100644 include/linux/connection.h

diff --git a/Documentation/driver-api/device_connection.rst b/Documentation/driver-api/device_connection.rst
new file mode 100644
index 000000000000..64a3e5e9bb7c
--- /dev/null
+++ b/Documentation/driver-api/device_connection.rst
@@ -0,0 +1,43 @@
+==================
+Device connections
+==================
+
+Introduction
+------------
+
+Devices often have connections to other devices that are outside of the direct
+child/parent relationship. A serial or network communication controller, which
+could be a PCI device, may need to be able to get a reference to its PHY
+component, which could be attached for example to the I2C bus. Some device
+drivers need to be able to control the clocks or the GPIOs for their devices,
+and so on.
+
+Device connections are generic descriptions of any type of connection between
+two separate devices.
+
+Device connections alone do not create a dependency between the two devices.
+They are only descriptions which are not tied to either of the devices directly.
+A dependency between the two devices exists only if one of the two endpoint
+devices requests a reference to the other. The descriptions themselves can be
+defined in firmware (not yet supported) or they can be built-in.
+
+Usage
+-----
+
+Device connections should exist before device ``->probe`` callback is called for
+either endpoint device in the description. If the connections are defined in
+firmware, this is not a problem. It should be considered if the connection
+descriptions are "built-in", and need to be added separately.
+
+The connection description consists of the names of the two devices with the
+connection, i.e. the endpoints, and unique identifier for the connection which
+is needed if there are multiple connections between the two devices.
+
+After a description exists, the devices in it can request reference to the other
+endpoint device, or they can request the description itself.
+
+API
+---
+
+.. kernel-doc:: drivers/base/devcon.c
+   : functions: __device_find_connection device_find_connection add_device_connection remove_device_connection
diff --git a/drivers/base/Makefile b/drivers/base/Makefile
index e32a52490051..12a7f64d35a9 100644
--- a/drivers/base/Makefile
+++ b/drivers/base/Makefile
@@ -5,7 +5,8 @@ obj-y			:= component.o core.o bus.o dd.o syscore.o \
 			   driver.o class.o platform.o \
 			   cpu.o firmware.o init.o map.o devres.o \
 			   attribute_container.o transport_class.o \
-			   topology.o container.o property.o cacheinfo.o
+			   topology.o container.o property.o cacheinfo.o \
+			   devcon.o
 obj-$(CONFIG_DEVTMPFS)	+= devtmpfs.o
 obj-$(CONFIG_DMA_CMA) += dma-contiguous.o
 obj-y			+= power/
diff --git a/drivers/base/devcon.c b/drivers/base/devcon.c
new file mode 100644
index 000000000000..1628dcb34709
--- /dev/null
+++ b/drivers/base/devcon.c
@@ -0,0 +1,139 @@
+// SPDX-License-Identifier: GPL-2.0
+/**
+ * Device connections
+ *
+ * Copyright (C) 2018 Intel Corporation
+ * Author: Heikki Krogerus <heikki.krogerus@linux.intel.com>
+ */
+
+#include <linux/connection.h>
+#include <linux/device.h>
+
+static DEFINE_MUTEX(devcon_lock);
+static LIST_HEAD(devcon_list);
+
+/**
+ * __device_find_connection - Find physical connection to a device
+ * @dev: Device with the connection
+ * @con_id: Identifier for the connection
+ * @data: Data for the match function
+ * @match: Function to check and convert the connection description
+ *
+ * Find a connection with unique identifier @con_id between @dev and another
+ * device. @match will be used to convert the connection description to data the
+ * caller is expecting to be returned.
+ */
+void *__device_find_connection(struct device *dev, const char *con_id,
+			       void *data,
+			       void *(*match)(struct devcon *con, int ep,
+					      void *data))
+{
+	const char *devname = dev_name(dev);
+	struct devcon *con;
+	void *ret = NULL;
+	int ep;
+
+	if (!match)
+		return NULL;
+
+	rcu_read_lock();
+
+	list_for_each_entry_rcu(con, &devcon_list, list) {
+		ep = match_string(con->endpoint, 2, devname);
+		if (ep < 0)
+			continue;
+
+		if (con_id && strcmp(con->id, con_id))
+			continue;
+
+		ret = match(con, !ep, data);
+		if (ret)
+			break;
+	}
+
+	rcu_read_unlock();
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(__device_find_connection);
+
+#include <linux/platform_device.h>
+#include <linux/spi/spi.h>
+#include <linux/i2c.h>
+#include <linux/pci.h>
+
+static struct bus_type *generic_match_buses[] = {
+	&platform_bus_type,
+#ifdef CONFIG_PCI
+	&pci_bus_type,
+#endif
+#ifdef CONFIG_I2C
+	&i2c_bus_type,
+#endif
+#ifdef CONFIG_SPI_MASTER
+	&spi_bus_type,
+#endif
+	NULL,
+};
+
+/* This tries to find the device from the most common bus types by name. */
+static void *generic_match(struct devcon *con, int ep, void *data)
+{
+	struct bus_type *bus;
+	struct device *dev;
+
+	for (bus = generic_match_buses[0]; bus; bus++) {
+		dev = bus_find_device_by_name(bus, NULL, con->endpoint[ep]);
+		if (dev)
+			return dev;
+	}
+
+	/*
+	 * We only get called if a connection was found, tell the caller to
+	 * wait for the other device to show up.
+	 */
+	return ERR_PTR(-EPROBE_DEFER);
+}
+
+/**
+ * device_find_connection - Find two devices connected together
+ * @dev: Device with the connection
+ * @con_id: Identifier for the connection
+ *
+ * Find a connection with unique identifier @con_id between @dev and
+ * another device. On success returns handle to the device that is connected
+ * to @dev, with the reference count for the found device incremented. Returns
+ * NULL if no matching connection was found, or ERR_PTR(-EPROBE_DEFER) when a
+ * connection was found but the other device has not been enumerated yet.
+ */
+struct device *device_find_connection(struct device *dev, const char *con_id)
+{
+	return __device_find_connection(dev, con_id, generic_match, NULL);
+}
+EXPORT_SYMBOL_GPL(device_find_connection);
+
+/**
+ * add_device_connection - Register a collection of connection descriptions
+ * @con: Collection of connection descriptions to be registered
+ */
+void add_device_connection(struct devcon *con)
+{
+	mutex_lock(&devcon_lock);
+	list_add_tail_rcu(&con->list, &devcon_list);
+	mutex_unlock(&devcon_lock);
+}
+EXPORT_SYMBOL_GPL(add_device_connection);
+
+/**
+ * remove_device_connections - Unregister collection of connection descriptions
+ * @con: Collection of connection descriptions to be unregistered
+ */
+void remove_device_connection(struct devcon *con)
+{
+	mutex_lock(&devcon_lock);
+	list_del_rcu(&con->list);
+	mutex_unlock(&devcon_lock);
+	/* The caller may free the devcon struct immediately afterwards. */
+	synchronize_rcu();
+}
+EXPORT_SYMBOL_GPL(remove_device_connection);
diff --git a/include/linux/connection.h b/include/linux/connection.h
new file mode 100644
index 000000000000..b2af70df5a97
--- /dev/null
+++ b/include/linux/connection.h
@@ -0,0 +1,34 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#ifndef _LINUX_CONNECTION_H_
+#define _LINUX_CONNECTION_H_
+
+#include <linux/list.h>
+
+struct device;
+
+/**
+ * struct devcon - Device Connection Descriptor
+ * @endpoint: The names of the two devices connected together
+ * @id: Unique identifier for the connection
+ * @list: List head, private for devcon internal use only
+ */
+struct devcon {
+	const char		*endpoint[2];
+	const char		*id;
+	struct list_head	list;
+};
+
+void *__device_find_connection(struct device *dev, const char *con_id,
+			       void *data,
+			       void *(*match)(struct devcon *con, int ep,
+					      void *data));
+
+struct device *device_find_connection(struct device *dev, const char *con_id);
+
+#define DEVCON(_ep0, _ep1, _id)    (struct devcon) { { _ep0, _ep1 }, _id, }
+
+void add_device_connection(struct devcon *con);
+void remove_device_connection(struct devcon *con);
+
+#endif /* _LINUX_CONNECTION_H_ */
-- 
2.14.3

^ permalink raw reply related	[flat|nested] 43+ messages in thread

* [v5,01/12] drivers: base: Unified device connection lookup
@ 2018-02-28 15:07   ` Hans de Goede
  0 siblings, 0 replies; 43+ messages in thread
From: Hans de Goede @ 2018-02-28 15:07 UTC (permalink / raw)
  To: Darren Hart, Andy Shevchenko, MyungJoo Ham, Chanwoo Choi,
	Mathias Nyman, Heikki Krogerus, Greg Kroah-Hartman,
	Guenter Roeck
  Cc: Hans de Goede, platform-driver-x86, linux-kernel, linux-usb

From: Heikki Krogerus <heikki.krogerus@linux.intel.com>

Several frameworks - clk, gpio, phy, pmw, etc. - maintain
lookup tables for describing connections and provide custom
API for handling them. This introduces a single generic
lookup table and API for the connections.

The motivation for this commit is centralizing the
connection lookup, but the goal is to ultimately extract the
connection descriptions also from firmware by using the
fwnode_graph_* functions and other mechanisms that are
available.

Signed-off-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Reviewed-by: Hans de Goede <hdegoede@redhat.com>
Reviewed-by: Andy Shevchenko <andy.shevchenko@gmail.com>
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
---
Changes in v5:
-Add missing documentation for @list struct devcon member

Changes in v4:
-Add Andy's Reviewed-by

Changes in v3:
-Various spelling and gramar fixes in the docs pointed out by Randy Dunlap

Changes in v2:
-Add a (struct devcon) cast to the DEVCON() macro
---
 Documentation/driver-api/device_connection.rst |  43 ++++++++
 drivers/base/Makefile                          |   3 +-
 drivers/base/devcon.c                          | 139 +++++++++++++++++++++++++
 include/linux/connection.h                     |  34 ++++++
 4 files changed, 218 insertions(+), 1 deletion(-)
 create mode 100644 Documentation/driver-api/device_connection.rst
 create mode 100644 drivers/base/devcon.c
 create mode 100644 include/linux/connection.h

diff --git a/Documentation/driver-api/device_connection.rst b/Documentation/driver-api/device_connection.rst
new file mode 100644
index 000000000000..64a3e5e9bb7c
--- /dev/null
+++ b/Documentation/driver-api/device_connection.rst
@@ -0,0 +1,43 @@
+==================
+Device connections
+==================
+
+Introduction
+------------
+
+Devices often have connections to other devices that are outside of the direct
+child/parent relationship. A serial or network communication controller, which
+could be a PCI device, may need to be able to get a reference to its PHY
+component, which could be attached for example to the I2C bus. Some device
+drivers need to be able to control the clocks or the GPIOs for their devices,
+and so on.
+
+Device connections are generic descriptions of any type of connection between
+two separate devices.
+
+Device connections alone do not create a dependency between the two devices.
+They are only descriptions which are not tied to either of the devices directly.
+A dependency between the two devices exists only if one of the two endpoint
+devices requests a reference to the other. The descriptions themselves can be
+defined in firmware (not yet supported) or they can be built-in.
+
+Usage
+-----
+
+Device connections should exist before device ``->probe`` callback is called for
+either endpoint device in the description. If the connections are defined in
+firmware, this is not a problem. It should be considered if the connection
+descriptions are "built-in", and need to be added separately.
+
+The connection description consists of the names of the two devices with the
+connection, i.e. the endpoints, and unique identifier for the connection which
+is needed if there are multiple connections between the two devices.
+
+After a description exists, the devices in it can request reference to the other
+endpoint device, or they can request the description itself.
+
+API
+---
+
+.. kernel-doc:: drivers/base/devcon.c
+   : functions: __device_find_connection device_find_connection add_device_connection remove_device_connection
diff --git a/drivers/base/Makefile b/drivers/base/Makefile
index e32a52490051..12a7f64d35a9 100644
--- a/drivers/base/Makefile
+++ b/drivers/base/Makefile
@@ -5,7 +5,8 @@ obj-y			:= component.o core.o bus.o dd.o syscore.o \
 			   driver.o class.o platform.o \
 			   cpu.o firmware.o init.o map.o devres.o \
 			   attribute_container.o transport_class.o \
-			   topology.o container.o property.o cacheinfo.o
+			   topology.o container.o property.o cacheinfo.o \
+			   devcon.o
 obj-$(CONFIG_DEVTMPFS)	+= devtmpfs.o
 obj-$(CONFIG_DMA_CMA) += dma-contiguous.o
 obj-y			+= power/
diff --git a/drivers/base/devcon.c b/drivers/base/devcon.c
new file mode 100644
index 000000000000..1628dcb34709
--- /dev/null
+++ b/drivers/base/devcon.c
@@ -0,0 +1,139 @@
+// SPDX-License-Identifier: GPL-2.0
+/**
+ * Device connections
+ *
+ * Copyright (C) 2018 Intel Corporation
+ * Author: Heikki Krogerus <heikki.krogerus@linux.intel.com>
+ */
+
+#include <linux/connection.h>
+#include <linux/device.h>
+
+static DEFINE_MUTEX(devcon_lock);
+static LIST_HEAD(devcon_list);
+
+/**
+ * __device_find_connection - Find physical connection to a device
+ * @dev: Device with the connection
+ * @con_id: Identifier for the connection
+ * @data: Data for the match function
+ * @match: Function to check and convert the connection description
+ *
+ * Find a connection with unique identifier @con_id between @dev and another
+ * device. @match will be used to convert the connection description to data the
+ * caller is expecting to be returned.
+ */
+void *__device_find_connection(struct device *dev, const char *con_id,
+			       void *data,
+			       void *(*match)(struct devcon *con, int ep,
+					      void *data))
+{
+	const char *devname = dev_name(dev);
+	struct devcon *con;
+	void *ret = NULL;
+	int ep;
+
+	if (!match)
+		return NULL;
+
+	rcu_read_lock();
+
+	list_for_each_entry_rcu(con, &devcon_list, list) {
+		ep = match_string(con->endpoint, 2, devname);
+		if (ep < 0)
+			continue;
+
+		if (con_id && strcmp(con->id, con_id))
+			continue;
+
+		ret = match(con, !ep, data);
+		if (ret)
+			break;
+	}
+
+	rcu_read_unlock();
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(__device_find_connection);
+
+#include <linux/platform_device.h>
+#include <linux/spi/spi.h>
+#include <linux/i2c.h>
+#include <linux/pci.h>
+
+static struct bus_type *generic_match_buses[] = {
+	&platform_bus_type,
+#ifdef CONFIG_PCI
+	&pci_bus_type,
+#endif
+#ifdef CONFIG_I2C
+	&i2c_bus_type,
+#endif
+#ifdef CONFIG_SPI_MASTER
+	&spi_bus_type,
+#endif
+	NULL,
+};
+
+/* This tries to find the device from the most common bus types by name. */
+static void *generic_match(struct devcon *con, int ep, void *data)
+{
+	struct bus_type *bus;
+	struct device *dev;
+
+	for (bus = generic_match_buses[0]; bus; bus++) {
+		dev = bus_find_device_by_name(bus, NULL, con->endpoint[ep]);
+		if (dev)
+			return dev;
+	}
+
+	/*
+	 * We only get called if a connection was found, tell the caller to
+	 * wait for the other device to show up.
+	 */
+	return ERR_PTR(-EPROBE_DEFER);
+}
+
+/**
+ * device_find_connection - Find two devices connected together
+ * @dev: Device with the connection
+ * @con_id: Identifier for the connection
+ *
+ * Find a connection with unique identifier @con_id between @dev and
+ * another device. On success returns handle to the device that is connected
+ * to @dev, with the reference count for the found device incremented. Returns
+ * NULL if no matching connection was found, or ERR_PTR(-EPROBE_DEFER) when a
+ * connection was found but the other device has not been enumerated yet.
+ */
+struct device *device_find_connection(struct device *dev, const char *con_id)
+{
+	return __device_find_connection(dev, con_id, generic_match, NULL);
+}
+EXPORT_SYMBOL_GPL(device_find_connection);
+
+/**
+ * add_device_connection - Register a collection of connection descriptions
+ * @con: Collection of connection descriptions to be registered
+ */
+void add_device_connection(struct devcon *con)
+{
+	mutex_lock(&devcon_lock);
+	list_add_tail_rcu(&con->list, &devcon_list);
+	mutex_unlock(&devcon_lock);
+}
+EXPORT_SYMBOL_GPL(add_device_connection);
+
+/**
+ * remove_device_connections - Unregister collection of connection descriptions
+ * @con: Collection of connection descriptions to be unregistered
+ */
+void remove_device_connection(struct devcon *con)
+{
+	mutex_lock(&devcon_lock);
+	list_del_rcu(&con->list);
+	mutex_unlock(&devcon_lock);
+	/* The caller may free the devcon struct immediately afterwards. */
+	synchronize_rcu();
+}
+EXPORT_SYMBOL_GPL(remove_device_connection);
diff --git a/include/linux/connection.h b/include/linux/connection.h
new file mode 100644
index 000000000000..b2af70df5a97
--- /dev/null
+++ b/include/linux/connection.h
@@ -0,0 +1,34 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#ifndef _LINUX_CONNECTION_H_
+#define _LINUX_CONNECTION_H_
+
+#include <linux/list.h>
+
+struct device;
+
+/**
+ * struct devcon - Device Connection Descriptor
+ * @endpoint: The names of the two devices connected together
+ * @id: Unique identifier for the connection
+ * @list: List head, private for devcon internal use only
+ */
+struct devcon {
+	const char		*endpoint[2];
+	const char		*id;
+	struct list_head	list;
+};
+
+void *__device_find_connection(struct device *dev, const char *con_id,
+			       void *data,
+			       void *(*match)(struct devcon *con, int ep,
+					      void *data));
+
+struct device *device_find_connection(struct device *dev, const char *con_id);
+
+#define DEVCON(_ep0, _ep1, _id)    (struct devcon) { { _ep0, _ep1 }, _id, }
+
+void add_device_connection(struct devcon *con);
+void remove_device_connection(struct devcon *con);
+
+#endif /* _LINUX_CONNECTION_H_ */

^ permalink raw reply related	[flat|nested] 43+ messages in thread

* [PATCH v5 02/12] usb: typec: Start using ERR_PTR
@ 2018-02-28 15:07   ` Hans de Goede
  0 siblings, 0 replies; 43+ messages in thread
From: Hans de Goede @ 2018-02-28 15:07 UTC (permalink / raw)
  To: Darren Hart, Andy Shevchenko, MyungJoo Ham, Chanwoo Choi,
	Mathias Nyman, Heikki Krogerus, Greg Kroah-Hartman,
	Guenter Roeck
  Cc: Hans de Goede, platform-driver-x86, linux-kernel, linux-usb

From: Heikki Krogerus <heikki.krogerus@linux.intel.com>

In order to allow the USB Type-C Class driver take care of
things like muxes and other possible dependencies for the
port drivers, returning ERR_PTR instead of NULL from the
registration functions in case of failure.

The reason for taking over control of the muxes for example
is because handling them in the port drivers would be just
boilerplate.

Signed-off-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Reviewed-by: Hans de Goede <hdegoede@redhat.com>
Reviewed-by: Guenter Roeck <linux@roeck-us.net>
Reviewed-by: Andy Shevchenko <andy.shevchenko@gmail.com>
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
---
Changes in v4:
-Add Andy's Reviewed-by

Changes in v3:
-Add Guenter's Reviewed-by

Changes in v2:
-Add IS_ERR_OR_NULL() checks to the unregister functions
---
 drivers/usb/typec/tcpm.c      | 16 +++++++++-------
 drivers/usb/typec/tps6598x.c  | 15 ++++++++-------
 drivers/usb/typec/typec.c     | 44 +++++++++++++++++++++----------------------
 drivers/usb/typec/ucsi/ucsi.c | 31 ++++++++++++++++++------------
 4 files changed, 58 insertions(+), 48 deletions(-)

diff --git a/drivers/usb/typec/tcpm.c b/drivers/usb/typec/tcpm.c
index f4d563ee7690..7cd28b700a7f 100644
--- a/drivers/usb/typec/tcpm.c
+++ b/drivers/usb/typec/tcpm.c
@@ -1044,7 +1044,7 @@ static int tcpm_pd_svdm(struct tcpm_port *port, const __le32 *payload, int cnt,
 		break;
 	case CMDT_RSP_ACK:
 		/* silently drop message if we are not connected */
-		if (!port->partner)
+		if (IS_ERR_OR_NULL(port->partner))
 			break;
 
 		switch (cmd) {
@@ -3743,8 +3743,8 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc)
 	port->port_type = tcpc->config->type;
 
 	port->typec_port = typec_register_port(port->dev, &port->typec_caps);
-	if (!port->typec_port) {
-		err = -ENOMEM;
+	if (IS_ERR(port->typec_port)) {
+		err = PTR_ERR(port->typec_port);
 		goto out_destroy_wq;
 	}
 
@@ -3753,15 +3753,17 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc)
 
 		i = 0;
 		while (paltmode->svid && i < ARRAY_SIZE(port->port_altmode)) {
-			port->port_altmode[i] =
-			  typec_port_register_altmode(port->typec_port,
-						      paltmode);
-			if (!port->port_altmode[i]) {
+			struct typec_altmode *alt;
+
+			alt = typec_port_register_altmode(port->typec_port,
+							  paltmode);
+			if (IS_ERR(alt)) {
 				tcpm_log(port,
 					 "%s: failed to register port alternate mode 0x%x",
 					 dev_name(dev), paltmode->svid);
 				break;
 			}
+			port->port_altmode[i] = alt;
 			i++;
 			paltmode++;
 		}
diff --git a/drivers/usb/typec/tps6598x.c b/drivers/usb/typec/tps6598x.c
index 2719f5d382f7..37a15c14a6c6 100644
--- a/drivers/usb/typec/tps6598x.c
+++ b/drivers/usb/typec/tps6598x.c
@@ -158,15 +158,15 @@ static int tps6598x_connect(struct tps6598x *tps, u32 status)
 		desc.identity = &tps->partner_identity;
 	}
 
-	tps->partner = typec_register_partner(tps->port, &desc);
-	if (!tps->partner)
-		return -ENODEV;
-
 	typec_set_pwr_opmode(tps->port, mode);
 	typec_set_pwr_role(tps->port, TPS_STATUS_PORTROLE(status));
 	typec_set_vconn_role(tps->port, TPS_STATUS_VCONN(status));
 	typec_set_data_role(tps->port, TPS_STATUS_DATAROLE(status));
 
+	tps->partner = typec_register_partner(tps->port, &desc);
+	if (IS_ERR(tps->partner))
+		return PTR_ERR(tps->partner);
+
 	if (desc.identity)
 		typec_partner_set_identity(tps->partner);
 
@@ -175,7 +175,8 @@ static int tps6598x_connect(struct tps6598x *tps, u32 status)
 
 static void tps6598x_disconnect(struct tps6598x *tps, u32 status)
 {
-	typec_unregister_partner(tps->partner);
+	if (!IS_ERR(tps->partner))
+		typec_unregister_partner(tps->partner);
 	tps->partner = NULL;
 	typec_set_pwr_opmode(tps->port, TYPEC_PWR_MODE_USB);
 	typec_set_pwr_role(tps->port, TPS_STATUS_PORTROLE(status));
@@ -418,8 +419,8 @@ static int tps6598x_probe(struct i2c_client *client)
 	tps->typec_cap.prefer_role = TYPEC_NO_PREFERRED_ROLE;
 
 	tps->port = typec_register_port(&client->dev, &tps->typec_cap);
-	if (!tps->port)
-		return -ENODEV;
+	if (IS_ERR(tps->port))
+		return PTR_ERR(tps->port);
 
 	if (status & TPS_STATUS_PLUG_PRESENT) {
 		ret = tps6598x_connect(tps, status);
diff --git a/drivers/usb/typec/typec.c b/drivers/usb/typec/typec.c
index 735726ced602..dc28ad868d93 100644
--- a/drivers/usb/typec/typec.c
+++ b/drivers/usb/typec/typec.c
@@ -386,7 +386,7 @@ typec_register_altmode(struct device *parent,
 
 	alt = kzalloc(sizeof(*alt), GFP_KERNEL);
 	if (!alt)
-		return NULL;
+		return ERR_PTR(-ENOMEM);
 
 	alt->svid = desc->svid;
 	alt->n_modes = desc->n_modes;
@@ -402,7 +402,7 @@ typec_register_altmode(struct device *parent,
 		dev_err(parent, "failed to register alternate mode (%d)\n",
 			ret);
 		put_device(&alt->dev);
-		return NULL;
+		return ERR_PTR(ret);
 	}
 
 	return alt;
@@ -417,7 +417,7 @@ typec_register_altmode(struct device *parent,
  */
 void typec_unregister_altmode(struct typec_altmode *alt)
 {
-	if (alt)
+	if (!IS_ERR_OR_NULL(alt))
 		device_unregister(&alt->dev);
 }
 EXPORT_SYMBOL_GPL(typec_unregister_altmode);
@@ -509,7 +509,7 @@ EXPORT_SYMBOL_GPL(typec_partner_register_altmode);
  *
  * Registers a device for USB Type-C Partner described in @desc.
  *
- * Returns handle to the partner on success or NULL on failure.
+ * Returns handle to the partner on success or ERR_PTR on failure.
  */
 struct typec_partner *typec_register_partner(struct typec_port *port,
 					     struct typec_partner_desc *desc)
@@ -519,7 +519,7 @@ struct typec_partner *typec_register_partner(struct typec_port *port,
 
 	partner = kzalloc(sizeof(*partner), GFP_KERNEL);
 	if (!partner)
-		return NULL;
+		return ERR_PTR(-ENOMEM);
 
 	partner->usb_pd = desc->usb_pd;
 	partner->accessory = desc->accessory;
@@ -542,7 +542,7 @@ struct typec_partner *typec_register_partner(struct typec_port *port,
 	if (ret) {
 		dev_err(&port->dev, "failed to register partner (%d)\n", ret);
 		put_device(&partner->dev);
-		return NULL;
+		return ERR_PTR(ret);
 	}
 
 	return partner;
@@ -557,7 +557,7 @@ EXPORT_SYMBOL_GPL(typec_register_partner);
  */
 void typec_unregister_partner(struct typec_partner *partner)
 {
-	if (partner)
+	if (!IS_ERR_OR_NULL(partner))
 		device_unregister(&partner->dev);
 }
 EXPORT_SYMBOL_GPL(typec_unregister_partner);
@@ -587,7 +587,7 @@ static const struct device_type typec_plug_dev_type = {
  * the plug lists in response to Discover Modes command need to be listed in an
  * array in @desc.
  *
- * Returns handle to the alternate mode on success or NULL on failure.
+ * Returns handle to the alternate mode on success or ERR_PTR on failure.
  */
 struct typec_altmode *
 typec_plug_register_altmode(struct typec_plug *plug,
@@ -606,7 +606,7 @@ EXPORT_SYMBOL_GPL(typec_plug_register_altmode);
  * Cable Plug represents a plug with electronics in it that can response to USB
  * Power Delivery SOP Prime or SOP Double Prime packages.
  *
- * Returns handle to the cable plug on success or NULL on failure.
+ * Returns handle to the cable plug on success or ERR_PTR on failure.
  */
 struct typec_plug *typec_register_plug(struct typec_cable *cable,
 				       struct typec_plug_desc *desc)
@@ -617,7 +617,7 @@ struct typec_plug *typec_register_plug(struct typec_cable *cable,
 
 	plug = kzalloc(sizeof(*plug), GFP_KERNEL);
 	if (!plug)
-		return NULL;
+		return ERR_PTR(-ENOMEM);
 
 	sprintf(name, "plug%d", desc->index);
 
@@ -631,7 +631,7 @@ struct typec_plug *typec_register_plug(struct typec_cable *cable,
 	if (ret) {
 		dev_err(&cable->dev, "failed to register plug (%d)\n", ret);
 		put_device(&plug->dev);
-		return NULL;
+		return ERR_PTR(ret);
 	}
 
 	return plug;
@@ -646,7 +646,7 @@ EXPORT_SYMBOL_GPL(typec_register_plug);
  */
 void typec_unregister_plug(struct typec_plug *plug)
 {
-	if (plug)
+	if (!IS_ERR_OR_NULL(plug))
 		device_unregister(&plug->dev);
 }
 EXPORT_SYMBOL_GPL(typec_unregister_plug);
@@ -724,7 +724,7 @@ EXPORT_SYMBOL_GPL(typec_cable_set_identity);
  * Registers a device for USB Type-C Cable described in @desc. The cable will be
  * parent for the optional cable plug devises.
  *
- * Returns handle to the cable on success or NULL on failure.
+ * Returns handle to the cable on success or ERR_PTR on failure.
  */
 struct typec_cable *typec_register_cable(struct typec_port *port,
 					 struct typec_cable_desc *desc)
@@ -734,7 +734,7 @@ struct typec_cable *typec_register_cable(struct typec_port *port,
 
 	cable = kzalloc(sizeof(*cable), GFP_KERNEL);
 	if (!cable)
-		return NULL;
+		return ERR_PTR(-ENOMEM);
 
 	cable->type = desc->type;
 	cable->active = desc->active;
@@ -757,7 +757,7 @@ struct typec_cable *typec_register_cable(struct typec_port *port,
 	if (ret) {
 		dev_err(&port->dev, "failed to register cable (%d)\n", ret);
 		put_device(&cable->dev);
-		return NULL;
+		return ERR_PTR(ret);
 	}
 
 	return cable;
@@ -772,7 +772,7 @@ EXPORT_SYMBOL_GPL(typec_register_cable);
  */
 void typec_unregister_cable(struct typec_cable *cable)
 {
-	if (cable)
+	if (!IS_ERR_OR_NULL(cable))
 		device_unregister(&cable->dev);
 }
 EXPORT_SYMBOL_GPL(typec_unregister_cable);
@@ -1256,7 +1256,7 @@ EXPORT_SYMBOL_GPL(typec_set_pwr_opmode);
  * This routine is used to register an alternate mode that @port is capable of
  * supporting.
  *
- * Returns handle to the alternate mode on success or NULL on failure.
+ * Returns handle to the alternate mode on success or ERR_PTR on failure.
  */
 struct typec_altmode *
 typec_port_register_altmode(struct typec_port *port,
@@ -1273,7 +1273,7 @@ EXPORT_SYMBOL_GPL(typec_port_register_altmode);
  *
  * Registers a device for USB Type-C Port described in @cap.
  *
- * Returns handle to the port on success or NULL on failure.
+ * Returns handle to the port on success or ERR_PTR on failure.
  */
 struct typec_port *typec_register_port(struct device *parent,
 				       const struct typec_capability *cap)
@@ -1285,12 +1285,12 @@ struct typec_port *typec_register_port(struct device *parent,
 
 	port = kzalloc(sizeof(*port), GFP_KERNEL);
 	if (!port)
-		return NULL;
+		return ERR_PTR(-ENOMEM);
 
 	id = ida_simple_get(&typec_index_ida, 0, 0, GFP_KERNEL);
 	if (id < 0) {
 		kfree(port);
-		return NULL;
+		return ERR_PTR(id);
 	}
 
 	if (cap->type == TYPEC_PORT_DFP)
@@ -1326,7 +1326,7 @@ struct typec_port *typec_register_port(struct device *parent,
 	if (ret) {
 		dev_err(parent, "failed to register port (%d)\n", ret);
 		put_device(&port->dev);
-		return NULL;
+		return ERR_PTR(ret);
 	}
 
 	return port;
@@ -1341,7 +1341,7 @@ EXPORT_SYMBOL_GPL(typec_register_port);
  */
 void typec_unregister_port(struct typec_port *port)
 {
-	if (port)
+	if (!IS_ERR_OR_NULL(port))
 		device_unregister(&port->dev);
 }
 EXPORT_SYMBOL_GPL(typec_unregister_port);
diff --git a/drivers/usb/typec/ucsi/ucsi.c b/drivers/usb/typec/ucsi/ucsi.c
index 79046fe66426..69d544cfcd45 100644
--- a/drivers/usb/typec/ucsi/ucsi.c
+++ b/drivers/usb/typec/ucsi/ucsi.c
@@ -260,38 +260,45 @@ static void ucsi_pwr_opmode_change(struct ucsi_connector *con)
 
 static int ucsi_register_partner(struct ucsi_connector *con)
 {
-	struct typec_partner_desc partner;
+	struct typec_partner_desc desc;
+	struct typec_partner *partner;
 
 	if (con->partner)
 		return 0;
 
-	memset(&partner, 0, sizeof(partner));
+	memset(&desc, 0, sizeof(desc));
 
 	switch (con->status.partner_type) {
 	case UCSI_CONSTAT_PARTNER_TYPE_DEBUG:
-		partner.accessory = TYPEC_ACCESSORY_DEBUG;
+		desc.accessory = TYPEC_ACCESSORY_DEBUG;
 		break;
 	case UCSI_CONSTAT_PARTNER_TYPE_AUDIO:
-		partner.accessory = TYPEC_ACCESSORY_AUDIO;
+		desc.accessory = TYPEC_ACCESSORY_AUDIO;
 		break;
 	default:
 		break;
 	}
 
-	partner.usb_pd = con->status.pwr_op_mode == UCSI_CONSTAT_PWR_OPMODE_PD;
+	desc.usb_pd = con->status.pwr_op_mode == UCSI_CONSTAT_PWR_OPMODE_PD;
 
-	con->partner = typec_register_partner(con->port, &partner);
-	if (!con->partner) {
-		dev_err(con->ucsi->dev, "con%d: failed to register partner\n",
-			con->num);
-		return -ENODEV;
+	partner = typec_register_partner(con->port, &desc);
+	if (IS_ERR(partner)) {
+		dev_err(con->ucsi->dev,
+			"con%d: failed to register partner (%ld)\n", con->num,
+			PTR_ERR(partner));
+		return PTR_ERR(partner);
 	}
 
+	con->partner = partner;
+
 	return 0;
 }
 
 static void ucsi_unregister_partner(struct ucsi_connector *con)
 {
+	if (!con->partner)
+		return;
+
 	typec_unregister_partner(con->partner);
 	con->partner = NULL;
 }
@@ -606,8 +613,8 @@ static int ucsi_register_port(struct ucsi *ucsi, int index)
 
 	/* Register the connector */
 	con->port = typec_register_port(ucsi->dev, cap);
-	if (!con->port)
-		return -ENODEV;
+	if (IS_ERR(con->port))
+		return PTR_ERR(con->port);
 
 	/* Get the status */
 	UCSI_CMD_GET_CONNECTOR_STATUS(ctrl, con->num);
-- 
2.14.3

^ permalink raw reply related	[flat|nested] 43+ messages in thread

* [v5,02/12] usb: typec: Start using ERR_PTR
@ 2018-02-28 15:07   ` Hans de Goede
  0 siblings, 0 replies; 43+ messages in thread
From: Hans de Goede @ 2018-02-28 15:07 UTC (permalink / raw)
  To: Darren Hart, Andy Shevchenko, MyungJoo Ham, Chanwoo Choi,
	Mathias Nyman, Heikki Krogerus, Greg Kroah-Hartman,
	Guenter Roeck
  Cc: Hans de Goede, platform-driver-x86, linux-kernel, linux-usb

From: Heikki Krogerus <heikki.krogerus@linux.intel.com>

In order to allow the USB Type-C Class driver take care of
things like muxes and other possible dependencies for the
port drivers, returning ERR_PTR instead of NULL from the
registration functions in case of failure.

The reason for taking over control of the muxes for example
is because handling them in the port drivers would be just
boilerplate.

Signed-off-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Reviewed-by: Hans de Goede <hdegoede@redhat.com>
Reviewed-by: Guenter Roeck <linux@roeck-us.net>
Reviewed-by: Andy Shevchenko <andy.shevchenko@gmail.com>
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
---
Changes in v4:
-Add Andy's Reviewed-by

Changes in v3:
-Add Guenter's Reviewed-by

Changes in v2:
-Add IS_ERR_OR_NULL() checks to the unregister functions
---
 drivers/usb/typec/tcpm.c      | 16 +++++++++-------
 drivers/usb/typec/tps6598x.c  | 15 ++++++++-------
 drivers/usb/typec/typec.c     | 44 +++++++++++++++++++++----------------------
 drivers/usb/typec/ucsi/ucsi.c | 31 ++++++++++++++++++------------
 4 files changed, 58 insertions(+), 48 deletions(-)

diff --git a/drivers/usb/typec/tcpm.c b/drivers/usb/typec/tcpm.c
index f4d563ee7690..7cd28b700a7f 100644
--- a/drivers/usb/typec/tcpm.c
+++ b/drivers/usb/typec/tcpm.c
@@ -1044,7 +1044,7 @@ static int tcpm_pd_svdm(struct tcpm_port *port, const __le32 *payload, int cnt,
 		break;
 	case CMDT_RSP_ACK:
 		/* silently drop message if we are not connected */
-		if (!port->partner)
+		if (IS_ERR_OR_NULL(port->partner))
 			break;
 
 		switch (cmd) {
@@ -3743,8 +3743,8 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc)
 	port->port_type = tcpc->config->type;
 
 	port->typec_port = typec_register_port(port->dev, &port->typec_caps);
-	if (!port->typec_port) {
-		err = -ENOMEM;
+	if (IS_ERR(port->typec_port)) {
+		err = PTR_ERR(port->typec_port);
 		goto out_destroy_wq;
 	}
 
@@ -3753,15 +3753,17 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc)
 
 		i = 0;
 		while (paltmode->svid && i < ARRAY_SIZE(port->port_altmode)) {
-			port->port_altmode[i] =
-			  typec_port_register_altmode(port->typec_port,
-						      paltmode);
-			if (!port->port_altmode[i]) {
+			struct typec_altmode *alt;
+
+			alt = typec_port_register_altmode(port->typec_port,
+							  paltmode);
+			if (IS_ERR(alt)) {
 				tcpm_log(port,
 					 "%s: failed to register port alternate mode 0x%x",
 					 dev_name(dev), paltmode->svid);
 				break;
 			}
+			port->port_altmode[i] = alt;
 			i++;
 			paltmode++;
 		}
diff --git a/drivers/usb/typec/tps6598x.c b/drivers/usb/typec/tps6598x.c
index 2719f5d382f7..37a15c14a6c6 100644
--- a/drivers/usb/typec/tps6598x.c
+++ b/drivers/usb/typec/tps6598x.c
@@ -158,15 +158,15 @@ static int tps6598x_connect(struct tps6598x *tps, u32 status)
 		desc.identity = &tps->partner_identity;
 	}
 
-	tps->partner = typec_register_partner(tps->port, &desc);
-	if (!tps->partner)
-		return -ENODEV;
-
 	typec_set_pwr_opmode(tps->port, mode);
 	typec_set_pwr_role(tps->port, TPS_STATUS_PORTROLE(status));
 	typec_set_vconn_role(tps->port, TPS_STATUS_VCONN(status));
 	typec_set_data_role(tps->port, TPS_STATUS_DATAROLE(status));
 
+	tps->partner = typec_register_partner(tps->port, &desc);
+	if (IS_ERR(tps->partner))
+		return PTR_ERR(tps->partner);
+
 	if (desc.identity)
 		typec_partner_set_identity(tps->partner);
 
@@ -175,7 +175,8 @@ static int tps6598x_connect(struct tps6598x *tps, u32 status)
 
 static void tps6598x_disconnect(struct tps6598x *tps, u32 status)
 {
-	typec_unregister_partner(tps->partner);
+	if (!IS_ERR(tps->partner))
+		typec_unregister_partner(tps->partner);
 	tps->partner = NULL;
 	typec_set_pwr_opmode(tps->port, TYPEC_PWR_MODE_USB);
 	typec_set_pwr_role(tps->port, TPS_STATUS_PORTROLE(status));
@@ -418,8 +419,8 @@ static int tps6598x_probe(struct i2c_client *client)
 	tps->typec_cap.prefer_role = TYPEC_NO_PREFERRED_ROLE;
 
 	tps->port = typec_register_port(&client->dev, &tps->typec_cap);
-	if (!tps->port)
-		return -ENODEV;
+	if (IS_ERR(tps->port))
+		return PTR_ERR(tps->port);
 
 	if (status & TPS_STATUS_PLUG_PRESENT) {
 		ret = tps6598x_connect(tps, status);
diff --git a/drivers/usb/typec/typec.c b/drivers/usb/typec/typec.c
index 735726ced602..dc28ad868d93 100644
--- a/drivers/usb/typec/typec.c
+++ b/drivers/usb/typec/typec.c
@@ -386,7 +386,7 @@ typec_register_altmode(struct device *parent,
 
 	alt = kzalloc(sizeof(*alt), GFP_KERNEL);
 	if (!alt)
-		return NULL;
+		return ERR_PTR(-ENOMEM);
 
 	alt->svid = desc->svid;
 	alt->n_modes = desc->n_modes;
@@ -402,7 +402,7 @@ typec_register_altmode(struct device *parent,
 		dev_err(parent, "failed to register alternate mode (%d)\n",
 			ret);
 		put_device(&alt->dev);
-		return NULL;
+		return ERR_PTR(ret);
 	}
 
 	return alt;
@@ -417,7 +417,7 @@ typec_register_altmode(struct device *parent,
  */
 void typec_unregister_altmode(struct typec_altmode *alt)
 {
-	if (alt)
+	if (!IS_ERR_OR_NULL(alt))
 		device_unregister(&alt->dev);
 }
 EXPORT_SYMBOL_GPL(typec_unregister_altmode);
@@ -509,7 +509,7 @@ EXPORT_SYMBOL_GPL(typec_partner_register_altmode);
  *
  * Registers a device for USB Type-C Partner described in @desc.
  *
- * Returns handle to the partner on success or NULL on failure.
+ * Returns handle to the partner on success or ERR_PTR on failure.
  */
 struct typec_partner *typec_register_partner(struct typec_port *port,
 					     struct typec_partner_desc *desc)
@@ -519,7 +519,7 @@ struct typec_partner *typec_register_partner(struct typec_port *port,
 
 	partner = kzalloc(sizeof(*partner), GFP_KERNEL);
 	if (!partner)
-		return NULL;
+		return ERR_PTR(-ENOMEM);
 
 	partner->usb_pd = desc->usb_pd;
 	partner->accessory = desc->accessory;
@@ -542,7 +542,7 @@ struct typec_partner *typec_register_partner(struct typec_port *port,
 	if (ret) {
 		dev_err(&port->dev, "failed to register partner (%d)\n", ret);
 		put_device(&partner->dev);
-		return NULL;
+		return ERR_PTR(ret);
 	}
 
 	return partner;
@@ -557,7 +557,7 @@ EXPORT_SYMBOL_GPL(typec_register_partner);
  */
 void typec_unregister_partner(struct typec_partner *partner)
 {
-	if (partner)
+	if (!IS_ERR_OR_NULL(partner))
 		device_unregister(&partner->dev);
 }
 EXPORT_SYMBOL_GPL(typec_unregister_partner);
@@ -587,7 +587,7 @@ static const struct device_type typec_plug_dev_type = {
  * the plug lists in response to Discover Modes command need to be listed in an
  * array in @desc.
  *
- * Returns handle to the alternate mode on success or NULL on failure.
+ * Returns handle to the alternate mode on success or ERR_PTR on failure.
  */
 struct typec_altmode *
 typec_plug_register_altmode(struct typec_plug *plug,
@@ -606,7 +606,7 @@ EXPORT_SYMBOL_GPL(typec_plug_register_altmode);
  * Cable Plug represents a plug with electronics in it that can response to USB
  * Power Delivery SOP Prime or SOP Double Prime packages.
  *
- * Returns handle to the cable plug on success or NULL on failure.
+ * Returns handle to the cable plug on success or ERR_PTR on failure.
  */
 struct typec_plug *typec_register_plug(struct typec_cable *cable,
 				       struct typec_plug_desc *desc)
@@ -617,7 +617,7 @@ struct typec_plug *typec_register_plug(struct typec_cable *cable,
 
 	plug = kzalloc(sizeof(*plug), GFP_KERNEL);
 	if (!plug)
-		return NULL;
+		return ERR_PTR(-ENOMEM);
 
 	sprintf(name, "plug%d", desc->index);
 
@@ -631,7 +631,7 @@ struct typec_plug *typec_register_plug(struct typec_cable *cable,
 	if (ret) {
 		dev_err(&cable->dev, "failed to register plug (%d)\n", ret);
 		put_device(&plug->dev);
-		return NULL;
+		return ERR_PTR(ret);
 	}
 
 	return plug;
@@ -646,7 +646,7 @@ EXPORT_SYMBOL_GPL(typec_register_plug);
  */
 void typec_unregister_plug(struct typec_plug *plug)
 {
-	if (plug)
+	if (!IS_ERR_OR_NULL(plug))
 		device_unregister(&plug->dev);
 }
 EXPORT_SYMBOL_GPL(typec_unregister_plug);
@@ -724,7 +724,7 @@ EXPORT_SYMBOL_GPL(typec_cable_set_identity);
  * Registers a device for USB Type-C Cable described in @desc. The cable will be
  * parent for the optional cable plug devises.
  *
- * Returns handle to the cable on success or NULL on failure.
+ * Returns handle to the cable on success or ERR_PTR on failure.
  */
 struct typec_cable *typec_register_cable(struct typec_port *port,
 					 struct typec_cable_desc *desc)
@@ -734,7 +734,7 @@ struct typec_cable *typec_register_cable(struct typec_port *port,
 
 	cable = kzalloc(sizeof(*cable), GFP_KERNEL);
 	if (!cable)
-		return NULL;
+		return ERR_PTR(-ENOMEM);
 
 	cable->type = desc->type;
 	cable->active = desc->active;
@@ -757,7 +757,7 @@ struct typec_cable *typec_register_cable(struct typec_port *port,
 	if (ret) {
 		dev_err(&port->dev, "failed to register cable (%d)\n", ret);
 		put_device(&cable->dev);
-		return NULL;
+		return ERR_PTR(ret);
 	}
 
 	return cable;
@@ -772,7 +772,7 @@ EXPORT_SYMBOL_GPL(typec_register_cable);
  */
 void typec_unregister_cable(struct typec_cable *cable)
 {
-	if (cable)
+	if (!IS_ERR_OR_NULL(cable))
 		device_unregister(&cable->dev);
 }
 EXPORT_SYMBOL_GPL(typec_unregister_cable);
@@ -1256,7 +1256,7 @@ EXPORT_SYMBOL_GPL(typec_set_pwr_opmode);
  * This routine is used to register an alternate mode that @port is capable of
  * supporting.
  *
- * Returns handle to the alternate mode on success or NULL on failure.
+ * Returns handle to the alternate mode on success or ERR_PTR on failure.
  */
 struct typec_altmode *
 typec_port_register_altmode(struct typec_port *port,
@@ -1273,7 +1273,7 @@ EXPORT_SYMBOL_GPL(typec_port_register_altmode);
  *
  * Registers a device for USB Type-C Port described in @cap.
  *
- * Returns handle to the port on success or NULL on failure.
+ * Returns handle to the port on success or ERR_PTR on failure.
  */
 struct typec_port *typec_register_port(struct device *parent,
 				       const struct typec_capability *cap)
@@ -1285,12 +1285,12 @@ struct typec_port *typec_register_port(struct device *parent,
 
 	port = kzalloc(sizeof(*port), GFP_KERNEL);
 	if (!port)
-		return NULL;
+		return ERR_PTR(-ENOMEM);
 
 	id = ida_simple_get(&typec_index_ida, 0, 0, GFP_KERNEL);
 	if (id < 0) {
 		kfree(port);
-		return NULL;
+		return ERR_PTR(id);
 	}
 
 	if (cap->type == TYPEC_PORT_DFP)
@@ -1326,7 +1326,7 @@ struct typec_port *typec_register_port(struct device *parent,
 	if (ret) {
 		dev_err(parent, "failed to register port (%d)\n", ret);
 		put_device(&port->dev);
-		return NULL;
+		return ERR_PTR(ret);
 	}
 
 	return port;
@@ -1341,7 +1341,7 @@ EXPORT_SYMBOL_GPL(typec_register_port);
  */
 void typec_unregister_port(struct typec_port *port)
 {
-	if (port)
+	if (!IS_ERR_OR_NULL(port))
 		device_unregister(&port->dev);
 }
 EXPORT_SYMBOL_GPL(typec_unregister_port);
diff --git a/drivers/usb/typec/ucsi/ucsi.c b/drivers/usb/typec/ucsi/ucsi.c
index 79046fe66426..69d544cfcd45 100644
--- a/drivers/usb/typec/ucsi/ucsi.c
+++ b/drivers/usb/typec/ucsi/ucsi.c
@@ -260,38 +260,45 @@ static void ucsi_pwr_opmode_change(struct ucsi_connector *con)
 
 static int ucsi_register_partner(struct ucsi_connector *con)
 {
-	struct typec_partner_desc partner;
+	struct typec_partner_desc desc;
+	struct typec_partner *partner;
 
 	if (con->partner)
 		return 0;
 
-	memset(&partner, 0, sizeof(partner));
+	memset(&desc, 0, sizeof(desc));
 
 	switch (con->status.partner_type) {
 	case UCSI_CONSTAT_PARTNER_TYPE_DEBUG:
-		partner.accessory = TYPEC_ACCESSORY_DEBUG;
+		desc.accessory = TYPEC_ACCESSORY_DEBUG;
 		break;
 	case UCSI_CONSTAT_PARTNER_TYPE_AUDIO:
-		partner.accessory = TYPEC_ACCESSORY_AUDIO;
+		desc.accessory = TYPEC_ACCESSORY_AUDIO;
 		break;
 	default:
 		break;
 	}
 
-	partner.usb_pd = con->status.pwr_op_mode == UCSI_CONSTAT_PWR_OPMODE_PD;
+	desc.usb_pd = con->status.pwr_op_mode == UCSI_CONSTAT_PWR_OPMODE_PD;
 
-	con->partner = typec_register_partner(con->port, &partner);
-	if (!con->partner) {
-		dev_err(con->ucsi->dev, "con%d: failed to register partner\n",
-			con->num);
-		return -ENODEV;
+	partner = typec_register_partner(con->port, &desc);
+	if (IS_ERR(partner)) {
+		dev_err(con->ucsi->dev,
+			"con%d: failed to register partner (%ld)\n", con->num,
+			PTR_ERR(partner));
+		return PTR_ERR(partner);
 	}
 
+	con->partner = partner;
+
 	return 0;
 }
 
 static void ucsi_unregister_partner(struct ucsi_connector *con)
 {
+	if (!con->partner)
+		return;
+
 	typec_unregister_partner(con->partner);
 	con->partner = NULL;
 }
@@ -606,8 +613,8 @@ static int ucsi_register_port(struct ucsi *ucsi, int index)
 
 	/* Register the connector */
 	con->port = typec_register_port(ucsi->dev, cap);
-	if (!con->port)
-		return -ENODEV;
+	if (IS_ERR(con->port))
+		return PTR_ERR(con->port);
 
 	/* Get the status */
 	UCSI_CMD_GET_CONNECTOR_STATUS(ctrl, con->num);

^ permalink raw reply related	[flat|nested] 43+ messages in thread

* [PATCH v5 03/12] usb: typec: API for controlling USB Type-C Multiplexers
@ 2018-02-28 15:07   ` Hans de Goede
  0 siblings, 0 replies; 43+ messages in thread
From: Hans de Goede @ 2018-02-28 15:07 UTC (permalink / raw)
  To: Darren Hart, Andy Shevchenko, MyungJoo Ham, Chanwoo Choi,
	Mathias Nyman, Heikki Krogerus, Greg Kroah-Hartman,
	Guenter Roeck
  Cc: Hans de Goede, platform-driver-x86, linux-kernel, linux-usb

From: Heikki Krogerus <heikki.krogerus@linux.intel.com>

USB Type-C connectors consist of various muxes and switches
that route the pins on the connector to the right locations.
The USB Type-C drivers need to be able to control the muxes,
as they are the ones that know things like the cable plug
orientation, and the current mode that was negotiated with
the partner.

This introduces a small API for registering and controlling
cable plug orientation switches, and separate small API for
registering and controlling pin multiplexer/demultiplexer
switches that are needed with Accessory/Alternate Modes.

Signed-off-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Reviewed-by: Hans de Goede <hdegoede@redhat.com>
Reviewed-by: Andy Shevchenko <andy.shevchenko@gmail.com>
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
---
Changes in v4:
-Add Andy's Reviewed-by

Changes in v3:
-Add #include-s for a few missing headers to drivers/usb/typec/mux.c
-Various spelling and gramar fixes in the docs pointed out by Randy Dunlap
---
 Documentation/driver-api/usb/typec.rst |  73 +++++++++++--
 drivers/usb/typec/Makefile             |   1 +
 drivers/usb/typec/{typec.c => class.c} |  70 ++++++++++++
 drivers/usb/typec/mux.c                | 190 +++++++++++++++++++++++++++++++++
 include/linux/usb/typec.h              |  14 +++
 include/linux/usb/typec_mux.h          |  55 ++++++++++
 6 files changed, 392 insertions(+), 11 deletions(-)
 rename drivers/usb/typec/{typec.c => class.c} (95%)
 create mode 100644 drivers/usb/typec/mux.c
 create mode 100644 include/linux/usb/typec_mux.h

diff --git a/Documentation/driver-api/usb/typec.rst b/Documentation/driver-api/usb/typec.rst
index 8a7249f2ff04..feb31946490b 100644
--- a/Documentation/driver-api/usb/typec.rst
+++ b/Documentation/driver-api/usb/typec.rst
@@ -61,7 +61,7 @@ Registering the ports
 The port drivers will describe every Type-C port they control with struct
 typec_capability data structure, and register them with the following API:
 
-.. kernel-doc:: drivers/usb/typec/typec.c
+.. kernel-doc:: drivers/usb/typec/class.c
    :functions: typec_register_port typec_unregister_port
 
 When registering the ports, the prefer_role member in struct typec_capability
@@ -80,7 +80,7 @@ typec_partner_desc. The class copies the details of the partner during
 registration. The class offers the following API for registering/unregistering
 partners.
 
-.. kernel-doc:: drivers/usb/typec/typec.c
+.. kernel-doc:: drivers/usb/typec/class.c
    :functions: typec_register_partner typec_unregister_partner
 
 The class will provide a handle to struct typec_partner if the registration was
@@ -92,7 +92,7 @@ should include handle to struct usb_pd_identity instance. The class will then
 create a sysfs directory for the identity under the partner device. The result
 of Discover Identity command can then be reported with the following API:
 
-.. kernel-doc:: drivers/usb/typec/typec.c
+.. kernel-doc:: drivers/usb/typec/class.c
    :functions: typec_partner_set_identity
 
 Registering Cables
@@ -113,7 +113,7 @@ typec_cable_desc and about a plug in struct typec_plug_desc. The class copies
 the details during registration. The class offers the following API for
 registering/unregistering cables and their plugs:
 
-.. kernel-doc:: drivers/usb/typec/typec.c
+.. kernel-doc:: drivers/usb/typec/class.c
    :functions: typec_register_cable typec_unregister_cable typec_register_plug typec_unregister_plug
 
 The class will provide a handle to struct typec_cable and struct typec_plug if
@@ -125,7 +125,7 @@ include handle to struct usb_pd_identity instance. The class will then create a
 sysfs directory for the identity under the cable device. The result of Discover
 Identity command can then be reported with the following API:
 
-.. kernel-doc:: drivers/usb/typec/typec.c
+.. kernel-doc:: drivers/usb/typec/class.c
    :functions: typec_cable_set_identity
 
 Notifications
@@ -135,7 +135,7 @@ When the partner has executed a role change, or when the default roles change
 during connection of a partner or cable, the port driver must use the following
 APIs to report it to the class:
 
-.. kernel-doc:: drivers/usb/typec/typec.c
+.. kernel-doc:: drivers/usb/typec/class.c
    :functions: typec_set_data_role typec_set_pwr_role typec_set_vconn_role typec_set_pwr_opmode
 
 Alternate Modes
@@ -150,7 +150,7 @@ and struct typec_altmode_desc which is a container for all the supported modes.
 Ports that support Alternate Modes need to register each SVID they support with
 the following API:
 
-.. kernel-doc:: drivers/usb/typec/typec.c
+.. kernel-doc:: drivers/usb/typec/class.c
    :functions: typec_port_register_altmode
 
 If a partner or cable plug provides a list of SVIDs as response to USB Power
@@ -159,12 +159,12 @@ registered.
 
 API for the partners:
 
-.. kernel-doc:: drivers/usb/typec/typec.c
+.. kernel-doc:: drivers/usb/typec/class.c
    :functions: typec_partner_register_altmode
 
 API for the Cable Plugs:
 
-.. kernel-doc:: drivers/usb/typec/typec.c
+.. kernel-doc:: drivers/usb/typec/class.c
    :functions: typec_plug_register_altmode
 
 So ports, partners and cable plugs will register the alternate modes with their
@@ -172,11 +172,62 @@ own functions, but the registration will always return a handle to struct
 typec_altmode on success, or NULL. The unregistration will happen with the same
 function:
 
-.. kernel-doc:: drivers/usb/typec/typec.c
+.. kernel-doc:: drivers/usb/typec/class.c
    :functions: typec_unregister_altmode
 
 If a partner or cable plug enters or exits a mode, the port driver needs to
 notify the class with the following API:
 
-.. kernel-doc:: drivers/usb/typec/typec.c
+.. kernel-doc:: drivers/usb/typec/class.c
    :functions: typec_altmode_update_active
+
+Multiplexer/DeMultiplexer Switches
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+USB Type-C connectors may have one or more mux/demux switches behind them. Since
+the plugs can be inserted right-side-up or upside-down, a switch is needed to
+route the correct data pairs from the connector to the USB controllers. If
+Alternate or Accessory Modes are supported, another switch is needed that can
+route the pins on the connector to some other component besides USB. USB Type-C
+Connector Class supplies an API for registering those switches.
+
+.. kernel-doc:: drivers/usb/typec/mux.c
+   :functions: typec_switch_register typec_switch_unregister typec_mux_register typec_mux_unregister
+
+In most cases the same physical mux will handle both the orientation and mode.
+However, as the port drivers will be responsible for the orientation, and the
+alternate mode drivers for the mode, the two are always separated into their
+own logical components: "mux" for the mode and "switch" for the orientation.
+
+When a port is registered, USB Type-C Connector Class requests both the mux and
+the switch for the port. The drivers can then use the following API for
+controlling them:
+
+.. kernel-doc:: drivers/usb/typec/class.c
+   :functions: typec_set_orientation typec_set_mode
+
+If the connector is dual-role capable, there may also be a switch for the data
+role. USB Type-C Connector Class does not supply separate API for them. The
+port drivers can use USB Role Class API with those.
+
+Illustration of the muxes behind a connector that supports an alternate mode:
+
+                     ------------------------
+                     |       Connector      |
+                     ------------------------
+                            |         |
+                     ------------------------
+                      \     Orientation    /
+                       --------------------
+                                |
+                       --------------------
+                      /        Mode        \
+                     ------------------------
+                         /              \
+      ------------------------        --------------------
+      |       Alt Mode       |       /      USB Role      \
+      ------------------------      ------------------------
+                                         /            \
+                     ------------------------      ------------------------
+                     |       USB Host       |      |       USB Device     |
+                     ------------------------      ------------------------
diff --git a/drivers/usb/typec/Makefile b/drivers/usb/typec/Makefile
index bb3138a6eaac..56b2e9516ec1 100644
--- a/drivers/usb/typec/Makefile
+++ b/drivers/usb/typec/Makefile
@@ -1,5 +1,6 @@
 # SPDX-License-Identifier: GPL-2.0
 obj-$(CONFIG_TYPEC)		+= typec.o
+typec-y				:= class.o mux.o
 obj-$(CONFIG_TYPEC_TCPM)	+= tcpm.o
 obj-y				+= fusb302/
 obj-$(CONFIG_TYPEC_WCOVE)	+= typec_wcove.o
diff --git a/drivers/usb/typec/typec.c b/drivers/usb/typec/class.c
similarity index 95%
rename from drivers/usb/typec/typec.c
rename to drivers/usb/typec/class.c
index dc28ad868d93..2620a694704f 100644
--- a/drivers/usb/typec/typec.c
+++ b/drivers/usb/typec/class.c
@@ -11,6 +11,7 @@
 #include <linux/mutex.h>
 #include <linux/slab.h>
 #include <linux/usb/typec.h>
+#include <linux/usb/typec_mux.h>
 
 struct typec_mode {
 	int				index;
@@ -70,6 +71,10 @@ struct typec_port {
 	enum typec_port_type		port_type;
 	struct mutex			port_type_lock;
 
+	enum typec_orientation		orientation;
+	struct typec_switch		*sw;
+	struct typec_mux		*mux;
+
 	const struct typec_capability	*cap;
 };
 
@@ -92,6 +97,7 @@ static const struct device_type typec_port_dev_type;
 static DEFINE_IDA(typec_index_ida);
 static struct class *typec_class;
 
+/* ------------------------------------------------------------------------- */
 /* Common attributes */
 
 static const char * const typec_accessory_modes[] = {
@@ -1137,6 +1143,8 @@ static void typec_release(struct device *dev)
 	struct typec_port *port = to_typec_port(dev);
 
 	ida_simple_remove(&typec_index_ida, port->id);
+	typec_switch_put(port->sw);
+	typec_mux_put(port->mux);
 	kfree(port);
 }
 
@@ -1246,6 +1254,47 @@ void typec_set_pwr_opmode(struct typec_port *port,
 }
 EXPORT_SYMBOL_GPL(typec_set_pwr_opmode);
 
+/* ------------------------------------------ */
+/* API for Multiplexer/DeMultiplexer Switches */
+
+/**
+ * typec_set_orientation - Set USB Type-C cable plug orientation
+ * @port: USB Type-C Port
+ * @orientation: USB Type-C cable plug orientation
+ *
+ * Set cable plug orientation for @port.
+ */
+int typec_set_orientation(struct typec_port *port,
+			  enum typec_orientation orientation)
+{
+	int ret;
+
+	if (port->sw) {
+		ret = port->sw->set(port->sw, orientation);
+		if (ret)
+			return ret;
+	}
+
+	port->orientation = orientation;
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(typec_set_orientation);
+
+/**
+ * typec_set_mode - Set mode of operation for USB Type-C connector
+ * @port: USB Type-C port for the connector
+ * @mode: Operation mode for the connector
+ *
+ * Set mode @mode for @port. This function will configure the muxes needed to
+ * enter @mode.
+ */
+int typec_set_mode(struct typec_port *port, int mode)
+{
+	return port->mux ? port->mux->set(port->mux, mode) : 0;
+}
+EXPORT_SYMBOL_GPL(typec_set_mode);
+
 /* --------------------------------------- */
 
 /**
@@ -1293,6 +1342,18 @@ struct typec_port *typec_register_port(struct device *parent,
 		return ERR_PTR(id);
 	}
 
+	port->sw = typec_switch_get(cap->fwnode ? &port->dev : parent);
+	if (IS_ERR(port->sw)) {
+		ret = PTR_ERR(port->sw);
+		goto err_switch;
+	}
+
+	port->mux = typec_mux_get(cap->fwnode ? &port->dev : parent);
+	if (IS_ERR(port->mux)) {
+		ret = PTR_ERR(port->mux);
+		goto err_mux;
+	}
+
 	if (cap->type == TYPEC_PORT_DFP)
 		role = TYPEC_SOURCE;
 	else if (cap->type == TYPEC_PORT_UFP)
@@ -1330,6 +1391,15 @@ struct typec_port *typec_register_port(struct device *parent,
 	}
 
 	return port;
+
+err_mux:
+	typec_switch_put(port->sw);
+
+err_switch:
+	ida_simple_remove(&typec_index_ida, port->id);
+	kfree(port);
+
+	return ERR_PTR(ret);
 }
 EXPORT_SYMBOL_GPL(typec_register_port);
 
diff --git a/drivers/usb/typec/mux.c b/drivers/usb/typec/mux.c
new file mode 100644
index 000000000000..efd4079a96f2
--- /dev/null
+++ b/drivers/usb/typec/mux.c
@@ -0,0 +1,190 @@
+// SPDX-License-Identifier: GPL-2.0
+/**
+ * USB Type-C Multiplexer/DeMultiplexer Switch support
+ *
+ * Copyright (C) 2018 Intel Corporation
+ * Author: Heikki Krogerus <heikki.krogerus@linux.intel.com>
+ *         Hans de Goede <hdegoede@redhat.com>
+ */
+
+#include <linux/connection.h>
+#include <linux/device.h>
+#include <linux/list.h>
+#include <linux/mutex.h>
+#include <linux/usb/typec_mux.h>
+
+static DEFINE_MUTEX(switch_lock);
+static DEFINE_MUTEX(mux_lock);
+static LIST_HEAD(switch_list);
+static LIST_HEAD(mux_list);
+
+static void *typec_switch_match(struct devcon *con, int ep, void *data)
+{
+	struct typec_switch *sw;
+
+	list_for_each_entry(sw, &switch_list, entry)
+		if (!strcmp(con->endpoint[ep], dev_name(sw->dev)))
+			return sw;
+
+	/*
+	 * We only get called if a connection was found, tell the caller to
+	 * wait for the switch to show up.
+	 */
+	return ERR_PTR(-EPROBE_DEFER);
+}
+
+/**
+ * typec_switch_get - Find USB Type-C orientation switch
+ * @dev: The caller device
+ *
+ * Finds a switch linked with @dev. Returns a reference to the switch on
+ * success, NULL if no matching connection was found, or
+ * ERR_PTR(-EPROBE_DEFER) when a connection was found but the switch
+ * has not been enumerated yet.
+ */
+struct typec_switch *typec_switch_get(struct device *dev)
+{
+	struct typec_switch *sw;
+
+	mutex_lock(&switch_lock);
+	sw = __device_find_connection(dev, "typec-switch", NULL,
+				      typec_switch_match);
+	if (!IS_ERR_OR_NULL(sw))
+		get_device(sw->dev);
+	mutex_unlock(&switch_lock);
+
+	return sw;
+}
+EXPORT_SYMBOL_GPL(typec_switch_get);
+
+/**
+ * typec_put_switch - Release USB Type-C orientation switch
+ * @sw: USB Type-C orientation switch
+ *
+ * Decrement reference count for @sw.
+ */
+void typec_switch_put(struct typec_switch *sw)
+{
+	if (!IS_ERR_OR_NULL(sw))
+		put_device(sw->dev);
+}
+EXPORT_SYMBOL_GPL(typec_switch_put);
+
+/**
+ * typec_switch_register - Register USB Type-C orientation switch
+ * @sw: USB Type-C orientation switch
+ *
+ * This function registers a switch that can be used for routing the correct
+ * data pairs depending on the cable plug orientation from the USB Type-C
+ * connector to the USB controllers. USB Type-C plugs can be inserted
+ * right-side-up or upside-down.
+ */
+int typec_switch_register(struct typec_switch *sw)
+{
+	mutex_lock(&switch_lock);
+	list_add_tail(&sw->entry, &switch_list);
+	mutex_unlock(&switch_lock);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(typec_switch_register);
+
+/**
+ * typec_switch_unregister - Unregister USB Type-C orientation switch
+ * @sw: USB Type-C orientation switch
+ *
+ * Unregister switch that was registered with typec_switch_register().
+ */
+void typec_switch_unregister(struct typec_switch *sw)
+{
+	mutex_lock(&switch_lock);
+	list_del(&sw->entry);
+	mutex_unlock(&switch_lock);
+}
+EXPORT_SYMBOL_GPL(typec_switch_unregister);
+
+/* ------------------------------------------------------------------------- */
+
+static void *typec_mux_match(struct devcon *con, int ep, void *data)
+{
+	struct typec_mux *mux;
+
+	list_for_each_entry(mux, &mux_list, entry)
+		if (!strcmp(con->endpoint[ep], dev_name(mux->dev)))
+			return mux;
+
+	/*
+	 * We only get called if a connection was found, tell the caller to
+	 * wait for the switch to show up.
+	 */
+	return ERR_PTR(-EPROBE_DEFER);
+}
+
+/**
+ * typec_mux_get - Find USB Type-C Multiplexer
+ * @dev: The caller device
+ *
+ * Finds a mux linked to the caller. This function is primarily meant for the
+ * Type-C drivers. Returns a reference to the mux on success, NULL if no
+ * matching connection was found, or ERR_PTR(-EPROBE_DEFER) when a connection
+ * was found but the mux has not been enumerated yet.
+ */
+struct typec_mux *typec_mux_get(struct device *dev)
+{
+	struct typec_mux *mux;
+
+	mutex_lock(&mux_lock);
+	mux = __device_find_connection(dev, "typec-mux", NULL, typec_mux_match);
+	if (!IS_ERR_OR_NULL(mux))
+		get_device(mux->dev);
+	mutex_unlock(&mux_lock);
+
+	return mux;
+}
+EXPORT_SYMBOL_GPL(typec_mux_get);
+
+/**
+ * typec_mux_put - Release handle to a Multiplexer
+ * @mux: USB Type-C Connector Multiplexer/DeMultiplexer
+ *
+ * Decrements reference count for @mux.
+ */
+void typec_mux_put(struct typec_mux *mux)
+{
+	if (!IS_ERR_OR_NULL(mux))
+		put_device(mux->dev);
+}
+EXPORT_SYMBOL_GPL(typec_mux_put);
+
+/**
+ * typec_mux_register - Register Multiplexer routing USB Type-C pins
+ * @mux: USB Type-C Connector Multiplexer/DeMultiplexer
+ *
+ * USB Type-C connectors can be used for alternate modes of operation besides
+ * USB when Accessory/Alternate Modes are supported. With some of those modes,
+ * the pins on the connector need to be reconfigured. This function registers
+ * multiplexer switches routing the pins on the connector.
+ */
+int typec_mux_register(struct typec_mux *mux)
+{
+	mutex_lock(&mux_lock);
+	list_add_tail(&mux->entry, &mux_list);
+	mutex_unlock(&mux_lock);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(typec_mux_register);
+
+/**
+ * typec_mux_unregister - Unregister Multiplexer Switch
+ * @sw: USB Type-C Connector Multiplexer/DeMultiplexer
+ *
+ * Unregister mux that was registered with typec_mux_register().
+ */
+void typec_mux_unregister(struct typec_mux *mux)
+{
+	mutex_lock(&mux_lock);
+	list_del(&mux->entry);
+	mutex_unlock(&mux_lock);
+}
+EXPORT_SYMBOL_GPL(typec_mux_unregister);
diff --git a/include/linux/usb/typec.h b/include/linux/usb/typec.h
index 0d44ce6af08f..2408e5c2ed91 100644
--- a/include/linux/usb/typec.h
+++ b/include/linux/usb/typec.h
@@ -60,6 +60,12 @@ enum typec_accessory {
 
 #define TYPEC_MAX_ACCESSORY	3
 
+enum typec_orientation {
+	TYPEC_ORIENTATION_NONE,
+	TYPEC_ORIENTATION_NORMAL,
+	TYPEC_ORIENTATION_REVERSE,
+};
+
 /*
  * struct usb_pd_identity - USB Power Delivery identity data
  * @id_header: ID Header VDO
@@ -185,6 +191,8 @@ struct typec_partner_desc {
  * @pd_revision: USB Power Delivery Specification revision if supported
  * @prefer_role: Initial role preference
  * @accessory: Supported Accessory Modes
+ * @sw: Cable plug orientation switch
+ * @mux: Multiplexer switch for Alternate/Accessory Modes
  * @fwnode: Optional fwnode of the port
  * @try_role: Set data role preference for DRP port
  * @dr_set: Set Data Role
@@ -202,6 +210,8 @@ struct typec_capability {
 	int			prefer_role;
 	enum typec_accessory	accessory[TYPEC_MAX_ACCESSORY];
 
+	struct typec_switch	*sw;
+	struct typec_mux	*mux;
 	struct fwnode_handle	*fwnode;
 
 	int		(*try_role)(const struct typec_capability *,
@@ -245,4 +255,8 @@ void typec_set_pwr_role(struct typec_port *port, enum typec_role role);
 void typec_set_vconn_role(struct typec_port *port, enum typec_role role);
 void typec_set_pwr_opmode(struct typec_port *port, enum typec_pwr_opmode mode);
 
+int typec_set_orientation(struct typec_port *port,
+			  enum typec_orientation orientation);
+int typec_set_mode(struct typec_port *port, int mode);
+
 #endif /* __LINUX_USB_TYPEC_H */
diff --git a/include/linux/usb/typec_mux.h b/include/linux/usb/typec_mux.h
new file mode 100644
index 000000000000..12c1b057834b
--- /dev/null
+++ b/include/linux/usb/typec_mux.h
@@ -0,0 +1,55 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#ifndef __USB_TYPEC_MUX
+#define __USB_TYPEC_MUX
+
+#include <linux/list.h>
+#include <linux/usb/typec.h>
+
+struct device;
+
+/**
+ * struct typec_switch - USB Type-C cable orientation switch
+ * @dev: Switch device
+ * @entry: List entry
+ * @set: Callback to the driver for setting the orientation
+ *
+ * USB Type-C pin flipper switch routing the correct data pairs from the
+ * connector to the USB controller depending on the orientation of the cable
+ * plug.
+ */
+struct typec_switch {
+	struct device *dev;
+	struct list_head entry;
+
+	int (*set)(struct typec_switch *sw, enum typec_orientation orientation);
+};
+
+/**
+ * struct typec_switch - USB Type-C connector pin mux
+ * @dev: Mux device
+ * @entry: List entry
+ * @set: Callback to the driver for setting the state of the mux
+ *
+ * Pin Multiplexer/DeMultiplexer switch routing the USB Type-C connector pins to
+ * different components depending on the requested mode of operation. Used with
+ * Accessory/Alternate modes.
+ */
+struct typec_mux {
+	struct device *dev;
+	struct list_head entry;
+
+	int (*set)(struct typec_mux *mux, int state);
+};
+
+struct typec_switch *typec_switch_get(struct device *dev);
+void typec_switch_put(struct typec_switch *sw);
+int typec_switch_register(struct typec_switch *sw);
+void typec_switch_unregister(struct typec_switch *sw);
+
+struct typec_mux *typec_mux_get(struct device *dev);
+void typec_mux_put(struct typec_mux *mux);
+int typec_mux_register(struct typec_mux *mux);
+void typec_mux_unregister(struct typec_mux *mux);
+
+#endif /* __USB_TYPEC_MUX */
-- 
2.14.3

^ permalink raw reply related	[flat|nested] 43+ messages in thread

* [v5,03/12] usb: typec: API for controlling USB Type-C Multiplexers
@ 2018-02-28 15:07   ` Hans de Goede
  0 siblings, 0 replies; 43+ messages in thread
From: Hans de Goede @ 2018-02-28 15:07 UTC (permalink / raw)
  To: Darren Hart, Andy Shevchenko, MyungJoo Ham, Chanwoo Choi,
	Mathias Nyman, Heikki Krogerus, Greg Kroah-Hartman,
	Guenter Roeck
  Cc: Hans de Goede, platform-driver-x86, linux-kernel, linux-usb

From: Heikki Krogerus <heikki.krogerus@linux.intel.com>

USB Type-C connectors consist of various muxes and switches
that route the pins on the connector to the right locations.
The USB Type-C drivers need to be able to control the muxes,
as they are the ones that know things like the cable plug
orientation, and the current mode that was negotiated with
the partner.

This introduces a small API for registering and controlling
cable plug orientation switches, and separate small API for
registering and controlling pin multiplexer/demultiplexer
switches that are needed with Accessory/Alternate Modes.

Signed-off-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Reviewed-by: Hans de Goede <hdegoede@redhat.com>
Reviewed-by: Andy Shevchenko <andy.shevchenko@gmail.com>
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
---
Changes in v4:
-Add Andy's Reviewed-by

Changes in v3:
-Add #include-s for a few missing headers to drivers/usb/typec/mux.c
-Various spelling and gramar fixes in the docs pointed out by Randy Dunlap
---
 Documentation/driver-api/usb/typec.rst |  73 +++++++++++--
 drivers/usb/typec/Makefile             |   1 +
 drivers/usb/typec/{typec.c => class.c} |  70 ++++++++++++
 drivers/usb/typec/mux.c                | 190 +++++++++++++++++++++++++++++++++
 include/linux/usb/typec.h              |  14 +++
 include/linux/usb/typec_mux.h          |  55 ++++++++++
 6 files changed, 392 insertions(+), 11 deletions(-)
 rename drivers/usb/typec/{typec.c => class.c} (95%)
 create mode 100644 drivers/usb/typec/mux.c
 create mode 100644 include/linux/usb/typec_mux.h

diff --git a/Documentation/driver-api/usb/typec.rst b/Documentation/driver-api/usb/typec.rst
index 8a7249f2ff04..feb31946490b 100644
--- a/Documentation/driver-api/usb/typec.rst
+++ b/Documentation/driver-api/usb/typec.rst
@@ -61,7 +61,7 @@ Registering the ports
 The port drivers will describe every Type-C port they control with struct
 typec_capability data structure, and register them with the following API:
 
-.. kernel-doc:: drivers/usb/typec/typec.c
+.. kernel-doc:: drivers/usb/typec/class.c
    :functions: typec_register_port typec_unregister_port
 
 When registering the ports, the prefer_role member in struct typec_capability
@@ -80,7 +80,7 @@ typec_partner_desc. The class copies the details of the partner during
 registration. The class offers the following API for registering/unregistering
 partners.
 
-.. kernel-doc:: drivers/usb/typec/typec.c
+.. kernel-doc:: drivers/usb/typec/class.c
    :functions: typec_register_partner typec_unregister_partner
 
 The class will provide a handle to struct typec_partner if the registration was
@@ -92,7 +92,7 @@ should include handle to struct usb_pd_identity instance. The class will then
 create a sysfs directory for the identity under the partner device. The result
 of Discover Identity command can then be reported with the following API:
 
-.. kernel-doc:: drivers/usb/typec/typec.c
+.. kernel-doc:: drivers/usb/typec/class.c
    :functions: typec_partner_set_identity
 
 Registering Cables
@@ -113,7 +113,7 @@ typec_cable_desc and about a plug in struct typec_plug_desc. The class copies
 the details during registration. The class offers the following API for
 registering/unregistering cables and their plugs:
 
-.. kernel-doc:: drivers/usb/typec/typec.c
+.. kernel-doc:: drivers/usb/typec/class.c
    :functions: typec_register_cable typec_unregister_cable typec_register_plug typec_unregister_plug
 
 The class will provide a handle to struct typec_cable and struct typec_plug if
@@ -125,7 +125,7 @@ include handle to struct usb_pd_identity instance. The class will then create a
 sysfs directory for the identity under the cable device. The result of Discover
 Identity command can then be reported with the following API:
 
-.. kernel-doc:: drivers/usb/typec/typec.c
+.. kernel-doc:: drivers/usb/typec/class.c
    :functions: typec_cable_set_identity
 
 Notifications
@@ -135,7 +135,7 @@ When the partner has executed a role change, or when the default roles change
 during connection of a partner or cable, the port driver must use the following
 APIs to report it to the class:
 
-.. kernel-doc:: drivers/usb/typec/typec.c
+.. kernel-doc:: drivers/usb/typec/class.c
    :functions: typec_set_data_role typec_set_pwr_role typec_set_vconn_role typec_set_pwr_opmode
 
 Alternate Modes
@@ -150,7 +150,7 @@ and struct typec_altmode_desc which is a container for all the supported modes.
 Ports that support Alternate Modes need to register each SVID they support with
 the following API:
 
-.. kernel-doc:: drivers/usb/typec/typec.c
+.. kernel-doc:: drivers/usb/typec/class.c
    :functions: typec_port_register_altmode
 
 If a partner or cable plug provides a list of SVIDs as response to USB Power
@@ -159,12 +159,12 @@ registered.
 
 API for the partners:
 
-.. kernel-doc:: drivers/usb/typec/typec.c
+.. kernel-doc:: drivers/usb/typec/class.c
    :functions: typec_partner_register_altmode
 
 API for the Cable Plugs:
 
-.. kernel-doc:: drivers/usb/typec/typec.c
+.. kernel-doc:: drivers/usb/typec/class.c
    :functions: typec_plug_register_altmode
 
 So ports, partners and cable plugs will register the alternate modes with their
@@ -172,11 +172,62 @@ own functions, but the registration will always return a handle to struct
 typec_altmode on success, or NULL. The unregistration will happen with the same
 function:
 
-.. kernel-doc:: drivers/usb/typec/typec.c
+.. kernel-doc:: drivers/usb/typec/class.c
    :functions: typec_unregister_altmode
 
 If a partner or cable plug enters or exits a mode, the port driver needs to
 notify the class with the following API:
 
-.. kernel-doc:: drivers/usb/typec/typec.c
+.. kernel-doc:: drivers/usb/typec/class.c
    :functions: typec_altmode_update_active
+
+Multiplexer/DeMultiplexer Switches
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+USB Type-C connectors may have one or more mux/demux switches behind them. Since
+the plugs can be inserted right-side-up or upside-down, a switch is needed to
+route the correct data pairs from the connector to the USB controllers. If
+Alternate or Accessory Modes are supported, another switch is needed that can
+route the pins on the connector to some other component besides USB. USB Type-C
+Connector Class supplies an API for registering those switches.
+
+.. kernel-doc:: drivers/usb/typec/mux.c
+   :functions: typec_switch_register typec_switch_unregister typec_mux_register typec_mux_unregister
+
+In most cases the same physical mux will handle both the orientation and mode.
+However, as the port drivers will be responsible for the orientation, and the
+alternate mode drivers for the mode, the two are always separated into their
+own logical components: "mux" for the mode and "switch" for the orientation.
+
+When a port is registered, USB Type-C Connector Class requests both the mux and
+the switch for the port. The drivers can then use the following API for
+controlling them:
+
+.. kernel-doc:: drivers/usb/typec/class.c
+   :functions: typec_set_orientation typec_set_mode
+
+If the connector is dual-role capable, there may also be a switch for the data
+role. USB Type-C Connector Class does not supply separate API for them. The
+port drivers can use USB Role Class API with those.
+
+Illustration of the muxes behind a connector that supports an alternate mode:
+
+                     ------------------------
+                     |       Connector      |
+                     ------------------------
+                            |         |
+                     ------------------------
+                      \     Orientation    /
+                       --------------------
+                                |
+                       --------------------
+                      /        Mode        \
+                     ------------------------
+                         /              \
+      ------------------------        --------------------
+      |       Alt Mode       |       /      USB Role      \
+      ------------------------      ------------------------
+                                         /            \
+                     ------------------------      ------------------------
+                     |       USB Host       |      |       USB Device     |
+                     ------------------------      ------------------------
diff --git a/drivers/usb/typec/Makefile b/drivers/usb/typec/Makefile
index bb3138a6eaac..56b2e9516ec1 100644
--- a/drivers/usb/typec/Makefile
+++ b/drivers/usb/typec/Makefile
@@ -1,5 +1,6 @@
 # SPDX-License-Identifier: GPL-2.0
 obj-$(CONFIG_TYPEC)		+= typec.o
+typec-y				:= class.o mux.o
 obj-$(CONFIG_TYPEC_TCPM)	+= tcpm.o
 obj-y				+= fusb302/
 obj-$(CONFIG_TYPEC_WCOVE)	+= typec_wcove.o
diff --git a/drivers/usb/typec/typec.c b/drivers/usb/typec/class.c
similarity index 95%
rename from drivers/usb/typec/typec.c
rename to drivers/usb/typec/class.c
index dc28ad868d93..2620a694704f 100644
--- a/drivers/usb/typec/typec.c
+++ b/drivers/usb/typec/class.c
@@ -11,6 +11,7 @@
 #include <linux/mutex.h>
 #include <linux/slab.h>
 #include <linux/usb/typec.h>
+#include <linux/usb/typec_mux.h>
 
 struct typec_mode {
 	int				index;
@@ -70,6 +71,10 @@ struct typec_port {
 	enum typec_port_type		port_type;
 	struct mutex			port_type_lock;
 
+	enum typec_orientation		orientation;
+	struct typec_switch		*sw;
+	struct typec_mux		*mux;
+
 	const struct typec_capability	*cap;
 };
 
@@ -92,6 +97,7 @@ static const struct device_type typec_port_dev_type;
 static DEFINE_IDA(typec_index_ida);
 static struct class *typec_class;
 
+/* ------------------------------------------------------------------------- */
 /* Common attributes */
 
 static const char * const typec_accessory_modes[] = {
@@ -1137,6 +1143,8 @@ static void typec_release(struct device *dev)
 	struct typec_port *port = to_typec_port(dev);
 
 	ida_simple_remove(&typec_index_ida, port->id);
+	typec_switch_put(port->sw);
+	typec_mux_put(port->mux);
 	kfree(port);
 }
 
@@ -1246,6 +1254,47 @@ void typec_set_pwr_opmode(struct typec_port *port,
 }
 EXPORT_SYMBOL_GPL(typec_set_pwr_opmode);
 
+/* ------------------------------------------ */
+/* API for Multiplexer/DeMultiplexer Switches */
+
+/**
+ * typec_set_orientation - Set USB Type-C cable plug orientation
+ * @port: USB Type-C Port
+ * @orientation: USB Type-C cable plug orientation
+ *
+ * Set cable plug orientation for @port.
+ */
+int typec_set_orientation(struct typec_port *port,
+			  enum typec_orientation orientation)
+{
+	int ret;
+
+	if (port->sw) {
+		ret = port->sw->set(port->sw, orientation);
+		if (ret)
+			return ret;
+	}
+
+	port->orientation = orientation;
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(typec_set_orientation);
+
+/**
+ * typec_set_mode - Set mode of operation for USB Type-C connector
+ * @port: USB Type-C port for the connector
+ * @mode: Operation mode for the connector
+ *
+ * Set mode @mode for @port. This function will configure the muxes needed to
+ * enter @mode.
+ */
+int typec_set_mode(struct typec_port *port, int mode)
+{
+	return port->mux ? port->mux->set(port->mux, mode) : 0;
+}
+EXPORT_SYMBOL_GPL(typec_set_mode);
+
 /* --------------------------------------- */
 
 /**
@@ -1293,6 +1342,18 @@ struct typec_port *typec_register_port(struct device *parent,
 		return ERR_PTR(id);
 	}
 
+	port->sw = typec_switch_get(cap->fwnode ? &port->dev : parent);
+	if (IS_ERR(port->sw)) {
+		ret = PTR_ERR(port->sw);
+		goto err_switch;
+	}
+
+	port->mux = typec_mux_get(cap->fwnode ? &port->dev : parent);
+	if (IS_ERR(port->mux)) {
+		ret = PTR_ERR(port->mux);
+		goto err_mux;
+	}
+
 	if (cap->type == TYPEC_PORT_DFP)
 		role = TYPEC_SOURCE;
 	else if (cap->type == TYPEC_PORT_UFP)
@@ -1330,6 +1391,15 @@ struct typec_port *typec_register_port(struct device *parent,
 	}
 
 	return port;
+
+err_mux:
+	typec_switch_put(port->sw);
+
+err_switch:
+	ida_simple_remove(&typec_index_ida, port->id);
+	kfree(port);
+
+	return ERR_PTR(ret);
 }
 EXPORT_SYMBOL_GPL(typec_register_port);
 
diff --git a/drivers/usb/typec/mux.c b/drivers/usb/typec/mux.c
new file mode 100644
index 000000000000..efd4079a96f2
--- /dev/null
+++ b/drivers/usb/typec/mux.c
@@ -0,0 +1,190 @@
+// SPDX-License-Identifier: GPL-2.0
+/**
+ * USB Type-C Multiplexer/DeMultiplexer Switch support
+ *
+ * Copyright (C) 2018 Intel Corporation
+ * Author: Heikki Krogerus <heikki.krogerus@linux.intel.com>
+ *         Hans de Goede <hdegoede@redhat.com>
+ */
+
+#include <linux/connection.h>
+#include <linux/device.h>
+#include <linux/list.h>
+#include <linux/mutex.h>
+#include <linux/usb/typec_mux.h>
+
+static DEFINE_MUTEX(switch_lock);
+static DEFINE_MUTEX(mux_lock);
+static LIST_HEAD(switch_list);
+static LIST_HEAD(mux_list);
+
+static void *typec_switch_match(struct devcon *con, int ep, void *data)
+{
+	struct typec_switch *sw;
+
+	list_for_each_entry(sw, &switch_list, entry)
+		if (!strcmp(con->endpoint[ep], dev_name(sw->dev)))
+			return sw;
+
+	/*
+	 * We only get called if a connection was found, tell the caller to
+	 * wait for the switch to show up.
+	 */
+	return ERR_PTR(-EPROBE_DEFER);
+}
+
+/**
+ * typec_switch_get - Find USB Type-C orientation switch
+ * @dev: The caller device
+ *
+ * Finds a switch linked with @dev. Returns a reference to the switch on
+ * success, NULL if no matching connection was found, or
+ * ERR_PTR(-EPROBE_DEFER) when a connection was found but the switch
+ * has not been enumerated yet.
+ */
+struct typec_switch *typec_switch_get(struct device *dev)
+{
+	struct typec_switch *sw;
+
+	mutex_lock(&switch_lock);
+	sw = __device_find_connection(dev, "typec-switch", NULL,
+				      typec_switch_match);
+	if (!IS_ERR_OR_NULL(sw))
+		get_device(sw->dev);
+	mutex_unlock(&switch_lock);
+
+	return sw;
+}
+EXPORT_SYMBOL_GPL(typec_switch_get);
+
+/**
+ * typec_put_switch - Release USB Type-C orientation switch
+ * @sw: USB Type-C orientation switch
+ *
+ * Decrement reference count for @sw.
+ */
+void typec_switch_put(struct typec_switch *sw)
+{
+	if (!IS_ERR_OR_NULL(sw))
+		put_device(sw->dev);
+}
+EXPORT_SYMBOL_GPL(typec_switch_put);
+
+/**
+ * typec_switch_register - Register USB Type-C orientation switch
+ * @sw: USB Type-C orientation switch
+ *
+ * This function registers a switch that can be used for routing the correct
+ * data pairs depending on the cable plug orientation from the USB Type-C
+ * connector to the USB controllers. USB Type-C plugs can be inserted
+ * right-side-up or upside-down.
+ */
+int typec_switch_register(struct typec_switch *sw)
+{
+	mutex_lock(&switch_lock);
+	list_add_tail(&sw->entry, &switch_list);
+	mutex_unlock(&switch_lock);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(typec_switch_register);
+
+/**
+ * typec_switch_unregister - Unregister USB Type-C orientation switch
+ * @sw: USB Type-C orientation switch
+ *
+ * Unregister switch that was registered with typec_switch_register().
+ */
+void typec_switch_unregister(struct typec_switch *sw)
+{
+	mutex_lock(&switch_lock);
+	list_del(&sw->entry);
+	mutex_unlock(&switch_lock);
+}
+EXPORT_SYMBOL_GPL(typec_switch_unregister);
+
+/* ------------------------------------------------------------------------- */
+
+static void *typec_mux_match(struct devcon *con, int ep, void *data)
+{
+	struct typec_mux *mux;
+
+	list_for_each_entry(mux, &mux_list, entry)
+		if (!strcmp(con->endpoint[ep], dev_name(mux->dev)))
+			return mux;
+
+	/*
+	 * We only get called if a connection was found, tell the caller to
+	 * wait for the switch to show up.
+	 */
+	return ERR_PTR(-EPROBE_DEFER);
+}
+
+/**
+ * typec_mux_get - Find USB Type-C Multiplexer
+ * @dev: The caller device
+ *
+ * Finds a mux linked to the caller. This function is primarily meant for the
+ * Type-C drivers. Returns a reference to the mux on success, NULL if no
+ * matching connection was found, or ERR_PTR(-EPROBE_DEFER) when a connection
+ * was found but the mux has not been enumerated yet.
+ */
+struct typec_mux *typec_mux_get(struct device *dev)
+{
+	struct typec_mux *mux;
+
+	mutex_lock(&mux_lock);
+	mux = __device_find_connection(dev, "typec-mux", NULL, typec_mux_match);
+	if (!IS_ERR_OR_NULL(mux))
+		get_device(mux->dev);
+	mutex_unlock(&mux_lock);
+
+	return mux;
+}
+EXPORT_SYMBOL_GPL(typec_mux_get);
+
+/**
+ * typec_mux_put - Release handle to a Multiplexer
+ * @mux: USB Type-C Connector Multiplexer/DeMultiplexer
+ *
+ * Decrements reference count for @mux.
+ */
+void typec_mux_put(struct typec_mux *mux)
+{
+	if (!IS_ERR_OR_NULL(mux))
+		put_device(mux->dev);
+}
+EXPORT_SYMBOL_GPL(typec_mux_put);
+
+/**
+ * typec_mux_register - Register Multiplexer routing USB Type-C pins
+ * @mux: USB Type-C Connector Multiplexer/DeMultiplexer
+ *
+ * USB Type-C connectors can be used for alternate modes of operation besides
+ * USB when Accessory/Alternate Modes are supported. With some of those modes,
+ * the pins on the connector need to be reconfigured. This function registers
+ * multiplexer switches routing the pins on the connector.
+ */
+int typec_mux_register(struct typec_mux *mux)
+{
+	mutex_lock(&mux_lock);
+	list_add_tail(&mux->entry, &mux_list);
+	mutex_unlock(&mux_lock);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(typec_mux_register);
+
+/**
+ * typec_mux_unregister - Unregister Multiplexer Switch
+ * @sw: USB Type-C Connector Multiplexer/DeMultiplexer
+ *
+ * Unregister mux that was registered with typec_mux_register().
+ */
+void typec_mux_unregister(struct typec_mux *mux)
+{
+	mutex_lock(&mux_lock);
+	list_del(&mux->entry);
+	mutex_unlock(&mux_lock);
+}
+EXPORT_SYMBOL_GPL(typec_mux_unregister);
diff --git a/include/linux/usb/typec.h b/include/linux/usb/typec.h
index 0d44ce6af08f..2408e5c2ed91 100644
--- a/include/linux/usb/typec.h
+++ b/include/linux/usb/typec.h
@@ -60,6 +60,12 @@ enum typec_accessory {
 
 #define TYPEC_MAX_ACCESSORY	3
 
+enum typec_orientation {
+	TYPEC_ORIENTATION_NONE,
+	TYPEC_ORIENTATION_NORMAL,
+	TYPEC_ORIENTATION_REVERSE,
+};
+
 /*
  * struct usb_pd_identity - USB Power Delivery identity data
  * @id_header: ID Header VDO
@@ -185,6 +191,8 @@ struct typec_partner_desc {
  * @pd_revision: USB Power Delivery Specification revision if supported
  * @prefer_role: Initial role preference
  * @accessory: Supported Accessory Modes
+ * @sw: Cable plug orientation switch
+ * @mux: Multiplexer switch for Alternate/Accessory Modes
  * @fwnode: Optional fwnode of the port
  * @try_role: Set data role preference for DRP port
  * @dr_set: Set Data Role
@@ -202,6 +210,8 @@ struct typec_capability {
 	int			prefer_role;
 	enum typec_accessory	accessory[TYPEC_MAX_ACCESSORY];
 
+	struct typec_switch	*sw;
+	struct typec_mux	*mux;
 	struct fwnode_handle	*fwnode;
 
 	int		(*try_role)(const struct typec_capability *,
@@ -245,4 +255,8 @@ void typec_set_pwr_role(struct typec_port *port, enum typec_role role);
 void typec_set_vconn_role(struct typec_port *port, enum typec_role role);
 void typec_set_pwr_opmode(struct typec_port *port, enum typec_pwr_opmode mode);
 
+int typec_set_orientation(struct typec_port *port,
+			  enum typec_orientation orientation);
+int typec_set_mode(struct typec_port *port, int mode);
+
 #endif /* __LINUX_USB_TYPEC_H */
diff --git a/include/linux/usb/typec_mux.h b/include/linux/usb/typec_mux.h
new file mode 100644
index 000000000000..12c1b057834b
--- /dev/null
+++ b/include/linux/usb/typec_mux.h
@@ -0,0 +1,55 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#ifndef __USB_TYPEC_MUX
+#define __USB_TYPEC_MUX
+
+#include <linux/list.h>
+#include <linux/usb/typec.h>
+
+struct device;
+
+/**
+ * struct typec_switch - USB Type-C cable orientation switch
+ * @dev: Switch device
+ * @entry: List entry
+ * @set: Callback to the driver for setting the orientation
+ *
+ * USB Type-C pin flipper switch routing the correct data pairs from the
+ * connector to the USB controller depending on the orientation of the cable
+ * plug.
+ */
+struct typec_switch {
+	struct device *dev;
+	struct list_head entry;
+
+	int (*set)(struct typec_switch *sw, enum typec_orientation orientation);
+};
+
+/**
+ * struct typec_switch - USB Type-C connector pin mux
+ * @dev: Mux device
+ * @entry: List entry
+ * @set: Callback to the driver for setting the state of the mux
+ *
+ * Pin Multiplexer/DeMultiplexer switch routing the USB Type-C connector pins to
+ * different components depending on the requested mode of operation. Used with
+ * Accessory/Alternate modes.
+ */
+struct typec_mux {
+	struct device *dev;
+	struct list_head entry;
+
+	int (*set)(struct typec_mux *mux, int state);
+};
+
+struct typec_switch *typec_switch_get(struct device *dev);
+void typec_switch_put(struct typec_switch *sw);
+int typec_switch_register(struct typec_switch *sw);
+void typec_switch_unregister(struct typec_switch *sw);
+
+struct typec_mux *typec_mux_get(struct device *dev);
+void typec_mux_put(struct typec_mux *mux);
+int typec_mux_register(struct typec_mux *mux);
+void typec_mux_unregister(struct typec_mux *mux);
+
+#endif /* __USB_TYPEC_MUX */

^ permalink raw reply related	[flat|nested] 43+ messages in thread

* [PATCH v5 04/12] usb: common: Small class for USB role switches
@ 2018-02-28 15:07   ` Hans de Goede
  0 siblings, 0 replies; 43+ messages in thread
From: Hans de Goede @ 2018-02-28 15:07 UTC (permalink / raw)
  To: Darren Hart, Andy Shevchenko, MyungJoo Ham, Chanwoo Choi,
	Mathias Nyman, Heikki Krogerus, Greg Kroah-Hartman,
	Guenter Roeck
  Cc: Hans de Goede, platform-driver-x86, linux-kernel, linux-usb

From: Heikki Krogerus <heikki.krogerus@linux.intel.com>

USB role switch is a device that can be used to choose the
data role for USB connector. With dual-role capable USB
controllers, the controller itself will be the switch, but
on some platforms the USB host and device controllers are
separate IPs and there is a mux between them and the
connector. On those platforms the mux driver will need to
register the switch.

With USB Type-C connectors, the host-to-device relationship
is negotiated over the Configuration Channel (CC). That
means the USB Type-C drivers need to be in control of the
role switch. The class provides a simple API for the USB
Type-C drivers for the control.

For other types of USB connectors (mainly microAB) the class
provides user space control via sysfs attribute file that
can be used to request role swapping from the switch.

Signed-off-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Reviewed-by: Hans de Goede <hdegoede@redhat.com>
Reviewed-by: Andy Shevchenko <andy.shevchenko@gmail.com>
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
---
Changes in v4:
-Consistently use IS_ERR_OR_NULL where applicable
-Add Andy's Reviewed-by

Changes in v2:
-Minor style fixes from review of v1
---
 Documentation/ABI/testing/sysfs-class-usb_role |  21 ++
 drivers/usb/Kconfig                            |   3 +
 drivers/usb/common/Makefile                    |   1 +
 drivers/usb/common/roles.c                     | 305 +++++++++++++++++++++++++
 include/linux/usb/role.h                       |  51 +++++
 5 files changed, 381 insertions(+)
 create mode 100644 Documentation/ABI/testing/sysfs-class-usb_role
 create mode 100644 drivers/usb/common/roles.c
 create mode 100644 include/linux/usb/role.h

diff --git a/Documentation/ABI/testing/sysfs-class-usb_role b/Documentation/ABI/testing/sysfs-class-usb_role
new file mode 100644
index 000000000000..3b810a425a52
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-class-usb_role
@@ -0,0 +1,21 @@
+What:		/sys/class/usb_role/
+Date:		Jan 2018
+Contact:	Heikki Krogerus <heikki.krogerus@linux.intel.com>
+Description:
+		Place in sysfs for USB Role Switches. USB Role Switch is a
+		device that can select the data role (host or device) for USB
+		port.
+
+What:		/sys/class/usb_role/<switch>/role
+Date:		Jan 2018
+Contact:	Heikki Krogerus <heikki.krogerus@linux.intel.com>
+Description:
+		The current role of the switch. This attribute can be used for
+		requesting role swapping with non-USB Type-C ports. With USB
+		Type-C ports, the ABI defined for USB Type-C connector class
+		must be used.
+
+		Valid values:
+		- none
+		- host
+		- device
diff --git a/drivers/usb/Kconfig b/drivers/usb/Kconfig
index 148f3ee70286..f278958e04ca 100644
--- a/drivers/usb/Kconfig
+++ b/drivers/usb/Kconfig
@@ -203,4 +203,7 @@ config USB_ULPI_BUS
 	  To compile this driver as a module, choose M here: the module will
 	  be called ulpi.
 
+config USB_ROLE_SWITCH
+	tristate
+
 endif # USB_SUPPORT
diff --git a/drivers/usb/common/Makefile b/drivers/usb/common/Makefile
index 0a7c45e85481..fb4d5ef4165c 100644
--- a/drivers/usb/common/Makefile
+++ b/drivers/usb/common/Makefile
@@ -9,3 +9,4 @@ usb-common-$(CONFIG_USB_LED_TRIG) += led.o
 
 obj-$(CONFIG_USB_OTG_FSM) += usb-otg-fsm.o
 obj-$(CONFIG_USB_ULPI_BUS)	+= ulpi.o
+obj-$(CONFIG_USB_ROLE_SWITCH)	+= roles.o
diff --git a/drivers/usb/common/roles.c b/drivers/usb/common/roles.c
new file mode 100644
index 000000000000..bd616042afca
--- /dev/null
+++ b/drivers/usb/common/roles.c
@@ -0,0 +1,305 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * USB Role Switch Support
+ *
+ * Copyright (C) 2018 Intel Corporation
+ * Author: Heikki Krogerus <heikki.krogerus@linux.intel.com>
+ *         Hans de Goede <hdegoede@redhat.com>
+ */
+
+#include <linux/connection.h>
+#include <linux/usb/role.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+
+static struct class *role_class;
+
+struct usb_role_switch {
+	struct device dev;
+	struct mutex lock; /* device lock*/
+	enum usb_role role;
+
+	/* From descriptor */
+	struct device *usb2_port;
+	struct device *usb3_port;
+	struct device *udc;
+	usb_role_switch_set_t set;
+	usb_role_switch_get_t get;
+	bool allow_userspace_control;
+};
+
+#define to_role_switch(d)	container_of(d, struct usb_role_switch, dev)
+
+/**
+ * usb_role_switch_set_role - Set USB role for a switch
+ * @sw: USB role switch
+ * @role: USB role to be switched to
+ *
+ * Set USB role @role for @sw.
+ */
+int usb_role_switch_set_role(struct usb_role_switch *sw, enum usb_role role)
+{
+	int ret;
+
+	if (IS_ERR_OR_NULL(sw))
+		return 0;
+
+	mutex_lock(&sw->lock);
+
+	ret = sw->set(sw->dev.parent, role);
+	if (!ret)
+		sw->role = role;
+
+	mutex_unlock(&sw->lock);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(usb_role_switch_set_role);
+
+/**
+ * usb_role_switch_get_role - Get the USB role for a switch
+ * @sw: USB role switch
+ *
+ * Depending on the role-switch-driver this function returns either a cached
+ * value of the last set role, or reads back the actual value from the hardware.
+ */
+enum usb_role usb_role_switch_get_role(struct usb_role_switch *sw)
+{
+	enum usb_role role;
+
+	if (IS_ERR_OR_NULL(sw))
+		return USB_ROLE_NONE;
+
+	mutex_lock(&sw->lock);
+
+	if (sw->get)
+		role = sw->get(sw->dev.parent);
+	else
+		role = sw->role;
+
+	mutex_unlock(&sw->lock);
+
+	return role;
+}
+EXPORT_SYMBOL_GPL(usb_role_switch_get_role);
+
+static int __switch_match(struct device *dev, const void *name)
+{
+	return !strcmp((const char *)name, dev_name(dev));
+}
+
+static void *usb_role_switch_match(struct devcon *con, int ep, void *data)
+{
+	struct device *dev;
+
+	dev = class_find_device(role_class, NULL, con->endpoint[ep],
+				__switch_match);
+
+	return dev ? to_role_switch(dev) : ERR_PTR(-EPROBE_DEFER);
+}
+
+/**
+ * usb_role_switch_get - Find USB role switch linked with the caller
+ * @dev: The caller device
+ *
+ * Finds and returns role switch linked with @dev. The reference count for the
+ * found switch is incremented.
+ */
+struct usb_role_switch *usb_role_switch_get(struct device *dev)
+{
+	return __device_find_connection(dev, "usb-role-switch", NULL,
+					usb_role_switch_match);
+}
+EXPORT_SYMBOL_GPL(usb_role_switch_get);
+
+/**
+ * usb_role_switch_put - Release handle to a switch
+ * @sw: USB Role Switch
+ *
+ * Decrement reference count for @sw.
+ */
+void usb_role_switch_put(struct usb_role_switch *sw)
+{
+	if (!IS_ERR_OR_NULL(sw))
+		put_device(&sw->dev);
+}
+EXPORT_SYMBOL_GPL(usb_role_switch_put);
+
+static umode_t
+usb_role_switch_is_visible(struct kobject *kobj, struct attribute *attr, int n)
+{
+	struct device *dev = container_of(kobj, typeof(*dev), kobj);
+	struct usb_role_switch *sw = to_role_switch(dev);
+
+	if (sw->allow_userspace_control)
+		return attr->mode;
+
+	return 0;
+}
+
+static const char * const usb_roles[] = {
+	[USB_ROLE_NONE]		= "none",
+	[USB_ROLE_HOST]		= "host",
+	[USB_ROLE_DEVICE]	= "device",
+};
+
+static ssize_t
+role_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct usb_role_switch *sw = to_role_switch(dev);
+	enum usb_role role = usb_role_switch_get_role(sw);
+
+	return sprintf(buf, "%s\n", usb_roles[role]);
+}
+
+static ssize_t role_store(struct device *dev, struct device_attribute *attr,
+			  const char *buf, size_t size)
+{
+	struct usb_role_switch *sw = to_role_switch(dev);
+	int ret;
+
+	ret = sysfs_match_string(usb_roles, buf);
+	if (ret < 0) {
+		bool res;
+
+		/* Extra check if the user wants to disable the switch */
+		ret = kstrtobool(buf, &res);
+		if (ret || res)
+			return -EINVAL;
+	}
+
+	ret = usb_role_switch_set_role(sw, ret);
+	if (ret)
+		return ret;
+
+	return size;
+}
+static DEVICE_ATTR_RW(role);
+
+static struct attribute *usb_role_switch_attrs[] = {
+	&dev_attr_role.attr,
+	NULL,
+};
+
+static const struct attribute_group usb_role_switch_group = {
+	.is_visible = usb_role_switch_is_visible,
+	.attrs = usb_role_switch_attrs,
+};
+
+static const struct attribute_group *usb_role_switch_groups[] = {
+	&usb_role_switch_group,
+	NULL,
+};
+
+static int
+usb_role_switch_uevent(struct device *dev, struct kobj_uevent_env *env)
+{
+	int ret;
+
+	ret = add_uevent_var(env, "USB_ROLE_SWITCH=%s", dev_name(dev));
+	if (ret)
+		dev_err(dev, "failed to add uevent USB_ROLE_SWITCH\n");
+
+	return ret;
+}
+
+static void usb_role_switch_release(struct device *dev)
+{
+	struct usb_role_switch *sw = to_role_switch(dev);
+
+	kfree(sw);
+}
+
+static const struct device_type usb_role_dev_type = {
+	.name = "usb_role_switch",
+	.groups = usb_role_switch_groups,
+	.uevent = usb_role_switch_uevent,
+	.release = usb_role_switch_release,
+};
+
+/**
+ * usb_role_switch_register - Register USB Role Switch
+ * @parent: Parent device for the switch
+ * @desc: Description of the switch
+ *
+ * USB Role Switch is a device capable or choosing the role for USB connector.
+ * On platforms where the USB controller is dual-role capable, the controller
+ * driver will need to register the switch. On platforms where the USB host and
+ * USB device controllers behind the connector are separate, there will be a
+ * mux, and the driver for that mux will need to register the switch.
+ *
+ * Returns handle to a new role switch or ERR_PTR. The content of @desc is
+ * copied.
+ */
+struct usb_role_switch *
+usb_role_switch_register(struct device *parent,
+			 const struct usb_role_switch_desc *desc)
+{
+	struct usb_role_switch *sw;
+	int ret;
+
+	if (!desc || !desc->set)
+		return ERR_PTR(-EINVAL);
+
+	sw = kzalloc(sizeof(*sw), GFP_KERNEL);
+	if (!sw)
+		return ERR_PTR(-ENOMEM);
+
+	mutex_init(&sw->lock);
+
+	sw->allow_userspace_control = desc->allow_userspace_control;
+	sw->usb2_port = desc->usb2_port;
+	sw->usb3_port = desc->usb3_port;
+	sw->udc = desc->udc;
+	sw->set = desc->set;
+	sw->get = desc->get;
+
+	sw->dev.parent = parent;
+	sw->dev.class = role_class;
+	sw->dev.type = &usb_role_dev_type;
+	dev_set_name(&sw->dev, "%s-role-switch", dev_name(parent));
+
+	ret = device_register(&sw->dev);
+	if (ret) {
+		put_device(&sw->dev);
+		return ERR_PTR(ret);
+	}
+
+	/* TODO: Symlinks for the host port and the device controller. */
+
+	return sw;
+}
+EXPORT_SYMBOL_GPL(usb_role_switch_register);
+
+/**
+ * usb_role_switch_unregister - Unregsiter USB Role Switch
+ * @sw: USB Role Switch
+ *
+ * Unregister switch that was registered with usb_role_switch_register().
+ */
+void usb_role_switch_unregister(struct usb_role_switch *sw)
+{
+	if (!IS_ERR_OR_NULL(sw))
+		device_unregister(&sw->dev);
+}
+EXPORT_SYMBOL_GPL(usb_role_switch_unregister);
+
+static int __init usb_roles_init(void)
+{
+	role_class = class_create(THIS_MODULE, "usb_role");
+	return PTR_ERR_OR_ZERO(role_class);
+}
+subsys_initcall(usb_roles_init);
+
+static void __exit usb_roles_exit(void)
+{
+	class_destroy(role_class);
+}
+module_exit(usb_roles_exit);
+
+MODULE_AUTHOR("Heikki Krogerus <heikki.krogerus@linux.intel.com>");
+MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("USB Role Class");
diff --git a/include/linux/usb/role.h b/include/linux/usb/role.h
new file mode 100644
index 000000000000..2ef347bd1fdb
--- /dev/null
+++ b/include/linux/usb/role.h
@@ -0,0 +1,51 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#ifndef __LINUX_USB_ROLE_H
+#define __LINUX_USB_ROLE_H
+
+struct usb_role_switch;
+
+enum usb_role {
+	USB_ROLE_NONE,
+	USB_ROLE_HOST,
+	USB_ROLE_DEVICE,
+};
+
+typedef int (*usb_role_switch_set_t)(struct device *dev, enum usb_role role);
+typedef enum usb_role (*usb_role_switch_get_t)(struct device *dev);
+
+/**
+ * struct usb_role_switch_desc - USB Role Switch Descriptor
+ * @usb2_port: Optional reference to the host controller port device (USB2)
+ * @usb3_port: Optional reference to the host controller port device (USB3)
+ * @udc: Optional reference to the peripheral controller device
+ * @set: Callback for setting the role
+ * @get: Callback for getting the role (optional)
+ * @allow_userspace_control: If true userspace may change the role through sysfs
+ *
+ * @usb2_port and @usb3_port will point to the USB host port and @udc to the USB
+ * device controller behind the USB connector with the role switch. If
+ * @usb2_port, @usb3_port and @udc are included in the description, the
+ * reference count for them should be incremented by the caller of
+ * usb_role_switch_register() before registering the switch.
+ */
+struct usb_role_switch_desc {
+	struct device *usb2_port;
+	struct device *usb3_port;
+	struct device *udc;
+	usb_role_switch_set_t set;
+	usb_role_switch_get_t get;
+	bool allow_userspace_control;
+};
+
+int usb_role_switch_set_role(struct usb_role_switch *sw, enum usb_role role);
+enum usb_role usb_role_switch_get_role(struct usb_role_switch *sw);
+struct usb_role_switch *usb_role_switch_get(struct device *dev);
+void usb_role_switch_put(struct usb_role_switch *sw);
+
+struct usb_role_switch *
+usb_role_switch_register(struct device *parent,
+			 const struct usb_role_switch_desc *desc);
+void usb_role_switch_unregister(struct usb_role_switch *sw);
+
+#endif /* __LINUX_USB_ROLE_H */
-- 
2.14.3

^ permalink raw reply related	[flat|nested] 43+ messages in thread

* [v5,04/12] usb: common: Small class for USB role switches
@ 2018-02-28 15:07   ` Hans de Goede
  0 siblings, 0 replies; 43+ messages in thread
From: Hans de Goede @ 2018-02-28 15:07 UTC (permalink / raw)
  To: Darren Hart, Andy Shevchenko, MyungJoo Ham, Chanwoo Choi,
	Mathias Nyman, Heikki Krogerus, Greg Kroah-Hartman,
	Guenter Roeck
  Cc: Hans de Goede, platform-driver-x86, linux-kernel, linux-usb

From: Heikki Krogerus <heikki.krogerus@linux.intel.com>

USB role switch is a device that can be used to choose the
data role for USB connector. With dual-role capable USB
controllers, the controller itself will be the switch, but
on some platforms the USB host and device controllers are
separate IPs and there is a mux between them and the
connector. On those platforms the mux driver will need to
register the switch.

With USB Type-C connectors, the host-to-device relationship
is negotiated over the Configuration Channel (CC). That
means the USB Type-C drivers need to be in control of the
role switch. The class provides a simple API for the USB
Type-C drivers for the control.

For other types of USB connectors (mainly microAB) the class
provides user space control via sysfs attribute file that
can be used to request role swapping from the switch.

Signed-off-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Reviewed-by: Hans de Goede <hdegoede@redhat.com>
Reviewed-by: Andy Shevchenko <andy.shevchenko@gmail.com>
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
---
Changes in v4:
-Consistently use IS_ERR_OR_NULL where applicable
-Add Andy's Reviewed-by

Changes in v2:
-Minor style fixes from review of v1
---
 Documentation/ABI/testing/sysfs-class-usb_role |  21 ++
 drivers/usb/Kconfig                            |   3 +
 drivers/usb/common/Makefile                    |   1 +
 drivers/usb/common/roles.c                     | 305 +++++++++++++++++++++++++
 include/linux/usb/role.h                       |  51 +++++
 5 files changed, 381 insertions(+)
 create mode 100644 Documentation/ABI/testing/sysfs-class-usb_role
 create mode 100644 drivers/usb/common/roles.c
 create mode 100644 include/linux/usb/role.h

diff --git a/Documentation/ABI/testing/sysfs-class-usb_role b/Documentation/ABI/testing/sysfs-class-usb_role
new file mode 100644
index 000000000000..3b810a425a52
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-class-usb_role
@@ -0,0 +1,21 @@
+What:		/sys/class/usb_role/
+Date:		Jan 2018
+Contact:	Heikki Krogerus <heikki.krogerus@linux.intel.com>
+Description:
+		Place in sysfs for USB Role Switches. USB Role Switch is a
+		device that can select the data role (host or device) for USB
+		port.
+
+What:		/sys/class/usb_role/<switch>/role
+Date:		Jan 2018
+Contact:	Heikki Krogerus <heikki.krogerus@linux.intel.com>
+Description:
+		The current role of the switch. This attribute can be used for
+		requesting role swapping with non-USB Type-C ports. With USB
+		Type-C ports, the ABI defined for USB Type-C connector class
+		must be used.
+
+		Valid values:
+		- none
+		- host
+		- device
diff --git a/drivers/usb/Kconfig b/drivers/usb/Kconfig
index 148f3ee70286..f278958e04ca 100644
--- a/drivers/usb/Kconfig
+++ b/drivers/usb/Kconfig
@@ -203,4 +203,7 @@ config USB_ULPI_BUS
 	  To compile this driver as a module, choose M here: the module will
 	  be called ulpi.
 
+config USB_ROLE_SWITCH
+	tristate
+
 endif # USB_SUPPORT
diff --git a/drivers/usb/common/Makefile b/drivers/usb/common/Makefile
index 0a7c45e85481..fb4d5ef4165c 100644
--- a/drivers/usb/common/Makefile
+++ b/drivers/usb/common/Makefile
@@ -9,3 +9,4 @@ usb-common-$(CONFIG_USB_LED_TRIG) += led.o
 
 obj-$(CONFIG_USB_OTG_FSM) += usb-otg-fsm.o
 obj-$(CONFIG_USB_ULPI_BUS)	+= ulpi.o
+obj-$(CONFIG_USB_ROLE_SWITCH)	+= roles.o
diff --git a/drivers/usb/common/roles.c b/drivers/usb/common/roles.c
new file mode 100644
index 000000000000..bd616042afca
--- /dev/null
+++ b/drivers/usb/common/roles.c
@@ -0,0 +1,305 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * USB Role Switch Support
+ *
+ * Copyright (C) 2018 Intel Corporation
+ * Author: Heikki Krogerus <heikki.krogerus@linux.intel.com>
+ *         Hans de Goede <hdegoede@redhat.com>
+ */
+
+#include <linux/connection.h>
+#include <linux/usb/role.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+
+static struct class *role_class;
+
+struct usb_role_switch {
+	struct device dev;
+	struct mutex lock; /* device lock*/
+	enum usb_role role;
+
+	/* From descriptor */
+	struct device *usb2_port;
+	struct device *usb3_port;
+	struct device *udc;
+	usb_role_switch_set_t set;
+	usb_role_switch_get_t get;
+	bool allow_userspace_control;
+};
+
+#define to_role_switch(d)	container_of(d, struct usb_role_switch, dev)
+
+/**
+ * usb_role_switch_set_role - Set USB role for a switch
+ * @sw: USB role switch
+ * @role: USB role to be switched to
+ *
+ * Set USB role @role for @sw.
+ */
+int usb_role_switch_set_role(struct usb_role_switch *sw, enum usb_role role)
+{
+	int ret;
+
+	if (IS_ERR_OR_NULL(sw))
+		return 0;
+
+	mutex_lock(&sw->lock);
+
+	ret = sw->set(sw->dev.parent, role);
+	if (!ret)
+		sw->role = role;
+
+	mutex_unlock(&sw->lock);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(usb_role_switch_set_role);
+
+/**
+ * usb_role_switch_get_role - Get the USB role for a switch
+ * @sw: USB role switch
+ *
+ * Depending on the role-switch-driver this function returns either a cached
+ * value of the last set role, or reads back the actual value from the hardware.
+ */
+enum usb_role usb_role_switch_get_role(struct usb_role_switch *sw)
+{
+	enum usb_role role;
+
+	if (IS_ERR_OR_NULL(sw))
+		return USB_ROLE_NONE;
+
+	mutex_lock(&sw->lock);
+
+	if (sw->get)
+		role = sw->get(sw->dev.parent);
+	else
+		role = sw->role;
+
+	mutex_unlock(&sw->lock);
+
+	return role;
+}
+EXPORT_SYMBOL_GPL(usb_role_switch_get_role);
+
+static int __switch_match(struct device *dev, const void *name)
+{
+	return !strcmp((const char *)name, dev_name(dev));
+}
+
+static void *usb_role_switch_match(struct devcon *con, int ep, void *data)
+{
+	struct device *dev;
+
+	dev = class_find_device(role_class, NULL, con->endpoint[ep],
+				__switch_match);
+
+	return dev ? to_role_switch(dev) : ERR_PTR(-EPROBE_DEFER);
+}
+
+/**
+ * usb_role_switch_get - Find USB role switch linked with the caller
+ * @dev: The caller device
+ *
+ * Finds and returns role switch linked with @dev. The reference count for the
+ * found switch is incremented.
+ */
+struct usb_role_switch *usb_role_switch_get(struct device *dev)
+{
+	return __device_find_connection(dev, "usb-role-switch", NULL,
+					usb_role_switch_match);
+}
+EXPORT_SYMBOL_GPL(usb_role_switch_get);
+
+/**
+ * usb_role_switch_put - Release handle to a switch
+ * @sw: USB Role Switch
+ *
+ * Decrement reference count for @sw.
+ */
+void usb_role_switch_put(struct usb_role_switch *sw)
+{
+	if (!IS_ERR_OR_NULL(sw))
+		put_device(&sw->dev);
+}
+EXPORT_SYMBOL_GPL(usb_role_switch_put);
+
+static umode_t
+usb_role_switch_is_visible(struct kobject *kobj, struct attribute *attr, int n)
+{
+	struct device *dev = container_of(kobj, typeof(*dev), kobj);
+	struct usb_role_switch *sw = to_role_switch(dev);
+
+	if (sw->allow_userspace_control)
+		return attr->mode;
+
+	return 0;
+}
+
+static const char * const usb_roles[] = {
+	[USB_ROLE_NONE]		= "none",
+	[USB_ROLE_HOST]		= "host",
+	[USB_ROLE_DEVICE]	= "device",
+};
+
+static ssize_t
+role_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct usb_role_switch *sw = to_role_switch(dev);
+	enum usb_role role = usb_role_switch_get_role(sw);
+
+	return sprintf(buf, "%s\n", usb_roles[role]);
+}
+
+static ssize_t role_store(struct device *dev, struct device_attribute *attr,
+			  const char *buf, size_t size)
+{
+	struct usb_role_switch *sw = to_role_switch(dev);
+	int ret;
+
+	ret = sysfs_match_string(usb_roles, buf);
+	if (ret < 0) {
+		bool res;
+
+		/* Extra check if the user wants to disable the switch */
+		ret = kstrtobool(buf, &res);
+		if (ret || res)
+			return -EINVAL;
+	}
+
+	ret = usb_role_switch_set_role(sw, ret);
+	if (ret)
+		return ret;
+
+	return size;
+}
+static DEVICE_ATTR_RW(role);
+
+static struct attribute *usb_role_switch_attrs[] = {
+	&dev_attr_role.attr,
+	NULL,
+};
+
+static const struct attribute_group usb_role_switch_group = {
+	.is_visible = usb_role_switch_is_visible,
+	.attrs = usb_role_switch_attrs,
+};
+
+static const struct attribute_group *usb_role_switch_groups[] = {
+	&usb_role_switch_group,
+	NULL,
+};
+
+static int
+usb_role_switch_uevent(struct device *dev, struct kobj_uevent_env *env)
+{
+	int ret;
+
+	ret = add_uevent_var(env, "USB_ROLE_SWITCH=%s", dev_name(dev));
+	if (ret)
+		dev_err(dev, "failed to add uevent USB_ROLE_SWITCH\n");
+
+	return ret;
+}
+
+static void usb_role_switch_release(struct device *dev)
+{
+	struct usb_role_switch *sw = to_role_switch(dev);
+
+	kfree(sw);
+}
+
+static const struct device_type usb_role_dev_type = {
+	.name = "usb_role_switch",
+	.groups = usb_role_switch_groups,
+	.uevent = usb_role_switch_uevent,
+	.release = usb_role_switch_release,
+};
+
+/**
+ * usb_role_switch_register - Register USB Role Switch
+ * @parent: Parent device for the switch
+ * @desc: Description of the switch
+ *
+ * USB Role Switch is a device capable or choosing the role for USB connector.
+ * On platforms where the USB controller is dual-role capable, the controller
+ * driver will need to register the switch. On platforms where the USB host and
+ * USB device controllers behind the connector are separate, there will be a
+ * mux, and the driver for that mux will need to register the switch.
+ *
+ * Returns handle to a new role switch or ERR_PTR. The content of @desc is
+ * copied.
+ */
+struct usb_role_switch *
+usb_role_switch_register(struct device *parent,
+			 const struct usb_role_switch_desc *desc)
+{
+	struct usb_role_switch *sw;
+	int ret;
+
+	if (!desc || !desc->set)
+		return ERR_PTR(-EINVAL);
+
+	sw = kzalloc(sizeof(*sw), GFP_KERNEL);
+	if (!sw)
+		return ERR_PTR(-ENOMEM);
+
+	mutex_init(&sw->lock);
+
+	sw->allow_userspace_control = desc->allow_userspace_control;
+	sw->usb2_port = desc->usb2_port;
+	sw->usb3_port = desc->usb3_port;
+	sw->udc = desc->udc;
+	sw->set = desc->set;
+	sw->get = desc->get;
+
+	sw->dev.parent = parent;
+	sw->dev.class = role_class;
+	sw->dev.type = &usb_role_dev_type;
+	dev_set_name(&sw->dev, "%s-role-switch", dev_name(parent));
+
+	ret = device_register(&sw->dev);
+	if (ret) {
+		put_device(&sw->dev);
+		return ERR_PTR(ret);
+	}
+
+	/* TODO: Symlinks for the host port and the device controller. */
+
+	return sw;
+}
+EXPORT_SYMBOL_GPL(usb_role_switch_register);
+
+/**
+ * usb_role_switch_unregister - Unregsiter USB Role Switch
+ * @sw: USB Role Switch
+ *
+ * Unregister switch that was registered with usb_role_switch_register().
+ */
+void usb_role_switch_unregister(struct usb_role_switch *sw)
+{
+	if (!IS_ERR_OR_NULL(sw))
+		device_unregister(&sw->dev);
+}
+EXPORT_SYMBOL_GPL(usb_role_switch_unregister);
+
+static int __init usb_roles_init(void)
+{
+	role_class = class_create(THIS_MODULE, "usb_role");
+	return PTR_ERR_OR_ZERO(role_class);
+}
+subsys_initcall(usb_roles_init);
+
+static void __exit usb_roles_exit(void)
+{
+	class_destroy(role_class);
+}
+module_exit(usb_roles_exit);
+
+MODULE_AUTHOR("Heikki Krogerus <heikki.krogerus@linux.intel.com>");
+MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("USB Role Class");
diff --git a/include/linux/usb/role.h b/include/linux/usb/role.h
new file mode 100644
index 000000000000..2ef347bd1fdb
--- /dev/null
+++ b/include/linux/usb/role.h
@@ -0,0 +1,51 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#ifndef __LINUX_USB_ROLE_H
+#define __LINUX_USB_ROLE_H
+
+struct usb_role_switch;
+
+enum usb_role {
+	USB_ROLE_NONE,
+	USB_ROLE_HOST,
+	USB_ROLE_DEVICE,
+};
+
+typedef int (*usb_role_switch_set_t)(struct device *dev, enum usb_role role);
+typedef enum usb_role (*usb_role_switch_get_t)(struct device *dev);
+
+/**
+ * struct usb_role_switch_desc - USB Role Switch Descriptor
+ * @usb2_port: Optional reference to the host controller port device (USB2)
+ * @usb3_port: Optional reference to the host controller port device (USB3)
+ * @udc: Optional reference to the peripheral controller device
+ * @set: Callback for setting the role
+ * @get: Callback for getting the role (optional)
+ * @allow_userspace_control: If true userspace may change the role through sysfs
+ *
+ * @usb2_port and @usb3_port will point to the USB host port and @udc to the USB
+ * device controller behind the USB connector with the role switch. If
+ * @usb2_port, @usb3_port and @udc are included in the description, the
+ * reference count for them should be incremented by the caller of
+ * usb_role_switch_register() before registering the switch.
+ */
+struct usb_role_switch_desc {
+	struct device *usb2_port;
+	struct device *usb3_port;
+	struct device *udc;
+	usb_role_switch_set_t set;
+	usb_role_switch_get_t get;
+	bool allow_userspace_control;
+};
+
+int usb_role_switch_set_role(struct usb_role_switch *sw, enum usb_role role);
+enum usb_role usb_role_switch_get_role(struct usb_role_switch *sw);
+struct usb_role_switch *usb_role_switch_get(struct device *dev);
+void usb_role_switch_put(struct usb_role_switch *sw);
+
+struct usb_role_switch *
+usb_role_switch_register(struct device *parent,
+			 const struct usb_role_switch_desc *desc);
+void usb_role_switch_unregister(struct usb_role_switch *sw);
+
+#endif /* __LINUX_USB_ROLE_H */

^ permalink raw reply related	[flat|nested] 43+ messages in thread

* [PATCH v5 05/12] usb: typec: tcpm: Set USB role switch to device mode when configured as such
@ 2018-02-28 15:07   ` Hans de Goede
  0 siblings, 0 replies; 43+ messages in thread
From: Hans de Goede @ 2018-02-28 15:07 UTC (permalink / raw)
  To: Darren Hart, Andy Shevchenko, MyungJoo Ham, Chanwoo Choi,
	Mathias Nyman, Heikki Krogerus, Greg Kroah-Hartman,
	Guenter Roeck
  Cc: Hans de Goede, platform-driver-x86, linux-kernel, linux-usb

Setting the mux to MUX_NONE and the switch to USB_SWITCH_DISCONNECT when
the data-role is device is not correct. Plenty of devices support
operating as USB device through a (separate) USB device controller.

We really need 2 different versions of USB_SWITCH_CONNECT,
USB_SWITCH_CONNECT_HOST and USB_SWITCH_DEVICE. Rather then modifying the
tcpc_usb_switch enum for this, simply remove it and switch to the
usb_role enum which provides exactly this, this will save use needing to
convert betweent the 2 enums when calling an usb-role-switch driver later.

Besides switching to the usb_role type, this commit also actually sets the
mux to TYPEC_MUX_USB and the switch to USB_ROLE_DEVICE instead of setting
both to none when the data-role is device.

This commit also makes tcpm_reset_port() call tcpm_mux_set(port,
TYPEC_MUX_NONE, USB_ROLE_NONE) so that the mux and switch
do _not_ stay in their last mode after a detach.

Reviewed-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Reviewed-by: Guenter Roeck <linux@roeck-us.net>
Reviewed-by: Andy Shevchenko <andy.shevchenko@gmail.com>
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
---
Changes in v4:
-Add Andy's Reviewed-by

Changes in v3:
-Add Guenter's Reviewed-by

Changes in v2:
-Added Heikki's Reviewed-by
---
 drivers/usb/typec/tcpm.c | 22 +++++++++++-----------
 include/linux/usb/tcpm.h |  8 ++------
 2 files changed, 13 insertions(+), 17 deletions(-)

diff --git a/drivers/usb/typec/tcpm.c b/drivers/usb/typec/tcpm.c
index 7cd28b700a7f..00ca2822432f 100644
--- a/drivers/usb/typec/tcpm.c
+++ b/drivers/usb/typec/tcpm.c
@@ -618,15 +618,15 @@ void tcpm_pd_transmit_complete(struct tcpm_port *port,
 EXPORT_SYMBOL_GPL(tcpm_pd_transmit_complete);
 
 static int tcpm_mux_set(struct tcpm_port *port, enum tcpc_mux_mode mode,
-			enum tcpc_usb_switch config)
+			enum usb_role usb_role)
 {
 	int ret = 0;
 
-	tcpm_log(port, "Requesting mux mode %d, config %d, polarity %d",
-		 mode, config, port->polarity);
+	tcpm_log(port, "Requesting mux mode %d, usb-role %d, polarity %d",
+		 mode, usb_role, port->polarity);
 
 	if (port->tcpc->mux)
-		ret = port->tcpc->mux->set(port->tcpc->mux, mode, config,
+		ret = port->tcpc->mux->set(port->tcpc->mux, mode, usb_role,
 					   port->polarity);
 
 	return ret;
@@ -742,14 +742,15 @@ static int tcpm_set_attached_state(struct tcpm_port *port, bool attached)
 static int tcpm_set_roles(struct tcpm_port *port, bool attached,
 			  enum typec_role role, enum typec_data_role data)
 {
+	enum usb_role usb_role;
 	int ret;
 
 	if (data == TYPEC_HOST)
-		ret = tcpm_mux_set(port, TYPEC_MUX_USB,
-				   TCPC_USB_SWITCH_CONNECT);
+		usb_role = USB_ROLE_HOST;
 	else
-		ret = tcpm_mux_set(port, TYPEC_MUX_NONE,
-				   TCPC_USB_SWITCH_DISCONNECT);
+		usb_role = USB_ROLE_DEVICE;
+
+	ret = tcpm_mux_set(port, TYPEC_MUX_USB, usb_role);
 	if (ret < 0)
 		return ret;
 
@@ -2096,7 +2097,7 @@ static int tcpm_src_attach(struct tcpm_port *port)
 out_disable_pd:
 	port->tcpc->set_pd_rx(port->tcpc, false);
 out_disable_mux:
-	tcpm_mux_set(port, TYPEC_MUX_NONE, TCPC_USB_SWITCH_DISCONNECT);
+	tcpm_mux_set(port, TYPEC_MUX_NONE, USB_ROLE_NONE);
 	return ret;
 }
 
@@ -2140,6 +2141,7 @@ static void tcpm_reset_port(struct tcpm_port *port)
 	tcpm_init_vconn(port);
 	tcpm_set_current_limit(port, 0, 0);
 	tcpm_set_polarity(port, TYPEC_POLARITY_CC1);
+	tcpm_mux_set(port, TYPEC_MUX_NONE, USB_ROLE_NONE);
 	tcpm_set_attached_state(port, false);
 	port->try_src_count = 0;
 	port->try_snk_count = 0;
@@ -2190,8 +2192,6 @@ static int tcpm_snk_attach(struct tcpm_port *port)
 static void tcpm_snk_detach(struct tcpm_port *port)
 {
 	tcpm_detach(port);
-
-	/* XXX: (Dis)connect SuperSpeed mux? */
 }
 
 static int tcpm_acc_attach(struct tcpm_port *port)
diff --git a/include/linux/usb/tcpm.h b/include/linux/usb/tcpm.h
index ca1c0b57f03f..268721bff2c1 100644
--- a/include/linux/usb/tcpm.h
+++ b/include/linux/usb/tcpm.h
@@ -16,6 +16,7 @@
 #define __LINUX_USB_TCPM_H
 
 #include <linux/bitops.h>
+#include <linux/usb/role.h>
 #include <linux/usb/typec.h>
 #include "pd.h"
 
@@ -97,11 +98,6 @@ struct tcpc_config {
 	const struct typec_altmode_desc *alt_modes;
 };
 
-enum tcpc_usb_switch {
-	TCPC_USB_SWITCH_CONNECT,
-	TCPC_USB_SWITCH_DISCONNECT,
-};
-
 /* Mux state attributes */
 #define TCPC_MUX_USB_ENABLED		BIT(0)	/* USB enabled */
 #define TCPC_MUX_DP_ENABLED		BIT(1)	/* DP enabled */
@@ -118,7 +114,7 @@ enum tcpc_mux_mode {
 
 struct tcpc_mux_dev {
 	int (*set)(struct tcpc_mux_dev *dev, enum tcpc_mux_mode mux_mode,
-		   enum tcpc_usb_switch usb_config,
+		   enum usb_role usb_role,
 		   enum typec_cc_polarity polarity);
 	bool dfp_only;
 	void *priv_data;
-- 
2.14.3

^ permalink raw reply related	[flat|nested] 43+ messages in thread

* [v5,05/12] usb: typec: tcpm: Set USB role switch to device mode when configured as such
@ 2018-02-28 15:07   ` Hans de Goede
  0 siblings, 0 replies; 43+ messages in thread
From: Hans de Goede @ 2018-02-28 15:07 UTC (permalink / raw)
  To: Darren Hart, Andy Shevchenko, MyungJoo Ham, Chanwoo Choi,
	Mathias Nyman, Heikki Krogerus, Greg Kroah-Hartman,
	Guenter Roeck
  Cc: Hans de Goede, platform-driver-x86, linux-kernel, linux-usb

Setting the mux to MUX_NONE and the switch to USB_SWITCH_DISCONNECT when
the data-role is device is not correct. Plenty of devices support
operating as USB device through a (separate) USB device controller.

We really need 2 different versions of USB_SWITCH_CONNECT,
USB_SWITCH_CONNECT_HOST and USB_SWITCH_DEVICE. Rather then modifying the
tcpc_usb_switch enum for this, simply remove it and switch to the
usb_role enum which provides exactly this, this will save use needing to
convert betweent the 2 enums when calling an usb-role-switch driver later.

Besides switching to the usb_role type, this commit also actually sets the
mux to TYPEC_MUX_USB and the switch to USB_ROLE_DEVICE instead of setting
both to none when the data-role is device.

This commit also makes tcpm_reset_port() call tcpm_mux_set(port,
TYPEC_MUX_NONE, USB_ROLE_NONE) so that the mux and switch
do _not_ stay in their last mode after a detach.

Reviewed-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Reviewed-by: Guenter Roeck <linux@roeck-us.net>
Reviewed-by: Andy Shevchenko <andy.shevchenko@gmail.com>
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
---
Changes in v4:
-Add Andy's Reviewed-by

Changes in v3:
-Add Guenter's Reviewed-by

Changes in v2:
-Added Heikki's Reviewed-by
---
 drivers/usb/typec/tcpm.c | 22 +++++++++++-----------
 include/linux/usb/tcpm.h |  8 ++------
 2 files changed, 13 insertions(+), 17 deletions(-)

diff --git a/drivers/usb/typec/tcpm.c b/drivers/usb/typec/tcpm.c
index 7cd28b700a7f..00ca2822432f 100644
--- a/drivers/usb/typec/tcpm.c
+++ b/drivers/usb/typec/tcpm.c
@@ -618,15 +618,15 @@ void tcpm_pd_transmit_complete(struct tcpm_port *port,
 EXPORT_SYMBOL_GPL(tcpm_pd_transmit_complete);
 
 static int tcpm_mux_set(struct tcpm_port *port, enum tcpc_mux_mode mode,
-			enum tcpc_usb_switch config)
+			enum usb_role usb_role)
 {
 	int ret = 0;
 
-	tcpm_log(port, "Requesting mux mode %d, config %d, polarity %d",
-		 mode, config, port->polarity);
+	tcpm_log(port, "Requesting mux mode %d, usb-role %d, polarity %d",
+		 mode, usb_role, port->polarity);
 
 	if (port->tcpc->mux)
-		ret = port->tcpc->mux->set(port->tcpc->mux, mode, config,
+		ret = port->tcpc->mux->set(port->tcpc->mux, mode, usb_role,
 					   port->polarity);
 
 	return ret;
@@ -742,14 +742,15 @@ static int tcpm_set_attached_state(struct tcpm_port *port, bool attached)
 static int tcpm_set_roles(struct tcpm_port *port, bool attached,
 			  enum typec_role role, enum typec_data_role data)
 {
+	enum usb_role usb_role;
 	int ret;
 
 	if (data == TYPEC_HOST)
-		ret = tcpm_mux_set(port, TYPEC_MUX_USB,
-				   TCPC_USB_SWITCH_CONNECT);
+		usb_role = USB_ROLE_HOST;
 	else
-		ret = tcpm_mux_set(port, TYPEC_MUX_NONE,
-				   TCPC_USB_SWITCH_DISCONNECT);
+		usb_role = USB_ROLE_DEVICE;
+
+	ret = tcpm_mux_set(port, TYPEC_MUX_USB, usb_role);
 	if (ret < 0)
 		return ret;
 
@@ -2096,7 +2097,7 @@ static int tcpm_src_attach(struct tcpm_port *port)
 out_disable_pd:
 	port->tcpc->set_pd_rx(port->tcpc, false);
 out_disable_mux:
-	tcpm_mux_set(port, TYPEC_MUX_NONE, TCPC_USB_SWITCH_DISCONNECT);
+	tcpm_mux_set(port, TYPEC_MUX_NONE, USB_ROLE_NONE);
 	return ret;
 }
 
@@ -2140,6 +2141,7 @@ static void tcpm_reset_port(struct tcpm_port *port)
 	tcpm_init_vconn(port);
 	tcpm_set_current_limit(port, 0, 0);
 	tcpm_set_polarity(port, TYPEC_POLARITY_CC1);
+	tcpm_mux_set(port, TYPEC_MUX_NONE, USB_ROLE_NONE);
 	tcpm_set_attached_state(port, false);
 	port->try_src_count = 0;
 	port->try_snk_count = 0;
@@ -2190,8 +2192,6 @@ static int tcpm_snk_attach(struct tcpm_port *port)
 static void tcpm_snk_detach(struct tcpm_port *port)
 {
 	tcpm_detach(port);
-
-	/* XXX: (Dis)connect SuperSpeed mux? */
 }
 
 static int tcpm_acc_attach(struct tcpm_port *port)
diff --git a/include/linux/usb/tcpm.h b/include/linux/usb/tcpm.h
index ca1c0b57f03f..268721bff2c1 100644
--- a/include/linux/usb/tcpm.h
+++ b/include/linux/usb/tcpm.h
@@ -16,6 +16,7 @@
 #define __LINUX_USB_TCPM_H
 
 #include <linux/bitops.h>
+#include <linux/usb/role.h>
 #include <linux/usb/typec.h>
 #include "pd.h"
 
@@ -97,11 +98,6 @@ struct tcpc_config {
 	const struct typec_altmode_desc *alt_modes;
 };
 
-enum tcpc_usb_switch {
-	TCPC_USB_SWITCH_CONNECT,
-	TCPC_USB_SWITCH_DISCONNECT,
-};
-
 /* Mux state attributes */
 #define TCPC_MUX_USB_ENABLED		BIT(0)	/* USB enabled */
 #define TCPC_MUX_DP_ENABLED		BIT(1)	/* DP enabled */
@@ -118,7 +114,7 @@ enum tcpc_mux_mode {
 
 struct tcpc_mux_dev {
 	int (*set)(struct tcpc_mux_dev *dev, enum tcpc_mux_mode mux_mode,
-		   enum tcpc_usb_switch usb_config,
+		   enum usb_role usb_role,
 		   enum typec_cc_polarity polarity);
 	bool dfp_only;
 	void *priv_data;

^ permalink raw reply related	[flat|nested] 43+ messages in thread

* [PATCH v5 06/12] usb: typec: tcpm: Use new Type-C switch/mux and usb-role-switch functions
@ 2018-02-28 15:07   ` Hans de Goede
  0 siblings, 0 replies; 43+ messages in thread
From: Hans de Goede @ 2018-02-28 15:07 UTC (permalink / raw)
  To: Darren Hart, Andy Shevchenko, MyungJoo Ham, Chanwoo Choi,
	Mathias Nyman, Heikki Krogerus, Greg Kroah-Hartman,
	Guenter Roeck
  Cc: Hans de Goede, platform-driver-x86, linux-kernel, linux-usb

Remove the unused (not implemented anywhere) tcpc_mux_dev abstraction
and replace it with calling the new typec_set_orientation,
usb_role_switch_set and typec_set_mode functions.

Reviewed-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Reviewed-by: Guenter Roeck <linux@roeck-us.net>
Reviewed-by: Andy Shevchenko <andy.shevchenko@gmail.com>
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
---
Changes in v4:
-Add Andy's Reviewed-by

Changes in v3:
-Add Guenter's Reviewed-by

Changes in v2:
-Added Heikki's Reviewed-by
---
 drivers/usb/typec/Kconfig           |  1 +
 drivers/usb/typec/fusb302/fusb302.c |  1 -
 drivers/usb/typec/tcpm.c            | 46 ++++++++++++++++++++++++++++---------
 include/linux/usb/tcpm.h            | 10 --------
 4 files changed, 36 insertions(+), 22 deletions(-)

diff --git a/drivers/usb/typec/Kconfig b/drivers/usb/typec/Kconfig
index bcb2744c5977..a2a0684e7c82 100644
--- a/drivers/usb/typec/Kconfig
+++ b/drivers/usb/typec/Kconfig
@@ -48,6 +48,7 @@ if TYPEC
 config TYPEC_TCPM
 	tristate "USB Type-C Port Controller Manager"
 	depends on USB
+	select USB_ROLE_SWITCH
 	help
 	  The Type-C Port Controller Manager provides a USB PD and USB Type-C
 	  state machine for use with Type-C Port Controllers.
diff --git a/drivers/usb/typec/fusb302/fusb302.c b/drivers/usb/typec/fusb302/fusb302.c
index dcd8ef085b30..a7b06053a538 100644
--- a/drivers/usb/typec/fusb302/fusb302.c
+++ b/drivers/usb/typec/fusb302/fusb302.c
@@ -1249,7 +1249,6 @@ static void init_tcpc_dev(struct tcpc_dev *fusb302_tcpc_dev)
 	fusb302_tcpc_dev->set_roles = tcpm_set_roles;
 	fusb302_tcpc_dev->start_drp_toggling = tcpm_start_drp_toggling;
 	fusb302_tcpc_dev->pd_transmit = tcpm_pd_transmit;
-	fusb302_tcpc_dev->mux = NULL;
 }
 
 static const char * const cc_polarity_name[] = {
diff --git a/drivers/usb/typec/tcpm.c b/drivers/usb/typec/tcpm.c
index 00ca2822432f..bfcaf6618a1f 100644
--- a/drivers/usb/typec/tcpm.c
+++ b/drivers/usb/typec/tcpm.c
@@ -20,6 +20,7 @@
 #include <linux/usb/pd.h>
 #include <linux/usb/pd_bdo.h>
 #include <linux/usb/pd_vdo.h>
+#include <linux/usb/role.h>
 #include <linux/usb/tcpm.h>
 #include <linux/usb/typec.h>
 #include <linux/workqueue.h>
@@ -176,6 +177,7 @@ struct tcpm_port {
 	struct typec_port *typec_port;
 
 	struct tcpc_dev	*tcpc;
+	struct usb_role_switch *role_sw;
 
 	enum typec_role vconn_role;
 	enum typec_role pwr_role;
@@ -618,18 +620,25 @@ void tcpm_pd_transmit_complete(struct tcpm_port *port,
 EXPORT_SYMBOL_GPL(tcpm_pd_transmit_complete);
 
 static int tcpm_mux_set(struct tcpm_port *port, enum tcpc_mux_mode mode,
-			enum usb_role usb_role)
+			enum usb_role usb_role,
+			enum typec_orientation orientation)
 {
-	int ret = 0;
+	int ret;
 
-	tcpm_log(port, "Requesting mux mode %d, usb-role %d, polarity %d",
-		 mode, usb_role, port->polarity);
+	tcpm_log(port, "Requesting mux mode %d, usb-role %d, orientation %d",
+		 mode, usb_role, orientation);
 
-	if (port->tcpc->mux)
-		ret = port->tcpc->mux->set(port->tcpc->mux, mode, usb_role,
-					   port->polarity);
+	ret = typec_set_orientation(port->typec_port, orientation);
+	if (ret)
+		return ret;
 
-	return ret;
+	if (port->role_sw) {
+		ret = usb_role_switch_set_role(port->role_sw, usb_role);
+		if (ret)
+			return ret;
+	}
+
+	return typec_set_mode(port->typec_port, mode);
 }
 
 static int tcpm_set_polarity(struct tcpm_port *port,
@@ -742,15 +751,21 @@ static int tcpm_set_attached_state(struct tcpm_port *port, bool attached)
 static int tcpm_set_roles(struct tcpm_port *port, bool attached,
 			  enum typec_role role, enum typec_data_role data)
 {
+	enum typec_orientation orientation;
 	enum usb_role usb_role;
 	int ret;
 
+	if (port->polarity == TYPEC_POLARITY_CC1)
+		orientation = TYPEC_ORIENTATION_NORMAL;
+	else
+		orientation = TYPEC_ORIENTATION_REVERSE;
+
 	if (data == TYPEC_HOST)
 		usb_role = USB_ROLE_HOST;
 	else
 		usb_role = USB_ROLE_DEVICE;
 
-	ret = tcpm_mux_set(port, TYPEC_MUX_USB, usb_role);
+	ret = tcpm_mux_set(port, TYPEC_MUX_USB, usb_role, orientation);
 	if (ret < 0)
 		return ret;
 
@@ -2097,7 +2112,8 @@ static int tcpm_src_attach(struct tcpm_port *port)
 out_disable_pd:
 	port->tcpc->set_pd_rx(port->tcpc, false);
 out_disable_mux:
-	tcpm_mux_set(port, TYPEC_MUX_NONE, USB_ROLE_NONE);
+	tcpm_mux_set(port, TYPEC_MUX_NONE, USB_ROLE_NONE,
+		     TYPEC_ORIENTATION_NONE);
 	return ret;
 }
 
@@ -2141,7 +2157,8 @@ static void tcpm_reset_port(struct tcpm_port *port)
 	tcpm_init_vconn(port);
 	tcpm_set_current_limit(port, 0, 0);
 	tcpm_set_polarity(port, TYPEC_POLARITY_CC1);
-	tcpm_mux_set(port, TYPEC_MUX_NONE, USB_ROLE_NONE);
+	tcpm_mux_set(port, TYPEC_MUX_NONE, USB_ROLE_NONE,
+		     TYPEC_ORIENTATION_NONE);
 	tcpm_set_attached_state(port, false);
 	port->try_src_count = 0;
 	port->try_snk_count = 0;
@@ -3742,6 +3759,12 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc)
 	port->partner_desc.identity = &port->partner_ident;
 	port->port_type = tcpc->config->type;
 
+	port->role_sw = usb_role_switch_get(port->dev);
+	if (IS_ERR(port->role_sw)) {
+		err = PTR_ERR(port->role_sw);
+		goto out_destroy_wq;
+	}
+
 	port->typec_port = typec_register_port(port->dev, &port->typec_caps);
 	if (IS_ERR(port->typec_port)) {
 		err = PTR_ERR(port->typec_port);
@@ -3777,6 +3800,7 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc)
 	return port;
 
 out_destroy_wq:
+	usb_role_switch_put(port->role_sw);
 	destroy_workqueue(port->wq);
 	return ERR_PTR(err);
 }
diff --git a/include/linux/usb/tcpm.h b/include/linux/usb/tcpm.h
index 268721bff2c1..fe3508e6e1df 100644
--- a/include/linux/usb/tcpm.h
+++ b/include/linux/usb/tcpm.h
@@ -16,7 +16,6 @@
 #define __LINUX_USB_TCPM_H
 
 #include <linux/bitops.h>
-#include <linux/usb/role.h>
 #include <linux/usb/typec.h>
 #include "pd.h"
 
@@ -112,14 +111,6 @@ enum tcpc_mux_mode {
 			  TCPC_MUX_DP_ENABLED,
 };
 
-struct tcpc_mux_dev {
-	int (*set)(struct tcpc_mux_dev *dev, enum tcpc_mux_mode mux_mode,
-		   enum usb_role usb_role,
-		   enum typec_cc_polarity polarity);
-	bool dfp_only;
-	void *priv_data;
-};
-
 /**
  * struct tcpc_dev - Port configuration and callback functions
  * @config:	Pointer to port configuration
@@ -171,7 +162,6 @@ struct tcpc_dev {
 	int (*try_role)(struct tcpc_dev *dev, int role);
 	int (*pd_transmit)(struct tcpc_dev *dev, enum tcpm_transmit_type type,
 			   const struct pd_message *msg);
-	struct tcpc_mux_dev *mux;
 };
 
 struct tcpm_port;
-- 
2.14.3

^ permalink raw reply related	[flat|nested] 43+ messages in thread

* [v5,06/12] usb: typec: tcpm: Use new Type-C switch/mux and usb-role-switch functions
@ 2018-02-28 15:07   ` Hans de Goede
  0 siblings, 0 replies; 43+ messages in thread
From: Hans de Goede @ 2018-02-28 15:07 UTC (permalink / raw)
  To: Darren Hart, Andy Shevchenko, MyungJoo Ham, Chanwoo Choi,
	Mathias Nyman, Heikki Krogerus, Greg Kroah-Hartman,
	Guenter Roeck
  Cc: Hans de Goede, platform-driver-x86, linux-kernel, linux-usb

Remove the unused (not implemented anywhere) tcpc_mux_dev abstraction
and replace it with calling the new typec_set_orientation,
usb_role_switch_set and typec_set_mode functions.

Reviewed-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Reviewed-by: Guenter Roeck <linux@roeck-us.net>
Reviewed-by: Andy Shevchenko <andy.shevchenko@gmail.com>
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
---
Changes in v4:
-Add Andy's Reviewed-by

Changes in v3:
-Add Guenter's Reviewed-by

Changes in v2:
-Added Heikki's Reviewed-by
---
 drivers/usb/typec/Kconfig           |  1 +
 drivers/usb/typec/fusb302/fusb302.c |  1 -
 drivers/usb/typec/tcpm.c            | 46 ++++++++++++++++++++++++++++---------
 include/linux/usb/tcpm.h            | 10 --------
 4 files changed, 36 insertions(+), 22 deletions(-)

diff --git a/drivers/usb/typec/Kconfig b/drivers/usb/typec/Kconfig
index bcb2744c5977..a2a0684e7c82 100644
--- a/drivers/usb/typec/Kconfig
+++ b/drivers/usb/typec/Kconfig
@@ -48,6 +48,7 @@ if TYPEC
 config TYPEC_TCPM
 	tristate "USB Type-C Port Controller Manager"
 	depends on USB
+	select USB_ROLE_SWITCH
 	help
 	  The Type-C Port Controller Manager provides a USB PD and USB Type-C
 	  state machine for use with Type-C Port Controllers.
diff --git a/drivers/usb/typec/fusb302/fusb302.c b/drivers/usb/typec/fusb302/fusb302.c
index dcd8ef085b30..a7b06053a538 100644
--- a/drivers/usb/typec/fusb302/fusb302.c
+++ b/drivers/usb/typec/fusb302/fusb302.c
@@ -1249,7 +1249,6 @@ static void init_tcpc_dev(struct tcpc_dev *fusb302_tcpc_dev)
 	fusb302_tcpc_dev->set_roles = tcpm_set_roles;
 	fusb302_tcpc_dev->start_drp_toggling = tcpm_start_drp_toggling;
 	fusb302_tcpc_dev->pd_transmit = tcpm_pd_transmit;
-	fusb302_tcpc_dev->mux = NULL;
 }
 
 static const char * const cc_polarity_name[] = {
diff --git a/drivers/usb/typec/tcpm.c b/drivers/usb/typec/tcpm.c
index 00ca2822432f..bfcaf6618a1f 100644
--- a/drivers/usb/typec/tcpm.c
+++ b/drivers/usb/typec/tcpm.c
@@ -20,6 +20,7 @@
 #include <linux/usb/pd.h>
 #include <linux/usb/pd_bdo.h>
 #include <linux/usb/pd_vdo.h>
+#include <linux/usb/role.h>
 #include <linux/usb/tcpm.h>
 #include <linux/usb/typec.h>
 #include <linux/workqueue.h>
@@ -176,6 +177,7 @@ struct tcpm_port {
 	struct typec_port *typec_port;
 
 	struct tcpc_dev	*tcpc;
+	struct usb_role_switch *role_sw;
 
 	enum typec_role vconn_role;
 	enum typec_role pwr_role;
@@ -618,18 +620,25 @@ void tcpm_pd_transmit_complete(struct tcpm_port *port,
 EXPORT_SYMBOL_GPL(tcpm_pd_transmit_complete);
 
 static int tcpm_mux_set(struct tcpm_port *port, enum tcpc_mux_mode mode,
-			enum usb_role usb_role)
+			enum usb_role usb_role,
+			enum typec_orientation orientation)
 {
-	int ret = 0;
+	int ret;
 
-	tcpm_log(port, "Requesting mux mode %d, usb-role %d, polarity %d",
-		 mode, usb_role, port->polarity);
+	tcpm_log(port, "Requesting mux mode %d, usb-role %d, orientation %d",
+		 mode, usb_role, orientation);
 
-	if (port->tcpc->mux)
-		ret = port->tcpc->mux->set(port->tcpc->mux, mode, usb_role,
-					   port->polarity);
+	ret = typec_set_orientation(port->typec_port, orientation);
+	if (ret)
+		return ret;
 
-	return ret;
+	if (port->role_sw) {
+		ret = usb_role_switch_set_role(port->role_sw, usb_role);
+		if (ret)
+			return ret;
+	}
+
+	return typec_set_mode(port->typec_port, mode);
 }
 
 static int tcpm_set_polarity(struct tcpm_port *port,
@@ -742,15 +751,21 @@ static int tcpm_set_attached_state(struct tcpm_port *port, bool attached)
 static int tcpm_set_roles(struct tcpm_port *port, bool attached,
 			  enum typec_role role, enum typec_data_role data)
 {
+	enum typec_orientation orientation;
 	enum usb_role usb_role;
 	int ret;
 
+	if (port->polarity == TYPEC_POLARITY_CC1)
+		orientation = TYPEC_ORIENTATION_NORMAL;
+	else
+		orientation = TYPEC_ORIENTATION_REVERSE;
+
 	if (data == TYPEC_HOST)
 		usb_role = USB_ROLE_HOST;
 	else
 		usb_role = USB_ROLE_DEVICE;
 
-	ret = tcpm_mux_set(port, TYPEC_MUX_USB, usb_role);
+	ret = tcpm_mux_set(port, TYPEC_MUX_USB, usb_role, orientation);
 	if (ret < 0)
 		return ret;
 
@@ -2097,7 +2112,8 @@ static int tcpm_src_attach(struct tcpm_port *port)
 out_disable_pd:
 	port->tcpc->set_pd_rx(port->tcpc, false);
 out_disable_mux:
-	tcpm_mux_set(port, TYPEC_MUX_NONE, USB_ROLE_NONE);
+	tcpm_mux_set(port, TYPEC_MUX_NONE, USB_ROLE_NONE,
+		     TYPEC_ORIENTATION_NONE);
 	return ret;
 }
 
@@ -2141,7 +2157,8 @@ static void tcpm_reset_port(struct tcpm_port *port)
 	tcpm_init_vconn(port);
 	tcpm_set_current_limit(port, 0, 0);
 	tcpm_set_polarity(port, TYPEC_POLARITY_CC1);
-	tcpm_mux_set(port, TYPEC_MUX_NONE, USB_ROLE_NONE);
+	tcpm_mux_set(port, TYPEC_MUX_NONE, USB_ROLE_NONE,
+		     TYPEC_ORIENTATION_NONE);
 	tcpm_set_attached_state(port, false);
 	port->try_src_count = 0;
 	port->try_snk_count = 0;
@@ -3742,6 +3759,12 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc)
 	port->partner_desc.identity = &port->partner_ident;
 	port->port_type = tcpc->config->type;
 
+	port->role_sw = usb_role_switch_get(port->dev);
+	if (IS_ERR(port->role_sw)) {
+		err = PTR_ERR(port->role_sw);
+		goto out_destroy_wq;
+	}
+
 	port->typec_port = typec_register_port(port->dev, &port->typec_caps);
 	if (IS_ERR(port->typec_port)) {
 		err = PTR_ERR(port->typec_port);
@@ -3777,6 +3800,7 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc)
 	return port;
 
 out_destroy_wq:
+	usb_role_switch_put(port->role_sw);
 	destroy_workqueue(port->wq);
 	return ERR_PTR(err);
 }
diff --git a/include/linux/usb/tcpm.h b/include/linux/usb/tcpm.h
index 268721bff2c1..fe3508e6e1df 100644
--- a/include/linux/usb/tcpm.h
+++ b/include/linux/usb/tcpm.h
@@ -16,7 +16,6 @@
 #define __LINUX_USB_TCPM_H
 
 #include <linux/bitops.h>
-#include <linux/usb/role.h>
 #include <linux/usb/typec.h>
 #include "pd.h"
 
@@ -112,14 +111,6 @@ enum tcpc_mux_mode {
 			  TCPC_MUX_DP_ENABLED,
 };
 
-struct tcpc_mux_dev {
-	int (*set)(struct tcpc_mux_dev *dev, enum tcpc_mux_mode mux_mode,
-		   enum usb_role usb_role,
-		   enum typec_cc_polarity polarity);
-	bool dfp_only;
-	void *priv_data;
-};
-
 /**
  * struct tcpc_dev - Port configuration and callback functions
  * @config:	Pointer to port configuration
@@ -171,7 +162,6 @@ struct tcpc_dev {
 	int (*try_role)(struct tcpc_dev *dev, int role);
 	int (*pd_transmit)(struct tcpc_dev *dev, enum tcpm_transmit_type type,
 			   const struct pd_message *msg);
-	struct tcpc_mux_dev *mux;
 };
 
 struct tcpm_port;

^ permalink raw reply related	[flat|nested] 43+ messages in thread

* [PATCH v5 07/12] xhci: Add option to get next extended capability in list by passing id = 0
@ 2018-02-28 15:07   ` Hans de Goede
  0 siblings, 0 replies; 43+ messages in thread
From: Hans de Goede @ 2018-02-28 15:07 UTC (permalink / raw)
  To: Darren Hart, Andy Shevchenko, MyungJoo Ham, Chanwoo Choi,
	Mathias Nyman, Heikki Krogerus, Greg Kroah-Hartman,
	Guenter Roeck
  Cc: Hans de Goede, platform-driver-x86, linux-kernel, linux-usb,
	Mathias Nyman

From: Mathias Nyman <mathias.nyman@linux.intel.com>

Modify xhci_find_next_ext_cap(base, offset, id) to return the next
capability offset if 0 is passed for id. Otherwise it will behave as
previously and return the offset of the next capability with matching id

capability id 0 is not used by xHCI (reserved)

This is useful when we want to loop through all capabilities.

Signed-off-by: Mathias Nyman <mathias.nyman@linux.intel.com>
Reviewed-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Reviewed-by: Andy Shevchenko <andy.shevchenko@gmail.com>
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
---
Changes in v4:
-Add Andy's Reviewed-by

Changes in v2:
-Added Heikki's Reviewed-by
---
 drivers/usb/host/xhci-ext-caps.h | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/drivers/usb/host/xhci-ext-caps.h b/drivers/usb/host/xhci-ext-caps.h
index bf7316e130d3..631e7cc62604 100644
--- a/drivers/usb/host/xhci-ext-caps.h
+++ b/drivers/usb/host/xhci-ext-caps.h
@@ -84,7 +84,8 @@
  * @base	PCI MMIO registers base address.
  * @start	address at which to start looking, (0 or HCC_PARAMS to start at
  *		beginning of list)
- * @id		Extended capability ID to search for.
+ * @id		Extended capability ID to search for, or 0 for the next
+ *		capability
  *
  * Returns the offset of the next matching extended capability structure.
  * Some capabilities can occur several times, e.g., the XHCI_EXT_CAPS_PROTOCOL,
@@ -110,7 +111,7 @@ static inline int xhci_find_next_ext_cap(void __iomem *base, u32 start, int id)
 		val = readl(base + offset);
 		if (val == ~0)
 			return 0;
-		if (XHCI_EXT_CAPS_ID(val) == id && offset != start)
+		if (offset != start && (id == 0 || XHCI_EXT_CAPS_ID(val) == id))
 			return offset;
 
 		next = XHCI_EXT_CAPS_NEXT(val);
-- 
2.14.3

^ permalink raw reply related	[flat|nested] 43+ messages in thread

* [v5,07/12] xhci: Add option to get next extended capability in list by passing id = 0
@ 2018-02-28 15:07   ` Hans de Goede
  0 siblings, 0 replies; 43+ messages in thread
From: Hans de Goede @ 2018-02-28 15:07 UTC (permalink / raw)
  To: Darren Hart, Andy Shevchenko, MyungJoo Ham, Chanwoo Choi,
	Mathias Nyman, Heikki Krogerus, Greg Kroah-Hartman,
	Guenter Roeck
  Cc: Hans de Goede, platform-driver-x86, linux-kernel, linux-usb,
	Mathias Nyman

From: Mathias Nyman <mathias.nyman@linux.intel.com>

Modify xhci_find_next_ext_cap(base, offset, id) to return the next
capability offset if 0 is passed for id. Otherwise it will behave as
previously and return the offset of the next capability with matching id

capability id 0 is not used by xHCI (reserved)

This is useful when we want to loop through all capabilities.

Signed-off-by: Mathias Nyman <mathias.nyman@linux.intel.com>
Reviewed-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Reviewed-by: Andy Shevchenko <andy.shevchenko@gmail.com>
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
---
Changes in v4:
-Add Andy's Reviewed-by

Changes in v2:
-Added Heikki's Reviewed-by
---
 drivers/usb/host/xhci-ext-caps.h | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/drivers/usb/host/xhci-ext-caps.h b/drivers/usb/host/xhci-ext-caps.h
index bf7316e130d3..631e7cc62604 100644
--- a/drivers/usb/host/xhci-ext-caps.h
+++ b/drivers/usb/host/xhci-ext-caps.h
@@ -84,7 +84,8 @@
  * @base	PCI MMIO registers base address.
  * @start	address at which to start looking, (0 or HCC_PARAMS to start at
  *		beginning of list)
- * @id		Extended capability ID to search for.
+ * @id		Extended capability ID to search for, or 0 for the next
+ *		capability
  *
  * Returns the offset of the next matching extended capability structure.
  * Some capabilities can occur several times, e.g., the XHCI_EXT_CAPS_PROTOCOL,
@@ -110,7 +111,7 @@ static inline int xhci_find_next_ext_cap(void __iomem *base, u32 start, int id)
 		val = readl(base + offset);
 		if (val == ~0)
 			return 0;
-		if (XHCI_EXT_CAPS_ID(val) == id && offset != start)
+		if (offset != start && (id == 0 || XHCI_EXT_CAPS_ID(val) == id))
 			return offset;
 
 		next = XHCI_EXT_CAPS_NEXT(val);

^ permalink raw reply related	[flat|nested] 43+ messages in thread

* [PATCH v5 08/12] xhci: Add Intel extended cap / otg phy mux handling
@ 2018-02-28 15:07   ` Hans de Goede
  0 siblings, 0 replies; 43+ messages in thread
From: Hans de Goede @ 2018-02-28 15:07 UTC (permalink / raw)
  To: Darren Hart, Andy Shevchenko, MyungJoo Ham, Chanwoo Choi,
	Mathias Nyman, Heikki Krogerus, Greg Kroah-Hartman,
	Guenter Roeck
  Cc: Hans de Goede, platform-driver-x86, linux-kernel, linux-usb

The xHCI controller on various Intel SoCs has an extended cap mmio-range
which contains registers to control the muxing to the xHCI (host mode)
or the dwc3 (device mode) and vbus-detection for the otg usb-phy.

Having a role-sw driver included in the xHCI code (under drivers/usb/host)
is not desirable. So this commit adds a simple handler for this extended
capability, which creates a platform device with the caps mmio region as
resource, this allows us to write a separate platform role-sw driver for
the role-switch.

Note this commit adds a call to the new xhci_ext_cap_init() function
to xhci_pci_probe(), it is added here because xhci_ext_cap_init() must
be called only once. If in the future we also want to handle ext-caps
on non pci xHCI HCDs from xhci_ext_cap_init() a call to it should also
be added to other bus probe paths.

Acked-by: Mathias Nyman <mathias.nyman@linux.intel.com>
Reviewed-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Reviewed-by: Andy Shevchenko <andy.shevchenko@gmail.com>
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
---
Changes in v4:
-Add Andy's Reviewed-by

Changes in v2:
-Use SPDX license header
-Various small style cleanups / changes
-Add Heikki's Reviewed-by

Changes from some time ago when this patch was part of another patch-set:
-Check xHCI controller PCI device-id instead of only checking for the
 Intel Extended capability ID, as the Extended capability ID is used on
 other model Intel xHCI controllers too
-Add a new generic xhci_ext_cap_init() function and handle the new
 XHCI_INTEL_CHT_USB_MUX quirk there.
-Stop using Cherry Trail / CHT in various places as other Intel SoCs
 (e.g. Broxton / Apollo Lake) also have this
---
 drivers/usb/host/Makefile        |  2 +-
 drivers/usb/host/xhci-ext-caps.c | 90 ++++++++++++++++++++++++++++++++++++++++
 drivers/usb/host/xhci-ext-caps.h |  2 +
 drivers/usb/host/xhci-pci.c      |  5 +++
 drivers/usb/host/xhci.h          |  2 +
 5 files changed, 100 insertions(+), 1 deletion(-)
 create mode 100644 drivers/usb/host/xhci-ext-caps.c

diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile
index 4ede4ce12366..8a8cffe0b445 100644
--- a/drivers/usb/host/Makefile
+++ b/drivers/usb/host/Makefile
@@ -11,7 +11,7 @@ fhci-y += fhci-mem.o fhci-tds.o fhci-sched.o
 
 fhci-$(CONFIG_FHCI_DEBUG) += fhci-dbg.o
 
-xhci-hcd-y := xhci.o xhci-mem.o
+xhci-hcd-y := xhci.o xhci-mem.o xhci-ext-caps.o
 xhci-hcd-y += xhci-ring.o xhci-hub.o xhci-dbg.o
 xhci-hcd-y += xhci-trace.o
 
diff --git a/drivers/usb/host/xhci-ext-caps.c b/drivers/usb/host/xhci-ext-caps.c
new file mode 100644
index 000000000000..399113f9fc5c
--- /dev/null
+++ b/drivers/usb/host/xhci-ext-caps.c
@@ -0,0 +1,90 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * XHCI extended capability handling
+ *
+ * Copyright (c) 2017 Hans de Goede <hdegoede@redhat.com>
+ */
+
+#include <linux/platform_device.h>
+#include "xhci.h"
+
+#define USB_SW_DRV_NAME		"intel_xhci_usb_sw"
+#define USB_SW_RESOURCE_SIZE	0x400
+
+static void xhci_intel_unregister_pdev(void *arg)
+{
+	platform_device_unregister(arg);
+}
+
+static int xhci_create_intel_xhci_sw_pdev(struct xhci_hcd *xhci, u32 cap_offset)
+{
+	struct usb_hcd *hcd = xhci_to_hcd(xhci);
+	struct device *dev = hcd->self.controller;
+	struct platform_device *pdev;
+	struct resource	res = { 0, };
+	int ret;
+
+	pdev = platform_device_alloc(USB_SW_DRV_NAME, PLATFORM_DEVID_NONE);
+	if (!pdev) {
+		xhci_err(xhci, "couldn't allocate %s platform device\n",
+			 USB_SW_DRV_NAME);
+		return -ENOMEM;
+	}
+
+	res.start = hcd->rsrc_start + cap_offset;
+	res.end	  = res.start + USB_SW_RESOURCE_SIZE - 1;
+	res.name  = USB_SW_DRV_NAME;
+	res.flags = IORESOURCE_MEM;
+
+	ret = platform_device_add_resources(pdev, &res, 1);
+	if (ret) {
+		dev_err(dev, "couldn't add resources to intel_xhci_usb_sw pdev\n");
+		platform_device_put(pdev);
+		return ret;
+	}
+
+	pdev->dev.parent = dev;
+
+	ret = platform_device_add(pdev);
+	if (ret) {
+		dev_err(dev, "couldn't register intel_xhci_usb_sw pdev\n");
+		platform_device_put(pdev);
+		return ret;
+	}
+
+	ret = devm_add_action_or_reset(dev, xhci_intel_unregister_pdev, pdev);
+	if (ret) {
+		dev_err(dev, "couldn't add unregister action for intel_xhci_usb_sw pdev\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+int xhci_ext_cap_init(struct xhci_hcd *xhci)
+{
+	void __iomem *base = &xhci->cap_regs->hc_capbase;
+	u32 offset, val;
+	int ret;
+
+	offset = xhci_find_next_ext_cap(base, 0, 0);
+
+	while (offset) {
+		val = readl(base + offset);
+
+		switch (XHCI_EXT_CAPS_ID(val)) {
+		case XHCI_EXT_CAPS_VENDOR_INTEL:
+			if (xhci->quirks & XHCI_INTEL_USB_ROLE_SW) {
+				ret = xhci_create_intel_xhci_sw_pdev(xhci,
+								     offset);
+				if (ret)
+					return ret;
+			}
+			break;
+		}
+		offset = xhci_find_next_ext_cap(base, offset, 0);
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(xhci_ext_cap_init);
diff --git a/drivers/usb/host/xhci-ext-caps.h b/drivers/usb/host/xhci-ext-caps.h
index 631e7cc62604..268328c20681 100644
--- a/drivers/usb/host/xhci-ext-caps.h
+++ b/drivers/usb/host/xhci-ext-caps.h
@@ -39,6 +39,8 @@
 #define XHCI_EXT_CAPS_ROUTE	5
 /* IDs 6-9 reserved */
 #define XHCI_EXT_CAPS_DEBUG	10
+/* Vendor caps */
+#define XHCI_EXT_CAPS_VENDOR_INTEL	192
 /* USB Legacy Support Capability - section 7.1.1 */
 #define XHCI_HC_BIOS_OWNED	(1 << 16)
 #define XHCI_HC_OS_OWNED	(1 << 24)
diff --git a/drivers/usb/host/xhci-pci.c b/drivers/usb/host/xhci-pci.c
index 6c79037876db..4486640d925c 100644
--- a/drivers/usb/host/xhci-pci.c
+++ b/drivers/usb/host/xhci-pci.c
@@ -164,6 +164,7 @@ static void xhci_pci_quirks(struct device *dev, struct xhci_hcd *xhci)
 	if (pdev->vendor == PCI_VENDOR_ID_INTEL &&
 		 pdev->device == PCI_DEVICE_ID_INTEL_CHERRYVIEW_XHCI) {
 		xhci->quirks |= XHCI_SSIC_PORT_UNUSED;
+		xhci->quirks |= XHCI_INTEL_USB_ROLE_SW;
 	}
 	if (pdev->vendor == PCI_VENDOR_ID_INTEL &&
 	    (pdev->device == PCI_DEVICE_ID_INTEL_CHERRYVIEW_XHCI ||
@@ -297,6 +298,10 @@ static int xhci_pci_probe(struct pci_dev *dev, const struct pci_device_id *id)
 		goto dealloc_usb2_hcd;
 	}
 
+	retval = xhci_ext_cap_init(xhci);
+	if (retval)
+		goto put_usb3_hcd;
+
 	retval = usb_add_hcd(xhci->shared_hcd, dev->irq,
 			IRQF_SHARED);
 	if (retval)
diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h
index 96099a245c69..5917e3095e2a 100644
--- a/drivers/usb/host/xhci.h
+++ b/drivers/usb/host/xhci.h
@@ -1825,6 +1825,7 @@ struct xhci_hcd {
 /* Reserved. It was XHCI_U2_DISABLE_WAKE */
 #define XHCI_ASMEDIA_MODIFY_FLOWCONTROL	(1 << 28)
 #define XHCI_HW_LPM_DISABLE	(1 << 29)
+#define XHCI_INTEL_USB_ROLE_SW	(1 << 30)
 
 	unsigned int		num_active_eps;
 	unsigned int		limit_active_eps;
@@ -2020,6 +2021,7 @@ int xhci_gen_setup(struct usb_hcd *hcd, xhci_get_quirks_t get_quirks);
 void xhci_init_driver(struct hc_driver *drv,
 		      const struct xhci_driver_overrides *over);
 int xhci_disable_slot(struct xhci_hcd *xhci, u32 slot_id);
+int xhci_ext_cap_init(struct xhci_hcd *xhci);
 
 int xhci_suspend(struct xhci_hcd *xhci, bool do_wakeup);
 int xhci_resume(struct xhci_hcd *xhci, bool hibernated);
-- 
2.14.3

^ permalink raw reply related	[flat|nested] 43+ messages in thread

* [v5,08/12] xhci: Add Intel extended cap / otg phy mux handling
@ 2018-02-28 15:07   ` Hans de Goede
  0 siblings, 0 replies; 43+ messages in thread
From: Hans de Goede @ 2018-02-28 15:07 UTC (permalink / raw)
  To: Darren Hart, Andy Shevchenko, MyungJoo Ham, Chanwoo Choi,
	Mathias Nyman, Heikki Krogerus, Greg Kroah-Hartman,
	Guenter Roeck
  Cc: Hans de Goede, platform-driver-x86, linux-kernel, linux-usb

The xHCI controller on various Intel SoCs has an extended cap mmio-range
which contains registers to control the muxing to the xHCI (host mode)
or the dwc3 (device mode) and vbus-detection for the otg usb-phy.

Having a role-sw driver included in the xHCI code (under drivers/usb/host)
is not desirable. So this commit adds a simple handler for this extended
capability, which creates a platform device with the caps mmio region as
resource, this allows us to write a separate platform role-sw driver for
the role-switch.

Note this commit adds a call to the new xhci_ext_cap_init() function
to xhci_pci_probe(), it is added here because xhci_ext_cap_init() must
be called only once. If in the future we also want to handle ext-caps
on non pci xHCI HCDs from xhci_ext_cap_init() a call to it should also
be added to other bus probe paths.

Acked-by: Mathias Nyman <mathias.nyman@linux.intel.com>
Reviewed-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Reviewed-by: Andy Shevchenko <andy.shevchenko@gmail.com>
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
---
Changes in v4:
-Add Andy's Reviewed-by

Changes in v2:
-Use SPDX license header
-Various small style cleanups / changes
-Add Heikki's Reviewed-by

Changes from some time ago when this patch was part of another patch-set:
-Check xHCI controller PCI device-id instead of only checking for the
 Intel Extended capability ID, as the Extended capability ID is used on
 other model Intel xHCI controllers too
-Add a new generic xhci_ext_cap_init() function and handle the new
 XHCI_INTEL_CHT_USB_MUX quirk there.
-Stop using Cherry Trail / CHT in various places as other Intel SoCs
 (e.g. Broxton / Apollo Lake) also have this
---
 drivers/usb/host/Makefile        |  2 +-
 drivers/usb/host/xhci-ext-caps.c | 90 ++++++++++++++++++++++++++++++++++++++++
 drivers/usb/host/xhci-ext-caps.h |  2 +
 drivers/usb/host/xhci-pci.c      |  5 +++
 drivers/usb/host/xhci.h          |  2 +
 5 files changed, 100 insertions(+), 1 deletion(-)
 create mode 100644 drivers/usb/host/xhci-ext-caps.c

diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile
index 4ede4ce12366..8a8cffe0b445 100644
--- a/drivers/usb/host/Makefile
+++ b/drivers/usb/host/Makefile
@@ -11,7 +11,7 @@ fhci-y += fhci-mem.o fhci-tds.o fhci-sched.o
 
 fhci-$(CONFIG_FHCI_DEBUG) += fhci-dbg.o
 
-xhci-hcd-y := xhci.o xhci-mem.o
+xhci-hcd-y := xhci.o xhci-mem.o xhci-ext-caps.o
 xhci-hcd-y += xhci-ring.o xhci-hub.o xhci-dbg.o
 xhci-hcd-y += xhci-trace.o
 
diff --git a/drivers/usb/host/xhci-ext-caps.c b/drivers/usb/host/xhci-ext-caps.c
new file mode 100644
index 000000000000..399113f9fc5c
--- /dev/null
+++ b/drivers/usb/host/xhci-ext-caps.c
@@ -0,0 +1,90 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * XHCI extended capability handling
+ *
+ * Copyright (c) 2017 Hans de Goede <hdegoede@redhat.com>
+ */
+
+#include <linux/platform_device.h>
+#include "xhci.h"
+
+#define USB_SW_DRV_NAME		"intel_xhci_usb_sw"
+#define USB_SW_RESOURCE_SIZE	0x400
+
+static void xhci_intel_unregister_pdev(void *arg)
+{
+	platform_device_unregister(arg);
+}
+
+static int xhci_create_intel_xhci_sw_pdev(struct xhci_hcd *xhci, u32 cap_offset)
+{
+	struct usb_hcd *hcd = xhci_to_hcd(xhci);
+	struct device *dev = hcd->self.controller;
+	struct platform_device *pdev;
+	struct resource	res = { 0, };
+	int ret;
+
+	pdev = platform_device_alloc(USB_SW_DRV_NAME, PLATFORM_DEVID_NONE);
+	if (!pdev) {
+		xhci_err(xhci, "couldn't allocate %s platform device\n",
+			 USB_SW_DRV_NAME);
+		return -ENOMEM;
+	}
+
+	res.start = hcd->rsrc_start + cap_offset;
+	res.end	  = res.start + USB_SW_RESOURCE_SIZE - 1;
+	res.name  = USB_SW_DRV_NAME;
+	res.flags = IORESOURCE_MEM;
+
+	ret = platform_device_add_resources(pdev, &res, 1);
+	if (ret) {
+		dev_err(dev, "couldn't add resources to intel_xhci_usb_sw pdev\n");
+		platform_device_put(pdev);
+		return ret;
+	}
+
+	pdev->dev.parent = dev;
+
+	ret = platform_device_add(pdev);
+	if (ret) {
+		dev_err(dev, "couldn't register intel_xhci_usb_sw pdev\n");
+		platform_device_put(pdev);
+		return ret;
+	}
+
+	ret = devm_add_action_or_reset(dev, xhci_intel_unregister_pdev, pdev);
+	if (ret) {
+		dev_err(dev, "couldn't add unregister action for intel_xhci_usb_sw pdev\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+int xhci_ext_cap_init(struct xhci_hcd *xhci)
+{
+	void __iomem *base = &xhci->cap_regs->hc_capbase;
+	u32 offset, val;
+	int ret;
+
+	offset = xhci_find_next_ext_cap(base, 0, 0);
+
+	while (offset) {
+		val = readl(base + offset);
+
+		switch (XHCI_EXT_CAPS_ID(val)) {
+		case XHCI_EXT_CAPS_VENDOR_INTEL:
+			if (xhci->quirks & XHCI_INTEL_USB_ROLE_SW) {
+				ret = xhci_create_intel_xhci_sw_pdev(xhci,
+								     offset);
+				if (ret)
+					return ret;
+			}
+			break;
+		}
+		offset = xhci_find_next_ext_cap(base, offset, 0);
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(xhci_ext_cap_init);
diff --git a/drivers/usb/host/xhci-ext-caps.h b/drivers/usb/host/xhci-ext-caps.h
index 631e7cc62604..268328c20681 100644
--- a/drivers/usb/host/xhci-ext-caps.h
+++ b/drivers/usb/host/xhci-ext-caps.h
@@ -39,6 +39,8 @@
 #define XHCI_EXT_CAPS_ROUTE	5
 /* IDs 6-9 reserved */
 #define XHCI_EXT_CAPS_DEBUG	10
+/* Vendor caps */
+#define XHCI_EXT_CAPS_VENDOR_INTEL	192
 /* USB Legacy Support Capability - section 7.1.1 */
 #define XHCI_HC_BIOS_OWNED	(1 << 16)
 #define XHCI_HC_OS_OWNED	(1 << 24)
diff --git a/drivers/usb/host/xhci-pci.c b/drivers/usb/host/xhci-pci.c
index 6c79037876db..4486640d925c 100644
--- a/drivers/usb/host/xhci-pci.c
+++ b/drivers/usb/host/xhci-pci.c
@@ -164,6 +164,7 @@ static void xhci_pci_quirks(struct device *dev, struct xhci_hcd *xhci)
 	if (pdev->vendor == PCI_VENDOR_ID_INTEL &&
 		 pdev->device == PCI_DEVICE_ID_INTEL_CHERRYVIEW_XHCI) {
 		xhci->quirks |= XHCI_SSIC_PORT_UNUSED;
+		xhci->quirks |= XHCI_INTEL_USB_ROLE_SW;
 	}
 	if (pdev->vendor == PCI_VENDOR_ID_INTEL &&
 	    (pdev->device == PCI_DEVICE_ID_INTEL_CHERRYVIEW_XHCI ||
@@ -297,6 +298,10 @@ static int xhci_pci_probe(struct pci_dev *dev, const struct pci_device_id *id)
 		goto dealloc_usb2_hcd;
 	}
 
+	retval = xhci_ext_cap_init(xhci);
+	if (retval)
+		goto put_usb3_hcd;
+
 	retval = usb_add_hcd(xhci->shared_hcd, dev->irq,
 			IRQF_SHARED);
 	if (retval)
diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h
index 96099a245c69..5917e3095e2a 100644
--- a/drivers/usb/host/xhci.h
+++ b/drivers/usb/host/xhci.h
@@ -1825,6 +1825,7 @@ struct xhci_hcd {
 /* Reserved. It was XHCI_U2_DISABLE_WAKE */
 #define XHCI_ASMEDIA_MODIFY_FLOWCONTROL	(1 << 28)
 #define XHCI_HW_LPM_DISABLE	(1 << 29)
+#define XHCI_INTEL_USB_ROLE_SW	(1 << 30)
 
 	unsigned int		num_active_eps;
 	unsigned int		limit_active_eps;
@@ -2020,6 +2021,7 @@ int xhci_gen_setup(struct usb_hcd *hcd, xhci_get_quirks_t get_quirks);
 void xhci_init_driver(struct hc_driver *drv,
 		      const struct xhci_driver_overrides *over);
 int xhci_disable_slot(struct xhci_hcd *xhci, u32 slot_id);
+int xhci_ext_cap_init(struct xhci_hcd *xhci);
 
 int xhci_suspend(struct xhci_hcd *xhci, bool do_wakeup);
 int xhci_resume(struct xhci_hcd *xhci, bool hibernated);

^ permalink raw reply related	[flat|nested] 43+ messages in thread

* [PATCH v5 09/12] usb: roles: Add Intel xHCI USB role switch driver
@ 2018-02-28 15:07   ` Hans de Goede
  0 siblings, 0 replies; 43+ messages in thread
From: Hans de Goede @ 2018-02-28 15:07 UTC (permalink / raw)
  To: Darren Hart, Andy Shevchenko, MyungJoo Ham, Chanwoo Choi,
	Mathias Nyman, Heikki Krogerus, Greg Kroah-Hartman,
	Guenter Roeck
  Cc: Hans de Goede, platform-driver-x86, linux-kernel, linux-usb

Various Intel SoCs (Cherry Trail, Broxton and others) have an internal USB
role switch for swiching the OTG USB data lines between the xHCI host
controller and the dwc3 gadget controller.

Note on some Cherry Trail systems there is ACPI/AML code listening to
edge interrupts on the id-pin (through an _AIE ACPI method) and switching
the role between ROLE_HOST and ROLE_NONE based on the id-pin. Note it does
not set the role to ROLE_DEVICE, because device-mode is usually not used
under Windows.

The presence of AML code which modifies the cfg0 reg (on some systems)
means that our read/write/modify of cfg0 may race with the AML code
doing the same to avoid this we take the global ACPI lock while doing
the read/write/modify.

Reviewed-by: Andy Shevchenko <andy.shevchenko@gmail.com>
Reviewed-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
---
Changes in v4:
-Add Andy's Reviewed-by
-Add Heikki's Reviewed-by

Changes in v2:
-Drop unnecessary depends on EXTCON from Kconfig
-Use BIT(), resource_size()
-Various other small style fixes
---
 MAINTAINERS                                    |   6 +
 drivers/usb/Kconfig                            |   2 +
 drivers/usb/Makefile                           |   2 +
 drivers/usb/roles/Kconfig                      |  14 ++
 drivers/usb/roles/Makefile                     |   1 +
 drivers/usb/roles/intel-xhci-usb-role-switch.c | 192 +++++++++++++++++++++++++
 6 files changed, 217 insertions(+)
 create mode 100644 drivers/usb/roles/Kconfig
 create mode 100644 drivers/usb/roles/Makefile
 create mode 100644 drivers/usb/roles/intel-xhci-usb-role-switch.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 0f66f044f988..523f14b4216d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -14394,6 +14394,12 @@ S:	Maintained
 F:	Documentation/hid/hiddev.txt
 F:	drivers/hid/usbhid/
 
+USB INTEL XHCI ROLE MUX DRIVER
+M:	Hans de Goede <hdegoede@redhat.com>
+L:	linux-usb@vger.kernel.org
+S:	Maintained
+F:	drivers/usb/roles/intel-xhci-usb-role-switch.c
+
 USB ISP116X DRIVER
 M:	Olav Kongas <ok@artecdesign.ee>
 L:	linux-usb@vger.kernel.org
diff --git a/drivers/usb/Kconfig b/drivers/usb/Kconfig
index f278958e04ca..75f7fb151f71 100644
--- a/drivers/usb/Kconfig
+++ b/drivers/usb/Kconfig
@@ -171,6 +171,8 @@ source "drivers/usb/gadget/Kconfig"
 
 source "drivers/usb/typec/Kconfig"
 
+source "drivers/usb/roles/Kconfig"
+
 config USB_LED_TRIG
 	bool "USB LED Triggers"
 	depends on LEDS_CLASS && LEDS_TRIGGERS
diff --git a/drivers/usb/Makefile b/drivers/usb/Makefile
index 060643a1b5c8..7d1b8c82b208 100644
--- a/drivers/usb/Makefile
+++ b/drivers/usb/Makefile
@@ -65,3 +65,5 @@ obj-$(CONFIG_USB_COMMON)	+= common/
 obj-$(CONFIG_USBIP_CORE)	+= usbip/
 
 obj-$(CONFIG_TYPEC)		+= typec/
+
+obj-$(CONFIG_USB_ROLE_SWITCH)	+= roles/
diff --git a/drivers/usb/roles/Kconfig b/drivers/usb/roles/Kconfig
new file mode 100644
index 000000000000..f5a5e6f79f1b
--- /dev/null
+++ b/drivers/usb/roles/Kconfig
@@ -0,0 +1,14 @@
+if USB_ROLE_SWITCH
+
+config USB_ROLES_INTEL_XHCI
+	tristate "Intel XHCI USB Role Switch"
+	depends on ACPI && X86
+	help
+	  Driver for the internal USB role switch for switching the USB data
+	  lines between the xHCI host controller and the dwc3 gadget controller
+	  found on various Intel SoCs.
+
+	  To compile the driver as a module, choose M here: the module will
+	  be called intel-xhci-usb-role-switch.
+
+endif # USB_ROLE_SWITCH
diff --git a/drivers/usb/roles/Makefile b/drivers/usb/roles/Makefile
new file mode 100644
index 000000000000..e44b179ba275
--- /dev/null
+++ b/drivers/usb/roles/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_USB_ROLES_INTEL_XHCI) += intel-xhci-usb-role-switch.o
diff --git a/drivers/usb/roles/intel-xhci-usb-role-switch.c b/drivers/usb/roles/intel-xhci-usb-role-switch.c
new file mode 100644
index 000000000000..3d7be5eace49
--- /dev/null
+++ b/drivers/usb/roles/intel-xhci-usb-role-switch.c
@@ -0,0 +1,192 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Intel XHCI (Cherry Trail, Broxton and others) USB OTG role switch driver
+ *
+ * Copyright (c) 2016-2017 Hans de Goede <hdegoede@redhat.com>
+ *
+ * Loosely based on android x86 kernel code which is:
+ *
+ * Copyright (C) 2014 Intel Corp.
+ *
+ * Author: Wu, Hao
+ */
+
+#include <linux/acpi.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/usb/role.h>
+
+/* register definition */
+#define DUAL_ROLE_CFG0			0x68
+#define SW_VBUS_VALID			BIT(24)
+#define SW_IDPIN_EN			BIT(21)
+#define SW_IDPIN			BIT(20)
+
+#define DUAL_ROLE_CFG1			0x6c
+#define HOST_MODE			BIT(29)
+
+#define DUAL_ROLE_CFG1_POLL_TIMEOUT	1000
+
+#define DRV_NAME			"intel_xhci_usb_sw"
+
+struct intel_xhci_usb_data {
+	struct usb_role_switch *role_sw;
+	void __iomem *base;
+};
+
+struct intel_xhci_acpi_match {
+	const char *hid;
+	int hrv;
+};
+
+/*
+ * ACPI IDs for PMICs which do not support separate data and power role
+ * detection (USB ACA detection for micro USB OTG), we allow userspace to
+ * change the role manually on these.
+ */
+static const struct intel_xhci_acpi_match allow_userspace_ctrl_ids[] = {
+	{ "INT33F4",  3 }, /* X-Powers AXP288 PMIC */
+};
+
+static int intel_xhci_usb_set_role(struct device *dev, enum usb_role role)
+{
+	struct intel_xhci_usb_data *data = dev_get_drvdata(dev);
+	unsigned long timeout;
+	acpi_status status;
+	u32 glk, val;
+
+	/*
+	 * On many CHT devices ACPI event (_AEI) handlers read / modify /
+	 * write the cfg0 register, just like we do. Take the ACPI lock
+	 * to avoid us racing with the AML code.
+	 */
+	status = acpi_acquire_global_lock(ACPI_WAIT_FOREVER, &glk);
+	if (ACPI_FAILURE(status) && status != AE_NOT_CONFIGURED) {
+		dev_err(dev, "Error could not acquire lock\n");
+		return -EIO;
+	}
+
+	/* Set idpin value as requested */
+	val = readl(data->base + DUAL_ROLE_CFG0);
+	switch (role) {
+	case USB_ROLE_NONE:
+		val |= SW_IDPIN;
+		val &= ~SW_VBUS_VALID;
+		break;
+	case USB_ROLE_HOST:
+		val &= ~SW_IDPIN;
+		val &= ~SW_VBUS_VALID;
+		break;
+	case USB_ROLE_DEVICE:
+		val |= SW_IDPIN;
+		val |= SW_VBUS_VALID;
+		break;
+	}
+	val |= SW_IDPIN_EN;
+
+	writel(val, data->base + DUAL_ROLE_CFG0);
+
+	acpi_release_global_lock(glk);
+
+	/* In most case it takes about 600ms to finish mode switching */
+	timeout = jiffies + msecs_to_jiffies(DUAL_ROLE_CFG1_POLL_TIMEOUT);
+
+	/* Polling on CFG1 register to confirm mode switch.*/
+	do {
+		val = readl(data->base + DUAL_ROLE_CFG1);
+		if (!!(val & HOST_MODE) == (role == USB_ROLE_HOST))
+			return 0;
+
+		/* Interval for polling is set to about 5 - 10 ms */
+		usleep_range(5000, 10000);
+	} while (time_before(jiffies, timeout));
+
+	dev_warn(dev, "Timeout waiting for role-switch\n");
+	return -ETIMEDOUT;
+}
+
+static enum usb_role intel_xhci_usb_get_role(struct device *dev)
+{
+	struct intel_xhci_usb_data *data = dev_get_drvdata(dev);
+	enum usb_role role;
+	u32 val;
+
+	val = readl(data->base + DUAL_ROLE_CFG0);
+
+	if (!(val & SW_IDPIN))
+		role = USB_ROLE_HOST;
+	else if (val & SW_VBUS_VALID)
+		role = USB_ROLE_DEVICE;
+	else
+		role = USB_ROLE_NONE;
+
+	return role;
+}
+
+static struct usb_role_switch_desc sw_desc = {
+	.set = intel_xhci_usb_set_role,
+	.get = intel_xhci_usb_get_role,
+};
+
+static int intel_xhci_usb_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct intel_xhci_usb_data *data;
+	struct resource *res;
+	int i;
+
+	data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	data->base = devm_ioremap_nocache(dev, res->start, resource_size(res));
+	if (IS_ERR(data->base))
+		return PTR_ERR(data->base);
+
+	for (i = 0; i < ARRAY_SIZE(allow_userspace_ctrl_ids); i++)
+		if (acpi_dev_present(allow_userspace_ctrl_ids[i].hid, "1",
+				     allow_userspace_ctrl_ids[i].hrv))
+			sw_desc.allow_userspace_control = true;
+
+	platform_set_drvdata(pdev, data);
+
+	data->role_sw = usb_role_switch_register(dev, &sw_desc);
+	if (IS_ERR(data->role_sw))
+		return PTR_ERR(data->role_sw);
+
+	return 0;
+}
+
+int intel_xhci_usb_remove(struct platform_device *pdev)
+{
+	struct intel_xhci_usb_data *data = platform_get_drvdata(pdev);
+
+	usb_role_switch_unregister(data->role_sw);
+	return 0;
+}
+
+static const struct platform_device_id intel_xhci_usb_table[] = {
+	{ .name = DRV_NAME },
+	{}
+};
+MODULE_DEVICE_TABLE(platform, intel_xhci_usb_table);
+
+static struct platform_driver intel_xhci_usb_driver = {
+	.driver = {
+		.name = DRV_NAME,
+	},
+	.id_table = intel_xhci_usb_table,
+	.probe = intel_xhci_usb_probe,
+	.remove = intel_xhci_usb_remove,
+};
+
+module_platform_driver(intel_xhci_usb_driver);
+
+MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
+MODULE_DESCRIPTION("Intel XHCI USB role switch driver");
+MODULE_LICENSE("GPL");
-- 
2.14.3


^ permalink raw reply related	[flat|nested] 43+ messages in thread

* [v5,09/12] usb: roles: Add Intel xHCI USB role switch driver
@ 2018-02-28 15:07   ` Hans de Goede
  0 siblings, 0 replies; 43+ messages in thread
From: Hans de Goede @ 2018-02-28 15:07 UTC (permalink / raw)
  To: Darren Hart, Andy Shevchenko, MyungJoo Ham, Chanwoo Choi,
	Mathias Nyman, Heikki Krogerus, Greg Kroah-Hartman,
	Guenter Roeck
  Cc: Hans de Goede, platform-driver-x86, linux-kernel, linux-usb

Various Intel SoCs (Cherry Trail, Broxton and others) have an internal USB
role switch for swiching the OTG USB data lines between the xHCI host
controller and the dwc3 gadget controller.

Note on some Cherry Trail systems there is ACPI/AML code listening to
edge interrupts on the id-pin (through an _AIE ACPI method) and switching
the role between ROLE_HOST and ROLE_NONE based on the id-pin. Note it does
not set the role to ROLE_DEVICE, because device-mode is usually not used
under Windows.

The presence of AML code which modifies the cfg0 reg (on some systems)
means that our read/write/modify of cfg0 may race with the AML code
doing the same to avoid this we take the global ACPI lock while doing
the read/write/modify.

Reviewed-by: Andy Shevchenko <andy.shevchenko@gmail.com>
Reviewed-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
---
Changes in v4:
-Add Andy's Reviewed-by
-Add Heikki's Reviewed-by

Changes in v2:
-Drop unnecessary depends on EXTCON from Kconfig
-Use BIT(), resource_size()
-Various other small style fixes
---
 MAINTAINERS                                    |   6 +
 drivers/usb/Kconfig                            |   2 +
 drivers/usb/Makefile                           |   2 +
 drivers/usb/roles/Kconfig                      |  14 ++
 drivers/usb/roles/Makefile                     |   1 +
 drivers/usb/roles/intel-xhci-usb-role-switch.c | 192 +++++++++++++++++++++++++
 6 files changed, 217 insertions(+)
 create mode 100644 drivers/usb/roles/Kconfig
 create mode 100644 drivers/usb/roles/Makefile
 create mode 100644 drivers/usb/roles/intel-xhci-usb-role-switch.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 0f66f044f988..523f14b4216d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -14394,6 +14394,12 @@ S:	Maintained
 F:	Documentation/hid/hiddev.txt
 F:	drivers/hid/usbhid/
 
+USB INTEL XHCI ROLE MUX DRIVER
+M:	Hans de Goede <hdegoede@redhat.com>
+L:	linux-usb@vger.kernel.org
+S:	Maintained
+F:	drivers/usb/roles/intel-xhci-usb-role-switch.c
+
 USB ISP116X DRIVER
 M:	Olav Kongas <ok@artecdesign.ee>
 L:	linux-usb@vger.kernel.org
diff --git a/drivers/usb/Kconfig b/drivers/usb/Kconfig
index f278958e04ca..75f7fb151f71 100644
--- a/drivers/usb/Kconfig
+++ b/drivers/usb/Kconfig
@@ -171,6 +171,8 @@ source "drivers/usb/gadget/Kconfig"
 
 source "drivers/usb/typec/Kconfig"
 
+source "drivers/usb/roles/Kconfig"
+
 config USB_LED_TRIG
 	bool "USB LED Triggers"
 	depends on LEDS_CLASS && LEDS_TRIGGERS
diff --git a/drivers/usb/Makefile b/drivers/usb/Makefile
index 060643a1b5c8..7d1b8c82b208 100644
--- a/drivers/usb/Makefile
+++ b/drivers/usb/Makefile
@@ -65,3 +65,5 @@ obj-$(CONFIG_USB_COMMON)	+= common/
 obj-$(CONFIG_USBIP_CORE)	+= usbip/
 
 obj-$(CONFIG_TYPEC)		+= typec/
+
+obj-$(CONFIG_USB_ROLE_SWITCH)	+= roles/
diff --git a/drivers/usb/roles/Kconfig b/drivers/usb/roles/Kconfig
new file mode 100644
index 000000000000..f5a5e6f79f1b
--- /dev/null
+++ b/drivers/usb/roles/Kconfig
@@ -0,0 +1,14 @@
+if USB_ROLE_SWITCH
+
+config USB_ROLES_INTEL_XHCI
+	tristate "Intel XHCI USB Role Switch"
+	depends on ACPI && X86
+	help
+	  Driver for the internal USB role switch for switching the USB data
+	  lines between the xHCI host controller and the dwc3 gadget controller
+	  found on various Intel SoCs.
+
+	  To compile the driver as a module, choose M here: the module will
+	  be called intel-xhci-usb-role-switch.
+
+endif # USB_ROLE_SWITCH
diff --git a/drivers/usb/roles/Makefile b/drivers/usb/roles/Makefile
new file mode 100644
index 000000000000..e44b179ba275
--- /dev/null
+++ b/drivers/usb/roles/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_USB_ROLES_INTEL_XHCI) += intel-xhci-usb-role-switch.o
diff --git a/drivers/usb/roles/intel-xhci-usb-role-switch.c b/drivers/usb/roles/intel-xhci-usb-role-switch.c
new file mode 100644
index 000000000000..3d7be5eace49
--- /dev/null
+++ b/drivers/usb/roles/intel-xhci-usb-role-switch.c
@@ -0,0 +1,192 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Intel XHCI (Cherry Trail, Broxton and others) USB OTG role switch driver
+ *
+ * Copyright (c) 2016-2017 Hans de Goede <hdegoede@redhat.com>
+ *
+ * Loosely based on android x86 kernel code which is:
+ *
+ * Copyright (C) 2014 Intel Corp.
+ *
+ * Author: Wu, Hao
+ */
+
+#include <linux/acpi.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/usb/role.h>
+
+/* register definition */
+#define DUAL_ROLE_CFG0			0x68
+#define SW_VBUS_VALID			BIT(24)
+#define SW_IDPIN_EN			BIT(21)
+#define SW_IDPIN			BIT(20)
+
+#define DUAL_ROLE_CFG1			0x6c
+#define HOST_MODE			BIT(29)
+
+#define DUAL_ROLE_CFG1_POLL_TIMEOUT	1000
+
+#define DRV_NAME			"intel_xhci_usb_sw"
+
+struct intel_xhci_usb_data {
+	struct usb_role_switch *role_sw;
+	void __iomem *base;
+};
+
+struct intel_xhci_acpi_match {
+	const char *hid;
+	int hrv;
+};
+
+/*
+ * ACPI IDs for PMICs which do not support separate data and power role
+ * detection (USB ACA detection for micro USB OTG), we allow userspace to
+ * change the role manually on these.
+ */
+static const struct intel_xhci_acpi_match allow_userspace_ctrl_ids[] = {
+	{ "INT33F4",  3 }, /* X-Powers AXP288 PMIC */
+};
+
+static int intel_xhci_usb_set_role(struct device *dev, enum usb_role role)
+{
+	struct intel_xhci_usb_data *data = dev_get_drvdata(dev);
+	unsigned long timeout;
+	acpi_status status;
+	u32 glk, val;
+
+	/*
+	 * On many CHT devices ACPI event (_AEI) handlers read / modify /
+	 * write the cfg0 register, just like we do. Take the ACPI lock
+	 * to avoid us racing with the AML code.
+	 */
+	status = acpi_acquire_global_lock(ACPI_WAIT_FOREVER, &glk);
+	if (ACPI_FAILURE(status) && status != AE_NOT_CONFIGURED) {
+		dev_err(dev, "Error could not acquire lock\n");
+		return -EIO;
+	}
+
+	/* Set idpin value as requested */
+	val = readl(data->base + DUAL_ROLE_CFG0);
+	switch (role) {
+	case USB_ROLE_NONE:
+		val |= SW_IDPIN;
+		val &= ~SW_VBUS_VALID;
+		break;
+	case USB_ROLE_HOST:
+		val &= ~SW_IDPIN;
+		val &= ~SW_VBUS_VALID;
+		break;
+	case USB_ROLE_DEVICE:
+		val |= SW_IDPIN;
+		val |= SW_VBUS_VALID;
+		break;
+	}
+	val |= SW_IDPIN_EN;
+
+	writel(val, data->base + DUAL_ROLE_CFG0);
+
+	acpi_release_global_lock(glk);
+
+	/* In most case it takes about 600ms to finish mode switching */
+	timeout = jiffies + msecs_to_jiffies(DUAL_ROLE_CFG1_POLL_TIMEOUT);
+
+	/* Polling on CFG1 register to confirm mode switch.*/
+	do {
+		val = readl(data->base + DUAL_ROLE_CFG1);
+		if (!!(val & HOST_MODE) == (role == USB_ROLE_HOST))
+			return 0;
+
+		/* Interval for polling is set to about 5 - 10 ms */
+		usleep_range(5000, 10000);
+	} while (time_before(jiffies, timeout));
+
+	dev_warn(dev, "Timeout waiting for role-switch\n");
+	return -ETIMEDOUT;
+}
+
+static enum usb_role intel_xhci_usb_get_role(struct device *dev)
+{
+	struct intel_xhci_usb_data *data = dev_get_drvdata(dev);
+	enum usb_role role;
+	u32 val;
+
+	val = readl(data->base + DUAL_ROLE_CFG0);
+
+	if (!(val & SW_IDPIN))
+		role = USB_ROLE_HOST;
+	else if (val & SW_VBUS_VALID)
+		role = USB_ROLE_DEVICE;
+	else
+		role = USB_ROLE_NONE;
+
+	return role;
+}
+
+static struct usb_role_switch_desc sw_desc = {
+	.set = intel_xhci_usb_set_role,
+	.get = intel_xhci_usb_get_role,
+};
+
+static int intel_xhci_usb_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct intel_xhci_usb_data *data;
+	struct resource *res;
+	int i;
+
+	data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	data->base = devm_ioremap_nocache(dev, res->start, resource_size(res));
+	if (IS_ERR(data->base))
+		return PTR_ERR(data->base);
+
+	for (i = 0; i < ARRAY_SIZE(allow_userspace_ctrl_ids); i++)
+		if (acpi_dev_present(allow_userspace_ctrl_ids[i].hid, "1",
+				     allow_userspace_ctrl_ids[i].hrv))
+			sw_desc.allow_userspace_control = true;
+
+	platform_set_drvdata(pdev, data);
+
+	data->role_sw = usb_role_switch_register(dev, &sw_desc);
+	if (IS_ERR(data->role_sw))
+		return PTR_ERR(data->role_sw);
+
+	return 0;
+}
+
+int intel_xhci_usb_remove(struct platform_device *pdev)
+{
+	struct intel_xhci_usb_data *data = platform_get_drvdata(pdev);
+
+	usb_role_switch_unregister(data->role_sw);
+	return 0;
+}
+
+static const struct platform_device_id intel_xhci_usb_table[] = {
+	{ .name = DRV_NAME },
+	{}
+};
+MODULE_DEVICE_TABLE(platform, intel_xhci_usb_table);
+
+static struct platform_driver intel_xhci_usb_driver = {
+	.driver = {
+		.name = DRV_NAME,
+	},
+	.id_table = intel_xhci_usb_table,
+	.probe = intel_xhci_usb_probe,
+	.remove = intel_xhci_usb_remove,
+};
+
+module_platform_driver(intel_xhci_usb_driver);
+
+MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
+MODULE_DESCRIPTION("Intel XHCI USB role switch driver");
+MODULE_LICENSE("GPL");

^ permalink raw reply related	[flat|nested] 43+ messages in thread

* [PATCH v5 10/12] usb: typec: driver for Pericom PI3USB30532 Type-C cross switch
@ 2018-02-28 15:07   ` Hans de Goede
  0 siblings, 0 replies; 43+ messages in thread
From: Hans de Goede @ 2018-02-28 15:07 UTC (permalink / raw)
  To: Darren Hart, Andy Shevchenko, MyungJoo Ham, Chanwoo Choi,
	Mathias Nyman, Heikki Krogerus, Greg Kroah-Hartman,
	Guenter Roeck
  Cc: Hans de Goede, platform-driver-x86, linux-kernel, linux-usb

Add a driver for the Pericom PI3USB30532 Type-C cross switch /
mux chip found on some devices with a Type-C port.

Reviewed-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Reviewed-by: Andy Shevchenko <andy.shevchenko@gmail.com>
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
---
Changes in v4:
-Add Andy's Reviewed-by

Changes in v2:
-Cleanup pi3usb30532_set_conf() error handling
-Add Heikki's Reviewed-by

Changes in v1:
-This is a rewrite of my previous driver which was using the
 drivers/mux framework to use the new drivers/usb/typec/mux framework
---
 MAINTAINERS                         |   6 ++
 drivers/usb/typec/Kconfig           |   2 +
 drivers/usb/typec/Makefile          |   1 +
 drivers/usb/typec/mux/Kconfig       |  10 ++
 drivers/usb/typec/mux/Makefile      |   3 +
 drivers/usb/typec/mux/pi3usb30532.c | 178 ++++++++++++++++++++++++++++++++++++
 6 files changed, 200 insertions(+)
 create mode 100644 drivers/usb/typec/mux/Kconfig
 create mode 100644 drivers/usb/typec/mux/Makefile
 create mode 100644 drivers/usb/typec/mux/pi3usb30532.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 523f14b4216d..cc66ac1b9ee0 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -14530,6 +14530,12 @@ F:	drivers/usb/
 F:	include/linux/usb.h
 F:	include/linux/usb/
 
+USB TYPEC PI3USB30532 MUX DRIVER
+M:	Hans de Goede <hdegoede@redhat.com>
+L:	linux-usb@vger.kernel.org
+S:	Maintained
+F:	drivers/usb/typec/mux/pi3usb30532.c
+
 USB TYPEC SUBSYSTEM
 M:	Heikki Krogerus <heikki.krogerus@linux.intel.com>
 L:	linux-usb@vger.kernel.org
diff --git a/drivers/usb/typec/Kconfig b/drivers/usb/typec/Kconfig
index a2a0684e7c82..030f88cb0c3f 100644
--- a/drivers/usb/typec/Kconfig
+++ b/drivers/usb/typec/Kconfig
@@ -85,4 +85,6 @@ config TYPEC_TPS6598X
 	  If you choose to build this driver as a dynamically linked module, the
 	  module will be called tps6598x.ko.
 
+source "drivers/usb/typec/mux/Kconfig"
+
 endif # TYPEC
diff --git a/drivers/usb/typec/Makefile b/drivers/usb/typec/Makefile
index 56b2e9516ec1..1f599a6c30cc 100644
--- a/drivers/usb/typec/Makefile
+++ b/drivers/usb/typec/Makefile
@@ -6,3 +6,4 @@ obj-y				+= fusb302/
 obj-$(CONFIG_TYPEC_WCOVE)	+= typec_wcove.o
 obj-$(CONFIG_TYPEC_UCSI)	+= ucsi/
 obj-$(CONFIG_TYPEC_TPS6598X)	+= tps6598x.o
+obj-$(CONFIG_TYPEC)		+= mux/
diff --git a/drivers/usb/typec/mux/Kconfig b/drivers/usb/typec/mux/Kconfig
new file mode 100644
index 000000000000..9a954d2b8d8f
--- /dev/null
+++ b/drivers/usb/typec/mux/Kconfig
@@ -0,0 +1,10 @@
+menu "USB Type-C Multiplexer/DeMultiplexer Switch support"
+
+config TYPEC_MUX_PI3USB30532
+	tristate "Pericom PI3USB30532 Type-C cross switch driver"
+	depends on I2C
+	help
+	  Say Y or M if your system has a Pericom PI3USB30532 Type-C cross
+	  switch / mux chip found on some devices with a Type-C port.
+
+endmenu
diff --git a/drivers/usb/typec/mux/Makefile b/drivers/usb/typec/mux/Makefile
new file mode 100644
index 000000000000..1332e469b8a0
--- /dev/null
+++ b/drivers/usb/typec/mux/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0
+
+obj-$(CONFIG_TYPEC_MUX_PI3USB30532)	+= pi3usb30532.o
diff --git a/drivers/usb/typec/mux/pi3usb30532.c b/drivers/usb/typec/mux/pi3usb30532.c
new file mode 100644
index 000000000000..86cda9f388f3
--- /dev/null
+++ b/drivers/usb/typec/mux/pi3usb30532.c
@@ -0,0 +1,178 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Pericom PI3USB30532 Type-C cross switch / mux driver
+ *
+ * Copyright (c) 2017-2018 Hans de Goede <hdegoede@redhat.com>
+ */
+
+#include <linux/i2c.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/usb/tcpm.h>
+#include <linux/usb/typec_mux.h>
+
+#define PI3USB30532_CONF			0x00
+
+#define PI3USB30532_CONF_OPEN			0x00
+#define PI3USB30532_CONF_SWAP			0x01
+#define PI3USB30532_CONF_4LANE_DP		0x02
+#define PI3USB30532_CONF_USB3			0x04
+#define PI3USB30532_CONF_USB3_AND_2LANE_DP	0x06
+
+struct pi3usb30532 {
+	struct i2c_client *client;
+	struct mutex lock; /* protects the cached conf register */
+	struct typec_switch sw;
+	struct typec_mux mux;
+	u8 conf;
+};
+
+static int pi3usb30532_set_conf(struct pi3usb30532 *pi, u8 new_conf)
+{
+	int ret = 0;
+
+	if (pi->conf == new_conf)
+		return 0;
+
+	ret = i2c_smbus_write_byte_data(pi->client, PI3USB30532_CONF, new_conf);
+	if (ret) {
+		dev_err(&pi->client->dev, "Error writing conf: %d\n", ret);
+		return ret;
+	}
+
+	pi->conf = new_conf;
+	return 0;
+}
+
+static int pi3usb30532_sw_set(struct typec_switch *sw,
+			      enum typec_orientation orientation)
+{
+	struct pi3usb30532 *pi = container_of(sw, struct pi3usb30532, sw);
+	u8 new_conf;
+	int ret;
+
+	mutex_lock(&pi->lock);
+	new_conf = pi->conf;
+
+	switch (orientation) {
+	case TYPEC_ORIENTATION_NONE:
+		new_conf = PI3USB30532_CONF_OPEN;
+		break;
+	case TYPEC_ORIENTATION_NORMAL:
+		new_conf &= ~PI3USB30532_CONF_SWAP;
+		break;
+	case TYPEC_ORIENTATION_REVERSE:
+		new_conf |= PI3USB30532_CONF_SWAP;
+		break;
+	};
+
+	ret = pi3usb30532_set_conf(pi, new_conf);
+	mutex_unlock(&pi->lock);
+
+	return ret;
+}
+
+static int pi3usb30532_mux_set(struct typec_mux *mux, int state)
+{
+	struct pi3usb30532 *pi = container_of(mux, struct pi3usb30532, mux);
+	u8 new_conf;
+	int ret;
+
+	mutex_lock(&pi->lock);
+	new_conf = pi->conf;
+
+	switch (state) {
+	case TYPEC_MUX_NONE:
+		new_conf = PI3USB30532_CONF_OPEN;
+		break;
+	case TYPEC_MUX_USB:
+		new_conf = (new_conf & PI3USB30532_CONF_SWAP) |
+			   PI3USB30532_CONF_USB3;
+		break;
+	case TYPEC_MUX_DP:
+		new_conf = (new_conf & PI3USB30532_CONF_SWAP) |
+			   PI3USB30532_CONF_4LANE_DP;
+		break;
+	case TYPEC_MUX_DOCK:
+		new_conf = (new_conf & PI3USB30532_CONF_SWAP) |
+			   PI3USB30532_CONF_USB3_AND_2LANE_DP;
+		break;
+	};
+
+	ret = pi3usb30532_set_conf(pi, new_conf);
+	mutex_unlock(&pi->lock);
+
+	return ret;
+}
+
+static int pi3usb30532_probe(struct i2c_client *client)
+{
+	struct device *dev = &client->dev;
+	struct pi3usb30532 *pi;
+	int ret;
+
+	pi = devm_kzalloc(dev, sizeof(*pi), GFP_KERNEL);
+	if (!pi)
+		return -ENOMEM;
+
+	pi->client = client;
+	pi->sw.dev = dev;
+	pi->sw.set = pi3usb30532_sw_set;
+	pi->mux.dev = dev;
+	pi->mux.set = pi3usb30532_mux_set;
+	mutex_init(&pi->lock);
+
+	ret = i2c_smbus_read_byte_data(client, PI3USB30532_CONF);
+	if (ret < 0) {
+		dev_err(dev, "Error reading config register %d\n", ret);
+		return ret;
+	}
+	pi->conf = ret;
+
+	ret = typec_switch_register(&pi->sw);
+	if (ret) {
+		dev_err(dev, "Error registering typec switch: %d\n", ret);
+		return ret;
+	}
+
+	ret = typec_mux_register(&pi->mux);
+	if (ret) {
+		typec_switch_unregister(&pi->sw);
+		dev_err(dev, "Error registering typec mux: %d\n", ret);
+		return ret;
+	}
+
+	i2c_set_clientdata(client, pi);
+	return 0;
+}
+
+static int pi3usb30532_remove(struct i2c_client *client)
+{
+	struct pi3usb30532 *pi = i2c_get_clientdata(client);
+
+	typec_mux_unregister(&pi->mux);
+	typec_switch_unregister(&pi->sw);
+	return 0;
+}
+
+static const struct i2c_device_id pi3usb30532_table[] = {
+	{ "pi3usb30532" },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, pi3usb30532_table);
+
+static struct i2c_driver pi3usb30532_driver = {
+	.driver = {
+		.name = "pi3usb30532",
+	},
+	.probe_new	= pi3usb30532_probe,
+	.remove		= pi3usb30532_remove,
+	.id_table	= pi3usb30532_table,
+};
+
+module_i2c_driver(pi3usb30532_driver);
+
+MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
+MODULE_DESCRIPTION("Pericom PI3USB30532 Type-C mux driver");
+MODULE_LICENSE("GPL");
-- 
2.14.3


^ permalink raw reply related	[flat|nested] 43+ messages in thread

* [v5,10/12] usb: typec: driver for Pericom PI3USB30532 Type-C cross switch
@ 2018-02-28 15:07   ` Hans de Goede
  0 siblings, 0 replies; 43+ messages in thread
From: Hans de Goede @ 2018-02-28 15:07 UTC (permalink / raw)
  To: Darren Hart, Andy Shevchenko, MyungJoo Ham, Chanwoo Choi,
	Mathias Nyman, Heikki Krogerus, Greg Kroah-Hartman,
	Guenter Roeck
  Cc: Hans de Goede, platform-driver-x86, linux-kernel, linux-usb

Add a driver for the Pericom PI3USB30532 Type-C cross switch /
mux chip found on some devices with a Type-C port.

Reviewed-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Reviewed-by: Andy Shevchenko <andy.shevchenko@gmail.com>
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
---
Changes in v4:
-Add Andy's Reviewed-by

Changes in v2:
-Cleanup pi3usb30532_set_conf() error handling
-Add Heikki's Reviewed-by

Changes in v1:
-This is a rewrite of my previous driver which was using the
 drivers/mux framework to use the new drivers/usb/typec/mux framework
---
 MAINTAINERS                         |   6 ++
 drivers/usb/typec/Kconfig           |   2 +
 drivers/usb/typec/Makefile          |   1 +
 drivers/usb/typec/mux/Kconfig       |  10 ++
 drivers/usb/typec/mux/Makefile      |   3 +
 drivers/usb/typec/mux/pi3usb30532.c | 178 ++++++++++++++++++++++++++++++++++++
 6 files changed, 200 insertions(+)
 create mode 100644 drivers/usb/typec/mux/Kconfig
 create mode 100644 drivers/usb/typec/mux/Makefile
 create mode 100644 drivers/usb/typec/mux/pi3usb30532.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 523f14b4216d..cc66ac1b9ee0 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -14530,6 +14530,12 @@ F:	drivers/usb/
 F:	include/linux/usb.h
 F:	include/linux/usb/
 
+USB TYPEC PI3USB30532 MUX DRIVER
+M:	Hans de Goede <hdegoede@redhat.com>
+L:	linux-usb@vger.kernel.org
+S:	Maintained
+F:	drivers/usb/typec/mux/pi3usb30532.c
+
 USB TYPEC SUBSYSTEM
 M:	Heikki Krogerus <heikki.krogerus@linux.intel.com>
 L:	linux-usb@vger.kernel.org
diff --git a/drivers/usb/typec/Kconfig b/drivers/usb/typec/Kconfig
index a2a0684e7c82..030f88cb0c3f 100644
--- a/drivers/usb/typec/Kconfig
+++ b/drivers/usb/typec/Kconfig
@@ -85,4 +85,6 @@ config TYPEC_TPS6598X
 	  If you choose to build this driver as a dynamically linked module, the
 	  module will be called tps6598x.ko.
 
+source "drivers/usb/typec/mux/Kconfig"
+
 endif # TYPEC
diff --git a/drivers/usb/typec/Makefile b/drivers/usb/typec/Makefile
index 56b2e9516ec1..1f599a6c30cc 100644
--- a/drivers/usb/typec/Makefile
+++ b/drivers/usb/typec/Makefile
@@ -6,3 +6,4 @@ obj-y				+= fusb302/
 obj-$(CONFIG_TYPEC_WCOVE)	+= typec_wcove.o
 obj-$(CONFIG_TYPEC_UCSI)	+= ucsi/
 obj-$(CONFIG_TYPEC_TPS6598X)	+= tps6598x.o
+obj-$(CONFIG_TYPEC)		+= mux/
diff --git a/drivers/usb/typec/mux/Kconfig b/drivers/usb/typec/mux/Kconfig
new file mode 100644
index 000000000000..9a954d2b8d8f
--- /dev/null
+++ b/drivers/usb/typec/mux/Kconfig
@@ -0,0 +1,10 @@
+menu "USB Type-C Multiplexer/DeMultiplexer Switch support"
+
+config TYPEC_MUX_PI3USB30532
+	tristate "Pericom PI3USB30532 Type-C cross switch driver"
+	depends on I2C
+	help
+	  Say Y or M if your system has a Pericom PI3USB30532 Type-C cross
+	  switch / mux chip found on some devices with a Type-C port.
+
+endmenu
diff --git a/drivers/usb/typec/mux/Makefile b/drivers/usb/typec/mux/Makefile
new file mode 100644
index 000000000000..1332e469b8a0
--- /dev/null
+++ b/drivers/usb/typec/mux/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0
+
+obj-$(CONFIG_TYPEC_MUX_PI3USB30532)	+= pi3usb30532.o
diff --git a/drivers/usb/typec/mux/pi3usb30532.c b/drivers/usb/typec/mux/pi3usb30532.c
new file mode 100644
index 000000000000..86cda9f388f3
--- /dev/null
+++ b/drivers/usb/typec/mux/pi3usb30532.c
@@ -0,0 +1,178 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Pericom PI3USB30532 Type-C cross switch / mux driver
+ *
+ * Copyright (c) 2017-2018 Hans de Goede <hdegoede@redhat.com>
+ */
+
+#include <linux/i2c.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/usb/tcpm.h>
+#include <linux/usb/typec_mux.h>
+
+#define PI3USB30532_CONF			0x00
+
+#define PI3USB30532_CONF_OPEN			0x00
+#define PI3USB30532_CONF_SWAP			0x01
+#define PI3USB30532_CONF_4LANE_DP		0x02
+#define PI3USB30532_CONF_USB3			0x04
+#define PI3USB30532_CONF_USB3_AND_2LANE_DP	0x06
+
+struct pi3usb30532 {
+	struct i2c_client *client;
+	struct mutex lock; /* protects the cached conf register */
+	struct typec_switch sw;
+	struct typec_mux mux;
+	u8 conf;
+};
+
+static int pi3usb30532_set_conf(struct pi3usb30532 *pi, u8 new_conf)
+{
+	int ret = 0;
+
+	if (pi->conf == new_conf)
+		return 0;
+
+	ret = i2c_smbus_write_byte_data(pi->client, PI3USB30532_CONF, new_conf);
+	if (ret) {
+		dev_err(&pi->client->dev, "Error writing conf: %d\n", ret);
+		return ret;
+	}
+
+	pi->conf = new_conf;
+	return 0;
+}
+
+static int pi3usb30532_sw_set(struct typec_switch *sw,
+			      enum typec_orientation orientation)
+{
+	struct pi3usb30532 *pi = container_of(sw, struct pi3usb30532, sw);
+	u8 new_conf;
+	int ret;
+
+	mutex_lock(&pi->lock);
+	new_conf = pi->conf;
+
+	switch (orientation) {
+	case TYPEC_ORIENTATION_NONE:
+		new_conf = PI3USB30532_CONF_OPEN;
+		break;
+	case TYPEC_ORIENTATION_NORMAL:
+		new_conf &= ~PI3USB30532_CONF_SWAP;
+		break;
+	case TYPEC_ORIENTATION_REVERSE:
+		new_conf |= PI3USB30532_CONF_SWAP;
+		break;
+	};
+
+	ret = pi3usb30532_set_conf(pi, new_conf);
+	mutex_unlock(&pi->lock);
+
+	return ret;
+}
+
+static int pi3usb30532_mux_set(struct typec_mux *mux, int state)
+{
+	struct pi3usb30532 *pi = container_of(mux, struct pi3usb30532, mux);
+	u8 new_conf;
+	int ret;
+
+	mutex_lock(&pi->lock);
+	new_conf = pi->conf;
+
+	switch (state) {
+	case TYPEC_MUX_NONE:
+		new_conf = PI3USB30532_CONF_OPEN;
+		break;
+	case TYPEC_MUX_USB:
+		new_conf = (new_conf & PI3USB30532_CONF_SWAP) |
+			   PI3USB30532_CONF_USB3;
+		break;
+	case TYPEC_MUX_DP:
+		new_conf = (new_conf & PI3USB30532_CONF_SWAP) |
+			   PI3USB30532_CONF_4LANE_DP;
+		break;
+	case TYPEC_MUX_DOCK:
+		new_conf = (new_conf & PI3USB30532_CONF_SWAP) |
+			   PI3USB30532_CONF_USB3_AND_2LANE_DP;
+		break;
+	};
+
+	ret = pi3usb30532_set_conf(pi, new_conf);
+	mutex_unlock(&pi->lock);
+
+	return ret;
+}
+
+static int pi3usb30532_probe(struct i2c_client *client)
+{
+	struct device *dev = &client->dev;
+	struct pi3usb30532 *pi;
+	int ret;
+
+	pi = devm_kzalloc(dev, sizeof(*pi), GFP_KERNEL);
+	if (!pi)
+		return -ENOMEM;
+
+	pi->client = client;
+	pi->sw.dev = dev;
+	pi->sw.set = pi3usb30532_sw_set;
+	pi->mux.dev = dev;
+	pi->mux.set = pi3usb30532_mux_set;
+	mutex_init(&pi->lock);
+
+	ret = i2c_smbus_read_byte_data(client, PI3USB30532_CONF);
+	if (ret < 0) {
+		dev_err(dev, "Error reading config register %d\n", ret);
+		return ret;
+	}
+	pi->conf = ret;
+
+	ret = typec_switch_register(&pi->sw);
+	if (ret) {
+		dev_err(dev, "Error registering typec switch: %d\n", ret);
+		return ret;
+	}
+
+	ret = typec_mux_register(&pi->mux);
+	if (ret) {
+		typec_switch_unregister(&pi->sw);
+		dev_err(dev, "Error registering typec mux: %d\n", ret);
+		return ret;
+	}
+
+	i2c_set_clientdata(client, pi);
+	return 0;
+}
+
+static int pi3usb30532_remove(struct i2c_client *client)
+{
+	struct pi3usb30532 *pi = i2c_get_clientdata(client);
+
+	typec_mux_unregister(&pi->mux);
+	typec_switch_unregister(&pi->sw);
+	return 0;
+}
+
+static const struct i2c_device_id pi3usb30532_table[] = {
+	{ "pi3usb30532" },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, pi3usb30532_table);
+
+static struct i2c_driver pi3usb30532_driver = {
+	.driver = {
+		.name = "pi3usb30532",
+	},
+	.probe_new	= pi3usb30532_probe,
+	.remove		= pi3usb30532_remove,
+	.id_table	= pi3usb30532_table,
+};
+
+module_i2c_driver(pi3usb30532_driver);
+
+MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
+MODULE_DESCRIPTION("Pericom PI3USB30532 Type-C mux driver");
+MODULE_LICENSE("GPL");

^ permalink raw reply related	[flat|nested] 43+ messages in thread

* [PATCH v5 11/12] platform/x86: intel_cht_int33fe: Add device connections for the Type-C port
@ 2018-02-28 15:07   ` Hans de Goede
  0 siblings, 0 replies; 43+ messages in thread
From: Hans de Goede @ 2018-02-28 15:07 UTC (permalink / raw)
  To: Darren Hart, Andy Shevchenko, MyungJoo Ham, Chanwoo Choi,
	Mathias Nyman, Heikki Krogerus, Greg Kroah-Hartman,
	Guenter Roeck
  Cc: Hans de Goede, platform-driver-x86, linux-kernel, linux-usb

We need to add device-connections for the Type-C mux/switch and usb-role
code to be able to find the PI3USB30532 Type-C cross-switch and the
device/host role-switch integrated in the CHT SoC.

Reviewed-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Reviewed-by: Andy Shevchenko <andy.shevchenko@gmail.com>
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
---
Changes in v4:
-Replace Andy's Acked-by with his Reviewed-by

Changes in v2:
-Add Andy's Acked-by
-Add Heikki's Reviewed-by
---
 drivers/platform/x86/intel_cht_int33fe.c | 25 +++++++++++++++++++++++++
 1 file changed, 25 insertions(+)

diff --git a/drivers/platform/x86/intel_cht_int33fe.c b/drivers/platform/x86/intel_cht_int33fe.c
index 380ef7ec094f..a3f8674f14da 100644
--- a/drivers/platform/x86/intel_cht_int33fe.c
+++ b/drivers/platform/x86/intel_cht_int33fe.c
@@ -21,6 +21,7 @@
  */
 
 #include <linux/acpi.h>
+#include <linux/connection.h>
 #include <linux/i2c.h>
 #include <linux/interrupt.h>
 #include <linux/module.h>
@@ -33,6 +34,8 @@ struct cht_int33fe_data {
 	struct i2c_client *max17047;
 	struct i2c_client *fusb302;
 	struct i2c_client *pi3usb30532;
+	/* Contain a list-head must be per device */
+	struct devcon connections[3];
 };
 
 /*
@@ -172,6 +175,20 @@ static int cht_int33fe_probe(struct i2c_client *client)
 			return -EPROBE_DEFER; /* Wait for i2c-adapter to load */
 	}
 
+	data->connections[0].endpoint[0] = "i2c-fusb302";
+	data->connections[0].endpoint[1] = "i2c-pi3usb30532";
+	data->connections[0].id = "typec-switch";
+	data->connections[1].endpoint[0] = "i2c-fusb302";
+	data->connections[1].endpoint[1] = "i2c-pi3usb30532";
+	data->connections[1].id = "typec-mux";
+	data->connections[2].endpoint[0] = "i2c-fusb302";
+	data->connections[2].endpoint[1] = "intel_xhci_usb_sw-role-switch";
+	data->connections[2].id = "usb-role-switch";
+
+	add_device_connection(&data->connections[0]);
+	add_device_connection(&data->connections[1]);
+	add_device_connection(&data->connections[2]);
+
 	memset(&board_info, 0, sizeof(board_info));
 	strlcpy(board_info.type, "typec_fusb302", I2C_NAME_SIZE);
 	board_info.dev_name = "fusb302";
@@ -201,6 +218,10 @@ static int cht_int33fe_probe(struct i2c_client *client)
 	if (data->max17047)
 		i2c_unregister_device(data->max17047);
 
+	remove_device_connection(&data->connections[2]);
+	remove_device_connection(&data->connections[1]);
+	remove_device_connection(&data->connections[0]);
+
 	return -EPROBE_DEFER; /* Wait for the i2c-adapter to load */
 }
 
@@ -213,6 +234,10 @@ static int cht_int33fe_remove(struct i2c_client *i2c)
 	if (data->max17047)
 		i2c_unregister_device(data->max17047);
 
+	remove_device_connection(&data->connections[2]);
+	remove_device_connection(&data->connections[1]);
+	remove_device_connection(&data->connections[0]);
+
 	return 0;
 }
 
-- 
2.14.3


^ permalink raw reply related	[flat|nested] 43+ messages in thread

* [v5,11/12] platform/x86: intel_cht_int33fe: Add device connections for the Type-C port
@ 2018-02-28 15:07   ` Hans de Goede
  0 siblings, 0 replies; 43+ messages in thread
From: Hans de Goede @ 2018-02-28 15:07 UTC (permalink / raw)
  To: Darren Hart, Andy Shevchenko, MyungJoo Ham, Chanwoo Choi,
	Mathias Nyman, Heikki Krogerus, Greg Kroah-Hartman,
	Guenter Roeck
  Cc: Hans de Goede, platform-driver-x86, linux-kernel, linux-usb

We need to add device-connections for the Type-C mux/switch and usb-role
code to be able to find the PI3USB30532 Type-C cross-switch and the
device/host role-switch integrated in the CHT SoC.

Reviewed-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Reviewed-by: Andy Shevchenko <andy.shevchenko@gmail.com>
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
---
Changes in v4:
-Replace Andy's Acked-by with his Reviewed-by

Changes in v2:
-Add Andy's Acked-by
-Add Heikki's Reviewed-by
---
 drivers/platform/x86/intel_cht_int33fe.c | 25 +++++++++++++++++++++++++
 1 file changed, 25 insertions(+)

diff --git a/drivers/platform/x86/intel_cht_int33fe.c b/drivers/platform/x86/intel_cht_int33fe.c
index 380ef7ec094f..a3f8674f14da 100644
--- a/drivers/platform/x86/intel_cht_int33fe.c
+++ b/drivers/platform/x86/intel_cht_int33fe.c
@@ -21,6 +21,7 @@
  */
 
 #include <linux/acpi.h>
+#include <linux/connection.h>
 #include <linux/i2c.h>
 #include <linux/interrupt.h>
 #include <linux/module.h>
@@ -33,6 +34,8 @@ struct cht_int33fe_data {
 	struct i2c_client *max17047;
 	struct i2c_client *fusb302;
 	struct i2c_client *pi3usb30532;
+	/* Contain a list-head must be per device */
+	struct devcon connections[3];
 };
 
 /*
@@ -172,6 +175,20 @@ static int cht_int33fe_probe(struct i2c_client *client)
 			return -EPROBE_DEFER; /* Wait for i2c-adapter to load */
 	}
 
+	data->connections[0].endpoint[0] = "i2c-fusb302";
+	data->connections[0].endpoint[1] = "i2c-pi3usb30532";
+	data->connections[0].id = "typec-switch";
+	data->connections[1].endpoint[0] = "i2c-fusb302";
+	data->connections[1].endpoint[1] = "i2c-pi3usb30532";
+	data->connections[1].id = "typec-mux";
+	data->connections[2].endpoint[0] = "i2c-fusb302";
+	data->connections[2].endpoint[1] = "intel_xhci_usb_sw-role-switch";
+	data->connections[2].id = "usb-role-switch";
+
+	add_device_connection(&data->connections[0]);
+	add_device_connection(&data->connections[1]);
+	add_device_connection(&data->connections[2]);
+
 	memset(&board_info, 0, sizeof(board_info));
 	strlcpy(board_info.type, "typec_fusb302", I2C_NAME_SIZE);
 	board_info.dev_name = "fusb302";
@@ -201,6 +218,10 @@ static int cht_int33fe_probe(struct i2c_client *client)
 	if (data->max17047)
 		i2c_unregister_device(data->max17047);
 
+	remove_device_connection(&data->connections[2]);
+	remove_device_connection(&data->connections[1]);
+	remove_device_connection(&data->connections[0]);
+
 	return -EPROBE_DEFER; /* Wait for the i2c-adapter to load */
 }
 
@@ -213,6 +234,10 @@ static int cht_int33fe_remove(struct i2c_client *i2c)
 	if (data->max17047)
 		i2c_unregister_device(data->max17047);
 
+	remove_device_connection(&data->connections[2]);
+	remove_device_connection(&data->connections[1]);
+	remove_device_connection(&data->connections[0]);
+
 	return 0;
 }
 

^ permalink raw reply related	[flat|nested] 43+ messages in thread

* [PATCH v5 12/12] extcon: axp288: Set USB role where necessary
@ 2018-02-28 15:07   ` Hans de Goede
  0 siblings, 0 replies; 43+ messages in thread
From: Hans de Goede @ 2018-02-28 15:07 UTC (permalink / raw)
  To: Darren Hart, Andy Shevchenko, MyungJoo Ham, Chanwoo Choi,
	Mathias Nyman, Heikki Krogerus, Greg Kroah-Hartman,
	Guenter Roeck
  Cc: Hans de Goede, platform-driver-x86, linux-kernel, linux-usb

The AXP288 BC1.2 charger detection / extcon code may seem like a strange
place to add code to control the USB role-switch on devices with an AXP288,
but there are 2 reasons to do this inside the axp288 extcon code:

1) On many devices the USB role is controlled by ACPI AML code, but the AML
   code only switches between the host and none roles, because of Windows
   not really using device mode. To make device mode work we need to toggle
   between the none/device roles based on Vbus presence, and the axp288
   extcon gets interrupts on Vbus insertion / removal.

2) In order for our BC1.2 charger detection to work properly the role
   mux must be properly set to device mode before we do the detection.

Also note the Kconfig help-text / obsolete depends on USB_PHY which are
remnants from older never upstreamed code also controlling the mux from
the axp288 extcon code.

This commit also adds code to get notifications from the INT3496 extcon
device, which is used on some devices to notify the kernel about id-pin
changes instead of them being handled through AML code.

This fixes:
-Device mode not working on most CHT devices with an AXP288
-Host mode not working on devices with an INT3496 ACPI device
-Charger-type misdetection (always SDP) on devices with an INT3496 when the
 USB role (always) gets initialized as host

Reviewed-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Reviewed-by: Andy Shevchenko <andy.shevchenko@gmail.com>
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
---
Changes in v4:
-Add Andy's Reviewed-by

Changes in v2:
-Add depends on X86 to Kconfig (the AXP288 PMIC is only used on X86)
-Use new acpi_dev_get_first_match_name() helper to get the INT3496 device-name
-Add Heikki's Reviewed-by
---
 drivers/extcon/Kconfig         |   3 +-
 drivers/extcon/extcon-axp288.c | 177 +++++++++++++++++++++++++++++++++++++++--
 2 files changed, 171 insertions(+), 9 deletions(-)

diff --git a/drivers/extcon/Kconfig b/drivers/extcon/Kconfig
index a7bca4207f44..de15bf55895b 100644
--- a/drivers/extcon/Kconfig
+++ b/drivers/extcon/Kconfig
@@ -30,7 +30,8 @@ config EXTCON_ARIZONA
 
 config EXTCON_AXP288
 	tristate "X-Power AXP288 EXTCON support"
-	depends on MFD_AXP20X && USB_PHY
+	depends on MFD_AXP20X && USB_SUPPORT && X86
+	select USB_ROLE_SWITCH
 	help
 	  Say Y here to enable support for USB peripheral detection
 	  and USB MUX switching by X-Power AXP288 PMIC.
diff --git a/drivers/extcon/extcon-axp288.c b/drivers/extcon/extcon-axp288.c
index 3ec4c715e240..51e77c7a32c2 100644
--- a/drivers/extcon/extcon-axp288.c
+++ b/drivers/extcon/extcon-axp288.c
@@ -1,6 +1,7 @@
 /*
  * extcon-axp288.c - X-Power AXP288 PMIC extcon cable detection driver
  *
+ * Copyright (c) 2017-2018 Hans de Goede <hdegoede@redhat.com>
  * Copyright (C) 2015 Intel Corporation
  * Author: Ramakrishna Pallala <ramakrishna.pallala@intel.com>
  *
@@ -14,6 +15,8 @@
  * GNU General Public License for more details.
  */
 
+#include <linux/acpi.h>
+#include <linux/connection.h>
 #include <linux/module.h>
 #include <linux/kernel.h>
 #include <linux/io.h>
@@ -25,6 +28,11 @@
 #include <linux/extcon-provider.h>
 #include <linux/regmap.h>
 #include <linux/mfd/axp20x.h>
+#include <linux/usb/role.h>
+#include <linux/workqueue.h>
+
+#include <asm/cpu_device_id.h>
+#include <asm/intel-family.h>
 
 /* Power source status register */
 #define PS_STAT_VBUS_TRIGGER		BIT(0)
@@ -97,9 +105,19 @@ struct axp288_extcon_info {
 	struct device *dev;
 	struct regmap *regmap;
 	struct regmap_irq_chip_data *regmap_irqc;
+	struct usb_role_switch *role_sw;
+	struct work_struct role_work;
 	int irq[EXTCON_IRQ_END];
 	struct extcon_dev *edev;
+	struct extcon_dev *id_extcon;
+	struct notifier_block id_nb;
 	unsigned int previous_cable;
+	bool vbus_attach;
+};
+
+static const struct x86_cpu_id cherry_trail_cpu_ids[] = {
+	{ X86_VENDOR_INTEL, 6, INTEL_FAM6_ATOM_AIRMONT, X86_FEATURE_ANY },
+	{}
 };
 
 /* Power up/down reason string array */
@@ -137,20 +155,74 @@ static void axp288_extcon_log_rsi(struct axp288_extcon_info *info)
 	regmap_write(info->regmap, AXP288_PS_BOOT_REASON_REG, clear_mask);
 }
 
-static int axp288_handle_chrg_det_event(struct axp288_extcon_info *info)
+/*
+ * The below code to control the USB role-switch on devices with an AXP288
+ * may seem out of place, but there are 2 reasons why this is the best place
+ * to control the USB role-switch on such devices:
+ * 1) On many devices the USB role is controlled by AML code, but the AML code
+ *    only switches between the host and none roles, because of Windows not
+ *    really using device mode. To make device mode work we need to toggle
+ *    between the none/device roles based on Vbus presence, and this driver
+ *    gets interrupts on Vbus insertion / removal.
+ * 2) In order for our BC1.2 charger detection to work properly the role
+ *    mux must be properly set to device mode before we do the detection.
+ */
+
+/* Returns the id-pin value, note pulled low / false == host-mode */
+static bool axp288_get_id_pin(struct axp288_extcon_info *info)
 {
-	int ret, stat, cfg, pwr_stat;
-	u8 chrg_type;
-	unsigned int cable = info->previous_cable;
-	bool vbus_attach = false;
+	enum usb_role role;
+
+	if (info->id_extcon)
+		return extcon_get_state(info->id_extcon, EXTCON_USB_HOST) <= 0;
+
+	/* We cannot access the id-pin, see what mode the AML code has set */
+	role = usb_role_switch_get_role(info->role_sw);
+	return role != USB_ROLE_HOST;
+}
+
+static void axp288_usb_role_work(struct work_struct *work)
+{
+	struct axp288_extcon_info *info =
+		container_of(work, struct axp288_extcon_info, role_work);
+	enum usb_role role;
+	bool id_pin;
+	int ret;
+
+	id_pin = axp288_get_id_pin(info);
+	if (!id_pin)
+		role = USB_ROLE_HOST;
+	else if (info->vbus_attach)
+		role = USB_ROLE_DEVICE;
+	else
+		role = USB_ROLE_NONE;
+
+	ret = usb_role_switch_set_role(info->role_sw, role);
+	if (ret)
+		dev_err(info->dev, "failed to set role: %d\n", ret);
+}
+
+static bool axp288_get_vbus_attach(struct axp288_extcon_info *info)
+{
+	int ret, pwr_stat;
 
 	ret = regmap_read(info->regmap, AXP288_PS_STAT_REG, &pwr_stat);
 	if (ret < 0) {
 		dev_err(info->dev, "failed to read vbus status\n");
-		return ret;
+		return false;
 	}
 
-	vbus_attach = (pwr_stat & PS_STAT_VBUS_VALID);
+	return !!(pwr_stat & PS_STAT_VBUS_VALID);
+}
+
+static int axp288_handle_chrg_det_event(struct axp288_extcon_info *info)
+{
+	int ret, stat, cfg;
+	u8 chrg_type;
+	unsigned int cable = info->previous_cable;
+	bool vbus_attach = false;
+
+	vbus_attach = axp288_get_vbus_attach(info);
 	if (!vbus_attach)
 		goto no_vbus;
 
@@ -201,6 +273,12 @@ static int axp288_handle_chrg_det_event(struct axp288_extcon_info *info)
 		info->previous_cable = cable;
 	}
 
+	if (info->role_sw && info->vbus_attach != vbus_attach) {
+		info->vbus_attach = vbus_attach;
+		/* Setting the role can take a while */
+		queue_work(system_long_wq, &info->role_work);
+	}
+
 	return 0;
 
 dev_det_ret:
@@ -210,6 +288,18 @@ static int axp288_handle_chrg_det_event(struct axp288_extcon_info *info)
 	return ret;
 }
 
+static int axp288_extcon_id_evt(struct notifier_block *nb,
+				unsigned long event, void *param)
+{
+	struct axp288_extcon_info *info =
+		container_of(nb, struct axp288_extcon_info, id_nb);
+
+	/* We may not sleep and setting the role can take a while */
+	queue_work(system_long_wq, &info->role_work);
+
+	return NOTIFY_OK;
+}
+
 static irqreturn_t axp288_extcon_isr(int irq, void *data)
 {
 	struct axp288_extcon_info *info = data;
@@ -231,10 +321,20 @@ static void axp288_extcon_enable(struct axp288_extcon_info *info)
 					BC_GLOBAL_RUN, BC_GLOBAL_RUN);
 }
 
+static void axp288_put_role_sw(void *data)
+{
+	struct axp288_extcon_info *info = data;
+
+	cancel_work_sync(&info->role_work);
+	usb_role_switch_put(info->role_sw);
+}
+
 static int axp288_extcon_probe(struct platform_device *pdev)
 {
 	struct axp288_extcon_info *info;
 	struct axp20x_dev *axp20x = dev_get_drvdata(pdev->dev.parent);
+	struct device *dev = &pdev->dev;
+	const char *name;
 	int ret, i, pirq;
 
 	info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
@@ -245,9 +345,33 @@ static int axp288_extcon_probe(struct platform_device *pdev)
 	info->regmap = axp20x->regmap;
 	info->regmap_irqc = axp20x->regmap_irqc;
 	info->previous_cable = EXTCON_NONE;
+	INIT_WORK(&info->role_work, axp288_usb_role_work);
+	info->id_nb.notifier_call = axp288_extcon_id_evt;
 
 	platform_set_drvdata(pdev, info);
 
+	info->role_sw = usb_role_switch_get(dev);
+	if (IS_ERR(info->role_sw))
+		return PTR_ERR(info->role_sw);
+	if (info->role_sw) {
+		ret = devm_add_action_or_reset(dev, axp288_put_role_sw, info);
+		if (ret)
+			return ret;
+
+		name = acpi_dev_get_first_match_name("INT3496", NULL, -1);
+		if (name) {
+			info->id_extcon = extcon_get_extcon_dev(name);
+			if (!info->id_extcon)
+				return -EPROBE_DEFER;
+
+			dev_info(dev, "controlling USB role\n");
+		} else {
+			dev_info(dev, "controlling USB role based on Vbus presence\n");
+		}
+	}
+
+	info->vbus_attach = axp288_get_vbus_attach(info);
+
 	axp288_extcon_log_rsi(info);
 
 	/* Initialize extcon device */
@@ -289,6 +413,19 @@ static int axp288_extcon_probe(struct platform_device *pdev)
 		}
 	}
 
+	if (info->id_extcon) {
+		ret = devm_extcon_register_notifier_all(dev, info->id_extcon,
+							&info->id_nb);
+		if (ret)
+			return ret;
+	}
+
+	/* Make sure the role-sw is set correctly before doing BC detection */
+	if (info->role_sw) {
+		queue_work(system_long_wq, &info->role_work);
+		flush_work(&info->role_work);
+	}
+
 	/* Start charger cable type detection */
 	axp288_extcon_enable(info);
 
@@ -308,8 +445,32 @@ static struct platform_driver axp288_extcon_driver = {
 		.name = "axp288_extcon",
 	},
 };
-module_platform_driver(axp288_extcon_driver);
+
+static struct devcon axp288_extcon_role_sw_conn = {
+	.endpoint[0] = "axp288_extcon",
+	.endpoint[1] = "intel_xhci_usb_sw-role-switch",
+	.id = "usb-role-switch",
+};
+
+static int __init axp288_extcon_init(void)
+{
+	if (x86_match_cpu(cherry_trail_cpu_ids))
+		add_device_connection(&axp288_extcon_role_sw_conn);
+
+	return platform_driver_register(&axp288_extcon_driver);
+}
+module_init(axp288_extcon_init);
+
+static void __exit axp288_extcon_exit(void)
+{
+	if (x86_match_cpu(cherry_trail_cpu_ids))
+		remove_device_connection(&axp288_extcon_role_sw_conn);
+
+	platform_driver_unregister(&axp288_extcon_driver);
+}
+module_exit(axp288_extcon_exit);
 
 MODULE_AUTHOR("Ramakrishna Pallala <ramakrishna.pallala@intel.com>");
+MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
 MODULE_DESCRIPTION("X-Powers AXP288 extcon driver");
 MODULE_LICENSE("GPL v2");
-- 
2.14.3


^ permalink raw reply related	[flat|nested] 43+ messages in thread

* [v5,12/12] extcon: axp288: Set USB role where necessary
@ 2018-02-28 15:07   ` Hans de Goede
  0 siblings, 0 replies; 43+ messages in thread
From: Hans de Goede @ 2018-02-28 15:07 UTC (permalink / raw)
  To: Darren Hart, Andy Shevchenko, MyungJoo Ham, Chanwoo Choi,
	Mathias Nyman, Heikki Krogerus, Greg Kroah-Hartman,
	Guenter Roeck
  Cc: Hans de Goede, platform-driver-x86, linux-kernel, linux-usb

The AXP288 BC1.2 charger detection / extcon code may seem like a strange
place to add code to control the USB role-switch on devices with an AXP288,
but there are 2 reasons to do this inside the axp288 extcon code:

1) On many devices the USB role is controlled by ACPI AML code, but the AML
   code only switches between the host and none roles, because of Windows
   not really using device mode. To make device mode work we need to toggle
   between the none/device roles based on Vbus presence, and the axp288
   extcon gets interrupts on Vbus insertion / removal.

2) In order for our BC1.2 charger detection to work properly the role
   mux must be properly set to device mode before we do the detection.

Also note the Kconfig help-text / obsolete depends on USB_PHY which are
remnants from older never upstreamed code also controlling the mux from
the axp288 extcon code.

This commit also adds code to get notifications from the INT3496 extcon
device, which is used on some devices to notify the kernel about id-pin
changes instead of them being handled through AML code.

This fixes:
-Device mode not working on most CHT devices with an AXP288
-Host mode not working on devices with an INT3496 ACPI device
-Charger-type misdetection (always SDP) on devices with an INT3496 when the
 USB role (always) gets initialized as host

Reviewed-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Reviewed-by: Andy Shevchenko <andy.shevchenko@gmail.com>
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
---
Changes in v4:
-Add Andy's Reviewed-by

Changes in v2:
-Add depends on X86 to Kconfig (the AXP288 PMIC is only used on X86)
-Use new acpi_dev_get_first_match_name() helper to get the INT3496 device-name
-Add Heikki's Reviewed-by
---
 drivers/extcon/Kconfig         |   3 +-
 drivers/extcon/extcon-axp288.c | 177 +++++++++++++++++++++++++++++++++++++++--
 2 files changed, 171 insertions(+), 9 deletions(-)

diff --git a/drivers/extcon/Kconfig b/drivers/extcon/Kconfig
index a7bca4207f44..de15bf55895b 100644
--- a/drivers/extcon/Kconfig
+++ b/drivers/extcon/Kconfig
@@ -30,7 +30,8 @@ config EXTCON_ARIZONA
 
 config EXTCON_AXP288
 	tristate "X-Power AXP288 EXTCON support"
-	depends on MFD_AXP20X && USB_PHY
+	depends on MFD_AXP20X && USB_SUPPORT && X86
+	select USB_ROLE_SWITCH
 	help
 	  Say Y here to enable support for USB peripheral detection
 	  and USB MUX switching by X-Power AXP288 PMIC.
diff --git a/drivers/extcon/extcon-axp288.c b/drivers/extcon/extcon-axp288.c
index 3ec4c715e240..51e77c7a32c2 100644
--- a/drivers/extcon/extcon-axp288.c
+++ b/drivers/extcon/extcon-axp288.c
@@ -1,6 +1,7 @@
 /*
  * extcon-axp288.c - X-Power AXP288 PMIC extcon cable detection driver
  *
+ * Copyright (c) 2017-2018 Hans de Goede <hdegoede@redhat.com>
  * Copyright (C) 2015 Intel Corporation
  * Author: Ramakrishna Pallala <ramakrishna.pallala@intel.com>
  *
@@ -14,6 +15,8 @@
  * GNU General Public License for more details.
  */
 
+#include <linux/acpi.h>
+#include <linux/connection.h>
 #include <linux/module.h>
 #include <linux/kernel.h>
 #include <linux/io.h>
@@ -25,6 +28,11 @@
 #include <linux/extcon-provider.h>
 #include <linux/regmap.h>
 #include <linux/mfd/axp20x.h>
+#include <linux/usb/role.h>
+#include <linux/workqueue.h>
+
+#include <asm/cpu_device_id.h>
+#include <asm/intel-family.h>
 
 /* Power source status register */
 #define PS_STAT_VBUS_TRIGGER		BIT(0)
@@ -97,9 +105,19 @@ struct axp288_extcon_info {
 	struct device *dev;
 	struct regmap *regmap;
 	struct regmap_irq_chip_data *regmap_irqc;
+	struct usb_role_switch *role_sw;
+	struct work_struct role_work;
 	int irq[EXTCON_IRQ_END];
 	struct extcon_dev *edev;
+	struct extcon_dev *id_extcon;
+	struct notifier_block id_nb;
 	unsigned int previous_cable;
+	bool vbus_attach;
+};
+
+static const struct x86_cpu_id cherry_trail_cpu_ids[] = {
+	{ X86_VENDOR_INTEL, 6, INTEL_FAM6_ATOM_AIRMONT, X86_FEATURE_ANY },
+	{}
 };
 
 /* Power up/down reason string array */
@@ -137,20 +155,74 @@ static void axp288_extcon_log_rsi(struct axp288_extcon_info *info)
 	regmap_write(info->regmap, AXP288_PS_BOOT_REASON_REG, clear_mask);
 }
 
-static int axp288_handle_chrg_det_event(struct axp288_extcon_info *info)
+/*
+ * The below code to control the USB role-switch on devices with an AXP288
+ * may seem out of place, but there are 2 reasons why this is the best place
+ * to control the USB role-switch on such devices:
+ * 1) On many devices the USB role is controlled by AML code, but the AML code
+ *    only switches between the host and none roles, because of Windows not
+ *    really using device mode. To make device mode work we need to toggle
+ *    between the none/device roles based on Vbus presence, and this driver
+ *    gets interrupts on Vbus insertion / removal.
+ * 2) In order for our BC1.2 charger detection to work properly the role
+ *    mux must be properly set to device mode before we do the detection.
+ */
+
+/* Returns the id-pin value, note pulled low / false == host-mode */
+static bool axp288_get_id_pin(struct axp288_extcon_info *info)
 {
-	int ret, stat, cfg, pwr_stat;
-	u8 chrg_type;
-	unsigned int cable = info->previous_cable;
-	bool vbus_attach = false;
+	enum usb_role role;
+
+	if (info->id_extcon)
+		return extcon_get_state(info->id_extcon, EXTCON_USB_HOST) <= 0;
+
+	/* We cannot access the id-pin, see what mode the AML code has set */
+	role = usb_role_switch_get_role(info->role_sw);
+	return role != USB_ROLE_HOST;
+}
+
+static void axp288_usb_role_work(struct work_struct *work)
+{
+	struct axp288_extcon_info *info =
+		container_of(work, struct axp288_extcon_info, role_work);
+	enum usb_role role;
+	bool id_pin;
+	int ret;
+
+	id_pin = axp288_get_id_pin(info);
+	if (!id_pin)
+		role = USB_ROLE_HOST;
+	else if (info->vbus_attach)
+		role = USB_ROLE_DEVICE;
+	else
+		role = USB_ROLE_NONE;
+
+	ret = usb_role_switch_set_role(info->role_sw, role);
+	if (ret)
+		dev_err(info->dev, "failed to set role: %d\n", ret);
+}
+
+static bool axp288_get_vbus_attach(struct axp288_extcon_info *info)
+{
+	int ret, pwr_stat;
 
 	ret = regmap_read(info->regmap, AXP288_PS_STAT_REG, &pwr_stat);
 	if (ret < 0) {
 		dev_err(info->dev, "failed to read vbus status\n");
-		return ret;
+		return false;
 	}
 
-	vbus_attach = (pwr_stat & PS_STAT_VBUS_VALID);
+	return !!(pwr_stat & PS_STAT_VBUS_VALID);
+}
+
+static int axp288_handle_chrg_det_event(struct axp288_extcon_info *info)
+{
+	int ret, stat, cfg;
+	u8 chrg_type;
+	unsigned int cable = info->previous_cable;
+	bool vbus_attach = false;
+
+	vbus_attach = axp288_get_vbus_attach(info);
 	if (!vbus_attach)
 		goto no_vbus;
 
@@ -201,6 +273,12 @@ static int axp288_handle_chrg_det_event(struct axp288_extcon_info *info)
 		info->previous_cable = cable;
 	}
 
+	if (info->role_sw && info->vbus_attach != vbus_attach) {
+		info->vbus_attach = vbus_attach;
+		/* Setting the role can take a while */
+		queue_work(system_long_wq, &info->role_work);
+	}
+
 	return 0;
 
 dev_det_ret:
@@ -210,6 +288,18 @@ static int axp288_handle_chrg_det_event(struct axp288_extcon_info *info)
 	return ret;
 }
 
+static int axp288_extcon_id_evt(struct notifier_block *nb,
+				unsigned long event, void *param)
+{
+	struct axp288_extcon_info *info =
+		container_of(nb, struct axp288_extcon_info, id_nb);
+
+	/* We may not sleep and setting the role can take a while */
+	queue_work(system_long_wq, &info->role_work);
+
+	return NOTIFY_OK;
+}
+
 static irqreturn_t axp288_extcon_isr(int irq, void *data)
 {
 	struct axp288_extcon_info *info = data;
@@ -231,10 +321,20 @@ static void axp288_extcon_enable(struct axp288_extcon_info *info)
 					BC_GLOBAL_RUN, BC_GLOBAL_RUN);
 }
 
+static void axp288_put_role_sw(void *data)
+{
+	struct axp288_extcon_info *info = data;
+
+	cancel_work_sync(&info->role_work);
+	usb_role_switch_put(info->role_sw);
+}
+
 static int axp288_extcon_probe(struct platform_device *pdev)
 {
 	struct axp288_extcon_info *info;
 	struct axp20x_dev *axp20x = dev_get_drvdata(pdev->dev.parent);
+	struct device *dev = &pdev->dev;
+	const char *name;
 	int ret, i, pirq;
 
 	info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
@@ -245,9 +345,33 @@ static int axp288_extcon_probe(struct platform_device *pdev)
 	info->regmap = axp20x->regmap;
 	info->regmap_irqc = axp20x->regmap_irqc;
 	info->previous_cable = EXTCON_NONE;
+	INIT_WORK(&info->role_work, axp288_usb_role_work);
+	info->id_nb.notifier_call = axp288_extcon_id_evt;
 
 	platform_set_drvdata(pdev, info);
 
+	info->role_sw = usb_role_switch_get(dev);
+	if (IS_ERR(info->role_sw))
+		return PTR_ERR(info->role_sw);
+	if (info->role_sw) {
+		ret = devm_add_action_or_reset(dev, axp288_put_role_sw, info);
+		if (ret)
+			return ret;
+
+		name = acpi_dev_get_first_match_name("INT3496", NULL, -1);
+		if (name) {
+			info->id_extcon = extcon_get_extcon_dev(name);
+			if (!info->id_extcon)
+				return -EPROBE_DEFER;
+
+			dev_info(dev, "controlling USB role\n");
+		} else {
+			dev_info(dev, "controlling USB role based on Vbus presence\n");
+		}
+	}
+
+	info->vbus_attach = axp288_get_vbus_attach(info);
+
 	axp288_extcon_log_rsi(info);
 
 	/* Initialize extcon device */
@@ -289,6 +413,19 @@ static int axp288_extcon_probe(struct platform_device *pdev)
 		}
 	}
 
+	if (info->id_extcon) {
+		ret = devm_extcon_register_notifier_all(dev, info->id_extcon,
+							&info->id_nb);
+		if (ret)
+			return ret;
+	}
+
+	/* Make sure the role-sw is set correctly before doing BC detection */
+	if (info->role_sw) {
+		queue_work(system_long_wq, &info->role_work);
+		flush_work(&info->role_work);
+	}
+
 	/* Start charger cable type detection */
 	axp288_extcon_enable(info);
 
@@ -308,8 +445,32 @@ static struct platform_driver axp288_extcon_driver = {
 		.name = "axp288_extcon",
 	},
 };
-module_platform_driver(axp288_extcon_driver);
+
+static struct devcon axp288_extcon_role_sw_conn = {
+	.endpoint[0] = "axp288_extcon",
+	.endpoint[1] = "intel_xhci_usb_sw-role-switch",
+	.id = "usb-role-switch",
+};
+
+static int __init axp288_extcon_init(void)
+{
+	if (x86_match_cpu(cherry_trail_cpu_ids))
+		add_device_connection(&axp288_extcon_role_sw_conn);
+
+	return platform_driver_register(&axp288_extcon_driver);
+}
+module_init(axp288_extcon_init);
+
+static void __exit axp288_extcon_exit(void)
+{
+	if (x86_match_cpu(cherry_trail_cpu_ids))
+		remove_device_connection(&axp288_extcon_role_sw_conn);
+
+	platform_driver_unregister(&axp288_extcon_driver);
+}
+module_exit(axp288_extcon_exit);
 
 MODULE_AUTHOR("Ramakrishna Pallala <ramakrishna.pallala@intel.com>");
+MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
 MODULE_DESCRIPTION("X-Powers AXP288 extcon driver");
 MODULE_LICENSE("GPL v2");

^ permalink raw reply related	[flat|nested] 43+ messages in thread

* Re: [PATCH v5 08/12] xhci: Add Intel extended cap / otg phy mux handling
@ 2018-02-28 15:15     ` Heikki Krogerus
  0 siblings, 0 replies; 43+ messages in thread
From: Heikki Krogerus @ 2018-02-28 15:15 UTC (permalink / raw)
  To: Hans de Goede
  Cc: Darren Hart, Andy Shevchenko, MyungJoo Ham, Chanwoo Choi,
	Mathias Nyman, Greg Kroah-Hartman, Guenter Roeck,
	platform-driver-x86, linux-kernel, linux-usb

On Wed, Feb 28, 2018 at 04:07:45PM +0100, Hans de Goede wrote:
> diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h
> index 96099a245c69..5917e3095e2a 100644
> --- a/drivers/usb/host/xhci.h
> +++ b/drivers/usb/host/xhci.h
> @@ -1825,6 +1825,7 @@ struct xhci_hcd {
>  /* Reserved. It was XHCI_U2_DISABLE_WAKE */
>  #define XHCI_ASMEDIA_MODIFY_FLOWCONTROL	(1 << 28)
>  #define XHCI_HW_LPM_DISABLE	(1 << 29)
> +#define XHCI_INTEL_USB_ROLE_SW	(1 << 30)

Did you rebased these on tope of the latest usb-next? This does not
apply cleanly on top of linux-next.


Thanks,

-- 
heikki

^ permalink raw reply	[flat|nested] 43+ messages in thread

* [v5,08/12] xhci: Add Intel extended cap / otg phy mux handling
@ 2018-02-28 15:15     ` Heikki Krogerus
  0 siblings, 0 replies; 43+ messages in thread
From: Heikki Krogerus @ 2018-02-28 15:15 UTC (permalink / raw)
  To: Hans de Goede
  Cc: Darren Hart, Andy Shevchenko, MyungJoo Ham, Chanwoo Choi,
	Mathias Nyman, Greg Kroah-Hartman, Guenter Roeck,
	platform-driver-x86, linux-kernel, linux-usb

On Wed, Feb 28, 2018 at 04:07:45PM +0100, Hans de Goede wrote:
> diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h
> index 96099a245c69..5917e3095e2a 100644
> --- a/drivers/usb/host/xhci.h
> +++ b/drivers/usb/host/xhci.h
> @@ -1825,6 +1825,7 @@ struct xhci_hcd {
>  /* Reserved. It was XHCI_U2_DISABLE_WAKE */
>  #define XHCI_ASMEDIA_MODIFY_FLOWCONTROL	(1 << 28)
>  #define XHCI_HW_LPM_DISABLE	(1 << 29)
> +#define XHCI_INTEL_USB_ROLE_SW	(1 << 30)

Did you rebased these on tope of the latest usb-next? This does not
apply cleanly on top of linux-next.


Thanks,

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCH v5 08/12] xhci: Add Intel extended cap / otg phy mux handling
@ 2018-02-28 15:42       ` Hans de Goede
  0 siblings, 0 replies; 43+ messages in thread
From: Hans de Goede @ 2018-02-28 15:42 UTC (permalink / raw)
  To: Heikki Krogerus
  Cc: Darren Hart, Andy Shevchenko, MyungJoo Ham, Chanwoo Choi,
	Mathias Nyman, Greg Kroah-Hartman, Guenter Roeck,
	platform-driver-x86, linux-kernel, linux-usb

Hi,

On 28-02-18 16:15, Heikki Krogerus wrote:
> On Wed, Feb 28, 2018 at 04:07:45PM +0100, Hans de Goede wrote:
>> diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h
>> index 96099a245c69..5917e3095e2a 100644
>> --- a/drivers/usb/host/xhci.h
>> +++ b/drivers/usb/host/xhci.h
>> @@ -1825,6 +1825,7 @@ struct xhci_hcd {
>>   /* Reserved. It was XHCI_U2_DISABLE_WAKE */
>>   #define XHCI_ASMEDIA_MODIFY_FLOWCONTROL	(1 << 28)
>>   #define XHCI_HW_LPM_DISABLE	(1 << 29)
>> +#define XHCI_INTEL_USB_ROLE_SW	(1 << 30)
> 
> Did you rebased these on tope of the latest usb-next?

No I did not expect that to be necessary, but I see now that it is.
I've just done a rebase locally, any other remarks before I send
out a v6?

> This does not
> apply cleanly on top of linux-next.

Regards,

Hans

^ permalink raw reply	[flat|nested] 43+ messages in thread

* [v5,08/12] xhci: Add Intel extended cap / otg phy mux handling
@ 2018-02-28 15:42       ` Hans de Goede
  0 siblings, 0 replies; 43+ messages in thread
From: Hans de Goede @ 2018-02-28 15:42 UTC (permalink / raw)
  To: Heikki Krogerus
  Cc: Darren Hart, Andy Shevchenko, MyungJoo Ham, Chanwoo Choi,
	Mathias Nyman, Greg Kroah-Hartman, Guenter Roeck,
	platform-driver-x86, linux-kernel, linux-usb

Hi,

On 28-02-18 16:15, Heikki Krogerus wrote:
> On Wed, Feb 28, 2018 at 04:07:45PM +0100, Hans de Goede wrote:
>> diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h
>> index 96099a245c69..5917e3095e2a 100644
>> --- a/drivers/usb/host/xhci.h
>> +++ b/drivers/usb/host/xhci.h
>> @@ -1825,6 +1825,7 @@ struct xhci_hcd {
>>   /* Reserved. It was XHCI_U2_DISABLE_WAKE */
>>   #define XHCI_ASMEDIA_MODIFY_FLOWCONTROL	(1 << 28)
>>   #define XHCI_HW_LPM_DISABLE	(1 << 29)
>> +#define XHCI_INTEL_USB_ROLE_SW	(1 << 30)
> 
> Did you rebased these on tope of the latest usb-next?

No I did not expect that to be necessary, but I see now that it is.
I've just done a rebase locally, any other remarks before I send
out a v6?

> This does not
> apply cleanly on top of linux-next.

Regards,

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

^ permalink raw reply	[flat|nested] 43+ messages in thread

* RE: [PATCH v5 01/12] drivers: base: Unified device connection lookup
@ 2018-03-01  0:56     ` Jun Li
  0 siblings, 0 replies; 43+ messages in thread
From: Jun Li @ 2018-03-01  0:56 UTC (permalink / raw)
  To: Hans de Goede, Darren Hart, Andy Shevchenko, MyungJoo Ham,
	Chanwoo Choi, Mathias Nyman, Heikki Krogerus, Greg Kroah-Hartman,
	Guenter Roeck
  Cc: platform-driver-x86, linux-kernel, linux-usb

Hi,
> -----Original Message-----

> +struct device *device_find_connection(struct device *dev, const char
> +*con_id) {
> +	return __device_find_connection(dev, con_id, generic_match, NULL); }

-       return __device_find_connection(dev, con_id, generic_match, NULL);
+       return __device_find_connection(dev, con_id, NULL, generic_match);

Jun Li
> +EXPORT_SYMBOL_GPL(device_find_connection);


^ permalink raw reply	[flat|nested] 43+ messages in thread

* [v5,01/12] drivers: base: Unified device connection lookup
@ 2018-03-01  0:56     ` Jun Li
  0 siblings, 0 replies; 43+ messages in thread
From: Jun Li @ 2018-03-01  0:56 UTC (permalink / raw)
  To: Hans de Goede, Darren Hart, Andy Shevchenko, MyungJoo Ham,
	Chanwoo Choi, Mathias Nyman, Heikki Krogerus, Greg Kroah-Hartman,
	Guenter Roeck
  Cc: platform-driver-x86, linux-kernel, linux-usb

Hi,
> -----Original Message-----

> +struct device *device_find_connection(struct device *dev, const char
> +*con_id) {
> +	return __device_find_connection(dev, con_id, generic_match, NULL); }

-       return __device_find_connection(dev, con_id, generic_match, NULL);
+       return __device_find_connection(dev, con_id, NULL, generic_match);

Jun Li
> +EXPORT_SYMBOL_GPL(device_find_connection);
---
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCH v5 08/12] xhci: Add Intel extended cap / otg phy mux handling
@ 2018-03-01  7:23         ` Heikki Krogerus
  0 siblings, 0 replies; 43+ messages in thread
From: Heikki Krogerus @ 2018-03-01  7:23 UTC (permalink / raw)
  To: Hans de Goede
  Cc: Darren Hart, Andy Shevchenko, MyungJoo Ham, Chanwoo Choi,
	Mathias Nyman, Greg Kroah-Hartman, Guenter Roeck,
	platform-driver-x86, linux-kernel, linux-usb

On Wed, Feb 28, 2018 at 04:42:32PM +0100, Hans de Goede wrote:
> Hi,
> 
> On 28-02-18 16:15, Heikki Krogerus wrote:
> > On Wed, Feb 28, 2018 at 04:07:45PM +0100, Hans de Goede wrote:
> > > diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h
> > > index 96099a245c69..5917e3095e2a 100644
> > > --- a/drivers/usb/host/xhci.h
> > > +++ b/drivers/usb/host/xhci.h
> > > @@ -1825,6 +1825,7 @@ struct xhci_hcd {
> > >   /* Reserved. It was XHCI_U2_DISABLE_WAKE */
> > >   #define XHCI_ASMEDIA_MODIFY_FLOWCONTROL	(1 << 28)
> > >   #define XHCI_HW_LPM_DISABLE	(1 << 29)
> > > +#define XHCI_INTEL_USB_ROLE_SW	(1 << 30)
> > 
> > Did you rebased these on tope of the latest usb-next?
> 
> No I did not expect that to be necessary, but I see now that it is.
> I've just done a rebase locally, any other remarks before I send
> out a v6?

Nothing from me.


Thanks,

-- 
heikki

^ permalink raw reply	[flat|nested] 43+ messages in thread

* [v5,08/12] xhci: Add Intel extended cap / otg phy mux handling
@ 2018-03-01  7:23         ` Heikki Krogerus
  0 siblings, 0 replies; 43+ messages in thread
From: Heikki Krogerus @ 2018-03-01  7:23 UTC (permalink / raw)
  To: Hans de Goede
  Cc: Darren Hart, Andy Shevchenko, MyungJoo Ham, Chanwoo Choi,
	Mathias Nyman, Greg Kroah-Hartman, Guenter Roeck,
	platform-driver-x86, linux-kernel, linux-usb

On Wed, Feb 28, 2018 at 04:42:32PM +0100, Hans de Goede wrote:
> Hi,
> 
> On 28-02-18 16:15, Heikki Krogerus wrote:
> > On Wed, Feb 28, 2018 at 04:07:45PM +0100, Hans de Goede wrote:
> > > diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h
> > > index 96099a245c69..5917e3095e2a 100644
> > > --- a/drivers/usb/host/xhci.h
> > > +++ b/drivers/usb/host/xhci.h
> > > @@ -1825,6 +1825,7 @@ struct xhci_hcd {
> > >   /* Reserved. It was XHCI_U2_DISABLE_WAKE */
> > >   #define XHCI_ASMEDIA_MODIFY_FLOWCONTROL	(1 << 28)
> > >   #define XHCI_HW_LPM_DISABLE	(1 << 29)
> > > +#define XHCI_INTEL_USB_ROLE_SW	(1 << 30)
> > 
> > Did you rebased these on tope of the latest usb-next?
> 
> No I did not expect that to be necessary, but I see now that it is.
> I've just done a rebase locally, any other remarks before I send
> out a v6?

Nothing from me.


Thanks,

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCH v5 01/12] drivers: base: Unified device connection lookup
@ 2018-03-01  7:28       ` Heikki Krogerus
  0 siblings, 0 replies; 43+ messages in thread
From: Heikki Krogerus @ 2018-03-01  7:28 UTC (permalink / raw)
  To: Jun Li
  Cc: Hans de Goede, Darren Hart, Andy Shevchenko, MyungJoo Ham,
	Chanwoo Choi, Mathias Nyman, Greg Kroah-Hartman, Guenter Roeck,
	platform-driver-x86, linux-kernel, linux-usb

Hi,

On Thu, Mar 01, 2018 at 12:56:57AM +0000, Jun Li wrote:
> > +struct device *device_find_connection(struct device *dev, const char
> > +*con_id) {
> > +	return __device_find_connection(dev, con_id, generic_match, NULL); }
> 
> -       return __device_find_connection(dev, con_id, generic_match, NULL);
> +       return __device_find_connection(dev, con_id, NULL, generic_match);

Good catch!

Thanks,

-- 
heikki

^ permalink raw reply	[flat|nested] 43+ messages in thread

* [v5,01/12] drivers: base: Unified device connection lookup
@ 2018-03-01  7:28       ` Heikki Krogerus
  0 siblings, 0 replies; 43+ messages in thread
From: Heikki Krogerus @ 2018-03-01  7:28 UTC (permalink / raw)
  To: Jun Li
  Cc: Hans de Goede, Darren Hart, Andy Shevchenko, MyungJoo Ham,
	Chanwoo Choi, Mathias Nyman, Greg Kroah-Hartman, Guenter Roeck,
	platform-driver-x86, linux-kernel, linux-usb

Hi,

On Thu, Mar 01, 2018 at 12:56:57AM +0000, Jun Li wrote:
> > +struct device *device_find_connection(struct device *dev, const char
> > +*con_id) {
> > +	return __device_find_connection(dev, con_id, generic_match, NULL); }
> 
> -       return __device_find_connection(dev, con_id, generic_match, NULL);
> +       return __device_find_connection(dev, con_id, NULL, generic_match);

Good catch!

Thanks,

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCH v5 01/12] drivers: base: Unified device connection lookup
@ 2018-03-01  9:32         ` Andy Shevchenko
  0 siblings, 0 replies; 43+ messages in thread
From: Andy Shevchenko @ 2018-03-01  9:32 UTC (permalink / raw)
  To: Heikki Krogerus
  Cc: Jun Li, Hans de Goede, Darren Hart, Andy Shevchenko,
	MyungJoo Ham, Chanwoo Choi, Mathias Nyman, Greg Kroah-Hartman,
	Guenter Roeck, platform-driver-x86, linux-kernel, linux-usb

On Thu, Mar 1, 2018 at 9:28 AM, Heikki Krogerus
<heikki.krogerus@linux.intel.com> wrote:
> Hi,
>
> On Thu, Mar 01, 2018 at 12:56:57AM +0000, Jun Li wrote:
>> > +struct device *device_find_connection(struct device *dev, const char
>> > +*con_id) {
>> > +   return __device_find_connection(dev, con_id, generic_match, NULL); }
>>
>> -       return __device_find_connection(dev, con_id, generic_match, NULL);
>> +       return __device_find_connection(dev, con_id, NULL, generic_match);
>
> Good catch!

It seems I proposed to put function first parameter followed by opaque
data pointer for it.
In that case it would be exactly like now.

Though, I didn't check if the parameter ordering was changed in the prototype.

-- 
With Best Regards,
Andy Shevchenko

^ permalink raw reply	[flat|nested] 43+ messages in thread

* [v5,01/12] drivers: base: Unified device connection lookup
@ 2018-03-01  9:32         ` Andy Shevchenko
  0 siblings, 0 replies; 43+ messages in thread
From: Andy Shevchenko @ 2018-03-01  9:32 UTC (permalink / raw)
  To: Heikki Krogerus
  Cc: Jun Li, Hans de Goede, Darren Hart, Andy Shevchenko,
	MyungJoo Ham, Chanwoo Choi, Mathias Nyman, Greg Kroah-Hartman,
	Guenter Roeck, platform-driver-x86, linux-kernel, linux-usb

On Thu, Mar 1, 2018 at 9:28 AM, Heikki Krogerus
<heikki.krogerus@linux.intel.com> wrote:
> Hi,
>
> On Thu, Mar 01, 2018 at 12:56:57AM +0000, Jun Li wrote:
>> > +struct device *device_find_connection(struct device *dev, const char
>> > +*con_id) {
>> > +   return __device_find_connection(dev, con_id, generic_match, NULL); }
>>
>> -       return __device_find_connection(dev, con_id, generic_match, NULL);
>> +       return __device_find_connection(dev, con_id, NULL, generic_match);
>
> Good catch!

It seems I proposed to put function first parameter followed by opaque
data pointer for it.
In that case it would be exactly like now.

Though, I didn't check if the parameter ordering was changed in the prototype.

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCH v5 01/12] drivers: base: Unified device connection lookup
@ 2018-03-01  9:45           ` Hans de Goede
  0 siblings, 0 replies; 43+ messages in thread
From: Hans de Goede @ 2018-03-01  9:45 UTC (permalink / raw)
  To: Andy Shevchenko, Heikki Krogerus
  Cc: Jun Li, Darren Hart, Andy Shevchenko, MyungJoo Ham, Chanwoo Choi,
	Mathias Nyman, Greg Kroah-Hartman, Guenter Roeck,
	platform-driver-x86, linux-kernel, linux-usb

Hi,

On 01-03-18 10:32, Andy Shevchenko wrote:
> On Thu, Mar 1, 2018 at 9:28 AM, Heikki Krogerus
> <heikki.krogerus@linux.intel.com> wrote:
>> Hi,
>>
>> On Thu, Mar 01, 2018 at 12:56:57AM +0000, Jun Li wrote:
>>>> +struct device *device_find_connection(struct device *dev, const char
>>>> +*con_id) {
>>>> +   return __device_find_connection(dev, con_id, generic_match, NULL); }
>>>
>>> -       return __device_find_connection(dev, con_id, generic_match, NULL);
>>> +       return __device_find_connection(dev, con_id, NULL, generic_match);
>>
>> Good catch!
> 
> It seems I proposed to put function first parameter followed by opaque
> data pointer for it.
> In that case it would be exactly like now.

Yes, but as mentioned I decided to keep it as is, so this is really a bug,
will fix for v6.

Regards,

Hans

^ permalink raw reply	[flat|nested] 43+ messages in thread

* [v5,01/12] drivers: base: Unified device connection lookup
@ 2018-03-01  9:45           ` Hans de Goede
  0 siblings, 0 replies; 43+ messages in thread
From: Hans de Goede @ 2018-03-01  9:45 UTC (permalink / raw)
  To: Andy Shevchenko, Heikki Krogerus
  Cc: Jun Li, Darren Hart, Andy Shevchenko, MyungJoo Ham, Chanwoo Choi,
	Mathias Nyman, Greg Kroah-Hartman, Guenter Roeck,
	platform-driver-x86, linux-kernel, linux-usb

Hi,

On 01-03-18 10:32, Andy Shevchenko wrote:
> On Thu, Mar 1, 2018 at 9:28 AM, Heikki Krogerus
> <heikki.krogerus@linux.intel.com> wrote:
>> Hi,
>>
>> On Thu, Mar 01, 2018 at 12:56:57AM +0000, Jun Li wrote:
>>>> +struct device *device_find_connection(struct device *dev, const char
>>>> +*con_id) {
>>>> +   return __device_find_connection(dev, con_id, generic_match, NULL); }
>>>
>>> -       return __device_find_connection(dev, con_id, generic_match, NULL);
>>> +       return __device_find_connection(dev, con_id, NULL, generic_match);
>>
>> Good catch!
> 
> It seems I proposed to put function first parameter followed by opaque
> data pointer for it.
> In that case it would be exactly like now.

Yes, but as mentioned I decided to keep it as is, so this is really a bug,
will fix for v6.

Regards,

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

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCH v5 12/12] extcon: axp288: Set USB role where necessary
@ 2018-03-02  0:39     ` Chanwoo Choi
  0 siblings, 0 replies; 43+ messages in thread
From: Chanwoo Choi @ 2018-03-02  0:39 UTC (permalink / raw)
  To: Hans de Goede, Darren Hart, Andy Shevchenko, MyungJoo Ham,
	Mathias Nyman, Heikki Krogerus, Greg Kroah-Hartman,
	Guenter Roeck
  Cc: platform-driver-x86, linux-kernel, linux-usb

Hi,

Basically, I have no objection. But I'll reply the my ack tag 
after finishing the review of 'devcon and usb_role_switch' from USB maintainer.

And I have a question.
Before this patch, extcon-axp288 is used to detect charger connector
and extcon-intel-int3496 is used to detect the USB_HOST connector
on one h/w device?

Best Regards,
Chanwoo Choi
Samsung Electronics

On 2018년 03월 01일 00:07, Hans de Goede wrote:
> The AXP288 BC1.2 charger detection / extcon code may seem like a strange
> place to add code to control the USB role-switch on devices with an AXP288,
> but there are 2 reasons to do this inside the axp288 extcon code:
> 
> 1) On many devices the USB role is controlled by ACPI AML code, but the AML
>    code only switches between the host and none roles, because of Windows
>    not really using device mode. To make device mode work we need to toggle
>    between the none/device roles based on Vbus presence, and the axp288
>    extcon gets interrupts on Vbus insertion / removal.
> 
> 2) In order for our BC1.2 charger detection to work properly the role
>    mux must be properly set to device mode before we do the detection.
> 
> Also note the Kconfig help-text / obsolete depends on USB_PHY which are
> remnants from older never upstreamed code also controlling the mux from
> the axp288 extcon code.
> 
> This commit also adds code to get notifications from the INT3496 extcon
> device, which is used on some devices to notify the kernel about id-pin
> changes instead of them being handled through AML code.
> 
> This fixes:
> -Device mode not working on most CHT devices with an AXP288
> -Host mode not working on devices with an INT3496 ACPI device
> -Charger-type misdetection (always SDP) on devices with an INT3496 when the
>  USB role (always) gets initialized as host
> 
> Reviewed-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
> Reviewed-by: Andy Shevchenko <andy.shevchenko@gmail.com>
> Signed-off-by: Hans de Goede <hdegoede@redhat.com>
> ---
> Changes in v4:
> -Add Andy's Reviewed-by
> 
> Changes in v2:
> -Add depends on X86 to Kconfig (the AXP288 PMIC is only used on X86)
> -Use new acpi_dev_get_first_match_name() helper to get the INT3496 device-name
> -Add Heikki's Reviewed-by
> ---
>  drivers/extcon/Kconfig         |   3 +-
>  drivers/extcon/extcon-axp288.c | 177 +++++++++++++++++++++++++++++++++++++++--
>  2 files changed, 171 insertions(+), 9 deletions(-)
> 
> diff --git a/drivers/extcon/Kconfig b/drivers/extcon/Kconfig
> index a7bca4207f44..de15bf55895b 100644
> --- a/drivers/extcon/Kconfig
> +++ b/drivers/extcon/Kconfig
> @@ -30,7 +30,8 @@ config EXTCON_ARIZONA
>  
>  config EXTCON_AXP288
>  	tristate "X-Power AXP288 EXTCON support"
> -	depends on MFD_AXP20X && USB_PHY
> +	depends on MFD_AXP20X && USB_SUPPORT && X86
> +	select USB_ROLE_SWITCH
>  	help
>  	  Say Y here to enable support for USB peripheral detection
>  	  and USB MUX switching by X-Power AXP288 PMIC.
> diff --git a/drivers/extcon/extcon-axp288.c b/drivers/extcon/extcon-axp288.c
> index 3ec4c715e240..51e77c7a32c2 100644
> --- a/drivers/extcon/extcon-axp288.c
> +++ b/drivers/extcon/extcon-axp288.c
> @@ -1,6 +1,7 @@
>  /*
>   * extcon-axp288.c - X-Power AXP288 PMIC extcon cable detection driver
>   *
> + * Copyright (c) 2017-2018 Hans de Goede <hdegoede@redhat.com>
>   * Copyright (C) 2015 Intel Corporation
>   * Author: Ramakrishna Pallala <ramakrishna.pallala@intel.com>
>   *
> @@ -14,6 +15,8 @@
>   * GNU General Public License for more details.
>   */
>  
> +#include <linux/acpi.h>
> +#include <linux/connection.h>
>  #include <linux/module.h>
>  #include <linux/kernel.h>
>  #include <linux/io.h>
> @@ -25,6 +28,11 @@
>  #include <linux/extcon-provider.h>
>  #include <linux/regmap.h>
>  #include <linux/mfd/axp20x.h>
> +#include <linux/usb/role.h>
> +#include <linux/workqueue.h>
> +
> +#include <asm/cpu_device_id.h>
> +#include <asm/intel-family.h>
>  
>  /* Power source status register */
>  #define PS_STAT_VBUS_TRIGGER		BIT(0)
> @@ -97,9 +105,19 @@ struct axp288_extcon_info {
>  	struct device *dev;
>  	struct regmap *regmap;
>  	struct regmap_irq_chip_data *regmap_irqc;
> +	struct usb_role_switch *role_sw;
> +	struct work_struct role_work;
>  	int irq[EXTCON_IRQ_END];
>  	struct extcon_dev *edev;
> +	struct extcon_dev *id_extcon;
> +	struct notifier_block id_nb;
>  	unsigned int previous_cable;
> +	bool vbus_attach;
> +};
> +
> +static const struct x86_cpu_id cherry_trail_cpu_ids[] = {
> +	{ X86_VENDOR_INTEL, 6, INTEL_FAM6_ATOM_AIRMONT, X86_FEATURE_ANY },
> +	{}
>  };
>  
>  /* Power up/down reason string array */
> @@ -137,20 +155,74 @@ static void axp288_extcon_log_rsi(struct axp288_extcon_info *info)
>  	regmap_write(info->regmap, AXP288_PS_BOOT_REASON_REG, clear_mask);
>  }
>  
> -static int axp288_handle_chrg_det_event(struct axp288_extcon_info *info)
> +/*
> + * The below code to control the USB role-switch on devices with an AXP288
> + * may seem out of place, but there are 2 reasons why this is the best place
> + * to control the USB role-switch on such devices:
> + * 1) On many devices the USB role is controlled by AML code, but the AML code
> + *    only switches between the host and none roles, because of Windows not
> + *    really using device mode. To make device mode work we need to toggle
> + *    between the none/device roles based on Vbus presence, and this driver
> + *    gets interrupts on Vbus insertion / removal.
> + * 2) In order for our BC1.2 charger detection to work properly the role
> + *    mux must be properly set to device mode before we do the detection.
> + */
> +
> +/* Returns the id-pin value, note pulled low / false == host-mode */
> +static bool axp288_get_id_pin(struct axp288_extcon_info *info)
>  {
> -	int ret, stat, cfg, pwr_stat;
> -	u8 chrg_type;
> -	unsigned int cable = info->previous_cable;
> -	bool vbus_attach = false;
> +	enum usb_role role;
> +
> +	if (info->id_extcon)
> +		return extcon_get_state(info->id_extcon, EXTCON_USB_HOST) <= 0;
> +
> +	/* We cannot access the id-pin, see what mode the AML code has set */
> +	role = usb_role_switch_get_role(info->role_sw);
> +	return role != USB_ROLE_HOST;
> +}
> +
> +static void axp288_usb_role_work(struct work_struct *work)
> +{
> +	struct axp288_extcon_info *info =
> +		container_of(work, struct axp288_extcon_info, role_work);
> +	enum usb_role role;
> +	bool id_pin;
> +	int ret;
> +
> +	id_pin = axp288_get_id_pin(info);
> +	if (!id_pin)
> +		role = USB_ROLE_HOST;
> +	else if (info->vbus_attach)
> +		role = USB_ROLE_DEVICE;
> +	else
> +		role = USB_ROLE_NONE;
> +
> +	ret = usb_role_switch_set_role(info->role_sw, role);
> +	if (ret)
> +		dev_err(info->dev, "failed to set role: %d\n", ret);
> +}
> +
> +static bool axp288_get_vbus_attach(struct axp288_extcon_info *info)
> +{
> +	int ret, pwr_stat;
>  
>  	ret = regmap_read(info->regmap, AXP288_PS_STAT_REG, &pwr_stat);
>  	if (ret < 0) {
>  		dev_err(info->dev, "failed to read vbus status\n");
> -		return ret;
> +		return false;
>  	}
>  
> -	vbus_attach = (pwr_stat & PS_STAT_VBUS_VALID);
> +	return !!(pwr_stat & PS_STAT_VBUS_VALID);
> +}
> +
> +static int axp288_handle_chrg_det_event(struct axp288_extcon_info *info)
> +{
> +	int ret, stat, cfg;
> +	u8 chrg_type;
> +	unsigned int cable = info->previous_cable;
> +	bool vbus_attach = false;
> +
> +	vbus_attach = axp288_get_vbus_attach(info);
>  	if (!vbus_attach)
>  		goto no_vbus;
>  
> @@ -201,6 +273,12 @@ static int axp288_handle_chrg_det_event(struct axp288_extcon_info *info)
>  		info->previous_cable = cable;
>  	}
>  
> +	if (info->role_sw && info->vbus_attach != vbus_attach) {
> +		info->vbus_attach = vbus_attach;
> +		/* Setting the role can take a while */
> +		queue_work(system_long_wq, &info->role_work);
> +	}
> +
>  	return 0;
>  
>  dev_det_ret:
> @@ -210,6 +288,18 @@ static int axp288_handle_chrg_det_event(struct axp288_extcon_info *info)
>  	return ret;
>  }
>  
> +static int axp288_extcon_id_evt(struct notifier_block *nb,
> +				unsigned long event, void *param)
> +{
> +	struct axp288_extcon_info *info =
> +		container_of(nb, struct axp288_extcon_info, id_nb);
> +
> +	/* We may not sleep and setting the role can take a while */
> +	queue_work(system_long_wq, &info->role_work);
> +
> +	return NOTIFY_OK;
> +}
> +
>  static irqreturn_t axp288_extcon_isr(int irq, void *data)
>  {
>  	struct axp288_extcon_info *info = data;
> @@ -231,10 +321,20 @@ static void axp288_extcon_enable(struct axp288_extcon_info *info)
>  					BC_GLOBAL_RUN, BC_GLOBAL_RUN);
>  }
>  
> +static void axp288_put_role_sw(void *data)
> +{
> +	struct axp288_extcon_info *info = data;
> +
> +	cancel_work_sync(&info->role_work);
> +	usb_role_switch_put(info->role_sw);
> +}
> +
>  static int axp288_extcon_probe(struct platform_device *pdev)
>  {
>  	struct axp288_extcon_info *info;
>  	struct axp20x_dev *axp20x = dev_get_drvdata(pdev->dev.parent);
> +	struct device *dev = &pdev->dev;
> +	const char *name;
>  	int ret, i, pirq;
>  
>  	info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
> @@ -245,9 +345,33 @@ static int axp288_extcon_probe(struct platform_device *pdev)
>  	info->regmap = axp20x->regmap;
>  	info->regmap_irqc = axp20x->regmap_irqc;
>  	info->previous_cable = EXTCON_NONE;
> +	INIT_WORK(&info->role_work, axp288_usb_role_work);
> +	info->id_nb.notifier_call = axp288_extcon_id_evt;
>  
>  	platform_set_drvdata(pdev, info);
>  
> +	info->role_sw = usb_role_switch_get(dev);
> +	if (IS_ERR(info->role_sw))
> +		return PTR_ERR(info->role_sw);
> +	if (info->role_sw) {
> +		ret = devm_add_action_or_reset(dev, axp288_put_role_sw, info);
> +		if (ret)
> +			return ret;
> +
> +		name = acpi_dev_get_first_match_name("INT3496", NULL, -1);
> +		if (name) {
> +			info->id_extcon = extcon_get_extcon_dev(name);
> +			if (!info->id_extcon)
> +				return -EPROBE_DEFER;
> +
> +			dev_info(dev, "controlling USB role\n");
> +		} else {
> +			dev_info(dev, "controlling USB role based on Vbus presence\n");
> +		}
> +	}
> +
> +	info->vbus_attach = axp288_get_vbus_attach(info);
> +
>  	axp288_extcon_log_rsi(info);
>  
>  	/* Initialize extcon device */
> @@ -289,6 +413,19 @@ static int axp288_extcon_probe(struct platform_device *pdev)
>  		}
>  	}
>  
> +	if (info->id_extcon) {
> +		ret = devm_extcon_register_notifier_all(dev, info->id_extcon,
> +							&info->id_nb);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	/* Make sure the role-sw is set correctly before doing BC detection */
> +	if (info->role_sw) {
> +		queue_work(system_long_wq, &info->role_work);
> +		flush_work(&info->role_work);
> +	}
> +
>  	/* Start charger cable type detection */
>  	axp288_extcon_enable(info);
>  
> @@ -308,8 +445,32 @@ static struct platform_driver axp288_extcon_driver = {
>  		.name = "axp288_extcon",
>  	},
>  };
> -module_platform_driver(axp288_extcon_driver);
> +
> +static struct devcon axp288_extcon_role_sw_conn = {
> +	.endpoint[0] = "axp288_extcon",
> +	.endpoint[1] = "intel_xhci_usb_sw-role-switch",
> +	.id = "usb-role-switch",
> +};
> +
> +static int __init axp288_extcon_init(void)
> +{
> +	if (x86_match_cpu(cherry_trail_cpu_ids))
> +		add_device_connection(&axp288_extcon_role_sw_conn);
> +
> +	return platform_driver_register(&axp288_extcon_driver);
> +}
> +module_init(axp288_extcon_init);
> +
> +static void __exit axp288_extcon_exit(void)
> +{
> +	if (x86_match_cpu(cherry_trail_cpu_ids))
> +		remove_device_connection(&axp288_extcon_role_sw_conn);
> +
> +	platform_driver_unregister(&axp288_extcon_driver);
> +}
> +module_exit(axp288_extcon_exit);
>  
>  MODULE_AUTHOR("Ramakrishna Pallala <ramakrishna.pallala@intel.com>");
> +MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
>  MODULE_DESCRIPTION("X-Powers AXP288 extcon driver");
>  MODULE_LICENSE("GPL v2");
> 


^ permalink raw reply	[flat|nested] 43+ messages in thread

* [v5,12/12] extcon: axp288: Set USB role where necessary
@ 2018-03-02  0:39     ` Chanwoo Choi
  0 siblings, 0 replies; 43+ messages in thread
From: Chanwoo Choi @ 2018-03-02  0:39 UTC (permalink / raw)
  To: Hans de Goede, Darren Hart, Andy Shevchenko, MyungJoo Ham,
	Mathias Nyman, Heikki Krogerus, Greg Kroah-Hartman,
	Guenter Roeck
  Cc: platform-driver-x86, linux-kernel, linux-usb

Hi,

Basically, I have no objection. But I'll reply the my ack tag 
after finishing the review of 'devcon and usb_role_switch' from USB maintainer.

And I have a question.
Before this patch, extcon-axp288 is used to detect charger connector
and extcon-intel-int3496 is used to detect the USB_HOST connector
on one h/w device?

Best Regards,
Chanwoo Choi
Samsung Electronics

On 2018년 03월 01일 00:07, Hans de Goede wrote:
> The AXP288 BC1.2 charger detection / extcon code may seem like a strange
> place to add code to control the USB role-switch on devices with an AXP288,
> but there are 2 reasons to do this inside the axp288 extcon code:
> 
> 1) On many devices the USB role is controlled by ACPI AML code, but the AML
>    code only switches between the host and none roles, because of Windows
>    not really using device mode. To make device mode work we need to toggle
>    between the none/device roles based on Vbus presence, and the axp288
>    extcon gets interrupts on Vbus insertion / removal.
> 
> 2) In order for our BC1.2 charger detection to work properly the role
>    mux must be properly set to device mode before we do the detection.
> 
> Also note the Kconfig help-text / obsolete depends on USB_PHY which are
> remnants from older never upstreamed code also controlling the mux from
> the axp288 extcon code.
> 
> This commit also adds code to get notifications from the INT3496 extcon
> device, which is used on some devices to notify the kernel about id-pin
> changes instead of them being handled through AML code.
> 
> This fixes:
> -Device mode not working on most CHT devices with an AXP288
> -Host mode not working on devices with an INT3496 ACPI device
> -Charger-type misdetection (always SDP) on devices with an INT3496 when the
>  USB role (always) gets initialized as host
> 
> Reviewed-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
> Reviewed-by: Andy Shevchenko <andy.shevchenko@gmail.com>
> Signed-off-by: Hans de Goede <hdegoede@redhat.com>
> ---
> Changes in v4:
> -Add Andy's Reviewed-by
> 
> Changes in v2:
> -Add depends on X86 to Kconfig (the AXP288 PMIC is only used on X86)
> -Use new acpi_dev_get_first_match_name() helper to get the INT3496 device-name
> -Add Heikki's Reviewed-by
> ---
>  drivers/extcon/Kconfig         |   3 +-
>  drivers/extcon/extcon-axp288.c | 177 +++++++++++++++++++++++++++++++++++++++--
>  2 files changed, 171 insertions(+), 9 deletions(-)
> 
> diff --git a/drivers/extcon/Kconfig b/drivers/extcon/Kconfig
> index a7bca4207f44..de15bf55895b 100644
> --- a/drivers/extcon/Kconfig
> +++ b/drivers/extcon/Kconfig
> @@ -30,7 +30,8 @@ config EXTCON_ARIZONA
>  
>  config EXTCON_AXP288
>  	tristate "X-Power AXP288 EXTCON support"
> -	depends on MFD_AXP20X && USB_PHY
> +	depends on MFD_AXP20X && USB_SUPPORT && X86
> +	select USB_ROLE_SWITCH
>  	help
>  	  Say Y here to enable support for USB peripheral detection
>  	  and USB MUX switching by X-Power AXP288 PMIC.
> diff --git a/drivers/extcon/extcon-axp288.c b/drivers/extcon/extcon-axp288.c
> index 3ec4c715e240..51e77c7a32c2 100644
> --- a/drivers/extcon/extcon-axp288.c
> +++ b/drivers/extcon/extcon-axp288.c
> @@ -1,6 +1,7 @@
>  /*
>   * extcon-axp288.c - X-Power AXP288 PMIC extcon cable detection driver
>   *
> + * Copyright (c) 2017-2018 Hans de Goede <hdegoede@redhat.com>
>   * Copyright (C) 2015 Intel Corporation
>   * Author: Ramakrishna Pallala <ramakrishna.pallala@intel.com>
>   *
> @@ -14,6 +15,8 @@
>   * GNU General Public License for more details.
>   */
>  
> +#include <linux/acpi.h>
> +#include <linux/connection.h>
>  #include <linux/module.h>
>  #include <linux/kernel.h>
>  #include <linux/io.h>
> @@ -25,6 +28,11 @@
>  #include <linux/extcon-provider.h>
>  #include <linux/regmap.h>
>  #include <linux/mfd/axp20x.h>
> +#include <linux/usb/role.h>
> +#include <linux/workqueue.h>
> +
> +#include <asm/cpu_device_id.h>
> +#include <asm/intel-family.h>
>  
>  /* Power source status register */
>  #define PS_STAT_VBUS_TRIGGER		BIT(0)
> @@ -97,9 +105,19 @@ struct axp288_extcon_info {
>  	struct device *dev;
>  	struct regmap *regmap;
>  	struct regmap_irq_chip_data *regmap_irqc;
> +	struct usb_role_switch *role_sw;
> +	struct work_struct role_work;
>  	int irq[EXTCON_IRQ_END];
>  	struct extcon_dev *edev;
> +	struct extcon_dev *id_extcon;
> +	struct notifier_block id_nb;
>  	unsigned int previous_cable;
> +	bool vbus_attach;
> +};
> +
> +static const struct x86_cpu_id cherry_trail_cpu_ids[] = {
> +	{ X86_VENDOR_INTEL, 6, INTEL_FAM6_ATOM_AIRMONT, X86_FEATURE_ANY },
> +	{}
>  };
>  
>  /* Power up/down reason string array */
> @@ -137,20 +155,74 @@ static void axp288_extcon_log_rsi(struct axp288_extcon_info *info)
>  	regmap_write(info->regmap, AXP288_PS_BOOT_REASON_REG, clear_mask);
>  }
>  
> -static int axp288_handle_chrg_det_event(struct axp288_extcon_info *info)
> +/*
> + * The below code to control the USB role-switch on devices with an AXP288
> + * may seem out of place, but there are 2 reasons why this is the best place
> + * to control the USB role-switch on such devices:
> + * 1) On many devices the USB role is controlled by AML code, but the AML code
> + *    only switches between the host and none roles, because of Windows not
> + *    really using device mode. To make device mode work we need to toggle
> + *    between the none/device roles based on Vbus presence, and this driver
> + *    gets interrupts on Vbus insertion / removal.
> + * 2) In order for our BC1.2 charger detection to work properly the role
> + *    mux must be properly set to device mode before we do the detection.
> + */
> +
> +/* Returns the id-pin value, note pulled low / false == host-mode */
> +static bool axp288_get_id_pin(struct axp288_extcon_info *info)
>  {
> -	int ret, stat, cfg, pwr_stat;
> -	u8 chrg_type;
> -	unsigned int cable = info->previous_cable;
> -	bool vbus_attach = false;
> +	enum usb_role role;
> +
> +	if (info->id_extcon)
> +		return extcon_get_state(info->id_extcon, EXTCON_USB_HOST) <= 0;
> +
> +	/* We cannot access the id-pin, see what mode the AML code has set */
> +	role = usb_role_switch_get_role(info->role_sw);
> +	return role != USB_ROLE_HOST;
> +}
> +
> +static void axp288_usb_role_work(struct work_struct *work)
> +{
> +	struct axp288_extcon_info *info =
> +		container_of(work, struct axp288_extcon_info, role_work);
> +	enum usb_role role;
> +	bool id_pin;
> +	int ret;
> +
> +	id_pin = axp288_get_id_pin(info);
> +	if (!id_pin)
> +		role = USB_ROLE_HOST;
> +	else if (info->vbus_attach)
> +		role = USB_ROLE_DEVICE;
> +	else
> +		role = USB_ROLE_NONE;
> +
> +	ret = usb_role_switch_set_role(info->role_sw, role);
> +	if (ret)
> +		dev_err(info->dev, "failed to set role: %d\n", ret);
> +}
> +
> +static bool axp288_get_vbus_attach(struct axp288_extcon_info *info)
> +{
> +	int ret, pwr_stat;
>  
>  	ret = regmap_read(info->regmap, AXP288_PS_STAT_REG, &pwr_stat);
>  	if (ret < 0) {
>  		dev_err(info->dev, "failed to read vbus status\n");
> -		return ret;
> +		return false;
>  	}
>  
> -	vbus_attach = (pwr_stat & PS_STAT_VBUS_VALID);
> +	return !!(pwr_stat & PS_STAT_VBUS_VALID);
> +}
> +
> +static int axp288_handle_chrg_det_event(struct axp288_extcon_info *info)
> +{
> +	int ret, stat, cfg;
> +	u8 chrg_type;
> +	unsigned int cable = info->previous_cable;
> +	bool vbus_attach = false;
> +
> +	vbus_attach = axp288_get_vbus_attach(info);
>  	if (!vbus_attach)
>  		goto no_vbus;
>  
> @@ -201,6 +273,12 @@ static int axp288_handle_chrg_det_event(struct axp288_extcon_info *info)
>  		info->previous_cable = cable;
>  	}
>  
> +	if (info->role_sw && info->vbus_attach != vbus_attach) {
> +		info->vbus_attach = vbus_attach;
> +		/* Setting the role can take a while */
> +		queue_work(system_long_wq, &info->role_work);
> +	}
> +
>  	return 0;
>  
>  dev_det_ret:
> @@ -210,6 +288,18 @@ static int axp288_handle_chrg_det_event(struct axp288_extcon_info *info)
>  	return ret;
>  }
>  
> +static int axp288_extcon_id_evt(struct notifier_block *nb,
> +				unsigned long event, void *param)
> +{
> +	struct axp288_extcon_info *info =
> +		container_of(nb, struct axp288_extcon_info, id_nb);
> +
> +	/* We may not sleep and setting the role can take a while */
> +	queue_work(system_long_wq, &info->role_work);
> +
> +	return NOTIFY_OK;
> +}
> +
>  static irqreturn_t axp288_extcon_isr(int irq, void *data)
>  {
>  	struct axp288_extcon_info *info = data;
> @@ -231,10 +321,20 @@ static void axp288_extcon_enable(struct axp288_extcon_info *info)
>  					BC_GLOBAL_RUN, BC_GLOBAL_RUN);
>  }
>  
> +static void axp288_put_role_sw(void *data)
> +{
> +	struct axp288_extcon_info *info = data;
> +
> +	cancel_work_sync(&info->role_work);
> +	usb_role_switch_put(info->role_sw);
> +}
> +
>  static int axp288_extcon_probe(struct platform_device *pdev)
>  {
>  	struct axp288_extcon_info *info;
>  	struct axp20x_dev *axp20x = dev_get_drvdata(pdev->dev.parent);
> +	struct device *dev = &pdev->dev;
> +	const char *name;
>  	int ret, i, pirq;
>  
>  	info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
> @@ -245,9 +345,33 @@ static int axp288_extcon_probe(struct platform_device *pdev)
>  	info->regmap = axp20x->regmap;
>  	info->regmap_irqc = axp20x->regmap_irqc;
>  	info->previous_cable = EXTCON_NONE;
> +	INIT_WORK(&info->role_work, axp288_usb_role_work);
> +	info->id_nb.notifier_call = axp288_extcon_id_evt;
>  
>  	platform_set_drvdata(pdev, info);
>  
> +	info->role_sw = usb_role_switch_get(dev);
> +	if (IS_ERR(info->role_sw))
> +		return PTR_ERR(info->role_sw);
> +	if (info->role_sw) {
> +		ret = devm_add_action_or_reset(dev, axp288_put_role_sw, info);
> +		if (ret)
> +			return ret;
> +
> +		name = acpi_dev_get_first_match_name("INT3496", NULL, -1);
> +		if (name) {
> +			info->id_extcon = extcon_get_extcon_dev(name);
> +			if (!info->id_extcon)
> +				return -EPROBE_DEFER;
> +
> +			dev_info(dev, "controlling USB role\n");
> +		} else {
> +			dev_info(dev, "controlling USB role based on Vbus presence\n");
> +		}
> +	}
> +
> +	info->vbus_attach = axp288_get_vbus_attach(info);
> +
>  	axp288_extcon_log_rsi(info);
>  
>  	/* Initialize extcon device */
> @@ -289,6 +413,19 @@ static int axp288_extcon_probe(struct platform_device *pdev)
>  		}
>  	}
>  
> +	if (info->id_extcon) {
> +		ret = devm_extcon_register_notifier_all(dev, info->id_extcon,
> +							&info->id_nb);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	/* Make sure the role-sw is set correctly before doing BC detection */
> +	if (info->role_sw) {
> +		queue_work(system_long_wq, &info->role_work);
> +		flush_work(&info->role_work);
> +	}
> +
>  	/* Start charger cable type detection */
>  	axp288_extcon_enable(info);
>  
> @@ -308,8 +445,32 @@ static struct platform_driver axp288_extcon_driver = {
>  		.name = "axp288_extcon",
>  	},
>  };
> -module_platform_driver(axp288_extcon_driver);
> +
> +static struct devcon axp288_extcon_role_sw_conn = {
> +	.endpoint[0] = "axp288_extcon",
> +	.endpoint[1] = "intel_xhci_usb_sw-role-switch",
> +	.id = "usb-role-switch",
> +};
> +
> +static int __init axp288_extcon_init(void)
> +{
> +	if (x86_match_cpu(cherry_trail_cpu_ids))
> +		add_device_connection(&axp288_extcon_role_sw_conn);
> +
> +	return platform_driver_register(&axp288_extcon_driver);
> +}
> +module_init(axp288_extcon_init);
> +
> +static void __exit axp288_extcon_exit(void)
> +{
> +	if (x86_match_cpu(cherry_trail_cpu_ids))
> +		remove_device_connection(&axp288_extcon_role_sw_conn);
> +
> +	platform_driver_unregister(&axp288_extcon_driver);
> +}
> +module_exit(axp288_extcon_exit);
>  
>  MODULE_AUTHOR("Ramakrishna Pallala <ramakrishna.pallala@intel.com>");
> +MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
>  MODULE_DESCRIPTION("X-Powers AXP288 extcon driver");
>  MODULE_LICENSE("GPL v2");
>
---
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCH v5 12/12] extcon: axp288: Set USB role where necessary
@ 2018-03-02  9:06       ` Hans de Goede
  0 siblings, 0 replies; 43+ messages in thread
From: Hans de Goede @ 2018-03-02  9:06 UTC (permalink / raw)
  To: Chanwoo Choi, Darren Hart, Andy Shevchenko, MyungJoo Ham,
	Mathias Nyman, Heikki Krogerus, Greg Kroah-Hartman,
	Guenter Roeck
  Cc: platform-driver-x86, linux-kernel, linux-usb

Hi,

On 02-03-18 01:39, Chanwoo Choi wrote:
> Hi,
> 
> Basically, I have no objection. But I'll reply the my ack tag
> after finishing the review of 'devcon and usb_role_switch' from USB maintainer.
> 
> And I have a question.
> Before this patch, extcon-axp288 is used to detect charger connector
> and extcon-intel-int3496 is used to detect the USB_HOST connector
> on one h/w device?

Yes the ACPI tables of some devices with an AXP288 PMIC have an INT3496
ACPI device which gives access to the id-pin on the micro-AB connector
which the extcon-intel-int3496 exports as a USB_HOST connector, on these
devices we fully control the USB role.

On other devices the switching of the USB data lines is controlled by AML
code which switches the data lines, but never sets the VBus valid bit
in the USB PHY control registers, so it is effectively switching between
between the host and none roles, since the device role only works when
the VBus valid bit is set.

This patch addresses both types of devices / ACPI tables and makes
host *and* device mode work. This patch also makes switching between them
by plugging in a different cable after boot work.

Regards,

Hans



> 
> Best Regards,
> Chanwoo Choi
> Samsung Electronics
> 
> On 2018년 03월 01일 00:07, Hans de Goede wrote:
>> The AXP288 BC1.2 charger detection / extcon code may seem like a strange
>> place to add code to control the USB role-switch on devices with an AXP288,
>> but there are 2 reasons to do this inside the axp288 extcon code:
>>
>> 1) On many devices the USB role is controlled by ACPI AML code, but the AML
>>     code only switches between the host and none roles, because of Windows
>>     not really using device mode. To make device mode work we need to toggle
>>     between the none/device roles based on Vbus presence, and the axp288
>>     extcon gets interrupts on Vbus insertion / removal.
>>
>> 2) In order for our BC1.2 charger detection to work properly the role
>>     mux must be properly set to device mode before we do the detection.
>>
>> Also note the Kconfig help-text / obsolete depends on USB_PHY which are
>> remnants from older never upstreamed code also controlling the mux from
>> the axp288 extcon code.
>>
>> This commit also adds code to get notifications from the INT3496 extcon
>> device, which is used on some devices to notify the kernel about id-pin
>> changes instead of them being handled through AML code.
>>
>> This fixes:
>> -Device mode not working on most CHT devices with an AXP288
>> -Host mode not working on devices with an INT3496 ACPI device
>> -Charger-type misdetection (always SDP) on devices with an INT3496 when the
>>   USB role (always) gets initialized as host
>>
>> Reviewed-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
>> Reviewed-by: Andy Shevchenko <andy.shevchenko@gmail.com>
>> Signed-off-by: Hans de Goede <hdegoede@redhat.com>
>> ---
>> Changes in v4:
>> -Add Andy's Reviewed-by
>>
>> Changes in v2:
>> -Add depends on X86 to Kconfig (the AXP288 PMIC is only used on X86)
>> -Use new acpi_dev_get_first_match_name() helper to get the INT3496 device-name
>> -Add Heikki's Reviewed-by
>> ---
>>   drivers/extcon/Kconfig         |   3 +-
>>   drivers/extcon/extcon-axp288.c | 177 +++++++++++++++++++++++++++++++++++++++--
>>   2 files changed, 171 insertions(+), 9 deletions(-)
>>
>> diff --git a/drivers/extcon/Kconfig b/drivers/extcon/Kconfig
>> index a7bca4207f44..de15bf55895b 100644
>> --- a/drivers/extcon/Kconfig
>> +++ b/drivers/extcon/Kconfig
>> @@ -30,7 +30,8 @@ config EXTCON_ARIZONA
>>   
>>   config EXTCON_AXP288
>>   	tristate "X-Power AXP288 EXTCON support"
>> -	depends on MFD_AXP20X && USB_PHY
>> +	depends on MFD_AXP20X && USB_SUPPORT && X86
>> +	select USB_ROLE_SWITCH
>>   	help
>>   	  Say Y here to enable support for USB peripheral detection
>>   	  and USB MUX switching by X-Power AXP288 PMIC.
>> diff --git a/drivers/extcon/extcon-axp288.c b/drivers/extcon/extcon-axp288.c
>> index 3ec4c715e240..51e77c7a32c2 100644
>> --- a/drivers/extcon/extcon-axp288.c
>> +++ b/drivers/extcon/extcon-axp288.c
>> @@ -1,6 +1,7 @@
>>   /*
>>    * extcon-axp288.c - X-Power AXP288 PMIC extcon cable detection driver
>>    *
>> + * Copyright (c) 2017-2018 Hans de Goede <hdegoede@redhat.com>
>>    * Copyright (C) 2015 Intel Corporation
>>    * Author: Ramakrishna Pallala <ramakrishna.pallala@intel.com>
>>    *
>> @@ -14,6 +15,8 @@
>>    * GNU General Public License for more details.
>>    */
>>   
>> +#include <linux/acpi.h>
>> +#include <linux/connection.h>
>>   #include <linux/module.h>
>>   #include <linux/kernel.h>
>>   #include <linux/io.h>
>> @@ -25,6 +28,11 @@
>>   #include <linux/extcon-provider.h>
>>   #include <linux/regmap.h>
>>   #include <linux/mfd/axp20x.h>
>> +#include <linux/usb/role.h>
>> +#include <linux/workqueue.h>
>> +
>> +#include <asm/cpu_device_id.h>
>> +#include <asm/intel-family.h>
>>   
>>   /* Power source status register */
>>   #define PS_STAT_VBUS_TRIGGER		BIT(0)
>> @@ -97,9 +105,19 @@ struct axp288_extcon_info {
>>   	struct device *dev;
>>   	struct regmap *regmap;
>>   	struct regmap_irq_chip_data *regmap_irqc;
>> +	struct usb_role_switch *role_sw;
>> +	struct work_struct role_work;
>>   	int irq[EXTCON_IRQ_END];
>>   	struct extcon_dev *edev;
>> +	struct extcon_dev *id_extcon;
>> +	struct notifier_block id_nb;
>>   	unsigned int previous_cable;
>> +	bool vbus_attach;
>> +};
>> +
>> +static const struct x86_cpu_id cherry_trail_cpu_ids[] = {
>> +	{ X86_VENDOR_INTEL, 6, INTEL_FAM6_ATOM_AIRMONT, X86_FEATURE_ANY },
>> +	{}
>>   };
>>   
>>   /* Power up/down reason string array */
>> @@ -137,20 +155,74 @@ static void axp288_extcon_log_rsi(struct axp288_extcon_info *info)
>>   	regmap_write(info->regmap, AXP288_PS_BOOT_REASON_REG, clear_mask);
>>   }
>>   
>> -static int axp288_handle_chrg_det_event(struct axp288_extcon_info *info)
>> +/*
>> + * The below code to control the USB role-switch on devices with an AXP288
>> + * may seem out of place, but there are 2 reasons why this is the best place
>> + * to control the USB role-switch on such devices:
>> + * 1) On many devices the USB role is controlled by AML code, but the AML code
>> + *    only switches between the host and none roles, because of Windows not
>> + *    really using device mode. To make device mode work we need to toggle
>> + *    between the none/device roles based on Vbus presence, and this driver
>> + *    gets interrupts on Vbus insertion / removal.
>> + * 2) In order for our BC1.2 charger detection to work properly the role
>> + *    mux must be properly set to device mode before we do the detection.
>> + */
>> +
>> +/* Returns the id-pin value, note pulled low / false == host-mode */
>> +static bool axp288_get_id_pin(struct axp288_extcon_info *info)
>>   {
>> -	int ret, stat, cfg, pwr_stat;
>> -	u8 chrg_type;
>> -	unsigned int cable = info->previous_cable;
>> -	bool vbus_attach = false;
>> +	enum usb_role role;
>> +
>> +	if (info->id_extcon)
>> +		return extcon_get_state(info->id_extcon, EXTCON_USB_HOST) <= 0;
>> +
>> +	/* We cannot access the id-pin, see what mode the AML code has set */
>> +	role = usb_role_switch_get_role(info->role_sw);
>> +	return role != USB_ROLE_HOST;
>> +}
>> +
>> +static void axp288_usb_role_work(struct work_struct *work)
>> +{
>> +	struct axp288_extcon_info *info =
>> +		container_of(work, struct axp288_extcon_info, role_work);
>> +	enum usb_role role;
>> +	bool id_pin;
>> +	int ret;
>> +
>> +	id_pin = axp288_get_id_pin(info);
>> +	if (!id_pin)
>> +		role = USB_ROLE_HOST;
>> +	else if (info->vbus_attach)
>> +		role = USB_ROLE_DEVICE;
>> +	else
>> +		role = USB_ROLE_NONE;
>> +
>> +	ret = usb_role_switch_set_role(info->role_sw, role);
>> +	if (ret)
>> +		dev_err(info->dev, "failed to set role: %d\n", ret);
>> +}
>> +
>> +static bool axp288_get_vbus_attach(struct axp288_extcon_info *info)
>> +{
>> +	int ret, pwr_stat;
>>   
>>   	ret = regmap_read(info->regmap, AXP288_PS_STAT_REG, &pwr_stat);
>>   	if (ret < 0) {
>>   		dev_err(info->dev, "failed to read vbus status\n");
>> -		return ret;
>> +		return false;
>>   	}
>>   
>> -	vbus_attach = (pwr_stat & PS_STAT_VBUS_VALID);
>> +	return !!(pwr_stat & PS_STAT_VBUS_VALID);
>> +}
>> +
>> +static int axp288_handle_chrg_det_event(struct axp288_extcon_info *info)
>> +{
>> +	int ret, stat, cfg;
>> +	u8 chrg_type;
>> +	unsigned int cable = info->previous_cable;
>> +	bool vbus_attach = false;
>> +
>> +	vbus_attach = axp288_get_vbus_attach(info);
>>   	if (!vbus_attach)
>>   		goto no_vbus;
>>   
>> @@ -201,6 +273,12 @@ static int axp288_handle_chrg_det_event(struct axp288_extcon_info *info)
>>   		info->previous_cable = cable;
>>   	}
>>   
>> +	if (info->role_sw && info->vbus_attach != vbus_attach) {
>> +		info->vbus_attach = vbus_attach;
>> +		/* Setting the role can take a while */
>> +		queue_work(system_long_wq, &info->role_work);
>> +	}
>> +
>>   	return 0;
>>   
>>   dev_det_ret:
>> @@ -210,6 +288,18 @@ static int axp288_handle_chrg_det_event(struct axp288_extcon_info *info)
>>   	return ret;
>>   }
>>   
>> +static int axp288_extcon_id_evt(struct notifier_block *nb,
>> +				unsigned long event, void *param)
>> +{
>> +	struct axp288_extcon_info *info =
>> +		container_of(nb, struct axp288_extcon_info, id_nb);
>> +
>> +	/* We may not sleep and setting the role can take a while */
>> +	queue_work(system_long_wq, &info->role_work);
>> +
>> +	return NOTIFY_OK;
>> +}
>> +
>>   static irqreturn_t axp288_extcon_isr(int irq, void *data)
>>   {
>>   	struct axp288_extcon_info *info = data;
>> @@ -231,10 +321,20 @@ static void axp288_extcon_enable(struct axp288_extcon_info *info)
>>   					BC_GLOBAL_RUN, BC_GLOBAL_RUN);
>>   }
>>   
>> +static void axp288_put_role_sw(void *data)
>> +{
>> +	struct axp288_extcon_info *info = data;
>> +
>> +	cancel_work_sync(&info->role_work);
>> +	usb_role_switch_put(info->role_sw);
>> +}
>> +
>>   static int axp288_extcon_probe(struct platform_device *pdev)
>>   {
>>   	struct axp288_extcon_info *info;
>>   	struct axp20x_dev *axp20x = dev_get_drvdata(pdev->dev.parent);
>> +	struct device *dev = &pdev->dev;
>> +	const char *name;
>>   	int ret, i, pirq;
>>   
>>   	info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
>> @@ -245,9 +345,33 @@ static int axp288_extcon_probe(struct platform_device *pdev)
>>   	info->regmap = axp20x->regmap;
>>   	info->regmap_irqc = axp20x->regmap_irqc;
>>   	info->previous_cable = EXTCON_NONE;
>> +	INIT_WORK(&info->role_work, axp288_usb_role_work);
>> +	info->id_nb.notifier_call = axp288_extcon_id_evt;
>>   
>>   	platform_set_drvdata(pdev, info);
>>   
>> +	info->role_sw = usb_role_switch_get(dev);
>> +	if (IS_ERR(info->role_sw))
>> +		return PTR_ERR(info->role_sw);
>> +	if (info->role_sw) {
>> +		ret = devm_add_action_or_reset(dev, axp288_put_role_sw, info);
>> +		if (ret)
>> +			return ret;
>> +
>> +		name = acpi_dev_get_first_match_name("INT3496", NULL, -1);
>> +		if (name) {
>> +			info->id_extcon = extcon_get_extcon_dev(name);
>> +			if (!info->id_extcon)
>> +				return -EPROBE_DEFER;
>> +
>> +			dev_info(dev, "controlling USB role\n");
>> +		} else {
>> +			dev_info(dev, "controlling USB role based on Vbus presence\n");
>> +		}
>> +	}
>> +
>> +	info->vbus_attach = axp288_get_vbus_attach(info);
>> +
>>   	axp288_extcon_log_rsi(info);
>>   
>>   	/* Initialize extcon device */
>> @@ -289,6 +413,19 @@ static int axp288_extcon_probe(struct platform_device *pdev)
>>   		}
>>   	}
>>   
>> +	if (info->id_extcon) {
>> +		ret = devm_extcon_register_notifier_all(dev, info->id_extcon,
>> +							&info->id_nb);
>> +		if (ret)
>> +			return ret;
>> +	}
>> +
>> +	/* Make sure the role-sw is set correctly before doing BC detection */
>> +	if (info->role_sw) {
>> +		queue_work(system_long_wq, &info->role_work);
>> +		flush_work(&info->role_work);
>> +	}
>> +
>>   	/* Start charger cable type detection */
>>   	axp288_extcon_enable(info);
>>   
>> @@ -308,8 +445,32 @@ static struct platform_driver axp288_extcon_driver = {
>>   		.name = "axp288_extcon",
>>   	},
>>   };
>> -module_platform_driver(axp288_extcon_driver);
>> +
>> +static struct devcon axp288_extcon_role_sw_conn = {
>> +	.endpoint[0] = "axp288_extcon",
>> +	.endpoint[1] = "intel_xhci_usb_sw-role-switch",
>> +	.id = "usb-role-switch",
>> +};
>> +
>> +static int __init axp288_extcon_init(void)
>> +{
>> +	if (x86_match_cpu(cherry_trail_cpu_ids))
>> +		add_device_connection(&axp288_extcon_role_sw_conn);
>> +
>> +	return platform_driver_register(&axp288_extcon_driver);
>> +}
>> +module_init(axp288_extcon_init);
>> +
>> +static void __exit axp288_extcon_exit(void)
>> +{
>> +	if (x86_match_cpu(cherry_trail_cpu_ids))
>> +		remove_device_connection(&axp288_extcon_role_sw_conn);
>> +
>> +	platform_driver_unregister(&axp288_extcon_driver);
>> +}
>> +module_exit(axp288_extcon_exit);
>>   
>>   MODULE_AUTHOR("Ramakrishna Pallala <ramakrishna.pallala@intel.com>");
>> +MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
>>   MODULE_DESCRIPTION("X-Powers AXP288 extcon driver");
>>   MODULE_LICENSE("GPL v2");
>>
> 

^ permalink raw reply	[flat|nested] 43+ messages in thread

* [v5,12/12] extcon: axp288: Set USB role where necessary
@ 2018-03-02  9:06       ` Hans de Goede
  0 siblings, 0 replies; 43+ messages in thread
From: Hans de Goede @ 2018-03-02  9:06 UTC (permalink / raw)
  To: Chanwoo Choi, Darren Hart, Andy Shevchenko, MyungJoo Ham,
	Mathias Nyman, Heikki Krogerus, Greg Kroah-Hartman,
	Guenter Roeck
  Cc: platform-driver-x86, linux-kernel, linux-usb

Hi,

On 02-03-18 01:39, Chanwoo Choi wrote:
> Hi,
> 
> Basically, I have no objection. But I'll reply the my ack tag
> after finishing the review of 'devcon and usb_role_switch' from USB maintainer.
> 
> And I have a question.
> Before this patch, extcon-axp288 is used to detect charger connector
> and extcon-intel-int3496 is used to detect the USB_HOST connector
> on one h/w device?

Yes the ACPI tables of some devices with an AXP288 PMIC have an INT3496
ACPI device which gives access to the id-pin on the micro-AB connector
which the extcon-intel-int3496 exports as a USB_HOST connector, on these
devices we fully control the USB role.

On other devices the switching of the USB data lines is controlled by AML
code which switches the data lines, but never sets the VBus valid bit
in the USB PHY control registers, so it is effectively switching between
between the host and none roles, since the device role only works when
the VBus valid bit is set.

This patch addresses both types of devices / ACPI tables and makes
host *and* device mode work. This patch also makes switching between them
by plugging in a different cable after boot work.

Regards,

Hans



> 
> Best Regards,
> Chanwoo Choi
> Samsung Electronics
> 
> On 2018년 03월 01일 00:07, Hans de Goede wrote:
>> The AXP288 BC1.2 charger detection / extcon code may seem like a strange
>> place to add code to control the USB role-switch on devices with an AXP288,
>> but there are 2 reasons to do this inside the axp288 extcon code:
>>
>> 1) On many devices the USB role is controlled by ACPI AML code, but the AML
>>     code only switches between the host and none roles, because of Windows
>>     not really using device mode. To make device mode work we need to toggle
>>     between the none/device roles based on Vbus presence, and the axp288
>>     extcon gets interrupts on Vbus insertion / removal.
>>
>> 2) In order for our BC1.2 charger detection to work properly the role
>>     mux must be properly set to device mode before we do the detection.
>>
>> Also note the Kconfig help-text / obsolete depends on USB_PHY which are
>> remnants from older never upstreamed code also controlling the mux from
>> the axp288 extcon code.
>>
>> This commit also adds code to get notifications from the INT3496 extcon
>> device, which is used on some devices to notify the kernel about id-pin
>> changes instead of them being handled through AML code.
>>
>> This fixes:
>> -Device mode not working on most CHT devices with an AXP288
>> -Host mode not working on devices with an INT3496 ACPI device
>> -Charger-type misdetection (always SDP) on devices with an INT3496 when the
>>   USB role (always) gets initialized as host
>>
>> Reviewed-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
>> Reviewed-by: Andy Shevchenko <andy.shevchenko@gmail.com>
>> Signed-off-by: Hans de Goede <hdegoede@redhat.com>
>> ---
>> Changes in v4:
>> -Add Andy's Reviewed-by
>>
>> Changes in v2:
>> -Add depends on X86 to Kconfig (the AXP288 PMIC is only used on X86)
>> -Use new acpi_dev_get_first_match_name() helper to get the INT3496 device-name
>> -Add Heikki's Reviewed-by
>> ---
>>   drivers/extcon/Kconfig         |   3 +-
>>   drivers/extcon/extcon-axp288.c | 177 +++++++++++++++++++++++++++++++++++++++--
>>   2 files changed, 171 insertions(+), 9 deletions(-)
>>
>> diff --git a/drivers/extcon/Kconfig b/drivers/extcon/Kconfig
>> index a7bca4207f44..de15bf55895b 100644
>> --- a/drivers/extcon/Kconfig
>> +++ b/drivers/extcon/Kconfig
>> @@ -30,7 +30,8 @@ config EXTCON_ARIZONA
>>   
>>   config EXTCON_AXP288
>>   	tristate "X-Power AXP288 EXTCON support"
>> -	depends on MFD_AXP20X && USB_PHY
>> +	depends on MFD_AXP20X && USB_SUPPORT && X86
>> +	select USB_ROLE_SWITCH
>>   	help
>>   	  Say Y here to enable support for USB peripheral detection
>>   	  and USB MUX switching by X-Power AXP288 PMIC.
>> diff --git a/drivers/extcon/extcon-axp288.c b/drivers/extcon/extcon-axp288.c
>> index 3ec4c715e240..51e77c7a32c2 100644
>> --- a/drivers/extcon/extcon-axp288.c
>> +++ b/drivers/extcon/extcon-axp288.c
>> @@ -1,6 +1,7 @@
>>   /*
>>    * extcon-axp288.c - X-Power AXP288 PMIC extcon cable detection driver
>>    *
>> + * Copyright (c) 2017-2018 Hans de Goede <hdegoede@redhat.com>
>>    * Copyright (C) 2015 Intel Corporation
>>    * Author: Ramakrishna Pallala <ramakrishna.pallala@intel.com>
>>    *
>> @@ -14,6 +15,8 @@
>>    * GNU General Public License for more details.
>>    */
>>   
>> +#include <linux/acpi.h>
>> +#include <linux/connection.h>
>>   #include <linux/module.h>
>>   #include <linux/kernel.h>
>>   #include <linux/io.h>
>> @@ -25,6 +28,11 @@
>>   #include <linux/extcon-provider.h>
>>   #include <linux/regmap.h>
>>   #include <linux/mfd/axp20x.h>
>> +#include <linux/usb/role.h>
>> +#include <linux/workqueue.h>
>> +
>> +#include <asm/cpu_device_id.h>
>> +#include <asm/intel-family.h>
>>   
>>   /* Power source status register */
>>   #define PS_STAT_VBUS_TRIGGER		BIT(0)
>> @@ -97,9 +105,19 @@ struct axp288_extcon_info {
>>   	struct device *dev;
>>   	struct regmap *regmap;
>>   	struct regmap_irq_chip_data *regmap_irqc;
>> +	struct usb_role_switch *role_sw;
>> +	struct work_struct role_work;
>>   	int irq[EXTCON_IRQ_END];
>>   	struct extcon_dev *edev;
>> +	struct extcon_dev *id_extcon;
>> +	struct notifier_block id_nb;
>>   	unsigned int previous_cable;
>> +	bool vbus_attach;
>> +};
>> +
>> +static const struct x86_cpu_id cherry_trail_cpu_ids[] = {
>> +	{ X86_VENDOR_INTEL, 6, INTEL_FAM6_ATOM_AIRMONT, X86_FEATURE_ANY },
>> +	{}
>>   };
>>   
>>   /* Power up/down reason string array */
>> @@ -137,20 +155,74 @@ static void axp288_extcon_log_rsi(struct axp288_extcon_info *info)
>>   	regmap_write(info->regmap, AXP288_PS_BOOT_REASON_REG, clear_mask);
>>   }
>>   
>> -static int axp288_handle_chrg_det_event(struct axp288_extcon_info *info)
>> +/*
>> + * The below code to control the USB role-switch on devices with an AXP288
>> + * may seem out of place, but there are 2 reasons why this is the best place
>> + * to control the USB role-switch on such devices:
>> + * 1) On many devices the USB role is controlled by AML code, but the AML code
>> + *    only switches between the host and none roles, because of Windows not
>> + *    really using device mode. To make device mode work we need to toggle
>> + *    between the none/device roles based on Vbus presence, and this driver
>> + *    gets interrupts on Vbus insertion / removal.
>> + * 2) In order for our BC1.2 charger detection to work properly the role
>> + *    mux must be properly set to device mode before we do the detection.
>> + */
>> +
>> +/* Returns the id-pin value, note pulled low / false == host-mode */
>> +static bool axp288_get_id_pin(struct axp288_extcon_info *info)
>>   {
>> -	int ret, stat, cfg, pwr_stat;
>> -	u8 chrg_type;
>> -	unsigned int cable = info->previous_cable;
>> -	bool vbus_attach = false;
>> +	enum usb_role role;
>> +
>> +	if (info->id_extcon)
>> +		return extcon_get_state(info->id_extcon, EXTCON_USB_HOST) <= 0;
>> +
>> +	/* We cannot access the id-pin, see what mode the AML code has set */
>> +	role = usb_role_switch_get_role(info->role_sw);
>> +	return role != USB_ROLE_HOST;
>> +}
>> +
>> +static void axp288_usb_role_work(struct work_struct *work)
>> +{
>> +	struct axp288_extcon_info *info =
>> +		container_of(work, struct axp288_extcon_info, role_work);
>> +	enum usb_role role;
>> +	bool id_pin;
>> +	int ret;
>> +
>> +	id_pin = axp288_get_id_pin(info);
>> +	if (!id_pin)
>> +		role = USB_ROLE_HOST;
>> +	else if (info->vbus_attach)
>> +		role = USB_ROLE_DEVICE;
>> +	else
>> +		role = USB_ROLE_NONE;
>> +
>> +	ret = usb_role_switch_set_role(info->role_sw, role);
>> +	if (ret)
>> +		dev_err(info->dev, "failed to set role: %d\n", ret);
>> +}
>> +
>> +static bool axp288_get_vbus_attach(struct axp288_extcon_info *info)
>> +{
>> +	int ret, pwr_stat;
>>   
>>   	ret = regmap_read(info->regmap, AXP288_PS_STAT_REG, &pwr_stat);
>>   	if (ret < 0) {
>>   		dev_err(info->dev, "failed to read vbus status\n");
>> -		return ret;
>> +		return false;
>>   	}
>>   
>> -	vbus_attach = (pwr_stat & PS_STAT_VBUS_VALID);
>> +	return !!(pwr_stat & PS_STAT_VBUS_VALID);
>> +}
>> +
>> +static int axp288_handle_chrg_det_event(struct axp288_extcon_info *info)
>> +{
>> +	int ret, stat, cfg;
>> +	u8 chrg_type;
>> +	unsigned int cable = info->previous_cable;
>> +	bool vbus_attach = false;
>> +
>> +	vbus_attach = axp288_get_vbus_attach(info);
>>   	if (!vbus_attach)
>>   		goto no_vbus;
>>   
>> @@ -201,6 +273,12 @@ static int axp288_handle_chrg_det_event(struct axp288_extcon_info *info)
>>   		info->previous_cable = cable;
>>   	}
>>   
>> +	if (info->role_sw && info->vbus_attach != vbus_attach) {
>> +		info->vbus_attach = vbus_attach;
>> +		/* Setting the role can take a while */
>> +		queue_work(system_long_wq, &info->role_work);
>> +	}
>> +
>>   	return 0;
>>   
>>   dev_det_ret:
>> @@ -210,6 +288,18 @@ static int axp288_handle_chrg_det_event(struct axp288_extcon_info *info)
>>   	return ret;
>>   }
>>   
>> +static int axp288_extcon_id_evt(struct notifier_block *nb,
>> +				unsigned long event, void *param)
>> +{
>> +	struct axp288_extcon_info *info =
>> +		container_of(nb, struct axp288_extcon_info, id_nb);
>> +
>> +	/* We may not sleep and setting the role can take a while */
>> +	queue_work(system_long_wq, &info->role_work);
>> +
>> +	return NOTIFY_OK;
>> +}
>> +
>>   static irqreturn_t axp288_extcon_isr(int irq, void *data)
>>   {
>>   	struct axp288_extcon_info *info = data;
>> @@ -231,10 +321,20 @@ static void axp288_extcon_enable(struct axp288_extcon_info *info)
>>   					BC_GLOBAL_RUN, BC_GLOBAL_RUN);
>>   }
>>   
>> +static void axp288_put_role_sw(void *data)
>> +{
>> +	struct axp288_extcon_info *info = data;
>> +
>> +	cancel_work_sync(&info->role_work);
>> +	usb_role_switch_put(info->role_sw);
>> +}
>> +
>>   static int axp288_extcon_probe(struct platform_device *pdev)
>>   {
>>   	struct axp288_extcon_info *info;
>>   	struct axp20x_dev *axp20x = dev_get_drvdata(pdev->dev.parent);
>> +	struct device *dev = &pdev->dev;
>> +	const char *name;
>>   	int ret, i, pirq;
>>   
>>   	info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
>> @@ -245,9 +345,33 @@ static int axp288_extcon_probe(struct platform_device *pdev)
>>   	info->regmap = axp20x->regmap;
>>   	info->regmap_irqc = axp20x->regmap_irqc;
>>   	info->previous_cable = EXTCON_NONE;
>> +	INIT_WORK(&info->role_work, axp288_usb_role_work);
>> +	info->id_nb.notifier_call = axp288_extcon_id_evt;
>>   
>>   	platform_set_drvdata(pdev, info);
>>   
>> +	info->role_sw = usb_role_switch_get(dev);
>> +	if (IS_ERR(info->role_sw))
>> +		return PTR_ERR(info->role_sw);
>> +	if (info->role_sw) {
>> +		ret = devm_add_action_or_reset(dev, axp288_put_role_sw, info);
>> +		if (ret)
>> +			return ret;
>> +
>> +		name = acpi_dev_get_first_match_name("INT3496", NULL, -1);
>> +		if (name) {
>> +			info->id_extcon = extcon_get_extcon_dev(name);
>> +			if (!info->id_extcon)
>> +				return -EPROBE_DEFER;
>> +
>> +			dev_info(dev, "controlling USB role\n");
>> +		} else {
>> +			dev_info(dev, "controlling USB role based on Vbus presence\n");
>> +		}
>> +	}
>> +
>> +	info->vbus_attach = axp288_get_vbus_attach(info);
>> +
>>   	axp288_extcon_log_rsi(info);
>>   
>>   	/* Initialize extcon device */
>> @@ -289,6 +413,19 @@ static int axp288_extcon_probe(struct platform_device *pdev)
>>   		}
>>   	}
>>   
>> +	if (info->id_extcon) {
>> +		ret = devm_extcon_register_notifier_all(dev, info->id_extcon,
>> +							&info->id_nb);
>> +		if (ret)
>> +			return ret;
>> +	}
>> +
>> +	/* Make sure the role-sw is set correctly before doing BC detection */
>> +	if (info->role_sw) {
>> +		queue_work(system_long_wq, &info->role_work);
>> +		flush_work(&info->role_work);
>> +	}
>> +
>>   	/* Start charger cable type detection */
>>   	axp288_extcon_enable(info);
>>   
>> @@ -308,8 +445,32 @@ static struct platform_driver axp288_extcon_driver = {
>>   		.name = "axp288_extcon",
>>   	},
>>   };
>> -module_platform_driver(axp288_extcon_driver);
>> +
>> +static struct devcon axp288_extcon_role_sw_conn = {
>> +	.endpoint[0] = "axp288_extcon",
>> +	.endpoint[1] = "intel_xhci_usb_sw-role-switch",
>> +	.id = "usb-role-switch",
>> +};
>> +
>> +static int __init axp288_extcon_init(void)
>> +{
>> +	if (x86_match_cpu(cherry_trail_cpu_ids))
>> +		add_device_connection(&axp288_extcon_role_sw_conn);
>> +
>> +	return platform_driver_register(&axp288_extcon_driver);
>> +}
>> +module_init(axp288_extcon_init);
>> +
>> +static void __exit axp288_extcon_exit(void)
>> +{
>> +	if (x86_match_cpu(cherry_trail_cpu_ids))
>> +		remove_device_connection(&axp288_extcon_role_sw_conn);
>> +
>> +	platform_driver_unregister(&axp288_extcon_driver);
>> +}
>> +module_exit(axp288_extcon_exit);
>>   
>>   MODULE_AUTHOR("Ramakrishna Pallala <ramakrishna.pallala@intel.com>");
>> +MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
>>   MODULE_DESCRIPTION("X-Powers AXP288 extcon driver");
>>   MODULE_LICENSE("GPL v2");
>>
>
---
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

^ permalink raw reply	[flat|nested] 43+ messages in thread

end of thread, other threads:[~2018-03-02  9:06 UTC | newest]

Thread overview: 43+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2018-02-28 15:07 [PATCH v5 00/12] USB Type-C device-connection, mux and switch support Hans de Goede
2018-02-28 15:07 ` [PATCH v5 01/12] drivers: base: Unified device connection lookup Hans de Goede
2018-02-28 15:07   ` [v5,01/12] " Hans de Goede
2018-03-01  0:56   ` [PATCH v5 01/12] " Jun Li
2018-03-01  0:56     ` [v5,01/12] " Jun Li
2018-03-01  7:28     ` [PATCH v5 01/12] " Heikki Krogerus
2018-03-01  7:28       ` [v5,01/12] " Heikki Krogerus
2018-03-01  9:32       ` [PATCH v5 01/12] " Andy Shevchenko
2018-03-01  9:32         ` [v5,01/12] " Andy Shevchenko
2018-03-01  9:45         ` [PATCH v5 01/12] " Hans de Goede
2018-03-01  9:45           ` [v5,01/12] " Hans de Goede
2018-02-28 15:07 ` [PATCH v5 02/12] usb: typec: Start using ERR_PTR Hans de Goede
2018-02-28 15:07   ` [v5,02/12] " Hans de Goede
2018-02-28 15:07 ` [PATCH v5 03/12] usb: typec: API for controlling USB Type-C Multiplexers Hans de Goede
2018-02-28 15:07   ` [v5,03/12] " Hans de Goede
2018-02-28 15:07 ` [PATCH v5 04/12] usb: common: Small class for USB role switches Hans de Goede
2018-02-28 15:07   ` [v5,04/12] " Hans de Goede
2018-02-28 15:07 ` [PATCH v5 05/12] usb: typec: tcpm: Set USB role switch to device mode when configured as such Hans de Goede
2018-02-28 15:07   ` [v5,05/12] " Hans de Goede
2018-02-28 15:07 ` [PATCH v5 06/12] usb: typec: tcpm: Use new Type-C switch/mux and usb-role-switch functions Hans de Goede
2018-02-28 15:07   ` [v5,06/12] " Hans de Goede
2018-02-28 15:07 ` [PATCH v5 07/12] xhci: Add option to get next extended capability in list by passing id = 0 Hans de Goede
2018-02-28 15:07   ` [v5,07/12] " Hans de Goede
2018-02-28 15:07 ` [PATCH v5 08/12] xhci: Add Intel extended cap / otg phy mux handling Hans de Goede
2018-02-28 15:07   ` [v5,08/12] " Hans de Goede
2018-02-28 15:15   ` [PATCH v5 08/12] " Heikki Krogerus
2018-02-28 15:15     ` [v5,08/12] " Heikki Krogerus
2018-02-28 15:42     ` [PATCH v5 08/12] " Hans de Goede
2018-02-28 15:42       ` [v5,08/12] " Hans de Goede
2018-03-01  7:23       ` [PATCH v5 08/12] " Heikki Krogerus
2018-03-01  7:23         ` [v5,08/12] " Heikki Krogerus
2018-02-28 15:07 ` [PATCH v5 09/12] usb: roles: Add Intel xHCI USB role switch driver Hans de Goede
2018-02-28 15:07   ` [v5,09/12] " Hans de Goede
2018-02-28 15:07 ` [PATCH v5 10/12] usb: typec: driver for Pericom PI3USB30532 Type-C cross switch Hans de Goede
2018-02-28 15:07   ` [v5,10/12] " Hans de Goede
2018-02-28 15:07 ` [PATCH v5 11/12] platform/x86: intel_cht_int33fe: Add device connections for the Type-C port Hans de Goede
2018-02-28 15:07   ` [v5,11/12] " Hans de Goede
2018-02-28 15:07 ` [PATCH v5 12/12] extcon: axp288: Set USB role where necessary Hans de Goede
2018-02-28 15:07   ` [v5,12/12] " Hans de Goede
2018-03-02  0:39   ` [PATCH v5 12/12] " Chanwoo Choi
2018-03-02  0:39     ` [v5,12/12] " Chanwoo Choi
2018-03-02  9:06     ` [PATCH v5 12/12] " Hans de Goede
2018-03-02  9:06       ` [v5,12/12] " Hans de Goede

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.