From: Bryan O'Donoghue <bryan.odonoghue@linaro.org>
To: david@ixit.cz, Alexey Minnekhanov <alexeymin@minlexx.ru>,
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
Subject: Re: [PATCH RFC 06/12] usb: typec: qcom: Add gen1 Type-C port support
Date: Sat, 26 Sep 2026 15:22:26 +0100 [thread overview]
Message-ID: <4f089c98-a479-45c9-9b11-0207c895367a@linaro.org> (raw)
In-Reply-To: <20260926-typec-v1-6-31adc19f32c6@ixit.cz>
On 26/09/2026 13:40, David Heidelberg via B4 Relay wrote:
> 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 },
I think this should be
> + { .compatible = "qcom,pm660-typec", .data = &pm660_typec_res },
> { .compatible = "qcom,pm8150b-typec", .data = &pm8150b_typec_res },
> { .compatible = "qcom,pmi632-typec", .data = &pmi632_typec_res },
> + { .compatible = "qcom,pmi8998-typec", .data = &pmi8998_typec_res },
Since I'm not sure "gen1" is a real thing and besides, I see this
gen1/gen2/gen3 stuff in CAMSS and it means almost nothing. The silicon
version is the salient thing.
/rant
> { }
> };
> 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
I'd rename the existing file to qcom_pmic_typec_port_pm8150b and the new
file to qcom_pmic_typec_port_pm660.c or pmi8998.c at your preference,
probably whichever version hit the market first would make sense.
> 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 */
> +};
> +
Existing data-structure is :
struct pmic_typec_port {
struct device *dev;
struct tcpm_port *tcpm_port;
struct regmap *regmap;
u32 base;
unsigned int nr_irqs;
struct pmic_typec_port_irq_data *irq_data;
struct regulator *vdd_vbus;
bool vbus_enabled;
struct mutex vbus_lock; /* VBUS
state serialization */
int cc;
bool debouncing_cc;
struct delayed_work cc_debounce_dwork;
spinlock_t lock; /* Register atomicity */
};
Just re-use that one - also that way we ahe a consistent aproach to locking.
At the worst case you guys need to add .vbus_present ?
It seems weird to me to have one version of the port use spinlock with
another using mutex - which leads to other questions down below.
> +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;
Why would the write fail and why wouldn't that be a critical case if the
write did fail ?
> + 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);
This describes a race condition which is implied in the existing code no ?
> +
> + return ret;
> +}
The get() I agree with the set() I'm not so sure.
Looking at the original:
static int qcom_pmic_typec_port_set_vbus(struct tcpc_dev *tcpc, bool on,
bool sink)
{
struct pmic_typec *tcpm = tcpc_to_tcpm(tcpc);
struct pmic_typec_port *pmic_typec_port = tcpm->pmic_typec_port;
int ret = 0;
mutex_lock(&pmic_typec_port->vbus_lock);
if (pmic_typec_port->vbus_enabled == on)
goto done;
ret = qcom_pmic_typec_port_vbus_toggle(pmic_typec_port, on);
if (ret)
goto done;
pmic_typec_port->vbus_enabled = on;
tcpm_vbus_change(tcpm->tcpm_port);
done:
dev_dbg(tcpm->dev, "set_vbus set: %d result %d\n", on, ret);
mutex_unlock(&pmic_typec_port->vbus_lock);
return ret;
}
Why would the logic be substantially different to that ?
If such a race exists why is the solution not to have vbus_lock own the
assignement of tcpm_port - and why not hold that lock for the durection
of set_vbus as is done with pm8150b ?
reads code...
vbus_lock is supposed to own the vbus state is why - the point remains
how is tcpm_port racy and why is that raciness unique to this particular
version of set_vbus () ?
> +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;
> +}
This probe function is 1:1 with the existing code for a number of lines
=> functional decompositon and code reuse - breaking out if/where it is
warranted.
So that's my overall comment here - code sharing except where it cannot
be sustained which implies a shared data-structure, consistent type of
locks and IMO consistent taking and releasing of those locks.
set_vbus could surely have a generic function where we represent the
_logic_ of what we mean better/consistently instead of clone/own and
potentially substantial differentation between the two schemes in the
one driver.
> 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__ */
>
next prev parent reply other threads:[~2026-09-26 14:22 UTC|newest]
Thread overview: 22+ 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 13:34 ` Bryan O'Donoghue
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 13:39 ` Bryan O'Donoghue
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 13:40 ` Bryan O'Donoghue
2026-09-26 12:40 ` [PATCH RFC 06/12] usb: typec: qcom: Add gen1 Type-C port support David Heidelberg via B4 Relay
2026-09-26 14:22 ` Bryan O'Donoghue [this message]
2026-09-26 14:32 ` David Heidelberg
2026-09-26 14:41 ` Bryan O'Donoghue
2026-09-26 14:45 ` David Heidelberg
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 14:25 ` Bryan O'Donoghue
2026-09-26 12:40 ` [PATCH RFC 08/12] arm64: dts: qcom: pmi8998: " David Heidelberg via B4 Relay
2026-09-26 14:28 ` Bryan O'Donoghue
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=4f089c98-a479-45c9-9b11-0207c895367a@linaro.org \
--to=bryan.odonoghue@linaro.org \
--cc=alexeymin@minlexx.ru \
--cc=andersson@kernel.org \
--cc=broonie@kernel.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®