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
next prev 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®