From: Ivaylo Dimitrov <ivo.g.dimitrov.75@gmail.com>
To: Vinod Koul <vkoul@kernel.org>,
Neil Armstrong <neil.armstrong@linaro.org>,
Rob Herring <robh@kernel.org>,
Krzysztof Kozlowski <krzk+dt@kernel.org>,
Conor Dooley <conor+dt@kernel.org>,
Aaro Koskinen <aaro.koskinen@iki.fi>,
Andreas Kemnade <andreas@kemnade.info>,
Kevin Hilman <khilman@baylibre.com>,
Roger Quadros <rogerq@kernel.org>,
Tony Lindgren <tony@atomide.com>,
Linus Walleij <linusw@kernel.org>,
Bartosz Golaszewski <brgl@kernel.org>,
--cc=linux-phy@lists.infradead.org, devicetree@vger.kernel.org,
linux-kernel@vger.kernel.org, linux-omap@vger.kernel.org,
linux-gpio@vger.kernel.org
Cc: Ivaylo Dimitrov <ivo.g.dimitrov.75@gmail.com>
Subject: [PATCH v5 4/7] phy: cpcap-usb: add DCP detection and make UART idle mode optional
Date: Sat, 11 Jul 2026 23:42:07 +0300 [thread overview]
Message-ID: <20260711204210.197144-5-ivo.g.dimitrov.75@gmail.com> (raw)
In-Reply-To: <20260711204210.197144-1-ivo.g.dimitrov.75@gmail.com>
Handle DCP separately from USB host connections using CPCAP charger
detection status.
Make the existing idle UART mode optional via the "enable_uart" module
parameter. When disabled (default), the PHY remains in its USB/charger
detection configuration while idle.
Also initialize the PHY into the baseline configuration required for
reliable charger detection during probe.
Use the optional "safe" pinctrl state before switching between modes to
avoid glitches on USB or UART lines.
Note: Enabling UART idle mode increases idle power consumption (by 25mW
on droid4).
Signed-off-by: Ivaylo Dimitrov <ivo.g.dimitrov.75@gmail.com>
# Conflicts:
# drivers/phy/motorola/phy-cpcap-usb.c
---
drivers/phy/motorola/phy-cpcap-usb.c | 301 +++++++++++++++++++++------
1 file changed, 238 insertions(+), 63 deletions(-)
diff --git a/drivers/phy/motorola/phy-cpcap-usb.c b/drivers/phy/motorola/phy-cpcap-usb.c
index 741145c89e5b..2d770ff19e93 100644
--- a/drivers/phy/motorola/phy-cpcap-usb.c
+++ b/drivers/phy/motorola/phy-cpcap-usb.c
@@ -110,6 +110,15 @@ enum cpcap_gpio_mode {
CPCAP_OTG_DM_DP,
};
+enum cpcap_mode {
+ CPCAP_UNKNOWN,
+ CPCAP_IDLE,
+ CPCAP_CHARGER,
+ CPCAP_USB,
+ CPCAP_USB_HOST,
+ CPCAP_DOCK,
+};
+
struct cpcap_phy_ddata {
struct regmap *reg;
struct device *dev;
@@ -119,15 +128,19 @@ struct cpcap_phy_ddata {
struct pinctrl_state *pins_ulpi;
struct pinctrl_state *pins_utmi;
struct pinctrl_state *pins_uart;
+ struct pinctrl_state *pins_safe;
struct gpio_desc *gpio[2];
struct iio_channel *vbus;
struct iio_channel *id;
struct regulator *vusb;
atomic_t active;
- unsigned int vbus_provider:1;
- unsigned int docked:1;
+ enum cpcap_mode mode;
};
+static bool cpcap_enable_uart;
+module_param_named(enable_uart, cpcap_enable_uart, bool, 0644);
+MODULE_PARM_DESC(enable_uart,
+ "Enable UART on the USB connector while idle (increases power consumption)");
static bool cpcap_usb_vbus_valid(struct cpcap_phy_ddata *ddata)
{
int error, value = 0;
@@ -196,8 +209,9 @@ static int cpcap_phy_get_ints_state(struct cpcap_phy_ddata *ddata,
return 0;
}
-static int cpcap_usb_set_uart_mode(struct cpcap_phy_ddata *ddata);
+static int cpcap_usb_set_idle_mode(struct cpcap_phy_ddata *ddata);
static int cpcap_usb_set_usb_mode(struct cpcap_phy_ddata *ddata);
+static int cpcap_usb_set_dcp_mode(struct cpcap_phy_ddata *ddata);
static void cpcap_usb_try_musb_mailbox(struct cpcap_phy_ddata *ddata,
enum musb_vbus_id_status status)
@@ -228,7 +242,7 @@ static void cpcap_usb_detect(struct work_struct *work)
vbus = cpcap_usb_vbus_valid(ddata);
/* We need to kick the VBUS as USB A-host */
- if (s.id_ground && ddata->vbus_provider) {
+ if (s.id_ground && ddata->mode == CPCAP_USB_HOST) {
dev_dbg(ddata->dev, "still in USB A-host mode, kicking VBUS\n");
cpcap_usb_try_musb_mailbox(ddata, MUSB_ID_GROUND);
@@ -243,7 +257,7 @@ static void cpcap_usb_detect(struct work_struct *work)
return;
}
- if (vbus && s.id_ground && ddata->docked) {
+ if (vbus && s.id_ground && ddata->mode == CPCAP_DOCK) {
dev_dbg(ddata->dev, "still docked as A-host, signal ID down\n");
cpcap_usb_try_musb_mailbox(ddata, MUSB_ID_GROUND);
@@ -252,15 +266,14 @@ static void cpcap_usb_detect(struct work_struct *work)
}
/* No VBUS needed with docks */
- if (vbus && s.id_ground && !ddata->vbus_provider) {
+ if (vbus && s.id_ground && ddata->mode != CPCAP_USB_HOST) {
dev_dbg(ddata->dev, "connected to a dock\n");
- ddata->docked = true;
-
error = cpcap_usb_set_usb_mode(ddata);
if (error)
goto out_err;
+ ddata->mode = CPCAP_DOCK;
cpcap_usb_try_musb_mailbox(ddata, MUSB_ID_GROUND);
/*
@@ -274,17 +287,13 @@ static void cpcap_usb_detect(struct work_struct *work)
return;
}
- if (s.id_ground && !ddata->docked) {
+ if (s.id_ground && ddata->mode != CPCAP_DOCK) {
dev_dbg(ddata->dev, "id ground, USB host mode\n");
- ddata->vbus_provider = true;
-
error = cpcap_usb_set_usb_mode(ddata);
if (error)
goto out_err;
- cpcap_usb_try_musb_mailbox(ddata, MUSB_ID_GROUND);
-
error = regmap_update_bits(ddata->reg, CPCAP_REG_USBC3,
CPCAP_BIT_VBUSSTBY_EN |
CPCAP_BIT_VBUSEN_SPI,
@@ -292,38 +301,74 @@ static void cpcap_usb_detect(struct work_struct *work)
if (error)
goto out_err;
+ ddata->mode = CPCAP_USB_HOST;
+ cpcap_usb_try_musb_mailbox(ddata, MUSB_ID_GROUND);
+
return;
}
- error = regmap_update_bits(ddata->reg, CPCAP_REG_USBC3,
- CPCAP_BIT_VBUSSTBY_EN |
- CPCAP_BIT_VBUSEN_SPI, 0);
- if (error)
- goto out_err;
-
vbus = cpcap_usb_vbus_valid(ddata);
- /* Otherwise assume we're connected to a USB host */
- if (vbus) {
- dev_dbg(ddata->dev, "connected to USB host\n");
- error = cpcap_usb_set_usb_mode(ddata);
+ /* Check if we're connected to a USB host or charger */
+ if (vbus && ddata->mode != CPCAP_USB_HOST) {
+ enum cpcap_mode new_mode;
+
+ if (s.se1)
+ new_mode = CPCAP_CHARGER;
+ else if (!s.dm && !s.dp)
+ new_mode = CPCAP_USB;
+ else {
+ error = regmap_update_bits(ddata->reg, CPCAP_REG_USBC2,
+ CPCAP_BIT_USBXCVREN |
+ CPCAP_BIT_UARTMUX0 |
+ CPCAP_BIT_EMUMODE0, 0);
+ if (error)
+ goto out_err;
+
+ schedule_delayed_work(&ddata->detect_work,
+ msecs_to_jiffies(100));
+ return;
+ }
+
+ dev_dbg(ddata->dev, "connected to %s\n",
+ s.se1 ? "DCP" : "USB host");
+
+ error = regmap_update_bits(ddata->reg, CPCAP_REG_USBC3,
+ CPCAP_BIT_VBUSSTBY_EN |
+ CPCAP_BIT_VBUSEN_SPI, 0);
if (error)
goto out_err;
- cpcap_usb_try_musb_mailbox(ddata, MUSB_VBUS_VALID);
+
+ if (new_mode == CPCAP_CHARGER)
+ error = cpcap_usb_set_dcp_mode(ddata);
+ else
+ error = cpcap_usb_set_usb_mode(ddata);
+
+ if (error)
+ goto out_err;
+
+ ddata->mode = new_mode;
+ if (ddata->mode == CPCAP_USB)
+ cpcap_usb_try_musb_mailbox(ddata, MUSB_VBUS_VALID);
return;
}
- ddata->vbus_provider = false;
- ddata->docked = false;
- cpcap_usb_try_musb_mailbox(ddata, MUSB_VBUS_OFF);
+ if (ddata->mode == CPCAP_IDLE)
+ return;
- /* Default to debug UART mode */
- error = cpcap_usb_set_uart_mode(ddata);
+ error = regmap_update_bits(ddata->reg, CPCAP_REG_USBC3,
+ CPCAP_BIT_VBUSSTBY_EN |
+ CPCAP_BIT_VBUSEN_SPI, 0);
+ if (error)
+ goto out_err;
+
+ error = cpcap_usb_set_idle_mode(ddata);
if (error)
goto out_err;
- dev_dbg(ddata->dev, "set UART mode\n");
+ ddata->mode = CPCAP_IDLE;
+ cpcap_usb_try_musb_mailbox(ddata, MUSB_VBUS_OFF);
return;
@@ -424,43 +469,84 @@ static int cpcap_usb_gpio_set_mode(struct cpcap_phy_ddata *ddata,
return 0;
}
-static int cpcap_usb_set_uart_mode(struct cpcap_phy_ddata *ddata)
+static int cpcap_usb_set_safe_mode(struct cpcap_phy_ddata *ddata)
{
int error;
/* Disable lines to prevent glitches from waking up mdm6600 */
error = cpcap_usb_gpio_set_mode(ddata, CPCAP_UNKNOWN_DISABLED);
if (error)
- goto out_err;
+ return error;
- if (ddata->pins_uart) {
- error = pinctrl_select_state(ddata->pins, ddata->pins_uart);
- if (error)
- goto out_err;
+ if (ddata->pins_safe) {
+ error = pinctrl_select_state(ddata->pins, ddata->pins_safe);
+ if (error) {
+ dev_err(ddata->dev, "could not set safe mode: %i\n",
+ error);
+ }
}
+ return error;
+}
+
+static int cpcap_usb_set_idle_mode(struct cpcap_phy_ddata *ddata)
+{
+ int error;
+ unsigned int val;
+ bool enable_uart = cpcap_enable_uart;
+
+ error = cpcap_usb_set_safe_mode(ddata);
+ if (error)
+ return error;
+
error = regmap_update_bits(ddata->reg, CPCAP_REG_USBC1,
+ CPCAP_BIT_DP150KPU |
+ CPCAP_BIT_DP1K5PU |
+ CPCAP_BIT_DM1K5PU |
+ CPCAP_BIT_DPPD |
+ CPCAP_BIT_DMPD |
CPCAP_BIT_VBUSPD,
+ CPCAP_BIT_DP150KPU |
CPCAP_BIT_VBUSPD);
if (error)
goto out_err;
- error = regmap_update_bits(ddata->reg, CPCAP_REG_USBC2,
- 0xffff, CPCAP_BIT_UARTMUX0 |
- CPCAP_BIT_EMUMODE0);
+ val = CPCAP_BIT_USBSUSPEND;
+
+ if (enable_uart)
+ val |= (CPCAP_BIT_UARTMUX0 | CPCAP_BIT_EMUMODE0);
+
+ error = regmap_update_bits(ddata->reg, CPCAP_REG_USBC2, 0xffff, val);
if (error)
goto out_err;
- error = regmap_update_bits(ddata->reg, CPCAP_REG_USBC3, 0x7fff,
- CPCAP_BIT_IDPU_SPI);
+ val = CPCAP_BIT_VBUSSTBY_EN |
+ CPCAP_BIT_VBUSPD_SPI |
+ CPCAP_BIT_DMPD_SPI |
+ CPCAP_BIT_DPPD_SPI |
+ CPCAP_BIT_PU_SPI |
+ CPCAP_BIT_IDPU_SPI |
+ CPCAP_BIT_ULPI_SPI_SEL;
+
+ if (!enable_uart)
+ val |= CPCAP_BIT_SUSPEND_SPI;
+
+ error = regmap_update_bits(ddata->reg, CPCAP_REG_USBC3, 0x7fff, val);
if (error)
goto out_err;
- /* Enable UART mode */
error = cpcap_usb_gpio_set_mode(ddata, CPCAP_DM_DP);
if (error)
goto out_err;
+ if (enable_uart && ddata->pins_uart) {
+ error = pinctrl_select_state(ddata->pins, ddata->pins_uart);
+ if (error)
+ goto out_err;
+ }
+
+ dev_dbg(ddata->dev, "set %s mode\n", enable_uart ? "UART" : "IDLE");
+
return 0;
out_err:
@@ -473,21 +559,10 @@ static int cpcap_usb_set_usb_mode(struct cpcap_phy_ddata *ddata)
{
int error;
- /* Disable lines to prevent glitches from waking up mdm6600 */
- error = cpcap_usb_gpio_set_mode(ddata, CPCAP_UNKNOWN_DISABLED);
+ error = cpcap_usb_set_safe_mode(ddata);
if (error)
return error;
- if (ddata->pins_utmi) {
- error = pinctrl_select_state(ddata->pins, ddata->pins_utmi);
- if (error) {
- dev_err(ddata->dev, "could not set usb mode: %i\n",
- error);
-
- return error;
- }
- }
-
error = regmap_update_bits(ddata->reg, CPCAP_REG_USBC1,
CPCAP_BIT_VBUSPD, 0);
if (error)
@@ -503,11 +578,23 @@ static int cpcap_usb_set_usb_mode(struct cpcap_phy_ddata *ddata)
goto out_err;
error = regmap_update_bits(ddata->reg, CPCAP_REG_USBC2,
- CPCAP_BIT_USBXCVREN,
+ CPCAP_BIT_USBXCVREN |
+ CPCAP_BIT_UARTMUX0 |
+ CPCAP_BIT_EMUMODE0,
CPCAP_BIT_USBXCVREN);
if (error)
goto out_err;
+ if (ddata->pins_utmi) {
+ error = pinctrl_select_state(ddata->pins, ddata->pins_utmi);
+ if (error) {
+ dev_err(ddata->dev, "could not set usb mode: %i\n",
+ error);
+
+ return error;
+ }
+ }
+
/* Enable USB mode */
error = cpcap_usb_gpio_set_mode(ddata, CPCAP_OTG_DM_DP);
if (error)
@@ -521,6 +608,38 @@ static int cpcap_usb_set_usb_mode(struct cpcap_phy_ddata *ddata)
return error;
}
+static int cpcap_usb_set_dcp_mode(struct cpcap_phy_ddata *ddata)
+{
+ int error;
+
+ error = cpcap_usb_set_safe_mode(ddata);
+ if (error)
+ return error;
+
+ error = regmap_update_bits(ddata->reg, CPCAP_REG_USBC2,
+ CPCAP_BIT_USBXCVREN |
+ CPCAP_BIT_UARTMUX0 |
+ CPCAP_BIT_EMUMODE0, 0);
+ if (error)
+ goto out_err;
+
+ error = regmap_update_bits(ddata->reg, CPCAP_REG_USBC3,
+ CPCAP_BIT_SUSPEND_SPI, 0);
+ if (error)
+ goto out_err;
+
+ error = cpcap_usb_gpio_set_mode(ddata, CPCAP_DM_DP);
+ if (error)
+ goto out_err;
+
+ return 0;
+
+out_err:
+ dev_err(ddata->dev, "%s failed with %i\n", __func__, error);
+
+ return error;
+}
+
static int cpcap_usb_init_optional_pins(struct cpcap_phy_ddata *ddata)
{
ddata->pins = devm_pinctrl_get(ddata->dev);
@@ -550,8 +669,11 @@ static int cpcap_usb_init_optional_pins(struct cpcap_phy_ddata *ddata)
ddata->pins_uart = NULL;
}
- if (ddata->pins_uart)
- return pinctrl_select_state(ddata->pins, ddata->pins_uart);
+ ddata->pins_safe = pinctrl_lookup_state(ddata->pins, "safe");
+ if (IS_ERR(ddata->pins_safe)) {
+ dev_info(ddata->dev, "safe pins not configured\n");
+ ddata->pins_safe = NULL;
+ }
return 0;
}
@@ -605,6 +727,59 @@ static int cpcap_usb_init_iio(struct cpcap_phy_ddata *ddata)
return error;
}
+static int cpcap_usb_init_usb(struct cpcap_phy_ddata *ddata)
+{
+ struct cpcap_usb_ints_state s;
+ int error;
+ int mask;
+
+ error = cpcap_phy_get_ints_state(ddata, &s);
+ if (error)
+ return error;
+
+ error = cpcap_usb_set_safe_mode(ddata);
+ if (error)
+ return error;
+
+ /*
+ * Do not touch DM/DP PD as this will break charger detection if charger
+ * is connected before we detect it.
+ */
+ if (s.se1) {
+ mask = CPCAP_BIT_VBUSEN_SPI | CPCAP_BIT_VBUSPU_SPI |
+ CPCAP_BIT_SUSPEND_SPI | CPCAP_BIT_ULPI_SPI_SEL;
+ } else {
+ mask = CPCAP_BIT_VBUSEN_SPI | CPCAP_BIT_VBUSPU_SPI |
+ CPCAP_BIT_DMPD_SPI | CPCAP_BIT_DPPD_SPI |
+ CPCAP_BIT_SUSPEND_SPI | CPCAP_BIT_PU_SPI |
+ CPCAP_BIT_ULPI_SPI_SEL;
+ }
+
+ error = regmap_update_bits(ddata->reg, CPCAP_REG_USBC3, mask,
+ CPCAP_BIT_ULPI_SPI_SEL);
+ if (error)
+ return error;
+
+ /*
+ * Configure safe DP/DM termination state.
+ *
+ * Vendor kernel uses:
+ * DP150KPU = enabled
+ * all USB pullups/pulldowns = disabled
+ */
+ error = regmap_update_bits(ddata->reg, CPCAP_REG_USBC1,
+ CPCAP_BIT_DP150KPU |
+ CPCAP_BIT_DP1K5PU |
+ CPCAP_BIT_DM1K5PU |
+ CPCAP_BIT_DPPD |
+ CPCAP_BIT_DMPD,
+ CPCAP_BIT_DP150KPU);
+ if (error)
+ return error;
+
+ return cpcap_usb_gpio_set_mode(ddata, CPCAP_DM_DP);
+}
+
#ifdef CONFIG_OF
static const struct of_device_id cpcap_usb_phy_id_table[] = {
{
@@ -638,6 +813,7 @@ static int cpcap_usb_phy_probe(struct platform_device *pdev)
if (!otg)
return -ENOMEM;
+ ddata->mode = CPCAP_UNKNOWN;
ddata->dev = &pdev->dev;
ddata->phy.dev = ddata->dev;
ddata->phy.label = "cpcap_usb_phy";
@@ -682,6 +858,10 @@ static int cpcap_usb_phy_probe(struct platform_device *pdev)
if (error)
goto out_reg_disable;
+ error = cpcap_usb_init_usb(ddata);
+ if (error)
+ goto out_reg_disable;
+
error = cpcap_usb_init_interrupts(pdev, ddata);
if (error)
goto out_reg_disable;
@@ -701,17 +881,12 @@ static int cpcap_usb_phy_probe(struct platform_device *pdev)
static void cpcap_usb_phy_remove(struct platform_device *pdev)
{
struct cpcap_phy_ddata *ddata = platform_get_drvdata(pdev);
- int error;
atomic_set(&ddata->active, 0);
cpcap_usb_fini_interrupts(pdev, ddata);
cancel_delayed_work_sync(&ddata->detect_work);
- error = cpcap_usb_set_uart_mode(ddata);
- if (error)
- dev_err(ddata->dev, "could not set UART mode\n");
-
+ cpcap_usb_set_idle_mode(ddata);
cpcap_usb_try_musb_mailbox(ddata, MUSB_VBUS_OFF);
-
usb_remove_phy(&ddata->phy);
regulator_disable(ddata->vusb);
}
--
2.39.5
next prev parent reply other threads:[~2026-07-11 20:42 UTC|newest]
Thread overview: 9+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-11 20:42 [PATCH v5 0/7] phy: cpcap-usb: improve charger detection and export cable state Ivaylo Dimitrov
2026-07-11 20:42 ` [PATCH v5 1/7] dt-bindings: phy: motorola,cpcap-usb: add chrg_det interrupt Ivaylo Dimitrov
2026-07-11 20:42 ` [PATCH v5 2/7] dt-bindings: phy: motorola,cpcap-usb-phy: add optional safe pinctrl state Ivaylo Dimitrov
2026-07-11 20:42 ` [PATCH v5 3/7] phy: cpcap-usb: fix IRQ teardown race Ivaylo Dimitrov
2026-07-11 20:42 ` Ivaylo Dimitrov [this message]
2026-07-11 20:54 ` [PATCH v5 4/7] phy: cpcap-usb: add DCP detection and make UART idle mode optional sashiko-bot
2026-07-11 20:42 ` [PATCH v5 5/7] phy: cpcap-usb: add extcon support Ivaylo Dimitrov
2026-07-11 20:42 ` [PATCH v5 6/7] ARM: dts: ti: cpcap-mapphone: add charger detection interrupt for CPCAP USB PHY Ivaylo Dimitrov
2026-07-11 20:42 ` [PATCH v5 7/7] ARM: dts: ti: cpcap-mapphone: add USB safe pinctrl state Ivaylo Dimitrov
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=20260711204210.197144-5-ivo.g.dimitrov.75@gmail.com \
--to=ivo.g.dimitrov.75@gmail.com \
--cc=--cc=linux-phy@lists.infradead.org \
--cc=aaro.koskinen@iki.fi \
--cc=andreas@kemnade.info \
--cc=brgl@kernel.org \
--cc=conor+dt@kernel.org \
--cc=devicetree@vger.kernel.org \
--cc=khilman@baylibre.com \
--cc=krzk+dt@kernel.org \
--cc=linusw@kernel.org \
--cc=linux-gpio@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-omap@vger.kernel.org \
--cc=neil.armstrong@linaro.org \
--cc=robh@kernel.org \
--cc=rogerq@kernel.org \
--cc=tony@atomide.com \
--cc=vkoul@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