On Thu, Mar 22, 2018 at 03:52:02PM +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 <[email protected]>
> Acked-by: Heikki Krogerus <[email protected]>
> ---
>  drivers/usb/typec/tcpm.c | 524 
> +++++++++++++++++++++++++++++++++++++++++++++--
>  include/linux/usb/pd.h   |   4 +-
>  include/linux/usb/tcpm.h |   2 +-
>  3 files changed, 513 insertions(+), 17 deletions(-)
> 
> diff --git a/drivers/usb/typec/tcpm.c b/drivers/usb/typec/tcpm.c
> index 4c0fc54..5618b3f 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;
> @@ -259,6 +271,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;
> @@ -275,8 +288,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];
> @@ -494,6 +512,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;
> @@ -777,11 +805,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);
>       }
> @@ -802,11 +832,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);
>       }
> @@ -1173,6 +1205,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]);
> @@ -1244,6 +1277,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[] = {
> @@ -1259,6 +1294,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,
> @@ -1308,6 +1347,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");
>                       }
> @@ -1333,11 +1392,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) {
> @@ -1356,6 +1420,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)

Some static checkers complain about else after break, and I tend to agree.

> +                     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.
> @@ -1376,6 +1450,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) {

Same here.

> +                     port->negotiated_rev = rev;
> +             }
> +
>               port->sink_request = le32_to_cpu(msg->payload[0]);
>               tcpm_set_state(port, SRC_NEGOTIATE_CAPABILITIES, 0);
>               break;
> @@ -1400,6 +1487,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)
>  {
> @@ -1476,6 +1572,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);
> @@ -1498,6 +1602,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:
> @@ -1652,6 +1763,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);
> @@ -1761,6 +1873,8 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port)
>       unsigned int i, max_mw = 0, max_mv = 0;
>       int ret = -EINVAL;
>  
> +     port->pps_data.supported = false;
> +
>       /*
>        * Select the source PDO providing the most power while staying within
>        * the board's voltage limits. Prefer PDO providing exp
> @@ -1770,20 +1884,41 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port)
>               enum pd_pdo_type type = pdo_type(pdo);
>               unsigned int mv, ma, mw;
>  
> -             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;
> +             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;
> +             }
>  
> -             if (type == PDO_TYPE_BATT) {
> -                     mw = pdo_max_power(pdo);
> -             } else {
> +             switch (type) {
> +             case PDO_TYPE_FIXED:
> +             case PDO_TYPE_VAR:
>                       ma = min(pdo_max_current(pdo),
>                                port->max_snk_ma);
>                       mw = ma * mv / 1000;
> +                     break;
> +             case PDO_TYPE_BATT:
> +                     mw = pdo_max_power(pdo);
> +                     break;
> +             case PDO_TYPE_APDO:
> +                     continue;
> +             default:
> +                     tcpm_log(port, "Invalid PDO type, ignoring");
> +                     continue;
>               }
>  
> -             /* Perfer higher voltages if available */
> +             /* Prefer higher voltages if available */
>               if ((mw > max_mw || (mw == max_mw && mv > max_mv)) &&
>                   mv <= port->max_snk_mv) {
>                       ret = i;
> @@ -1795,6 +1930,65 @@ static int tcpm_pd_select_pdo(struct tcpm_port *port)
>       return ret;
>  }
>  
> +static unsigned int tcpm_pd_select_pps_apdo(struct tcpm_port *port)
> +{
> +     unsigned int i, max_mw = 0, max_mv = 0;
> +     unsigned int pps_min_mv, pps_max_mv, ma, mw;
> +     enum pd_pdo_type type;
> +     u32 pdo;
> +     unsigned int index = 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];
> +             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;
> +                     }
> +
> +                     pps_min_mv = pdo_pps_apdo_min_voltage(pdo);
> +                     pps_max_mv = pdo_pps_apdo_max_voltage(pdo);
> +                     ma = min(pdo_pps_apdo_max_current(pdo), 
> port->max_snk_ma);
> +                     mw = (ma * pps_max_mv) / 1000;
> +                     break;
> +             default:
> +                     tcpm_log(port, "Not APDO type, ignoring");
> +                     continue;
> +             }
> +
> +             /* Prefer higher voltages if available */
> +             if ((mw > max_mw || (mw == max_mw && pps_max_mv > max_mv)) &&
> +                 pps_max_mv <= port->max_snk_mv) {
> +                     index = i;
> +                     max_mw = mw;
> +                     max_mv = pps_max_mv;
> +             }
> +     }
> +
> +     if (index) {
> +             pdo = port->source_caps[index];
> +
> +             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(pdo_pps_apdo_max_current(pdo), port->max_snk_ma);
> +             port->pps_data.out_volt =
> +                     min(pdo_pps_apdo_max_voltage(pdo), 
> port->pps_data.out_volt);
> +             port->pps_data.op_curr =
> +                     min(pdo_pps_apdo_max_current(pdo), 
> port->pps_data.op_curr);
> +     }
> +
> +     return index;
> +}
> +
>  static int tcpm_pd_build_request(struct tcpm_port *port, u32 *rdo)
>  {
>       unsigned int mv, ma, mw, flags;
> @@ -1809,10 +2003,18 @@ static int tcpm_pd_build_request(struct tcpm_port 
> *port, u32 *rdo)
>       pdo = port->source_caps[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 board's power limit */
>       if (type == PDO_TYPE_BATT) {
> @@ -1875,6 +2077,105 @@ 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 index;
> +     u32 pdo;
> +
> +     index = tcpm_pd_select_pps_apdo(port);
> +     if (!index)
> +             return -EOPNOTSUPP;
> +
> +     pdo = port->source_caps[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);

Still unnecessary outer ( ).

> +             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);

Same here.

> +                     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(index + 1, out_mv, op_ma, flags);
> +
> +     tcpm_log(port, "Requesting APDO %d: %u mV, %u mA",
> +              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);
>  
> @@ -2060,6 +2361,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
> @@ -2075,6 +2377,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)
> @@ -2321,6 +2625,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;
> @@ -2381,6 +2686,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
> @@ -2404,6 +2710,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);
> @@ -2412,6 +2719,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);
> +

Please no whitespace changes.

>               break;
>       case SNK_ATTACH_WAIT:
>               if ((port->cc1 == TYPEC_CC_OPEN &&
> @@ -2493,6 +2801,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;
> @@ -2563,6 +2872,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),
> @@ -2570,6 +2897,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);
> @@ -2578,7 +2906,11 @@ static void run_state_machine(struct tcpm_port *port)
>  
>               tcpm_swap_complete(port, 0);
>               tcpm_typec_connect(port);
> +

Please no whitespace changes.

>               tcpm_check_send_discover(port);
> +
> +             tcpm_pps_complete(port, port->pps_status);
> +
>               break;
>  
>       /* Accessory states */
> @@ -2625,6 +2957,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);
> @@ -2845,6 +3178,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:
> @@ -3310,7 +3644,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)))

Hmm. I don't think changing this define is an improvement.
It might be better to define a new timeout for the PPS commands
instead of changing the existing code.

>               ret = -ETIMEDOUT;
>       else
>               ret = port->swap_status;
> @@ -3355,7 +3689,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;
> @@ -3395,7 +3729,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;
> @@ -3427,6 +3761,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;
> +
> +     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_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);
> +
> +     return ret;
> +}
> +
> +static int 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_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);
> +
> +     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);
> +
> +     return ret;
> +}
> +
>  static void tcpm_init(struct tcpm_port *port)
>  {
>       enum typec_cc_status cc1, cc2;
> @@ -3566,13 +4056,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;
> @@ -3614,6 +4109,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,
> @@ -3642,7 +4138,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
> 

Reply via email to