[PATCH 2/5] phy: cpcap-usb: add DCP detection and make UART idle mode optional
Ivaylo Dimitrov <[email protected]> Sat, 4 Jul 2026 10:38:40 +0300
| Newsgroups | org.kernel.vger.linux-omap,org.infradead.lists.linux-phy,org.kernel.vger.linux-devicetree,org.kernel.vger.linux-gpio,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <[email protected]> |
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 <[email protected]> --- 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