On Tue, Jan 02, 2018 at 03:50:52PM +0000, Adam Thomson wrote: > This commit adds code to handle requesting of PPS APDOs. Switching > between standard PDOs and APDOs, and re-requesting an APDO to > modify operating voltage/current will be triggered by an > external call into TCPM. > > Signed-off-by: Adam Thomson <Adam.Thomson.Opensource@xxxxxxxxxxx> > --- > drivers/usb/typec/tcpm.c | 533 ++++++++++++++++++++++++++++++++++++++++++++++- > include/linux/usb/pd.h | 4 +- > include/linux/usb/tcpm.h | 2 +- > 3 files changed, 525 insertions(+), 14 deletions(-) > > diff --git a/drivers/usb/typec/tcpm.c b/drivers/usb/typec/tcpm.c > index f4d563e..b66d26c 100644 > --- a/drivers/usb/typec/tcpm.c > +++ b/drivers/usb/typec/tcpm.c > @@ -47,6 +47,7 @@ > S(SNK_DISCOVERY_DEBOUNCE_DONE), \ > S(SNK_WAIT_CAPABILITIES), \ > S(SNK_NEGOTIATE_CAPABILITIES), \ > + S(SNK_NEGOTIATE_PPS_CAPABILITIES), \ > S(SNK_TRANSITION_SINK), \ > S(SNK_TRANSITION_SINK_VBUS), \ > S(SNK_READY), \ > @@ -166,6 +167,16 @@ struct pd_mode_data { > struct typec_altmode_desc altmode_desc[SVID_DISCOVERY_MAX]; > }; > > +struct pd_pps_data { > + u32 min_volt; > + u32 max_volt; > + u32 max_curr; > + u32 out_volt; > + u32 op_curr; > + bool supported; > + bool active; > +}; > + > struct tcpm_port { > struct device *dev; > > @@ -233,6 +244,7 @@ struct tcpm_port { > struct completion swap_complete; > int swap_status; > > + unsigned int negotiated_rev; > unsigned int message_id; > unsigned int caps_count; > unsigned int hard_reset_count; > @@ -255,6 +267,7 @@ struct tcpm_port { > unsigned int nr_fixed; /* number of fixed sink PDOs */ > unsigned int nr_var; /* number of variable sink PDOs */ > unsigned int nr_batt; /* number of battery sink PDOs */ > + unsigned int nr_apdo; /* number of APDO type PDOs */ > u32 snk_vdo[VDO_MAX_OBJECTS]; > unsigned int nr_snk_vdo; > > @@ -262,6 +275,7 @@ struct tcpm_port { > unsigned int max_snk_ma; > unsigned int max_snk_mw; > unsigned int operating_snk_mw; > + bool update_sink_caps; > > /* Requested current / voltage */ > u32 current_limit; > @@ -278,8 +292,13 @@ struct tcpm_port { > /* VDO to retry if UFP responder replied busy */ > u32 vdo_retry; > > - /* Alternate mode data */ > + /* PPS */ > + struct pd_pps_data pps_data; > + struct completion pps_complete; > + bool pps_pending; > + int pps_status; > > + /* Alternate mode data */ > struct pd_mode_data mode_data; > struct typec_altmode *partner_altmode[SVID_DISCOVERY_MAX]; > struct typec_altmode *port_altmode[SVID_DISCOVERY_MAX]; > @@ -497,6 +516,16 @@ static void tcpm_log_source_caps(struct tcpm_port *port) > pdo_max_voltage(pdo), > pdo_max_power(pdo)); > break; > + case PDO_TYPE_APDO: > + if (pdo_apdo_type(pdo) == APDO_TYPE_PPS) > + scnprintf(msg, sizeof(msg), > + "%u-%u mV, %u mA", > + pdo_pps_apdo_min_voltage(pdo), > + pdo_pps_apdo_max_voltage(pdo), > + pdo_pps_apdo_max_current(pdo)); > + else > + strcpy(msg, "undefined APDO"); > + break; > default: > strcpy(msg, "undefined"); > break; > @@ -791,11 +820,13 @@ static int tcpm_pd_send_source_caps(struct tcpm_port *port) > msg.header = PD_HEADER_LE(PD_CTRL_REJECT, > port->pwr_role, > port->data_role, > + port->negotiated_rev, > port->message_id, 0); > } else { > msg.header = PD_HEADER_LE(PD_DATA_SOURCE_CAP, > port->pwr_role, > port->data_role, > + port->negotiated_rev, > port->message_id, > port->nr_src_pdo); > } > @@ -816,11 +847,13 @@ static int tcpm_pd_send_sink_caps(struct tcpm_port *port) > msg.header = PD_HEADER_LE(PD_CTRL_REJECT, > port->pwr_role, > port->data_role, > + port->negotiated_rev, > port->message_id, 0); > } else { > msg.header = PD_HEADER_LE(PD_DATA_SINK_CAP, > port->pwr_role, > port->data_role, > + port->negotiated_rev, > port->message_id, > port->nr_snk_pdo); > } > @@ -1187,6 +1220,7 @@ static void vdm_run_state_machine(struct tcpm_port *port) > msg.header = PD_HEADER_LE(PD_DATA_VENDOR_DEF, > port->pwr_role, > port->data_role, > + port->negotiated_rev, > port->message_id, port->vdo_count); > for (i = 0; i < port->vdo_count; i++) > msg.payload[i] = cpu_to_le32(port->vdo_data[i]); > @@ -1258,6 +1292,8 @@ enum pdo_err { > PDO_ERR_FIXED_NOT_SORTED, > PDO_ERR_VARIABLE_BATT_NOT_SORTED, > PDO_ERR_DUPE_PDO, > + PDO_ERR_PPS_APDO_NOT_SORTED, > + PDO_ERR_DUPE_PPS_APDO, > }; > > static const char * const pdo_err_msg[] = { > @@ -1273,6 +1309,10 @@ enum pdo_err { > " err: Variable/Battery supply pdos should be in increasing order of their minimum voltage", > [PDO_ERR_DUPE_PDO] = > " err: Variable/Batt supply pdos cannot have same min/max voltage", > + [PDO_ERR_PPS_APDO_NOT_SORTED] = > + " err: Programmable power supply apdos should be in increasing order of their maximum voltage", > + [PDO_ERR_DUPE_PPS_APDO] = > + " err: Programmable power supply apdos cannot have same min/max voltage and max current", > }; > > static enum pdo_err tcpm_caps_err(struct tcpm_port *port, const u32 *pdo, > @@ -1322,6 +1362,26 @@ static enum pdo_err tcpm_caps_err(struct tcpm_port *port, const u32 *pdo, > pdo_min_voltage(pdo[i - 1]))) > return PDO_ERR_DUPE_PDO; > break; > + /* > + * The Programmable Power Supply APDOs, if present, > + * shall be sent in Maximum Voltage order; > + * lowest to highest. > + */ > + case PDO_TYPE_APDO: > + if (pdo_apdo_type(pdo[i]) != APDO_TYPE_PPS) > + break; > + > + if (pdo_pps_apdo_max_current(pdo[i]) < > + pdo_pps_apdo_max_current(pdo[i - 1])) > + return PDO_ERR_PPS_APDO_NOT_SORTED; > + else if ((pdo_pps_apdo_min_voltage(pdo[i]) == > + pdo_pps_apdo_min_voltage(pdo[i - 1])) && > + (pdo_pps_apdo_max_voltage(pdo[i]) == > + pdo_pps_apdo_max_voltage(pdo[i - 1])) && > + (pdo_pps_apdo_max_current(pdo[i]) == > + pdo_pps_apdo_max_current(pdo[i - 1]))) > + return PDO_ERR_DUPE_PPS_APDO; > + break; > default: > tcpm_log_force(port, " Unknown pdo type"); > } > @@ -1347,11 +1407,16 @@ static int tcpm_validate_caps(struct tcpm_port *port, const u32 *pdo, > /* > * PD (data, control) command handling functions > */ > + > +static int tcpm_pd_send_control(struct tcpm_port *port, > + enum pd_ctrl_msg_type type); > + > static void tcpm_pd_data_request(struct tcpm_port *port, > const struct pd_message *msg) > { > enum pd_data_msg_type type = pd_header_type_le(msg->header); > unsigned int cnt = pd_header_cnt_le(msg->header); > + unsigned int rev = pd_header_rev_le(msg->header); > unsigned int i; > > switch (type) { > @@ -1370,6 +1435,16 @@ static void tcpm_pd_data_request(struct tcpm_port *port, > port->nr_source_caps); > > /* > + * Adjust revision in subsequent message headers, as required, > + * to comply with 6.2.1.1.5 of the USB PD 3.0 spec. We don't > + * support Rev 1.0 so just do nothing in that scenario. > + */ > + if (rev == PD_REV10) > + break; > + else if (rev < PD_MAX_REV) > + port->negotiated_rev = rev; > + > + /* > * This message may be received even if VBUS is not > * present. This is quite unexpected; see USB PD > * specification, sections 8.3.3.6.3.1 and 8.3.3.6.3.2. > @@ -1390,6 +1465,19 @@ static void tcpm_pd_data_request(struct tcpm_port *port, > tcpm_queue_message(port, PD_MSG_CTRL_REJECT); > break; > } > + > + /* > + * Adjust revision in subsequent message headers, as required, > + * to comply with 6.2.1.1.5 of the USB PD 3.0 spec. We don't > + * support Rev 1.0 so just reject in that scenario. > + */ > + if (rev == PD_REV10) { > + tcpm_queue_message(port, PD_MSG_CTRL_REJECT); > + break; > + } else if (rev < PD_MAX_REV) { > + port->negotiated_rev = rev; > + } > + > port->sink_request = le32_to_cpu(msg->payload[0]); > tcpm_set_state(port, SRC_NEGOTIATE_CAPABILITIES, 0); > break; > @@ -1414,6 +1502,15 @@ static void tcpm_pd_data_request(struct tcpm_port *port, > } > } > > +static void tcpm_pps_complete(struct tcpm_port *port, int result) > +{ > + if (port->pps_pending) { > + port->pps_status = result; > + port->pps_pending = false; > + complete(&port->pps_complete); > + } > +} > + > static void tcpm_pd_ctrl_request(struct tcpm_port *port, > const struct pd_message *msg) > { > @@ -1490,6 +1587,14 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port, > next_state = SNK_WAIT_CAPABILITIES; > tcpm_set_state(port, next_state, 0); > break; > + case SNK_NEGOTIATE_PPS_CAPABILITIES: > + /* Revert data back from any requested PPS updates */ > + port->pps_data.out_volt = port->supply_voltage; > + port->pps_data.op_curr = port->current_limit; > + port->pps_status = (type == PD_CTRL_WAIT ? > + -EAGAIN : -EOPNOTSUPP); > + tcpm_set_state(port, SNK_READY, 0); > + break; > case DR_SWAP_SEND: > port->swap_status = (type == PD_CTRL_WAIT ? > -EAGAIN : -EOPNOTSUPP); > @@ -1512,6 +1617,13 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port, > case PD_CTRL_ACCEPT: > switch (port->state) { > case SNK_NEGOTIATE_CAPABILITIES: > + port->pps_data.active = false; > + tcpm_set_state(port, SNK_TRANSITION_SINK, 0); > + break; > + case SNK_NEGOTIATE_PPS_CAPABILITIES: > + port->pps_data.active = true; > + port->supply_voltage = port->pps_data.out_volt; > + port->current_limit = port->pps_data.op_curr; > tcpm_set_state(port, SNK_TRANSITION_SINK, 0); > break; > case SOFT_RESET_SEND: > @@ -1666,6 +1778,7 @@ static int tcpm_pd_send_control(struct tcpm_port *port, > memset(&msg, 0, sizeof(msg)); > msg.header = PD_HEADER_LE(type, port->pwr_role, > port->data_role, > + port->negotiated_rev, > port->message_id, 0); > > return tcpm_pd_transmit(port, TCPC_TX_SOP, &msg); > @@ -1779,6 +1892,8 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port, int *sink_pdo, > unsigned int i, j, max_mw = 0, max_mv = 0, mw = 0, mv = 0, ma = 0; > int ret = -EINVAL; > > + port->pps_data.supported = false; > + > /* > * Select the source PDO providing the most power which has a > * matchig sink cap. > @@ -1787,7 +1902,8 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port, int *sink_pdo, > u32 pdo = port->source_caps[i]; > enum pd_pdo_type type = pdo_type(pdo); > > - if (type == PDO_TYPE_FIXED) { > + switch (type) { > + case PDO_TYPE_FIXED: > for (j = 0; j < port->nr_fixed; j++) { > if (pdo_fixed_voltage(pdo) == > pdo_fixed_voltage(port->snk_pdo[j])) { > @@ -1809,7 +1925,8 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port, int *sink_pdo, > break; > } > } > - } else if (type == PDO_TYPE_BATT) { > + break; > + case PDO_TYPE_BATT: > for (j = port->nr_fixed; > j < port->nr_fixed + > port->nr_batt; > @@ -1830,7 +1947,8 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port, int *sink_pdo, > } > } > } > - } else if (type == PDO_TYPE_VAR) { > + break; > + case PDO_TYPE_VAR: > for (j = port->nr_fixed + > port->nr_batt; > j < port->nr_fixed + > @@ -1854,12 +1972,98 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port, int *sink_pdo, > } > } > } > + break; > + case PDO_TYPE_APDO: > + if (pdo_apdo_type(pdo) == APDO_TYPE_PPS) > + port->pps_data.supported = true; > + continue; > + default: > + tcpm_log(port, "Invalid PDO type, ignoring"); > + continue; > } > } > > return ret; > } > > +#define min_pps_apdo_current(x, y) \ > + min(pdo_pps_apdo_max_current(x), pdo_pps_apdo_max_current(y)) > + > +static unsigned int tcpm_pd_select_pps_apdo(struct tcpm_port *port, > + int *snk_pdo, int *src_pdo) > +{ > + unsigned int i, j, max_mw = 0, max_mv = 0, mw = 0, mv = 0, ma = 0; > + enum pd_pdo_type type; > + u32 pdo; > + int ret = -EOPNOTSUPP; > + > + /* > + * Select the source PPS APDO providing the most power while staying > + * within the board's limits. We skip the first PDO as this is always > + * 5V 3A. > + */ > + *src_pdo = 0; > + for (i = 1; i < port->nr_source_caps; ++i) { > + pdo = port->source_caps[i]; > + type = pdo_type(pdo); > + > + switch (type) { > + case PDO_TYPE_APDO: > + if (pdo_apdo_type(pdo) != APDO_TYPE_PPS) { > + tcpm_log(port, "Not PPS APDO, ignoring"); > + continue; > + } > + > + for (j = port->nr_fixed + > + port->nr_batt + > + port->nr_var; > + j < port->nr_fixed + > + port->nr_batt + > + port->nr_var + > + port->nr_apdo; > + ++j) { > + if ((pdo_pps_apdo_min_voltage(pdo) >= > + pdo_pps_apdo_min_voltage(port->snk_pdo[j])) && > + (pdo_pps_apdo_max_voltage(pdo) <= > + pdo_pps_apdo_max_voltage(port->snk_pdo[j]))) { > + ma = min_pps_apdo_current(pdo, > + port->snk_pdo[j]); > + mv = pdo_pps_apdo_max_voltage(pdo); > + mw = (ma * mv) / 1000; > + if ((mw > max_mw) || > + ((mw == max_mw) && (mv > max_mv))) { > + ret = 0; > + *src_pdo = i; > + *snk_pdo = j; > + max_mw = mw; > + max_mv = mv; > + } > + } > + } > + > + break; > + default: > + tcpm_log(port, "Not APDO type, ignoring"); > + continue; > + } > + } > + > + if (*src_pdo > 0) { > + pdo = port->source_caps[*src_pdo]; > + > + port->pps_data.min_volt = pdo_pps_apdo_min_voltage(pdo); > + port->pps_data.max_volt = pdo_pps_apdo_max_voltage(pdo); > + port->pps_data.max_curr = > + min_pps_apdo_current(pdo, port->snk_pdo[*snk_pdo]); > + port->pps_data.out_volt = > + min(port->pps_data.out_volt, pdo_pps_apdo_max_voltage(pdo)); > + port->pps_data.op_curr = > + min(port->pps_data.op_curr, pdo_pps_apdo_max_current(pdo)); > + } > + > + return ret; > +} > + > static int tcpm_pd_build_request(struct tcpm_port *port, u32 *rdo) > { > unsigned int mv, ma, mw, flags; > @@ -1875,10 +2079,18 @@ static int tcpm_pd_build_request(struct tcpm_port *port, u32 *rdo) > matching_snk_pdo = port->snk_pdo[snk_pdo_index]; > type = pdo_type(pdo); > > - if (type == PDO_TYPE_FIXED) > + switch (type) { > + case PDO_TYPE_FIXED: > mv = pdo_fixed_voltage(pdo); > - else > + break; > + case PDO_TYPE_BATT: > + case PDO_TYPE_VAR: > mv = pdo_min_voltage(pdo); > + break; > + default: > + tcpm_log(port, "Invalid PDO selected!"); > + return -EINVAL; > + } > > /* Select maximum available current within the sink pdo's limit */ > if (type == PDO_TYPE_BATT) { > @@ -1943,6 +2155,107 @@ static int tcpm_pd_send_request(struct tcpm_port *port) > msg.header = PD_HEADER_LE(PD_DATA_REQUEST, > port->pwr_role, > port->data_role, > + port->negotiated_rev, > + port->message_id, 1); > + msg.payload[0] = cpu_to_le32(rdo); > + > + return tcpm_pd_transmit(port, TCPC_TX_SOP, &msg); > +} > + > +static int tcpm_pd_build_pps_request(struct tcpm_port *port, u32 *rdo) > +{ > + unsigned int out_mv, op_ma, op_mw, min_mv, max_mv, max_ma, flags; > + enum pd_pdo_type type; > + int src_pdo_index, snk_pdo_index; > + u32 pdo, matching_snk_pdo; > + int ret; > + > + ret = tcpm_pd_select_pps_apdo(port, &snk_pdo_index, &src_pdo_index); > + if (ret) > + return ret; > + > + pdo = port->source_caps[src_pdo_index]; > + matching_snk_pdo = port->snk_pdo[snk_pdo_index]; > + type = pdo_type(pdo); > + > + switch (type) { > + case PDO_TYPE_APDO: > + if (pdo_apdo_type(pdo) != APDO_TYPE_PPS) { > + tcpm_log(port, "Invalid APDO selected!"); > + return -EINVAL; > + } > + min_mv = pdo_pps_apdo_min_voltage(pdo); > + max_mv = pdo_pps_apdo_max_voltage(pdo); > + max_ma = pdo_pps_apdo_max_current(pdo); > + out_mv = port->pps_data.out_volt; > + op_ma = port->pps_data.op_curr; > + break; > + default: > + tcpm_log(port, "Invalid PDO selected!"); > + return -EINVAL; > + } > + > + flags = RDO_USB_COMM | RDO_NO_SUSPEND; > + > + op_mw = (op_ma * out_mv) / 1000; > + if (op_mw < port->operating_snk_mw) { > + /* > + * Try raising current to meet power needs. If that's not enough > + * then try upping the voltage. If that's still not enoguh > + * then we've obviously chosen a PPS APDO which really isn't > + * suitable so abandon ship. > + */ > + op_ma = ((port->operating_snk_mw * 1000) / out_mv); > + if ((port->operating_snk_mw * 1000) % out_mv) > + ++op_ma; > + op_ma += RDO_PROG_CURR_MA_STEP - (op_ma % RDO_PROG_CURR_MA_STEP); > + > + if (op_ma > max_ma) { > + op_ma = max_ma; > + out_mv = ((port->operating_snk_mw * 1000) / op_ma); > + if ((port->operating_snk_mw * 1000) % op_ma) > + ++out_mv; > + out_mv += RDO_PROG_VOLT_MV_STEP - > + (out_mv % RDO_PROG_VOLT_MV_STEP); > + > + if (out_mv > max_mv) { > + tcpm_log(port, "Invalid PPS APDO selected!"); > + return -EINVAL; > + } > + } > + } > + > + tcpm_log(port, "cc=%d cc1=%d cc2=%d vbus=%d vconn=%s polarity=%d", > + port->cc_req, port->cc1, port->cc2, port->vbus_source, > + port->vconn_role == TYPEC_SOURCE ? "source" : "sink", > + port->polarity); > + > + *rdo = RDO_PROG(src_pdo_index + 1, out_mv, op_ma, flags); > + > + tcpm_log(port, "Requesting APDO %d: %u mV, %u mA", > + src_pdo_index, out_mv, op_ma); > + > + port->pps_data.op_curr = op_ma; > + port->pps_data.out_volt = out_mv; > + > + return 0; > +} > + > +static int tcpm_pd_send_pps_request(struct tcpm_port *port) > +{ > + struct pd_message msg; > + int ret; > + u32 rdo; > + > + ret = tcpm_pd_build_pps_request(port, &rdo); > + if (ret < 0) > + return ret; > + > + memset(&msg, 0, sizeof(msg)); > + msg.header = PD_HEADER_LE(PD_DATA_REQUEST, > + port->pwr_role, > + port->data_role, > + port->negotiated_rev, > port->message_id, 1); > msg.payload[0] = cpu_to_le32(rdo); > > @@ -2128,6 +2441,7 @@ static void tcpm_reset_port(struct tcpm_port *port) > tcpm_typec_disconnect(port); > port->attached = false; > port->pd_capable = false; > + port->pps_data.supported = false; > > /* > * First Rx ID should be 0; set this to a sentinel of -1 so that > @@ -2143,6 +2457,8 @@ static void tcpm_reset_port(struct tcpm_port *port) > tcpm_set_attached_state(port, false); > port->try_src_count = 0; > port->try_snk_count = 0; > + port->supply_voltage = 0; > + port->current_limit = 0; > } > > static void tcpm_detach(struct tcpm_port *port) > @@ -2389,6 +2705,7 @@ static void run_state_machine(struct tcpm_port *port) > typec_set_pwr_opmode(port->typec_port, opmode); > port->pwr_opmode = TYPEC_PWR_MODE_USB; > port->caps_count = 0; > + port->negotiated_rev = PD_MAX_REV; > port->message_id = 0; > port->rx_msgid = -1; > port->explicit_contract = false; > @@ -2449,6 +2766,7 @@ static void run_state_machine(struct tcpm_port *port) > > tcpm_swap_complete(port, 0); > tcpm_typec_connect(port); > + > tcpm_check_send_discover(port); > /* > * 6.3.5 > @@ -2472,6 +2790,7 @@ static void run_state_machine(struct tcpm_port *port) > case SNK_UNATTACHED: > if (!port->non_pd_role_swap) > tcpm_swap_complete(port, -ENOTCONN); > + tcpm_pps_complete(port, -ENOTCONN); > tcpm_snk_detach(port); > if (tcpm_start_drp_toggling(port)) { > tcpm_set_state(port, DRP_TOGGLING, 0); > @@ -2480,6 +2799,7 @@ static void run_state_machine(struct tcpm_port *port) > tcpm_set_cc(port, TYPEC_CC_RD); > if (port->port_type == TYPEC_PORT_DRP) > tcpm_set_state(port, SRC_UNATTACHED, PD_T_DRP_SRC); > + > break; > case SNK_ATTACH_WAIT: > if ((port->cc1 == TYPEC_CC_OPEN && > @@ -2561,6 +2881,7 @@ static void run_state_machine(struct tcpm_port *port) > port->cc2 : port->cc1); > typec_set_pwr_opmode(port->typec_port, opmode); > port->pwr_opmode = TYPEC_PWR_MODE_USB; > + port->negotiated_rev = PD_MAX_REV; > port->message_id = 0; > port->rx_msgid = -1; > port->explicit_contract = false; > @@ -2631,6 +2952,24 @@ static void run_state_machine(struct tcpm_port *port) > PD_T_SENDER_RESPONSE); > } > break; > + case SNK_NEGOTIATE_PPS_CAPABILITIES: > + ret = tcpm_pd_send_pps_request(port); > + if (ret < 0) { > + port->pps_status = ret; > + /* > + * If this was called due to updates to sink > + * capabilities, and pps is no longer valid, we should > + * safely fall back to a standard PDO. > + */ > + if (port->update_sink_caps) > + tcpm_set_state(port, SNK_NEGOTIATE_CAPABILITIES, 0); > + else > + tcpm_set_state(port, SNK_READY, 0); > + } else { > + tcpm_set_state_cond(port, hard_reset_state(port), > + PD_T_SENDER_RESPONSE); > + } > + break; > case SNK_TRANSITION_SINK: > case SNK_TRANSITION_SINK_VBUS: > tcpm_set_state(port, hard_reset_state(port), > @@ -2638,6 +2977,7 @@ static void run_state_machine(struct tcpm_port *port) > break; > case SNK_READY: > port->try_snk_count = 0; > + port->update_sink_caps = false; > if (port->explicit_contract) { > typec_set_pwr_opmode(port->typec_port, > TYPEC_PWR_MODE_PD); > @@ -2646,7 +2986,11 @@ static void run_state_machine(struct tcpm_port *port) > > tcpm_swap_complete(port, 0); > tcpm_typec_connect(port); > + > tcpm_check_send_discover(port); > + > + tcpm_pps_complete(port, port->pps_status); > + > break; > > /* Accessory states */ > @@ -2693,6 +3037,7 @@ static void run_state_machine(struct tcpm_port *port) > tcpm_set_state(port, SRC_UNATTACHED, PD_T_PS_SOURCE_ON); > break; > case SNK_HARD_RESET_SINK_OFF: > + memset(&port->pps_data, 0, sizeof(port->pps_data)); > tcpm_set_vconn(port, false); > tcpm_set_charge(port, false); > tcpm_set_roles(port, false, TYPEC_SINK, TYPEC_DEVICE); > @@ -2913,6 +3258,7 @@ static void run_state_machine(struct tcpm_port *port) > break; > case ERROR_RECOVERY: > tcpm_swap_complete(port, -EPROTO); > + tcpm_pps_complete(port, -EPROTO); > tcpm_set_state(port, PORT_RESET, 0); > break; > case PORT_RESET: > @@ -3378,7 +3724,7 @@ static int tcpm_dr_set(const struct typec_capability *cap, > mutex_unlock(&port->lock); > > if (!wait_for_completion_timeout(&port->swap_complete, > - msecs_to_jiffies(PD_ROLE_SWAP_TIMEOUT))) > + msecs_to_jiffies(PD_STATE_MACHINE_TIMEOUT))) > ret = -ETIMEDOUT; > else > ret = port->swap_status; > @@ -3423,7 +3769,7 @@ static int tcpm_pr_set(const struct typec_capability *cap, > mutex_unlock(&port->lock); > > if (!wait_for_completion_timeout(&port->swap_complete, > - msecs_to_jiffies(PD_ROLE_SWAP_TIMEOUT))) > + msecs_to_jiffies(PD_STATE_MACHINE_TIMEOUT))) > ret = -ETIMEDOUT; > else > ret = port->swap_status; > @@ -3463,7 +3809,7 @@ static int tcpm_vconn_set(const struct typec_capability *cap, > mutex_unlock(&port->lock); > > if (!wait_for_completion_timeout(&port->swap_complete, > - msecs_to_jiffies(PD_ROLE_SWAP_TIMEOUT))) > + msecs_to_jiffies(PD_STATE_MACHINE_TIMEOUT))) > ret = -ETIMEDOUT; > else > ret = port->swap_status; > @@ -3495,6 +3841,162 @@ static int tcpm_try_role(const struct typec_capability *cap, int role) > return ret; > } > > +static int tcpm_pps_set_op_curr(struct tcpm_port *port, u16 op_curr) > +{ > + unsigned int target_mw; > + int ret = 0; > + > + mutex_lock(&port->swap_lock); > + mutex_lock(&port->lock); > + > + if (!port->pps_data.active) { > + ret = -EOPNOTSUPP; > + goto port_unlock; > + } > + > + if (port->state != SNK_READY) { > + ret = -EAGAIN; > + goto port_unlock; > + } > + > + if (op_curr > port->pps_data.max_curr) { > + ret = -EINVAL; > + goto port_unlock; > + } > + > + target_mw = (op_curr * port->pps_data.out_volt) / 1000; > + if (target_mw < port->operating_snk_mw) { > + ret = -EINVAL; > + goto port_unlock; > + } > + > + reinit_completion(&port->pps_complete); > + port->pps_data.op_curr = op_curr; > + port->pps_status = 0; > + port->pps_pending = true; > + tcpm_set_state(port, SNK_NEGOTIATE_PPS_CAPABILITIES, 0); Why not just take the swap_lock here.. > + mutex_unlock(&port->lock); > + > + if (!wait_for_completion_timeout(&port->pps_complete, > + msecs_to_jiffies(PD_STATE_MACHINE_TIMEOUT))) > + ret = -ETIMEDOUT; > + else > + ret = port->pps_status; > + > + goto swap_unlock; and you don't need that goto.. > +port_unlock: > + mutex_unlock(&port->lock); > +swap_unlock: > + mutex_unlock(&port->swap_lock); and this becomes.. mutex_unlock(&port->swap_lock); return ret; port_unlock: mutex_unlock(&port->lock); return ret; > + return ret; > +} > + > +static int tcpm_pps_set_out_volt(struct tcpm_port *port, u16 out_volt) > +{ > + unsigned int target_mw; > + int ret = 0; > + > + mutex_lock(&port->swap_lock); > + mutex_lock(&port->lock); > + > + if (!port->pps_data.active) { > + ret = -EOPNOTSUPP; > + goto port_unlock; Or, on top of what I said above, you could actually consider releasing the port lock here and just returning. Then you would not need those port_unlock and swap_unlock labels at all.. mutex_unlock(&port->lock); return -EOPNOTSUPP; > + } > + > + if (port->state != SNK_READY) { > + ret = -EAGAIN; > + goto port_unlock; mutex_unlock(&port->lock); return -EAGAIN; > + } > + > + if ((out_volt < port->pps_data.min_volt) || > + (out_volt > port->pps_data.max_volt)) { > + ret = -EINVAL; > + goto port_unlock; mutex_unlock(&port->lock); return -EINVAL; > + } > + > + target_mw = (port->pps_data.op_curr * out_volt) / 1000; > + if (target_mw < port->operating_snk_mw) { > + ret = -EINVAL; > + goto port_unlock; mutex_unlock(&port->lock); return -EINVAL; > + } > + > + reinit_completion(&port->pps_complete); > + port->pps_data.out_volt = out_volt; > + port->pps_status = 0; > + port->pps_pending = true; > + tcpm_set_state(port, SNK_NEGOTIATE_PPS_CAPABILITIES, 0); mutex_lock(&port->swap_lock); > + mutex_unlock(&port->lock); > + > + if (!wait_for_completion_timeout(&port->pps_complete, > + msecs_to_jiffies(PD_STATE_MACHINE_TIMEOUT))) > + ret = -ETIMEDOUT; > + else > + ret = port->pps_status; mutex_unlock(&port->swap_lock); return ret; > + goto swap_unlock; > + > +port_unlock: > + mutex_unlock(&port->lock); > +swap_unlock: > + mutex_unlock(&port->swap_lock); > + > + return ret; > +} > + > +static int tcpm_pps_activate(struct tcpm_port *port, bool activate) > +{ > + int ret = 0; > + > + mutex_lock(&port->swap_lock); > + mutex_lock(&port->lock); > + > + if (!port->pps_data.supported) { > + ret = -EOPNOTSUPP; > + goto port_unlock; > + } > + > + /* Trying to deactivate PPS when already deactivated so just bail */ > + if ((!port->pps_data.active) && (!activate)) > + goto port_unlock; > + > + if (port->state != SNK_READY) { > + ret = -EAGAIN; > + goto port_unlock; > + } > + > + reinit_completion(&port->pps_complete); > + port->pps_status = 0; > + port->pps_pending = true; > + > + /* Trigger PPS request or move back to standard PDO contract */ > + if (activate) { > + port->pps_data.out_volt = port->supply_voltage; > + port->pps_data.op_curr = port->current_limit; > + tcpm_set_state(port, SNK_NEGOTIATE_PPS_CAPABILITIES, 0); > + } else { > + tcpm_set_state(port, SNK_NEGOTIATE_CAPABILITIES, 0); > + } > + mutex_unlock(&port->lock); > + > + if (!wait_for_completion_timeout(&port->pps_complete, > + msecs_to_jiffies(PD_STATE_MACHINE_TIMEOUT))) > + ret = -ETIMEDOUT; > + else > + ret = port->pps_status; > + > + goto swap_unlock; > + > +port_unlock: > + mutex_unlock(&port->lock); > +swap_unlock: > + mutex_unlock(&port->swap_lock); You can do the same as above here as well. > + return ret; > +} > + > static void tcpm_init(struct tcpm_port *port) > { > enum typec_cc_status cc1, cc2; > @@ -3634,13 +4136,18 @@ int tcpm_update_sink_capabilities(struct tcpm_port *port, const u32 *pdo, > port->max_snk_ma = max_snk_ma; > port->max_snk_mw = max_snk_mw; > port->operating_snk_mw = operating_snk_mw; > + port->update_sink_caps = true; > > switch (port->state) { > case SNK_NEGOTIATE_CAPABILITIES: > + case SNK_NEGOTIATE_PPS_CAPABILITIES: > case SNK_READY: > case SNK_TRANSITION_SINK: > case SNK_TRANSITION_SINK_VBUS: > - tcpm_set_state(port, SNK_NEGOTIATE_CAPABILITIES, 0); > + if (port->pps_data.active) > + tcpm_set_state(port, SNK_NEGOTIATE_PPS_CAPABILITIES, 0); > + else > + tcpm_set_state(port, SNK_NEGOTIATE_CAPABILITIES, 0); > break; > default: > break; > @@ -3695,6 +4202,7 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc) > > init_completion(&port->tx_complete); > init_completion(&port->swap_complete); > + init_completion(&port->pps_complete); > tcpm_debugfs_init(port); > > if (tcpm_validate_caps(port, tcpc->config->src_pdo, > @@ -3717,6 +4225,9 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc) > port->nr_batt = nr_type_pdos(port->snk_pdo, > port->nr_snk_pdo, > PDO_TYPE_BATT); > + port->nr_apdo = nr_type_pdos(port->snk_pdo, > + port->nr_snk_pdo, > + PDO_TYPE_APDO); > port->nr_snk_vdo = tcpm_copy_vdos(port->snk_vdo, tcpc->config->snk_vdo, > tcpc->config->nr_snk_vdo); > > @@ -3732,7 +4243,7 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc) > port->typec_caps.prefer_role = tcpc->config->default_role; > port->typec_caps.type = tcpc->config->type; > port->typec_caps.revision = 0x0120; /* Type-C spec release 1.2 */ > - port->typec_caps.pd_revision = 0x0200; /* USB-PD spec release 2.0 */ > + port->typec_caps.pd_revision = 0x0300; /* USB-PD spec release 3.0 */ > port->typec_caps.dr_set = tcpm_dr_set; > port->typec_caps.pr_set = tcpm_pr_set; > port->typec_caps.vconn_set = tcpm_vconn_set; > diff --git a/include/linux/usb/pd.h b/include/linux/usb/pd.h > index ff359bdf..09b570f 100644 > --- a/include/linux/usb/pd.h > +++ b/include/linux/usb/pd.h > @@ -103,8 +103,8 @@ enum pd_ext_msg_type { > (((cnt) & PD_HEADER_CNT_MASK) << PD_HEADER_CNT_SHIFT) | \ > ((ext_hdr) ? PD_HEADER_EXT_HDR : 0)) > > -#define PD_HEADER_LE(type, pwr, data, id, cnt) \ > - cpu_to_le16(PD_HEADER((type), (pwr), (data), PD_REV20, (id), (cnt), (0))) > +#define PD_HEADER_LE(type, pwr, data, rev, id, cnt) \ > + cpu_to_le16(PD_HEADER((type), (pwr), (data), (rev), (id), (cnt), (0))) > > static inline unsigned int pd_header_cnt(u16 header) > { > diff --git a/include/linux/usb/tcpm.h b/include/linux/usb/tcpm.h > index ca1c0b5..d6673f7 100644 > --- a/include/linux/usb/tcpm.h > +++ b/include/linux/usb/tcpm.h > @@ -35,7 +35,7 @@ enum typec_cc_polarity { > > /* Time to wait for TCPC to complete transmit */ > #define PD_T_TCPC_TX_TIMEOUT 100 /* in ms */ > -#define PD_ROLE_SWAP_TIMEOUT (MSEC_PER_SEC * 10) > +#define PD_STATE_MACHINE_TIMEOUT (MSEC_PER_SEC * 10) > > enum tcpm_transmit_status { > TCPC_TX_SUCCESS = 0, > -- > 1.9.1 -- heikki -- To unsubscribe from this list: send the line "unsubscribe linux-usb" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html