S(SNK_WAIT_CAPABILITIES_TIMEOUT), \
S(SNK_NEGOTIATE_CAPABILITIES), \
S(SNK_NEGOTIATE_PPS_CAPABILITIES), \
+ S(SNK_NEGOTIATE_SPR_AVS_CAPABILITIES), \
S(SNK_TRANSITION_SINK), \
S(SNK_TRANSITION_SINK_VBUS), \
S(SNK_READY), \
bool active;
};
+enum spr_avs_status {
+ SPR_AVS_UNKNOWN,
+ SPR_AVS_NOT_SUPPORTED,
+ SPR_AVS_SUPPORTED
+};
+
+static const char * const spr_avs_status_strings[] = {
+ [SPR_AVS_UNKNOWN] = "Unknown",
+ [SPR_AVS_SUPPORTED] = "Supported",
+ [SPR_AVS_NOT_SUPPORTED] = "Not Supported",
+};
+
+/*
+ * Standard Power Range Adjustable Voltage Supply (SPR - AVS) data
+ * @max_current_ma_9v_to_15v: Max current for 9V to 15V range derived from
+ * source cap & sink cap
+ * @max_current_ma_15v_to_20v: Max current for 15V to 20V range derived from
+ * source cap & sink cap
+ * @req_op_curr_ma: Requested operating current to the port partner acting as source
+ * @req_out_volt_mv: Requested output voltage to the port partner acting as source
+ * @max_out_volt_mv: Max SPR voltage supported by the port and the port partner
+ * @max_current_ma; MAX SPR current supported by the port and the port partner
+ * @port_partner_src_status: SPR AVS status of port partner acting as source
+ * @port_partner_src_pdo_index: PDO index of SPR AVS cap of the port partner
+ * acting as source. Valid only when
+ * port_partner_src_status is SPR_AVS_SUPPORTED.
+ * @port_snk_status: SPR AVS status of the local port acting as sink.
+ * @port_snk_pdo_index: PDO index of SPR AVS cap of local port acting as sink
+ * @active: True when the local port acting as the sink has negotiated SPR AVS
+ * with the partner acting as source.
+ */
+struct pd_spr_avs_data {
+ u32 max_current_ma_9v_to_15v;
+ u32 max_current_ma_15v_to_20v;
+ u32 req_op_curr_ma;
+ u32 req_out_volt_mv;
+ u32 max_out_volt_mv;
+ u32 max_current_ma;
+ enum spr_avs_status port_partner_src_status;
+ unsigned int port_partner_src_pdo_index;
+ enum spr_avs_status port_snk_status;
+ unsigned int port_snk_pdo_index;
+ bool active;
+};
+
struct pd_data {
struct usb_power_delivery *pd;
struct usb_power_delivery_capabilities *source_cap;
u8 spr_max_pdp;
};
+enum aug_req_type {
+ PD_PPS,
+ PD_SPR_AVS,
+};
+
struct tcpm_port {
struct device *dev;
/* PPS */
struct pd_pps_data pps_data;
- struct completion pps_complete;
- bool pps_pending;
- int pps_status;
+
+ /* SPR AVS */
+ struct pd_spr_avs_data spr_avs_data;
+
+ /* Augmented supply request - PPS; SPR_AVS */
+ struct completion aug_supply_req_complete;
+ bool aug_supply_req_pending;
+ int aug_supply_req_status;
/* Alternate mode data */
struct pd_mode_data mode_data;
switch (type) {
case PD_DATA_SOURCE_CAP:
+ port->spr_avs_data.port_partner_src_status = SPR_AVS_UNKNOWN;
for (i = 0; i < cnt; i++)
port->source_caps[i] = le32_to_cpu(msg->payload[i]);
}
}
-static void tcpm_pps_complete(struct tcpm_port *port, int result)
+static void tcpm_aug_supply_req_complete(struct tcpm_port *port, int result)
{
- if (port->pps_pending) {
- port->pps_status = result;
- port->pps_pending = false;
- complete(&port->pps_complete);
+ if (port->aug_supply_req_pending) {
+ port->aug_supply_req_status = result;
+ port->aug_supply_req_pending = false;
+ complete(&port->aug_supply_req_complete);
}
}
/* Revert data back from any requested PPS updates */
port->pps_data.req_out_volt = port->supply_voltage;
port->pps_data.req_op_curr = port->current_limit;
- port->pps_status = (type == PD_CTRL_WAIT ?
+ port->aug_supply_req_status = (type == PD_CTRL_WAIT ?
-EAGAIN : -EOPNOTSUPP);
/* Threshold was relaxed before sending Request. Restore it back. */
port->pps_data.active,
port->supply_voltage);
+ tcpm_set_state(port, SNK_READY, 0);
+ break;
+ case SNK_NEGOTIATE_SPR_AVS_CAPABILITIES:
+ /* Revert data back from any requested SPR AVS updates */
+ port->spr_avs_data.req_out_volt_mv = port->supply_voltage;
+ port->spr_avs_data.req_op_curr_ma = port->current_limit;
+ port->aug_supply_req_status = (type == PD_CTRL_WAIT ?
+ -EAGAIN : -EOPNOTSUPP);
+
+ /* Threshold was relaxed before sending Request. Restore it back. */
+ tcpm_set_auto_vbus_discharge_threshold(port, TYPEC_PWR_MODE_PD,
+ port->spr_avs_data.active,
+ port->supply_voltage);
+
tcpm_set_state(port, SNK_READY, 0);
break;
case DR_SWAP_SEND:
switch (port->state) {
case SNK_NEGOTIATE_CAPABILITIES:
port->pps_data.active = false;
+ port->spr_avs_data.active = false;
tcpm_set_state(port, SNK_TRANSITION_SINK, 0);
break;
case SNK_NEGOTIATE_PPS_CAPABILITIES:
power_supply_changed(port->psy);
tcpm_set_state(port, SNK_TRANSITION_SINK, 0);
break;
+ case SNK_NEGOTIATE_SPR_AVS_CAPABILITIES:
+ port->spr_avs_data.active = true;
+ port->req_supply_voltage = port->spr_avs_data.req_out_volt_mv;
+ port->req_current_limit = port->spr_avs_data.req_op_curr_ma;
+ power_supply_changed(port->psy);
+ tcpm_set_state(port, SNK_TRANSITION_SINK, 0);
+ break;
case SOFT_RESET_SEND:
if (port->ams == SOFT_RESET_AMS)
tcpm_ams_finish(port);
case PDO_TYPE_APDO:
if (pdo_apdo_type(pdo) == APDO_TYPE_PPS) {
port->pps_data.supported = true;
- port->usb_type =
- POWER_SUPPLY_USB_TYPE_PD_PPS;
- power_supply_changed(port->psy);
+ } else if (pdo_apdo_type(pdo) == APDO_TYPE_SPR_AVS) {
+ port->spr_avs_data.port_partner_src_status = SPR_AVS_SUPPORTED;
+ port->spr_avs_data.port_partner_src_pdo_index = i;
}
continue;
default:
min_snk_mv = pdo_min_voltage(pdo);
break;
case PDO_TYPE_APDO:
+ if (pdo_apdo_type(pdo) == APDO_TYPE_SPR_AVS) {
+ port->spr_avs_data.port_snk_status = SPR_AVS_SUPPORTED;
+ port->spr_avs_data.port_snk_pdo_index = j;
+ }
continue;
default:
tcpm_log(port, "Invalid sink PDO type, ignoring");
}
}
+ if (port->spr_avs_data.port_snk_status == SPR_AVS_UNKNOWN)
+ port->spr_avs_data.port_snk_status = SPR_AVS_NOT_SUPPORTED;
+
+ if (port->spr_avs_data.port_partner_src_status == SPR_AVS_UNKNOWN)
+ port->spr_avs_data.port_partner_src_status = SPR_AVS_NOT_SUPPORTED;
+
+ if (port->pps_data.supported &&
+ port->spr_avs_data.port_partner_src_status == SPR_AVS_SUPPORTED)
+ port->usb_type = POWER_SUPPLY_USB_TYPE_PD_PPS_SPR_AVS;
+ else if (port->pps_data.supported)
+ port->usb_type = POWER_SUPPLY_USB_TYPE_PD_PPS;
+ else if (port->spr_avs_data.port_partner_src_status == SPR_AVS_SUPPORTED)
+ port->usb_type = POWER_SUPPLY_USB_TYPE_PD_SPR_AVS;
+
+ if (port->usb_type != POWER_SUPPLY_USB_TYPE_PD)
+ power_supply_changed(port->psy);
+
return ret;
}
return src_pdo;
}
+static int tcpm_pd_select_spr_avs_apdo(struct tcpm_port *port)
+{
+ u32 req_out_volt_mv, req_op_curr_ma, src_max_curr_ma = 0, source_cap;
+ u32 snk_max_curr_ma = 0, src_pdo_index, snk_pdo_index, snk_pdo;
+
+ if (port->spr_avs_data.port_snk_status != SPR_AVS_SUPPORTED ||
+ port->spr_avs_data.port_partner_src_status !=
+ SPR_AVS_SUPPORTED) {
+ tcpm_log(port, "SPR AVS not supported. port:%s partner:%s",
+ spr_avs_status_strings[port->spr_avs_data.port_snk_status],
+ spr_avs_status_strings[port->spr_avs_data.port_partner_src_status]);
+ return -EOPNOTSUPP;
+ }
+
+ /* Round up to SPR_AVS_VOLT_MV_STEP */
+ req_out_volt_mv = port->spr_avs_data.req_out_volt_mv;
+ if (req_out_volt_mv % SPR_AVS_VOLT_MV_STEP) {
+ req_out_volt_mv += SPR_AVS_VOLT_MV_STEP -
+ (req_out_volt_mv % SPR_AVS_VOLT_MV_STEP);
+ port->spr_avs_data.req_out_volt_mv = req_out_volt_mv;
+ }
+
+ /* Round up to RDO_SPR_AVS_CURR_MA_STEP */
+ req_op_curr_ma = port->spr_avs_data.req_op_curr_ma;
+ if (req_op_curr_ma % RDO_SPR_AVS_CURR_MA_STEP) {
+ req_op_curr_ma += RDO_SPR_AVS_CURR_MA_STEP -
+ (req_op_curr_ma % RDO_SPR_AVS_CURR_MA_STEP);
+ port->spr_avs_data.req_op_curr_ma = req_op_curr_ma;
+ }
+
+ src_pdo_index = port->spr_avs_data.port_partner_src_pdo_index;
+ snk_pdo_index = port->spr_avs_data.port_snk_pdo_index;
+ source_cap = port->source_caps[src_pdo_index];
+ snk_pdo = port->snk_pdo[snk_pdo_index];
+ tcpm_log(port,
+ "SPR AVS src_pdo_index:%d snk_pdo_index:%d req_op_curr_ma roundup:%u req_out_volt_mv roundup:%u",
+ src_pdo_index, snk_pdo_index, req_op_curr_ma, req_out_volt_mv);
+
+ if (req_out_volt_mv >= SPR_AVS_TIER1_MIN_VOLT_MV &&
+ req_out_volt_mv <= SPR_AVS_TIER1_MAX_VOLT_MV) {
+ src_max_curr_ma =
+ pdo_spr_avs_apdo_9v_to_15v_max_current_ma(source_cap);
+ snk_max_curr_ma =
+ pdo_spr_avs_apdo_9v_to_15v_max_current_ma(snk_pdo);
+ } else if (req_out_volt_mv > SPR_AVS_TIER1_MAX_VOLT_MV &&
+ req_out_volt_mv <= SPR_AVS_TIER2_MAX_VOLT_MV) {
+ src_max_curr_ma =
+ pdo_spr_avs_apdo_15v_to_20v_max_current_ma(source_cap);
+ snk_max_curr_ma =
+ pdo_spr_avs_apdo_15v_to_20v_max_current_ma(snk_pdo);
+ } else {
+ tcpm_log(port, "Invalid SPR AVS req_volt:%umV", req_out_volt_mv);
+ return -EINVAL;
+ }
+
+ if (req_op_curr_ma > src_max_curr_ma ||
+ req_op_curr_ma > snk_max_curr_ma) {
+ tcpm_log(port,
+ "Invalid SPR AVS request. req_volt:%umV req_curr:%umA src_max_cur:%umA snk_max_cur:%umA",
+ req_out_volt_mv, req_op_curr_ma, src_max_curr_ma,
+ snk_max_curr_ma);
+ return -EINVAL;
+ }
+
+ /* Max SPR voltage based on both the port and the partner caps */
+ if (pdo_spr_avs_apdo_15v_to_20v_max_current_ma(snk_pdo) &&
+ pdo_spr_avs_apdo_15v_to_20v_max_current_ma(source_cap))
+ port->spr_avs_data.max_out_volt_mv = SPR_AVS_TIER2_MAX_VOLT_MV;
+ else
+ port->spr_avs_data.max_out_volt_mv = SPR_AVS_TIER1_MAX_VOLT_MV;
+
+ /*
+ * Max SPR AVS curr based on 9V to 15V. This should be higher than or
+ * equal to 15V to 20V range.
+ */
+ port->spr_avs_data.max_current_ma =
+ min(pdo_spr_avs_apdo_9v_to_15v_max_current_ma(source_cap),
+ pdo_spr_avs_apdo_9v_to_15v_max_current_ma(snk_pdo));
+
+ return src_pdo_index;
+}
+
static int tcpm_pd_build_request(struct tcpm_port *port, u32 *rdo)
{
unsigned int mv, ma, mw, flags;
return 0;
}
-static int tcpm_pd_send_pps_request(struct tcpm_port *port)
+static int tcpm_pd_build_spr_avs_request(struct tcpm_port *port, u32 *rdo)
+{
+ u32 out_mv, op_ma, flags, snk_pdo_index, source_cap;
+ unsigned int src_power_mw, snk_power_mw;
+ int src_pdo_index;
+ u32 snk_pdo;
+
+ src_pdo_index = tcpm_pd_select_spr_avs_apdo(port);
+ if (src_pdo_index < 0)
+ return src_pdo_index;
+ snk_pdo_index = port->spr_avs_data.port_snk_pdo_index;
+ source_cap = port->source_caps[src_pdo_index];
+ snk_pdo = port->snk_pdo[snk_pdo_index];
+ out_mv = port->spr_avs_data.req_out_volt_mv;
+ op_ma = port->spr_avs_data.req_op_curr_ma;
+
+ flags = RDO_USB_COMM | RDO_NO_SUSPEND;
+
+ /*
+ * Set capability mismatch when the maximum power needs in the current
+ * requested AVS voltage tier range is greater than
+ * port->operating_snk_mw, however, the maximum power offered by the
+ * source at the current requested AVS voltage tier is less than
+ * port->operating_sink_mw.
+ */
+ if (out_mv > SPR_AVS_TIER1_MAX_VOLT_MV) {
+ src_power_mw =
+ pdo_spr_avs_apdo_15v_to_20v_max_current_ma(source_cap) *
+ SPR_AVS_TIER2_MAX_VOLT_MV / 1000;
+ snk_power_mw =
+ pdo_spr_avs_apdo_15v_to_20v_max_current_ma(snk_pdo) *
+ SPR_AVS_TIER2_MAX_VOLT_MV / 1000;
+ } else {
+ src_power_mw =
+ pdo_spr_avs_apdo_9v_to_15v_max_current_ma(source_cap) *
+ SPR_AVS_TIER1_MAX_VOLT_MV / 1000;
+ snk_power_mw =
+ pdo_spr_avs_apdo_9v_to_15v_max_current_ma(snk_pdo) *
+ SPR_AVS_TIER1_MAX_VOLT_MV / 1000;
+ }
+
+ if (snk_power_mw >= port->operating_snk_mw &&
+ src_power_mw < port->operating_snk_mw)
+ flags |= RDO_CAP_MISMATCH;
+
+ *rdo = RDO_AVS(src_pdo_index + 1, out_mv, op_ma, flags);
+
+ tcpm_log(port, "Requesting APDO SPR AVS %d: %u mV, %u mA",
+ src_pdo_index, out_mv, op_ma);
+
+ return 0;
+}
+
+static int tcpm_pd_send_aug_supply_request(struct tcpm_port *port,
+ enum aug_req_type type)
{
struct pd_message msg;
int ret;
u32 rdo;
- ret = tcpm_pd_build_pps_request(port, &rdo);
+ if (type == PD_PPS) {
+ ret = tcpm_pd_build_pps_request(port, &rdo);
+ } else if (type == PD_SPR_AVS) {
+ ret = tcpm_pd_build_spr_avs_request(port, &rdo);
+ } else {
+ tcpm_log(port, "Invalid aug_req_type %d", type);
+ ret = -EOPNOTSUPP;
+ }
if (ret < 0)
return ret;
port->tcpc->set_partner_usb_comm_capable(port->tcpc, capable);
}
+static void tcpm_partner_source_caps_reset(struct tcpm_port *port)
+{
+ usb_power_delivery_unregister_capabilities(port->partner_source_caps);
+ port->partner_source_caps = NULL;
+ port->spr_avs_data.port_partner_src_status = SPR_AVS_UNKNOWN;
+ port->spr_avs_data.active = false;
+}
+
static void tcpm_reset_port(struct tcpm_port *port)
{
tcpm_enable_auto_vbus_discharge(port, false);
usb_power_delivery_unregister_capabilities(port->partner_sink_caps);
port->partner_sink_caps = NULL;
- usb_power_delivery_unregister_capabilities(port->partner_source_caps);
- port->partner_source_caps = NULL;
+ tcpm_partner_source_caps_reset(port);
usb_power_delivery_unregister(port->partner_pd);
port->partner_pd = NULL;
}
case SNK_UNATTACHED:
if (!port->non_pd_role_swap)
tcpm_swap_complete(port, -ENOTCONN);
- tcpm_pps_complete(port, -ENOTCONN);
+ tcpm_aug_supply_req_complete(port, -ENOTCONN);
tcpm_snk_detach(port);
if (port->potential_contaminant) {
tcpm_set_state(port, CHECK_CONTAMINANT, 0);
}
break;
case SNK_NEGOTIATE_PPS_CAPABILITIES:
- ret = tcpm_pd_send_pps_request(port);
+ case SNK_NEGOTIATE_SPR_AVS_CAPABILITIES:
+ ret = tcpm_pd_send_aug_supply_request(port, port->state ==
+ SNK_NEGOTIATE_PPS_CAPABILITIES ?
+ PD_PPS : PD_SPR_AVS);
if (ret < 0) {
/* Restore back to the original state */
tcpm_set_auto_vbus_discharge_threshold(port, TYPEC_PWR_MODE_PD,
port->pps_data.active,
port->supply_voltage);
- port->pps_status = ret;
+ port->aug_supply_req_status = ret;
/*
* If this was called due to updates to sink
* capabilities, and pps is no longer valid, we should
}
break;
case SNK_TRANSITION_SINK:
- /* From the USB PD spec:
- * "The Sink Shall transition to Sink Standby before a positive or
- * negative voltage transition of VBUS. During Sink Standby
- * the Sink Shall reduce its power draw to pSnkStdby."
- *
- * This is not applicable to PPS though as the port can continue
- * to draw negotiated power without switching to standby.
- */
- if (port->supply_voltage != port->req_supply_voltage && !port->pps_data.active &&
- port->current_limit * port->supply_voltage / 1000 > PD_P_SNK_STDBY_MW) {
- u32 stdby_ma = PD_P_SNK_STDBY_MW * 1000 / port->supply_voltage;
+ if (port->spr_avs_data.active) {
+ if (abs(port->req_supply_voltage - port->supply_voltage) >
+ SPR_AVS_AVS_SMALL_STEP_V * 1000) {
+ /*
+ * The Sink Shall reduce its current draw to
+ * iSnkStdby within tSnkStdby. The reduction to
+ * iSnkStdby is not required if the voltage
+ * increase is less than or equal to
+ * vAvsSmallStep.
+ */
+ tcpm_log(port,
+ "SPR AVS Setting iSnkstandby. Req vol: %u mV Curr vol: %u mV",
+ port->req_supply_voltage,
+ port->supply_voltage);
+ tcpm_set_current_limit(port, PD_I_SNK_STBY_MA,
+ port->supply_voltage);
+ }
+ /*
+ * Although tAvsSrcTransSmall is expected to be used
+ * for voltage transistions smaller than 1V, using
+ * tAvsSrcTransLarge to be resilient against chargers
+ * which strictly cannot honor tAvsSrcTransSmall to
+ * improve interoperability.
+ */
+ tcpm_set_state(port, hard_reset_state(port),
+ PD_T_AVS_SRC_TRANS_LARGE);
+ /*
+ * From the USB PD spec:
+ * "The Sink Shall transition to Sink Standby before a
+ * positive ornegative voltage transition of VBUS.
+ * During Sink Standby the Sink Shall reduce its power
+ * draw to pSnkStdby."
+ *
+ * This is not applicable to PPS though as the port can
+ * continue to draw negotiated power without switching
+ * to standby.
+ */
+ } else if (port->supply_voltage != port->req_supply_voltage &&
+ !port->pps_data.active &&
+ (port->current_limit * port->supply_voltage / 1000 >
+ PD_P_SNK_STDBY_MW)) {
+ u32 stdby_ma = PD_P_SNK_STDBY_MW * 1000 /
+ port->supply_voltage;
tcpm_log(port, "Setting standby current %u mV @ %u mA",
port->supply_voltage, stdby_ma);
- tcpm_set_current_limit(port, stdby_ma, port->supply_voltage);
+ tcpm_set_current_limit(port, stdby_ma,
+ port->supply_voltage);
+ tcpm_set_state(port, hard_reset_state(port),
+ PD_T_PS_TRANSITION);
}
- fallthrough;
+ break;
case SNK_TRANSITION_SINK_VBUS:
tcpm_set_state(port, hard_reset_state(port),
PD_T_PS_TRANSITION);
tcpm_typec_connect(port);
if (port->pd_capable && port->source_caps[0] & PDO_FIXED_DUAL_ROLE)
mod_enable_frs_delayed_work(port, 0);
- tcpm_pps_complete(port, port->pps_status);
+ tcpm_aug_supply_req_complete(port, port->aug_supply_req_status);
if (port->ams != NONE_AMS)
tcpm_ams_finish(port);
port->message_id = 0;
port->rx_msgid = -1;
/* remove existing capabilities */
- usb_power_delivery_unregister_capabilities(port->partner_source_caps);
- port->partner_source_caps = NULL;
+ tcpm_partner_source_caps_reset(port);
tcpm_pd_send_control(port, PD_CTRL_ACCEPT, TCPC_TX_SOP);
tcpm_ams_finish(port);
if (port->pwr_role == TYPEC_SOURCE) {
port->message_id = 0;
port->rx_msgid = -1;
/* remove existing capabilities */
- usb_power_delivery_unregister_capabilities(port->partner_source_caps);
- port->partner_source_caps = NULL;
+ tcpm_partner_source_caps_reset(port);
if (tcpm_pd_send_control(port, PD_CTRL_SOFT_RESET, TCPC_TX_SOP))
tcpm_set_state_cond(port, hard_reset_state(port), 0);
else
break;
case PR_SWAP_SNK_SRC_SINK_OFF:
/* will be source, remove existing capabilities */
- usb_power_delivery_unregister_capabilities(port->partner_source_caps);
- port->partner_source_caps = NULL;
+ tcpm_partner_source_caps_reset(port);
/*
* Prevent vbus discharge circuit from turning on during PR_SWAP
* as this is not a disconnect.
break;
case ERROR_RECOVERY:
tcpm_swap_complete(port, -EPROTO);
- tcpm_pps_complete(port, -EPROTO);
+ tcpm_aug_supply_req_complete(port, -EPROTO);
tcpm_set_state(port, PORT_RESET, 0);
break;
case PORT_RESET:
return ret;
}
-static int tcpm_pps_set_op_curr(struct tcpm_port *port, u16 req_op_curr)
+static int tcpm_aug_set_op_curr(struct tcpm_port *port, u16 req_op_curr_ma)
{
unsigned int target_mw;
int ret;
mutex_lock(&port->swap_lock);
mutex_lock(&port->lock);
- if (!port->pps_data.active) {
+ if (port->pps_data.active) {
+ req_op_curr_ma = req_op_curr_ma -
+ (req_op_curr_ma % RDO_PROG_CURR_MA_STEP);
+ if (req_op_curr_ma > port->pps_data.max_curr) {
+ ret = -EINVAL;
+ goto port_unlock;
+ }
+ target_mw = (req_op_curr_ma * port->supply_voltage) / 1000;
+ if (target_mw < port->operating_snk_mw) {
+ ret = -EINVAL;
+ goto port_unlock;
+ }
+ } else if (!port->spr_avs_data.active) {
ret = -EOPNOTSUPP;
goto port_unlock;
}
goto port_unlock;
}
- if (req_op_curr > port->pps_data.max_curr) {
- ret = -EINVAL;
- goto port_unlock;
- }
-
- target_mw = (req_op_curr * port->supply_voltage) / 1000;
- if (target_mw < port->operating_snk_mw) {
- ret = -EINVAL;
- goto port_unlock;
- }
+ if (port->pps_data.active)
+ port->upcoming_state = SNK_NEGOTIATE_PPS_CAPABILITIES;
+ else
+ port->upcoming_state = SNK_NEGOTIATE_SPR_AVS_CAPABILITIES;
- port->upcoming_state = SNK_NEGOTIATE_PPS_CAPABILITIES;
ret = tcpm_ams_start(port, POWER_NEGOTIATION);
if (ret == -EAGAIN) {
port->upcoming_state = INVALID_STATE;
goto port_unlock;
}
- /* Round down operating current to align with PPS valid steps */
- req_op_curr = req_op_curr - (req_op_curr % RDO_PROG_CURR_MA_STEP);
-
- reinit_completion(&port->pps_complete);
- port->pps_data.req_op_curr = req_op_curr;
- port->pps_status = 0;
- port->pps_pending = true;
+ reinit_completion(&port->aug_supply_req_complete);
+ if (port->pps_data.active)
+ port->pps_data.req_op_curr = req_op_curr_ma;
+ else
+ port->spr_avs_data.req_op_curr_ma = req_op_curr_ma;
+ port->aug_supply_req_status = 0;
+ port->aug_supply_req_pending = true;
mutex_unlock(&port->lock);
- if (!wait_for_completion_timeout(&port->pps_complete,
- msecs_to_jiffies(PD_PPS_CTRL_TIMEOUT)))
+ if (!wait_for_completion_timeout(&port->aug_supply_req_complete,
+ msecs_to_jiffies(PD_AUG_PSY_CTRL_TIMEOUT)))
ret = -ETIMEDOUT;
else
- ret = port->pps_status;
+ ret = port->aug_supply_req_status;
goto swap_unlock;
return ret;
}
-static int tcpm_pps_set_out_volt(struct tcpm_port *port, u16 req_out_volt)
+static int tcpm_aug_set_out_volt(struct tcpm_port *port, u16 req_out_volt_mv)
{
unsigned int target_mw;
int ret;
mutex_lock(&port->swap_lock);
mutex_lock(&port->lock);
- if (!port->pps_data.active) {
+ if (port->pps_data.active) {
+ req_out_volt_mv = req_out_volt_mv - (req_out_volt_mv %
+ RDO_PROG_VOLT_MV_STEP);
+ /* Round down output voltage to align with PPS valid steps */
+ target_mw = (port->current_limit * req_out_volt_mv) / 1000;
+ if (target_mw < port->operating_snk_mw) {
+ ret = -EINVAL;
+ goto port_unlock;
+ }
+ } else if (!port->spr_avs_data.active) {
ret = -EOPNOTSUPP;
goto port_unlock;
}
goto port_unlock;
}
- target_mw = (port->current_limit * req_out_volt) / 1000;
- if (target_mw < port->operating_snk_mw) {
- ret = -EINVAL;
- goto port_unlock;
- }
+ if (port->pps_data.active)
+ port->upcoming_state = SNK_NEGOTIATE_PPS_CAPABILITIES;
+ else
+ port->upcoming_state = SNK_NEGOTIATE_SPR_AVS_CAPABILITIES;
- port->upcoming_state = SNK_NEGOTIATE_PPS_CAPABILITIES;
ret = tcpm_ams_start(port, POWER_NEGOTIATION);
if (ret == -EAGAIN) {
port->upcoming_state = INVALID_STATE;
goto port_unlock;
}
- /* Round down output voltage to align with PPS valid steps */
- req_out_volt = req_out_volt - (req_out_volt % RDO_PROG_VOLT_MV_STEP);
-
- reinit_completion(&port->pps_complete);
- port->pps_data.req_out_volt = req_out_volt;
- port->pps_status = 0;
- port->pps_pending = true;
+ reinit_completion(&port->aug_supply_req_complete);
+ if (port->pps_data.active)
+ port->pps_data.req_out_volt = req_out_volt_mv;
+ else
+ port->spr_avs_data.req_out_volt_mv = req_out_volt_mv;
+ port->aug_supply_req_status = 0;
+ port->aug_supply_req_pending = true;
mutex_unlock(&port->lock);
- if (!wait_for_completion_timeout(&port->pps_complete,
- msecs_to_jiffies(PD_PPS_CTRL_TIMEOUT)))
+ if (!wait_for_completion_timeout(&port->aug_supply_req_complete,
+ msecs_to_jiffies(PD_AUG_PSY_CTRL_TIMEOUT)))
ret = -ETIMEDOUT;
else
- ret = port->pps_status;
+ ret = port->aug_supply_req_status;
goto swap_unlock;
goto port_unlock;
}
- reinit_completion(&port->pps_complete);
- port->pps_status = 0;
- port->pps_pending = true;
+ reinit_completion(&port->aug_supply_req_complete);
+ port->aug_supply_req_status = 0;
+ port->aug_supply_req_pending = true;
/* Trigger PPS request or move back to standard PDO contract */
if (activate) {
}
mutex_unlock(&port->lock);
- if (!wait_for_completion_timeout(&port->pps_complete,
- msecs_to_jiffies(PD_PPS_CTRL_TIMEOUT)))
+ if (!wait_for_completion_timeout(&port->aug_supply_req_complete,
+ msecs_to_jiffies(PD_AUG_PSY_CTRL_TIMEOUT)))
+ ret = -ETIMEDOUT;
+ else
+ ret = port->aug_supply_req_status;
+
+ goto swap_unlock;
+
+port_unlock:
+ mutex_unlock(&port->lock);
+swap_unlock:
+ mutex_unlock(&port->swap_lock);
+
+ return ret;
+}
+
+static int tcpm_spr_avs_activate(struct tcpm_port *port, bool activate)
+{
+ int ret = 0;
+
+ mutex_lock(&port->swap_lock);
+ mutex_lock(&port->lock);
+
+ if (port->spr_avs_data.port_snk_status == SPR_AVS_NOT_SUPPORTED ||
+ port->spr_avs_data.port_partner_src_status == SPR_AVS_NOT_SUPPORTED) {
+ tcpm_log(port, "SPR_AVS not supported");
+ ret = -EOPNOTSUPP;
+ goto port_unlock;
+ }
+
+ /* Trying to deactivate SPR AVS when already deactivated so just bail */
+ if (!port->spr_avs_data.active && !activate)
+ goto port_unlock;
+
+ if (port->state != SNK_READY) {
+ tcpm_log(port,
+ "SPR_AVS cannot be activated. Port not in SNK_READY");
+ ret = -EAGAIN;
+ goto port_unlock;
+ }
+
+ if (activate)
+ port->upcoming_state = SNK_NEGOTIATE_SPR_AVS_CAPABILITIES;
+ else
+ port->upcoming_state = SNK_NEGOTIATE_CAPABILITIES;
+ ret = tcpm_ams_start(port, POWER_NEGOTIATION);
+ if (ret == -EAGAIN) {
+ tcpm_log(port, "SPR_AVS cannot be %s. AMS start failed",
+ activate ? "activated" : "deactivated");
+ port->upcoming_state = INVALID_STATE;
+ goto port_unlock;
+ }
+
+ reinit_completion(&port->aug_supply_req_complete);
+ port->aug_supply_req_status = 0;
+ port->aug_supply_req_pending = true;
+
+ /* Trigger AVS request or move back to standard PDO contract */
+ if (activate) {
+ port->spr_avs_data.req_out_volt_mv = port->supply_voltage;
+ port->spr_avs_data.req_op_curr_ma = port->current_limit;
+ }
+ mutex_unlock(&port->lock);
+
+ if (!wait_for_completion_timeout(&port->aug_supply_req_complete,
+ msecs_to_jiffies(PD_AUG_PSY_CTRL_TIMEOUT)))
ret = -ETIMEDOUT;
else
- ret = port->pps_status;
+ ret = port->aug_supply_req_status;
goto swap_unlock;
break;
case SNK_NEGOTIATE_CAPABILITIES:
case SNK_NEGOTIATE_PPS_CAPABILITIES:
+ case SNK_NEGOTIATE_SPR_AVS_CAPABILITIES:
case SNK_READY:
case SNK_TRANSITION_SINK:
case SNK_TRANSITION_SINK_VBUS:
- if (port->pps_data.active)
+ if (port->pps_data.active) {
port->upcoming_state = SNK_NEGOTIATE_PPS_CAPABILITIES;
- else if (port->pd_capable)
+ } else if (port->pd_capable) {
port->upcoming_state = SNK_NEGOTIATE_CAPABILITIES;
- else
+ if (port->spr_avs_data.active) {
+ /*
+ * De-activate AVS and fallback to PD to
+ * re-evaluate whether AVS is supported in the
+ * current sink cap set.
+ */
+ port->spr_avs_data.active = false;
+ port->spr_avs_data.port_snk_status = SPR_AVS_UNKNOWN;
+ }
+ } else {
break;
-
+ }
port->update_sink_caps = true;
ret = tcpm_ams_start(port, POWER_NEGOTIATION);
enum tcpm_psy_online_states {
TCPM_PSY_OFFLINE = 0,
TCPM_PSY_FIXED_ONLINE,
- TCPM_PSY_PROG_ONLINE,
+ TCPM_PSY_PPS_ONLINE,
+ TCPM_PSY_SPR_AVS_ONLINE,
};
static enum power_supply_property tcpm_psy_props[] = {
{
if (port->vbus_charge) {
if (port->pps_data.active)
- val->intval = TCPM_PSY_PROG_ONLINE;
+ val->intval = TCPM_PSY_PPS_ONLINE;
+ else if (port->spr_avs_data.active)
+ val->intval = TCPM_PSY_SPR_AVS_ONLINE;
else
val->intval = TCPM_PSY_FIXED_ONLINE;
} else {
{
if (port->pps_data.active)
val->intval = port->pps_data.min_volt * 1000;
+ else if (port->spr_avs_data.active)
+ val->intval = SPR_AVS_TIER1_MIN_VOLT_MV * 1000;
else
val->intval = port->supply_voltage * 1000;
{
if (port->pps_data.active)
val->intval = port->pps_data.max_volt * 1000;
+ else if (port->spr_avs_data.active)
+ val->intval = port->spr_avs_data.max_out_volt_mv * 1000;
else
val->intval = port->supply_voltage * 1000;
{
if (port->pps_data.active)
val->intval = port->pps_data.max_curr * 1000;
+ else if (port->spr_avs_data.active)
+ val->intval = port->spr_avs_data.max_current_ma * 1000;
else
val->intval = port->current_limit * 1000;
return ret;
}
+static int tcpm_disable_pps_avs(struct tcpm_port *port)
+{
+ int ret = 0;
+
+ if (port->pps_data.active)
+ ret = tcpm_pps_activate(port, false);
+ else if (port->spr_avs_data.active)
+ ret = tcpm_spr_avs_activate(port, false);
+
+ return ret;
+}
+
static int tcpm_psy_set_online(struct tcpm_port *port,
const union power_supply_propval *val)
{
- int ret;
+ int ret = 0;
switch (val->intval) {
case TCPM_PSY_FIXED_ONLINE:
- ret = tcpm_pps_activate(port, false);
+ ret = tcpm_disable_pps_avs(port);
+ break;
+ case TCPM_PSY_PPS_ONLINE:
+ if (port->spr_avs_data.active)
+ ret = tcpm_spr_avs_activate(port, false);
+ if (!ret)
+ ret = tcpm_pps_activate(port, true);
break;
- case TCPM_PSY_PROG_ONLINE:
- ret = tcpm_pps_activate(port, true);
+ case TCPM_PSY_SPR_AVS_ONLINE:
+ tcpm_log(port, "request to set AVS online");
+ if (port->spr_avs_data.active)
+ return 0;
+ ret = tcpm_disable_pps_avs(port);
+ if (ret)
+ break;
+ ret = tcpm_spr_avs_activate(port, true);
break;
default:
ret = -EINVAL;
ret = tcpm_psy_set_online(port, val);
break;
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
- ret = tcpm_pps_set_out_volt(port, val->intval / 1000);
+ ret = tcpm_aug_set_out_volt(port, val->intval / 1000);
break;
case POWER_SUPPLY_PROP_CURRENT_NOW:
- if (val->intval > port->pps_data.max_curr * 1000)
- ret = -EINVAL;
- else
- ret = tcpm_pps_set_op_curr(port, val->intval / 1000);
+ ret = tcpm_aug_set_op_curr(port, val->intval / 1000);
break;
default:
ret = -EINVAL;
port->psy_desc.type = POWER_SUPPLY_TYPE_USB;
port->psy_desc.usb_types = BIT(POWER_SUPPLY_USB_TYPE_C) |
BIT(POWER_SUPPLY_USB_TYPE_PD) |
- BIT(POWER_SUPPLY_USB_TYPE_PD_PPS);
+ BIT(POWER_SUPPLY_USB_TYPE_PD_PPS) |
+ BIT(POWER_SUPPLY_USB_TYPE_PD_PPS_SPR_AVS) |
+ BIT(POWER_SUPPLY_USB_TYPE_PD_SPR_AVS);
port->psy_desc.properties = tcpm_psy_props;
port->psy_desc.num_properties = ARRAY_SIZE(tcpm_psy_props);
port->psy_desc.get_property = tcpm_psy_get_prop;
init_completion(&port->tx_complete);
init_completion(&port->swap_complete);
- init_completion(&port->pps_complete);
+ init_completion(&port->aug_supply_req_complete);
tcpm_debugfs_init(port);
err = tcpm_fw_get_caps(port, tcpc->fwnode);