Re: [PATCH v7 06/11] usb: typec: tcpm: Add support for Sink Fast Role SWAP(FRS)

From: Heikki Krogerus
Date: Fri Sep 18 2020 - 08:36:48 EST


On Thu, Sep 17, 2020 at 03:18:51AM -0700, Badhri Jagan Sridharan wrote:
> PD 3.0 spec defines a new mechanism for power role swap called
> Fast role swap. This change enables TCPM to support FRS when
> acting as sink.
>
> Once the explicit contract is negotiated, sink port is
> expected to query the source port for sink caps to
> determine whether the source is FRS capable.
> Bits 23 & 24 of fixed pdo of the sink caps from the source, when
> set, indicates the current needed by the source when fast role
> swap is in progress(Implicit contract phasae). 0 indicates that
> the source does not support Fast Role Swap.
>
> Upon receiving the FRS signal from the source,
> TCPC(TCPM_FRS_EVENT) informs TCPM to start the Fast role swap sequence.
>
> 1. TCPM sends FRS PD message: FR_SWAP_SEND
> 2. If response is not received within the expiry of
> SenderResponseTimer, Error recovery is triggered.:
> FR_SWAP_SEND_TIMEOUT
> 3. Upon receipt of the accept message, TCPM waits for
> PSSourceOffTimer for PS_READY message from the partner:
> FR_SWAP_SNK_SRC_NEW_SINK_READY.
>
> TCPC is expected to autonomously turn on vbus once the FRS
> signal is received and vbus voltage falls below vsafe5v within
> tSrcFrSwap. This is different from traditional power role swap
> where the vbus sourcing is turned on by TCPM.
>
> 4. By this time, TCPC most likely would have started to
> source vbus, TCPM waits for tSrcFrSwap to see if the
> lower level TCPC driver signals TCPM_SOURCING_VBUS event:
> FR_SWAP_SNK_SRC_SOURCE_VBUS_APPLIED.
> 5. When TCPC signals sourcing vbus, TCPM sends PS_READY msg and
> changes the CC pin from Rd to Rp. This is the end of fast
> role swap sequence and TCPM initiates the sequnce to negotiate
> explicit contract by transitioning into SRC_STARTUP after
> SwapSrcStart.
>
> The code is written based on the sequence described in "Figure 8-107:
> Dual-role Port in Sink to Source Fast Role Swap State Diagram" of
> USB Power Delivery Specification Revision 3.0, Version 1.2.
>
> Signed-off-by: Badhri Jagan Sridharan <badhri@xxxxxxxxxx>

Reviewed-by: Heikki Krogerus <heikki.krogerus@xxxxxxxxxxxxxxx>

> ---
> Changes since v1:
> - Changing patch version to v6 to fix version number confusion.
> - Rebased on top of usb-next and resolved conflicts due to the below
> changes:
> 3ed8e1c2ac99 usb: typec: tcpm: Migrate workqueue to RT priority for processing events
> 6bbe2a90a0bb usb: typec: tcpm: During PR_SWAP, source caps should be sent only after tSwapSourceStart
> - enable_frs sequence is now run as part of the same kthread that runs
> the state machines.
> - Fixed the implicit fallthrough warning in the switch case for
> FR_SWAP_CANCEL case.
>
> Changes since v6:
> - Moved frs_current from caps to tcpm_port as Heikki suggested.
> ---
>
> drivers/usb/typec/tcpm/tcpm.c | 229 +++++++++++++++++++++++++++++++++-
> include/linux/usb/pd.h | 19 +--
> include/linux/usb/tcpm.h | 8 +-
> 3 files changed, 244 insertions(+), 12 deletions(-)
>
> diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c
> index 92806547f485..55535c4f66bf 100644
> --- a/drivers/usb/typec/tcpm/tcpm.c
> +++ b/drivers/usb/typec/tcpm/tcpm.c
> @@ -106,6 +106,13 @@
> S(VCONN_SWAP_TURN_ON_VCONN), \
> S(VCONN_SWAP_TURN_OFF_VCONN), \
> \
> + S(FR_SWAP_SEND), \
> + S(FR_SWAP_SEND_TIMEOUT), \
> + S(FR_SWAP_SNK_SRC_TRANSITION_TO_OFF), \
> + S(FR_SWAP_SNK_SRC_NEW_SINK_READY), \
> + S(FR_SWAP_SNK_SRC_SOURCE_VBUS_APPLIED), \
> + S(FR_SWAP_CANCEL), \
> + \
> S(SNK_TRY), \
> S(SNK_TRY_WAIT), \
> S(SNK_TRY_WAIT_DEBOUNCE), \
> @@ -127,6 +134,9 @@
> S(GET_PPS_STATUS_SEND), \
> S(GET_PPS_STATUS_SEND_TIMEOUT), \
> \
> + S(GET_SINK_CAP), \
> + S(GET_SINK_CAP_TIMEOUT), \
> + \
> S(ERROR_RECOVERY), \
> S(PORT_RESET), \
> S(PORT_RESET_WAIT_OFF)
> @@ -170,11 +180,25 @@ enum adev_actions {
> ADEV_ATTENTION,
> };
>
> +/*
> + * Initial current capability of the new source when vSafe5V is applied during PD3.0 Fast Role Swap.
> + * Based on "Table 6-14 Fixed Supply PDO - Sink" of "USB Power Delivery Specification Revision 3.0,
> + * Version 1.2"
> + */
> +enum frs_typec_current {
> + FRS_NOT_SUPPORTED,
> + FRS_DEFAULT_POWER,
> + FRS_5V_1P5A,
> + FRS_5V_3A,
> +};
> +
> /* Events from low level driver */
>
> #define TCPM_CC_EVENT BIT(0)
> #define TCPM_VBUS_EVENT BIT(1)
> #define TCPM_RESET_EVENT BIT(2)
> +#define TCPM_FRS_EVENT BIT(3)
> +#define TCPM_SOURCING_VBUS BIT(4)
>
> #define LOG_BUFFER_ENTRIES 1024
> #define LOG_BUFFER_ENTRY_SIZE 128
> @@ -184,6 +208,8 @@ enum adev_actions {
> #define SVID_DISCOVERY_MAX 16
> #define ALTMODE_DISCOVERY_MAX (SVID_DISCOVERY_MAX * MODE_DISCOVERY_MAX)
>
> +#define GET_SINK_CAP_RETRY_MS 100
> +
> struct pd_mode_data {
> int svid_index; /* current SVID index */
> int nsvids;
> @@ -261,6 +287,8 @@ struct tcpm_port {
> struct kthread_work state_machine;
> struct hrtimer vdm_state_machine_timer;
> struct kthread_work vdm_state_machine;
> + struct hrtimer enable_frs_timer;
> + struct kthread_work enable_frs;
> bool state_machine_running;
>
> struct completion tx_complete;
> @@ -335,6 +363,12 @@ struct tcpm_port {
> /* port belongs to a self powered device */
> bool self_powered;
>
> + /* FRS */
> + enum frs_typec_current frs_current;
> +
> + /* Sink caps have been queried */
> + bool sink_cap_done;
> +
> #ifdef CONFIG_DEBUG_FS
> struct dentry *dentry;
> struct mutex logbuffer_lock; /* log buffer access lock */
> @@ -940,6 +974,16 @@ static void mod_vdm_delayed_work(struct tcpm_port *port, unsigned int delay_ms)
> }
> }
>
> +static void mod_enable_frs_delayed_work(struct tcpm_port *port, unsigned int delay_ms)
> +{
> + if (delay_ms) {
> + hrtimer_start(&port->enable_frs_timer, ms_to_ktime(delay_ms), HRTIMER_MODE_REL);
> + } else {
> + hrtimer_cancel(&port->enable_frs_timer);
> + kthread_queue_work(port->wq, &port->enable_frs);
> + }
> +}
> +
> static void tcpm_set_state(struct tcpm_port *port, enum tcpm_state state,
> unsigned int delay_ms)
> {
> @@ -1669,6 +1713,9 @@ static void tcpm_pd_data_request(struct tcpm_port *port,
> unsigned int cnt = pd_header_cnt_le(msg->header);
> unsigned int rev = pd_header_rev_le(msg->header);
> unsigned int i;
> + enum frs_typec_current frs_current;
> + bool frs_enable;
> + int ret;
>
> switch (type) {
> case PD_DATA_SOURCE_CAP:
> @@ -1738,7 +1785,21 @@ static void tcpm_pd_data_request(struct tcpm_port *port,
> /* We don't do anything with this at the moment... */
> for (i = 0; i < cnt; i++)
> port->sink_caps[i] = le32_to_cpu(msg->payload[i]);
> +
> + frs_current = (port->sink_caps[0] & PDO_FIXED_FRS_CURR_MASK) >>
> + PDO_FIXED_FRS_CURR_SHIFT;
> + frs_enable = frs_current && (frs_current <= port->frs_current);
> + tcpm_log(port,
> + "Port partner FRS capable partner_frs_current:%u port_frs_current:%u enable:%c",
> + frs_current, port->frs_current, frs_enable ? 'y' : 'n');
> + if (frs_enable) {
> + ret = port->tcpc->enable_frs(port->tcpc, true);
> + tcpm_log(port, "Enable FRS %s, ret:%d\n", ret ? "fail" : "success", ret);
> + }
> +
> port->nr_sink_caps = cnt;
> + port->sink_cap_done = true;
> + tcpm_set_state(port, SNK_READY, 0);
> break;
> case PD_DATA_VENDOR_DEF:
> tcpm_handle_vdm_request(port, msg->payload, cnt);
> @@ -1833,6 +1894,9 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port,
> case VCONN_SWAP_WAIT_FOR_VCONN:
> tcpm_set_state(port, VCONN_SWAP_TURN_OFF_VCONN, 0);
> break;
> + case FR_SWAP_SNK_SRC_TRANSITION_TO_OFF:
> + tcpm_set_state(port, FR_SWAP_SNK_SRC_NEW_SINK_READY, 0);
> + break;
> default:
> break;
> }
> @@ -1872,6 +1936,13 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port,
> -EAGAIN : -EOPNOTSUPP);
> tcpm_set_state(port, VCONN_SWAP_CANCEL, 0);
> break;
> + case FR_SWAP_SEND:
> + tcpm_set_state(port, FR_SWAP_CANCEL, 0);
> + break;
> + case GET_SINK_CAP:
> + port->sink_cap_done = true;
> + tcpm_set_state(port, ready_state(port), 0);
> + break;
> default:
> break;
> }
> @@ -1906,6 +1977,9 @@ static void tcpm_pd_ctrl_request(struct tcpm_port *port,
> case VCONN_SWAP_SEND:
> tcpm_set_state(port, VCONN_SWAP_START, 0);
> break;
> + case FR_SWAP_SEND:
> + tcpm_set_state(port, FR_SWAP_SNK_SRC_TRANSITION_TO_OFF, 0);
> + break;
> default:
> break;
> }
> @@ -2806,6 +2880,10 @@ static void tcpm_reset_port(struct tcpm_port *port)
> port->try_src_count = 0;
> port->try_snk_count = 0;
> port->usb_type = POWER_SUPPLY_USB_TYPE_C;
> + port->nr_sink_caps = 0;
> + port->sink_cap_done = false;
> + if (port->tcpc->enable_frs)
> + port->tcpc->enable_frs(port->tcpc, false);
>
> power_supply_changed(port->psy);
> }
> @@ -3356,10 +3434,9 @@ static void run_state_machine(struct tcpm_port *port)
> tcpm_swap_complete(port, 0);
> tcpm_typec_connect(port);
> tcpm_check_send_discover(port);
> + mod_enable_frs_delayed_work(port, 0);
> tcpm_pps_complete(port, port->pps_status);
> -
> power_supply_changed(port->psy);
> -
> break;
>
> /* Accessory states */
> @@ -3383,9 +3460,13 @@ static void run_state_machine(struct tcpm_port *port)
> tcpm_set_state(port, HARD_RESET_START, 0);
> break;
> case HARD_RESET_START:
> + port->sink_cap_done = false;
> + if (port->tcpc->enable_frs)
> + port->tcpc->enable_frs(port->tcpc, false);
> port->hard_reset_count++;
> port->tcpc->set_pd_rx(port->tcpc, false);
> tcpm_unregister_altmodes(port);
> + port->nr_sink_caps = 0;
> port->send_discover = true;
> if (port->pwr_role == TYPEC_SOURCE)
> tcpm_set_state(port, SRC_HARD_RESET_VBUS_OFF,
> @@ -3517,6 +3598,35 @@ static void run_state_machine(struct tcpm_port *port)
> tcpm_set_state(port, ready_state(port), 0);
> break;
>
> + case FR_SWAP_SEND:
> + if (tcpm_pd_send_control(port, PD_CTRL_FR_SWAP)) {
> + tcpm_set_state(port, ERROR_RECOVERY, 0);
> + break;
> + }
> + tcpm_set_state_cond(port, FR_SWAP_SEND_TIMEOUT, PD_T_SENDER_RESPONSE);
> + break;
> + case FR_SWAP_SEND_TIMEOUT:
> + tcpm_set_state(port, ERROR_RECOVERY, 0);
> + break;
> + case FR_SWAP_SNK_SRC_TRANSITION_TO_OFF:
> + tcpm_set_state(port, ERROR_RECOVERY, PD_T_PS_SOURCE_OFF);
> + break;
> + case FR_SWAP_SNK_SRC_NEW_SINK_READY:
> + if (port->vbus_source)
> + tcpm_set_state(port, FR_SWAP_SNK_SRC_SOURCE_VBUS_APPLIED, 0);
> + else
> + tcpm_set_state(port, ERROR_RECOVERY, PD_T_RECEIVER_RESPONSE);
> + break;
> + case FR_SWAP_SNK_SRC_SOURCE_VBUS_APPLIED:
> + tcpm_set_pwr_role(port, TYPEC_SOURCE);
> + if (tcpm_pd_send_control(port, PD_CTRL_PS_RDY)) {
> + tcpm_set_state(port, ERROR_RECOVERY, 0);
> + break;
> + }
> + tcpm_set_cc(port, tcpm_rp_cc(port));
> + tcpm_set_state(port, SRC_STARTUP, PD_T_SWAP_SRC_START);
> + break;
> +
> /* PR_Swap states */
> case PR_SWAP_ACCEPT:
> tcpm_pd_send_control(port, PD_CTRL_ACCEPT);
> @@ -3640,6 +3750,12 @@ static void run_state_machine(struct tcpm_port *port)
> else
> tcpm_set_state(port, SNK_READY, 0);
> break;
> + case FR_SWAP_CANCEL:
> + if (port->pwr_role == TYPEC_SOURCE)
> + tcpm_set_state(port, SRC_READY, 0);
> + else
> + tcpm_set_state(port, SNK_READY, 0);
> + break;
>
> case BIST_RX:
> switch (BDO_MODE_MASK(port->bist_request)) {
> @@ -3674,6 +3790,14 @@ static void run_state_machine(struct tcpm_port *port)
> case GET_PPS_STATUS_SEND_TIMEOUT:
> tcpm_set_state(port, ready_state(port), 0);
> break;
> + case GET_SINK_CAP:
> + tcpm_pd_send_control(port, PD_CTRL_GET_SINK_CAP);
> + tcpm_set_state(port, GET_SINK_CAP_TIMEOUT, PD_T_SENDER_RESPONSE);
> + break;
> + case GET_SINK_CAP_TIMEOUT:
> + port->sink_cap_done = true;
> + tcpm_set_state(port, ready_state(port), 0);
> + break;
> case ERROR_RECOVERY:
> tcpm_swap_complete(port, -EPROTO);
> tcpm_pps_complete(port, -EPROTO);
> @@ -3889,6 +4013,13 @@ static void _tcpm_cc_change(struct tcpm_port *port, enum typec_cc_status cc1,
> * Ignore it.
> */
> break;
> + case FR_SWAP_SEND:
> + case FR_SWAP_SEND_TIMEOUT:
> + case FR_SWAP_SNK_SRC_TRANSITION_TO_OFF:
> + case FR_SWAP_SNK_SRC_NEW_SINK_READY:
> + case FR_SWAP_SNK_SRC_SOURCE_VBUS_APPLIED:
> + /* Do nothing, CC change expected */
> + break;
>
> case PORT_RESET:
> case PORT_RESET_WAIT_OFF:
> @@ -3959,6 +4090,9 @@ static void _tcpm_pd_vbus_on(struct tcpm_port *port)
> case SRC_TRY_DEBOUNCE:
> /* Do nothing, waiting for sink detection */
> break;
> + case FR_SWAP_SNK_SRC_NEW_SINK_READY:
> + tcpm_set_state(port, FR_SWAP_SNK_SRC_SOURCE_VBUS_APPLIED, 0);
> + break;
>
> case PORT_RESET:
> case PORT_RESET_WAIT_OFF:
> @@ -4038,6 +4172,14 @@ static void _tcpm_pd_vbus_off(struct tcpm_port *port)
> */
> break;
>
> + case FR_SWAP_SEND:
> + case FR_SWAP_SEND_TIMEOUT:
> + case FR_SWAP_SNK_SRC_TRANSITION_TO_OFF:
> + case FR_SWAP_SNK_SRC_NEW_SINK_READY:
> + case FR_SWAP_SNK_SRC_SOURCE_VBUS_APPLIED:
> + /* Do nothing, vbus drop expected */
> + break;
> +
> default:
> if (port->pwr_role == TYPEC_SINK &&
> port->attached)
> @@ -4092,6 +4234,25 @@ static void tcpm_pd_event_handler(struct kthread_work *work)
> if (port->tcpc->get_cc(port->tcpc, &cc1, &cc2) == 0)
> _tcpm_cc_change(port, cc1, cc2);
> }
> + if (events & TCPM_FRS_EVENT) {
> + if (port->state == SNK_READY)
> + tcpm_set_state(port, FR_SWAP_SEND, 0);
> + else
> + tcpm_log(port, "Discarding FRS_SIGNAL! Not in sink ready");
> + }
> + if (events & TCPM_SOURCING_VBUS) {
> + tcpm_log(port, "sourcing vbus");
> + /*
> + * In fast role swap case TCPC autonomously sources vbus. Set vbus_source
> + * true as TCPM wouldn't have called tcpm_set_vbus.
> + *
> + * When vbus is sourced on the command on TCPM i.e. TCPM called
> + * tcpm_set_vbus to source vbus, vbus_source would already be true.
> + */
> + port->vbus_source = true;
> + _tcpm_pd_vbus_on(port);
> + }
> +
> spin_lock(&port->pd_event_lock);
> }
> spin_unlock(&port->pd_event_lock);
> @@ -4125,6 +4286,50 @@ void tcpm_pd_hard_reset(struct tcpm_port *port)
> }
> EXPORT_SYMBOL_GPL(tcpm_pd_hard_reset);
>
> +void tcpm_sink_frs(struct tcpm_port *port)
> +{
> + spin_lock(&port->pd_event_lock);
> + port->pd_events = TCPM_FRS_EVENT;
> + spin_unlock(&port->pd_event_lock);
> + kthread_queue_work(port->wq, &port->event_work);
> +}
> +EXPORT_SYMBOL_GPL(tcpm_sink_frs);
> +
> +void tcpm_sourcing_vbus(struct tcpm_port *port)
> +{
> + spin_lock(&port->pd_event_lock);
> + port->pd_events = TCPM_SOURCING_VBUS;
> + spin_unlock(&port->pd_event_lock);
> + kthread_queue_work(port->wq, &port->event_work);
> +}
> +EXPORT_SYMBOL_GPL(tcpm_sourcing_vbus);
> +
> +static void tcpm_enable_frs_work(struct kthread_work *work)
> +{
> + struct tcpm_port *port = container_of(work, struct tcpm_port, enable_frs);
> +
> + mutex_lock(&port->lock);
> + /* Not FRS capable */
> + if (!port->connected || port->port_type != TYPEC_PORT_DRP ||
> + port->pwr_opmode != TYPEC_PWR_MODE_PD ||
> + !port->tcpc->enable_frs ||
> + /* Sink caps queried */
> + port->sink_cap_done || port->negotiated_rev < PD_REV30)
> + goto unlock;
> +
> + /* Send when the state machine is idle */
> + if (port->state != SNK_READY || port->vdm_state != VDM_STATE_DONE || port->send_discover)
> + goto resched;
> +
> + tcpm_set_state(port, GET_SINK_CAP, 0);
> + port->sink_cap_done = true;
> +
> +resched:
> + mod_enable_frs_delayed_work(port, GET_SINK_CAP_RETRY_MS);
> +unlock:
> + mutex_unlock(&port->lock);
> +}
> +
> static int tcpm_dr_set(struct typec_port *p, enum typec_data_role data)
> {
> struct tcpm_port *port = typec_get_drvdata(p);
> @@ -4532,7 +4737,7 @@ static int tcpm_fw_get_caps(struct tcpm_port *port,
> {
> const char *cap_str;
> int ret;
> - u32 mw;
> + u32 mw, frs_current;
>
> if (!fwnode)
> return -EINVAL;
> @@ -4601,6 +4806,13 @@ static int tcpm_fw_get_caps(struct tcpm_port *port,
>
> port->self_powered = fwnode_property_read_bool(fwnode, "self-powered");
>
> + /* FRS can only be supported byb DRP ports */
> + if (port->port_type == TYPEC_PORT_DRP) {
> + ret = fwnode_property_read_u32(fwnode, "frs-typec-current", &frs_current);
> + if (ret >= 0 && frs_current <= FRS_5V_3A)
> + port->frs_current = frs_current;
> + }
> +
> return 0;
> }
>
> @@ -4845,6 +5057,14 @@ static enum hrtimer_restart vdm_state_machine_timer_handler(struct hrtimer *time
> return HRTIMER_NORESTART;
> }
>
> +static enum hrtimer_restart enable_frs_timer_handler(struct hrtimer *timer)
> +{
> + struct tcpm_port *port = container_of(timer, struct tcpm_port, enable_frs_timer);
> +
> + kthread_queue_work(port->wq, &port->enable_frs);
> + return HRTIMER_NORESTART;
> +}
> +
> struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc)
> {
> struct tcpm_port *port;
> @@ -4874,10 +5094,13 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc)
> kthread_init_work(&port->state_machine, tcpm_state_machine_work);
> kthread_init_work(&port->vdm_state_machine, vdm_state_machine_work);
> kthread_init_work(&port->event_work, tcpm_pd_event_handler);
> + kthread_init_work(&port->enable_frs, tcpm_enable_frs_work);
> hrtimer_init(&port->state_machine_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
> port->state_machine_timer.function = state_machine_timer_handler;
> hrtimer_init(&port->vdm_state_machine_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
> port->vdm_state_machine_timer.function = vdm_state_machine_timer_handler;
> + hrtimer_init(&port->enable_frs_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
> + port->enable_frs_timer.function = enable_frs_timer_handler;
>
> spin_lock_init(&port->pd_event_lock);
>
> diff --git a/include/linux/usb/pd.h b/include/linux/usb/pd.h
> index f842e4589bd2..3a805e2ecbc9 100644
> --- a/include/linux/usb/pd.h
> +++ b/include/linux/usb/pd.h
> @@ -219,14 +219,16 @@ enum pd_pdo_type {
> #define PDO_CURR_MASK 0x3ff
> #define PDO_PWR_MASK 0x3ff
>
> -#define PDO_FIXED_DUAL_ROLE BIT(29) /* Power role swap supported */
> -#define PDO_FIXED_SUSPEND BIT(28) /* USB Suspend supported (Source) */
> -#define PDO_FIXED_HIGHER_CAP BIT(28) /* Requires more than vSafe5V (Sink) */
> -#define PDO_FIXED_EXTPOWER BIT(27) /* Externally powered */
> -#define PDO_FIXED_USB_COMM BIT(26) /* USB communications capable */
> -#define PDO_FIXED_DATA_SWAP BIT(25) /* Data role swap supported */
> -#define PDO_FIXED_VOLT_SHIFT 10 /* 50mV units */
> -#define PDO_FIXED_CURR_SHIFT 0 /* 10mA units */
> +#define PDO_FIXED_DUAL_ROLE BIT(29) /* Power role swap supported */
> +#define PDO_FIXED_SUSPEND BIT(28) /* USB Suspend supported (Source) */
> +#define PDO_FIXED_HIGHER_CAP BIT(28) /* Requires more than vSafe5V (Sink) */
> +#define PDO_FIXED_EXTPOWER BIT(27) /* Externally powered */
> +#define PDO_FIXED_USB_COMM BIT(26) /* USB communications capable */
> +#define PDO_FIXED_DATA_SWAP BIT(25) /* Data role swap supported */
> +#define PDO_FIXED_FRS_CURR_MASK (BIT(24) | BIT(23)) /* FR_Swap Current (Sink) */
> +#define PDO_FIXED_FRS_CURR_SHIFT 23
> +#define PDO_FIXED_VOLT_SHIFT 10 /* 50mV units */
> +#define PDO_FIXED_CURR_SHIFT 0 /* 10mA units */
>
> #define PDO_FIXED_VOLT(mv) ((((mv) / 50) & PDO_VOLT_MASK) << PDO_FIXED_VOLT_SHIFT)
> #define PDO_FIXED_CURR(ma) ((((ma) / 10) & PDO_CURR_MASK) << PDO_FIXED_CURR_SHIFT)
> @@ -454,6 +456,7 @@ static inline unsigned int rdo_max_power(u32 rdo)
> #define PD_T_DB_DETECT 10000 /* 10 - 15 seconds */
> #define PD_T_SEND_SOURCE_CAP 150 /* 100 - 200 ms */
> #define PD_T_SENDER_RESPONSE 60 /* 24 - 30 ms, relaxed */
> +#define PD_T_RECEIVER_RESPONSE 15 /* 15ms max */
> #define PD_T_SOURCE_ACTIVITY 45
> #define PD_T_SINK_ACTIVITY 135
> #define PD_T_SINK_WAIT_CAP 240
> diff --git a/include/linux/usb/tcpm.h b/include/linux/usb/tcpm.h
> index 89f58760cf48..09762d26fa0c 100644
> --- a/include/linux/usb/tcpm.h
> +++ b/include/linux/usb/tcpm.h
> @@ -78,8 +78,11 @@ enum tcpm_transmit_type {
> * automatically if a connection is established.
> * @try_role: Optional; called to set a preferred role
> * @pd_transmit:Called to transmit PD message
> - * @mux: Pointer to multiplexer data
> * @set_bist_data: Turn on/off bist data mode for compliance testing
> + * @enable_frs:
> + * Optional; Called to enable/disable PD 3.0 fast role swap.
> + * Enabling frs is accessory dependent as not all PD3.0
> + * accessories support fast role swap.
> */
> struct tcpc_dev {
> struct fwnode_handle *fwnode;
> @@ -105,6 +108,7 @@ struct tcpc_dev {
> int (*pd_transmit)(struct tcpc_dev *dev, enum tcpm_transmit_type type,
> const struct pd_message *msg);
> int (*set_bist_data)(struct tcpc_dev *dev, bool on);
> + int (*enable_frs)(struct tcpc_dev *dev, bool enable);
> };
>
> struct tcpm_port;
> @@ -114,6 +118,8 @@ void tcpm_unregister_port(struct tcpm_port *port);
>
> void tcpm_vbus_change(struct tcpm_port *port);
> void tcpm_cc_change(struct tcpm_port *port);
> +void tcpm_sink_frs(struct tcpm_port *port);
> +void tcpm_sourcing_vbus(struct tcpm_port *port);
> void tcpm_pd_receive(struct tcpm_port *port,
> const struct pd_message *msg);
> void tcpm_pd_transmit_complete(struct tcpm_port *port,
> --
> 2.28.0.618.gf4bc123cb7-goog

--
heikki