usb: typec: mux: ps883x: Power the retimer off when not in use

When there's nothing going through the retimer, there's no reason to
keep it online. Put it in reset when possible to save power.

Also, remove the register cache-compare optimization as it makes little
sense now that the chip resets during almost all transitions and
tracking the validity of that cache becomes a headache.

Suggested-by: Jack Pham <jack.pham@oss.qualcomm.com>
Signed-off-by: Konrad Dybcio <konrad.dybcio@oss.qualcomm.com>
Acked-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Tested-by: Anthony Ruhier <aruhier@mailbox.org>
Link: https://patch.msgid.link/20260420-topic-ps883x_unused_reset-v1-1-7aabf7004d2a@oss.qualcomm.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
Konrad Dybcio 2026-04-20 13:40:28 +02:00 committed by Greg Kroah-Hartman
parent e4d7362dc9
commit b38fa168a8

View File

@ -61,167 +61,9 @@ struct ps883x_retimer {
struct mutex lock; /* protect non-concurrent retimer & switch */
enum typec_orientation orientation;
u8 cfg0;
u8 cfg1;
u8 cfg2;
bool in_reset;
};
static int ps883x_configure(struct ps883x_retimer *retimer, int cfg0,
int cfg1, int cfg2)
{
struct device *dev = &retimer->client->dev;
int ret;
if (retimer->cfg0 == cfg0 && retimer->cfg1 == cfg1 && retimer->cfg2 == cfg2)
return 0;
ret = regmap_write(retimer->regmap, REG_USB_PORT_CONN_STATUS_0, cfg0);
if (ret) {
dev_err(dev, "failed to write conn_status_0: %d\n", ret);
return ret;
}
ret = regmap_write(retimer->regmap, REG_USB_PORT_CONN_STATUS_1, cfg1);
if (ret) {
dev_err(dev, "failed to write conn_status_1: %d\n", ret);
return ret;
}
ret = regmap_write(retimer->regmap, REG_USB_PORT_CONN_STATUS_2, cfg2);
if (ret) {
dev_err(dev, "failed to write conn_status_2: %d\n", ret);
return ret;
}
retimer->cfg0 = cfg0;
retimer->cfg1 = cfg1;
retimer->cfg2 = cfg2;
return 0;
}
static int ps883x_set(struct ps883x_retimer *retimer, struct typec_retimer_state *state)
{
struct typec_thunderbolt_data *tb_data;
const struct enter_usb_data *eudo_data;
int cfg0 = CONN_STATUS_0_CONNECTION_PRESENT;
int cfg1 = 0x00;
int cfg2 = 0x00;
if (retimer->orientation == TYPEC_ORIENTATION_REVERSE)
cfg0 |= CONN_STATUS_0_ORIENTATION_REVERSED;
if (state->alt) {
switch (state->alt->svid) {
case USB_TYPEC_DP_SID:
cfg1 |= CONN_STATUS_1_DP_CONNECTED |
CONN_STATUS_1_DP_HPD_LEVEL;
switch (state->mode) {
case TYPEC_DP_STATE_C:
cfg1 |= CONN_STATUS_1_DP_SINK_REQUESTED |
CONN_STATUS_1_DP_PIN_ASSIGNMENT_C_D;
fallthrough;
case TYPEC_DP_STATE_D:
cfg1 |= CONN_STATUS_0_USB_3_1_CONNECTED;
break;
default: /* MODE_E */
break;
}
break;
case USB_TYPEC_TBT_SID:
tb_data = state->data;
/* Unconditional */
cfg2 |= CONN_STATUS_2_TBT_CONNECTED;
if (tb_data->cable_mode & TBT_CABLE_ACTIVE_PASSIVE)
cfg0 |= CONN_STATUS_0_ACTIVE_CABLE;
if (tb_data->enter_vdo & TBT_ENTER_MODE_UNI_DIR_LSRX)
cfg2 |= CONN_STATUS_2_TBT_UNIDIR_LSRX_ACT_LT;
break;
default:
dev_err(&retimer->client->dev, "Got unsupported SID: 0x%x\n",
state->alt->svid);
return -EOPNOTSUPP;
}
} else {
switch (state->mode) {
case TYPEC_STATE_SAFE:
/* USB2 pins don't even go through this chip */
case TYPEC_MODE_USB2:
break;
case TYPEC_STATE_USB:
case TYPEC_MODE_USB3:
cfg0 |= CONN_STATUS_0_USB_3_1_CONNECTED;
break;
case TYPEC_MODE_USB4:
eudo_data = state->data;
cfg2 |= CONN_STATUS_2_USB4_CONNECTED;
if (FIELD_GET(EUDO_CABLE_TYPE_MASK, eudo_data->eudo) != EUDO_CABLE_TYPE_PASSIVE)
cfg0 |= CONN_STATUS_0_ACTIVE_CABLE;
break;
default:
dev_err(&retimer->client->dev, "Got unsupported mode: %lu\n",
state->mode);
return -EOPNOTSUPP;
}
}
return ps883x_configure(retimer, cfg0, cfg1, cfg2);
}
static int ps883x_sw_set(struct typec_switch_dev *sw,
enum typec_orientation orientation)
{
struct ps883x_retimer *retimer = typec_switch_get_drvdata(sw);
int ret = 0;
ret = typec_switch_set(retimer->typec_switch, orientation);
if (ret)
return ret;
mutex_lock(&retimer->lock);
if (retimer->orientation != orientation) {
retimer->orientation = orientation;
ret = regmap_assign_bits(retimer->regmap, REG_USB_PORT_CONN_STATUS_0,
CONN_STATUS_0_ORIENTATION_REVERSED,
orientation == TYPEC_ORIENTATION_REVERSE);
if (ret)
dev_err(&retimer->client->dev, "failed to set orientation: %d\n", ret);
}
mutex_unlock(&retimer->lock);
return ret;
}
static int ps883x_retimer_set(struct typec_retimer *rtmr,
struct typec_retimer_state *state)
{
struct ps883x_retimer *retimer = typec_retimer_get_drvdata(rtmr);
struct typec_mux_state mux_state;
int ret = 0;
mutex_lock(&retimer->lock);
ret = ps883x_set(retimer, state);
mutex_unlock(&retimer->lock);
if (ret)
return ret;
mux_state.alt = state->alt;
mux_state.data = state->data;
mux_state.mode = state->mode;
return typec_mux_set(retimer->typec_mux, &mux_state);
}
static int ps883x_enable_vregs(struct ps883x_retimer *retimer)
{
struct device *dev = &retimer->client->dev;
@ -291,6 +133,193 @@ static void ps883x_disable_vregs(struct ps883x_retimer *retimer)
regulator_disable(retimer->vdd33_supply);
}
static void ps883x_reset(struct ps883x_retimer *retimer)
{
if (retimer->in_reset)
return;
gpiod_set_value(retimer->reset_gpio, 1);
ps883x_disable_vregs(retimer);
retimer->in_reset = true;
}
static int ps883x_configure(struct ps883x_retimer *retimer, int cfg0,
int cfg1, int cfg2, bool reset)
{
struct device *dev = &retimer->client->dev;
int ret;
if (reset) {
ps883x_reset(retimer);
return 0;
} else if (retimer->in_reset) {
ret = ps883x_enable_vregs(retimer);
if (ret)
return ret;
gpiod_set_value(retimer->reset_gpio, 0);
/* firmware initialization delay */
msleep(60);
retimer->in_reset = false;
}
ret = regmap_write(retimer->regmap, REG_USB_PORT_CONN_STATUS_0, cfg0);
if (ret) {
dev_err(dev, "failed to write conn_status_0: %d\n", ret);
return ret;
}
ret = regmap_write(retimer->regmap, REG_USB_PORT_CONN_STATUS_1, cfg1);
if (ret) {
dev_err(dev, "failed to write conn_status_1: %d\n", ret);
return ret;
}
ret = regmap_write(retimer->regmap, REG_USB_PORT_CONN_STATUS_2, cfg2);
if (ret) {
dev_err(dev, "failed to write conn_status_2: %d\n", ret);
return ret;
}
return 0;
}
static int ps883x_set(struct ps883x_retimer *retimer, struct typec_retimer_state *state)
{
struct typec_thunderbolt_data *tb_data;
const struct enter_usb_data *eudo_data;
int cfg0 = CONN_STATUS_0_CONNECTION_PRESENT;
int cfg1 = 0x00;
int cfg2 = 0x00;
bool reset = false;
if (retimer->orientation == TYPEC_ORIENTATION_REVERSE)
cfg0 |= CONN_STATUS_0_ORIENTATION_REVERSED;
if (state->alt) {
switch (state->alt->svid) {
case USB_TYPEC_DP_SID:
cfg1 |= CONN_STATUS_1_DP_CONNECTED |
CONN_STATUS_1_DP_HPD_LEVEL;
switch (state->mode) {
case TYPEC_DP_STATE_C:
cfg1 |= CONN_STATUS_1_DP_SINK_REQUESTED |
CONN_STATUS_1_DP_PIN_ASSIGNMENT_C_D;
fallthrough;
case TYPEC_DP_STATE_D:
cfg1 |= CONN_STATUS_0_USB_3_1_CONNECTED;
break;
default: /* MODE_E */
break;
}
break;
case USB_TYPEC_TBT_SID:
tb_data = state->data;
/* Unconditional */
cfg2 |= CONN_STATUS_2_TBT_CONNECTED;
if (tb_data->cable_mode & TBT_CABLE_ACTIVE_PASSIVE)
cfg0 |= CONN_STATUS_0_ACTIVE_CABLE;
if (tb_data->enter_vdo & TBT_ENTER_MODE_UNI_DIR_LSRX)
cfg2 |= CONN_STATUS_2_TBT_UNIDIR_LSRX_ACT_LT;
break;
default:
dev_err(&retimer->client->dev, "Got unsupported SID: 0x%x\n",
state->alt->svid);
return -EOPNOTSUPP;
}
} else {
switch (state->mode) {
/* SAFE can be transient or point to an actual disconnect */
case TYPEC_STATE_SAFE:
reset = retimer->orientation == TYPEC_ORIENTATION_NONE;
break;
/* USB2 pins don't even go through this chip */
case TYPEC_MODE_USB2:
reset = true;
break;
case TYPEC_STATE_USB:
case TYPEC_MODE_USB3:
cfg0 |= CONN_STATUS_0_USB_3_1_CONNECTED;
break;
case TYPEC_MODE_USB4:
eudo_data = state->data;
cfg2 |= CONN_STATUS_2_USB4_CONNECTED;
if (FIELD_GET(EUDO_CABLE_TYPE_MASK, eudo_data->eudo) != EUDO_CABLE_TYPE_PASSIVE)
cfg0 |= CONN_STATUS_0_ACTIVE_CABLE;
break;
default:
dev_err(&retimer->client->dev, "Got unsupported mode: %lu\n",
state->mode);
return -EOPNOTSUPP;
}
}
return ps883x_configure(retimer, cfg0, cfg1, cfg2, reset);
}
static int ps883x_sw_set(struct typec_switch_dev *sw,
enum typec_orientation orientation)
{
struct ps883x_retimer *retimer = typec_switch_get_drvdata(sw);
int ret = 0;
ret = typec_switch_set(retimer->typec_switch, orientation);
if (ret)
return ret;
guard(mutex)(&retimer->lock);
if (retimer->orientation != orientation) {
retimer->orientation = orientation;
/*
* Orientation notifications usually come prior to mode switch
* events. If the retimer is already in reset, we still want to
* cache the new orientation value for the subsequent ps883x_set().
*/
if (retimer->in_reset)
return 0;
ret = regmap_assign_bits(retimer->regmap, REG_USB_PORT_CONN_STATUS_0,
CONN_STATUS_0_ORIENTATION_REVERSED,
orientation == TYPEC_ORIENTATION_REVERSE);
if (ret)
dev_err(&retimer->client->dev, "failed to set orientation: %d\n", ret);
}
return ret;
}
static int ps883x_retimer_set(struct typec_retimer *rtmr,
struct typec_retimer_state *state)
{
struct ps883x_retimer *retimer = typec_retimer_get_drvdata(rtmr);
struct typec_mux_state mux_state;
int ret = 0;
mutex_lock(&retimer->lock);
ret = ps883x_set(retimer, state);
mutex_unlock(&retimer->lock);
if (ret)
return ret;
mux_state.alt = state->alt;
mux_state.data = state->data;
mux_state.mode = state->mode;
return typec_mux_set(retimer->typec_mux, &mux_state);
}
static int ps883x_get_vregs(struct ps883x_retimer *retimer)
{
struct device *dev = &retimer->client->dev;
@ -422,6 +451,9 @@ static int ps883x_retimer_probe(struct i2c_client *client)
}
}
/* Keep the retimer in reset until a Type-C notification comes */
ps883x_reset(retimer);
sw_desc.drvdata = retimer;
sw_desc.fwnode = dev_fwnode(dev);
sw_desc.set = ps883x_sw_set;