[PATCH 2/5] phy: cpcap-usb: add DCP detection and make UART idle mode optional
Ivaylo Dimitrov
ivo.g.dimitrov.75 at gmail.com
Sat Jul 4 00:38:40 PDT 2026
Handle DCP separately from USB host connections based on CPCAP charger
detection status.
Make the existing idle UART mode optional via the "enable_uart" module
parameter. When disabled, the PHY remains in its USB/charger detection
configuration while idle. This reduces power usage in idle by 25mW.
Also initialize the PHY into the baseline configuration required for
reliable charger detection during probe.
Note: Enabling UART idle mode increases idle power consumption.
Signed-off-by: Ivaylo Dimitrov <ivo.g.dimitrov.75 at gmail.com>
---
drivers/phy/motorola/phy-cpcap-usb.c | 235 ++++++++++++++++++++++-----
1 file changed, 191 insertions(+), 44 deletions(-)
diff --git a/drivers/phy/motorola/phy-cpcap-usb.c b/drivers/phy/motorola/phy-cpcap-usb.c
index c6cc8fce6940..3be2b3afaaf4 100644
--- a/drivers/phy/motorola/phy-cpcap-usb.c
+++ b/drivers/phy/motorola/phy-cpcap-usb.c
@@ -111,6 +111,14 @@ enum cpcap_gpio_mode {
CPCAP_OTG_DM_DP,
};
+enum cpcap_mode {
+ CPCAP_IDLE,
+ CPCAP_CHARGER,
+ CPCAP_USB,
+ CPCAP_USB_HOST,
+ CPCAP_DOCK,
+};
+
struct cpcap_phy_ddata {
struct regmap *reg;
struct device *dev;
@@ -125,10 +133,13 @@ struct cpcap_phy_ddata {
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;
@@ -176,6 +187,7 @@ static int cpcap_phy_get_ints_state(struct cpcap_phy_ddata *ddata,
s->id_ground = val & BIT(15);
s->id_float = val & BIT(14);
+ s->chrg_det = val & BIT(13);
s->vbusov = val & BIT(11);
error = regmap_read(ddata->reg, CPCAP_REG_INTS2, &val);
@@ -197,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)
@@ -229,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);
@@ -244,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);
@@ -253,10 +266,10 @@ 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;
+ ddata->mode = CPCAP_DOCK;
error = cpcap_usb_set_usb_mode(ddata);
if (error)
@@ -275,10 +288,10 @@ 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;
+ ddata->mode = CPCAP_USB_HOST;
error = cpcap_usb_set_usb_mode(ddata);
if (error)
@@ -296,36 +309,53 @@ static void cpcap_usb_detect(struct work_struct *work)
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 */
+ if (vbus && ddata->mode != CPCAP_USB_HOST) {
+ if (!s.chrg_det || !s.sessvld || ddata->mode != CPCAP_IDLE)
+ return;
+
+ if (s.se1)
+ ddata->mode = CPCAP_CHARGER;
+ else if (!s.dm && !s.dp)
+ ddata->mode = CPCAP_USB;
+ else
+ 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 (ddata->mode == CPCAP_CHARGER)
+ error = cpcap_usb_set_dcp_mode(ddata);
+ else
+ error = cpcap_usb_set_usb_mode(ddata);
+
+ if (error)
+ goto out_err;
+
+ if (ddata->mode == CPCAP_USB)
+ cpcap_usb_try_musb_mailbox(ddata, MUSB_VBUS_VALID);
return;
}
- ddata->vbus_provider = false;
- ddata->docked = false;
+ if (ddata->mode == CPCAP_IDLE)
+ return;
+
+ ddata->mode = CPCAP_IDLE;
cpcap_usb_try_musb_mailbox(ddata, MUSB_VBUS_OFF);
- /* Default to debug UART mode */
- error = cpcap_usb_set_uart_mode(ddata);
+ error = cpcap_usb_set_idle_mode(ddata);
if (error)
goto out_err;
- dev_dbg(ddata->dev, "set UART mode\n");
-
return;
out_err:
@@ -374,7 +404,7 @@ static const char * const cpcap_phy_irqs[] = {
"id_ground", "id_float",
/* REG_INT1 */
- "se0conn", "vbusvld", "sessvld", "sessend", "se1",
+ "se0conn", "vbusvld", "sessvld", "sessend", "chrg_det",
/* REG_INT_3 */
"dm", "dp",
@@ -412,9 +442,10 @@ 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_idle_mode(struct cpcap_phy_ddata *ddata)
{
int sysrq, error;
+ unsigned int val;
/* Disable sysrq to prevent random sysrq events on line glitches */
sysrq = sysrq_mask();
@@ -426,30 +457,52 @@ static int cpcap_usb_set_uart_mode(struct cpcap_phy_ddata *ddata)
if (error)
goto out_err;
- if (ddata->pins_uart) {
- error = pinctrl_select_state(ddata->pins, ddata->pins_uart);
+ if (cpcap_enable_uart && ddata->pins_uart) {
+ error = pinctrl_select_state(ddata->pins, ddata->pins_uart);
+ if (error)
+ goto out_err;
+ } else if (!cpcap_enable_uart && ddata->pins_ulpi) {
+ error = pinctrl_select_state(ddata->pins, ddata->pins_ulpi);
if (error)
goto out_err;
}
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 (cpcap_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 (!cpcap_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;
@@ -457,6 +510,8 @@ static int cpcap_usb_set_uart_mode(struct cpcap_phy_ddata *ddata)
if (sysrq & 1)
sysrq_toggle_support(sysrq);
+ dev_dbg(ddata->dev, "set %s mode\n", cpcap_enable_uart ? "UART" : "IDLE");
+
return 0;
out_err:
@@ -517,6 +572,42 @@ 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 = 0;
+
+ /* Disable lines to prevent glitches from waking up mdm6600 */
+ error = cpcap_usb_gpio_set_mode(ddata, CPCAP_UNKNOWN_DISABLED);
+ if (error)
+ return error;
+
+ if (ddata->pins_ulpi) {
+ error = pinctrl_select_state(ddata->pins, ddata->pins_ulpi);
+ if (error) {
+ dev_err(ddata->dev, "could not set usb mode: %i\n",
+ error);
+
+ return error;
+ }
+ }
+
+ 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);
@@ -546,9 +637,6 @@ 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);
-
return 0;
}
@@ -601,6 +689,65 @@ 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_gpio_set_mode(ddata, CPCAP_UNKNOWN_DISABLED);
+ if (error)
+ return error;
+
+ if (ddata->pins_ulpi) {
+ error = pinctrl_select_state(ddata->pins, ddata->pins_ulpi);
+ 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[] = {
{
@@ -678,6 +825,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;
@@ -697,13 +848,9 @@ 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);
- 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);
--
2.25.1
More information about the linux-phy
mailing list