All of lore.kernel.org
 help / color / mirror / Atom feed
From: Marek Vasut <marex@nabladev.com>
To: linux-usb@vger.kernel.org
Cc: fabrice.gasnier@foss.st.com, Marek Vasut <marex@nabladev.com>,
	Alexandre Torgue <alexandre.torgue@foss.st.com>,
	Conor Dooley <conor+dt@kernel.org>,
	Greg Kroah-Hartman <gregkh@linuxfoundation.org>,
	Heikki Krogerus <heikki.krogerus@linux.intel.com>,
	Krzysztof Kozlowski <krzk+dt@kernel.org>,
	Maxime Coquelin <mcoquelin.stm32@gmail.com>,
	Rob Herring <robh@kernel.org>,
	Sebastian Reichel <sebastian.reichel@collabora.com>,
	devicetree@vger.kernel.org, kernel@dh-electronics.com,
	linux-arm-kernel@lists.infradead.org,
	linux-kernel@vger.kernel.org,
	linux-stm32@st-md-mailman.stormreply.com
Subject: [PATCH 2/4] usb: typec: ucpd: Add ST UCPD driver
Date: Wed, 19 Aug 2026 18:33:53 +0200	[thread overview]
Message-ID: <20260819163410.119755-2-marex@nabladev.com> (raw)
In-Reply-To: <20260819163410.119755-1-marex@nabladev.com>

Add ST UCPD Type-C block driver. The UCPD integrates the physical
layer of the Power Delivery (PD), with CC signaling method, for
operation with Type-C cables. Vbus and Vconn control is performed
using an external chip, usually ST TCPP01/TCPP02/TCPP03 or similar.

Signed-off-by: Marek Vasut <marex@nabladev.com>
---
Cc: Alexandre Torgue <alexandre.torgue@foss.st.com>
Cc: Conor Dooley <conor+dt@kernel.org>
Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Cc: Krzysztof Kozlowski <krzk+dt@kernel.org>
Cc: Maxime Coquelin <mcoquelin.stm32@gmail.com>
Cc: Rob Herring <robh@kernel.org>
Cc: Sebastian Reichel <sebastian.reichel@collabora.com>
Cc: devicetree@vger.kernel.org
Cc: kernel@dh-electronics.com
Cc: linux-arm-kernel@lists.infradead.org
Cc: linux-kernel@vger.kernel.org
Cc: linux-stm32@st-md-mailman.stormreply.com
Cc: linux-usb@vger.kernel.org
---
 drivers/usb/typec/tcpm/Kconfig  |   9 +
 drivers/usb/typec/tcpm/Makefile |   1 +
 drivers/usb/typec/tcpm/ucpd.c   | 951 ++++++++++++++++++++++++++++++++
 3 files changed, 961 insertions(+)
 create mode 100644 drivers/usb/typec/tcpm/ucpd.c

diff --git a/drivers/usb/typec/tcpm/Kconfig b/drivers/usb/typec/tcpm/Kconfig
index 00baa7503d452..eeab366ae08a8 100644
--- a/drivers/usb/typec/tcpm/Kconfig
+++ b/drivers/usb/typec/tcpm/Kconfig
@@ -91,4 +91,13 @@ config TYPEC_QCOM_PMIC
 	  This driver enables Type-C role switching, orientation, Alternate
 	  mode and Power Delivery support both for VBUS and VCONN.
 
+config TYPEC_ST_UCPD
+	tristate "ST UCPD Type-C Port Controller Manager driver"
+	depends on ARCH_STM32 || COMPILE_TEST
+	help
+	  The ST UCPD Type-C block integrates the physical layer of the
+	  Power Delivery (PD), with CC signaling method, for operation
+	  with Type-C cables. Vbus and Vconn control is performed using
+	  an external chip, usually ST TCPP01/TCPP02/TCPP03 or similar.
+
 endif # TYPEC_TCPM
diff --git a/drivers/usb/typec/tcpm/Makefile b/drivers/usb/typec/tcpm/Makefile
index 7a8cad0c0bdb4..5cde6d9aedb2e 100644
--- a/drivers/usb/typec/tcpm/Makefile
+++ b/drivers/usb/typec/tcpm/Makefile
@@ -10,3 +10,4 @@ obj-$(CONFIG_TYPEC_TCPCI_MT6370)	+= tcpci_mt6370.o
 obj-$(CONFIG_TYPEC_TCPCI_MAXIM)		+= tcpci_maxim.o
 tcpci_maxim-y				+= tcpci_maxim_core.o maxim_contaminant.o
 obj-$(CONFIG_TYPEC_QCOM_PMIC)		+= qcom/
+obj-$(CONFIG_TYPEC_ST_UCPD)		+= ucpd.o
diff --git a/drivers/usb/typec/tcpm/ucpd.c b/drivers/usb/typec/tcpm/ucpd.c
new file mode 100644
index 0000000000000..fa7c0ffbfd577
--- /dev/null
+++ b/drivers/usb/typec/tcpm/ucpd.c
@@ -0,0 +1,951 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright 2026 Marek Vasut
+ *
+ * STM32MP2 UCPD Type-C Driver
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/gpio/consumer.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/nvmem-consumer.h>
+#include <linux/of.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/reset.h>
+#include <linux/spinlock.h>
+#include <linux/types.h>
+#include <linux/usb.h>
+#include <linux/usb/pd.h>
+#include <linux/usb/tcpm.h>
+#include <linux/usb/typec.h>
+#include <linux/usb/typec_mux.h>
+
+#define UCPD_CFGR1					0x00
+#define UCPD_CFGR1_UCPDEN				BIT(31)
+#define UCPD_CFGR1_RXDMAEN				BIT(30)
+#define UCPD_CFGR1_TXDMAEN				BIT(29)
+#define UCPD_CFGR1_RXORDSETEN				GENMASK(28, 20)
+#define UCPD_CFGR1_RXORDSETEN_SOP			BIT(20)
+#define UCPD_CFGR1_RXORDSETEN_SOP_PRIME			BIT(21)
+#define UCPD_CFGR1_RXORDSETEN_SOP_PRIME_PRIME		BIT(22)
+#define UCPD_CFGR1_RXORDSETEN_HRST			BIT(23)
+#define UCPD_CFGR1_RXORDSETEN_CRST			BIT(24)
+#define UCPD_CFGR1_RXORDSETEN_SOP_PRIME_DEBUG		BIT(25)
+#define UCPD_CFGR1_RXORDSETEN_SOP_PRIME_PRIME_DEBUG	BIT(26)
+#define UCPD_CFGR1_RXORDSETEN_SOP_EXT1			BIT(27)
+#define UCPD_CFGR1_RXORDSETEN_SOP_EXT2			BIT(28)
+#define UCPD_CFGR1_PSC_UCPDCLK				GENMASK(19, 17)
+#define UCPD_CFGR1_TRANSWIN				GENMASK(15, 11)
+#define UCPD_CFGR1_IFRGAP				GENMASK(10, 6)
+#define UCPD_CFGR1_HBITCLKDIV				GENMASK(5, 0)
+
+#define UCPD_CFGR2					0x04
+#define UCPD_CFGR2_WUPEN				BIT(3)
+#define UCPD_CFGR2_FORCECLK				BIT(2)
+#define UCPD_CFGR2_RXFILT2N3				BIT(1)
+#define UCPD_CFGR2_RXFILTDIS				BIT(0)
+
+#define UCPD_CFGR3					0x08
+#define UCPD_CFGR3_TRIM_CC2_RP				GENMASK(28, 25)
+#define UCPD_CFGR3_TRIM_CC2_RD				GENMASK(19, 16)
+#define UCPD_CFGR3_TRIM_CC1_RP				GENMASK(12, 9)
+#define UCPD_CFGR3_TRIM_CC1_RD				GENMASK(3, 0)
+
+#define UCPD_CR						0x0c
+#define UCPD_CR_CC2TCDIS				BIT(21)
+#define UCPD_CR_CC1TCDIS				BIT(20)
+#define UCPD_CR_RDCH					BIT(18)
+#define UCPD_CR_CCENABLE_CC2				BIT(11)
+#define UCPD_CR_CCENABLE_CC1				BIT(10)
+#define UCPD_CR_ANAMODE					BIT(9)	/* 0:SRC 1:SNK */
+#define UCPD_CR_ANASUBMODE				GENMASK(8, 7)
+#define UCPD_CR_PHYCCSEL				BIT(6)	/* 0:CC1 1:CC2 */
+#define UCPD_CR_PHYRXEN					BIT(5)
+#define UCPD_CR_RXMODE					BIT(4)
+#define UCPD_CR_TXHRST					BIT(3)
+#define UCPD_CR_TXSEND					BIT(2)
+#define UCPD_CR_TXMODE					GENMASK(1, 0)
+#define UCPD_CR_TXMODE_PACKET				0
+#define UCPD_CR_TXMODE_CABLE				1
+#define UCPD_CR_TXMODE_BIST				2
+
+#define UCPD_IMR					0x10
+#define UCPD_IMR_TYPECEVT2IE				BIT(15)
+#define UCPD_IMR_TYPECEVT1IE				BIT(14)
+#define UCPD_IMR_RXMSGENDIE				BIT(12)
+#define UCPD_IMR_RXOVRIE				BIT(11)
+#define UCPD_IMR_RXHRSTDETIE				BIT(10)
+#define UCPD_IMR_RXORDDETIE				BIT(9)
+#define UCPD_IMR_RXNEIE					BIT(8)
+#define UCPD_IMR_TXUNDIE				BIT(6)
+#define UCPD_IMR_HRSTSENTIE				BIT(5)
+#define UCPD_IMR_HRSTDISCIE				BIT(4)
+#define UCPD_IMR_TXMSGABTIE				BIT(3)
+#define UCPD_IMR_TXMSGSENTIE				BIT(2)
+#define UCPD_IMR_TXMSGDISCIE				BIT(1)
+#define UCPD_IMR_TXISIE					BIT(0)
+
+#define UCPD_SR						0x14
+#define UCPD_SR_TYPEC_VSTATE_CC2			GENMASK(19, 18)
+#define UCPD_SR_TYPEC_VSTATE_CC1			GENMASK(17, 16)
+#define UCPD_SR_TYPEC_VSTATE_CC_LOWEST			0
+#define UCPD_SR_TYPEC_VSTATE_CC_LOW			1
+#define UCPD_SR_TYPEC_VSTATE_CC_HIGH			2
+#define UCPD_SR_TYPEC_VSTATE_CC_HIGHEST			3
+#define UCPD_SR_TYPECEVT2				BIT(15)
+#define UCPD_SR_TYPECEVT1				BIT(14)
+#define UCPD_SR_RXERR					BIT(13)
+#define UCPD_SR_RXMSGEND				BIT(12)
+#define UCPD_SR_RXOVR					BIT(11)
+#define UCPD_SR_RXHRSTDET				BIT(10)
+#define UCPD_SR_RXORDDET				BIT(9)
+#define UCPD_SR_RXNE					BIT(8)
+#define UCPD_SR_TXUND					BIT(6)
+#define UCPD_SR_HRSTSENT				BIT(5)
+#define UCPD_SR_HRSTDISC				BIT(4)
+#define UCPD_SR_TXMSGABT				BIT(3)
+#define UCPD_SR_TXMSGSENT				BIT(2)
+#define UCPD_SR_TXMSGDISC				BIT(1)
+#define UCPD_SR_TXIS					BIT(0)
+
+#define UCPD_ICR					0x18
+#define UCPD_ICR_TYPECEVT2CF				BIT(15)
+#define UCPD_ICR_TYPECEVT1CF				BIT(14)
+#define UCPD_ICR_RXMSGENDCF				BIT(12)
+#define UCPD_ICR_RXOVRCF				BIT(11)
+#define UCPD_ICR_RXHRSTDETCF				BIT(10)
+#define UCPD_ICR_RXORDDETCF				BIT(9)
+#define UCPD_ICR_TXUNDCF				BIT(6)
+#define UCPD_ICR_HRSTSENTCF				BIT(5)
+#define UCPD_ICR_HRSTDISCCF				BIT(4)
+#define UCPD_ICR_TXMSGABTCF				BIT(3)
+#define UCPD_ICR_TXMSGSENTCF				BIT(2)
+#define UCPD_ICR_TXMSGDISCCF				BIT(1)
+
+#define UCPD_TX_ORDSETR					0x1c
+#define UCPD_TX_ORDSETR_TXORDSET			GENMASK(19, 0)
+#define SYNC1						0x18
+#define SYNC2						0x11
+#define SYNC3						0x06
+#define RST1						0x07
+#define RST2						0x19
+#define EOP						0x0d
+#define ORDSET(x, y, z, w)				((x) | ((x) << 5) | ((x) << 10) | ((x) << 15))
+#define UCPD_TX_ORDSETR_TXORDSET_SOP			ORDSET(SYNC1, SYNC1, SYNC1, SYNC2)
+#define UCPD_TX_ORDSETR_TXORDSET_SOP_PRIME		ORDSET(SYNC1, SYNC1, SYNC3, SYNC3)
+#define UCPD_TX_ORDSETR_TXORDSET_SOP_PRIME_PRIME	ORDSET(SYNC1, SYNC3, SYNC1, SYNC3)
+#define UCPD_TX_ORDSETR_TXORDSET_HRST			ORDSET(RST1, RST1, RST1, RST2)
+#define UCPD_TX_ORDSETR_TXORDSET_CRST			ORDSET(RST1, SYNC1, RST1, RST3)
+#define UCPD_TX_ORDSETR_TXORDSET_SOP_PRIME_DEBUG	ORDSET(SYNC1, RST2, RST2, SYNC3)
+#define UCPD_TX_ORDSETR_TXORDSET_SOP_PRIME_PRIME_DEBUG	ORDSET(SYNC1, RST2, SYNC3, SYNC2)
+
+#define UCPD_TX_PAYSZR					0x20
+#define UCPD_TX_PAYSZR_TXPAYSZ				GENMASK(9, 0)
+
+#define UCPD_TXDR					0x24
+#define UCPD_TXDR_TXDATA				GENMASK(7, 0)
+
+#define UCPD_RX_ORDSETR					0x28
+#define UCPD_RX_ORDSETR_RXSOPKINVALID			GENMASK(6, 4)
+#define UCPD_RX_ORDSETR_RXSOP3OF4			BIT(3)
+#define UCPD_RX_ORDSETR_RXORDSET			GENMASK(2, 0)
+
+#define UCPD_RX_PAYSZR					0x2c
+#define UCPD_RX_PAYSZR_RXPAYSZ				GENMASK(9, 0)
+
+#define UCPD_RXDR					0x30
+#define UCPD_RXDR_RXDATA				GENMASK(7, 0)
+
+#define UCPD_RX_ORDEXTR1				0x34
+#define UCPD_RX_ORDEXTR1_RXSOPX1			GENMASK(19, 0)
+
+#define UCPD_RX_ORDEXTR2				0x38
+#define UCPD_RX_ORDEXTR2_RXSOPX2			GENMASK(19, 0)
+
+/* IDs that have no impact on the IP operation */
+#define UCPD_VERR					0x3f4
+#define UCPD_IPIDR					0x3f8
+#define UCPD_SIDR					0x3fc
+
+/* OTP trim offsets (base offset is handled by NVMEM) */
+#define OTP_TRIM_1A5_RP					GENMASK(15, 12)
+#define OTP_TRIM_CC2_RD					GENMASK(11, 8)
+#define OTP_TRIM_3A0_RP					GENMASK(7, 4)
+#define OTP_TRIM_CC1_RD					GENMASK(3, 0)
+
+static const struct regmap_range ucpd_readable_ranges[] = {
+	regmap_reg_range(UCPD_CFGR1, UCPD_SR),
+	regmap_reg_range(UCPD_TX_ORDSETR, UCPD_RX_ORDEXTR2),
+	regmap_reg_range(UCPD_VERR, UCPD_SIDR),
+};
+
+static const struct regmap_access_table ucpd_readable_table = {
+	.yes_ranges	= ucpd_readable_ranges,
+	.n_yes_ranges	= ARRAY_SIZE(ucpd_readable_ranges),
+};
+
+static const struct regmap_range ucpd_writeable_ranges[] = {
+	regmap_reg_range(UCPD_CFGR1, UCPD_IMR),
+	regmap_reg_range(UCPD_ICR, UCPD_TXDR),
+	regmap_reg_range(UCPD_RX_ORDEXTR1, UCPD_RX_ORDEXTR2),
+};
+
+static const struct regmap_access_table ucpd_writeable_table = {
+	.yes_ranges	= ucpd_writeable_ranges,
+	.n_yes_ranges	= ARRAY_SIZE(ucpd_writeable_ranges),
+};
+
+static const struct regmap_range ucpd_volatile_ranges[] = {
+	regmap_reg_range(UCPD_SR, UCPD_SR),
+	regmap_reg_range(UCPD_TXDR, UCPD_TXDR),
+	regmap_reg_range(UCPD_RX_ORDSETR, UCPD_RXDR),
+};
+
+static const struct regmap_access_table ucpd_volatile_table = {
+	.yes_ranges	= ucpd_volatile_ranges,
+	.n_yes_ranges	= ARRAY_SIZE(ucpd_volatile_ranges),
+};
+
+static const struct regmap_config ucpd_regmap_config = {
+	.reg_bits		= 32,
+	.reg_stride		= 4,
+	.val_bits		= 32,
+	.max_register		= UCPD_SIDR,
+	.rd_table		= &ucpd_readable_table,
+	.wr_table		= &ucpd_writeable_table,
+	.volatile_table		= &ucpd_volatile_table,
+	.cache_type		= REGCACHE_NONE/*FLAT*/,
+};
+
+struct ucpd {
+	struct device *dev;
+	struct tcpm_port *tcpm_port;
+	struct tcpc_dev tcpc_dev;
+	struct regmap *regmap;
+	struct reset_control *phy_reset;
+	struct regulator *vbus;
+	struct gpio_desc *vbus_in;
+	struct gpio_descs *vconn_gpios;
+	struct typec_switch *orient_sw;
+
+	/* Rp/Rd trimming */
+	u32 trim;
+
+	/* lock for sharing ucpd states */
+	struct mutex lock;
+
+	/* port status */
+	bool vconn_on;
+	bool vbus_on;
+	bool tx_pending;
+	enum typec_cc_polarity cc_polarity;
+	enum typec_cc_status cc1;
+	enum typec_cc_status cc2;
+	enum typec_role pwr_role;
+	enum typec_data_role data_role;
+};
+
+static struct ucpd *tcpm_to_ucpd(struct tcpc_dev *dev)
+{
+	return container_of(dev, struct ucpd, tcpc_dev);
+}
+
+static void tcpm_load_trim_rd(struct ucpd *ucpd)
+{
+	const u32 rd_cc1 = FIELD_GET(OTP_TRIM_CC1_RD, ucpd->trim);
+	const u32 rd_cc2 = FIELD_GET(OTP_TRIM_CC2_RD, ucpd->trim);
+	const u32 msk = UCPD_CFGR3_TRIM_CC2_RD | UCPD_CFGR3_TRIM_CC1_RD;
+	const u32 val = FIELD_PREP(UCPD_CFGR3_TRIM_CC1_RD, rd_cc1) |
+			FIELD_PREP(UCPD_CFGR3_TRIM_CC2_RD, rd_cc2);
+
+	regmap_update_bits(ucpd->regmap, UCPD_CFGR3, msk, val);
+}
+
+static void tcpm_load_trim_rp(struct ucpd *ucpd, bool rp3a0)
+{
+	const u32 rp_1a5 = FIELD_GET(OTP_TRIM_1A5_RP, ucpd->trim);
+	const u32 rp_3a0 = FIELD_GET(OTP_TRIM_3A0_RP, ucpd->trim);
+	const u32 msk = UCPD_CFGR3_TRIM_CC2_RP | UCPD_CFGR3_TRIM_CC1_RP;
+	const u32 val = FIELD_PREP(UCPD_CFGR3_TRIM_CC1_RP, rp3a0 ? rp_1a5 : rp_3a0) |
+			FIELD_PREP(UCPD_CFGR3_TRIM_CC2_RP, rp3a0 ? rp_1a5 : rp_3a0);
+
+	regmap_update_bits(ucpd->regmap, UCPD_CFGR3, msk, val);
+}
+
+static int tcpm_init(struct tcpc_dev *dev)
+{
+	struct ucpd *ucpd = tcpm_to_ucpd(dev);
+
+	reset_control_assert(ucpd->phy_reset);
+	reset_control_status(ucpd->phy_reset);
+	fsleep(1000);
+	reset_control_deassert(ucpd->phy_reset);
+
+	/*
+	 * UCPD clock is 16 MHz / 2 = 8 MHz
+	 * Bit clock is 16 MHz / 2 / (13 + 1) ~= 570 kHz
+	 */
+	regmap_write(ucpd->regmap, UCPD_CFGR1,
+		     UCPD_CFGR1_RXORDSETEN_SOP |
+		     UCPD_CFGR1_RXORDSETEN_SOP_PRIME |
+		     UCPD_CFGR1_RXORDSETEN_SOP_PRIME_PRIME |
+		     UCPD_CFGR1_RXORDSETEN_HRST |
+		     UCPD_CFGR1_RXORDSETEN_CRST |
+		     FIELD_PREP(UCPD_CFGR1_PSC_UCPDCLK, 1) |
+		     FIELD_PREP(UCPD_CFGR1_TRANSWIN, 7) |
+		     FIELD_PREP(UCPD_CFGR1_IFRGAP, 0x1f) |
+		     FIELD_PREP(UCPD_CFGR1_HBITCLKDIV, 13));
+
+	regmap_set_bits(ucpd->regmap, UCPD_CFGR1, UCPD_CFGR1_UCPDEN);
+
+	tcpm_load_trim_rd(ucpd);
+	tcpm_load_trim_rp(ucpd, true);
+
+	regmap_set_bits(ucpd->regmap, UCPD_CR, UCPD_CR_CCENABLE_CC1 | UCPD_CR_CCENABLE_CC2);
+
+	regmap_write(ucpd->regmap, UCPD_ICR,
+		     UCPD_IMR_TYPECEVT2IE | UCPD_IMR_TYPECEVT1IE |
+		     UCPD_IMR_HRSTSENTIE | UCPD_IMR_HRSTDISCIE);
+	regmap_write(ucpd->regmap, UCPD_IMR,
+		     UCPD_IMR_TYPECEVT2IE | UCPD_IMR_TYPECEVT1IE |
+		     UCPD_IMR_HRSTSENTIE | UCPD_IMR_HRSTDISCIE);
+
+	return 0;
+}
+
+static int tcpm_get_vbus(struct tcpc_dev *dev)
+{
+	struct ucpd *ucpd = tcpm_to_ucpd(dev);
+
+	return gpiod_get_value_cansleep(ucpd->vbus_in);
+}
+
+static int tcpm_set_cc(struct tcpc_dev *dev, enum typec_cc_status cc)
+{
+	const u32 ccmsk = UCPD_CR_CCENABLE_CC2 | UCPD_CR_CCENABLE_CC1 | UCPD_CR_ANASUBMODE;
+	struct ucpd *ucpd = tcpm_to_ucpd(dev);
+	int ret = 0;
+	u32 ccset;
+
+	mutex_lock(&ucpd->lock);
+
+	if (ucpd->vconn_on) {
+		if (ucpd->cc_polarity == TYPEC_POLARITY_CC1)
+			ccset = UCPD_CR_CCENABLE_CC1;
+		if (ucpd->cc_polarity == TYPEC_POLARITY_CC2)
+			ccset = UCPD_CR_CCENABLE_CC2;
+	} else {
+		ccset = UCPD_CR_CCENABLE_CC1 | UCPD_CR_CCENABLE_CC2;
+	}
+
+	switch (cc) {
+	case TYPEC_CC_OPEN:
+		regmap_clear_bits(ucpd->regmap, UCPD_CR, ccmsk);
+		break;
+	case TYPEC_CC_RA:
+		regmap_update_bits(ucpd->regmap, UCPD_CR, ccmsk,
+				   ccset | FIELD_PREP(UCPD_CR_ANASUBMODE, 0));
+		break;
+	case TYPEC_CC_RD:
+		regmap_update_bits(ucpd->regmap, UCPD_CR, ccmsk | UCPD_CR_ANAMODE,
+				   ccset | UCPD_CR_ANAMODE | FIELD_PREP(UCPD_CR_ANASUBMODE, 1));
+		break;
+	case TYPEC_CC_RP_DEF:
+	case TYPEC_CC_RP_1_5:
+		tcpm_load_trim_rp(ucpd, false);
+		regmap_update_bits(ucpd->regmap, UCPD_CR, ccmsk | UCPD_CR_ANAMODE,
+				   ccset | FIELD_PREP(UCPD_CR_ANASUBMODE, 2));
+		break;
+	case TYPEC_CC_RP_3_0:
+		tcpm_load_trim_rp(ucpd, true);
+		regmap_update_bits(ucpd->regmap, UCPD_CR, ccmsk | UCPD_CR_ANAMODE,
+				   ccset | FIELD_PREP(UCPD_CR_ANASUBMODE, 3));
+		break;
+	default:
+		dev_err(ucpd->dev, "unsupported cc value %d", cc);
+		ret = -EINVAL;
+		break;
+	}
+
+	mutex_unlock(&ucpd->lock);
+
+	return ret;
+}
+
+static enum typec_cc_status tcpm_decode_cc(const int cr, const int cc)
+{
+	bool sink = !!(cr & UCPD_CR_ANAMODE);
+
+	switch (cc) {
+	case UCPD_SR_TYPEC_VSTATE_CC_LOWEST:
+		return TYPEC_CC_RA;
+	case UCPD_SR_TYPEC_VSTATE_CC_LOW:
+		return TYPEC_CC_RD;
+	case UCPD_SR_TYPEC_VSTATE_CC_HIGH:
+		return sink ? TYPEC_CC_RP_1_5 : TYPEC_CC_OPEN;
+	default:
+		return sink ? TYPEC_CC_RP_3_0 : TYPEC_CC_OPEN;
+	}
+}
+
+static int tcpm_get_cc(struct tcpc_dev *dev,
+		       enum typec_cc_status *cc1,
+		       enum typec_cc_status *cc2)
+{
+	struct ucpd *ucpd = tcpm_to_ucpd(dev);
+	int cr, sr;
+
+	mutex_lock(&ucpd->lock);
+
+	regmap_read(ucpd->regmap, UCPD_CR, &cr);
+	regmap_read(ucpd->regmap, UCPD_SR, &sr);
+
+	if (cr & UCPD_CR_CCENABLE_CC1)
+		ucpd->cc1 = tcpm_decode_cc(cr, FIELD_GET(UCPD_SR_TYPEC_VSTATE_CC1, sr));
+	*cc1 = ucpd->cc1;
+
+	if (cr & UCPD_CR_CCENABLE_CC2)
+		ucpd->cc2 = tcpm_decode_cc(cr, FIELD_GET(UCPD_SR_TYPEC_VSTATE_CC2, sr));
+	*cc2 = ucpd->cc2;
+
+	mutex_unlock(&ucpd->lock);
+
+	return 0;
+}
+
+static int tcpm_set_polarity(struct tcpc_dev *dev,
+			     enum typec_cc_polarity polarity)
+{
+	struct ucpd *ucpd = tcpm_to_ucpd(dev);
+	int ret;
+
+	regmap_update_bits(ucpd->regmap, UCPD_CR, UCPD_CR_PHYCCSEL,
+			   (polarity == TYPEC_POLARITY_CC1) ? 0 : UCPD_CR_PHYCCSEL);
+
+	if (!ucpd->orient_sw)
+		return 0;
+
+	if (polarity == TYPEC_POLARITY_CC1)
+		ret = typec_switch_set(ucpd->orient_sw, TYPEC_ORIENTATION_NORMAL);
+	else if (polarity == TYPEC_POLARITY_CC2)
+		ret = typec_switch_set(ucpd->orient_sw, TYPEC_ORIENTATION_REVERSE);
+	else
+		ret = typec_switch_set(ucpd->orient_sw, TYPEC_ORIENTATION_NONE);
+	if (ret)
+		dev_err(ucpd->dev, "failed to set orientation %d\n", ret);
+
+	return ret;
+}
+
+static int tcpm_set_vconn(struct tcpc_dev *dev, bool on)
+{
+	struct ucpd *ucpd = tcpm_to_ucpd(dev);
+	DECLARE_BITMAP(bitmap, 2) = { };
+	enum typec_cc_status cc1, cc2;
+	int cr, sr;
+	int ret;
+
+	mutex_lock(&ucpd->lock);
+	if (ucpd->vconn_on == on) {
+		dev_err(ucpd->dev, "vconn is already %d\n", on);
+		goto done;
+	}
+
+	regmap_read(ucpd->regmap, UCPD_CR, &cr);
+	regmap_read(ucpd->regmap, UCPD_SR, &sr);
+
+	if (cr & UCPD_CR_CCENABLE_CC1)
+		cc1 = tcpm_decode_cc(cr, FIELD_GET(UCPD_SR_TYPEC_VSTATE_CC1, sr));
+	else
+		cc1 = TYPEC_CC_RA;
+
+	if (cr & UCPD_CR_CCENABLE_CC2)
+		cc2 = tcpm_decode_cc(cr, FIELD_GET(UCPD_SR_TYPEC_VSTATE_CC2, sr));
+	else
+		cc2 = TYPEC_CC_RA;
+
+	if (cc1 == TYPEC_CC_OPEN || cc1 == TYPEC_CC_RA) {
+		ucpd->cc_polarity = TYPEC_POLARITY_CC2;
+	} else if (cc2 == TYPEC_CC_OPEN || cc2 == TYPEC_CC_RA) {
+		ucpd->cc_polarity = TYPEC_POLARITY_CC1;
+	} else {
+		dev_err(ucpd->dev, "unknown CC polarity %d %d\n", cc1, cc2);
+		goto done;
+	}
+
+	if (on) {
+		regmap_update_bits(ucpd->regmap, UCPD_CR,
+				   UCPD_CR_CCENABLE_CC2 | UCPD_CR_CCENABLE_CC1,
+				   (ucpd->cc_polarity == TYPEC_POLARITY_CC1) ?
+				   UCPD_CR_CCENABLE_CC1 : UCPD_CR_CCENABLE_CC2);
+		bitmap_write(bitmap, (ucpd->cc_polarity == TYPEC_POLARITY_CC1) ? 2 : 1, 0, 2);
+		ret = gpiod_multi_set_value_cansleep(ucpd->vconn_gpios, bitmap);
+	} else {
+		bitmap_write(bitmap, 0, 0, 2);
+		ret = gpiod_multi_set_value_cansleep(ucpd->vconn_gpios, bitmap);
+		regmap_set_bits(ucpd->regmap, UCPD_CR,
+				UCPD_CR_CCENABLE_CC2 | UCPD_CR_CCENABLE_CC1);
+	}
+
+	ucpd->vconn_on = on;
+
+done:
+	mutex_unlock(&ucpd->lock);
+
+	return ret;
+}
+
+static int tcpm_set_vbus(struct tcpc_dev *dev, bool on, bool charge)
+{
+	struct ucpd *ucpd = tcpm_to_ucpd(dev);
+	int i, ret = 0;
+
+	mutex_lock(&ucpd->lock);
+	if (ucpd->vbus_on == on) {
+		dev_err(ucpd->dev, "vbus is already %d\n", on);
+		goto done;
+	}
+
+	if (on)
+		ret = regulator_enable(ucpd->vbus);
+	else
+		ret = regulator_disable(ucpd->vbus);
+
+	/* Wait a bit for the regulator and readback to sync. */
+	for (i = 0; i < 10; i++) {
+		if (regulator_is_enabled(ucpd->vbus) == gpiod_get_value_cansleep(ucpd->vbus_in))
+			break;
+		fsleep(100);
+	}
+	tcpm_vbus_change(ucpd->tcpm_port);
+
+	if (ret < 0) {
+		dev_err(ucpd->dev, "cannot set vbus regulator %d, ret=%d",
+			on, ret);
+		goto done;
+	}
+
+	ucpd->vbus_on = on;
+
+done:
+	mutex_unlock(&ucpd->lock);
+
+	return ret;
+}
+
+static int tcpm_set_pd_rx(struct tcpc_dev *dev, bool on)
+{
+	struct ucpd *ucpd = tcpm_to_ucpd(dev);
+	const u32 rx_imr_mask = UCPD_IMR_RXNEIE | UCPD_IMR_RXORDDETIE |
+				UCPD_IMR_RXHRSTDETIE | UCPD_IMR_RXOVRIE;
+	const u32 rx_icr_mask = UCPD_ICR_RXORDDETCF | UCPD_ICR_RXHRSTDETCF |
+				UCPD_ICR_RXOVRCF;
+
+	mutex_lock(&ucpd->lock);
+
+	if (on) {
+		/* Clear out stale bits, unmask and enable PHY PD RX */
+		regmap_set_bits(ucpd->regmap, UCPD_ICR, rx_icr_mask);
+		regmap_set_bits(ucpd->regmap, UCPD_IMR, rx_imr_mask);
+		regmap_set_bits(ucpd->regmap, UCPD_CR, UCPD_CR_PHYRXEN);
+	} else {
+		/* Disable PHY PD RX and clear out stale bits. */
+		regmap_clear_bits(ucpd->regmap, UCPD_CR, UCPD_CR_PHYRXEN);
+		regmap_clear_bits(ucpd->regmap, UCPD_IMR, rx_imr_mask);
+	}
+
+	mutex_unlock(&ucpd->lock);
+
+	return 0;
+}
+
+static int tcpm_set_roles(struct tcpc_dev *dev, bool attached,
+			  enum typec_role pwr, enum typec_data_role data)
+{
+	struct ucpd *ucpd = tcpm_to_ucpd(dev);
+
+	ucpd->pwr_role = pwr;
+	ucpd->data_role = data;
+
+	return 0;
+}
+
+static int ucpd_pd_get_ordset(enum tcpm_transmit_type type, u32 *ordset)
+{
+	if (type == TCPC_TX_SOP)
+		*ordset = UCPD_TX_ORDSETR_TXORDSET_SOP;
+	else if (type == TCPC_TX_SOP_PRIME)
+		*ordset = UCPD_TX_ORDSETR_TXORDSET_SOP_PRIME;
+	else if (type == TCPC_TX_SOP_PRIME_PRIME)
+		*ordset = UCPD_TX_ORDSETR_TXORDSET_SOP_PRIME_PRIME;
+	else if (type == TCPC_TX_HARD_RESET)
+		*ordset = UCPD_TX_ORDSETR_TXORDSET_HRST;
+	else if (type == TCPC_TX_CABLE_RESET)
+		*ordset = UCPD_TX_ORDSETR_TXORDSET_CRST;
+	else if (type == TCPC_TX_SOP_DEBUG_PRIME)
+		*ordset = UCPD_TX_ORDSETR_TXORDSET_SOP_PRIME_DEBUG;
+	else if (type == TCPC_TX_SOP_DEBUG_PRIME_PRIME)
+		*ordset = UCPD_TX_ORDSETR_TXORDSET_SOP_PRIME_PRIME_DEBUG;
+	else
+		return -EINVAL;
+
+	return 0;
+}
+
+static int ucpd_pd_send_message(struct ucpd *ucpd, enum tcpm_transmit_type type,
+				const struct pd_message *msg)
+{
+	const int len = (pd_header_cnt_le(msg->header) * 4) + 2;
+	u8 *mh = (u8 *)(&msg->header);
+	u8 *mp = (u8 *)(msg->payload);
+	int count = 0, ret;
+	u32 ordset, val;
+
+	regmap_update_bits(ucpd->regmap, UCPD_CR,
+			   UCPD_CR_TXHRST | UCPD_CR_TXSEND | UCPD_CR_TXMODE,
+			   UCPD_CR_TXMODE_PACKET);
+
+	ret = ucpd_pd_get_ordset(type, &ordset);
+	if (ret)
+		return ret;
+
+	regmap_write(ucpd->regmap, UCPD_TX_ORDSETR, ordset);
+	regmap_write(ucpd->regmap, UCPD_TX_PAYSZR, len);
+
+	if (ucpd->tx_pending) {
+		regmap_read_poll_timeout(ucpd->regmap, UCPD_SR,
+					       val, val & UCPD_SR_TXMSGSENT,
+					       0, 100000);
+		regmap_write(ucpd->regmap, UCPD_ICR, UCPD_ICR_TXMSGSENTCF);
+		ucpd->tx_pending = false;
+	}
+
+	/* Start the transfer */
+	regmap_set_bits(ucpd->regmap, UCPD_CR, UCPD_CR_TXSEND);
+
+	while (count < len) {
+		ret = regmap_read_poll_timeout(ucpd->regmap, UCPD_SR,
+					       val, val & UCPD_SR_TXIS,
+					       0, 1000000);
+		if (ret)
+			return ret;
+
+		/* First two bytes are header, rest is message */
+		if (count < 2)
+			regmap_write(ucpd->regmap, UCPD_TXDR, mh[count]);
+		else
+			regmap_write(ucpd->regmap, UCPD_TXDR, mp[count - 2]);
+
+		count++;
+	}
+
+	ucpd->tx_pending = true;
+
+	return 0;
+}
+
+static int tcpm_pd_transmit(struct tcpc_dev *dev, enum tcpm_transmit_type type,
+			    const struct pd_message *msg, unsigned int negotiated_rev)
+{
+	struct ucpd *ucpd = tcpm_to_ucpd(dev);
+	u32 ordset;
+	int ret;
+
+	mutex_lock(&ucpd->lock);
+	switch (type) {
+	case TCPC_TX_SOP:
+		ret = ucpd_pd_send_message(ucpd, type, msg);
+		break;
+	case TCPC_TX_HARD_RESET:
+		ret = ucpd_pd_get_ordset(type, &ordset);
+		if (ret)
+			return ret;
+
+		regmap_write(ucpd->regmap, UCPD_TX_ORDSETR, ordset);
+		regmap_update_bits(ucpd->regmap, UCPD_CR,
+				   UCPD_CR_TXHRST | UCPD_CR_TXSEND | UCPD_CR_TXMODE,
+				   UCPD_CR_TXHRST | UCPD_CR_TXMODE_PACKET);
+		break;
+	case TCPC_TX_CABLE_RESET:
+		ret = ucpd_pd_get_ordset(type, &ordset);
+		if (ret)
+			return ret;
+
+		regmap_write(ucpd->regmap, UCPD_TX_ORDSETR, ordset);
+		regmap_update_bits(ucpd->regmap, UCPD_CR,
+				   UCPD_CR_TXHRST | UCPD_CR_TXSEND | UCPD_CR_TXMODE,
+				   UCPD_CR_TXHRST | UCPD_CR_TXMODE_CABLE);
+		break;
+	case TCPC_TX_BIST_MODE_2:
+		regmap_update_bits(ucpd->regmap, UCPD_CR,
+				   UCPD_CR_TXHRST | UCPD_CR_TXSEND | UCPD_CR_TXMODE,
+				   UCPD_CR_TXHRST | UCPD_CR_TXMODE_BIST);
+		ret = 0;
+		break;
+	default:
+		dev_err(ucpd->dev, "type %d not supported", type);
+		ret = -EINVAL;
+	}
+	mutex_unlock(&ucpd->lock);
+
+	return ret;
+}
+
+static void ucpd_pd_read_message(struct ucpd *ucpd)
+{
+	const u32 mask = UCPD_SR_RXORDDET | UCPD_SR_RXNE | UCPD_SR_RXMSGEND;
+	struct pd_message pd_msg = { 0 };
+	u8 *mh = (u8 *)(&(pd_msg.header));
+	u8 *mp = (u8 *)(pd_msg.payload);
+	u32 val, rxdata;
+	int count = 0;
+	int len, ret;
+
+	/* RX */
+	for (;;) {
+		ret = regmap_read_poll_timeout(ucpd->regmap, UCPD_SR,
+					       val, val & mask,
+					       0, PD_T_RECEIVER_RESPONSE * 1000);
+		if (ret) {
+			dev_err(ucpd->dev, "PD RX timeout (SR 0x%x, ret=%d)\n",
+				val, ret);
+			return;
+		}
+
+		if (val & UCPD_SR_RXORDDET)
+			regmap_write(ucpd->regmap, UCPD_ICR, UCPD_ICR_RXORDDETCF);
+
+		if (val & UCPD_SR_RXNE) {
+			regmap_read(ucpd->regmap, UCPD_RXDR, &rxdata);
+
+			/* First two bytes are header, rest is message */
+			if (count < 2)
+				mh[count] = rxdata & 0xff;
+			else
+				mp[count - 2] = rxdata & 0xff;
+			count++;
+		}
+
+		if (val & UCPD_SR_RXMSGEND) {
+			regmap_write(ucpd->regmap, UCPD_ICR, UCPD_ICR_RXMSGENDCF);
+			break;
+		}
+	}
+
+	len = pd_header_cnt_le(pd_msg.header) * 4;
+
+	/* Add 4 to length to include the CRC */
+	if (len > PD_MAX_PAYLOAD * 4) {
+		dev_err(ucpd->dev, "PD RX message too long %d", len);
+		return;
+	}
+
+	/*
+	 * Check if we've read off a GoodCRC message. If so then indicate to
+	 * TCPM that the previous transmission has completed. Otherwise we pass
+	 * the received message over to TCPM for processing.
+	 *
+	 * We make this check here instead of basing the reporting decision on
+	 * the IRQ event type, as it's possible for the ucpd to report the
+	 * TX_SUCCESS and GCRCSENT events out of order on occasion, so we need
+	 * to check the message type to ensure correct reporting to TCPM.
+	 */
+	if (!len && (pd_header_type_le(pd_msg.header) == PD_CTRL_GOOD_CRC)) {
+		tcpm_pd_transmit_complete(ucpd->tcpm_port, TCPC_TX_SUCCESS);
+	} else {
+		struct pd_message msg = {
+			.header = PD_HEADER_LE(PD_CTRL_GOOD_CRC,
+					       ucpd->pwr_role,
+					       ucpd->data_role,
+					       pd_header_rev_le(pd_msg.header),
+					       pd_header_msgid_le(pd_msg.header),
+					       0),
+		};
+		ucpd_pd_send_message(ucpd, TCPC_TX_SOP, &msg);
+		tcpm_pd_receive(ucpd->tcpm_port, &pd_msg, TCPC_TX_SOP);
+	}
+}
+
+static irqreturn_t ucpd_irq(int irq, void *dev_id)
+{
+	struct ucpd *ucpd = dev_id;
+	int sr, mr;
+
+	mutex_lock(&ucpd->lock);
+
+	regmap_read(ucpd->regmap, UCPD_SR, &sr);
+	regmap_read(ucpd->regmap, UCPD_IMR, &mr);
+	sr &= mr;
+
+	if (sr & UCPD_SR_RXOVR) {
+		regmap_write(ucpd->regmap, UCPD_ICR, UCPD_ICR_RXOVRCF);
+		dev_warn_ratelimited(ucpd->dev, "PD RX overflow\n");
+	}
+
+	/* Received hard reset */
+	if (sr & UCPD_SR_RXHRSTDET) {
+		regmap_write(ucpd->regmap, UCPD_ICR, UCPD_ICR_RXHRSTDETCF);
+		tcpm_pd_hard_reset(ucpd->tcpm_port);
+	}
+
+	/* Received ordered set received or data, RX PD message */
+	if (sr & (UCPD_SR_RXORDDET | UCPD_SR_RXNE))
+		ucpd_pd_read_message(ucpd);
+
+	/* Handle CC line changes only for the enabled CC lines */
+	if (sr & (UCPD_SR_TYPECEVT2 | UCPD_SR_TYPECEVT1)) {
+		regmap_write(ucpd->regmap, UCPD_ICR,
+			     sr & (UCPD_ICR_TYPECEVT2CF | UCPD_ICR_TYPECEVT1CF));
+
+		tcpm_cc_change(ucpd->tcpm_port);
+		tcpm_vbus_change(ucpd->tcpm_port);
+	}
+
+	mutex_unlock(&ucpd->lock);
+	return IRQ_HANDLED;
+}
+
+static int ucpd_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct clk_bulk_data *clk;
+	void __iomem *mmio;
+	struct ucpd *ucpd;
+	int irq, ret;
+
+	ucpd = devm_kzalloc(dev, sizeof(*ucpd), GFP_KERNEL);
+	if (!ucpd)
+		return -ENOMEM;
+
+	ret = nvmem_cell_read_variable_le_u32(dev, "trim", &ucpd->trim);
+	if (ret)
+		return dev_err_probe(dev, ret, "Failed to access nvmem\n");
+
+	mmio = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(mmio))
+		return PTR_ERR(mmio);
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0)
+		return irq;
+
+	ucpd->dev = dev;
+	mutex_init(&ucpd->lock);
+
+	ret = devm_regulator_get_enable(ucpd->dev, "vdd33");
+	if (ret)
+		return ret;
+
+	ucpd->vconn_gpios = devm_gpiod_get_array(dev, "vconn", GPIOD_OUT_LOW);
+	if (IS_ERR(ucpd->vconn_gpios))
+		return PTR_ERR(ucpd->vconn_gpios);
+
+	if (ucpd->vconn_gpios->ndescs != 2)
+		return -EINVAL;
+
+	ucpd->phy_reset = devm_reset_control_get_exclusive(dev, "phy");
+	if (IS_ERR(ucpd->phy_reset))
+		return dev_err_probe(dev, PTR_ERR(ucpd->phy_reset),
+				     "Failed to get PHY reset\n");
+
+	ret = devm_clk_bulk_get_all_enabled(dev, &clk);
+	if (ret < 0)
+		return dev_err_probe(dev, ret, "Failed to get clocks\n");
+
+	ucpd->regmap = devm_regmap_init_mmio(dev, mmio, &ucpd_regmap_config);
+	if (IS_ERR(ucpd->regmap))
+		return dev_err_probe(dev, PTR_ERR(ucpd->regmap),
+				     "Failed to initialize regmap\n");
+
+	/* Set TCPC callbacks */
+	ucpd->tcpc_dev.init = tcpm_init;
+	ucpd->tcpc_dev.get_vbus = tcpm_get_vbus;
+	ucpd->tcpc_dev.set_vbus = tcpm_set_vbus;
+	ucpd->tcpc_dev.set_vconn = tcpm_set_vconn;
+	ucpd->tcpc_dev.set_cc = tcpm_set_cc;
+	ucpd->tcpc_dev.get_cc = tcpm_get_cc;
+	ucpd->tcpc_dev.set_polarity = tcpm_set_polarity;
+	ucpd->tcpc_dev.set_pd_rx = tcpm_set_pd_rx;
+	ucpd->tcpc_dev.set_roles = tcpm_set_roles;
+	ucpd->tcpc_dev.pd_transmit = tcpm_pd_transmit;
+
+	ucpd->tcpc_dev.fwnode = device_get_named_child_node(dev, "connector");
+	if (IS_ERR(ucpd->tcpc_dev.fwnode))
+		return PTR_ERR(ucpd->tcpc_dev.fwnode);
+
+	platform_set_drvdata(pdev, ucpd);
+
+	/* Vbus input GPIO is on the connector subnode. */
+	ucpd->vbus_in = devm_fwnode_gpiod_get(dev, ucpd->tcpc_dev.fwnode,
+					      "vbus", GPIOD_IN, "vbus");
+	if (IS_ERR(ucpd->vbus_in)) {
+		ret = PTR_ERR(ucpd->vbus_in);
+		goto tcpm_put_fwnode;
+	}
+
+	ucpd->vbus = devm_regulator_get(ucpd->dev, "vbus");
+	if (IS_ERR(ucpd->vbus)) {
+		ret = PTR_ERR(ucpd->vbus);
+		goto tcpm_put_fwnode;
+	}
+
+	ucpd->orient_sw = fwnode_typec_switch_get(ucpd->tcpc_dev.fwnode);
+	if (IS_ERR(ucpd->orient_sw)) {
+		return dev_err_probe(dev, PTR_ERR(ucpd->orient_sw),
+				     "Failed to get orientation switch\n");
+	}
+
+	ret = devm_request_threaded_irq(dev, irq, NULL, ucpd_irq,
+					IRQF_ONESHOT, "ucpd", ucpd);
+	if (ret < 0)
+		goto tcpm_put_fwnode;
+
+	ucpd->tcpm_port = tcpm_register_port(dev, &ucpd->tcpc_dev);
+	if (IS_ERR(ucpd->tcpm_port)) {
+		ret = PTR_ERR(ucpd->tcpm_port);
+		goto tcpm_put_fwnode;
+	}
+
+	return ret;
+
+tcpm_put_fwnode:
+	fwnode_handle_put(ucpd->tcpc_dev.fwnode);
+
+	return ret;
+}
+
+static void ucpd_remove(struct platform_device *pdev)
+{
+	struct ucpd *ucpd = platform_get_drvdata(pdev);
+
+	tcpm_unregister_port(ucpd->tcpm_port);
+	fwnode_handle_put(ucpd->tcpc_dev.fwnode);
+}
+
+static const struct of_device_id ucpd_dt_match[] = {
+	{ .compatible = "st,stm32mp25-ucpd" },
+	{},
+};
+MODULE_DEVICE_TABLE(of, ucpd_dt_match);
+
+static struct platform_driver ucpd_driver = {
+	.probe = ucpd_probe,
+	.remove = ucpd_remove,
+	.driver = {
+		.name = "ucpd",
+		.of_match_table = ucpd_dt_match,
+	},
+};
+module_platform_driver(ucpd_driver);
+
+MODULE_AUTHOR("Marek Vasut");
+MODULE_DESCRIPTION("ST UCPD Type-C Chip Driver");
+MODULE_LICENSE("GPL");
-- 
2.53.0


  reply	other threads:[~2026-08-19 16:34 UTC|newest]

Thread overview: 7+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-19 16:33 [PATCH 1/4] dt-bindings: usb: Document ST UCPD Marek Vasut
2026-08-19 16:33 ` Marek Vasut [this message]
2026-08-19 16:47   ` [PATCH 2/4] usb: typec: ucpd: Add ST UCPD driver sashiko-bot
2026-08-19 16:33 ` [PATCH 3/4] arm64: dts: st: Add UCPD node on stm32mp231 Marek Vasut
2026-08-19 16:42   ` sashiko-bot
2026-08-19 16:33 ` [PATCH 4/4] arm64: dts: st: Add UCPD node on stm32mp251 Marek Vasut
2026-08-19 16:44 ` [PATCH 1/4] dt-bindings: usb: Document ST UCPD sashiko-bot

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260819163410.119755-2-marex@nabladev.com \
    --to=marex@nabladev.com \
    --cc=alexandre.torgue@foss.st.com \
    --cc=conor+dt@kernel.org \
    --cc=devicetree@vger.kernel.org \
    --cc=fabrice.gasnier@foss.st.com \
    --cc=gregkh@linuxfoundation.org \
    --cc=heikki.krogerus@linux.intel.com \
    --cc=kernel@dh-electronics.com \
    --cc=krzk+dt@kernel.org \
    --cc=linux-arm-kernel@lists.infradead.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-stm32@st-md-mailman.stormreply.com \
    --cc=linux-usb@vger.kernel.org \
    --cc=mcoquelin.stm32@gmail.com \
    --cc=robh@kernel.org \
    --cc=sebastian.reichel@collabora.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.