Commit 2eadc33f authored by Adam Thomson's avatar Adam Thomson Committed by Greg Kroah-Hartman

typec: tcpm: Add core support for sink side PPS

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: default avatarAdam Thomson <Adam.Thomson.Opensource@diasemi.com>
Acked-by: default avatarHeikki Krogerus <heikki.krogerus@linux.intel.com>
Reviewed-by: default avatarGuenter Roeck <linux@roeck-us.net>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent 7eced58d
...@@ -48,6 +48,7 @@ ...@@ -48,6 +48,7 @@
S(SNK_DISCOVERY_DEBOUNCE_DONE), \ S(SNK_DISCOVERY_DEBOUNCE_DONE), \
S(SNK_WAIT_CAPABILITIES), \ S(SNK_WAIT_CAPABILITIES), \
S(SNK_NEGOTIATE_CAPABILITIES), \ S(SNK_NEGOTIATE_CAPABILITIES), \
S(SNK_NEGOTIATE_PPS_CAPABILITIES), \
S(SNK_TRANSITION_SINK), \ S(SNK_TRANSITION_SINK), \
S(SNK_TRANSITION_SINK_VBUS), \ S(SNK_TRANSITION_SINK_VBUS), \
S(SNK_READY), \ S(SNK_READY), \
...@@ -167,6 +168,16 @@ struct pd_mode_data { ...@@ -167,6 +168,16 @@ struct pd_mode_data {
struct typec_altmode_desc altmode_desc[SVID_DISCOVERY_MAX]; 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 tcpm_port {
struct device *dev; struct device *dev;
...@@ -235,6 +246,7 @@ struct tcpm_port { ...@@ -235,6 +246,7 @@ struct tcpm_port {
struct completion swap_complete; struct completion swap_complete;
int swap_status; int swap_status;
unsigned int negotiated_rev;
unsigned int message_id; unsigned int message_id;
unsigned int caps_count; unsigned int caps_count;
unsigned int hard_reset_count; unsigned int hard_reset_count;
...@@ -258,6 +270,7 @@ struct tcpm_port { ...@@ -258,6 +270,7 @@ struct tcpm_port {
unsigned int nr_snk_vdo; unsigned int nr_snk_vdo;
unsigned int operating_snk_mw; unsigned int operating_snk_mw;
bool update_sink_caps;
/* Requested current / voltage */ /* Requested current / voltage */
u32 current_limit; u32 current_limit;
...@@ -274,8 +287,13 @@ struct tcpm_port { ...@@ -274,8 +287,13 @@ struct tcpm_port {
/* VDO to retry if UFP responder replied busy */ /* VDO to retry if UFP responder replied busy */
u32 vdo_retry; 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 pd_mode_data mode_data;
struct typec_altmode *partner_altmode[SVID_DISCOVERY_MAX]; struct typec_altmode *partner_altmode[SVID_DISCOVERY_MAX];
struct typec_altmode *port_altmode[SVID_DISCOVERY_MAX]; struct typec_altmode *port_altmode[SVID_DISCOVERY_MAX];
...@@ -493,6 +511,16 @@ static void tcpm_log_source_caps(struct tcpm_port *port) ...@@ -493,6 +511,16 @@ static void tcpm_log_source_caps(struct tcpm_port *port)
pdo_max_voltage(pdo), pdo_max_voltage(pdo),
pdo_max_power(pdo)); pdo_max_power(pdo));
break; 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: default:
strcpy(msg, "undefined"); strcpy(msg, "undefined");
break; break;
...@@ -790,11 +818,13 @@ static int tcpm_pd_send_source_caps(struct tcpm_port *port) ...@@ -790,11 +818,13 @@ static int tcpm_pd_send_source_caps(struct tcpm_port *port)
msg.header = PD_HEADER_LE(PD_CTRL_REJECT, msg.header = PD_HEADER_LE(PD_CTRL_REJECT,
port->pwr_role, port->pwr_role,
port->data_role, port->data_role,
port->negotiated_rev,
port->message_id, 0); port->message_id, 0);
} else { } else {
msg.header = PD_HEADER_LE(PD_DATA_SOURCE_CAP, msg.header = PD_HEADER_LE(PD_DATA_SOURCE_CAP,
port->pwr_role, port->pwr_role,
port->data_role, port->data_role,
port->negotiated_rev,
port->message_id, port->message_id,
port->nr_src_pdo); port->nr_src_pdo);
} }
...@@ -815,11 +845,13 @@ static int tcpm_pd_send_sink_caps(struct tcpm_port *port) ...@@ -815,11 +845,13 @@ static int tcpm_pd_send_sink_caps(struct tcpm_port *port)
msg.header = PD_HEADER_LE(PD_CTRL_REJECT, msg.header = PD_HEADER_LE(PD_CTRL_REJECT,
port->pwr_role, port->pwr_role,
port->data_role, port->data_role,
port->negotiated_rev,
port->message_id, 0); port->message_id, 0);
} else { } else {
msg.header = PD_HEADER_LE(PD_DATA_SINK_CAP, msg.header = PD_HEADER_LE(PD_DATA_SINK_CAP,
port->pwr_role, port->pwr_role,
port->data_role, port->data_role,
port->negotiated_rev,
port->message_id, port->message_id,
port->nr_snk_pdo); port->nr_snk_pdo);
} }
...@@ -1186,6 +1218,7 @@ static void vdm_run_state_machine(struct tcpm_port *port) ...@@ -1186,6 +1218,7 @@ static void vdm_run_state_machine(struct tcpm_port *port)
msg.header = PD_HEADER_LE(PD_DATA_VENDOR_DEF, msg.header = PD_HEADER_LE(PD_DATA_VENDOR_DEF,
port->pwr_role, port->pwr_role,
port->data_role, port->data_role,
port->negotiated_rev,
port->message_id, port->vdo_count); port->message_id, port->vdo_count);
for (i = 0; i < port->vdo_count; i++) for (i = 0; i < port->vdo_count; i++)
msg.payload[i] = cpu_to_le32(port->vdo_data[i]); msg.payload[i] = cpu_to_le32(port->vdo_data[i]);
...@@ -1257,6 +1290,8 @@ enum pdo_err { ...@@ -1257,6 +1290,8 @@ enum pdo_err {
PDO_ERR_FIXED_NOT_SORTED, PDO_ERR_FIXED_NOT_SORTED,
PDO_ERR_VARIABLE_BATT_NOT_SORTED, PDO_ERR_VARIABLE_BATT_NOT_SORTED,
PDO_ERR_DUPE_PDO, PDO_ERR_DUPE_PDO,
PDO_ERR_PPS_APDO_NOT_SORTED,
PDO_ERR_DUPE_PPS_APDO,
}; };
static const char * const pdo_err_msg[] = { static const char * const pdo_err_msg[] = {
...@@ -1272,6 +1307,10 @@ static const char * const pdo_err_msg[] = { ...@@ -1272,6 +1307,10 @@ static const char * const pdo_err_msg[] = {
" err: Variable/Battery supply pdos should be in increasing order of their minimum voltage", " err: Variable/Battery supply pdos should be in increasing order of their minimum voltage",
[PDO_ERR_DUPE_PDO] = [PDO_ERR_DUPE_PDO] =
" err: Variable/Batt supply pdos cannot have same min/max voltage", " 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, static enum pdo_err tcpm_caps_err(struct tcpm_port *port, const u32 *pdo,
...@@ -1321,6 +1360,26 @@ static enum pdo_err tcpm_caps_err(struct tcpm_port *port, const u32 *pdo, ...@@ -1321,6 +1360,26 @@ static enum pdo_err tcpm_caps_err(struct tcpm_port *port, const u32 *pdo,
pdo_min_voltage(pdo[i - 1]))) pdo_min_voltage(pdo[i - 1])))
return PDO_ERR_DUPE_PDO; return PDO_ERR_DUPE_PDO;
break; 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: default:
tcpm_log_force(port, " Unknown pdo type"); tcpm_log_force(port, " Unknown pdo type");
} }
...@@ -1346,11 +1405,16 @@ static int tcpm_validate_caps(struct tcpm_port *port, const u32 *pdo, ...@@ -1346,11 +1405,16 @@ static int tcpm_validate_caps(struct tcpm_port *port, const u32 *pdo,
/* /*
* PD (data, control) command handling functions * 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, static void tcpm_pd_data_request(struct tcpm_port *port,
const struct pd_message *msg) const struct pd_message *msg)
{ {
enum pd_data_msg_type type = pd_header_type_le(msg->header); enum pd_data_msg_type type = pd_header_type_le(msg->header);
unsigned int cnt = pd_header_cnt_le(msg->header); unsigned int cnt = pd_header_cnt_le(msg->header);
unsigned int rev = pd_header_rev_le(msg->header);
unsigned int i; unsigned int i;
switch (type) { switch (type) {
...@@ -1368,6 +1432,17 @@ static void tcpm_pd_data_request(struct tcpm_port *port, ...@@ -1368,6 +1432,17 @@ static void tcpm_pd_data_request(struct tcpm_port *port,
tcpm_validate_caps(port, port->source_caps, tcpm_validate_caps(port, port->source_caps,
port->nr_source_caps); 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;
if (rev < PD_MAX_REV)
port->negotiated_rev = rev;
/* /*
* This message may be received even if VBUS is not * This message may be received even if VBUS is not
* present. This is quite unexpected; see USB PD * present. This is quite unexpected; see USB PD
...@@ -1389,6 +1464,20 @@ static void tcpm_pd_data_request(struct tcpm_port *port, ...@@ -1389,6 +1464,20 @@ static void tcpm_pd_data_request(struct tcpm_port *port,
tcpm_queue_message(port, PD_MSG_CTRL_REJECT); tcpm_queue_message(port, PD_MSG_CTRL_REJECT);
break; 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;
}
if (rev < PD_MAX_REV)
port->negotiated_rev = rev;
port->sink_request = le32_to_cpu(msg->payload[0]); port->sink_request = le32_to_cpu(msg->payload[0]);
tcpm_set_state(port, SRC_NEGOTIATE_CAPABILITIES, 0); tcpm_set_state(port, SRC_NEGOTIATE_CAPABILITIES, 0);
break; break;
...@@ -1413,6 +1502,15 @@ static void tcpm_pd_data_request(struct tcpm_port *port, ...@@ -1413,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, static void tcpm_pd_ctrl_request(struct tcpm_port *port,
const struct pd_message *msg) const struct pd_message *msg)
{ {
...@@ -1489,6 +1587,14 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port, ...@@ -1489,6 +1587,14 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port,
next_state = SNK_WAIT_CAPABILITIES; next_state = SNK_WAIT_CAPABILITIES;
tcpm_set_state(port, next_state, 0); tcpm_set_state(port, next_state, 0);
break; 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: case DR_SWAP_SEND:
port->swap_status = (type == PD_CTRL_WAIT ? port->swap_status = (type == PD_CTRL_WAIT ?
-EAGAIN : -EOPNOTSUPP); -EAGAIN : -EOPNOTSUPP);
...@@ -1511,6 +1617,13 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port, ...@@ -1511,6 +1617,13 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port,
case PD_CTRL_ACCEPT: case PD_CTRL_ACCEPT:
switch (port->state) { switch (port->state) {
case SNK_NEGOTIATE_CAPABILITIES: 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); tcpm_set_state(port, SNK_TRANSITION_SINK, 0);
break; break;
case SOFT_RESET_SEND: case SOFT_RESET_SEND:
...@@ -1665,6 +1778,7 @@ static int tcpm_pd_send_control(struct tcpm_port *port, ...@@ -1665,6 +1778,7 @@ static int tcpm_pd_send_control(struct tcpm_port *port,
memset(&msg, 0, sizeof(msg)); memset(&msg, 0, sizeof(msg));
msg.header = PD_HEADER_LE(type, port->pwr_role, msg.header = PD_HEADER_LE(type, port->pwr_role,
port->data_role, port->data_role,
port->negotiated_rev,
port->message_id, 0); port->message_id, 0);
return tcpm_pd_transmit(port, TCPC_TX_SOP, &msg); return tcpm_pd_transmit(port, TCPC_TX_SOP, &msg);
...@@ -1780,6 +1894,8 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port, int *sink_pdo, ...@@ -1780,6 +1894,8 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port, int *sink_pdo,
min_snk_mv = 0; min_snk_mv = 0;
int ret = -EINVAL; int ret = -EINVAL;
port->pps_data.supported = false;
/* /*
* Select the source PDO providing the most power which has a * Select the source PDO providing the most power which has a
* matchig sink cap. * matchig sink cap.
...@@ -1788,30 +1904,59 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port, int *sink_pdo, ...@@ -1788,30 +1904,59 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port, int *sink_pdo,
u32 pdo = port->source_caps[i]; u32 pdo = port->source_caps[i];
enum pd_pdo_type type = pdo_type(pdo); enum pd_pdo_type type = pdo_type(pdo);
if (type == PDO_TYPE_FIXED) { switch (type) {
case PDO_TYPE_FIXED:
max_src_mv = pdo_fixed_voltage(pdo); max_src_mv = pdo_fixed_voltage(pdo);
min_src_mv = max_src_mv; min_src_mv = max_src_mv;
} else { break;
case PDO_TYPE_BATT:
case PDO_TYPE_VAR:
max_src_mv = pdo_max_voltage(pdo); max_src_mv = pdo_max_voltage(pdo);
min_src_mv = pdo_min_voltage(pdo); min_src_mv = pdo_min_voltage(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 source PDO type, ignoring");
continue;
} }
if (type == PDO_TYPE_BATT) { switch (type) {
src_mw = pdo_max_power(pdo); case PDO_TYPE_FIXED:
} else { case PDO_TYPE_VAR:
src_ma = pdo_max_current(pdo); src_ma = pdo_max_current(pdo);
src_mw = src_ma * min_src_mv / 1000; src_mw = src_ma * min_src_mv / 1000;
break;
case PDO_TYPE_BATT:
src_mw = pdo_max_power(pdo);
break;
case PDO_TYPE_APDO:
continue;
default:
tcpm_log(port, "Invalid source PDO type, ignoring");
continue;
} }
for (j = 0; j < port->nr_snk_pdo; j++) { for (j = 0; j < port->nr_snk_pdo; j++) {
pdo = port->snk_pdo[j]; pdo = port->snk_pdo[j];
if (pdo_type(pdo) == PDO_TYPE_FIXED) { switch (pdo_type(pdo)) {
min_snk_mv = pdo_fixed_voltage(pdo); case PDO_TYPE_FIXED:
max_snk_mv = pdo_fixed_voltage(pdo); max_snk_mv = pdo_fixed_voltage(pdo);
} else { min_snk_mv = max_snk_mv;
min_snk_mv = pdo_min_voltage(pdo); break;
case PDO_TYPE_BATT:
case PDO_TYPE_VAR:
max_snk_mv = pdo_max_voltage(pdo); max_snk_mv = pdo_max_voltage(pdo);
min_snk_mv = pdo_min_voltage(pdo);
break;
case PDO_TYPE_APDO:
continue;
default:
tcpm_log(port, "Invalid sink PDO type, ignoring");
continue;
} }
if (max_src_mv <= max_snk_mv && if (max_src_mv <= max_snk_mv &&
...@@ -1832,6 +1977,103 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port, int *sink_pdo, ...@@ -1832,6 +1977,103 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port, int *sink_pdo,
return ret; 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)
{
unsigned int i, j, max_mw = 0, max_mv = 0;
unsigned int min_src_mv, max_src_mv, src_ma, src_mw;
unsigned int min_snk_mv, max_snk_mv, snk_ma;
u32 pdo;
unsigned int src_pdo = 0, snk_pdo = 0;
/*
* 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.
*/
for (i = 1; i < port->nr_source_caps; ++i) {
pdo = port->source_caps[i];
switch (pdo_type(pdo)) {
case PDO_TYPE_APDO:
if (pdo_apdo_type(pdo) != APDO_TYPE_PPS) {
tcpm_log(port, "Not PPS APDO (source), ignoring");
continue;
}
min_src_mv = pdo_pps_apdo_min_voltage(pdo);
max_src_mv = pdo_pps_apdo_max_voltage(pdo);
src_ma = pdo_pps_apdo_max_current(pdo);
src_mw = (src_ma * max_src_mv) / 1000;
/*
* Now search through the sink PDOs to find a matching
* PPS APDO. Again skip the first sink PDO as this will
* always be 5V 3A.
*/
for (j = i; j < port->nr_snk_pdo; j++) {
pdo = port->snk_pdo[j];
switch (pdo_type(pdo)) {
case PDO_TYPE_APDO:
if (pdo_apdo_type(pdo) != APDO_TYPE_PPS) {
tcpm_log(port,
"Not PPS APDO (sink), ignoring");
continue;
}
min_snk_mv =
pdo_pps_apdo_min_voltage(pdo);
max_snk_mv =
pdo_pps_apdo_max_voltage(pdo);
snk_ma =
pdo_pps_apdo_max_current(pdo);
break;
default:
tcpm_log(port,
"Not APDO type (sink), ignoring");
continue;
}
if (max_src_mv <= max_snk_mv &&
min_src_mv >= min_snk_mv) {
/* Prefer higher voltages if available */
if ((src_mw == max_mw &&
min_src_mv > max_mv) ||
src_mw > max_mw) {
src_pdo = i;
snk_pdo = j;
max_mw = src_mw;
max_mv = max_src_mv;
}
}
}
break;
default:
tcpm_log(port, "Not APDO type (source), ignoring");
continue;
}
}
if (src_pdo) {
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(pdo_pps_apdo_max_voltage(pdo), port->pps_data.out_volt);
port->pps_data.op_curr =
min(port->pps_data.max_curr, port->pps_data.op_curr);
}
return src_pdo;
}
static int tcpm_pd_build_request(struct tcpm_port *port, u32 *rdo) static int tcpm_pd_build_request(struct tcpm_port *port, u32 *rdo)
{ {
unsigned int mv, ma, mw, flags; unsigned int mv, ma, mw, flags;
...@@ -1849,10 +2091,18 @@ static int tcpm_pd_build_request(struct tcpm_port *port, u32 *rdo) ...@@ -1849,10 +2091,18 @@ static int tcpm_pd_build_request(struct tcpm_port *port, u32 *rdo)
matching_snk_pdo = port->snk_pdo[snk_pdo_index]; matching_snk_pdo = port->snk_pdo[snk_pdo_index];
type = pdo_type(pdo); type = pdo_type(pdo);
if (type == PDO_TYPE_FIXED) switch (type) {
case PDO_TYPE_FIXED:
mv = pdo_fixed_voltage(pdo); mv = pdo_fixed_voltage(pdo);
else break;
case PDO_TYPE_BATT:
case PDO_TYPE_VAR:
mv = pdo_min_voltage(pdo); 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 */ /* Select maximum available current within the sink pdo's limit */
if (type == PDO_TYPE_BATT) { if (type == PDO_TYPE_BATT) {
...@@ -1917,6 +2167,105 @@ static int tcpm_pd_send_request(struct tcpm_port *port) ...@@ -1917,6 +2167,105 @@ static int tcpm_pd_send_request(struct tcpm_port *port)
msg.header = PD_HEADER_LE(PD_DATA_REQUEST, msg.header = PD_HEADER_LE(PD_DATA_REQUEST,
port->pwr_role, port->pwr_role,
port->data_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;
unsigned int src_pdo_index;
u32 pdo;
src_pdo_index = tcpm_pd_select_pps_apdo(port);
if (!src_pdo_index)
return -EOPNOTSUPP;
pdo = port->source_caps[src_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 = port->pps_data.min_volt;
max_mv = port->pps_data.max_volt;
max_ma = port->pps_data.max_curr;
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 enough
* 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); port->message_id, 1);
msg.payload[0] = cpu_to_le32(rdo); msg.payload[0] = cpu_to_le32(rdo);
...@@ -2103,6 +2452,7 @@ static void tcpm_reset_port(struct tcpm_port *port) ...@@ -2103,6 +2452,7 @@ static void tcpm_reset_port(struct tcpm_port *port)
tcpm_typec_disconnect(port); tcpm_typec_disconnect(port);
port->attached = false; port->attached = false;
port->pd_capable = 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 * First Rx ID should be 0; set this to a sentinel of -1 so that
...@@ -2120,6 +2470,8 @@ static void tcpm_reset_port(struct tcpm_port *port) ...@@ -2120,6 +2470,8 @@ static void tcpm_reset_port(struct tcpm_port *port)
tcpm_set_attached_state(port, false); tcpm_set_attached_state(port, false);
port->try_src_count = 0; port->try_src_count = 0;
port->try_snk_count = 0; port->try_snk_count = 0;
port->supply_voltage = 0;
port->current_limit = 0;
} }
static void tcpm_detach(struct tcpm_port *port) static void tcpm_detach(struct tcpm_port *port)
...@@ -2364,6 +2716,7 @@ static void run_state_machine(struct tcpm_port *port) ...@@ -2364,6 +2716,7 @@ static void run_state_machine(struct tcpm_port *port)
typec_set_pwr_opmode(port->typec_port, opmode); typec_set_pwr_opmode(port->typec_port, opmode);
port->pwr_opmode = TYPEC_PWR_MODE_USB; port->pwr_opmode = TYPEC_PWR_MODE_USB;
port->caps_count = 0; port->caps_count = 0;
port->negotiated_rev = PD_MAX_REV;
port->message_id = 0; port->message_id = 0;
port->rx_msgid = -1; port->rx_msgid = -1;
port->explicit_contract = false; port->explicit_contract = false;
...@@ -2424,6 +2777,7 @@ static void run_state_machine(struct tcpm_port *port) ...@@ -2424,6 +2777,7 @@ static void run_state_machine(struct tcpm_port *port)
tcpm_swap_complete(port, 0); tcpm_swap_complete(port, 0);
tcpm_typec_connect(port); tcpm_typec_connect(port);
tcpm_check_send_discover(port); tcpm_check_send_discover(port);
/* /*
* 6.3.5 * 6.3.5
...@@ -2447,6 +2801,7 @@ static void run_state_machine(struct tcpm_port *port) ...@@ -2447,6 +2801,7 @@ static void run_state_machine(struct tcpm_port *port)
case SNK_UNATTACHED: case SNK_UNATTACHED:
if (!port->non_pd_role_swap) if (!port->non_pd_role_swap)
tcpm_swap_complete(port, -ENOTCONN); tcpm_swap_complete(port, -ENOTCONN);
tcpm_pps_complete(port, -ENOTCONN);
tcpm_snk_detach(port); tcpm_snk_detach(port);
if (tcpm_start_drp_toggling(port)) { if (tcpm_start_drp_toggling(port)) {
tcpm_set_state(port, DRP_TOGGLING, 0); tcpm_set_state(port, DRP_TOGGLING, 0);
...@@ -2536,6 +2891,7 @@ static void run_state_machine(struct tcpm_port *port) ...@@ -2536,6 +2891,7 @@ static void run_state_machine(struct tcpm_port *port)
port->cc2 : port->cc1); port->cc2 : port->cc1);
typec_set_pwr_opmode(port->typec_port, opmode); typec_set_pwr_opmode(port->typec_port, opmode);
port->pwr_opmode = TYPEC_PWR_MODE_USB; port->pwr_opmode = TYPEC_PWR_MODE_USB;
port->negotiated_rev = PD_MAX_REV;
port->message_id = 0; port->message_id = 0;
port->rx_msgid = -1; port->rx_msgid = -1;
port->explicit_contract = false; port->explicit_contract = false;
...@@ -2606,6 +2962,24 @@ static void run_state_machine(struct tcpm_port *port) ...@@ -2606,6 +2962,24 @@ static void run_state_machine(struct tcpm_port *port)
PD_T_SENDER_RESPONSE); PD_T_SENDER_RESPONSE);
} }
break; 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:
case SNK_TRANSITION_SINK_VBUS: case SNK_TRANSITION_SINK_VBUS:
tcpm_set_state(port, hard_reset_state(port), tcpm_set_state(port, hard_reset_state(port),
...@@ -2613,6 +2987,7 @@ static void run_state_machine(struct tcpm_port *port) ...@@ -2613,6 +2987,7 @@ static void run_state_machine(struct tcpm_port *port)
break; break;
case SNK_READY: case SNK_READY:
port->try_snk_count = 0; port->try_snk_count = 0;
port->update_sink_caps = false;
if (port->explicit_contract) { if (port->explicit_contract) {
typec_set_pwr_opmode(port->typec_port, typec_set_pwr_opmode(port->typec_port,
TYPEC_PWR_MODE_PD); TYPEC_PWR_MODE_PD);
...@@ -2622,6 +2997,8 @@ static void run_state_machine(struct tcpm_port *port) ...@@ -2622,6 +2997,8 @@ static void run_state_machine(struct tcpm_port *port)
tcpm_swap_complete(port, 0); tcpm_swap_complete(port, 0);
tcpm_typec_connect(port); tcpm_typec_connect(port);
tcpm_check_send_discover(port); tcpm_check_send_discover(port);
tcpm_pps_complete(port, port->pps_status);
break; break;
/* Accessory states */ /* Accessory states */
...@@ -2668,6 +3045,7 @@ static void run_state_machine(struct tcpm_port *port) ...@@ -2668,6 +3045,7 @@ static void run_state_machine(struct tcpm_port *port)
tcpm_set_state(port, SRC_UNATTACHED, PD_T_PS_SOURCE_ON); tcpm_set_state(port, SRC_UNATTACHED, PD_T_PS_SOURCE_ON);
break; break;
case SNK_HARD_RESET_SINK_OFF: case SNK_HARD_RESET_SINK_OFF:
memset(&port->pps_data, 0, sizeof(port->pps_data));
tcpm_set_vconn(port, false); tcpm_set_vconn(port, false);
tcpm_set_charge(port, false); tcpm_set_charge(port, false);
tcpm_set_roles(port, false, TYPEC_SINK, TYPEC_DEVICE); tcpm_set_roles(port, false, TYPEC_SINK, TYPEC_DEVICE);
...@@ -2888,6 +3266,7 @@ static void run_state_machine(struct tcpm_port *port) ...@@ -2888,6 +3266,7 @@ static void run_state_machine(struct tcpm_port *port)
break; break;
case ERROR_RECOVERY: case ERROR_RECOVERY:
tcpm_swap_complete(port, -EPROTO); tcpm_swap_complete(port, -EPROTO);
tcpm_pps_complete(port, -EPROTO);
tcpm_set_state(port, PORT_RESET, 0); tcpm_set_state(port, PORT_RESET, 0);
break; break;
case PORT_RESET: case PORT_RESET:
...@@ -3470,6 +3849,162 @@ static int tcpm_try_role(const struct typec_capability *cap, int role) ...@@ -3470,6 +3849,162 @@ static int tcpm_try_role(const struct typec_capability *cap, int role)
return ret; return ret;
} }
static int __maybe_unused tcpm_pps_set_op_curr(struct tcpm_port *port, u16 op_curr)
{
unsigned int target_mw;
int ret;
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);
mutex_unlock(&port->lock);
if (!wait_for_completion_timeout(&port->pps_complete,
msecs_to_jiffies(PD_PPS_CTRL_TIMEOUT)))
ret = -ETIMEDOUT;
else
ret = port->pps_status;
goto swap_unlock;
port_unlock:
mutex_unlock(&port->lock);
swap_unlock:
mutex_unlock(&port->swap_lock);
return ret;
}
static int __maybe_unused tcpm_pps_set_out_volt(struct tcpm_port *port, u16 out_volt)
{
unsigned int target_mw;
int ret;
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 (out_volt < port->pps_data.min_volt ||
out_volt > port->pps_data.max_volt) {
ret = -EINVAL;
goto port_unlock;
}
target_mw = (port->pps_data.op_curr * out_volt) / 1000;
if (target_mw < port->operating_snk_mw) {
ret = -EINVAL;
goto port_unlock;
}
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_unlock(&port->lock);
if (!wait_for_completion_timeout(&port->pps_complete,
msecs_to_jiffies(PD_PPS_CTRL_TIMEOUT)))
ret = -ETIMEDOUT;
else
ret = port->pps_status;
goto swap_unlock;
port_unlock:
mutex_unlock(&port->lock);
swap_unlock:
mutex_unlock(&port->swap_lock);
return ret;
}
static int __maybe_unused 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_PPS_CTRL_TIMEOUT)))
ret = -ETIMEDOUT;
else
ret = port->pps_status;
goto swap_unlock;
port_unlock:
mutex_unlock(&port->lock);
swap_unlock:
mutex_unlock(&port->swap_lock);
return ret;
}
static void tcpm_init(struct tcpm_port *port) static void tcpm_init(struct tcpm_port *port)
{ {
enum typec_cc_status cc1, cc2; enum typec_cc_status cc1, cc2;
...@@ -3603,12 +4138,17 @@ int tcpm_update_sink_capabilities(struct tcpm_port *port, const u32 *pdo, ...@@ -3603,12 +4138,17 @@ int tcpm_update_sink_capabilities(struct tcpm_port *port, const u32 *pdo,
mutex_lock(&port->lock); mutex_lock(&port->lock);
port->nr_snk_pdo = tcpm_copy_pdos(port->snk_pdo, pdo, nr_pdo); port->nr_snk_pdo = tcpm_copy_pdos(port->snk_pdo, pdo, nr_pdo);
port->operating_snk_mw = operating_snk_mw; port->operating_snk_mw = operating_snk_mw;
port->update_sink_caps = true;
switch (port->state) { switch (port->state) {
case SNK_NEGOTIATE_CAPABILITIES: case SNK_NEGOTIATE_CAPABILITIES:
case SNK_NEGOTIATE_PPS_CAPABILITIES:
case SNK_READY: case SNK_READY:
case SNK_TRANSITION_SINK: case SNK_TRANSITION_SINK:
case SNK_TRANSITION_SINK_VBUS: case SNK_TRANSITION_SINK_VBUS:
if (port->pps_data.active)
tcpm_set_state(port, SNK_NEGOTIATE_PPS_CAPABILITIES, 0);
else
tcpm_set_state(port, SNK_NEGOTIATE_CAPABILITIES, 0); tcpm_set_state(port, SNK_NEGOTIATE_CAPABILITIES, 0);
break; break;
default: default:
...@@ -3651,6 +4191,7 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc) ...@@ -3651,6 +4191,7 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc)
init_completion(&port->tx_complete); init_completion(&port->tx_complete);
init_completion(&port->swap_complete); init_completion(&port->swap_complete);
init_completion(&port->pps_complete);
tcpm_debugfs_init(port); tcpm_debugfs_init(port);
if (tcpm_validate_caps(port, tcpc->config->src_pdo, if (tcpm_validate_caps(port, tcpc->config->src_pdo,
...@@ -3677,7 +4218,7 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc) ...@@ -3677,7 +4218,7 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc)
port->typec_caps.type = tcpc->config->type; port->typec_caps.type = tcpc->config->type;
port->typec_caps.data = tcpc->config->data; port->typec_caps.data = tcpc->config->data;
port->typec_caps.revision = 0x0120; /* Type-C spec release 1.2 */ 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.dr_set = tcpm_dr_set;
port->typec_caps.pr_set = tcpm_pr_set; port->typec_caps.pr_set = tcpm_pr_set;
port->typec_caps.vconn_set = tcpm_vconn_set; port->typec_caps.vconn_set = tcpm_vconn_set;
......
...@@ -103,8 +103,8 @@ enum pd_ext_msg_type { ...@@ -103,8 +103,8 @@ enum pd_ext_msg_type {
(((cnt) & PD_HEADER_CNT_MASK) << PD_HEADER_CNT_SHIFT) | \ (((cnt) & PD_HEADER_CNT_MASK) << PD_HEADER_CNT_SHIFT) | \
((ext_hdr) ? PD_HEADER_EXT_HDR : 0)) ((ext_hdr) ? PD_HEADER_EXT_HDR : 0))
#define PD_HEADER_LE(type, pwr, data, id, cnt) \ #define PD_HEADER_LE(type, pwr, data, rev, id, cnt) \
cpu_to_le16(PD_HEADER((type), (pwr), (data), PD_REV20, (id), (cnt), (0))) cpu_to_le16(PD_HEADER((type), (pwr), (data), (rev), (id), (cnt), (0)))
static inline unsigned int pd_header_cnt(u16 header) static inline unsigned int pd_header_cnt(u16 header)
{ {
......
...@@ -36,6 +36,7 @@ enum typec_cc_polarity { ...@@ -36,6 +36,7 @@ enum typec_cc_polarity {
/* Time to wait for TCPC to complete transmit */ /* Time to wait for TCPC to complete transmit */
#define PD_T_TCPC_TX_TIMEOUT 100 /* in ms */ #define PD_T_TCPC_TX_TIMEOUT 100 /* in ms */
#define PD_ROLE_SWAP_TIMEOUT (MSEC_PER_SEC * 10) #define PD_ROLE_SWAP_TIMEOUT (MSEC_PER_SEC * 10)
#define PD_PPS_CTRL_TIMEOUT (MSEC_PER_SEC * 10)
enum tcpm_transmit_status { enum tcpm_transmit_status {
TCPC_TX_SUCCESS = 0, TCPC_TX_SUCCESS = 0,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment