diff mbox series

[2/2] usb: typec: add support for the nb7vpq904m Type-C Linear Redriver

Message ID 20230601-topic-sm8x50-upstream-redriver-v1-2-6ad21094ff6f@linaro.org (mailing list archive)
State Superseded
Headers show
Series typec: add support for the ON Semiconductor nb7vpq904m Type-C Linear Redriver | expand

Commit Message

Neil Armstrong June 1, 2023, 9:21 a.m. UTC
From: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>

Add support for the ON Semiconductor NB7VPQ904M Type-C USB SuperSpeed
and DisplayPort ALT Mode Linear Redriver chip found on some devices
with a Type-C port.

The redriver compensates ultra High-Speeed DisplayPort and USB
Super Speed signal integrity losses mainly due to PCB & transmission
cables.

The redriver doesn't support SuperSpeed lines swapping, but
can support Type-C SBU lines swapping.

Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
Signed-off-by: Neil Armstrong <neil.armstrong@linaro.org>
---
 drivers/usb/typec/mux/Kconfig      |   8 +
 drivers/usb/typec/mux/Makefile     |   1 +
 drivers/usb/typec/mux/nb7vpq904m.c | 526 +++++++++++++++++++++++++++++++++++++
 3 files changed, 535 insertions(+)

Comments

Dmitry Baryshkov June 1, 2023, 9:37 a.m. UTC | #1
On 01/06/2023 12:21, neil.armstrong@linaro.org wrote:
> From: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
> 
> Add support for the ON Semiconductor NB7VPQ904M Type-C USB SuperSpeed
> and DisplayPort ALT Mode Linear Redriver chip found on some devices
> with a Type-C port.
> 
> The redriver compensates ultra High-Speeed DisplayPort and USB
> Super Speed signal integrity losses mainly due to PCB & transmission
> cables.
> 
> The redriver doesn't support SuperSpeed lines swapping, but
> can support Type-C SBU lines swapping.
> 
> Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
> Signed-off-by: Neil Armstrong <neil.armstrong@linaro.org>
> ---
>   drivers/usb/typec/mux/Kconfig      |   8 +
>   drivers/usb/typec/mux/Makefile     |   1 +
>   drivers/usb/typec/mux/nb7vpq904m.c | 526 +++++++++++++++++++++++++++++++++++++
>   3 files changed, 535 insertions(+)
> 
> diff --git a/drivers/usb/typec/mux/Kconfig b/drivers/usb/typec/mux/Kconfig
> index c46fa4f9d3df..8c4d6b8fb75c 100644
> --- a/drivers/usb/typec/mux/Kconfig
> +++ b/drivers/usb/typec/mux/Kconfig
> @@ -35,4 +35,12 @@ config TYPEC_MUX_INTEL_PMC
>   	  control the USB role switch and also the multiplexer/demultiplexer
>   	  switches used with USB Type-C Alternate Modes.
>   
> +config TYPEC_MUX_NB7VPQ904M
> +	tristate "On Semiconductor NB7VPQ904M Type-C redriver driver"
> +	depends on I2C
> +	select REGMAP_I2C
> +	help
> +	  Say Y or M if your system has a On Semiconductor NB7VPQ904M Type-C
> +	  redriver 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
> index dda67e19b58b..76196096ef41 100644
> --- a/drivers/usb/typec/mux/Makefile
> +++ b/drivers/usb/typec/mux/Makefile
> @@ -4,3 +4,4 @@ obj-$(CONFIG_TYPEC_MUX_FSA4480)		+= fsa4480.o
>   obj-$(CONFIG_TYPEC_MUX_GPIO_SBU)	+= gpio-sbu-mux.o
>   obj-$(CONFIG_TYPEC_MUX_PI3USB30532)	+= pi3usb30532.o
>   obj-$(CONFIG_TYPEC_MUX_INTEL_PMC)	+= intel_pmc_mux.o
> +obj-$(CONFIG_TYPEC_MUX_NB7VPQ904M)	+= nb7vpq904m.o
> diff --git a/drivers/usb/typec/mux/nb7vpq904m.c b/drivers/usb/typec/mux/nb7vpq904m.c
> new file mode 100644
> index 000000000000..2f85ad9e417a
> --- /dev/null
> +++ b/drivers/usb/typec/mux/nb7vpq904m.c
> @@ -0,0 +1,526 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * OnSemi NB7VPQ904M Type-C driver
> + *
> + * Copyright (C) 2023 Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
> + */
> +#include <linux/i2c.h>
> +#include <linux/mutex.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/regmap.h>
> +#include <linux/of_graph.h>
> +#include <drm/drm_bridge.h>
> +#include <linux/usb/typec_dp.h>
> +#include <linux/usb/typec_mux.h>
> +#include <linux/gpio/consumer.h>
> +#include <linux/regulator/consumer.h>
> +
> +#define NB7_CHNA		0
> +#define NB7_CHNB		1
> +#define NB7_CHNC		2
> +#define NB7_CHND		3
> +#define NB7_IS_CHAN_AD(channel) (channel == NB7_CHNA || channel == NB7_CHND)
> +
> +#define GEN_DEV_SET_REG			0x00
> +
> +#define GEN_DEV_SET_CHIP_EN		BIT(0)
> +#define GEN_DEV_SET_CHNA_EN		BIT(4)
> +#define GEN_DEV_SET_CHNB_EN		BIT(5)
> +#define GEN_DEV_SET_CHNC_EN		BIT(6)
> +#define GEN_DEV_SET_CHND_EN		BIT(7)
> +
> +#define GEN_DEV_SET_OP_MODE_MASK	GENMASK(3, 1)
> +
> +#define GEN_DEV_SET_OP_MODE_DP_CC2	0
> +#define GEN_DEV_SET_OP_MODE_DP_CC1	1
> +#define GEN_DEV_SET_OP_MODE_DP_4LANE	2
> +#define GEN_DEV_SET_OP_MODE_USB		5
> +
> +#define EQ_SETTING_REG_BASE		0x01
> +#define EQ_SETTING_REG(n)		(EQ_SETTING_REG_BASE + (n) * 2)
> +#define EQ_SETTING_MASK			GENMASK(3, 1)
> +
> +#define OUTPUT_COMPRESSION_AND_POL_REG_BASE	0x02
> +#define OUTPUT_COMPRESSION_AND_POL_REG(n)	(OUTPUT_COMPRESSION_AND_POL_REG_BASE + (n) * 2)
> +#define OUTPUT_COMPRESSION_MASK		GENMASK(2, 1)
> +
> +#define FLAT_GAIN_REG_BASE		0x18
> +#define FLAT_GAIN_REG(n)		(FLAT_GAIN_REG_BASE + (n) * 2)
> +#define FLAT_GAIN_MASK			GENMASK(1, 0)
> +
> +#define LOSS_MATCH_REG_BASE		0x19
> +#define LOSS_MATCH_REG(n)		(LOSS_MATCH_REG_BASE + (n) * 2)
> +#define LOSS_MATCH_MASK			GENMASK(1, 0)
> +
> +#define AUX_CC_REG			0x09
> +
> +#define CHIP_VERSION_REG		0x17
> +
> +struct nb7vpq904m {
> +	struct i2c_client *client;
> +	struct gpio_desc *enable_gpio;
> +	struct regulator *vcc_supply;
> +	struct regmap *regmap;
> +	struct typec_switch_dev *sw;
> +	struct typec_mux_dev *mux;
> +
> +	bool swap_data_lanes;
> +	struct typec_switch *typec_switch;
> +
> +	struct drm_bridge bridge;
> +
> +	struct mutex lock; /* protect non-concurrent mux & switch */
> +
> +	enum typec_orientation orientation;
> +	unsigned long mode;
> +	unsigned int svid;
> +};
> +
> +static void nb7vpq904m_set_channel(struct nb7vpq904m *nb7, unsigned int channel, bool dp)
> +{
> +	u8 eq, out_comp, flat_gain, loss_match;
> +
> +	if (dp) {
> +		eq = NB7_IS_CHAN_AD(channel) ? 0x6 : 0x4;
> +		out_comp = 0x3;
> +		flat_gain = NB7_IS_CHAN_AD(channel) ? 0x2 : 0x1;
> +		loss_match = 0x3;
> +	} else {
> +		eq = 0x4;
> +		out_comp = 0x3;
> +		flat_gain = NB7_IS_CHAN_AD(channel) ? 0x3 : 0x1;
> +		loss_match = NB7_IS_CHAN_AD(channel) ? 0x1 : 0x3;
> +	}
> +
> +	regmap_update_bits(nb7->regmap, EQ_SETTING_REG(channel),
> +			   EQ_SETTING_MASK, FIELD_PREP(EQ_SETTING_MASK, eq));
> +	regmap_update_bits(nb7->regmap, OUTPUT_COMPRESSION_AND_POL_REG(channel),
> +			   OUTPUT_COMPRESSION_MASK, FIELD_PREP(OUTPUT_COMPRESSION_MASK, out_comp));
> +	regmap_update_bits(nb7->regmap, FLAT_GAIN_REG(channel),
> +			   FLAT_GAIN_MASK, FIELD_PREP(FLAT_GAIN_MASK, flat_gain));
> +	regmap_update_bits(nb7->regmap, LOSS_MATCH_REG(channel),
> +			   LOSS_MATCH_MASK, FIELD_PREP(LOSS_MATCH_MASK, loss_match));
> +}
> +
> +static int nb7vpq904m_set(struct nb7vpq904m *nb7)
> +{
> +	bool reverse = (nb7->orientation == TYPEC_ORIENTATION_REVERSE);
> +
> +	switch (nb7->mode) {
> +	case TYPEC_STATE_SAFE:
> +		regmap_write(nb7->regmap, GEN_DEV_SET_REG,
> +			     GEN_DEV_SET_CHIP_EN |
> +			     GEN_DEV_SET_CHNA_EN |
> +			     GEN_DEV_SET_CHNB_EN |
> +			     GEN_DEV_SET_CHNC_EN |
> +			     GEN_DEV_SET_CHND_EN |
> +			     FIELD_PREP(GEN_DEV_SET_OP_MODE_MASK,
> +					GEN_DEV_SET_OP_MODE_USB));
> +		nb7vpq904m_set_channel(nb7, NB7_CHNA, false);
> +		nb7vpq904m_set_channel(nb7, NB7_CHNB, false);
> +		nb7vpq904m_set_channel(nb7, NB7_CHNC, false);
> +		nb7vpq904m_set_channel(nb7, NB7_CHND, false);
> +		regmap_write(nb7->regmap, AUX_CC_REG, 0x2);
> +
> +		return 0;
> +
> +	case TYPEC_STATE_USB:
> +		/*
> +		 * Normal Orientation (CC1)
> +		 * A -> USB RX
> +		 * B -> USB TX
> +		 * C -> X
> +		 * D -> X
> +		 * Flipped Orientation (CC2)
> +		 * A -> X
> +		 * B -> X
> +		 * C -> USB TX
> +		 * D -> USB RX
> +		 *
> +		 * Reversed if data lanes are swapped
> +		 */
> +		if (reverse ^ nb7->swap_data_lanes) {
> +			regmap_write(nb7->regmap, GEN_DEV_SET_REG,
> +				     GEN_DEV_SET_CHIP_EN |
> +				     GEN_DEV_SET_CHNA_EN |
> +				     GEN_DEV_SET_CHNB_EN |
> +				     FIELD_PREP(GEN_DEV_SET_OP_MODE_MASK,
> +						GEN_DEV_SET_OP_MODE_USB));
> +			nb7vpq904m_set_channel(nb7, NB7_CHNA, false);
> +			nb7vpq904m_set_channel(nb7, NB7_CHNB, false);
> +		} else {
> +			regmap_write(nb7->regmap, GEN_DEV_SET_REG,
> +				     GEN_DEV_SET_CHIP_EN |
> +				     GEN_DEV_SET_CHNC_EN |
> +				     GEN_DEV_SET_CHND_EN |
> +				     FIELD_PREP(GEN_DEV_SET_OP_MODE_MASK,
> +						GEN_DEV_SET_OP_MODE_USB));
> +			nb7vpq904m_set_channel(nb7, NB7_CHNC, false);
> +			nb7vpq904m_set_channel(nb7, NB7_CHND, false);
> +		}
> +		regmap_write(nb7->regmap, AUX_CC_REG, 0x2);
> +
> +		return 0;
> +
> +	default:
> +		if (nb7->svid != USB_TYPEC_DP_SID)
> +			return -EINVAL;
> +
> +		break;
> +	}
> +
> +	/* DP Altmode Setup */
> +
> +	regmap_write(nb7->regmap, AUX_CC_REG, reverse ? 0x1 : 0x0);
> +
> +	switch (nb7->mode) {
> +	case TYPEC_DP_STATE_C:
> +	case TYPEC_DP_STATE_E:
> +		/*
> +		 * Normal Orientation (CC1)
> +		 * A -> DP3
> +		 * B -> DP2
> +		 * C -> DP1
> +		 * D -> DP0
> +		 * Flipped Orientation (CC2)
> +		 * A -> DP0
> +		 * B -> DP1
> +		 * C -> DP2
> +		 * D -> DP3
> +		 */
> +		regmap_write(nb7->regmap, GEN_DEV_SET_REG,
> +			     GEN_DEV_SET_CHIP_EN |
> +			     GEN_DEV_SET_CHNA_EN |
> +			     GEN_DEV_SET_CHNB_EN |
> +			     GEN_DEV_SET_CHNC_EN |
> +			     GEN_DEV_SET_CHND_EN |
> +			     FIELD_PREP(GEN_DEV_SET_OP_MODE_MASK,
> +					GEN_DEV_SET_OP_MODE_DP_4LANE));
> +		nb7vpq904m_set_channel(nb7, NB7_CHNA, true);
> +		nb7vpq904m_set_channel(nb7, NB7_CHNB, true);
> +		nb7vpq904m_set_channel(nb7, NB7_CHNC, true);
> +		nb7vpq904m_set_channel(nb7, NB7_CHND, true);
> +		break;
> +
> +	case TYPEC_DP_STATE_D:
> +	case TYPEC_DP_STATE_F:
> +		regmap_write(nb7->regmap, GEN_DEV_SET_REG,
> +			     GEN_DEV_SET_CHIP_EN |
> +			     GEN_DEV_SET_CHNA_EN |
> +			     GEN_DEV_SET_CHNB_EN |
> +			     GEN_DEV_SET_CHNC_EN |
> +			     GEN_DEV_SET_CHND_EN |
> +			     FIELD_PREP(GEN_DEV_SET_OP_MODE_MASK,
> +					reverse ^ nb7->swap_data_lanes ?
> +						GEN_DEV_SET_OP_MODE_DP_CC2
> +						: GEN_DEV_SET_OP_MODE_DP_CC1));
> +
> +		/*
> +		 * Normal Orientation (CC1)
> +		 * A -> USB RX
> +		 * B -> USB TX
> +		 * C -> DP1
> +		 * D -> DP0
> +		 * Flipped Orientation (CC2)
> +		 * A -> DP0
> +		 * B -> DP1
> +		 * C -> USB TX
> +		 * D -> USB RX
> +		 *
> +		 * Reversed if data lanes are swapped
> +		 */
> +		if (nb7->swap_data_lanes) {
> +			nb7vpq904m_set_channel(nb7, NB7_CHNA, !reverse);
> +			nb7vpq904m_set_channel(nb7, NB7_CHNB, !reverse);
> +			nb7vpq904m_set_channel(nb7, NB7_CHNC, reverse);
> +			nb7vpq904m_set_channel(nb7, NB7_CHND, reverse);
> +		} else {
> +			nb7vpq904m_set_channel(nb7, NB7_CHNA, reverse);
> +			nb7vpq904m_set_channel(nb7, NB7_CHNB, reverse);
> +			nb7vpq904m_set_channel(nb7, NB7_CHNC, !reverse);
> +			nb7vpq904m_set_channel(nb7, NB7_CHND, !reverse);
> +		}
> +		break;
> +
> +	default:
> +		return -ENOTSUPP;
> +	}
> +
> +	return 0;
> +}
> +
> +static int nb7vpq904m_sw_set(struct typec_switch_dev *sw,
> +			      enum typec_orientation orientation)
> +{
> +	struct nb7vpq904m *nb7 = typec_switch_get_drvdata(sw);
> +	int ret;
> +
> +	ret = typec_switch_set(nb7->typec_switch, orientation);
> +	if (ret)
> +		return ret;
> +
> +	mutex_lock(&nb7->lock);
> +
> +	if (nb7->orientation != orientation) {
> +		nb7->orientation = orientation;
> +
> +		ret = nb7vpq904m_set(nb7);
> +	}
> +
> +	mutex_unlock(&nb7->lock);
> +
> +	return ret;
> +}
> +
> +static int nb7vpq904m_mux_set(struct typec_mux_dev *mux, struct typec_mux_state *state)
> +{
> +	struct nb7vpq904m *nb7 = typec_mux_get_drvdata(mux);
> +	int ret = 0;
> +
> +	mutex_lock(&nb7->lock);
> +
> +	if (nb7->mode != state->mode) {
> +		nb7->mode = state->mode;
> +
> +		if (state->alt)
> +			nb7->svid = state->alt->svid;
> +		else
> +			nb7->svid = 0; // No SVID
> +
> +		ret = nb7vpq904m_set(nb7);
> +	}
> +
> +	mutex_unlock(&nb7->lock);
> +
> +	return ret;
> +}
> +
> +#if IS_ENABLED(CONFIG_DRM)
> +static int nb7vpq904m_bridge_attach(struct drm_bridge *bridge,
> +				    enum drm_bridge_attach_flags flags)
> +{
> +	struct nb7vpq904m *nb7 = container_of(bridge, struct nb7vpq904m, bridge);
> +	struct drm_bridge *next_bridge;
> +
> +	if (!(flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR))
> +		return -EINVAL;
> +
> +	next_bridge = devm_drm_of_get_bridge(&nb7->client->dev, nb7->client->dev.of_node, 0, 0);
> +	if (IS_ERR(next_bridge)) {
> +		dev_err(&nb7->client->dev, "failed to acquire drm_bridge: %pe\n", next_bridge);
> +		return PTR_ERR(next_bridge);
> +	}
> +
> +	return drm_bridge_attach(bridge->encoder, next_bridge, bridge,
> +				 DRM_BRIDGE_ATTACH_NO_CONNECTOR);
> +}
> +
> +static const struct drm_bridge_funcs nb7vpq904m_bridge_funcs = {
> +	.attach	= nb7vpq904m_bridge_attach,
> +};
> +
> +static int nb7vpq904m_register_bridge(struct nb7vpq904m *nb7)
> +{
> +	nb7->bridge.funcs = &nb7vpq904m_bridge_funcs;
> +	nb7->bridge.of_node = nb7->client->dev.of_node;
> +
> +	return devm_drm_bridge_add(&nb7->client->dev, &nb7->bridge);
> +}
> +#else
> +static int nb7vpq904m_register_bridge(struct nb7vpq904m *nb7)
> +{
> +	return 0;
> +}
> +#endif

This is what I feared of when we started mixing USB-C and drm bridges. A 
part of me still thinks that the proper solution should involve OOB 
notifications.

If we can expect a sizeable amount of such drivers, can we have a 
generic drm helper for such passthrough bridges?

> +
> +static const struct regmap_config nb7_regmap = {
> +	.max_register = 0x1f,
> +	.reg_bits = 8,
> +	.val_bits = 8,
> +};
> +
> +enum {
> +	NORMAL_LANE_MAPPING,
> +	INVERT_LANE_MAPPING,
> +};
> +
> +#define DATA_LANES_COUNT	4
> +
> +static const int supported_data_lane_mapping[][DATA_LANES_COUNT] = {
> +	[NORMAL_LANE_MAPPING] = { 0, 1, 2, 3 },
> +	[INVERT_LANE_MAPPING] = { 2, 3, 0, 1 },
> +};
> +
> +static int nb7vpq904m_parse_data_lanes_mapping(struct nb7vpq904m *nb7)
> +{
> +	struct device_node *ep;
> +	u32 data_lanes[4];
> +	int ret, i, j;
> +
> +	ep = of_graph_get_endpoint_by_regs(nb7->client->dev.of_node, 1, 0);
> +
> +	if (ep) {
> +		ret = of_property_count_u32_elems(ep, "data-lanes");
> +		if (ret == -EINVAL)
> +			/* Property isn't here, consider default mapping */
> +			goto out_done;
> +		if (ret < 0)
> +			goto out_error;
> +
> +		if (ret != DATA_LANES_COUNT) {
> +			dev_err(&nb7->client->dev, "expected 4 data lanes\n");
> +			ret = -EINVAL;
> +			goto out_error;
> +		}
> +
> +		ret = of_property_read_u32_array(ep, "data-lanes", data_lanes, DATA_LANES_COUNT);
> +		if (ret)
> +			goto out_error;
> +
> +		for (i = 0; i < ARRAY_SIZE(supported_data_lane_mapping); i++) {
> +			for (j = 0; j < DATA_LANES_COUNT; j++) {
> +				if (data_lanes[j] != supported_data_lane_mapping[i][j])
> +					break;
> +			}
> +
> +			if (j == DATA_LANES_COUNT)
> +				break;
> +		}
> +
> +		switch (i) {
> +		case NORMAL_LANE_MAPPING:
> +			break;
> +		case INVERT_LANE_MAPPING:
> +			nb7->swap_data_lanes = true;
> +			dev_info(&nb7->client->dev, "using inverted data lanes mapping\n");
> +			break;
> +		default:
> +			dev_err(&nb7->client->dev, "invalid data lanes mapping\n");
> +			ret = -EINVAL;
> +			goto out_error;
> +		}
> +	}
> +
> +out_done:
> +	ret = 0;
> +
> +out_error:
> +	of_node_put(ep);
> +
> +	return ret;
> +}
> +
> +static int nb7vpq904m_probe(struct i2c_client *client)
> +{
> +	struct device *dev = &client->dev;
> +	struct typec_switch_desc sw_desc = { };
> +	struct typec_mux_desc mux_desc = { };
> +	struct nb7vpq904m *nb7;
> +	int ret;
> +
> +	nb7 = devm_kzalloc(dev, sizeof(*nb7), GFP_KERNEL);
> +	if (!nb7)
> +		return -ENOMEM;
> +
> +	nb7->client = client;
> +
> +	nb7->regmap = devm_regmap_init_i2c(client, &nb7_regmap);
> +	if (IS_ERR(nb7->regmap)) {
> +		dev_err(&client->dev, "Failed to allocate register map\n");
> +		return PTR_ERR(nb7->regmap);
> +	}
> +
> +	nb7->mode = TYPEC_STATE_SAFE;
> +	nb7->orientation = TYPEC_ORIENTATION_NONE;
> +
> +	mutex_init(&nb7->lock);
> +
> +	nb7->enable_gpio = devm_gpiod_get_optional(dev, "enable", GPIOD_OUT_LOW);
> +	if (IS_ERR(nb7->enable_gpio))
> +		return dev_err_probe(dev, PTR_ERR(nb7->enable_gpio),
> +				     "unable to acquire enable gpio\n");
> +
> +	nb7->vcc_supply = devm_regulator_get_optional(dev, "vcc");
> +	if (IS_ERR(nb7->vcc_supply))
> +		return PTR_ERR(nb7->vcc_supply);
> +
> +	nb7->typec_switch = fwnode_typec_switch_get(dev->fwnode);
> +	if (IS_ERR(nb7->typec_switch))
> +		return dev_err_probe(dev, PTR_ERR(nb7->typec_switch),
> +				     "failed to acquire orientation-switch\n");
> +
> +	ret = nb7vpq904m_parse_data_lanes_mapping(nb7);
> +	if (ret)
> +		return ret;
> +
> +	ret = regulator_enable(nb7->vcc_supply);
> +	if (ret)
> +		dev_warn(dev, "Failed to enable vcc: %d\n", ret);
> +
> +	gpiod_set_value(nb7->enable_gpio, 1);
> +
> +	ret = nb7vpq904m_register_bridge(nb7);
> +	if (ret)
> +		return ret;
> +
> +	sw_desc.drvdata = nb7;
> +	sw_desc.fwnode = dev->fwnode;
> +	sw_desc.set = nb7vpq904m_sw_set;
> +
> +	nb7->sw = typec_switch_register(dev, &sw_desc);
> +	if (IS_ERR(nb7->sw))
> +		return dev_err_probe(dev, PTR_ERR(nb7->sw), "Error registering typec switch\n");
> +
> +	mux_desc.drvdata = nb7;
> +	mux_desc.fwnode = dev->fwnode;
> +	mux_desc.set = nb7vpq904m_mux_set;
> +
> +	nb7->mux = typec_mux_register(dev, &mux_desc);
> +	if (IS_ERR(nb7->mux)) {
> +		typec_switch_unregister(nb7->sw);
> +		return dev_err_probe(dev, PTR_ERR(nb7->mux), "Error registering typec mux\n");
> +	}
> +
> +	return 0;
> +}
> +
> +static void nb7vpq904m_remove(struct i2c_client *client)
> +{
> +	struct nb7vpq904m *nb7 = i2c_get_clientdata(client);
> +
> +	typec_mux_unregister(nb7->mux);
> +	typec_switch_unregister(nb7->sw);
> +
> +	gpiod_set_value(nb7->enable_gpio, 0);
> +
> +	regulator_disable(nb7->vcc_supply);
> +}
> +
> +static const struct i2c_device_id nb7vpq904m_table[] = {
> +	{ "nb7vpq904m" },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(i2c, nb7vpq904m_table);
> +
> +static const struct of_device_id nb7vpq904m_of_table[] = {
> +	{ .compatible = "onnn,nb7vpq904m" },
> +	{ }
> +};
> +MODULE_DEVICE_TABLE(of, nb7vpq904m_of_table);
> +
> +static struct i2c_driver nb7vpq904m_driver = {
> +	.driver = {
> +		.name = "nb7vpq904m",
> +		.of_match_table = nb7vpq904m_of_table,
> +	},
> +	.probe_new	= nb7vpq904m_probe,
> +	.remove		= nb7vpq904m_remove,
> +	.id_table	= nb7vpq904m_table,
> +};
> +
> +module_i2c_driver(nb7vpq904m_driver);
> +
> +MODULE_AUTHOR("Dmitry Baryshkov <dmitry.baryshkov@linaro.org>");
> +MODULE_DESCRIPTION("OnSemi NB7VPQ904M Type-C driver");
> +MODULE_LICENSE("GPL");
>
Neil Armstrong June 1, 2023, 9:56 a.m. UTC | #2
On 01/06/2023 11:37, Dmitry Baryshkov wrote:
> On 01/06/2023 12:21, neil.armstrong@linaro.org wrote:
>> From: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
>>
>> Add support for the ON Semiconductor NB7VPQ904M Type-C USB SuperSpeed
>> and DisplayPort ALT Mode Linear Redriver chip found on some devices
>> with a Type-C port.
>>
>> The redriver compensates ultra High-Speeed DisplayPort and USB
>> Super Speed signal integrity losses mainly due to PCB & transmission
>> cables.
>>
>> The redriver doesn't support SuperSpeed lines swapping, but
>> can support Type-C SBU lines swapping.
>>
>> Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
>> Signed-off-by: Neil Armstrong <neil.armstrong@linaro.org>
>> ---
>>   drivers/usb/typec/mux/Kconfig      |   8 +
>>   drivers/usb/typec/mux/Makefile     |   1 +
>>   drivers/usb/typec/mux/nb7vpq904m.c | 526 +++++++++++++++++++++++++++++++++++++
>>   3 files changed, 535 insertions(+)
>>
>> diff --git a/drivers/usb/typec/mux/Kconfig b/drivers/usb/typec/mux/Kconfig
>> index c46fa4f9d3df..8c4d6b8fb75c 100644
>> --- a/drivers/usb/typec/mux/Kconfig
>> +++ b/drivers/usb/typec/mux/Kconfig
>> @@ -35,4 +35,12 @@ config TYPEC_MUX_INTEL_PMC
>>         control the USB role switch and also the multiplexer/demultiplexer
>>         switches used with USB Type-C Alternate Modes.
>> +config TYPEC_MUX_NB7VPQ904M
>> +    tristate "On Semiconductor NB7VPQ904M Type-C redriver driver"
>> +    depends on I2C
>> +    select REGMAP_I2C
>> +    help
>> +      Say Y or M if your system has a On Semiconductor NB7VPQ904M Type-C
>> +      redriver 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
>> index dda67e19b58b..76196096ef41 100644
>> --- a/drivers/usb/typec/mux/Makefile
>> +++ b/drivers/usb/typec/mux/Makefile
>> @@ -4,3 +4,4 @@ obj-$(CONFIG_TYPEC_MUX_FSA4480)        += fsa4480.o
>>   obj-$(CONFIG_TYPEC_MUX_GPIO_SBU)    += gpio-sbu-mux.o
>>   obj-$(CONFIG_TYPEC_MUX_PI3USB30532)    += pi3usb30532.o
>>   obj-$(CONFIG_TYPEC_MUX_INTEL_PMC)    += intel_pmc_mux.o
>> +obj-$(CONFIG_TYPEC_MUX_NB7VPQ904M)    += nb7vpq904m.o
>> diff --git a/drivers/usb/typec/mux/nb7vpq904m.c b/drivers/usb/typec/mux/nb7vpq904m.c
>> new file mode 100644
>> index 000000000000..2f85ad9e417a
>> --- /dev/null
>> +++ b/drivers/usb/typec/mux/nb7vpq904m.c
>> @@ -0,0 +1,526 @@
>> +// SPDX-License-Identifier: GPL-2.0+
>> +/*
>> + * OnSemi NB7VPQ904M Type-C driver
>> + *
>> + * Copyright (C) 2023 Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
>> + */
>> +#include <linux/i2c.h>
>> +#include <linux/mutex.h>
>> +#include <linux/kernel.h>
>> +#include <linux/module.h>
>> +#include <linux/regmap.h>
>> +#include <linux/of_graph.h>
>> +#include <drm/drm_bridge.h>
>> +#include <linux/usb/typec_dp.h>
>> +#include <linux/usb/typec_mux.h>
>> +#include <linux/gpio/consumer.h>
>> +#include <linux/regulator/consumer.h>
>> +
>> +#define NB7_CHNA        0
>> +#define NB7_CHNB        1
>> +#define NB7_CHNC        2
>> +#define NB7_CHND        3
>> +#define NB7_IS_CHAN_AD(channel) (channel == NB7_CHNA || channel == NB7_CHND)
>> +
>> +#define GEN_DEV_SET_REG            0x00
>> +
>> +#define GEN_DEV_SET_CHIP_EN        BIT(0)
>> +#define GEN_DEV_SET_CHNA_EN        BIT(4)
>> +#define GEN_DEV_SET_CHNB_EN        BIT(5)
>> +#define GEN_DEV_SET_CHNC_EN        BIT(6)
>> +#define GEN_DEV_SET_CHND_EN        BIT(7)
>> +
>> +#define GEN_DEV_SET_OP_MODE_MASK    GENMASK(3, 1)
>> +
>> +#define GEN_DEV_SET_OP_MODE_DP_CC2    0
>> +#define GEN_DEV_SET_OP_MODE_DP_CC1    1
>> +#define GEN_DEV_SET_OP_MODE_DP_4LANE    2
>> +#define GEN_DEV_SET_OP_MODE_USB        5
>> +
>> +#define EQ_SETTING_REG_BASE        0x01
>> +#define EQ_SETTING_REG(n)        (EQ_SETTING_REG_BASE + (n) * 2)
>> +#define EQ_SETTING_MASK            GENMASK(3, 1)
>> +
>> +#define OUTPUT_COMPRESSION_AND_POL_REG_BASE    0x02
>> +#define OUTPUT_COMPRESSION_AND_POL_REG(n)    (OUTPUT_COMPRESSION_AND_POL_REG_BASE + (n) * 2)
>> +#define OUTPUT_COMPRESSION_MASK        GENMASK(2, 1)
>> +
>> +#define FLAT_GAIN_REG_BASE        0x18
>> +#define FLAT_GAIN_REG(n)        (FLAT_GAIN_REG_BASE + (n) * 2)
>> +#define FLAT_GAIN_MASK            GENMASK(1, 0)
>> +
>> +#define LOSS_MATCH_REG_BASE        0x19
>> +#define LOSS_MATCH_REG(n)        (LOSS_MATCH_REG_BASE + (n) * 2)
>> +#define LOSS_MATCH_MASK            GENMASK(1, 0)
>> +
>> +#define AUX_CC_REG            0x09
>> +
>> +#define CHIP_VERSION_REG        0x17
>> +
>> +struct nb7vpq904m {
>> +    struct i2c_client *client;
>> +    struct gpio_desc *enable_gpio;
>> +    struct regulator *vcc_supply;
>> +    struct regmap *regmap;
>> +    struct typec_switch_dev *sw;
>> +    struct typec_mux_dev *mux;
>> +
>> +    bool swap_data_lanes;
>> +    struct typec_switch *typec_switch;
>> +
>> +    struct drm_bridge bridge;
>> +
>> +    struct mutex lock; /* protect non-concurrent mux & switch */
>> +
>> +    enum typec_orientation orientation;
>> +    unsigned long mode;
>> +    unsigned int svid;
>> +};
>> +
>> +static void nb7vpq904m_set_channel(struct nb7vpq904m *nb7, unsigned int channel, bool dp)
>> +{
>> +    u8 eq, out_comp, flat_gain, loss_match;
>> +
>> +    if (dp) {
>> +        eq = NB7_IS_CHAN_AD(channel) ? 0x6 : 0x4;
>> +        out_comp = 0x3;
>> +        flat_gain = NB7_IS_CHAN_AD(channel) ? 0x2 : 0x1;
>> +        loss_match = 0x3;
>> +    } else {
>> +        eq = 0x4;
>> +        out_comp = 0x3;
>> +        flat_gain = NB7_IS_CHAN_AD(channel) ? 0x3 : 0x1;
>> +        loss_match = NB7_IS_CHAN_AD(channel) ? 0x1 : 0x3;
>> +    }
>> +
>> +    regmap_update_bits(nb7->regmap, EQ_SETTING_REG(channel),
>> +               EQ_SETTING_MASK, FIELD_PREP(EQ_SETTING_MASK, eq));
>> +    regmap_update_bits(nb7->regmap, OUTPUT_COMPRESSION_AND_POL_REG(channel),
>> +               OUTPUT_COMPRESSION_MASK, FIELD_PREP(OUTPUT_COMPRESSION_MASK, out_comp));
>> +    regmap_update_bits(nb7->regmap, FLAT_GAIN_REG(channel),
>> +               FLAT_GAIN_MASK, FIELD_PREP(FLAT_GAIN_MASK, flat_gain));
>> +    regmap_update_bits(nb7->regmap, LOSS_MATCH_REG(channel),
>> +               LOSS_MATCH_MASK, FIELD_PREP(LOSS_MATCH_MASK, loss_match));
>> +}
>> +
>> +static int nb7vpq904m_set(struct nb7vpq904m *nb7)
>> +{
>> +    bool reverse = (nb7->orientation == TYPEC_ORIENTATION_REVERSE);
>> +
>> +    switch (nb7->mode) {
>> +    case TYPEC_STATE_SAFE:
>> +        regmap_write(nb7->regmap, GEN_DEV_SET_REG,
>> +                 GEN_DEV_SET_CHIP_EN |
>> +                 GEN_DEV_SET_CHNA_EN |
>> +                 GEN_DEV_SET_CHNB_EN |
>> +                 GEN_DEV_SET_CHNC_EN |
>> +                 GEN_DEV_SET_CHND_EN |
>> +                 FIELD_PREP(GEN_DEV_SET_OP_MODE_MASK,
>> +                    GEN_DEV_SET_OP_MODE_USB));
>> +        nb7vpq904m_set_channel(nb7, NB7_CHNA, false);
>> +        nb7vpq904m_set_channel(nb7, NB7_CHNB, false);
>> +        nb7vpq904m_set_channel(nb7, NB7_CHNC, false);
>> +        nb7vpq904m_set_channel(nb7, NB7_CHND, false);
>> +        regmap_write(nb7->regmap, AUX_CC_REG, 0x2);
>> +
>> +        return 0;
>> +
>> +    case TYPEC_STATE_USB:
>> +        /*
>> +         * Normal Orientation (CC1)
>> +         * A -> USB RX
>> +         * B -> USB TX
>> +         * C -> X
>> +         * D -> X
>> +         * Flipped Orientation (CC2)
>> +         * A -> X
>> +         * B -> X
>> +         * C -> USB TX
>> +         * D -> USB RX
>> +         *
>> +         * Reversed if data lanes are swapped
>> +         */
>> +        if (reverse ^ nb7->swap_data_lanes) {
>> +            regmap_write(nb7->regmap, GEN_DEV_SET_REG,
>> +                     GEN_DEV_SET_CHIP_EN |
>> +                     GEN_DEV_SET_CHNA_EN |
>> +                     GEN_DEV_SET_CHNB_EN |
>> +                     FIELD_PREP(GEN_DEV_SET_OP_MODE_MASK,
>> +                        GEN_DEV_SET_OP_MODE_USB));
>> +            nb7vpq904m_set_channel(nb7, NB7_CHNA, false);
>> +            nb7vpq904m_set_channel(nb7, NB7_CHNB, false);
>> +        } else {
>> +            regmap_write(nb7->regmap, GEN_DEV_SET_REG,
>> +                     GEN_DEV_SET_CHIP_EN |
>> +                     GEN_DEV_SET_CHNC_EN |
>> +                     GEN_DEV_SET_CHND_EN |
>> +                     FIELD_PREP(GEN_DEV_SET_OP_MODE_MASK,
>> +                        GEN_DEV_SET_OP_MODE_USB));
>> +            nb7vpq904m_set_channel(nb7, NB7_CHNC, false);
>> +            nb7vpq904m_set_channel(nb7, NB7_CHND, false);
>> +        }
>> +        regmap_write(nb7->regmap, AUX_CC_REG, 0x2);
>> +
>> +        return 0;
>> +
>> +    default:
>> +        if (nb7->svid != USB_TYPEC_DP_SID)
>> +            return -EINVAL;
>> +
>> +        break;
>> +    }
>> +
>> +    /* DP Altmode Setup */
>> +
>> +    regmap_write(nb7->regmap, AUX_CC_REG, reverse ? 0x1 : 0x0);
>> +
>> +    switch (nb7->mode) {
>> +    case TYPEC_DP_STATE_C:
>> +    case TYPEC_DP_STATE_E:
>> +        /*
>> +         * Normal Orientation (CC1)
>> +         * A -> DP3
>> +         * B -> DP2
>> +         * C -> DP1
>> +         * D -> DP0
>> +         * Flipped Orientation (CC2)
>> +         * A -> DP0
>> +         * B -> DP1
>> +         * C -> DP2
>> +         * D -> DP3
>> +         */
>> +        regmap_write(nb7->regmap, GEN_DEV_SET_REG,
>> +                 GEN_DEV_SET_CHIP_EN |
>> +                 GEN_DEV_SET_CHNA_EN |
>> +                 GEN_DEV_SET_CHNB_EN |
>> +                 GEN_DEV_SET_CHNC_EN |
>> +                 GEN_DEV_SET_CHND_EN |
>> +                 FIELD_PREP(GEN_DEV_SET_OP_MODE_MASK,
>> +                    GEN_DEV_SET_OP_MODE_DP_4LANE));
>> +        nb7vpq904m_set_channel(nb7, NB7_CHNA, true);
>> +        nb7vpq904m_set_channel(nb7, NB7_CHNB, true);
>> +        nb7vpq904m_set_channel(nb7, NB7_CHNC, true);
>> +        nb7vpq904m_set_channel(nb7, NB7_CHND, true);
>> +        break;
>> +
>> +    case TYPEC_DP_STATE_D:
>> +    case TYPEC_DP_STATE_F:
>> +        regmap_write(nb7->regmap, GEN_DEV_SET_REG,
>> +                 GEN_DEV_SET_CHIP_EN |
>> +                 GEN_DEV_SET_CHNA_EN |
>> +                 GEN_DEV_SET_CHNB_EN |
>> +                 GEN_DEV_SET_CHNC_EN |
>> +                 GEN_DEV_SET_CHND_EN |
>> +                 FIELD_PREP(GEN_DEV_SET_OP_MODE_MASK,
>> +                    reverse ^ nb7->swap_data_lanes ?
>> +                        GEN_DEV_SET_OP_MODE_DP_CC2
>> +                        : GEN_DEV_SET_OP_MODE_DP_CC1));
>> +
>> +        /*
>> +         * Normal Orientation (CC1)
>> +         * A -> USB RX
>> +         * B -> USB TX
>> +         * C -> DP1
>> +         * D -> DP0
>> +         * Flipped Orientation (CC2)
>> +         * A -> DP0
>> +         * B -> DP1
>> +         * C -> USB TX
>> +         * D -> USB RX
>> +         *
>> +         * Reversed if data lanes are swapped
>> +         */
>> +        if (nb7->swap_data_lanes) {
>> +            nb7vpq904m_set_channel(nb7, NB7_CHNA, !reverse);
>> +            nb7vpq904m_set_channel(nb7, NB7_CHNB, !reverse);
>> +            nb7vpq904m_set_channel(nb7, NB7_CHNC, reverse);
>> +            nb7vpq904m_set_channel(nb7, NB7_CHND, reverse);
>> +        } else {
>> +            nb7vpq904m_set_channel(nb7, NB7_CHNA, reverse);
>> +            nb7vpq904m_set_channel(nb7, NB7_CHNB, reverse);
>> +            nb7vpq904m_set_channel(nb7, NB7_CHNC, !reverse);
>> +            nb7vpq904m_set_channel(nb7, NB7_CHND, !reverse);
>> +        }
>> +        break;
>> +
>> +    default:
>> +        return -ENOTSUPP;
>> +    }
>> +
>> +    return 0;
>> +}
>> +
>> +static int nb7vpq904m_sw_set(struct typec_switch_dev *sw,
>> +                  enum typec_orientation orientation)
>> +{
>> +    struct nb7vpq904m *nb7 = typec_switch_get_drvdata(sw);
>> +    int ret;
>> +
>> +    ret = typec_switch_set(nb7->typec_switch, orientation);
>> +    if (ret)
>> +        return ret;
>> +
>> +    mutex_lock(&nb7->lock);
>> +
>> +    if (nb7->orientation != orientation) {
>> +        nb7->orientation = orientation;
>> +
>> +        ret = nb7vpq904m_set(nb7);
>> +    }
>> +
>> +    mutex_unlock(&nb7->lock);
>> +
>> +    return ret;
>> +}
>> +
>> +static int nb7vpq904m_mux_set(struct typec_mux_dev *mux, struct typec_mux_state *state)
>> +{
>> +    struct nb7vpq904m *nb7 = typec_mux_get_drvdata(mux);
>> +    int ret = 0;
>> +
>> +    mutex_lock(&nb7->lock);
>> +
>> +    if (nb7->mode != state->mode) {
>> +        nb7->mode = state->mode;
>> +
>> +        if (state->alt)
>> +            nb7->svid = state->alt->svid;
>> +        else
>> +            nb7->svid = 0; // No SVID
>> +
>> +        ret = nb7vpq904m_set(nb7);
>> +    }
>> +
>> +    mutex_unlock(&nb7->lock);
>> +
>> +    return ret;
>> +}
>> +
>> +#if IS_ENABLED(CONFIG_DRM)
>> +static int nb7vpq904m_bridge_attach(struct drm_bridge *bridge,
>> +                    enum drm_bridge_attach_flags flags)
>> +{
>> +    struct nb7vpq904m *nb7 = container_of(bridge, struct nb7vpq904m, bridge);
>> +    struct drm_bridge *next_bridge;
>> +
>> +    if (!(flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR))
>> +        return -EINVAL;
>> +
>> +    next_bridge = devm_drm_of_get_bridge(&nb7->client->dev, nb7->client->dev.of_node, 0, 0);
>> +    if (IS_ERR(next_bridge)) {
>> +        dev_err(&nb7->client->dev, "failed to acquire drm_bridge: %pe\n", next_bridge);
>> +        return PTR_ERR(next_bridge);
>> +    }
>> +
>> +    return drm_bridge_attach(bridge->encoder, next_bridge, bridge,
>> +                 DRM_BRIDGE_ATTACH_NO_CONNECTOR);
>> +}
>> +
>> +static const struct drm_bridge_funcs nb7vpq904m_bridge_funcs = {
>> +    .attach    = nb7vpq904m_bridge_attach,
>> +};
>> +
>> +static int nb7vpq904m_register_bridge(struct nb7vpq904m *nb7)
>> +{
>> +    nb7->bridge.funcs = &nb7vpq904m_bridge_funcs;
>> +    nb7->bridge.of_node = nb7->client->dev.of_node;
>> +
>> +    return devm_drm_bridge_add(&nb7->client->dev, &nb7->bridge);
>> +}
>> +#else
>> +static int nb7vpq904m_register_bridge(struct nb7vpq904m *nb7)
>> +{
>> +    return 0;
>> +}
>> +#endif
> 
> This is what I feared of when we started mixing USB-C and drm bridges. A part of me still thinks that the proper solution should involve OOB notifications.
> 

OOB looks nice, but as I lookes, it would require a large amount of changes.

The advantage of bridge chaining is we can add specific ops for prepare/enable/disable/unprepare
for each elements of the USB-C data path.

> If we can expect a sizeable amount of such drivers, can we have a generic drm helper for such passthrough bridges?

Sure, we can add this to drm-bridge.c as a simple pass-throught bridge code

Neil

> 
>> +
>> +static const struct regmap_config nb7_regmap = {
>> +    .max_register = 0x1f,
>> +    .reg_bits = 8,
>> +    .val_bits = 8,
>> +};
>> +
>> +enum {
>> +    NORMAL_LANE_MAPPING,
>> +    INVERT_LANE_MAPPING,
>> +};
>> +
>> +#define DATA_LANES_COUNT    4
>> +
>> +static const int supported_data_lane_mapping[][DATA_LANES_COUNT] = {
>> +    [NORMAL_LANE_MAPPING] = { 0, 1, 2, 3 },
>> +    [INVERT_LANE_MAPPING] = { 2, 3, 0, 1 },
>> +};
>> +
>> +static int nb7vpq904m_parse_data_lanes_mapping(struct nb7vpq904m *nb7)
>> +{
>> +    struct device_node *ep;
>> +    u32 data_lanes[4];
>> +    int ret, i, j;
>> +
>> +    ep = of_graph_get_endpoint_by_regs(nb7->client->dev.of_node, 1, 0);
>> +
>> +    if (ep) {
>> +        ret = of_property_count_u32_elems(ep, "data-lanes");
>> +        if (ret == -EINVAL)
>> +            /* Property isn't here, consider default mapping */
>> +            goto out_done;
>> +        if (ret < 0)
>> +            goto out_error;
>> +
>> +        if (ret != DATA_LANES_COUNT) {
>> +            dev_err(&nb7->client->dev, "expected 4 data lanes\n");
>> +            ret = -EINVAL;
>> +            goto out_error;
>> +        }
>> +
>> +        ret = of_property_read_u32_array(ep, "data-lanes", data_lanes, DATA_LANES_COUNT);
>> +        if (ret)
>> +            goto out_error;
>> +
>> +        for (i = 0; i < ARRAY_SIZE(supported_data_lane_mapping); i++) {
>> +            for (j = 0; j < DATA_LANES_COUNT; j++) {
>> +                if (data_lanes[j] != supported_data_lane_mapping[i][j])
>> +                    break;
>> +            }
>> +
>> +            if (j == DATA_LANES_COUNT)
>> +                break;
>> +        }
>> +
>> +        switch (i) {
>> +        case NORMAL_LANE_MAPPING:
>> +            break;
>> +        case INVERT_LANE_MAPPING:
>> +            nb7->swap_data_lanes = true;
>> +            dev_info(&nb7->client->dev, "using inverted data lanes mapping\n");
>> +            break;
>> +        default:
>> +            dev_err(&nb7->client->dev, "invalid data lanes mapping\n");
>> +            ret = -EINVAL;
>> +            goto out_error;
>> +        }
>> +    }
>> +
>> +out_done:
>> +    ret = 0;
>> +
>> +out_error:
>> +    of_node_put(ep);
>> +
>> +    return ret;
>> +}
>> +
>> +static int nb7vpq904m_probe(struct i2c_client *client)
>> +{
>> +    struct device *dev = &client->dev;
>> +    struct typec_switch_desc sw_desc = { };
>> +    struct typec_mux_desc mux_desc = { };
>> +    struct nb7vpq904m *nb7;
>> +    int ret;
>> +
>> +    nb7 = devm_kzalloc(dev, sizeof(*nb7), GFP_KERNEL);
>> +    if (!nb7)
>> +        return -ENOMEM;
>> +
>> +    nb7->client = client;
>> +
>> +    nb7->regmap = devm_regmap_init_i2c(client, &nb7_regmap);
>> +    if (IS_ERR(nb7->regmap)) {
>> +        dev_err(&client->dev, "Failed to allocate register map\n");
>> +        return PTR_ERR(nb7->regmap);
>> +    }
>> +
>> +    nb7->mode = TYPEC_STATE_SAFE;
>> +    nb7->orientation = TYPEC_ORIENTATION_NONE;
>> +
>> +    mutex_init(&nb7->lock);
>> +
>> +    nb7->enable_gpio = devm_gpiod_get_optional(dev, "enable", GPIOD_OUT_LOW);
>> +    if (IS_ERR(nb7->enable_gpio))
>> +        return dev_err_probe(dev, PTR_ERR(nb7->enable_gpio),
>> +                     "unable to acquire enable gpio\n");
>> +
>> +    nb7->vcc_supply = devm_regulator_get_optional(dev, "vcc");
>> +    if (IS_ERR(nb7->vcc_supply))
>> +        return PTR_ERR(nb7->vcc_supply);
>> +
>> +    nb7->typec_switch = fwnode_typec_switch_get(dev->fwnode);
>> +    if (IS_ERR(nb7->typec_switch))
>> +        return dev_err_probe(dev, PTR_ERR(nb7->typec_switch),
>> +                     "failed to acquire orientation-switch\n");
>> +
>> +    ret = nb7vpq904m_parse_data_lanes_mapping(nb7);
>> +    if (ret)
>> +        return ret;
>> +
>> +    ret = regulator_enable(nb7->vcc_supply);
>> +    if (ret)
>> +        dev_warn(dev, "Failed to enable vcc: %d\n", ret);
>> +
>> +    gpiod_set_value(nb7->enable_gpio, 1);
>> +
>> +    ret = nb7vpq904m_register_bridge(nb7);
>> +    if (ret)
>> +        return ret;
>> +
>> +    sw_desc.drvdata = nb7;
>> +    sw_desc.fwnode = dev->fwnode;
>> +    sw_desc.set = nb7vpq904m_sw_set;
>> +
>> +    nb7->sw = typec_switch_register(dev, &sw_desc);
>> +    if (IS_ERR(nb7->sw))
>> +        return dev_err_probe(dev, PTR_ERR(nb7->sw), "Error registering typec switch\n");
>> +
>> +    mux_desc.drvdata = nb7;
>> +    mux_desc.fwnode = dev->fwnode;
>> +    mux_desc.set = nb7vpq904m_mux_set;
>> +
>> +    nb7->mux = typec_mux_register(dev, &mux_desc);
>> +    if (IS_ERR(nb7->mux)) {
>> +        typec_switch_unregister(nb7->sw);
>> +        return dev_err_probe(dev, PTR_ERR(nb7->mux), "Error registering typec mux\n");
>> +    }
>> +
>> +    return 0;
>> +}
>> +
>> +static void nb7vpq904m_remove(struct i2c_client *client)
>> +{
>> +    struct nb7vpq904m *nb7 = i2c_get_clientdata(client);
>> +
>> +    typec_mux_unregister(nb7->mux);
>> +    typec_switch_unregister(nb7->sw);
>> +
>> +    gpiod_set_value(nb7->enable_gpio, 0);
>> +
>> +    regulator_disable(nb7->vcc_supply);
>> +}
>> +
>> +static const struct i2c_device_id nb7vpq904m_table[] = {
>> +    { "nb7vpq904m" },
>> +    { }
>> +};
>> +MODULE_DEVICE_TABLE(i2c, nb7vpq904m_table);
>> +
>> +static const struct of_device_id nb7vpq904m_of_table[] = {
>> +    { .compatible = "onnn,nb7vpq904m" },
>> +    { }
>> +};
>> +MODULE_DEVICE_TABLE(of, nb7vpq904m_of_table);
>> +
>> +static struct i2c_driver nb7vpq904m_driver = {
>> +    .driver = {
>> +        .name = "nb7vpq904m",
>> +        .of_match_table = nb7vpq904m_of_table,
>> +    },
>> +    .probe_new    = nb7vpq904m_probe,
>> +    .remove        = nb7vpq904m_remove,
>> +    .id_table    = nb7vpq904m_table,
>> +};
>> +
>> +module_i2c_driver(nb7vpq904m_driver);
>> +
>> +MODULE_AUTHOR("Dmitry Baryshkov <dmitry.baryshkov@linaro.org>");
>> +MODULE_DESCRIPTION("OnSemi NB7VPQ904M Type-C driver");
>> +MODULE_LICENSE("GPL");
>>
>
Heikki Krogerus June 5, 2023, 8:24 a.m. UTC | #3
Hi Neil,

On Thu, Jun 01, 2023 at 11:21:13AM +0200, neil.armstrong@linaro.org wrote:
> From: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
> 
> Add support for the ON Semiconductor NB7VPQ904M Type-C USB SuperSpeed
> and DisplayPort ALT Mode Linear Redriver chip found on some devices
> with a Type-C port.
> 
> The redriver compensates ultra High-Speeed DisplayPort and USB
> Super Speed signal integrity losses mainly due to PCB & transmission
> cables.
> 
> The redriver doesn't support SuperSpeed lines swapping, but
> can support Type-C SBU lines swapping.
> 
> Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
> Signed-off-by: Neil Armstrong <neil.armstrong@linaro.org>
> ---
>  drivers/usb/typec/mux/Kconfig      |   8 +
>  drivers/usb/typec/mux/Makefile     |   1 +
>  drivers/usb/typec/mux/nb7vpq904m.c | 526 +++++++++++++++++++++++++++++++++++++
>  3 files changed, 535 insertions(+)

This looks good to me, but I think you should register a retimer
instead of a mode switch (the orientation switch is fine).

Retimers are handled just like the muxes, so this patch would not need
that many changes, but you would need to change the first patch too.
You would need to declare a "redriver-switch" instead of "mode-switch"
property in your DT (or perhaps make it just "redriver" instead of
"redriver-switch"?).

We just need to add a device type for redrivers to the retimer class -
check the attached diff. Something like that.

Let me know what you guys think.

thanks,
Neil Armstrong June 5, 2023, 8:31 a.m. UTC | #4
Hi,

On 05/06/2023 10:24, Heikki Krogerus wrote:
> Hi Neil,
> 
> On Thu, Jun 01, 2023 at 11:21:13AM +0200, neil.armstrong@linaro.org wrote:
>> From: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
>>
>> Add support for the ON Semiconductor NB7VPQ904M Type-C USB SuperSpeed
>> and DisplayPort ALT Mode Linear Redriver chip found on some devices
>> with a Type-C port.
>>
>> The redriver compensates ultra High-Speeed DisplayPort and USB
>> Super Speed signal integrity losses mainly due to PCB & transmission
>> cables.
>>
>> The redriver doesn't support SuperSpeed lines swapping, but
>> can support Type-C SBU lines swapping.
>>
>> Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
>> Signed-off-by: Neil Armstrong <neil.armstrong@linaro.org>
>> ---
>>   drivers/usb/typec/mux/Kconfig      |   8 +
>>   drivers/usb/typec/mux/Makefile     |   1 +
>>   drivers/usb/typec/mux/nb7vpq904m.c | 526 +++++++++++++++++++++++++++++++++++++
>>   3 files changed, 535 insertions(+)
> 
> This looks good to me, but I think you should register a retimer
> instead of a mode switch (the orientation switch is fine).

Indeed, I'm not familiar with namings but by looking around it seems that
redriver and retimers for USB-C applications are exactly the same.

> 
> Retimers are handled just like the muxes, so this patch would not need
> that many changes, but you would need to change the first patch too.
> You would need to declare a "redriver-switch" instead of "mode-switch"
> property in your DT (or perhaps make it just "redriver" instead of
> "redriver-switch"?).

So it would need some additional handling to also set the retimer state
along the mux, but I was wondering, why having a separate handling
while the retimer state struct is exactly the same as the mux ?

> 
> We just need to add a device type for redrivers to the retimer class -
> check the attached diff. Something like that.
> 
> Let me know what you guys think.

I think I'll switch to retimer, thanks for pointing it to me...

Thanks,
Neil

> 
> thanks,
>
diff mbox series

Patch

diff --git a/drivers/usb/typec/mux/Kconfig b/drivers/usb/typec/mux/Kconfig
index c46fa4f9d3df..8c4d6b8fb75c 100644
--- a/drivers/usb/typec/mux/Kconfig
+++ b/drivers/usb/typec/mux/Kconfig
@@ -35,4 +35,12 @@  config TYPEC_MUX_INTEL_PMC
 	  control the USB role switch and also the multiplexer/demultiplexer
 	  switches used with USB Type-C Alternate Modes.
 
+config TYPEC_MUX_NB7VPQ904M
+	tristate "On Semiconductor NB7VPQ904M Type-C redriver driver"
+	depends on I2C
+	select REGMAP_I2C
+	help
+	  Say Y or M if your system has a On Semiconductor NB7VPQ904M Type-C
+	  redriver 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
index dda67e19b58b..76196096ef41 100644
--- a/drivers/usb/typec/mux/Makefile
+++ b/drivers/usb/typec/mux/Makefile
@@ -4,3 +4,4 @@  obj-$(CONFIG_TYPEC_MUX_FSA4480)		+= fsa4480.o
 obj-$(CONFIG_TYPEC_MUX_GPIO_SBU)	+= gpio-sbu-mux.o
 obj-$(CONFIG_TYPEC_MUX_PI3USB30532)	+= pi3usb30532.o
 obj-$(CONFIG_TYPEC_MUX_INTEL_PMC)	+= intel_pmc_mux.o
+obj-$(CONFIG_TYPEC_MUX_NB7VPQ904M)	+= nb7vpq904m.o
diff --git a/drivers/usb/typec/mux/nb7vpq904m.c b/drivers/usb/typec/mux/nb7vpq904m.c
new file mode 100644
index 000000000000..2f85ad9e417a
--- /dev/null
+++ b/drivers/usb/typec/mux/nb7vpq904m.c
@@ -0,0 +1,526 @@ 
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * OnSemi NB7VPQ904M Type-C driver
+ *
+ * Copyright (C) 2023 Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
+ */
+#include <linux/i2c.h>
+#include <linux/mutex.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/of_graph.h>
+#include <drm/drm_bridge.h>
+#include <linux/usb/typec_dp.h>
+#include <linux/usb/typec_mux.h>
+#include <linux/gpio/consumer.h>
+#include <linux/regulator/consumer.h>
+
+#define NB7_CHNA		0
+#define NB7_CHNB		1
+#define NB7_CHNC		2
+#define NB7_CHND		3
+#define NB7_IS_CHAN_AD(channel) (channel == NB7_CHNA || channel == NB7_CHND)
+
+#define GEN_DEV_SET_REG			0x00
+
+#define GEN_DEV_SET_CHIP_EN		BIT(0)
+#define GEN_DEV_SET_CHNA_EN		BIT(4)
+#define GEN_DEV_SET_CHNB_EN		BIT(5)
+#define GEN_DEV_SET_CHNC_EN		BIT(6)
+#define GEN_DEV_SET_CHND_EN		BIT(7)
+
+#define GEN_DEV_SET_OP_MODE_MASK	GENMASK(3, 1)
+
+#define GEN_DEV_SET_OP_MODE_DP_CC2	0
+#define GEN_DEV_SET_OP_MODE_DP_CC1	1
+#define GEN_DEV_SET_OP_MODE_DP_4LANE	2
+#define GEN_DEV_SET_OP_MODE_USB		5
+
+#define EQ_SETTING_REG_BASE		0x01
+#define EQ_SETTING_REG(n)		(EQ_SETTING_REG_BASE + (n) * 2)
+#define EQ_SETTING_MASK			GENMASK(3, 1)
+
+#define OUTPUT_COMPRESSION_AND_POL_REG_BASE	0x02
+#define OUTPUT_COMPRESSION_AND_POL_REG(n)	(OUTPUT_COMPRESSION_AND_POL_REG_BASE + (n) * 2)
+#define OUTPUT_COMPRESSION_MASK		GENMASK(2, 1)
+
+#define FLAT_GAIN_REG_BASE		0x18
+#define FLAT_GAIN_REG(n)		(FLAT_GAIN_REG_BASE + (n) * 2)
+#define FLAT_GAIN_MASK			GENMASK(1, 0)
+
+#define LOSS_MATCH_REG_BASE		0x19
+#define LOSS_MATCH_REG(n)		(LOSS_MATCH_REG_BASE + (n) * 2)
+#define LOSS_MATCH_MASK			GENMASK(1, 0)
+
+#define AUX_CC_REG			0x09
+
+#define CHIP_VERSION_REG		0x17
+
+struct nb7vpq904m {
+	struct i2c_client *client;
+	struct gpio_desc *enable_gpio;
+	struct regulator *vcc_supply;
+	struct regmap *regmap;
+	struct typec_switch_dev *sw;
+	struct typec_mux_dev *mux;
+
+	bool swap_data_lanes;
+	struct typec_switch *typec_switch;
+
+	struct drm_bridge bridge;
+
+	struct mutex lock; /* protect non-concurrent mux & switch */
+
+	enum typec_orientation orientation;
+	unsigned long mode;
+	unsigned int svid;
+};
+
+static void nb7vpq904m_set_channel(struct nb7vpq904m *nb7, unsigned int channel, bool dp)
+{
+	u8 eq, out_comp, flat_gain, loss_match;
+
+	if (dp) {
+		eq = NB7_IS_CHAN_AD(channel) ? 0x6 : 0x4;
+		out_comp = 0x3;
+		flat_gain = NB7_IS_CHAN_AD(channel) ? 0x2 : 0x1;
+		loss_match = 0x3;
+	} else {
+		eq = 0x4;
+		out_comp = 0x3;
+		flat_gain = NB7_IS_CHAN_AD(channel) ? 0x3 : 0x1;
+		loss_match = NB7_IS_CHAN_AD(channel) ? 0x1 : 0x3;
+	}
+
+	regmap_update_bits(nb7->regmap, EQ_SETTING_REG(channel),
+			   EQ_SETTING_MASK, FIELD_PREP(EQ_SETTING_MASK, eq));
+	regmap_update_bits(nb7->regmap, OUTPUT_COMPRESSION_AND_POL_REG(channel),
+			   OUTPUT_COMPRESSION_MASK, FIELD_PREP(OUTPUT_COMPRESSION_MASK, out_comp));
+	regmap_update_bits(nb7->regmap, FLAT_GAIN_REG(channel),
+			   FLAT_GAIN_MASK, FIELD_PREP(FLAT_GAIN_MASK, flat_gain));
+	regmap_update_bits(nb7->regmap, LOSS_MATCH_REG(channel),
+			   LOSS_MATCH_MASK, FIELD_PREP(LOSS_MATCH_MASK, loss_match));
+}
+
+static int nb7vpq904m_set(struct nb7vpq904m *nb7)
+{
+	bool reverse = (nb7->orientation == TYPEC_ORIENTATION_REVERSE);
+
+	switch (nb7->mode) {
+	case TYPEC_STATE_SAFE:
+		regmap_write(nb7->regmap, GEN_DEV_SET_REG,
+			     GEN_DEV_SET_CHIP_EN |
+			     GEN_DEV_SET_CHNA_EN |
+			     GEN_DEV_SET_CHNB_EN |
+			     GEN_DEV_SET_CHNC_EN |
+			     GEN_DEV_SET_CHND_EN |
+			     FIELD_PREP(GEN_DEV_SET_OP_MODE_MASK,
+					GEN_DEV_SET_OP_MODE_USB));
+		nb7vpq904m_set_channel(nb7, NB7_CHNA, false);
+		nb7vpq904m_set_channel(nb7, NB7_CHNB, false);
+		nb7vpq904m_set_channel(nb7, NB7_CHNC, false);
+		nb7vpq904m_set_channel(nb7, NB7_CHND, false);
+		regmap_write(nb7->regmap, AUX_CC_REG, 0x2);
+
+		return 0;
+
+	case TYPEC_STATE_USB:
+		/*
+		 * Normal Orientation (CC1)
+		 * A -> USB RX
+		 * B -> USB TX
+		 * C -> X
+		 * D -> X
+		 * Flipped Orientation (CC2)
+		 * A -> X
+		 * B -> X
+		 * C -> USB TX
+		 * D -> USB RX
+		 *
+		 * Reversed if data lanes are swapped
+		 */
+		if (reverse ^ nb7->swap_data_lanes) {
+			regmap_write(nb7->regmap, GEN_DEV_SET_REG,
+				     GEN_DEV_SET_CHIP_EN |
+				     GEN_DEV_SET_CHNA_EN |
+				     GEN_DEV_SET_CHNB_EN |
+				     FIELD_PREP(GEN_DEV_SET_OP_MODE_MASK,
+						GEN_DEV_SET_OP_MODE_USB));
+			nb7vpq904m_set_channel(nb7, NB7_CHNA, false);
+			nb7vpq904m_set_channel(nb7, NB7_CHNB, false);
+		} else {
+			regmap_write(nb7->regmap, GEN_DEV_SET_REG,
+				     GEN_DEV_SET_CHIP_EN |
+				     GEN_DEV_SET_CHNC_EN |
+				     GEN_DEV_SET_CHND_EN |
+				     FIELD_PREP(GEN_DEV_SET_OP_MODE_MASK,
+						GEN_DEV_SET_OP_MODE_USB));
+			nb7vpq904m_set_channel(nb7, NB7_CHNC, false);
+			nb7vpq904m_set_channel(nb7, NB7_CHND, false);
+		}
+		regmap_write(nb7->regmap, AUX_CC_REG, 0x2);
+
+		return 0;
+
+	default:
+		if (nb7->svid != USB_TYPEC_DP_SID)
+			return -EINVAL;
+
+		break;
+	}
+
+	/* DP Altmode Setup */
+
+	regmap_write(nb7->regmap, AUX_CC_REG, reverse ? 0x1 : 0x0);
+
+	switch (nb7->mode) {
+	case TYPEC_DP_STATE_C:
+	case TYPEC_DP_STATE_E:
+		/*
+		 * Normal Orientation (CC1)
+		 * A -> DP3
+		 * B -> DP2
+		 * C -> DP1
+		 * D -> DP0
+		 * Flipped Orientation (CC2)
+		 * A -> DP0
+		 * B -> DP1
+		 * C -> DP2
+		 * D -> DP3
+		 */
+		regmap_write(nb7->regmap, GEN_DEV_SET_REG,
+			     GEN_DEV_SET_CHIP_EN |
+			     GEN_DEV_SET_CHNA_EN |
+			     GEN_DEV_SET_CHNB_EN |
+			     GEN_DEV_SET_CHNC_EN |
+			     GEN_DEV_SET_CHND_EN |
+			     FIELD_PREP(GEN_DEV_SET_OP_MODE_MASK,
+					GEN_DEV_SET_OP_MODE_DP_4LANE));
+		nb7vpq904m_set_channel(nb7, NB7_CHNA, true);
+		nb7vpq904m_set_channel(nb7, NB7_CHNB, true);
+		nb7vpq904m_set_channel(nb7, NB7_CHNC, true);
+		nb7vpq904m_set_channel(nb7, NB7_CHND, true);
+		break;
+
+	case TYPEC_DP_STATE_D:
+	case TYPEC_DP_STATE_F:
+		regmap_write(nb7->regmap, GEN_DEV_SET_REG,
+			     GEN_DEV_SET_CHIP_EN |
+			     GEN_DEV_SET_CHNA_EN |
+			     GEN_DEV_SET_CHNB_EN |
+			     GEN_DEV_SET_CHNC_EN |
+			     GEN_DEV_SET_CHND_EN |
+			     FIELD_PREP(GEN_DEV_SET_OP_MODE_MASK,
+					reverse ^ nb7->swap_data_lanes ?
+						GEN_DEV_SET_OP_MODE_DP_CC2
+						: GEN_DEV_SET_OP_MODE_DP_CC1));
+
+		/*
+		 * Normal Orientation (CC1)
+		 * A -> USB RX
+		 * B -> USB TX
+		 * C -> DP1
+		 * D -> DP0
+		 * Flipped Orientation (CC2)
+		 * A -> DP0
+		 * B -> DP1
+		 * C -> USB TX
+		 * D -> USB RX
+		 *
+		 * Reversed if data lanes are swapped
+		 */
+		if (nb7->swap_data_lanes) {
+			nb7vpq904m_set_channel(nb7, NB7_CHNA, !reverse);
+			nb7vpq904m_set_channel(nb7, NB7_CHNB, !reverse);
+			nb7vpq904m_set_channel(nb7, NB7_CHNC, reverse);
+			nb7vpq904m_set_channel(nb7, NB7_CHND, reverse);
+		} else {
+			nb7vpq904m_set_channel(nb7, NB7_CHNA, reverse);
+			nb7vpq904m_set_channel(nb7, NB7_CHNB, reverse);
+			nb7vpq904m_set_channel(nb7, NB7_CHNC, !reverse);
+			nb7vpq904m_set_channel(nb7, NB7_CHND, !reverse);
+		}
+		break;
+
+	default:
+		return -ENOTSUPP;
+	}
+
+	return 0;
+}
+
+static int nb7vpq904m_sw_set(struct typec_switch_dev *sw,
+			      enum typec_orientation orientation)
+{
+	struct nb7vpq904m *nb7 = typec_switch_get_drvdata(sw);
+	int ret;
+
+	ret = typec_switch_set(nb7->typec_switch, orientation);
+	if (ret)
+		return ret;
+
+	mutex_lock(&nb7->lock);
+
+	if (nb7->orientation != orientation) {
+		nb7->orientation = orientation;
+
+		ret = nb7vpq904m_set(nb7);
+	}
+
+	mutex_unlock(&nb7->lock);
+
+	return ret;
+}
+
+static int nb7vpq904m_mux_set(struct typec_mux_dev *mux, struct typec_mux_state *state)
+{
+	struct nb7vpq904m *nb7 = typec_mux_get_drvdata(mux);
+	int ret = 0;
+
+	mutex_lock(&nb7->lock);
+
+	if (nb7->mode != state->mode) {
+		nb7->mode = state->mode;
+
+		if (state->alt)
+			nb7->svid = state->alt->svid;
+		else
+			nb7->svid = 0; // No SVID
+
+		ret = nb7vpq904m_set(nb7);
+	}
+
+	mutex_unlock(&nb7->lock);
+
+	return ret;
+}
+
+#if IS_ENABLED(CONFIG_DRM)
+static int nb7vpq904m_bridge_attach(struct drm_bridge *bridge,
+				    enum drm_bridge_attach_flags flags)
+{
+	struct nb7vpq904m *nb7 = container_of(bridge, struct nb7vpq904m, bridge);
+	struct drm_bridge *next_bridge;
+
+	if (!(flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR))
+		return -EINVAL;
+
+	next_bridge = devm_drm_of_get_bridge(&nb7->client->dev, nb7->client->dev.of_node, 0, 0);
+	if (IS_ERR(next_bridge)) {
+		dev_err(&nb7->client->dev, "failed to acquire drm_bridge: %pe\n", next_bridge);
+		return PTR_ERR(next_bridge);
+	}
+
+	return drm_bridge_attach(bridge->encoder, next_bridge, bridge,
+				 DRM_BRIDGE_ATTACH_NO_CONNECTOR);
+}
+
+static const struct drm_bridge_funcs nb7vpq904m_bridge_funcs = {
+	.attach	= nb7vpq904m_bridge_attach,
+};
+
+static int nb7vpq904m_register_bridge(struct nb7vpq904m *nb7)
+{
+	nb7->bridge.funcs = &nb7vpq904m_bridge_funcs;
+	nb7->bridge.of_node = nb7->client->dev.of_node;
+
+	return devm_drm_bridge_add(&nb7->client->dev, &nb7->bridge);
+}
+#else
+static int nb7vpq904m_register_bridge(struct nb7vpq904m *nb7)
+{
+	return 0;
+}
+#endif
+
+static const struct regmap_config nb7_regmap = {
+	.max_register = 0x1f,
+	.reg_bits = 8,
+	.val_bits = 8,
+};
+
+enum {
+	NORMAL_LANE_MAPPING,
+	INVERT_LANE_MAPPING,
+};
+
+#define DATA_LANES_COUNT	4
+
+static const int supported_data_lane_mapping[][DATA_LANES_COUNT] = {
+	[NORMAL_LANE_MAPPING] = { 0, 1, 2, 3 },
+	[INVERT_LANE_MAPPING] = { 2, 3, 0, 1 },
+};
+
+static int nb7vpq904m_parse_data_lanes_mapping(struct nb7vpq904m *nb7)
+{
+	struct device_node *ep;
+	u32 data_lanes[4];
+	int ret, i, j;
+
+	ep = of_graph_get_endpoint_by_regs(nb7->client->dev.of_node, 1, 0);
+
+	if (ep) {
+		ret = of_property_count_u32_elems(ep, "data-lanes");
+		if (ret == -EINVAL)
+			/* Property isn't here, consider default mapping */
+			goto out_done;
+		if (ret < 0)
+			goto out_error;
+
+		if (ret != DATA_LANES_COUNT) {
+			dev_err(&nb7->client->dev, "expected 4 data lanes\n");
+			ret = -EINVAL;
+			goto out_error;
+		}
+
+		ret = of_property_read_u32_array(ep, "data-lanes", data_lanes, DATA_LANES_COUNT);
+		if (ret)
+			goto out_error;
+
+		for (i = 0; i < ARRAY_SIZE(supported_data_lane_mapping); i++) {
+			for (j = 0; j < DATA_LANES_COUNT; j++) {
+				if (data_lanes[j] != supported_data_lane_mapping[i][j])
+					break;
+			}
+
+			if (j == DATA_LANES_COUNT)
+				break;
+		}
+
+		switch (i) {
+		case NORMAL_LANE_MAPPING:
+			break;
+		case INVERT_LANE_MAPPING:
+			nb7->swap_data_lanes = true;
+			dev_info(&nb7->client->dev, "using inverted data lanes mapping\n");
+			break;
+		default:
+			dev_err(&nb7->client->dev, "invalid data lanes mapping\n");
+			ret = -EINVAL;
+			goto out_error;
+		}
+	}
+
+out_done:
+	ret = 0;
+
+out_error:
+	of_node_put(ep);
+
+	return ret;
+}
+
+static int nb7vpq904m_probe(struct i2c_client *client)
+{
+	struct device *dev = &client->dev;
+	struct typec_switch_desc sw_desc = { };
+	struct typec_mux_desc mux_desc = { };
+	struct nb7vpq904m *nb7;
+	int ret;
+
+	nb7 = devm_kzalloc(dev, sizeof(*nb7), GFP_KERNEL);
+	if (!nb7)
+		return -ENOMEM;
+
+	nb7->client = client;
+
+	nb7->regmap = devm_regmap_init_i2c(client, &nb7_regmap);
+	if (IS_ERR(nb7->regmap)) {
+		dev_err(&client->dev, "Failed to allocate register map\n");
+		return PTR_ERR(nb7->regmap);
+	}
+
+	nb7->mode = TYPEC_STATE_SAFE;
+	nb7->orientation = TYPEC_ORIENTATION_NONE;
+
+	mutex_init(&nb7->lock);
+
+	nb7->enable_gpio = devm_gpiod_get_optional(dev, "enable", GPIOD_OUT_LOW);
+	if (IS_ERR(nb7->enable_gpio))
+		return dev_err_probe(dev, PTR_ERR(nb7->enable_gpio),
+				     "unable to acquire enable gpio\n");
+
+	nb7->vcc_supply = devm_regulator_get_optional(dev, "vcc");
+	if (IS_ERR(nb7->vcc_supply))
+		return PTR_ERR(nb7->vcc_supply);
+
+	nb7->typec_switch = fwnode_typec_switch_get(dev->fwnode);
+	if (IS_ERR(nb7->typec_switch))
+		return dev_err_probe(dev, PTR_ERR(nb7->typec_switch),
+				     "failed to acquire orientation-switch\n");
+
+	ret = nb7vpq904m_parse_data_lanes_mapping(nb7);
+	if (ret)
+		return ret;
+
+	ret = regulator_enable(nb7->vcc_supply);
+	if (ret)
+		dev_warn(dev, "Failed to enable vcc: %d\n", ret);
+
+	gpiod_set_value(nb7->enable_gpio, 1);
+
+	ret = nb7vpq904m_register_bridge(nb7);
+	if (ret)
+		return ret;
+
+	sw_desc.drvdata = nb7;
+	sw_desc.fwnode = dev->fwnode;
+	sw_desc.set = nb7vpq904m_sw_set;
+
+	nb7->sw = typec_switch_register(dev, &sw_desc);
+	if (IS_ERR(nb7->sw))
+		return dev_err_probe(dev, PTR_ERR(nb7->sw), "Error registering typec switch\n");
+
+	mux_desc.drvdata = nb7;
+	mux_desc.fwnode = dev->fwnode;
+	mux_desc.set = nb7vpq904m_mux_set;
+
+	nb7->mux = typec_mux_register(dev, &mux_desc);
+	if (IS_ERR(nb7->mux)) {
+		typec_switch_unregister(nb7->sw);
+		return dev_err_probe(dev, PTR_ERR(nb7->mux), "Error registering typec mux\n");
+	}
+
+	return 0;
+}
+
+static void nb7vpq904m_remove(struct i2c_client *client)
+{
+	struct nb7vpq904m *nb7 = i2c_get_clientdata(client);
+
+	typec_mux_unregister(nb7->mux);
+	typec_switch_unregister(nb7->sw);
+
+	gpiod_set_value(nb7->enable_gpio, 0);
+
+	regulator_disable(nb7->vcc_supply);
+}
+
+static const struct i2c_device_id nb7vpq904m_table[] = {
+	{ "nb7vpq904m" },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, nb7vpq904m_table);
+
+static const struct of_device_id nb7vpq904m_of_table[] = {
+	{ .compatible = "onnn,nb7vpq904m" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, nb7vpq904m_of_table);
+
+static struct i2c_driver nb7vpq904m_driver = {
+	.driver = {
+		.name = "nb7vpq904m",
+		.of_match_table = nb7vpq904m_of_table,
+	},
+	.probe_new	= nb7vpq904m_probe,
+	.remove		= nb7vpq904m_remove,
+	.id_table	= nb7vpq904m_table,
+};
+
+module_i2c_driver(nb7vpq904m_driver);
+
+MODULE_AUTHOR("Dmitry Baryshkov <dmitry.baryshkov@linaro.org>");
+MODULE_DESCRIPTION("OnSemi NB7VPQ904M Type-C driver");
+MODULE_LICENSE("GPL");