mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: David Heidelberg via B4 Relay <devnull+david.ixit.cz@kernel.org>
To: Alexey Minnekhanov <alexeymin@minlexx.ru>,
	 Bryan O'Donoghue <bryan.odonoghue@linaro.org>,
	 Heikki Krogerus <heikki.krogerus@linux.intel.com>,
	 Greg Kroah-Hartman <gregkh@linuxfoundation.org>,
	 Liam Girdwood <lgirdwood@gmail.com>,
	Mark Brown <broonie@kernel.org>,
	 Bjorn Andersson <andersson@kernel.org>,
	 Konrad Dybcio <konradybcio@kernel.org>,
	Rob Herring <robh@kernel.org>,
	 Krzysztof Kozlowski <krzk+dt@kernel.org>,
	 Conor Dooley <conor+dt@kernel.org>,
	 Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>,
	 Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>,
	 Lee Jones <lee@kernel.org>, Stephen Boyd <sboyd@kernel.org>
Cc: linux-arm-msm@vger.kernel.org, linux-usb@vger.kernel.org,
	 linux-kernel@vger.kernel.org, devicetree@vger.kernel.org,
	 phone-devel@vger.kernel.org, mfd@lists.linux.dev,
	 David Heidelberg <david@ixit.cz>
Subject: [PATCH RFC 06/12] usb: typec: qcom: Add gen1 Type-C port support
Date: Sat, 26 Sep 2026 14:40:25 +0200	[thread overview]
Message-ID: <20260926-typec-v1-6-31adc19f32c6@ixit.cz> (raw)
In-Reply-To: <20260926-typec-v1-0-31adc19f32c6@ixit.cz>

From: David Heidelberg <david@ixit.cz>

Add a port backend for the first generation of the Qualcomm PMIC Type-C
block, found on PM660 and PMI8998.

On gen1 the Type-C CC logic is part of the charger's USBIN peripheral
(base 0x1300) instead of the standalone Type-C peripheral used by
PM8150B and later PMICs. All Type-C events (CC state change,
tCCDebounce done, VBUS change and error) are signalled through a single
aggregate "type-c-change" interrupt, the handler re-reads TYPEC_STATUS_4
to find out what changed. The PD PHY is register compatible with PM8150B
and is reused as is.

Differences to the PM8150B backend:
 - get_cc() returns -EBUSY until the hardware reports tCCDebounce done,
   instead of using a software debounce.
 - As a source, only the default and 1.5A Rp can be advertised, 3.0A
   requests are advertised as 1.5A.
 - The PBS workaround of the downstream SMB2 driver (TM_IO_DTEST4_SEL)
   is applied on every power role change.

VBUS sourcing is optional and only used when the connector provides
a vbus-supply, as the charger doesn't expose a VBUS regulator yet.

Assisted-by: LLM
Co-developed-by: Alexey Minnekhanov <alexeymin@minlexx.ru>
Signed-off-by: Alexey Minnekhanov <alexeymin@minlexx.ru>
Signed-off-by: David Heidelberg <david@ixit.cz>
---
 drivers/usb/typec/tcpm/qcom/Makefile               |   1 +
 drivers/usb/typec/tcpm/qcom/qcom_pmic_typec.c      |   9 +
 .../typec/tcpm/qcom/qcom_pmic_typec_port_gen1.c    | 605 +++++++++++++++++++++
 .../typec/tcpm/qcom/qcom_pmic_typec_port_gen1.h    |  15 +
 4 files changed, 630 insertions(+)

diff --git a/drivers/usb/typec/tcpm/qcom/Makefile b/drivers/usb/typec/tcpm/qcom/Makefile
index cc23042b94878..b8b9350184c19 100644
--- a/drivers/usb/typec/tcpm/qcom/Makefile
+++ b/drivers/usb/typec/tcpm/qcom/Makefile
@@ -1,7 +1,8 @@
 # SPDX-License-Identifier: GPL-2.0
 #
 obj-$(CONFIG_TYPEC_QCOM_PMIC)		+= qcom_pmic_tcpm.o
 qcom_pmic_tcpm-y			+= qcom_pmic_typec.o \
 					   qcom_pmic_typec_port.o \
+					   qcom_pmic_typec_port_gen1.o \
 					   qcom_pmic_typec_pdphy.o \
 					   qcom_pmic_typec_pdphy_stub.o \
diff --git a/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec.c b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec.c
index 41aaae6bb3724..ea1d5b0927436 100644
--- a/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec.c
+++ b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec.c
@@ -12,16 +12,17 @@
 #include <linux/slab.h>
 #include <linux/usb/tcpm.h>
 
 #include <drm/bridge/aux-bridge.h>
 
 #include "qcom_pmic_typec.h"
 #include "qcom_pmic_typec_pdphy.h"
 #include "qcom_pmic_typec_port.h"
+#include "qcom_pmic_typec_port_gen1.h"
 
 typedef int (*pmic_typec_port_probe_fn)(struct platform_device *pdev,
 					struct pmic_typec *tcpm,
 					const struct pmic_typec_port_resources *res,
 					struct regmap *regmap, u32 base);
 
 struct pmic_typec_resources {
 	const struct pmic_typec_pdphy_resources	*pdphy_res;
@@ -134,31 +135,39 @@ static void qcom_pmic_typec_remove(struct platform_device *pdev)
 	struct pmic_typec *tcpm = platform_get_drvdata(pdev);
 
 	tcpm->pdphy_stop(tcpm);
 	tcpm->port_stop(tcpm);
 	tcpm_unregister_port(tcpm->tcpm_port);
 	fwnode_handle_put(tcpm->tcpc.fwnode);
 }
 
+static const struct pmic_typec_resources gen1_typec_res = {
+	.pdphy_res = &pm8150b_pdphy_res,
+	.port_res = &gen1_port_res,
+	.port_probe = qcom_pmic_typec_gen1_port_probe,
+};
+
 static const struct pmic_typec_resources pm8150b_typec_res = {
 	.pdphy_res = &pm8150b_pdphy_res,
 	.port_res = &pm8150b_port_res,
 	.port_probe = qcom_pmic_typec_port_probe,
 };
 
 static const struct pmic_typec_resources pmi632_typec_res = {
 	/* PD PHY not present */
 	.port_res = &pm8150b_port_res,
 	.port_probe = qcom_pmic_typec_port_probe,
 };
 
 static const struct of_device_id qcom_pmic_typec_table[] = {
+	{ .compatible = "qcom,pm660-typec", .data = &gen1_typec_res },
 	{ .compatible = "qcom,pm8150b-typec", .data = &pm8150b_typec_res },
 	{ .compatible = "qcom,pmi632-typec", .data = &pmi632_typec_res },
+	{ .compatible = "qcom,pmi8998-typec", .data = &gen1_typec_res },
 	{ }
 };
 MODULE_DEVICE_TABLE(of, qcom_pmic_typec_table);
 
 static struct platform_driver qcom_pmic_typec_driver = {
 	.driver = {
 		.name = "qcom,pmic-typec",
 		.of_match_table = qcom_pmic_typec_table,
diff --git a/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_port_gen1.c b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_port_gen1.c
new file mode 100644
index 0000000000000..b4e3e49664f9d
--- /dev/null
+++ b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_port_gen1.c
@@ -0,0 +1,605 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Qualcomm Type-C port backend (1st gen)
+ *
+ * GEN1 predates the standalone PM8150B-style Type-C register block.
+ * Its CC/role controls live in the USBIN peripheral, while the PD PHY
+ * is a separate PMIC peripheral.
+ */
+
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+#include <linux/usb/tcpm.h>
+
+#include "qcom_pmic_typec.h"
+#include "qcom_pmic_typec_port.h"
+#include "qcom_pmic_typec_port_gen1.h"
+
+/* USBIN Type-C status */
+#define TYPEC_STATUS_1_REG				0x0b
+#define  UFP_TYPEC_MASK					GENMASK(7, 5)
+#define  UFP_TYPEC_RDSTD				BIT(7)
+#define  UFP_TYPEC_RD1P5				BIT(6)
+#define  UFP_TYPEC_RD3P0				BIT(5)
+
+#define TYPEC_STATUS_2_REG				0x0c
+#define  DFP_TYPEC_MASK					GENMASK(3, 0)
+#define  DFP_RD_OPEN					BIT(3)
+#define  DFP_RD_RA_VCONN				BIT(2)
+#define  DFP_RD_RD					BIT(1)
+#define  DFP_RA_RA					BIT(0)
+
+#define TYPEC_STATUS_4_REG				0x0e
+#define  UFP_DFP_MODE_STATUS				BIT(7)
+#define  TYPEC_VBUS_STATUS				BIT(6)
+#define  TYPEC_VBUS_ERROR				BIT(5)
+#define  TYPEC_DEBOUNCE_DONE				BIT(4)
+#define  CC_ORIENTATION					BIT(1)
+#define  CC_ATTACHED					BIT(0)
+
+/* USBIN Type-C configuration */
+#define TYPEC_CFG_REG					0x58
+#define  TYPEC_OR_U_USB					BIT(0)
+
+#define TYPEC_CFG_2_REG					0x59
+#define  EN_TRY_SOURCE_MODE				BIT(3)
+#define  EN_80UA_180UA_CUR_SOURCE			BIT(0)
+
+#define TYPEC_CFG_3_REG					0x5a
+#define  EN_TRYSINK_MODE				BIT(2)
+
+#define TYPEC_INTRPT_ENB_REG				0x67
+#define  VBUS_ERROR_INT_EN				BIT(5)
+#define  DEBOUNCE_DONE_INT_EN				BIT(3)
+#define  CCSTATE_CHANGE_INT_EN				BIT(2)
+#define  VBUS_DEASSERT_INT_EN				BIT(1)
+#define  VBUS_ASSERT_INT_EN				BIT(0)
+
+#define TYPEC_SW_CTRL_REG				0x68
+#define  VCONN_EN_ORIENTATION				BIT(6)
+#define  VCONN_EN_SRC					BIT(4)
+#define  VCONN_EN_VALUE					BIT(3)
+#define  POWER_ROLE_CMD_MASK				GENMASK(2, 0)
+#define  UFP_EN_CMD					BIT(2)
+#define  DFP_EN_CMD					BIT(1)
+#define  TYPEC_DISABLE_CMD				BIT(0)
+
+/* GEN1 TYPEC_PBS_WA_BIT workaround used by the downstream SMB2 driver. */
+#define MISC_BASE_OFFSET				0x300
+#define TM_IO_DTEST4_SEL				0xe9
+#define PBS_CRUDE_SENSOR_ENABLE				0xa5
+
+/* Interrupt numbers */
+#define PMIC_TYPEC_CHANGE_IRQ				0x0
+
+struct gen1_typec_port {
+	struct device		*dev;
+	struct tcpm_port	*tcpm_port;
+	struct regmap		*regmap;
+	u32			base;
+	int			irq;
+
+	struct regulator	*vbus;
+	bool			vbus_enabled;
+	bool			vbus_present;
+	bool			vbus_valid;
+	struct mutex		lock; /* serializes register RMW and VBUS state */
+};
+
+const struct pmic_typec_port_resources gen1_port_res = {
+	.nr_irqs = 1,
+	.irq_params = {
+		{
+			.irq_name = "type-c-change",
+			.virq = PMIC_TYPEC_CHANGE_IRQ,
+		},
+	},
+};
+
+/*
+ * Failure of this workaround write is non-fatal and
+ * we can continue with the role transition.
+ */
+static void gen1_typec_pbs_wa(struct gen1_typec_port *port, bool sink)
+{
+	unsigned int val = sink ? 0 : PBS_CRUDE_SENSOR_ENABLE;
+	int ret;
+
+	ret = regmap_write(port->regmap, port->base +
+			   MISC_BASE_OFFSET + TM_IO_DTEST4_SEL, val);
+	if (!ret)
+		return;
+
+	dev_warn(port->dev,
+		 "failed to update GEN1 Type-C PBS workaround: %d\n",
+		 ret);
+}
+
+static int gen1_typec_set_power_role(struct gen1_typec_port *port,
+				     unsigned int role)
+{
+	int ret;
+
+	gen1_typec_pbs_wa(port, role == UFP_EN_CMD);
+
+	ret = regmap_update_bits(port->regmap,
+				 port->base + TYPEC_SW_CTRL_REG,
+				 POWER_ROLE_CMD_MASK, role);
+	if (ret)
+		dev_err(port->dev, "failed to set Type-C power role: %d\n", ret);
+
+	return ret;
+}
+
+static int gen1_typec_get_vbus(struct tcpc_dev *tcpc)
+{
+	struct pmic_typec *tcpm = tcpc_to_tcpm(tcpc);
+	struct gen1_typec_port *port = tcpm->pmic_typec_port;
+	unsigned int status;
+	int ret;
+
+	mutex_lock(&port->lock);
+	ret = regmap_read(port->regmap, port->base +
+			  TYPEC_STATUS_4_REG, &status);
+	if (ret)
+		ret = port->vbus_enabled;
+	else
+		ret = port->vbus_enabled || !!(status & TYPEC_VBUS_STATUS);
+	mutex_unlock(&port->lock);
+
+	return ret;
+}
+
+static int gen1_typec_set_vbus(struct tcpc_dev *tcpc, bool on, bool sink)
+{
+	struct pmic_typec *tcpm = tcpc_to_tcpm(tcpc);
+	struct gen1_typec_port *port = tcpm->pmic_typec_port;
+	bool changed = false;
+	int ret = 0;
+
+	if (!port->vbus)
+		return 0;
+
+	mutex_lock(&port->lock);
+
+	if (port->vbus_enabled == on)
+		goto out;
+
+	if (on)
+		ret = regulator_enable(port->vbus);
+	else
+		ret = regulator_disable(port->vbus);
+
+	if (!ret) {
+		port->vbus_enabled = on;
+		changed = true;
+	}
+
+out:
+	mutex_unlock(&port->lock);
+
+	/*
+	 * set_vbus() may run during tcpm_register_port() before port_start()
+	 * provides tcpm_port. Once registered, notify TCPM whenever the
+	 * source regulator actually changes state. get_vbus() also accounts
+	 * for vbus_enabled when GEN1 does not reflect sourced VBUS in
+	 * TYPEC_VBUS_STATUS.
+	 */
+	if (changed && port->tcpm_port)
+		tcpm_vbus_change(port->tcpm_port);
+
+	return ret;
+}
+
+static int gen1_typec_get_cc(struct tcpc_dev *tcpc,
+			     enum typec_cc_status *cc1,
+			     enum typec_cc_status *cc2)
+{
+	struct pmic_typec *tcpm = tcpc_to_tcpm(tcpc);
+	struct gen1_typec_port *port = tcpm->pmic_typec_port;
+	enum typec_cc_status active = TYPEC_CC_OPEN;
+	unsigned int status1, status2, status4;
+	bool orientation_cc2;
+	int ret;
+
+	*cc1 = TYPEC_CC_OPEN;
+	*cc2 = TYPEC_CC_OPEN;
+
+	mutex_lock(&port->lock);
+
+	ret = regmap_read(port->regmap, port->base +
+			  TYPEC_STATUS_4_REG, &status4);
+	if (ret)
+		goto out;
+
+	if (!(status4 & CC_ATTACHED))
+		goto out;
+
+	/*
+	 * CC is not stable until tCCDebounce has elapsed. TCPM ignores
+	 * -EBUSY and the DEBOUNCE_DONE interrupt triggers a new read.
+	 */
+	if (!(status4 & TYPEC_DEBOUNCE_DONE)) {
+		ret = -EBUSY;
+		goto out;
+	}
+
+	ret = regmap_read(port->regmap, port->base +
+			  TYPEC_STATUS_1_REG, &status1);
+	if (ret)
+		goto out;
+
+	ret = regmap_read(port->regmap, port->base +
+			  TYPEC_STATUS_2_REG, &status2);
+	if (ret)
+		goto out;
+
+	orientation_cc2 = !!(status4 & CC_ORIENTATION);
+
+	if (status4 & UFP_DFP_MODE_STATUS) {
+		/* Local port is DFP/source; decode the partner's Rd/Ra. */
+		switch (status2 & DFP_TYPEC_MASK) {
+		case DFP_RA_RA:
+			*cc1 = TYPEC_CC_RA;
+			*cc2 = TYPEC_CC_RA;
+			goto out;
+		case DFP_RD_RD:
+			*cc1 = TYPEC_CC_RD;
+			*cc2 = TYPEC_CC_RD;
+			goto out;
+		case DFP_RD_RA_VCONN:
+			active = TYPEC_CC_RD;
+			*cc1 = TYPEC_CC_RA;
+			*cc2 = TYPEC_CC_RA;
+			break;
+		case DFP_RD_OPEN:
+			active = TYPEC_CC_RD;
+			break;
+		default:
+			dev_dbg(port->dev, "unhandled GEN1 DFP status %#x\n",
+				status2);
+			goto out;
+		}
+	} else {
+		/* Local port is UFP/sink; decode the partner's advertised Rp. */
+		switch (status1 & UFP_TYPEC_MASK) {
+		case UFP_TYPEC_RDSTD:
+			active = TYPEC_CC_RP_DEF;
+			break;
+		case UFP_TYPEC_RD1P5:
+			active = TYPEC_CC_RP_1_5;
+			break;
+		case UFP_TYPEC_RD3P0:
+			active = TYPEC_CC_RP_3_0;
+			break;
+		default:
+			dev_dbg(port->dev, "unhandled GEN1 UFP status %#x\n",
+				status1);
+			goto out;
+		}
+	}
+
+	if (orientation_cc2)
+		*cc2 = active;
+	else
+		*cc1 = active;
+
+out:
+	mutex_unlock(&port->lock);
+	return ret;
+}
+
+static int gen1_typec_set_cc(struct tcpc_dev *tcpc,
+			     enum typec_cc_status cc)
+{
+	struct pmic_typec *tcpm = tcpc_to_tcpm(tcpc);
+	struct gen1_typec_port *port = tcpm->pmic_typec_port;
+	unsigned int role;
+	unsigned int rp_current = 0;
+	int ret;
+
+	switch (cc) {
+	case TYPEC_CC_OPEN:
+		role = TYPEC_DISABLE_CMD;
+		break;
+	case TYPEC_CC_RD:
+		role = UFP_EN_CMD;
+		break;
+	case TYPEC_CC_RP_DEF:
+		role = DFP_EN_CMD;
+		rp_current = 0;
+		break;
+	case TYPEC_CC_RP_1_5:
+	case TYPEC_CC_RP_3_0:
+		/*
+		 * GEN1 can only source 80 uA (default) or 180 uA (1.5 A).
+		 * Advertise 1.5 A for 3.0 A requests, TCPM ignores set_cc()
+		 * errors and would otherwise keep a stale Rp.
+		 */
+		role = DFP_EN_CMD;
+		rp_current = EN_80UA_180UA_CUR_SOURCE;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	mutex_lock(&port->lock);
+
+	if (role == DFP_EN_CMD) {
+		ret = regmap_update_bits(port->regmap,
+					 port->base + TYPEC_CFG_2_REG,
+					 EN_80UA_180UA_CUR_SOURCE,
+					 rp_current);
+		if (ret)
+			goto out;
+	}
+
+	ret = gen1_typec_set_power_role(port, role);
+
+out:
+	mutex_unlock(&port->lock);
+	return ret;
+}
+
+static int gen1_typec_set_polarity(struct tcpc_dev *tcpc,
+				   enum typec_cc_polarity polarity)
+{
+	/* USB PHY orientation is handled outside this PMIC Type-C block. */
+	return 0;
+}
+
+static int gen1_typec_set_vconn(struct tcpc_dev *tcpc, bool on)
+{
+	struct pmic_typec *tcpm = tcpc_to_tcpm(tcpc);
+	struct gen1_typec_port *port = tcpm->pmic_typec_port;
+	unsigned int status4, orientation, mask, val;
+	int ret;
+
+	mutex_lock(&port->lock);
+
+	ret = regmap_read(port->regmap, port->base +
+			  TYPEC_STATUS_4_REG, &status4);
+	if (ret)
+		goto out;
+
+	/* VCONN is driven on the inactive CC pin. */
+	orientation = (status4 & CC_ORIENTATION) ?
+		      0 : VCONN_EN_ORIENTATION;
+
+	if (on) {
+		mask = VCONN_EN_ORIENTATION | VCONN_EN_VALUE;
+		val = orientation | VCONN_EN_VALUE;
+	} else {
+		mask = VCONN_EN_VALUE;
+		val = 0;
+	}
+
+	ret = regmap_update_bits(port->regmap,
+				 port->base + TYPEC_SW_CTRL_REG,
+				 mask, val);
+
+out:
+	mutex_unlock(&port->lock);
+	return ret;
+}
+
+static int gen1_typec_start_toggling(struct tcpc_dev *tcpc,
+				     enum typec_port_type port_type,
+				     enum typec_cc_status cc)
+{
+	struct pmic_typec *tcpm = tcpc_to_tcpm(tcpc);
+	struct gen1_typec_port *port = tcpm->pmic_typec_port;
+	unsigned int role;
+	unsigned int try_src = 0;
+	unsigned int try_snk = 0;
+	int ret;
+
+	switch (port_type) {
+	case TYPEC_PORT_SRC:
+		role = DFP_EN_CMD;
+		break;
+	case TYPEC_PORT_SNK:
+		role = UFP_EN_CMD;
+		break;
+	case TYPEC_PORT_DRP:
+		role = 0;
+		/*
+		 * Like the PM8150B backend, keep hardware Try.SNK enabled
+		 * while TCPM owns the policy state machine.
+		 */
+		try_snk = EN_TRYSINK_MODE;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	mutex_lock(&port->lock);
+
+	ret = regmap_update_bits(port->regmap,
+				 port->base + TYPEC_CFG_2_REG,
+				 EN_TRY_SOURCE_MODE, try_src);
+	if (ret)
+		goto out;
+
+	ret = regmap_update_bits(port->regmap,
+				 port->base + TYPEC_CFG_3_REG,
+				 EN_TRYSINK_MODE, try_snk);
+	if (ret)
+		goto out;
+
+	/* Disable first so the state machine restarts toggling. */
+	ret = gen1_typec_set_power_role(port, TYPEC_DISABLE_CMD);
+	if (ret)
+		goto out;
+
+	ret = gen1_typec_set_power_role(port, role);
+
+out:
+	mutex_unlock(&port->lock);
+	return ret;
+}
+
+static irqreturn_t gen1_typec_irq(int irq, void *data)
+{
+	struct gen1_typec_port *port = data;
+	unsigned int status4;
+	bool vbus;
+	bool vbus_changed = false;
+
+	/*
+	 * GEN1 exposes a single aggregate Type-C change interrupt. Always
+	 * notify TCPM of CC changes. Only generate a VBUS event when the
+	 * hardware VBUS state changes, or on the first observation so TCPM
+	 * can establish the initial vSafe0V state.
+	 */
+	mutex_lock(&port->lock);
+
+	if (!regmap_read(port->regmap, port->base +
+			 TYPEC_STATUS_4_REG, &status4)) {
+		if (status4 & TYPEC_VBUS_ERROR)
+			dev_warn_ratelimited(port->dev, "Type-C VBUS error\n");
+
+		vbus = !!(status4 & TYPEC_VBUS_STATUS);
+
+		if (!port->vbus_valid || vbus != port->vbus_present) {
+			port->vbus_present = vbus;
+			port->vbus_valid = true;
+			vbus_changed = true;
+		}
+	}
+
+	mutex_unlock(&port->lock);
+
+	if (port->tcpm_port) {
+		if (vbus_changed)
+			tcpm_vbus_change(port->tcpm_port);
+
+		tcpm_cc_change(port->tcpm_port);
+	}
+
+	return IRQ_HANDLED;
+}
+
+static int gen1_typec_port_start(struct pmic_typec *tcpm,
+				 struct tcpm_port *tcpm_port)
+{
+	struct gen1_typec_port *port = tcpm->pmic_typec_port;
+	unsigned int irq_mask;
+	int ret;
+
+	/*
+	 * TYPE_C_OR_U_USB == 0 selects the Type-C state machine on GEN1.
+	 */
+	ret = regmap_update_bits(port->regmap,
+				 port->base + TYPEC_CFG_REG,
+				 TYPEC_OR_U_USB, 0);
+	if (ret)
+		return ret;
+
+	/*
+	 * GEN1 exposes a single aggregate type-c-change interrupt, the
+	 * handler re-reads TYPEC_STATUS_4 to find out what changed.
+	 */
+	irq_mask = CCSTATE_CHANGE_INT_EN | DEBOUNCE_DONE_INT_EN |
+		   VBUS_ASSERT_INT_EN | VBUS_DEASSERT_INT_EN |
+		   VBUS_ERROR_INT_EN;
+
+	ret = regmap_write(port->regmap,
+			   port->base + TYPEC_INTRPT_ENB_REG,
+			   irq_mask);
+	if (ret)
+		return ret;
+
+	/* Select software control for VCONN, initially disabled. */
+	ret = regmap_update_bits(port->regmap,
+				 port->base + TYPEC_SW_CTRL_REG,
+				 VCONN_EN_SRC | VCONN_EN_VALUE,
+				 VCONN_EN_SRC);
+	if (ret)
+		return ret;
+
+	port->tcpm_port = tcpm_port;
+	enable_irq(port->irq);
+
+	return 0;
+}
+
+static void gen1_typec_port_stop(struct pmic_typec *tcpm)
+{
+	struct gen1_typec_port *port = tcpm->pmic_typec_port;
+
+	disable_irq(port->irq);
+	port->tcpm_port = NULL;
+}
+
+int qcom_pmic_typec_gen1_port_probe(struct platform_device *pdev,
+				    struct pmic_typec *tcpm,
+				    const struct pmic_typec_port_resources *res,
+				    struct regmap *regmap,
+				    u32 base)
+{
+	struct device *dev = &pdev->dev;
+	struct gen1_typec_port *port;
+	struct fwnode_handle *connector;
+	int ret;
+
+	if (!res->nr_irqs || res->nr_irqs > PMIC_TYPEC_MAX_IRQS)
+		return -EINVAL;
+
+	port = devm_kzalloc(dev, sizeof(*port), GFP_KERNEL);
+	if (!port)
+		return -ENOMEM;
+
+	connector = device_get_named_child_node(dev, "connector");
+	if (!connector)
+		return -EINVAL;
+
+	port->vbus = devm_of_regulator_get_optional(dev,
+						    to_of_node(connector),
+						    "vbus");
+	fwnode_handle_put(connector);
+	if (IS_ERR(port->vbus)) {
+		if (PTR_ERR(port->vbus) != -ENODEV)
+			return PTR_ERR(port->vbus);
+		port->vbus = NULL;
+	}
+
+	port->irq = platform_get_irq_byname(pdev,
+					    res->irq_params[0].irq_name);
+	if (port->irq < 0)
+		return port->irq;
+
+	port->dev = dev;
+	port->regmap = regmap;
+	port->base = base;
+	mutex_init(&port->lock);
+
+	ret = devm_request_threaded_irq(dev, port->irq, NULL,
+					gen1_typec_irq,
+					IRQF_ONESHOT | IRQF_NO_AUTOEN,
+					res->irq_params[0].irq_name, port);
+	if (ret)
+		return ret;
+
+	tcpm->pmic_typec_port = port;
+
+	tcpm->tcpc.get_vbus = gen1_typec_get_vbus;
+	tcpm->tcpc.set_vbus = gen1_typec_set_vbus;
+	tcpm->tcpc.get_cc = gen1_typec_get_cc;
+	tcpm->tcpc.set_cc = gen1_typec_set_cc;
+	tcpm->tcpc.set_polarity = gen1_typec_set_polarity;
+	tcpm->tcpc.set_vconn = gen1_typec_set_vconn;
+	tcpm->tcpc.start_toggling = gen1_typec_start_toggling;
+
+	tcpm->port_start = gen1_typec_port_start;
+	tcpm->port_stop = gen1_typec_port_stop;
+
+	return 0;
+}
diff --git a/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_port_gen1.h b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_port_gen1.h
new file mode 100644
index 0000000000000..45e373d22edca
--- /dev/null
+++ b/drivers/usb/typec/tcpm/qcom/qcom_pmic_typec_port_gen1.h
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef __QCOM_PMIC_TYPEC_PORT_GEN1_H__
+#define __QCOM_PMIC_TYPEC_PORT_GEN1_H__
+
+#include "qcom_pmic_typec_port.h"
+
+extern const struct pmic_typec_port_resources gen1_port_res;
+
+int qcom_pmic_typec_gen1_port_probe(struct platform_device *pdev,
+				    struct pmic_typec *tcpm,
+				    const struct pmic_typec_port_resources *res,
+				    struct regmap *regmap,
+				    u32 base);
+
+#endif /* __QCOM_PMIC_TYPEC_PORT_GEN1_H__ */

-- 
2.55.0



  parent reply	other threads:[~2026-09-26 12:40 UTC|newest]

Thread overview: 13+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-26 12:40 [PATCH RFC 00/12] Qualcomm Type-C support for 1st generation David Heidelberg via B4 Relay
2026-09-26 12:40 ` [PATCH RFC 01/12] usb: typec: tcpm: qcom: Release the connector fwnode reference David Heidelberg via B4 Relay
2026-09-26 12:40 ` [PATCH RFC 02/12] dt-bindings: usb: qcom,pmic-typec: Update example with vbus-supply David Heidelberg via B4 Relay
2026-09-26 12:40 ` [PATCH RFC 03/12] dt-bindings: usb: Add Qualcomm 1st-gen PMIC Type-C David Heidelberg via B4 Relay
2026-09-26 12:40 ` [PATCH RFC 04/12] usb: typec: qcom: Make PMIC port probe selectable David Heidelberg via B4 Relay
2026-09-26 12:40 ` [PATCH RFC 05/12] usb: typec: qcom: Make typec_port accept also different structure David Heidelberg via B4 Relay
2026-09-26 12:40 ` David Heidelberg via B4 Relay [this message]
2026-09-26 12:40 ` [PATCH RFC 07/12] arm64: dts: qcom: pm660: Add USB Type-C port controller node David Heidelberg via B4 Relay
2026-09-26 12:40 ` [PATCH RFC 08/12] arm64: dts: qcom: pmi8998: " David Heidelberg via B4 Relay
2026-09-26 12:40 ` [PATCH RFC 09/12] arm64: dts: qcom: sdm670-google-common: Enable USB Type-C port controller David Heidelberg via B4 Relay
2026-09-26 12:40 ` [PATCH RFC 10/12] arm64: dts: qcom: sdm845-google-common: " David Heidelberg via B4 Relay
2026-09-26 12:40 ` [PATCH RFC 11/12] arm64: dts: qcom: sdm845-oneplus-common: " David Heidelberg via B4 Relay
2026-09-26 12:40 ` [PATCH RFC 12/12] arm64: dts: qcom: sdm845-shift-axolotl: " David Heidelberg via B4 Relay

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=20260926-typec-v1-6-31adc19f32c6@ixit.cz \
    --to=devnull+david.ixit.cz@kernel.org \
    --cc=alexeymin@minlexx.ru \
    --cc=andersson@kernel.org \
    --cc=broonie@kernel.org \
    --cc=bryan.odonoghue@linaro.org \
    --cc=conor+dt@kernel.org \
    --cc=david@ixit.cz \
    --cc=devicetree@vger.kernel.org \
    --cc=dmitry.baryshkov@oss.qualcomm.com \
    --cc=gregkh@linuxfoundation.org \
    --cc=heikki.krogerus@linux.intel.com \
    --cc=konrad.dybcio@oss.qualcomm.com \
    --cc=konradybcio@kernel.org \
    --cc=krzk+dt@kernel.org \
    --cc=lee@kernel.org \
    --cc=lgirdwood@gmail.com \
    --cc=linux-arm-msm@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-usb@vger.kernel.org \
    --cc=mfd@lists.linux.dev \
    --cc=phone-devel@vger.kernel.org \
    --cc=robh@kernel.org \
    --cc=sboyd@kernel.org \
    /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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®