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),               \
        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)
        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
 #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;
        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;
        /* 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 */
        }
 }
 
+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)
 {
        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:
                /* 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);
                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;
                }
                                             -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;
                }
                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;
                }
        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);
 }
                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 */
                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,
                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);
                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)) {
        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);
                 * 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:
        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:
                 */
                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)
                        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);
 }
 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);
 {
        const char *cap_str;
        int ret;
-       u32 mw;
+       u32 mw, frs_current;
 
        if (!fwnode)
                return -EINVAL;
 
        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;
 }
 
        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;
        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);