[PATCH 2/2] usb: typec: Add support for Parade PS5169 USB Type-C redriver

Esteban Urrutia via B4 Relay <[email protected]> Mon, 27 Jul 2026 21:19:35 -0400
Newsgroups org.kernel.vger.phone-devel,org.kernel.feeds.b4-sent,org.kernel.vger.linux-devicetree,org.kernel.vger.linux-kernel,org.kernel.vger.linux-usb
Message-ID <[email protected]>
From: Esteban Urrutia <[email protected]>

Add driver for the Parade PS5169, an USB Type-C redriver capable of
handling 10Gb/s USB signals and 8.1Gb/s DisplayPort signals.

Signed-off-by: Esteban Urrutia <[email protected]>
---
 MAINTAINERS                    |   7 +
 drivers/usb/typec/mux/Kconfig  |  10 +
 drivers/usb/typec/mux/Makefile |   1 +
 drivers/usb/typec/mux/ps5169.c | 535 +++++++++++++++++++++++++++++++++++++++++
 4 files changed, 553 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index cc3c01eecfa2..3fa3a795c29b 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -28277,6 +28277,13 @@ F:	drivers/usb/typec/tcpm/tcpm.c
 F:	include/linux/usb/tcpci.h
 F:	include/linux/usb/tcpm.h
 
+USB TYPEC PS5169 MUX DRIVER
+M:	Esteban Urrutia <[email protected]>
+L:	[email protected]
+S:	Maintained
+F:	Documentation/devicetree/bindings/usb/parade,ps5169.yaml
+F:	drivers/usb/typec/mux/ps5169.c
+
 USB TYPEC TUSB1046 MUX DRIVER
 M:	Romain Gantois <[email protected]>
 L:	[email protected]
diff --git a/drivers/usb/typec/mux/Kconfig b/drivers/usb/typec/mux/Kconfig
index 6dd8f961b593..2838a53ce564 100644
--- a/drivers/usb/typec/mux/Kconfig
+++ b/drivers/usb/typec/mux/Kconfig
@@ -56,6 +56,16 @@ config TYPEC_MUX_NB7VPQ904M
 	  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.
 
+config TYPEC_MUX_PS5169
+	tristate "Parade PS5169 Type-C redriver driver"
+	depends on I2C
+	depends on DRM || DRM=n
+	select DRM_AUX_BRIDGE if DRM_BRIDGE && OF
+	select REGMAP_I2C
+	help
+	  Say Y or M if your system has a Parade PS5169 Type-C redriver chip
+	  found on some devices with a Type-C port.
+
 config TYPEC_MUX_PS883X
 	tristate "Parade PS883x Type-C retimer driver"
 	depends on I2C
diff --git a/drivers/usb/typec/mux/Makefile b/drivers/usb/typec/mux/Makefile
index b4f599eb5053..c775bfa12068 100644
--- a/drivers/usb/typec/mux/Makefile
+++ b/drivers/usb/typec/mux/Makefile
@@ -6,6 +6,7 @@ obj-$(CONFIG_TYPEC_MUX_PI3USB30532)	+= pi3usb30532.o
 obj-$(CONFIG_TYPEC_MUX_INTEL_PMC)	+= intel_pmc_mux.o
 obj-$(CONFIG_TYPEC_MUX_IT5205)		+= it5205.o
 obj-$(CONFIG_TYPEC_MUX_NB7VPQ904M)	+= nb7vpq904m.o
+obj-$(CONFIG_TYPEC_MUX_PS5169)		+= ps5169.o
 obj-$(CONFIG_TYPEC_MUX_PS883X)		+= ps883x.o
 obj-$(CONFIG_TYPEC_MUX_PTN36502)	+= ptn36502.o
 obj-$(CONFIG_TYPEC_MUX_TUSB1046)	+= tusb1046.o
diff --git a/drivers/usb/typec/mux/ps5169.c b/drivers/usb/typec/mux/ps5169.c
new file mode 100644
index 000000000000..3ee04843fa2d
--- /dev/null
+++ b/drivers/usb/typec/mux/ps5169.c
@@ -0,0 +1,535 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Driver for Parade PS5169 USB Type-C linear redriver
+ * Registers and bits decoded from downstream drivers
+ *
+ * Copyright (c) 2026 Esteban Urrutia <[email protected]>
+ */
+
+#include <drm/bridge/aux-bridge.h>
+
+#include <linux/i2c.h>
+#include <linux/regmap.h>
+
+#include <linux/usb/typec_altmode.h>
+#include <linux/usb/typec_dp.h>
+#include <linux/usb/typec_mux.h>
+#include <linux/usb/typec_retimer.h>
+
+/* Useful constants */
+#define PS5169_DP_EQ_REG_COUNT		2
+#define PS5169_USB_RX_EQ_REG_COUNT	4
+#define PS5169_USB_TX_EQ_REG_COUNT	3
+
+#define PS5169_DP_EQ_LEVEL_COUNT	8
+#define PS5169_USB_RX_EQ_LEVEL_COUNT	8
+#define PS5169_USB_TX_EQ_LEVEL_COUNT	8
+
+/* PS5169 registers */
+#define PS5169_CONFIG_REG		0x40
+#define PS5169_GAIN_REG			0x5c
+#define PS5169_USB_RX1_LFPS_REG		0x8d
+#define PS5169_USB_RX2_LFPS_REG		0x90
+#define PS5169_AUX_RX_DATA_REG		0x9f
+#define PS5169_AUX_MONITORING_REG	0xa0
+#define PS5169_HPD_LEVEL_REG		0xa1
+#define PS5169_CHIP_ID_REG		0xac
+#define PS5169_CHIP_REVISION_REG	0xae
+/* Chip revision is split in two registers */
+#define PS5169_MAX_REG			(PS5169_CHIP_REVISION_REG + 1)
+
+/* PS5169 register bits */
+#define PS5169_CONFIG_REVERSE		BIT(4)
+#define PS5169_CONFIG_DP_EN		BIT(5)
+#define PS5169_CONFIG_USB3_EN		BIT(6)
+#define PS5169_CONFIG_BASE		BIT(7)
+
+#define PS5169_GAIN_DP			BIT(4)
+#define PS5169_GAIN_USB_RX		BIT(2)
+#define PS5169_GAIN_USB_TX		BIT(0)
+
+#define PS5169_USB_RX_LFPS_50_OHM	BIT(0)
+
+#define PS5169_AUX_RX_DATA_REVERSE	BIT(1)
+
+#define PS5169_AUX_MONITORING_BIT	BIT(1)
+#define PS5169_HPD_LEVEL_BIT		BIT(2)
+
+/* PS5169 register values */
+#define PS5169_CHIP_ID			0x6987
+
+/* PS5169 register tables */
+static const int ps5169_dp_eq_reg[PS5169_DP_EQ_REG_COUNT]		= {0x52, 0x5e};
+static const int ps5169_usb_rx_eq_reg[PS5169_USB_RX_EQ_REG_COUNT]	= {0x51, 0x77, 0x54, 0x78};
+static const int ps5169_usb_tx_eq_reg[PS5169_USB_TX_EQ_REG_COUNT]	= {0x50, 0x5d, 0x54};
+
+/* PS5169 register mask tables */
+static const int ps5169_dp_eq_mask[PS5169_DP_EQ_REG_COUNT]		= {0x70, 0x07};
+static const int ps5169_usb_rx_eq_mask[PS5169_USB_RX_EQ_REG_COUNT]	= {0xf6, 0xf0, 0x0f, 0xe1};
+static const int ps5169_usb_tx_eq_mask[PS5169_USB_TX_EQ_REG_COUNT]	= {0x70, 0x70, 0xf0};
+
+/* PS5169 register value tables */
+static const int ps5169_dp_eq[PS5169_DP_EQ_LEVEL_COUNT][PS5169_DP_EQ_REG_COUNT] = {
+	{0x00, 0x04},			/* 2    dB */
+	{0x10, 0x05},			/* 5.5  dB */
+	{0x20, 0x06},			/* 6.5  dB */
+	{0x30, 0x06},			/* 7.5  dB */
+	{0x40, 0x06},			/* 8    dB */
+	{0x50, 0x07},			/* 8.5  dB */
+	{0x60, 0x07},			/* 9.5  dB */
+	{0x70, 0x07},			/* 10   dB, supposedly */
+};
+
+static const int ps5169_usb_rx_eq[PS5169_USB_RX_EQ_LEVEL_COUNT][PS5169_USB_RX_EQ_REG_COUNT] = {
+	{0x86, 0x00, 0x00, 0x20},	/* 5.2  dB */
+	{0x96, 0x00, 0x01, 0x20},	/* 6    dB */
+	{0xa6, 0x50, 0x01, 0x40},	/* 7    dB */
+	{0xb6, 0x50, 0x05, 0x40},	/* 8    dB */
+	{0xc6, 0xb0, 0x0c, 0x80},	/* 8.8  dB */
+	{0xd6, 0xf0, 0x05, 0x80},	/* 9.6  dB */
+	{0xe6, 0xf0, 0x0f, 0x80},	/* 10.4 dB */
+	{0xf6, 0x30, 0x0f, 0xa1},	/* 11.2 dB */
+};
+
+static const int ps5169_usb_tx_eq[PS5169_USB_TX_EQ_LEVEL_COUNT][PS5169_USB_TX_EQ_REG_COUNT] = {
+	{0x00, 0x40, 0x00},		/* 2    dB */
+	{0x10, 0x50, 0x10},		/* 5.5  dB */
+	{0x20, 0x60, 0x10},		/* 6.5  dB */
+	{0x30, 0x60, 0x50},		/* 7.5  dB */
+	{0x40, 0x60, 0xc0},		/* 8    dB */
+	{0x50, 0x70, 0x50},		/* 8.5  dB */
+	{0x60, 0x70, 0xf0},		/* 9.5  dB */
+	{0x70, 0x70, 0xf0},		/* 10   dB, supposedly */
+};
+
+struct redriver {
+	struct regulator *vcc;
+	struct gpio_desc *reset_gpio;
+
+	struct regmap *regmap;
+
+	struct typec_switch *typec_switch;
+	struct typec_mux *typec_mux;
+
+	struct typec_switch_dev *sw;
+	struct typec_retimer *retimer;
+
+	unsigned long mode;
+	enum typec_orientation orientation;
+
+	u32 dp_eq;
+	u32 usb_rx_eq;
+	u32 usb_tx_eq;
+
+	bool aux_rx_data_reverse;
+	bool tune_lfps;
+
+	bool dp_set_gain;
+	bool usb_rx_set_gain;
+	bool usb_tx_set_gain;
+
+	struct mutex lock; /* protect non-concurrent retimer & switch */
+
+	unsigned int svid;
+};
+
+static const struct regmap_config ps5169_regmap = {
+	.max_register = PS5169_MAX_REG,
+	.reg_bits = 8,
+	.val_bits = 8,
+	/* Accesses only done under ps5169->lock */
+	.disable_locking = true,
+};
+
+static void ps5169_write(struct redriver *ps5169, unsigned int reg, unsigned int val)
+{
+	regmap_write(ps5169->regmap, reg, val);
+
+	if (val & PS5169_CONFIG_DP_EN) {
+		/* Clearing this bit will enable the AUX channel */
+		regmap_clear_bits(ps5169->regmap, PS5169_AUX_MONITORING_REG,
+				  PS5169_AUX_MONITORING_BIT);
+		/* Setting this bit will set a high HPD level */
+		regmap_set_bits(ps5169->regmap, PS5169_HPD_LEVEL_REG, PS5169_HPD_LEVEL_BIT);
+	} else {
+		/* Setting this bit will disable the AUX channel */
+		regmap_set_bits(ps5169->regmap, PS5169_AUX_MONITORING_REG,
+				PS5169_AUX_MONITORING_BIT);
+		/* Clearing this bit will set a low HPD level */
+		regmap_clear_bits(ps5169->regmap, PS5169_HPD_LEVEL_REG, PS5169_HPD_LEVEL_BIT);
+	}
+
+	if (ps5169->tune_lfps) {
+		if (val & PS5169_CONFIG_USB3_EN) {
+			/* Setting these bits will enable a 50 ohm termination on USB RX */
+			regmap_set_bits(ps5169->regmap, PS5169_USB_RX1_LFPS_REG,
+					PS5169_USB_RX_LFPS_50_OHM);
+			regmap_set_bits(ps5169->regmap, PS5169_USB_RX2_LFPS_REG,
+					PS5169_USB_RX_LFPS_50_OHM);
+		} else {
+			/* Clearing these bits will disable a 50 ohm termination on USB RX */
+			regmap_clear_bits(ps5169->regmap, PS5169_USB_RX1_LFPS_REG,
+					  PS5169_USB_RX_LFPS_50_OHM);
+			regmap_clear_bits(ps5169->regmap, PS5169_USB_RX2_LFPS_REG,
+					  PS5169_USB_RX_LFPS_50_OHM);
+		}
+	}
+}
+
+static int ps5169_set(struct redriver *ps5169)
+{
+	bool reverse = (ps5169->orientation == TYPEC_ORIENTATION_REVERSE);
+	unsigned int val = PS5169_CONFIG_BASE;
+
+	switch (ps5169->mode) {
+	case TYPEC_STATE_SAFE:
+		ps5169_write(ps5169, PS5169_CONFIG_REG, val);
+
+		return 0;
+
+	case TYPEC_STATE_USB:
+		val |= PS5169_CONFIG_USB3_EN;
+		if (reverse)
+			val |= PS5169_CONFIG_REVERSE;
+		ps5169_write(ps5169, PS5169_CONFIG_REG, val);
+
+		return 0;
+
+	default:
+		if (ps5169->svid != USB_TYPEC_DP_SID)
+			return -EINVAL;
+
+		break;
+	}
+
+	switch (ps5169->mode) {
+	case TYPEC_DP_STATE_C:
+	case TYPEC_DP_STATE_E:
+		val |= PS5169_CONFIG_DP_EN;
+		if (reverse)
+			val |= PS5169_CONFIG_REVERSE;
+		ps5169_write(ps5169, PS5169_CONFIG_REG, val);
+
+		break;
+
+	case TYPEC_DP_STATE_D:
+		val |= PS5169_CONFIG_DP_EN | PS5169_CONFIG_USB3_EN;
+		if (reverse)
+			val |= PS5169_CONFIG_REVERSE;
+		ps5169_write(ps5169, PS5169_CONFIG_REG, val);
+
+		break;
+
+	default:
+		return -EOPNOTSUPP;
+	}
+
+	return 0;
+}
+
+static int ps5169_switch_set(struct typec_switch_dev *sw, enum typec_orientation orientation)
+{
+	struct redriver *ps5169 = typec_switch_get_drvdata(sw);
+	int ret;
+
+	ret = typec_switch_set(ps5169->typec_switch, orientation);
+	if (ret)
+		return ret;
+
+	mutex_lock(&ps5169->lock);
+
+	if (ps5169->orientation != orientation) {
+		ps5169->orientation = orientation;
+
+		ret = ps5169_set(ps5169);
+	}
+
+	mutex_unlock(&ps5169->lock);
+
+	return ret;
+}
+
+static int ps5169_retimer_set(struct typec_retimer *retimer, struct typec_retimer_state *state)
+{
+	struct redriver *ps5169 = typec_retimer_get_drvdata(retimer);
+	struct typec_mux_state mux_state;
+	int ret = 0;
+
+	mutex_lock(&ps5169->lock);
+
+	if (ps5169->mode != state->mode) {
+		ps5169->mode = state->mode;
+
+		if (state->alt)
+			ps5169->svid = state->alt->svid;
+		else
+			ps5169->svid = 0; // No SVID
+
+		ret = ps5169_set(ps5169);
+	}
+
+	mutex_unlock(&ps5169->lock);
+
+	if (ret)
+		return ret;
+
+	mux_state.alt = state->alt;
+	mux_state.data = state->data;
+	mux_state.mode = state->mode;
+
+	return typec_mux_set(ps5169->typec_mux, &mux_state);
+}
+
+static void ps5169_init(struct redriver *ps5169)
+{
+	int i;
+
+	/* DP equalization */
+	if (ps5169->dp_eq != U32_MAX)
+		for (i = 0; i < PS5169_DP_EQ_REG_COUNT; i++)
+			regmap_update_bits(ps5169->regmap, ps5169_dp_eq_reg[i],
+					   ps5169_dp_eq_mask[i], ps5169_dp_eq[ps5169->dp_eq][i]);
+
+	/* USB RX equalization */
+	if (ps5169->usb_rx_eq != U32_MAX)
+		for (i = 0; i < PS5169_USB_RX_EQ_REG_COUNT; i++)
+			regmap_update_bits(ps5169->regmap, ps5169_usb_rx_eq_reg[i],
+					   ps5169_usb_rx_eq_mask[i],
+					   ps5169_usb_rx_eq[ps5169->usb_rx_eq][i]);
+
+	/* USB TX equalization */
+	if (ps5169->usb_tx_eq != U32_MAX)
+		for (i = 0; i < PS5169_USB_TX_EQ_REG_COUNT; i++)
+			regmap_update_bits(ps5169->regmap, ps5169_usb_tx_eq_reg[i],
+					   ps5169_usb_tx_eq_mask[i],
+					   ps5169_usb_tx_eq[ps5169->usb_tx_eq][i]);
+
+	/* Some devices set this bit in this register, but its effect is unknown */
+	regmap_assign_bits(ps5169->regmap, PS5169_AUX_RX_DATA_REG, PS5169_AUX_RX_DATA_REVERSE,
+			   ps5169->aux_rx_data_reverse);
+
+	/* If these bits are assigned a -0.9 dB gain will be set on the corresponding channels */
+	regmap_assign_bits(ps5169->regmap, PS5169_GAIN_REG, PS5169_GAIN_DP, ps5169->dp_set_gain);
+	regmap_assign_bits(ps5169->regmap, PS5169_GAIN_REG, PS5169_GAIN_USB_RX,
+			   ps5169->usb_rx_set_gain);
+	regmap_assign_bits(ps5169->regmap, PS5169_GAIN_REG, PS5169_GAIN_USB_TX,
+			   ps5169->usb_tx_set_gain);
+}
+
+static int ps5169_probe(struct i2c_client *client)
+{
+	struct redriver *ps5169;
+	struct device *dev = &client->dev;
+	int ret;
+	u16 chip_id;
+	u16 chip_revision;
+	struct typec_switch_desc switch_desc = { };
+	struct typec_retimer_desc retimer_desc = { };
+
+	ps5169 = devm_kzalloc(dev, sizeof(*ps5169), GFP_KERNEL);
+	if (!ps5169)
+		return -ENOMEM;
+
+	ps5169->vcc = devm_regulator_get(dev, "vcc");
+	if (IS_ERR(ps5169->vcc))
+		return dev_err_probe(dev, PTR_ERR(ps5169->vcc), "Failed to get vcc-supply\n");
+
+	ps5169->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
+	if (IS_ERR(ps5169->reset_gpio))
+		return dev_err_probe(dev, PTR_ERR(ps5169->reset_gpio),
+				     "Failed to get reset-gpios\n");
+
+	ps5169->regmap = devm_regmap_init_i2c(client, &ps5169_regmap);
+	if (IS_ERR(ps5169->regmap))
+		return dev_err_probe(dev, PTR_ERR(ps5169->regmap), "Failed to initialize regmap\n");
+
+	ps5169->typec_switch = typec_switch_get(dev);
+	if (IS_ERR(ps5169->typec_switch))
+		return dev_err_probe(dev, PTR_ERR(ps5169->typec_switch),
+				     "Failed to get orientation-switch\n");
+
+	ps5169->typec_mux = typec_mux_get(dev);
+	if (IS_ERR(ps5169->typec_mux)) {
+		ret = dev_err_probe(dev, PTR_ERR(ps5169->typec_mux), "Failed to get mode-switch\n");
+		goto switch_put;
+	}
+
+	ret = regulator_enable(ps5169->vcc);
+	if (ret) {
+		dev_err(dev, "Failed to enable vcc-supply\n");
+		goto mux_put;
+	}
+
+	/* Reset the retimer */
+	gpiod_set_value(ps5169->reset_gpio, 1);
+	usleep_range(1000, 1500);
+	gpiod_set_value(ps5169->reset_gpio, 0);
+	usleep_range(10000, 11000);
+
+	ret = regmap_raw_read(ps5169->regmap, PS5169_CHIP_ID_REG, &chip_id, sizeof(chip_id));
+	if (ret) {
+		dev_err(dev, "Failed to read chip ID\n");
+		goto vcc_disable;
+	}
+
+	/* Compare chip IDs */
+	if (chip_id != PS5169_CHIP_ID) {
+		dev_err(dev, "Unexpected chip ID 0x%04x\n", chip_id);
+		goto vcc_disable;
+	}
+
+	/* Read chip revision */
+	ret = regmap_raw_read(ps5169->regmap, PS5169_CHIP_REVISION_REG, &chip_revision,
+			      sizeof(chip_revision));
+	if (ret) {
+		dev_err(dev, "Failed to read chip revision\n");
+		goto vcc_disable;
+	}
+	dev_dbg(dev, "Found PS5169 with chip revision 0x%04x\n", chip_revision);
+
+	ret = drm_aux_bridge_register(dev);
+	if (ret) {
+		dev_err(dev, "Failed to register aux_bridge\n");
+		goto vcc_disable;
+	}
+
+	/* orientation-switch */
+	switch_desc.drvdata = ps5169;
+	switch_desc.fwnode = dev_fwnode(dev);
+	switch_desc.set = ps5169_switch_set;
+
+	ps5169->sw = typec_switch_register(dev, &switch_desc);
+	if (IS_ERR(ps5169->sw)) {
+		ret = dev_err_probe(dev, PTR_ERR(ps5169->sw), "Failed to register switch\n");
+		goto vcc_disable;
+	}
+
+	/* retimer-switch */
+	retimer_desc.drvdata = ps5169;
+	retimer_desc.fwnode = dev_fwnode(dev);
+	retimer_desc.set = ps5169_retimer_set;
+
+	ps5169->retimer = typec_retimer_register(dev, &retimer_desc);
+	if (IS_ERR(ps5169->retimer)) {
+		ret = dev_err_probe(dev, PTR_ERR(ps5169->retimer), "Failed to register retimer\n");
+		goto switch_unregister;
+	}
+
+	ps5169->mode = TYPEC_STATE_SAFE;
+	ps5169->orientation = TYPEC_ORIENTATION_NONE;
+
+	/* DP equalization */
+	if (device_property_present(dev, "parade,dp-eq-level")) {
+		ret = device_property_read_u32(dev, "parade,dp-eq-level", &ps5169->dp_eq);
+		if (ret) {
+			dev_err(dev, "Failed to read parade,dp-eq-level\n");
+			goto switch_unregister;
+		}
+		if (ps5169->dp_eq > PS5169_DP_EQ_LEVEL_COUNT - 1) {
+			dev_err(dev, "parade,dp-eq-level exceeds maximum of %d\n",
+				PS5169_DP_EQ_LEVEL_COUNT - 1);
+			ret = -EINVAL;
+			goto switch_unregister;
+		}
+	} else {
+		dev_warn(dev, "parade,dp-eq-level is not present, leaving DP EQ as is\n");
+		ps5169->dp_eq = U32_MAX;
+	}
+
+	/* USB RX equalization */
+	if (device_property_present(dev, "parade,usb-rx-eq-level")) {
+		ret = device_property_read_u32(dev, "parade,usb-rx-eq-level", &ps5169->usb_rx_eq);
+		if (ret) {
+			dev_err(dev, "Failed to read parade,usb-rx-eq-level\n");
+			goto switch_unregister;
+		}
+		if (ps5169->usb_rx_eq > PS5169_USB_RX_EQ_LEVEL_COUNT - 1) {
+			dev_err(dev, "parade,usb-rx-eq-level exceeds maximum of %d\n",
+				PS5169_USB_RX_EQ_LEVEL_COUNT - 1);
+			ret = -EINVAL;
+			goto switch_unregister;
+		}
+	} else {
+		dev_warn(dev, "parade,usb-rx-eq-level is not present, leaving USB RX EQ as is\n");
+		ps5169->usb_rx_eq = U32_MAX;
+	}
+
+	/* USB TX equalization */
+	if (device_property_present(dev, "parade,usb-tx-eq-level")) {
+		ret = device_property_read_u32(dev, "parade,usb-tx-eq-level", &ps5169->usb_tx_eq);
+		if (ret) {
+			dev_err(dev, "Failed to read parade,usb-tx-eq-level\n");
+			goto switch_unregister;
+		}
+		if (ps5169->usb_tx_eq > PS5169_USB_TX_EQ_LEVEL_COUNT - 1) {
+			dev_err(dev, "parade,usb-tx-eq-level exceeds maximum of %d\n",
+				PS5169_USB_TX_EQ_LEVEL_COUNT - 1);
+			ret = -EINVAL;
+			goto switch_unregister;
+		}
+	} else {
+		dev_warn(dev, "parade,usb-tx-eq-level is not present, leaving USB TX EQ as is\n");
+		ps5169->usb_tx_eq = U32_MAX;
+	}
+
+	/* Read properties from DT */
+	ps5169->aux_rx_data_reverse = device_property_read_bool(dev, "parade,aux-rx-data-reverse");
+	ps5169->tune_lfps = device_property_read_bool(dev, "parade,fine-tune-lfps-swing");
+	ps5169->dp_set_gain = device_property_read_bool(dev, "parade,dp-set-gain");
+	ps5169->usb_rx_set_gain = device_property_read_bool(dev, "parade,usb-rx-set-gain");
+	ps5169->usb_tx_set_gain = device_property_read_bool(dev, "parade,usb-tx-set-gain");
+
+	ps5169_init(ps5169);
+
+	mutex_init(&ps5169->lock);
+
+	i2c_set_clientdata(client, ps5169);
+
+	return 0;
+
+switch_unregister:
+	typec_switch_unregister(ps5169->sw);
+
+vcc_disable:
+	regulator_disable(ps5169->vcc);
+
+mux_put:
+	typec_mux_put(ps5169->typec_mux);
+switch_put:
+	typec_switch_put(ps5169->typec_switch);
+
+	return ret;
+}
+
+static void ps5169_remove(struct i2c_client *client)
+{
+	struct redriver *ps5169 = i2c_get_clientdata(client);
+
+	typec_retimer_unregister(ps5169->retimer);
+	typec_switch_unregister(ps5169->sw);
+
+	regulator_disable(ps5169->vcc);
+
+	typec_mux_put(ps5169->typec_mux);
+	typec_switch_put(ps5169->typec_switch);
+}
+
+static const struct of_device_id ps5169_of_match_table[] = {
+	{ .compatible = "parade,ps5169" },
+	{ },
+};
+
+static struct i2c_driver ps5169_driver = {
+	.driver = {
+		.name = "ps5169",
+		.of_match_table = ps5169_of_match_table,
+	},
+	.probe = ps5169_probe,
+	.remove = ps5169_remove,
+};
+
+module_i2c_driver(ps5169_driver);
+
+MODULE_AUTHOR("Esteban Urrutia <[email protected]>");
+MODULE_DESCRIPTION("Driver for Parade PS5169 USB Type-C linear redriver");
+MODULE_LICENSE("GPL");

-- 
2.55.0