mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: taygoth <taygoth@gmail.com>
To: linux-arm-msm@vger.kernel.org, linux-usb@vger.kernel.org,
	devicetree@vger.kernel.org, linux-kernel@vger.kernel.org
Cc: Mark Brown <broonie@kernel.org>,
	Liam Girdwood <lgirdwood@gmail.com>,
	Wesley Cheng <quic_wcheng@quicinc.com>,
	Greg Kroah-Hartman <gregkh@linuxfoundation.org>,
	Heikki Krogerus <heikki.krogerus@linux.intel.com>,
	Rob Herring <robh@kernel.org>,
	Krzysztof Kozlowski <krzk+dt@kernel.org>,
	Conor Dooley <conor+dt@kernel.org>,
	Bjorn Andersson <andersson@kernel.org>,
	Konrad Dybcio <konradybcio@kernel.org>,
	Bryan O'Donoghue <bryan.odonoghue@linaro.org>,
	Casey Connolly <casey.connolly@linaro.org>
Subject: [PATCH 3/6] usb: typec: add Qualcomm PMI8998 role-switch driver
Date: Tue, 19 May 2026 01:22:48 +0500	[thread overview]
Message-ID: <6fedbcd344505b63de72037f69ea94f916255f03.1779127507.git.taygoth@gmail.com> (raw)
In-Reply-To: <cover.1779127507.git.taygoth@gmail.com>

The Qualcomm PMI8998 PMIC integrates a USB Type-C detection block in
its SMB2 charger USBIN region at offset 0x1300. The block performs CC
sensing, debounce and Rp/Rd resolution in silicon and reports role
changes through a single consolidated "type-c-change" interrupt.

This driver reads TYPE_C_STATUS_4 on probe and on every interrupt,
decodes the hardware-detected role (host / device / none) and pushes
it to a usb_role_switch consumer (typically dwc3) so the controller
can flip between peripheral and host on cable orientation change. An
optional VBUS supply regulator is toggled on USB_ROLE_HOST transitions
so bus-powered downstream peripherals can be powered.

No software TCPM state machine is used because the hardware handles
the Type-C protocol natively, and the PMI8998 interrupt topology does
not expose the granular per-event signals the TCPM code path expects.
Power Delivery is not implemented in this driver; the PMI8998 PDPHY
block at offset 0x1700 (register-identical to PM8150B) can be wired
up by a separate driver later if PD negotiation is required.

This enables USB host mode on the OnePlus 6 (enchilada), OnePlus 6T
(fajita), Xiaomi Poco F1 (beryllium), SHIFT 6mq (axolotl) and other
SDM845-based phones that use the PMI8998 as their second PMIC.

Signed-off-by: taygoth <taygoth@gmail.com>
---
 drivers/usb/typec/Kconfig              |  17 ++
 drivers/usb/typec/Makefile             |   1 +
 drivers/usb/typec/qcom_pmi8998_typec.c | 213 +++++++++++++++++++++++++
 3 files changed, 231 insertions(+)
 create mode 100644 drivers/usb/typec/qcom_pmi8998_typec.c

diff --git a/drivers/usb/typec/Kconfig b/drivers/usb/typec/Kconfig
index 2f80c2792dbd..1912ca3f6e0a 100644
--- a/drivers/usb/typec/Kconfig
+++ b/drivers/usb/typec/Kconfig
@@ -100,6 +100,23 @@ config TYPEC_STUSB160X
 	  If you choose to build this driver as a dynamically linked module, the
 	  module will be called stusb160x.ko.
 
+config TYPEC_QCOM_PMI8998
+	tristate "Qualcomm PMI8998 USB Type-C role-switch driver"
+	depends on ARCH_QCOM || COMPILE_TEST
+	depends on SPMI
+	select USB_ROLE_SWITCH
+	help
+	  Say Y or M here to enable the role-switch driver for the USB
+	  Type-C detection block integrated in the Qualcomm PMI8998 PMIC.
+	  This is the part used on SDM845 phones such as the OnePlus 6/6T,
+	  Xiaomi Poco F1 and SHIFT 6mq. The driver reports the
+	  hardware-detected role through the USB role switch class so the
+	  USB DRD controller (e.g. dwc3) can flip between peripheral and
+	  host on cable orientation change.
+
+	  If you choose to build this driver as a dynamically linked module,
+	  the module will be called qcom_pmi8998_typec.ko.
+
 config TYPEC_WUSB3801
 	tristate "Willsemi WUSB3801 Type-C port controller driver"
 	depends on I2C
diff --git a/drivers/usb/typec/Makefile b/drivers/usb/typec/Makefile
index 8a6a1c663eb6..568f8ee68c9d 100644
--- a/drivers/usb/typec/Makefile
+++ b/drivers/usb/typec/Makefile
@@ -10,5 +10,6 @@ obj-$(CONFIG_TYPEC_ANX7411)	+= anx7411.o
 obj-$(CONFIG_TYPEC_HD3SS3220)	+= hd3ss3220.o
 obj-$(CONFIG_TYPEC_STUSB160X) 	+= stusb160x.o
 obj-$(CONFIG_TYPEC_RT1719)	+= rt1719.o
+obj-$(CONFIG_TYPEC_QCOM_PMI8998)	+= qcom_pmi8998_typec.o
 obj-$(CONFIG_TYPEC_WUSB3801)	+= wusb3801.o
 obj-$(CONFIG_TYPEC)		+= mux/
diff --git a/drivers/usb/typec/qcom_pmi8998_typec.c b/drivers/usb/typec/qcom_pmi8998_typec.c
new file mode 100644
index 000000000000..77662b4f1b0c
--- /dev/null
+++ b/drivers/usb/typec/qcom_pmi8998_typec.c
@@ -0,0 +1,213 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Qualcomm PMI8998 USB Type-C role-switch driver.
+ *
+ * The PMI8998 PMIC integrates a USB Type-C detection block inside its
+ * SMB2 charger USBIN region at offset 0x1300. The block performs CC
+ * sensing, debounce and Rp/Rd resolution in hardware, then reports the
+ * negotiated role via TYPE_C_STATUS_4 and a single consolidated
+ * "type-c-change" interrupt.
+ *
+ * This driver translates the hardware-decided role into a
+ * usb_role_switch_set_role() call so the USB DRD controller (typically
+ * dwc3) can flip between peripheral and host. An optional VBUS supply
+ * regulator is enabled on USB_ROLE_HOST transitions so bus-powered
+ * peripherals can be powered.
+ *
+ * No software TCPM state machine is needed because the hardware handles
+ * the Type-C protocol natively. Power Delivery is not supported by this
+ * driver; the PMI8998 PDPHY block at offset 0x1700 (identical register
+ * layout to PM8150B) can be wired up separately by an additional driver
+ * if PD negotiation is required.
+ *
+ * Copyright (c) 2026 Maxim Furman <taygoth@gmail.com>
+ */
+
+#include <linux/bits.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/module.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/usb/role.h>
+
+#define TYPE_C_STATUS_1_REG			0x0b
+#define   UFP_TYPEC_RDSTD				BIT(7)
+#define   UFP_TYPEC_RD1P5				BIT(6)
+#define   UFP_TYPEC_RD3P0				BIT(5)
+
+#define TYPE_C_STATUS_2_REG			0x0c
+#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 TYPE_C_STATUS_4_REG			0x0e
+#define   UFP_DFP_MODE_STATUS				BIT(7)
+#define   TYPEC_VBUS_STATUS				BIT(6)
+#define   TYPEC_VBUS_ERROR_STATUS			BIT(5)
+#define   TYPEC_DEBOUNCE_DONE_STATUS			BIT(4)
+#define   CC_ORIENTATION				BIT(1)
+#define   CC_ATTACHED					BIT(0)
+
+#define TYPE_C_STATUS_5_REG			0x0f
+
+#define TYPE_C_INTRPT_ENB_REG			0x67
+#define TYPE_C_INTRPT_ENB_SW_CTRL_REG		0x68
+
+struct pmi8998_typec {
+	struct device		*dev;
+	struct regmap		*regmap;
+	u32			base;
+	int			irq;
+	struct usb_role_switch	*role_sw;
+	struct regulator	*vbus;
+	bool			vbus_enabled;
+	enum usb_role		role;
+};
+
+static enum usb_role pmi8998_typec_decode(unsigned int status)
+{
+	if (!(status & CC_ATTACHED))
+		return USB_ROLE_NONE;
+	if (!(status & TYPEC_DEBOUNCE_DONE_STATUS))
+		return USB_ROLE_NONE;
+
+	return (status & UFP_DFP_MODE_STATUS) ? USB_ROLE_HOST : USB_ROLE_DEVICE;
+}
+
+static int pmi8998_typec_apply(struct pmi8998_typec *typec)
+{
+	unsigned int status;
+	enum usb_role role;
+	int ret;
+
+	ret = regmap_read(typec->regmap,
+			  typec->base + TYPE_C_STATUS_4_REG, &status);
+	if (ret)
+		return ret;
+
+	role = pmi8998_typec_decode(status);
+	if (role == typec->role)
+		return 0;
+
+	if (typec->vbus) {
+		bool want_vbus = (role == USB_ROLE_HOST);
+
+		if (want_vbus && !typec->vbus_enabled) {
+			ret = regulator_enable(typec->vbus);
+			if (ret)
+				return ret;
+			typec->vbus_enabled = true;
+		} else if (!want_vbus && typec->vbus_enabled) {
+			regulator_disable(typec->vbus);
+			typec->vbus_enabled = false;
+		}
+	}
+
+	ret = usb_role_switch_set_role(typec->role_sw, role);
+	if (ret)
+		return ret;
+
+	typec->role = role;
+	return 0;
+}
+
+static irqreturn_t pmi8998_typec_isr(int irq, void *data)
+{
+	pmi8998_typec_apply(data);
+	return IRQ_HANDLED;
+}
+
+static int pmi8998_typec_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct fwnode_handle *connector;
+	struct pmi8998_typec *typec;
+	int ret;
+
+	typec = devm_kzalloc(dev, sizeof(*typec), GFP_KERNEL);
+	if (!typec)
+		return -ENOMEM;
+
+	typec->dev = dev;
+	typec->role = USB_ROLE_NONE;
+
+	typec->regmap = dev_get_regmap(dev->parent, NULL);
+	if (!typec->regmap)
+		return -ENODEV;
+
+	ret = of_property_read_u32_index(dev->of_node, "reg", 0, &typec->base);
+	if (ret)
+		return ret;
+
+	typec->irq = platform_get_irq_byname(pdev, "type-c-change");
+	if (typec->irq < 0)
+		return typec->irq;
+
+	connector = device_get_named_child_node(dev, "connector");
+	if (!connector)
+		return -EINVAL;
+
+	typec->role_sw = fwnode_usb_role_switch_get(connector);
+	fwnode_handle_put(connector);
+	if (IS_ERR(typec->role_sw))
+		return PTR_ERR(typec->role_sw);
+
+	typec->vbus = devm_regulator_get_optional(dev, "vdd-vbus");
+	if (IS_ERR(typec->vbus)) {
+		if (PTR_ERR(typec->vbus) != -ENODEV) {
+			ret = PTR_ERR(typec->vbus);
+			goto err_role_put;
+		}
+		typec->vbus = NULL;
+	}
+
+	platform_set_drvdata(pdev, typec);
+
+	pmi8998_typec_apply(typec);
+
+	ret = devm_request_threaded_irq(dev, typec->irq, NULL,
+					pmi8998_typec_isr, IRQF_ONESHOT,
+					dev_name(dev), typec);
+	if (ret)
+		goto err_role_put;
+
+	return 0;
+
+err_role_put:
+	usb_role_switch_put(typec->role_sw);
+	return ret;
+}
+
+static void pmi8998_typec_remove(struct platform_device *pdev)
+{
+	struct pmi8998_typec *typec = platform_get_drvdata(pdev);
+
+	if (typec->vbus_enabled)
+		regulator_disable(typec->vbus);
+	usb_role_switch_put(typec->role_sw);
+}
+
+static const struct of_device_id pmi8998_typec_of_match[] = {
+	{ .compatible = "qcom,pmi8998-typec" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, pmi8998_typec_of_match);
+
+static struct platform_driver pmi8998_typec_driver = {
+	.probe		= pmi8998_typec_probe,
+	.remove		= pmi8998_typec_remove,
+	.driver		= {
+		.name		= "qcom-pmi8998-typec",
+		.of_match_table	= pmi8998_typec_of_match,
+	},
+};
+module_platform_driver(pmi8998_typec_driver);
+
+MODULE_AUTHOR("Maxim Furman <taygoth@gmail.com>");
+MODULE_DESCRIPTION("Qualcomm PMI8998 USB Type-C role-switch driver");
+MODULE_LICENSE("GPL");
-- 
2.47.3


  parent reply	other threads:[~2026-05-18 20:23 UTC|newest]

Thread overview: 19+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-05-18 20:22 [PATCH 0/6] usb: typec: add Qualcomm PMI8998 USB Type-C role-switch support taygoth
2026-05-18 20:22 ` [PATCH 1/6] dt-bindings: regulator: qcom,usb-vbus-regulator: add PMI8998 taygoth
2026-05-18 21:56   ` Bryan O'Donoghue
2026-05-19 10:51   ` Krzysztof Kozlowski
2026-05-18 20:22 ` [PATCH 2/6] dt-bindings: usb: add Qualcomm PMI8998 Type-C controller taygoth
2026-05-18 22:03   ` Dmitry Baryshkov
2026-05-18 20:22 ` taygoth [this message]
2026-05-18 21:45   ` [PATCH 3/6] usb: typec: add Qualcomm PMI8998 role-switch driver Dmitry Baryshkov
2026-05-18 22:07     ` Bryan O'Donoghue
2026-05-18 22:09       ` Bryan O'Donoghue
2026-05-19 10:52   ` Krzysztof Kozlowski
2026-05-18 20:22 ` [PATCH 4/6] arm64: dts: qcom: pmi8998: add USB Type-C and VBUS regulator nodes taygoth
2026-05-18 20:22 ` [PATCH 5/6] arm64: dts: qcom: sdm845-oneplus-common: enable USB Type-C role switching taygoth
2026-05-18 20:22 ` [PATCH 6/6] MAINTAINERS: add entry for Qualcomm PMI8998 USB Type-C driver taygoth
2026-05-18 21:59 ` [PATCH 0/6] usb: typec: add Qualcomm PMI8998 USB Type-C role-switch support Bryan O'Donoghue
     [not found]   ` <CAFPzRonyVt9Kd+Sc0ooNz8By6b-Zr_jHr0sBXv-M25dQ0w9Cjg@mail.gmail.com>
2026-05-18 23:43     ` Bryan O'Donoghue
2026-05-20 15:00       ` Dmitry Baryshkov
2026-05-19 11:12 ` Konrad Dybcio
2026-07-28  8:25 ` David Heidelberg

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=6fedbcd344505b63de72037f69ea94f916255f03.1779127507.git.taygoth@gmail.com \
    --to=taygoth@gmail.com \
    --cc=andersson@kernel.org \
    --cc=broonie@kernel.org \
    --cc=bryan.odonoghue@linaro.org \
    --cc=casey.connolly@linaro.org \
    --cc=conor+dt@kernel.org \
    --cc=devicetree@vger.kernel.org \
    --cc=gregkh@linuxfoundation.org \
    --cc=heikki.krogerus@linux.intel.com \
    --cc=konradybcio@kernel.org \
    --cc=krzk+dt@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=quic_wcheng@quicinc.com \
    --cc=robh@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®