mbox series

[v5,0/7] typec: mux: Introduce support for multiple USB TypeC muxes

Message ID 20220422222351.1297276-1-bjorn.andersson@linaro.org
Headers show
Series typec: mux: Introduce support for multiple USB TypeC muxes | expand

Message

Bjorn Andersson April 22, 2022, 10:23 p.m. UTC
This series introduces a level of indirection between the controller's view of
a typec_mux/switch and the implementation and then expands that to support
multiple drivers.

This is needed in order to support devices such as the Qualcomm Snapdragon 888
HDK, which does muxing and orientation handling in the QMP (USB+DP) PHY and SBU
muxing in the external FSA4480 chip.

Bjorn Andersson (7):
  device property: Add helper to match multiple connections
  device property: Use multi-connection matchers for single case
  usb: typec: mux: Check dev_set_name() return value
  usb: typec: mux: Introduce indirection
  usb: typec: mux: Allow multiple mux_devs per mux
  dt-bindings: usb: Add binding for fcs,fsa4480
  usb: typec: mux: Add On Semi fsa4480 driver

 .../devicetree/bindings/usb/fcs,fsa4480.yaml  |  72 +++++
 drivers/base/property.c                       |  96 +++++--
 drivers/usb/typec/bus.c                       |   2 +-
 drivers/usb/typec/mux.c                       | 271 +++++++++++++-----
 drivers/usb/typec/mux.h                       |  12 +-
 drivers/usb/typec/mux/Kconfig                 |  10 +
 drivers/usb/typec/mux/Makefile                |   1 +
 drivers/usb/typec/mux/fsa4480.c               | 218 ++++++++++++++
 drivers/usb/typec/mux/intel_pmc_mux.c         |   8 +-
 drivers/usb/typec/mux/pi3usb30532.c           |   8 +-
 include/linux/property.h                      |   5 +
 include/linux/usb/typec_mux.h                 |  22 +-
 12 files changed, 614 insertions(+), 111 deletions(-)
 create mode 100644 Documentation/devicetree/bindings/usb/fcs,fsa4480.yaml
 create mode 100644 drivers/usb/typec/mux/fsa4480.c

Comments

Heikki Krogerus April 25, 2022, 8:50 a.m. UTC | #1
On Fri, Apr 22, 2022 at 03:23:51PM -0700, Bjorn Andersson wrote:
> The ON Semiconductor FSA4480 is a USB Type-C port multimedia switch with
> support for analog audio headsets. It allows sharing a common USB Type-C
> port to pass USB2.0 signal, analog audio, sideband use wires and analog
> microphone signal.
> 
> Due to lacking upstream audio support for testing, the audio muxing is
> left untouched, but implementation of muxing the SBU lines is provided
> as a pair of Type-C mux and switch devices. This provides the necessary
> support for enabling the DisplayPort altmode on devices with this
> circuit.
> 
> Signed-off-by: Bjorn Andersson <bjorn.andersson@linaro.org>

Reviewed-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>

> ---
> 
> Changes since v4:
> - Disable locking of the regmap
> - Use dev_fwnode() instead of explicit dereference
> 
>  drivers/usb/typec/mux/Kconfig   |  10 ++
>  drivers/usb/typec/mux/Makefile  |   1 +
>  drivers/usb/typec/mux/fsa4480.c | 218 ++++++++++++++++++++++++++++++++
>  3 files changed, 229 insertions(+)
>  create mode 100644 drivers/usb/typec/mux/fsa4480.c
> 
> diff --git a/drivers/usb/typec/mux/Kconfig b/drivers/usb/typec/mux/Kconfig
> index edead555835e..5eb2c17d72c1 100644
> --- a/drivers/usb/typec/mux/Kconfig
> +++ b/drivers/usb/typec/mux/Kconfig
> @@ -2,6 +2,16 @@
>  
>  menu "USB Type-C Multiplexer/DeMultiplexer Switch support"
>  
> +config TYPEC_MUX_FSA4480
> +	tristate "ON Semi FSA4480 Analog Audio Switch driver"
> +	depends on I2C
> +	select REGMAP_I2C
> +	help
> +	  Driver for the ON Semiconductor FSA4480 Analog Audio Switch, which
> +	  provides support for muxing analog audio and sideband signals on a
> +	  common USB Type-C connector.
> +	  If compiled as a module, the module will be named fsa4480.
> +
>  config TYPEC_MUX_PI3USB30532
>  	tristate "Pericom PI3USB30532 Type-C cross switch driver"
>  	depends on I2C
> diff --git a/drivers/usb/typec/mux/Makefile b/drivers/usb/typec/mux/Makefile
> index 280a6f553115..e52a56c16bfb 100644
> --- a/drivers/usb/typec/mux/Makefile
> +++ b/drivers/usb/typec/mux/Makefile
> @@ -1,4 +1,5 @@
>  # SPDX-License-Identifier: GPL-2.0
>  
> +obj-$(CONFIG_TYPEC_MUX_FSA4480)		+= fsa4480.o
>  obj-$(CONFIG_TYPEC_MUX_PI3USB30532)	+= pi3usb30532.o
>  obj-$(CONFIG_TYPEC_MUX_INTEL_PMC)	+= intel_pmc_mux.o
> diff --git a/drivers/usb/typec/mux/fsa4480.c b/drivers/usb/typec/mux/fsa4480.c
> new file mode 100644
> index 000000000000..6184f5367190
> --- /dev/null
> +++ b/drivers/usb/typec/mux/fsa4480.c
> @@ -0,0 +1,218 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (C) 2021-2022 Linaro Ltd.
> + * Copyright (C) 2018-2020 The Linux Foundation
> + */
> +
> +#include <linux/bits.h>
> +#include <linux/i2c.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/regmap.h>
> +#include <linux/usb/typec_dp.h>
> +#include <linux/usb/typec_mux.h>
> +
> +#define FSA4480_SWITCH_ENABLE	0x04
> +#define FSA4480_SWITCH_SELECT	0x05
> +#define FSA4480_SWITCH_STATUS1	0x07
> +#define FSA4480_SLOW_L		0x08
> +#define FSA4480_SLOW_R		0x09
> +#define FSA4480_SLOW_MIC	0x0a
> +#define FSA4480_SLOW_SENSE	0x0b
> +#define FSA4480_SLOW_GND	0x0c
> +#define FSA4480_DELAY_L_R	0x0d
> +#define FSA4480_DELAY_L_MIC	0x0e
> +#define FSA4480_DELAY_L_SENSE	0x0f
> +#define FSA4480_DELAY_L_AGND	0x10
> +#define FSA4480_RESET		0x1e
> +#define FSA4480_MAX_REGISTER	0x1f
> +
> +#define FSA4480_ENABLE_DEVICE	BIT(7)
> +#define FSA4480_ENABLE_SBU	GENMASK(6, 5)
> +#define FSA4480_ENABLE_USB	GENMASK(4, 3)
> +
> +#define FSA4480_SEL_SBU_REVERSE	GENMASK(6, 5)
> +#define FSA4480_SEL_USB		GENMASK(4, 3)
> +
> +struct fsa4480 {
> +	struct i2c_client *client;
> +
> +	/* used to serialize concurrent change requests */
> +	struct mutex lock;
> +
> +	struct typec_switch_dev *sw;
> +	struct typec_mux_dev *mux;
> +
> +	struct regmap *regmap;
> +
> +	u8 cur_enable;
> +	u8 cur_select;
> +};
> +
> +static const struct regmap_config fsa4480_regmap_config = {
> +	.reg_bits = 8,
> +	.val_bits = 8,
> +	.max_register = FSA4480_MAX_REGISTER,
> +	/* Accesses only done under fsa4480->lock */
> +	.disable_locking = true,
> +};
> +
> +static int fsa4480_switch_set(struct typec_switch_dev *sw,
> +			      enum typec_orientation orientation)
> +{
> +	struct fsa4480 *fsa = typec_switch_get_drvdata(sw);
> +	u8 new_sel;
> +
> +	mutex_lock(&fsa->lock);
> +	new_sel = FSA4480_SEL_USB;
> +	if (orientation == TYPEC_ORIENTATION_REVERSE)
> +		new_sel |= FSA4480_SEL_SBU_REVERSE;
> +
> +	if (new_sel == fsa->cur_select)
> +		goto out_unlock;
> +
> +	if (fsa->cur_enable & FSA4480_ENABLE_SBU) {
> +		/* Disable SBU output while re-configuring the switch */
> +		regmap_write(fsa->regmap, FSA4480_SWITCH_ENABLE,
> +			     fsa->cur_enable & ~FSA4480_ENABLE_SBU);
> +
> +		/* 35us to allow the SBU switch to turn off */
> +		usleep_range(35, 1000);
> +	}
> +
> +	regmap_write(fsa->regmap, FSA4480_SWITCH_SELECT, new_sel);
> +	fsa->cur_select = new_sel;
> +
> +	if (fsa->cur_enable & FSA4480_ENABLE_SBU) {
> +		regmap_write(fsa->regmap, FSA4480_SWITCH_ENABLE, fsa->cur_enable);
> +
> +		/* 15us to allow the SBU switch to turn on again */
> +		usleep_range(15, 1000);
> +	}
> +
> +out_unlock:
> +	mutex_unlock(&fsa->lock);
> +
> +	return 0;
> +}
> +
> +static int fsa4480_mux_set(struct typec_mux_dev *mux, struct typec_mux_state *state)
> +{
> +	struct fsa4480 *fsa = typec_mux_get_drvdata(mux);
> +	u8 new_enable;
> +
> +	mutex_lock(&fsa->lock);
> +
> +	new_enable = FSA4480_ENABLE_DEVICE | FSA4480_ENABLE_USB;
> +	if (state->mode >= TYPEC_DP_STATE_A)
> +		new_enable |= FSA4480_ENABLE_SBU;
> +
> +	if (new_enable == fsa->cur_enable)
> +		goto out_unlock;
> +
> +	regmap_write(fsa->regmap, FSA4480_SWITCH_ENABLE, new_enable);
> +	fsa->cur_enable = new_enable;
> +
> +	if (new_enable & FSA4480_ENABLE_SBU) {
> +		/* 15us to allow the SBU switch to turn off */
> +		usleep_range(15, 1000);
> +	}
> +
> +out_unlock:
> +	mutex_unlock(&fsa->lock);
> +
> +	return 0;
> +}
> +
> +static int fsa4480_probe(struct i2c_client *client)
> +{
> +	struct device *dev = &client->dev;
> +	struct typec_switch_desc sw_desc = { };
> +	struct typec_mux_desc mux_desc = { };
> +	struct fsa4480 *fsa;
> +
> +	fsa = devm_kzalloc(dev, sizeof(*fsa), GFP_KERNEL);
> +	if (!fsa)
> +		return -ENOMEM;
> +
> +	fsa->client = client;
> +	mutex_init(&fsa->lock);
> +
> +	fsa->regmap = devm_regmap_init_i2c(client, &fsa4480_regmap_config);
> +	if (IS_ERR(fsa->regmap))
> +		return dev_err_probe(dev, PTR_ERR(fsa->regmap), "failed to initialize regmap\n");
> +
> +	fsa->cur_enable = FSA4480_ENABLE_DEVICE | FSA4480_ENABLE_USB;
> +	fsa->cur_select = FSA4480_SEL_USB;
> +
> +	/* set default settings */
> +	regmap_write(fsa->regmap, FSA4480_SLOW_L, 0x00);
> +	regmap_write(fsa->regmap, FSA4480_SLOW_R, 0x00);
> +	regmap_write(fsa->regmap, FSA4480_SLOW_MIC, 0x00);
> +	regmap_write(fsa->regmap, FSA4480_SLOW_SENSE, 0x00);
> +	regmap_write(fsa->regmap, FSA4480_SLOW_GND, 0x00);
> +	regmap_write(fsa->regmap, FSA4480_DELAY_L_R, 0x00);
> +	regmap_write(fsa->regmap, FSA4480_DELAY_L_MIC, 0x00);
> +	regmap_write(fsa->regmap, FSA4480_DELAY_L_SENSE, 0x00);
> +	regmap_write(fsa->regmap, FSA4480_DELAY_L_AGND, 0x09);
> +	regmap_write(fsa->regmap, FSA4480_SWITCH_SELECT, fsa->cur_select);
> +	regmap_write(fsa->regmap, FSA4480_SWITCH_ENABLE, fsa->cur_enable);
> +
> +	sw_desc.drvdata = fsa;
> +	sw_desc.fwnode = dev_fwnode(dev);
> +	sw_desc.set = fsa4480_switch_set;
> +
> +	fsa->sw = typec_switch_register(dev, &sw_desc);
> +	if (IS_ERR(fsa->sw))
> +		return dev_err_probe(dev, PTR_ERR(fsa->sw), "failed to register typec switch\n");
> +
> +	mux_desc.drvdata = fsa;
> +	mux_desc.fwnode = dev_fwnode(dev);
> +	mux_desc.set = fsa4480_mux_set;
> +
> +	fsa->mux = typec_mux_register(dev, &mux_desc);
> +	if (IS_ERR(fsa->mux)) {
> +		typec_switch_unregister(fsa->sw);
> +		return dev_err_probe(dev, PTR_ERR(fsa->mux), "failed to register typec mux\n");
> +	}
> +
> +	i2c_set_clientdata(client, fsa);
> +	return 0;
> +}
> +
> +static int fsa4480_remove(struct i2c_client *client)
> +{
> +	struct fsa4480 *fsa = i2c_get_clientdata(client);
> +
> +	typec_mux_unregister(fsa->mux);
> +	typec_switch_unregister(fsa->sw);
> +
> +	return 0;
> +}
> +
> +static const struct i2c_device_id fsa4480_table[] = {
> +	{ "fsa4480" },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(i2c, fsa4480_table);
> +
> +static const struct of_device_id fsa4480_of_table[] = {
> +	{ .compatible = "fcs,fsa4480" },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(of, fsa4480_of_table);
> +
> +static struct i2c_driver fsa4480_driver = {
> +	.driver = {
> +		.name = "fsa4480",
> +		.of_match_table = fsa4480_of_table,
> +	},
> +	.probe_new	= fsa4480_probe,
> +	.remove		= fsa4480_remove,
> +	.id_table	= fsa4480_table,
> +};
> +module_i2c_driver(fsa4480_driver);
> +
> +MODULE_DESCRIPTION("ON Semiconductor FSA4480 driver");
> +MODULE_LICENSE("GPL v2");
> -- 
> 2.35.1
Andy Shevchenko April 26, 2022, 10:13 a.m. UTC | #2
On Sat, Apr 23, 2022 at 1:25 AM Bjorn Andersson
<bjorn.andersson@linaro.org> wrote:
>
> It's possible that dev_set_name() returns -ENOMEM, catch and handle this.

Reviewed-by: Andy Shevchenko <andy.shevchenko@gmail.com>

> Fixes: 3370db35193b ("usb: typec: Registering real device entries for the muxes")
> Reported-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
> Acked-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
> Signed-off-by: Bjorn Andersson <bjorn.andersson@linaro.org>
> ---
>
> Changes since v4:
> - Added Fixes tag
>
>  drivers/usb/typec/mux.c | 14 ++++++++++----
>  1 file changed, 10 insertions(+), 4 deletions(-)
>
> diff --git a/drivers/usb/typec/mux.c b/drivers/usb/typec/mux.c
> index c8340de0ed49..d2aaf294b649 100644
> --- a/drivers/usb/typec/mux.c
> +++ b/drivers/usb/typec/mux.c
> @@ -131,8 +131,11 @@ typec_switch_register(struct device *parent,
>         sw->dev.class = &typec_mux_class;
>         sw->dev.type = &typec_switch_dev_type;
>         sw->dev.driver_data = desc->drvdata;
> -       dev_set_name(&sw->dev, "%s-switch",
> -                    desc->name ? desc->name : dev_name(parent));
> +       ret = dev_set_name(&sw->dev, "%s-switch", desc->name ? desc->name : dev_name(parent));
> +       if (ret) {
> +               put_device(&sw->dev);
> +               return ERR_PTR(ret);
> +       }
>
>         ret = device_add(&sw->dev);
>         if (ret) {
> @@ -338,8 +341,11 @@ typec_mux_register(struct device *parent, const struct typec_mux_desc *desc)
>         mux->dev.class = &typec_mux_class;
>         mux->dev.type = &typec_mux_dev_type;
>         mux->dev.driver_data = desc->drvdata;
> -       dev_set_name(&mux->dev, "%s-mux",
> -                    desc->name ? desc->name : dev_name(parent));
> +       ret = dev_set_name(&mux->dev, "%s-mux", desc->name ? desc->name : dev_name(parent));
> +       if (ret) {
> +               put_device(&mux->dev);
> +               return ERR_PTR(ret);
> +       }
>
>         ret = device_add(&mux->dev);
>         if (ret) {
> --
> 2.35.1
>
Andy Shevchenko April 26, 2022, 10:15 a.m. UTC | #3
On Sat, Apr 23, 2022 at 1:25 AM Bjorn Andersson
<bjorn.andersson@linaro.org> wrote:
>
> In some cases multiple connections with the same connection id
> needs to be resolved from a fwnode graph.
>
> One such example is when separate hardware is used for performing muxing
> and/or orientation switching of the SuperSpeed and SBU lines in a USB
> Type-C connector. In this case the connector needs to belong to a graph
> with multiple matching remote endpoints, and the Type-C controller needs
> to be able to resolve them both.
>
> Add a new API that allows this kind of lookup.

LGTM now,
Reviewed-by: Andy Shevchenko <andy.shevchenko@gmail.com>
thanks!

> Signed-off-by: Bjorn Andersson <bjorn.andersson@linaro.org>
> ---
>
> Changes since v4:
> - Added "Add" to patch subject
> - Added "(Optional)" kernel-doc of fwnode_connection_find_matches()
>
>  drivers/base/property.c  | 109 +++++++++++++++++++++++++++++++++++++++
>  include/linux/property.h |   5 ++
>  2 files changed, 114 insertions(+)
>
> diff --git a/drivers/base/property.c b/drivers/base/property.c
> index 36401cfe432c..babab8cec7a0 100644
> --- a/drivers/base/property.c
> +++ b/drivers/base/property.c
> @@ -1201,6 +1201,40 @@ fwnode_graph_devcon_match(struct fwnode_handle *fwnode, const char *con_id,
>         return NULL;
>  }
>
> +static unsigned int fwnode_graph_devcon_matches(struct fwnode_handle *fwnode,
> +                                               const char *con_id, void *data,
> +                                               devcon_match_fn_t match,
> +                                               void **matches,
> +                                               unsigned int matches_len)
> +{
> +       struct fwnode_handle *node;
> +       struct fwnode_handle *ep;
> +       unsigned int count = 0;
> +       void *ret;
> +
> +       fwnode_graph_for_each_endpoint(fwnode, ep) {
> +               if (matches && count >= matches_len) {
> +                       fwnode_handle_put(ep);
> +                       break;
> +               }
> +
> +               node = fwnode_graph_get_remote_port_parent(ep);
> +               if (!fwnode_device_is_available(node)) {
> +                       fwnode_handle_put(node);
> +                       continue;
> +               }
> +
> +               ret = match(node, con_id, data);
> +               fwnode_handle_put(node);
> +               if (ret) {
> +                       if (matches)
> +                               matches[count] = ret;
> +                       count++;
> +               }
> +       }
> +       return count;
> +}
> +
>  static void *
>  fwnode_devcon_match(struct fwnode_handle *fwnode, const char *con_id,
>                     void *data, devcon_match_fn_t match)
> @@ -1223,6 +1257,37 @@ fwnode_devcon_match(struct fwnode_handle *fwnode, const char *con_id,
>         return NULL;
>  }
>
> +static unsigned int fwnode_devcon_matches(struct fwnode_handle *fwnode,
> +                                         const char *con_id, void *data,
> +                                         devcon_match_fn_t match,
> +                                         void **matches,
> +                                         unsigned int matches_len)
> +{
> +       struct fwnode_handle *node;
> +       unsigned int count = 0;
> +       unsigned int i;
> +       void *ret;
> +
> +       for (i = 0; ; i++) {
> +               if (matches && count >= matches_len)
> +                       break;
> +
> +               node = fwnode_find_reference(fwnode, con_id, i);
> +               if (IS_ERR(node))
> +                       break;
> +
> +               ret = match(node, NULL, data);
> +               fwnode_handle_put(node);
> +               if (ret) {
> +                       if (matches)
> +                               matches[count] = ret;
> +                       count++;
> +               }
> +       }
> +
> +       return count;
> +}
> +
>  /**
>   * fwnode_connection_find_match - Find connection from a device node
>   * @fwnode: Device node with the connection
> @@ -1250,3 +1315,47 @@ void *fwnode_connection_find_match(struct fwnode_handle *fwnode,
>         return fwnode_devcon_match(fwnode, con_id, data, match);
>  }
>  EXPORT_SYMBOL_GPL(fwnode_connection_find_match);
> +
> +/**
> + * fwnode_connection_find_matches - Find connections from a device node
> + * @fwnode: Device node with the connection
> + * @con_id: Identifier for the connection
> + * @data: Data for the match function
> + * @match: Function to check and convert the connection description
> + * @matches: (Optional) array of pointers to fill with matches
> + * @matches_len: Length of @matches
> + *
> + * Find up to @matches_len connections with unique identifier @con_id between
> + * @fwnode and other device nodes. @match will be used to convert the
> + * connection description to data the caller is expecting to be returned
> + * through the @matches array.
> + * If @matches is NULL @matches_len is ignored and the total number of resolved
> + * matches is returned.
> + *
> + * Return: Number of matches resolved, or negative errno.
> + */
> +int fwnode_connection_find_matches(struct fwnode_handle *fwnode,
> +                                  const char *con_id, void *data,
> +                                  devcon_match_fn_t match,
> +                                  void **matches, unsigned int matches_len)
> +{
> +       unsigned int count_graph;
> +       unsigned int count_ref;
> +
> +       if (!fwnode || !match)
> +               return -EINVAL;
> +
> +       count_graph = fwnode_graph_devcon_matches(fwnode, con_id, data, match,
> +                                                 matches, matches_len);
> +
> +       if (matches) {
> +               matches += count_graph;
> +               matches_len -= count_graph;
> +       }
> +
> +       count_ref = fwnode_devcon_matches(fwnode, con_id, data, match,
> +                                         matches, matches_len);
> +
> +       return count_graph + count_ref;
> +}
> +EXPORT_SYMBOL_GPL(fwnode_connection_find_matches);
> diff --git a/include/linux/property.h b/include/linux/property.h
> index fc24d45632eb..a5b429d623f6 100644
> --- a/include/linux/property.h
> +++ b/include/linux/property.h
> @@ -451,6 +451,11 @@ static inline void *device_connection_find_match(struct device *dev,
>         return fwnode_connection_find_match(dev_fwnode(dev), con_id, data, match);
>  }
>
> +int fwnode_connection_find_matches(struct fwnode_handle *fwnode,
> +                                  const char *con_id, void *data,
> +                                  devcon_match_fn_t match,
> +                                  void **matches, unsigned int matches_len);
> +
>  /* -------------------------------------------------------------------------- */
>  /* Software fwnode support - when HW description is incomplete or missing */
>
> --
> 2.35.1
>
Sakari Ailus April 27, 2022, 1:38 p.m. UTC | #4
Hi Björn,

On Fri, Apr 22, 2022 at 03:23:44PM -0700, Bjorn Andersson wrote:
> This series introduces a level of indirection between the controller's view of
> a typec_mux/switch and the implementation and then expands that to support
> multiple drivers.
> 
> This is needed in order to support devices such as the Qualcomm Snapdragon 888
> HDK, which does muxing and orientation handling in the QMP (USB+DP) PHY and SBU
> muxing in the external FSA4480 chip.

For patches 1 and 2:

Reviewed-by: Sakari Ailus <sakari.ailus@linux.intel.com>
Rafael J. Wysocki May 6, 2022, 6:21 p.m. UTC | #5
On Tue, Apr 26, 2022 at 12:15 PM Andy Shevchenko
<andy.shevchenko@gmail.com> wrote:
>
> On Sat, Apr 23, 2022 at 1:25 AM Bjorn Andersson
> <bjorn.andersson@linaro.org> wrote:
> >
> > In some cases multiple connections with the same connection id
> > needs to be resolved from a fwnode graph.
> >
> > One such example is when separate hardware is used for performing muxing
> > and/or orientation switching of the SuperSpeed and SBU lines in a USB
> > Type-C connector. In this case the connector needs to belong to a graph
> > with multiple matching remote endpoints, and the Type-C controller needs
> > to be able to resolve them both.
> >
> > Add a new API that allows this kind of lookup.
>
> LGTM now,
> Reviewed-by: Andy Shevchenko <andy.shevchenko@gmail.com>
> thanks!

For this and the next patch in the series:

Acked-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>

and please feel free to route the whole lot via USB.

Thanks!

> > Signed-off-by: Bjorn Andersson <bjorn.andersson@linaro.org>
> > ---
> >
> > Changes since v4:
> > - Added "Add" to patch subject
> > - Added "(Optional)" kernel-doc of fwnode_connection_find_matches()
> >
> >  drivers/base/property.c  | 109 +++++++++++++++++++++++++++++++++++++++
> >  include/linux/property.h |   5 ++
> >  2 files changed, 114 insertions(+)
> >
> > diff --git a/drivers/base/property.c b/drivers/base/property.c
> > index 36401cfe432c..babab8cec7a0 100644
> > --- a/drivers/base/property.c
> > +++ b/drivers/base/property.c
> > @@ -1201,6 +1201,40 @@ fwnode_graph_devcon_match(struct fwnode_handle *fwnode, const char *con_id,
> >         return NULL;
> >  }
> >
> > +static unsigned int fwnode_graph_devcon_matches(struct fwnode_handle *fwnode,
> > +                                               const char *con_id, void *data,
> > +                                               devcon_match_fn_t match,
> > +                                               void **matches,
> > +                                               unsigned int matches_len)
> > +{
> > +       struct fwnode_handle *node;
> > +       struct fwnode_handle *ep;
> > +       unsigned int count = 0;
> > +       void *ret;
> > +
> > +       fwnode_graph_for_each_endpoint(fwnode, ep) {
> > +               if (matches && count >= matches_len) {
> > +                       fwnode_handle_put(ep);
> > +                       break;
> > +               }
> > +
> > +               node = fwnode_graph_get_remote_port_parent(ep);
> > +               if (!fwnode_device_is_available(node)) {
> > +                       fwnode_handle_put(node);
> > +                       continue;
> > +               }
> > +
> > +               ret = match(node, con_id, data);
> > +               fwnode_handle_put(node);
> > +               if (ret) {
> > +                       if (matches)
> > +                               matches[count] = ret;
> > +                       count++;
> > +               }
> > +       }
> > +       return count;
> > +}
> > +
> >  static void *
> >  fwnode_devcon_match(struct fwnode_handle *fwnode, const char *con_id,
> >                     void *data, devcon_match_fn_t match)
> > @@ -1223,6 +1257,37 @@ fwnode_devcon_match(struct fwnode_handle *fwnode, const char *con_id,
> >         return NULL;
> >  }
> >
> > +static unsigned int fwnode_devcon_matches(struct fwnode_handle *fwnode,
> > +                                         const char *con_id, void *data,
> > +                                         devcon_match_fn_t match,
> > +                                         void **matches,
> > +                                         unsigned int matches_len)
> > +{
> > +       struct fwnode_handle *node;
> > +       unsigned int count = 0;
> > +       unsigned int i;
> > +       void *ret;
> > +
> > +       for (i = 0; ; i++) {
> > +               if (matches && count >= matches_len)
> > +                       break;
> > +
> > +               node = fwnode_find_reference(fwnode, con_id, i);
> > +               if (IS_ERR(node))
> > +                       break;
> > +
> > +               ret = match(node, NULL, data);
> > +               fwnode_handle_put(node);
> > +               if (ret) {
> > +                       if (matches)
> > +                               matches[count] = ret;
> > +                       count++;
> > +               }
> > +       }
> > +
> > +       return count;
> > +}
> > +
> >  /**
> >   * fwnode_connection_find_match - Find connection from a device node
> >   * @fwnode: Device node with the connection
> > @@ -1250,3 +1315,47 @@ void *fwnode_connection_find_match(struct fwnode_handle *fwnode,
> >         return fwnode_devcon_match(fwnode, con_id, data, match);
> >  }
> >  EXPORT_SYMBOL_GPL(fwnode_connection_find_match);
> > +
> > +/**
> > + * fwnode_connection_find_matches - Find connections from a device node
> > + * @fwnode: Device node with the connection
> > + * @con_id: Identifier for the connection
> > + * @data: Data for the match function
> > + * @match: Function to check and convert the connection description
> > + * @matches: (Optional) array of pointers to fill with matches
> > + * @matches_len: Length of @matches
> > + *
> > + * Find up to @matches_len connections with unique identifier @con_id between
> > + * @fwnode and other device nodes. @match will be used to convert the
> > + * connection description to data the caller is expecting to be returned
> > + * through the @matches array.
> > + * If @matches is NULL @matches_len is ignored and the total number of resolved
> > + * matches is returned.
> > + *
> > + * Return: Number of matches resolved, or negative errno.
> > + */
> > +int fwnode_connection_find_matches(struct fwnode_handle *fwnode,
> > +                                  const char *con_id, void *data,
> > +                                  devcon_match_fn_t match,
> > +                                  void **matches, unsigned int matches_len)
> > +{
> > +       unsigned int count_graph;
> > +       unsigned int count_ref;
> > +
> > +       if (!fwnode || !match)
> > +               return -EINVAL;
> > +
> > +       count_graph = fwnode_graph_devcon_matches(fwnode, con_id, data, match,
> > +                                                 matches, matches_len);
> > +
> > +       if (matches) {
> > +               matches += count_graph;
> > +               matches_len -= count_graph;
> > +       }
> > +
> > +       count_ref = fwnode_devcon_matches(fwnode, con_id, data, match,
> > +                                         matches, matches_len);
> > +
> > +       return count_graph + count_ref;
> > +}
> > +EXPORT_SYMBOL_GPL(fwnode_connection_find_matches);
> > diff --git a/include/linux/property.h b/include/linux/property.h
> > index fc24d45632eb..a5b429d623f6 100644
> > --- a/include/linux/property.h
> > +++ b/include/linux/property.h
> > @@ -451,6 +451,11 @@ static inline void *device_connection_find_match(struct device *dev,
> >         return fwnode_connection_find_match(dev_fwnode(dev), con_id, data, match);
> >  }
> >
> > +int fwnode_connection_find_matches(struct fwnode_handle *fwnode,
> > +                                  const char *con_id, void *data,
> > +                                  devcon_match_fn_t match,
> > +                                  void **matches, unsigned int matches_len);
> > +
> >  /* -------------------------------------------------------------------------- */
> >  /* Software fwnode support - when HW description is incomplete or missing */
> >
> > --
> > 2.35.1
> >
>
>
> --
> With Best Regards,
> Andy Shevchenko