From bedbef8ad3e75a304af6361af235302bcc61d06b Mon Sep 17 00:00:00 2001
From: hc <hc@nodka.com>
Date: Tue, 14 May 2024 06:39:01 +0000
Subject: [PATCH] 修改内核路径
---
kernel/drivers/usb/typec/tcpm/tcpm.c | 2331 ++++++++++++++++++++++++++++++++++++++---------------------
1 files changed, 1,488 insertions(+), 843 deletions(-)
diff --git a/kernel/drivers/usb/typec/tcpm/tcpm.c b/kernel/drivers/usb/typec/tcpm/tcpm.c
index deac55e..f474eeb 100644
--- a/kernel/drivers/usb/typec/tcpm/tcpm.c
+++ b/kernel/drivers/usb/typec/tcpm/tcpm.c
@@ -8,7 +8,6 @@
#include <linux/completion.h>
#include <linux/debugfs.h>
#include <linux/device.h>
-#include <linux/extcon-provider.h>
#include <linux/hrtimer.h>
#include <linux/jiffies.h>
#include <linux/kernel.h>
@@ -18,11 +17,11 @@
#include <linux/power_supply.h>
#include <linux/proc_fs.h>
#include <linux/property.h>
-#include <linux/regulator/consumer.h>
#include <linux/sched/clock.h>
#include <linux/seq_file.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
+#include <linux/usb.h>
#include <linux/usb/pd.h>
#include <linux/usb/pd_ado.h>
#include <linux/usb/pd_bdo.h>
@@ -31,8 +30,8 @@
#include <linux/usb/role.h>
#include <linux/usb/tcpm.h>
#include <linux/usb/typec_altmode.h>
-#include <linux/usb/typec_dp.h>
+#include <trace/hooks/typec.h>
#include <uapi/linux/sched/types.h>
#define FOREACH_STATE(S) \
@@ -145,7 +144,8 @@
S(PORT_RESET), \
S(PORT_RESET_WAIT_OFF), \
\
- S(AMS_START)
+ S(AMS_START), \
+ S(CHUNK_NOT_SUPP)
#define FOREACH_AMS(S) \
S(NONE_AMS), \
@@ -223,6 +223,14 @@
PD_MSG_DATA_SOURCE_CAP,
};
+enum adev_actions {
+ ADEV_NONE = 0,
+ ADEV_NOTIFY_USB_AND_QUEUE_VDM,
+ ADEV_QUEUE_VDM,
+ ADEV_QUEUE_VDM_SEND_EXIT_MODE_ON_FAIL,
+ 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,
@@ -234,24 +242,6 @@
FRS_5V_1P5A,
FRS_5V_3A,
};
-
-static const unsigned int tcpm_cable[] = {
- EXTCON_USB,
- EXTCON_USB_HOST,
- EXTCON_USB_VBUS_EN,
- EXTCON_CHG_USB_SDP,
- EXTCON_CHG_USB_CDP,
- EXTCON_CHG_USB_DCP,
- EXTCON_CHG_USB_SLOW,
- EXTCON_CHG_USB_FAST,
- EXTCON_DISP_DP,
- EXTCON_NONE,
-};
-
-/* Pin assignments where one channel is for USB */
-#define DP_PIN_ASSIGN_MULTI_FUNCTION_MASK (BIT(DP_PIN_ASSIGN_B) | \
- BIT(DP_PIN_ASSIGN_D) | \
- BIT(DP_PIN_ASSIGN_F))
/* Events from low level driver */
@@ -270,7 +260,7 @@
#define ALTMODE_DISCOVERY_MAX (SVID_DISCOVERY_MAX * MODE_DISCOVERY_MAX)
#define GET_SINK_CAP_RETRY_MS 100
-#define SEND_NEW_MODE_NOTIFY_MS 20
+#define SEND_DISCOVER_RETRY_MS 100
struct pd_mode_data {
int svid_index; /* current SVID index */
@@ -280,12 +270,27 @@
struct typec_altmode_desc altmode_desc[ALTMODE_DISCOVERY_MAX];
};
+/*
+ * @min_volt: Actual min voltage at the local port
+ * @req_min_volt: Requested min voltage to the port partner
+ * @max_volt: Actual max voltage at the local port
+ * @req_max_volt: Requested max voltage to the port partner
+ * @max_curr: Actual max current at the local port
+ * @req_max_curr: Requested max current of the port partner
+ * @req_out_volt: Requested output voltage to the port partner
+ * @req_op_curr: Requested operating current to the port partner
+ * @supported: Parter has atleast one APDO hence supports PPS
+ * @active: PPS mode is active
+ */
struct pd_pps_data {
u32 min_volt;
+ u32 req_min_volt;
u32 max_volt;
+ u32 req_max_volt;
u32 max_curr;
- u32 out_volt;
- u32 op_curr;
+ u32 req_max_curr;
+ u32 req_out_volt;
+ u32 req_op_curr;
bool supported;
bool active;
};
@@ -311,9 +316,6 @@
struct typec_partner_desc partner_desc;
struct typec_partner *partner;
- struct regulator *vbus;
- struct extcon_dev *extcon;
-
enum typec_cc_status cc_req;
enum typec_cc_status cc1;
@@ -323,14 +325,26 @@
bool attached;
bool connected;
enum typec_port_type port_type;
+
+ /*
+ * Set to true when vbus is greater than VSAFE5V min.
+ * Set to false when vbus falls below vSinkDisconnect max threshold.
+ */
bool vbus_present;
+
+ /*
+ * Set to true when vbus is less than VSAFE0V max.
+ * Set to false when vbus is greater than VSAFE0V max.
+ */
+ bool vbus_vsafe0v;
+
bool vbus_never_low;
bool vbus_source;
bool vbus_charge;
+ /* Set to true when Discover_Identity Command is expected to be sent in Ready states. */
bool send_discover;
bool op_vsafe5v;
- bool vbus_on;
int try_role;
int try_snk_count;
@@ -346,7 +360,9 @@
unsigned long delay_ms;
spinlock_t pd_event_lock;
+#ifdef CONFIG_NO_GKI
struct mutex pd_handler_lock;
+#endif
u32 pd_events;
struct kthread_work event_work;
@@ -356,9 +372,10 @@
struct kthread_work vdm_state_machine;
struct hrtimer enable_frs_timer;
struct kthread_work enable_frs;
- struct kthread_work data_role_swap;
- struct hrtimer data_role_swap_timer;
+ struct hrtimer send_discover_timer;
+ struct kthread_work send_discover_work;
bool state_machine_running;
+ /* Set to true when VDM State Machine has following actions. */
bool vdm_sm_running;
struct completion tx_complete;
@@ -378,10 +395,6 @@
bool explicit_contract;
unsigned int rx_msgid;
- u32 dp_pin_assignment;
- u32 dp_status;
- bool dp_configured;
-
/* Partner capabilities/requests */
u32 sink_request;
u32 source_caps[PDO_MAX_OBJECTS];
@@ -394,13 +407,18 @@
unsigned int nr_src_pdo;
u32 snk_pdo[PDO_MAX_OBJECTS];
unsigned int nr_snk_pdo;
+ u32 snk_vdo_v1[VDO_MAX_OBJECTS];
+ unsigned int nr_snk_vdo_v1;
u32 snk_vdo[VDO_MAX_OBJECTS];
unsigned int nr_snk_vdo;
unsigned int operating_snk_mw;
bool update_sink_caps;
- /* Requested current / voltage */
+ /* Requested current / voltage to the port partner */
+ u32 req_current_limit;
+ u32 req_supply_voltage;
+ /* Actual current / voltage limit of the local port */
u32 current_limit;
u32 supply_voltage;
@@ -437,17 +455,30 @@
/* port belongs to a self powered device */
bool self_powered;
- /* FRS */
- enum frs_typec_current frs_current;
+ /* Sink FRS */
+ enum frs_typec_current new_source_frs_current;
/* Sink caps have been queried */
bool sink_cap_done;
+ /* Port is still in tCCDebounce */
+ bool debouncing;
+
/* Collision Avoidance and Atomic Message Sequence */
enum tcpm_state upcoming_state;
enum tcpm_ams ams;
+ enum tcpm_ams next_ams;
bool in_ams;
+ /* Auto vbus discharge status */
+ bool auto_vbus_discharge_enabled;
+
+ /*
+ * When set, port requests PD_P_SNK_STDBY_MW upon entering SNK_DISCOVERY and
+ * the actual currrent limit after RX of PD_CTRL_PSRDY for PD link,
+ * SNK_READY for non-pd link.
+ */
+ bool slow_charger_loop;
#ifdef CONFIG_DEBUG_FS
struct dentry *dentry;
struct mutex logbuffer_lock; /* log buffer access lock */
@@ -505,9 +536,20 @@
((port)->try_src_count == 0 && (port)->try_role == TYPEC_SOURCE && \
(port)->port_type == TYPEC_PORT_DRP)
+#define tcpm_data_role_for_source(port) \
+ ((port)->typec_caps.data == TYPEC_PORT_UFP ? \
+ TYPEC_DEVICE : TYPEC_HOST)
+
+#define tcpm_data_role_for_sink(port) \
+ ((port)->typec_caps.data == TYPEC_PORT_DFP ? \
+ TYPEC_HOST : TYPEC_DEVICE)
+
#define tcpm_sink_tx_ok(port) \
(tcpm_port_is_sink(port) && \
((port)->cc1 == TYPEC_CC_RP_3_0 || (port)->cc2 == TYPEC_CC_RP_3_0))
+
+#define tcpm_wait_for_discharge(port) \
+ (((port)->auto_vbus_discharge_enabled && !(port)->vbus_vsafe0v) ? PD_T_SAFE_0V : 0)
static enum tcpm_state tcpm_default_state(struct tcpm_port *port)
{
@@ -521,12 +563,6 @@
return SNK_UNATTACHED;
}
return SRC_UNATTACHED;
-}
-
-static inline
-struct tcpm_port *typec_cap_to_tcpm(const struct typec_capability *cap)
-{
- return container_of(cap, struct tcpm_port, typec_caps);
}
static bool tcpm_port_is_disconnected(struct tcpm_port *port)
@@ -557,6 +593,7 @@
char tmpbuffer[LOG_BUFFER_ENTRY_SIZE];
u64 ts_nsec = local_clock();
unsigned long rem_nsec;
+ bool bypass_log = false;
mutex_lock(&port->logbuffer_lock);
if (!port->logbuffer[port->logbuffer_head]) {
@@ -569,6 +606,9 @@
}
vsnprintf(tmpbuffer, sizeof(tmpbuffer), fmt, args);
+ trace_android_vh_typec_tcpm_log(tmpbuffer, &bypass_log);
+ if (bypass_log)
+ goto abort;
if (tcpm_log_full(port)) {
port->logbuffer_head = max(port->logbuffer_head - 1, 0);
@@ -705,17 +745,13 @@
}
DEFINE_SHOW_ATTRIBUTE(tcpm_debug);
-static struct dentry *rootdir;
-
static void tcpm_debugfs_init(struct tcpm_port *port)
{
- mutex_init(&port->logbuffer_lock);
- /* /sys/kernel/debug/tcpm/usbcX */
- if (!rootdir)
- rootdir = debugfs_create_dir("tcpm", NULL);
+ char name[NAME_MAX];
- port->dentry = debugfs_create_file(dev_name(port->dev),
- S_IFREG | 0444, rootdir,
+ mutex_init(&port->logbuffer_lock);
+ snprintf(name, NAME_MAX, "tcpm-%s", dev_name(port->dev));
+ port->dentry = debugfs_create_file(name, S_IFREG | 0444, usb_debug_root,
port, &tcpm_debug_fops);
}
@@ -731,10 +767,6 @@
mutex_unlock(&port->logbuffer_lock);
debugfs_remove(port->dentry);
- if (list_empty(&rootdir->d_subdirs)) {
- debugfs_remove(rootdir);
- rootdir = NULL;
- }
}
#else
@@ -749,110 +781,39 @@
#endif
-static int tcpm_send_vbus_notify(struct tcpm_port *port, bool enable)
-{
- int ret = 0;
-
- if (port->vbus_on == enable) {
- tcpm_log(port, "vbus is already %s", enable ? "on" : "Off");
- goto done;
- }
-
- if (port->vbus) {
- if (enable)
- ret = regulator_enable(port->vbus);
- else
- ret = regulator_disable(port->vbus);
- if (ret < 0) {
- dev_err(port->dev, "failed to %s vbus supply(%d)\n",
- enable ? "enable" : "disable", ret);
- goto done;
- }
-
- /* Only set state here, don't sync notifier to PMIC */
- extcon_set_state(port->extcon, EXTCON_USB_VBUS_EN, enable);
- } else {
- extcon_set_state(port->extcon, EXTCON_USB_VBUS_EN, enable);
- extcon_sync(port->extcon, EXTCON_USB_VBUS_EN);
- tcpm_log(port, "tcpm driver send extcon to %s vbus 5v\n",
- enable ? "enable" : "disable");
- }
-
- port->vbus_on = enable;
-
-done:
- return ret;
-}
-
-static void tcpm_send_orientation_notify(struct tcpm_port *port)
-{
- union extcon_property_value property;
-
- property.intval = port->polarity;
- extcon_set_property(port->extcon, EXTCON_USB,
- EXTCON_PROP_USB_TYPEC_POLARITY, property);
- extcon_set_property(port->extcon, EXTCON_USB_HOST,
- EXTCON_PROP_USB_TYPEC_POLARITY, property);
- extcon_set_property(port->extcon, EXTCON_DISP_DP,
- EXTCON_PROP_USB_TYPEC_POLARITY, property);
-}
-
-static void tcpm_send_data_role_notify(struct tcpm_port *port, bool attached,
- enum typec_data_role data)
-{
- bool dfp = false;
- bool ufp = false;
-
- if (attached) {
- if (data == TYPEC_HOST)
- dfp = true;
- else
- ufp = true;
- }
- extcon_set_state(port->extcon, EXTCON_USB, ufp);
- extcon_set_state(port->extcon, EXTCON_USB_HOST, dfp);
- extcon_sync(port->extcon, EXTCON_USB);
- extcon_sync(port->extcon, EXTCON_USB_HOST);
-}
-
-static void tcpm_send_dp_notify(struct tcpm_port *port)
-{
- union extcon_property_value property;
- bool usb_ss = false;
- bool hpd = false;
-
- if (port->dp_configured) {
- usb_ss = (port->dp_pin_assignment &
- DP_PIN_ASSIGN_MULTI_FUNCTION_MASK) ? true : false;
- hpd = !!(port->dp_status & DP_STATUS_HPD_STATE);
- }
- property.intval = usb_ss;
- extcon_set_property(port->extcon, EXTCON_USB,
- EXTCON_PROP_USB_SS, property);
- extcon_set_property(port->extcon, EXTCON_USB_HOST,
- EXTCON_PROP_USB_SS, property);
- extcon_set_property(port->extcon, EXTCON_DISP_DP,
- EXTCON_PROP_USB_SS, property);
- extcon_set_state(port->extcon, EXTCON_DISP_DP, port->dp_configured && hpd);
- extcon_sync(port->extcon, EXTCON_DISP_DP);
-}
-
-static void tcpm_send_power_change_notify(struct tcpm_port *port)
-{
- union extcon_property_value property;
-
- property.intval = (port->current_limit << 15 | port->supply_voltage);
- extcon_set_property(port->extcon, EXTCON_CHG_USB_FAST,
- EXTCON_PROP_USB_TYPEC_POLARITY, property);
- extcon_set_state(port->extcon, EXTCON_CHG_USB_FAST, true);
- extcon_sync(port->extcon, EXTCON_CHG_USB_FAST);
-}
-
static void tcpm_set_cc(struct tcpm_port *port, enum typec_cc_status cc)
{
tcpm_log(port, "cc:=%d", cc);
port->cc_req = cc;
port->tcpc->set_cc(port->tcpc, cc);
+}
+
+static int tcpm_enable_auto_vbus_discharge(struct tcpm_port *port, bool enable)
+{
+ int ret = 0;
+
+ if (port->tcpc->enable_auto_vbus_discharge) {
+ ret = port->tcpc->enable_auto_vbus_discharge(port->tcpc, enable);
+ tcpm_log_force(port, "%s vbus discharge ret:%d", enable ? "enable" : "disable",
+ ret);
+ if (!ret)
+ port->auto_vbus_discharge_enabled = enable;
+ }
+
+ return ret;
+}
+
+static void tcpm_apply_rc(struct tcpm_port *port)
+{
+ /*
+ * TCPCI: Move to APPLY_RC state to prevent disconnect during PR_SWAP
+ * when Vbus auto discharge on disconnect is enabled.
+ */
+ if (port->tcpc->enable_auto_vbus_discharge && port->tcpc->apply_rc) {
+ tcpm_log(port, "Apply_RC");
+ port->tcpc->apply_rc(port->tcpc, port->cc_req, port->polarity);
+ tcpm_enable_auto_vbus_discharge(port, false);
+ }
}
/*
@@ -888,10 +849,8 @@
return TYPEC_CC_RP_DEF;
}
-static int tcpm_ams_finish(struct tcpm_port *port)
+static void tcpm_ams_finish(struct tcpm_port *port)
{
- int ret = 0;
-
tcpm_log(port, "AMS %s finished", tcpm_ams_str[port->ams]);
if (port->pd_capable && port->pwr_role == TYPEC_SOURCE) {
@@ -905,8 +864,6 @@
port->in_ams = false;
port->ams = NONE_AMS;
-
- return ret;
}
static int tcpm_pd_transmit(struct tcpm_port *port,
@@ -922,7 +879,7 @@
tcpm_log(port, "PD TX, type: %#x", type);
reinit_completion(&port->tx_complete);
- ret = port->tcpc->pd_transmit(port->tcpc, type, msg);
+ ret = port->tcpc->pd_transmit(port->tcpc, type, msg, port->negotiated_rev);
if (ret < 0)
return ret;
@@ -1024,6 +981,21 @@
return ret;
}
+bool tcpm_is_debouncing(struct tcpm_port *port)
+{
+ bool debounce;
+
+ if (!port)
+ return false;
+
+ mutex_lock(&port->lock);
+ debounce = port->debouncing;
+ mutex_unlock(&port->lock);
+
+ return debounce;
+}
+EXPORT_SYMBOL_GPL(tcpm_is_debouncing);
+
static u32 tcpm_get_current_limit(struct tcpm_port *port)
{
enum typec_cc_status cc;
@@ -1057,6 +1029,7 @@
port->supply_voltage = mv;
port->current_limit = max_ma;
+ power_supply_changed(port->psy);
if (port->tcpc->set_current_limit)
ret = port->tcpc->set_current_limit(port->tcpc, max_ma, mv);
@@ -1082,10 +1055,30 @@
else
orientation = TYPEC_ORIENTATION_REVERSE;
- if (data == TYPEC_HOST)
- usb_role = USB_ROLE_HOST;
- else
- usb_role = USB_ROLE_DEVICE;
+ if (port->typec_caps.data == TYPEC_PORT_DRD) {
+ if (data == TYPEC_HOST)
+ usb_role = USB_ROLE_HOST;
+ else
+ usb_role = USB_ROLE_DEVICE;
+ } else if (port->typec_caps.data == TYPEC_PORT_DFP) {
+ if (data == TYPEC_HOST) {
+ if (role == TYPEC_SOURCE)
+ usb_role = USB_ROLE_HOST;
+ else
+ usb_role = USB_ROLE_NONE;
+ } else {
+ return -ENOTSUPP;
+ }
+ } else {
+ if (data == TYPEC_DEVICE) {
+ if (role == TYPEC_SINK)
+ usb_role = USB_ROLE_DEVICE;
+ else
+ usb_role = USB_ROLE_NONE;
+ } else {
+ return -ENOTSUPP;
+ }
+ }
ret = tcpm_mux_set(port, TYPEC_STATE_USB, usb_role, orientation);
if (ret < 0)
@@ -1118,13 +1111,47 @@
return 0;
}
+/*
+ * Transform the PDO to be compliant to PD rev2.0.
+ * Return 0 if the PDO type is not defined in PD rev2.0.
+ * Otherwise, return the converted PDO.
+ */
+static u32 tcpm_forge_legacy_pdo(struct tcpm_port *port, u32 pdo, enum typec_role role)
+{
+ switch (pdo_type(pdo)) {
+ case PDO_TYPE_FIXED:
+ if (role == TYPEC_SINK)
+ return pdo & ~PDO_FIXED_FRS_CURR_MASK;
+ else
+ return pdo & ~PDO_FIXED_UNCHUNK_EXT;
+ case PDO_TYPE_VAR:
+ case PDO_TYPE_BATT:
+ return pdo;
+ case PDO_TYPE_APDO:
+ default:
+ return 0;
+ }
+}
+
static int tcpm_pd_send_source_caps(struct tcpm_port *port)
{
struct pd_message msg;
- int i;
+ u32 pdo;
+ unsigned int i, nr_pdo = 0;
memset(&msg, 0, sizeof(msg));
- if (!port->nr_src_pdo) {
+
+ for (i = 0; i < port->nr_src_pdo; i++) {
+ if (port->negotiated_rev >= PD_REV30) {
+ msg.payload[nr_pdo++] = cpu_to_le32(port->src_pdo[i]);
+ } else {
+ pdo = tcpm_forge_legacy_pdo(port, port->src_pdo[i], TYPEC_SOURCE);
+ if (pdo)
+ msg.payload[nr_pdo++] = cpu_to_le32(pdo);
+ }
+ }
+
+ if (!nr_pdo) {
/* No source capabilities defined, sink only */
msg.header = PD_HEADER_LE(PD_CTRL_REJECT,
port->pwr_role,
@@ -1137,10 +1164,8 @@
port->data_role,
port->negotiated_rev,
port->message_id,
- port->nr_src_pdo);
+ nr_pdo);
}
- for (i = 0; i < port->nr_src_pdo; i++)
- msg.payload[i] = cpu_to_le32(port->src_pdo[i]);
return tcpm_pd_transmit(port, TCPC_TX_SOP, &msg);
}
@@ -1148,10 +1173,22 @@
static int tcpm_pd_send_sink_caps(struct tcpm_port *port)
{
struct pd_message msg;
- int i;
+ u32 pdo;
+ unsigned int i, nr_pdo = 0;
memset(&msg, 0, sizeof(msg));
- if (!port->nr_snk_pdo) {
+
+ for (i = 0; i < port->nr_snk_pdo; i++) {
+ if (port->negotiated_rev >= PD_REV30) {
+ msg.payload[nr_pdo++] = cpu_to_le32(port->snk_pdo[i]);
+ } else {
+ pdo = tcpm_forge_legacy_pdo(port, port->snk_pdo[i], TYPEC_SINK);
+ if (pdo)
+ msg.payload[nr_pdo++] = cpu_to_le32(pdo);
+ }
+ }
+
+ if (!nr_pdo) {
/* No sink capabilities defined, source only */
msg.header = PD_HEADER_LE(PD_CTRL_REJECT,
port->pwr_role,
@@ -1164,10 +1201,8 @@
port->data_role,
port->negotiated_rev,
port->message_id,
- port->nr_snk_pdo);
+ nr_pdo);
}
- for (i = 0; i < port->nr_snk_pdo; i++)
- msg.payload[i] = cpu_to_le32(port->snk_pdo[i]);
return tcpm_pd_transmit(port, TCPC_TX_SOP, &msg);
}
@@ -1203,14 +1238,13 @@
}
}
-static void mod_data_role_swap_work(struct tcpm_port *port, unsigned int delay_ms)
+static void mod_send_discover_delayed_work(struct tcpm_port *port, unsigned int delay_ms)
{
if (delay_ms) {
- hrtimer_start(&port->data_role_swap_timer,
- ms_to_ktime(delay_ms), HRTIMER_MODE_REL);
+ hrtimer_start(&port->send_discover_timer, ms_to_ktime(delay_ms), HRTIMER_MODE_REL);
} else {
- hrtimer_cancel(&port->data_role_swap_timer);
- kthread_queue_work(port->wq, &port->data_role_swap);
+ hrtimer_cancel(&port->send_discover_timer);
+ kthread_queue_work(port->wq, &port->send_discover_work);
}
}
@@ -1322,7 +1356,8 @@
tcpm_log(port, "AMS %s start", tcpm_ams_str[ams]);
- if (!tcpm_ams_interruptible(port) && ams != HARD_RESET) {
+ if (!tcpm_ams_interruptible(port) &&
+ !(ams == HARD_RESET || ams == SOFT_RESET_AMS)) {
port->upcoming_state = INVALID_STATE;
tcpm_log(port, "AMS %s not interruptible, aborting",
tcpm_ams_str[port->ams]);
@@ -1340,11 +1375,10 @@
tcpm_set_state(port, HARD_RESET_START, 0);
return ret;
} else if (ams == SOFT_RESET_AMS) {
- if (!port->explicit_contract) {
- port->upcoming_state = INVALID_STATE;
+ if (!port->explicit_contract)
tcpm_set_cc(port, tcpm_rp_cc(port));
- return ret;
- }
+ tcpm_set_state(port, SOFT_RESET_SEND, 0);
+ return ret;
} else if (tcpm_vdm_ams(port)) {
/* tSinkTx is enforced in vdm_run_state_machine */
if (port->negotiated_rev >= PD_REV30)
@@ -1413,24 +1447,56 @@
static void tcpm_queue_vdm(struct tcpm_port *port, const u32 header,
const u32 *data, int cnt)
{
+ u32 vdo_hdr = port->vdo_data[0];
+
+ WARN_ON(!mutex_is_locked(&port->lock));
+
+ /* If is sending discover_identity, handle received message first */
+ if (PD_VDO_SVDM(vdo_hdr) && PD_VDO_CMD(vdo_hdr) == CMD_DISCOVER_IDENT) {
+ port->send_discover = true;
+ mod_send_discover_delayed_work(port, SEND_DISCOVER_RETRY_MS);
+ } else {
+ /* Make sure we are not still processing a previous VDM packet */
+ WARN_ON(port->vdm_state > VDM_STATE_DONE);
+ }
+
port->vdo_count = cnt + 1;
port->vdo_data[0] = header;
memcpy(&port->vdo_data[1], data, sizeof(u32) * cnt);
/* Set ready, vdm state machine will actually send */
port->vdm_retries = 0;
port->vdm_state = VDM_STATE_READY;
+ port->vdm_sm_running = true;
+
+ mod_vdm_delayed_work(port, 0);
}
-static void svdm_consume_identity(struct tcpm_port *port, const __le32 *payload,
- int cnt)
+static void tcpm_queue_vdm_unlocked(struct tcpm_port *port, const u32 header,
+ const u32 *data, int cnt)
{
- u32 vdo = le32_to_cpu(payload[VDO_INDEX_IDH]);
- u32 product = le32_to_cpu(payload[VDO_INDEX_PRODUCT]);
+#ifdef CONFIG_NO_GKI
+ mutex_lock(&port->pd_handler_lock);
+ if (tcpm_port_is_disconnected(port))
+ goto unlock;
+#endif
+ mutex_lock(&port->lock);
+ tcpm_queue_vdm(port, header, data, cnt);
+ mutex_unlock(&port->lock);
+#ifdef CONFIG_NO_GKI
+unlock:
+ mutex_unlock(&port->pd_handler_lock);
+#endif
+}
+
+static void svdm_consume_identity(struct tcpm_port *port, const u32 *p, int cnt)
+{
+ u32 vdo = p[VDO_INDEX_IDH];
+ u32 product = p[VDO_INDEX_PRODUCT];
memset(&port->mode_data, 0, sizeof(port->mode_data));
port->partner_ident.id_header = vdo;
- port->partner_ident.cert_stat = le32_to_cpu(payload[VDO_INDEX_CSTAT]);
+ port->partner_ident.cert_stat = p[VDO_INDEX_CSTAT];
port->partner_ident.product = product;
typec_partner_set_identity(port->partner);
@@ -1440,17 +1506,15 @@
PD_PRODUCT_PID(product), product & 0xffff);
}
-static bool svdm_consume_svids(struct tcpm_port *port, const __le32 *payload,
- int cnt)
+static bool svdm_consume_svids(struct tcpm_port *port, const u32 *p, int cnt)
{
struct pd_mode_data *pmdata = &port->mode_data;
int i;
for (i = 1; i < cnt; i++) {
- u32 p = le32_to_cpu(payload[i]);
u16 svid;
- svid = (p >> 16) & 0xffff;
+ svid = (p[i] >> 16) & 0xffff;
if (!svid)
return false;
@@ -1460,7 +1524,7 @@
pmdata->svids[pmdata->nsvids++] = svid;
tcpm_log(port, "SVID %d: 0x%x", pmdata->nsvids, svid);
- svid = p & 0xffff;
+ svid = p[i] & 0xffff;
if (!svid)
return false;
@@ -1470,14 +1534,27 @@
pmdata->svids[pmdata->nsvids++] = svid;
tcpm_log(port, "SVID %d: 0x%x", pmdata->nsvids, svid);
}
- return true;
+
+ /*
+ * PD3.0 Spec 6.4.4.3.2: The SVIDs are returned 2 per VDO (see Table
+ * 6-43), and can be returned maximum 6 VDOs per response (see Figure
+ * 6-19). If the Respondersupports 12 or more SVID then the Discover
+ * SVIDs Command Shall be executed multiple times until a Discover
+ * SVIDs VDO is returned ending either with a SVID value of 0x0000 in
+ * the last part of the last VDO or with a VDO containing two SVIDs
+ * with values of 0x0000.
+ *
+ * However, some odd dockers support SVIDs less than 12 but without
+ * 0x0000 in the last VDO, so we need to break the Discover SVIDs
+ * request and return false here.
+ */
+ return cnt == 7;
abort:
tcpm_log(port, "SVID_DISCOVERY_MAX(%d) too low!", SVID_DISCOVERY_MAX);
return false;
}
-static void svdm_consume_modes(struct tcpm_port *port, const __le32 *payload,
- int cnt)
+static void svdm_consume_modes(struct tcpm_port *port, const u32 *p, int cnt)
{
struct pd_mode_data *pmdata = &port->mode_data;
struct typec_altmode_desc *paltmode;
@@ -1494,7 +1571,7 @@
paltmode->svid = pmdata->svids[pmdata->svid_index];
paltmode->mode = i;
- paltmode->vdo = le32_to_cpu(payload[i]);
+ paltmode->vdo = p[i];
tcpm_log(port, " Alternate mode %d: SVID 0x%04x, VDO %d: 0x%08x",
pmdata->altmodes, paltmode->svid,
@@ -1513,29 +1590,29 @@
for (i = 0; i < modep->altmodes; i++) {
altmode = typec_partner_register_altmode(port->partner,
&modep->altmode_desc[i]);
- if (!altmode)
+ if (IS_ERR(altmode)) {
tcpm_log(port, "Failed to register partner SVID 0x%04x",
modep->altmode_desc[i].svid);
+ altmode = NULL;
+ }
port->partner_altmode[i] = altmode;
}
}
#define supports_modal(port) PD_IDH_MODAL_SUPP((port)->partner_ident.id_header)
-static int tcpm_pd_svdm(struct tcpm_port *port, const __le32 *payload, int cnt,
- u32 *response)
+static int tcpm_pd_svdm(struct tcpm_port *port, struct typec_altmode *adev,
+ const u32 *p, int cnt, u32 *response,
+ enum adev_actions *adev_action)
{
- struct typec_altmode *adev;
+ struct typec_port *typec = port->typec_port;
struct typec_altmode *pdev;
struct pd_mode_data *modep;
- u32 p[PD_MAX_PAYLOAD];
+ int svdm_version;
int rlen = 0;
int cmd_type;
int cmd;
int i;
-
- for (i = 0; i < cnt; i++)
- p[i] = le32_to_cpu(payload[i]);
cmd_type = PD_VDO_CMDT(p[0]);
cmd = PD_VDO_CMD(p[0]);
@@ -1545,36 +1622,61 @@
modep = &port->mode_data;
- adev = typec_match_altmode(port->port_altmode, ALTMODE_DISCOVERY_MAX,
- PD_VDO_VID(p[0]), PD_VDO_OPOS(p[0]));
-
pdev = typec_match_altmode(port->partner_altmode, ALTMODE_DISCOVERY_MAX,
PD_VDO_VID(p[0]), PD_VDO_OPOS(p[0]));
+
+ svdm_version = typec_get_negotiated_svdm_version(typec);
+ if (svdm_version < 0)
+ return 0;
switch (cmd_type) {
case CMDT_INIT:
switch (cmd) {
case CMD_DISCOVER_IDENT:
- /* 6.4.4.3.1: Only respond as UFP (device) */
- if (port->data_role == TYPEC_DEVICE &&
+ if (PD_VDO_VID(p[0]) != USB_SID_PD)
+ break;
+
+ if (PD_VDO_SVDM_VER(p[0]) < svdm_version) {
+ typec_partner_set_svdm_version(port->partner,
+ PD_VDO_SVDM_VER(p[0]));
+ svdm_version = PD_VDO_SVDM_VER(p[0]);
+ }
+
+ port->ams = DISCOVER_IDENTITY;
+ /*
+ * PD2.0 Spec 6.10.3: respond with NAK as DFP (data host)
+ * PD3.1 Spec 6.4.4.2.5.1: respond with NAK if "invalid field" or
+ * "wrong configuation" or "Unrecognized"
+ */
+ if ((port->data_role == TYPEC_DEVICE || svdm_version >= SVDM_VER_2_0) &&
port->nr_snk_vdo) {
- for (i = 0; i < port->nr_snk_vdo; i++)
- response[i + 1] = port->snk_vdo[i];
- rlen = port->nr_snk_vdo + 1;
+ if (svdm_version < SVDM_VER_2_0) {
+ for (i = 0; i < port->nr_snk_vdo_v1; i++)
+ response[i + 1] = port->snk_vdo_v1[i];
+ rlen = port->nr_snk_vdo_v1 + 1;
+
+ } else {
+ for (i = 0; i < port->nr_snk_vdo; i++)
+ response[i + 1] = port->snk_vdo[i];
+ rlen = port->nr_snk_vdo + 1;
+ }
}
break;
case CMD_DISCOVER_SVID:
+ port->ams = DISCOVER_SVIDS;
break;
case CMD_DISCOVER_MODES:
+ port->ams = DISCOVER_MODES;
break;
case CMD_ENTER_MODE:
+ port->ams = DFP_TO_UFP_ENTER_MODE;
break;
case CMD_EXIT_MODE:
+ port->ams = DFP_TO_UFP_EXIT_MODE;
break;
case CMD_ATTENTION:
/* Attention command does not have response */
- if (adev)
- typec_altmode_attention(adev, p[1]);
+ *adev_action = ADEV_ATTENTION;
return 0;
default:
break;
@@ -1588,6 +1690,8 @@
response[0] = p[0] | VDO_CMDT(CMDT_RSP_BUSY);
rlen = 1;
}
+ response[0] = (response[0] & ~VDO_SVDM_VERS_MASK) |
+ (VDO_SVDM_VERS(typec_get_negotiated_svdm_version(typec)));
break;
case CMDT_RSP_ACK:
/* silently drop message if we are not connected */
@@ -1598,101 +1702,139 @@
switch (cmd) {
case CMD_DISCOVER_IDENT:
+ if (PD_VDO_SVDM_VER(p[0]) < svdm_version)
+ typec_partner_set_svdm_version(port->partner,
+ PD_VDO_SVDM_VER(p[0]));
/* 6.4.4.3.1 */
- svdm_consume_identity(port, payload, cnt);
- response[0] = VDO(USB_SID_PD, 1, CMD_DISCOVER_SVID);
+ svdm_consume_identity(port, p, cnt);
+ response[0] = VDO(USB_SID_PD, 1, typec_get_negotiated_svdm_version(typec),
+ CMD_DISCOVER_SVID);
rlen = 1;
break;
case CMD_DISCOVER_SVID:
/* 6.4.4.3.2 */
- if (svdm_consume_svids(port, payload, cnt)) {
- response[0] = VDO(USB_SID_PD, 1,
- CMD_DISCOVER_SVID);
+ if (svdm_consume_svids(port, p, cnt)) {
+ response[0] = VDO(USB_SID_PD, 1, svdm_version, CMD_DISCOVER_SVID);
rlen = 1;
} else if (modep->nsvids && supports_modal(port)) {
- response[0] = VDO(modep->svids[0], 1,
+ response[0] = VDO(modep->svids[0], 1, svdm_version,
CMD_DISCOVER_MODES);
rlen = 1;
}
break;
case CMD_DISCOVER_MODES:
/* 6.4.4.3.3 */
- svdm_consume_modes(port, payload, cnt);
+ svdm_consume_modes(port, p, cnt);
modep->svid_index++;
if (modep->svid_index < modep->nsvids) {
u16 svid = modep->svids[modep->svid_index];
- response[0] = VDO(svid, 1, CMD_DISCOVER_MODES);
+ response[0] = VDO(svid, 1, svdm_version, CMD_DISCOVER_MODES);
rlen = 1;
- } else {
+ } else if (port->data_role == TYPEC_HOST) {
tcpm_register_partner_altmodes(port);
- port->vdm_sm_running = false;
+ } else {
+ /* Do dr_swap for ufp if the port supports drd */
+ if (port->typec_caps.data == TYPEC_PORT_DRD &&
+ !IS_ERR_OR_NULL(port->port_altmode[0])) {
+ port->vdm_sm_running = false;
+ port->upcoming_state = DR_SWAP_SEND;
+ tcpm_ams_start(port, DATA_ROLE_SWAP);
+ }
}
break;
case CMD_ENTER_MODE:
if (adev && pdev) {
typec_altmode_update_active(pdev, true);
-
- if (typec_altmode_vdm(adev, p[0], &p[1], cnt)) {
- response[0] = VDO(adev->svid, 1,
- CMD_EXIT_MODE);
- response[0] |= VDO_OPOS(adev->mode);
- return 1;
- }
+ *adev_action = ADEV_QUEUE_VDM_SEND_EXIT_MODE_ON_FAIL;
}
return 0;
case CMD_EXIT_MODE:
if (adev && pdev) {
typec_altmode_update_active(pdev, false);
-
/* Back to USB Operation */
- WARN_ON(typec_altmode_notify(adev,
- TYPEC_STATE_USB,
- NULL));
+ *adev_action = ADEV_NOTIFY_USB_AND_QUEUE_VDM;
+ return 0;
}
break;
+ case VDO_CMD_VENDOR(0) ... VDO_CMD_VENDOR(15):
+ break;
default:
+ /* Unrecognized SVDM */
+ response[0] = p[0] | VDO_CMDT(CMDT_RSP_NAK);
+ rlen = 1;
+ response[0] = (response[0] & ~VDO_SVDM_VERS_MASK) |
+ (VDO_SVDM_VERS(svdm_version));
break;
}
break;
case CMDT_RSP_NAK:
tcpm_ams_finish(port);
switch (cmd) {
+ case CMD_DISCOVER_IDENT:
+ /* Do dr_swap for ufp if the port supports drd */
+ if (port->typec_caps.data == TYPEC_PORT_DRD &&
+ port->data_role == TYPEC_DEVICE &&
+ !IS_ERR_OR_NULL(port->port_altmode[0])) {
+ port->vdm_sm_running = false;
+ port->upcoming_state = DR_SWAP_SEND;
+ tcpm_ams_start(port, DATA_ROLE_SWAP);
+ break;
+ }
+ fallthrough;
+ case CMD_DISCOVER_SVID:
+ case CMD_DISCOVER_MODES:
+ case VDO_CMD_VENDOR(0) ... VDO_CMD_VENDOR(15):
+ break;
case CMD_ENTER_MODE:
/* Back to USB Operation */
- if (adev)
- WARN_ON(typec_altmode_notify(adev,
- TYPEC_STATE_USB,
- NULL));
- break;
+ *adev_action = ADEV_NOTIFY_USB_AND_QUEUE_VDM;
+ return 0;
default:
+ /* Unrecognized SVDM */
+ response[0] = p[0] | VDO_CMDT(CMDT_RSP_NAK);
+ rlen = 1;
+ response[0] = (response[0] & ~VDO_SVDM_VERS_MASK) |
+ (VDO_SVDM_VERS(svdm_version));
break;
}
- port->vdm_sm_running = false;
break;
default:
- port->vdm_sm_running = false;
+ response[0] = p[0] | VDO_CMDT(CMDT_RSP_NAK);
+ rlen = 1;
+ response[0] = (response[0] & ~VDO_SVDM_VERS_MASK) |
+ (VDO_SVDM_VERS(svdm_version));
break;
}
/* Informing the alternate mode drivers about everything */
- if (adev)
- typec_altmode_vdm(adev, p[0], &p[1], cnt);
-
+ *adev_action = ADEV_QUEUE_VDM;
return rlen;
}
+
+static void tcpm_pd_handle_msg(struct tcpm_port *port,
+ enum pd_msg_request message,
+ enum tcpm_ams ams);
static void tcpm_handle_vdm_request(struct tcpm_port *port,
const __le32 *payload, int cnt)
{
- int rlen = 0;
+ enum adev_actions adev_action = ADEV_NONE;
+ struct typec_altmode *adev;
+ u32 p[PD_MAX_PAYLOAD];
u32 response[8] = { };
- u32 p0 = le32_to_cpu(payload[0]);
+ int i, rlen = 0;
+
+ for (i = 0; i < cnt; i++)
+ p[i] = le32_to_cpu(payload[i]);
+
+ adev = typec_match_altmode(port->port_altmode, ALTMODE_DISCOVERY_MAX,
+ PD_VDO_VID(p[0]), PD_VDO_OPOS(p[0]));
if (port->vdm_state == VDM_STATE_BUSY) {
/* If UFP responded busy retry after timeout */
- if (PD_VDO_CMDT(p0) == CMDT_RSP_BUSY) {
+ if (PD_VDO_CMDT(p[0]) == CMDT_RSP_BUSY) {
port->vdm_state = VDM_STATE_WAIT_RSP_BUSY;
- port->vdo_retry = (p0 & ~VDO_CMDT_MASK) |
+ port->vdo_retry = (p[0] & ~VDO_CMDT_MASK) |
CMDT_INIT;
mod_vdm_delayed_work(port, PD_T_VDM_BUSY);
return;
@@ -1700,29 +1842,111 @@
port->vdm_state = VDM_STATE_DONE;
}
- if (PD_VDO_SVDM(p0))
- rlen = tcpm_pd_svdm(port, payload, cnt, response);
-
- if (rlen > 0) {
- tcpm_queue_vdm(port, response[0], &response[1], rlen - 1);
- mod_vdm_delayed_work(port, 0);
+ if (PD_VDO_SVDM(p[0]) && (adev || tcpm_vdm_ams(port) || port->nr_snk_vdo)) {
+ /*
+ * Here a SVDM is received (INIT or RSP or unknown). Set the vdm_sm_running in
+ * advance because we are dropping the lock but may send VDMs soon.
+ * For the cases of INIT received:
+ * - If no response to send, it will be cleared later in this function.
+ * - If there are responses to send, it will be cleared in the state machine.
+ * For the cases of RSP received:
+ * - If no further INIT to send, it will be cleared later in this function.
+ * - Otherwise, it will be cleared in the state machine if timeout or it will go
+ * back here until no further INIT to send.
+ * For the cases of unknown type received:
+ * - We will send NAK and the flag will be cleared in the state machine.
+ */
+ port->vdm_sm_running = true;
+ rlen = tcpm_pd_svdm(port, adev, p, cnt, response, &adev_action);
+ } else {
+ if (port->negotiated_rev >= PD_REV30)
+ tcpm_pd_handle_msg(port, PD_MSG_CTRL_NOT_SUPP, NONE_AMS);
}
+
+ /*
+ * We are done with any state stored in the port struct now, except
+ * for any port struct changes done by the tcpm_queue_vdm() call
+ * below, which is a separate operation.
+ *
+ * So we can safely release the lock here; and we MUST release the
+ * lock here to avoid an AB BA lock inversion:
+ *
+ * If we keep the lock here then the lock ordering in this path is:
+ * 1. tcpm_pd_rx_handler take the tcpm port lock
+ * 2. One of the typec_altmode_* calls below takes the alt-mode's lock
+ *
+ * And we also have this ordering:
+ * 1. alt-mode driver takes the alt-mode's lock
+ * 2. alt-mode driver calls tcpm_altmode_enter which takes the
+ * tcpm port lock
+ *
+ * Dropping our lock here avoids this.
+ */
+ mutex_unlock(&port->lock);
+
+ if (adev) {
+ switch (adev_action) {
+ case ADEV_NONE:
+ break;
+ case ADEV_NOTIFY_USB_AND_QUEUE_VDM:
+ WARN_ON(typec_altmode_notify(adev, TYPEC_STATE_USB, NULL));
+ typec_altmode_vdm(adev, p[0], &p[1], cnt);
+ break;
+ case ADEV_QUEUE_VDM:
+ typec_altmode_vdm(adev, p[0], &p[1], cnt);
+ break;
+ case ADEV_QUEUE_VDM_SEND_EXIT_MODE_ON_FAIL:
+ if (typec_altmode_vdm(adev, p[0], &p[1], cnt)) {
+ int svdm_version = typec_get_negotiated_svdm_version(
+ port->typec_port);
+ if (svdm_version < 0)
+ break;
+
+ response[0] = VDO(adev->svid, 1, svdm_version,
+ CMD_EXIT_MODE);
+ response[0] |= VDO_OPOS(adev->mode);
+ rlen = 1;
+ }
+ break;
+ case ADEV_ATTENTION:
+ if (typec_altmode_attention(adev, p[1]))
+ tcpm_log(port, "typec_altmode_attention no port partner altmode");
+ break;
+ }
+ }
+
+ /*
+ * We must re-take the lock here to balance the unlock in
+ * tcpm_pd_rx_handler, note that no changes, other then the
+ * tcpm_queue_vdm call, are made while the lock is held again.
+ * All that is done after the call is unwinding the call stack until
+ * we return to tcpm_pd_rx_handler and do the unlock there.
+ */
+ mutex_lock(&port->lock);
+
+ if (rlen > 0)
+ tcpm_queue_vdm(port, response[0], &response[1], rlen - 1);
+ else
+ port->vdm_sm_running = false;
}
static void tcpm_send_vdm(struct tcpm_port *port, u32 vid, int cmd,
const u32 *data, int count)
{
+ int svdm_version = typec_get_negotiated_svdm_version(port->typec_port);
u32 header;
+
+ if (svdm_version < 0)
+ return;
if (WARN_ON(count > VDO_MAX_SIZE - 1))
count = VDO_MAX_SIZE - 1;
/* set VDM header with VID & CMD */
header = VDO(vid, ((vid & USB_SID_PD) == USB_SID_PD) ?
- 1 : (PD_VDO_CMD(cmd) <= CMD_ATTENTION), cmd);
+ 1 : (PD_VDO_CMD(cmd) <= CMD_ATTENTION),
+ svdm_version, cmd);
tcpm_queue_vdm(port, header, data, count);
-
- mod_vdm_delayed_work(port, 0);
}
static unsigned int vdm_ready_timeout(u32 vdm_hdr)
@@ -1769,16 +1993,23 @@
* if there's traffic or we're not in PDO ready state don't send
* a VDM.
*/
- if (port->state != SRC_READY && port->state != SNK_READY)
+ if (port->state != SRC_READY && port->state != SNK_READY) {
+ port->vdm_sm_running = false;
break;
+ }
/* TODO: AMS operation for Unstructured VDM */
if (PD_VDO_SVDM(vdo_hdr) && PD_VDO_CMDT(vdo_hdr) == CMDT_INIT) {
switch (PD_VDO_CMD(vdo_hdr)) {
case CMD_DISCOVER_IDENT:
res = tcpm_ams_start(port, DISCOVER_IDENTITY);
- if (res == 0)
+ if (res == 0) {
port->send_discover = false;
+ } else if (res == -EAGAIN) {
+ port->vdo_data[0] = 0;
+ mod_send_discover_delayed_work(port,
+ SEND_DISCOVER_RETRY_MS);
+ }
break;
case CMD_DISCOVER_SVID:
res = tcpm_ams_start(port, DISCOVER_SVIDS);
@@ -1804,7 +2035,7 @@
}
if (res < 0) {
- port->vdm_sm_running = false;
+ port->vdm_state = VDM_STATE_ERR_BUSY;
return;
}
}
@@ -1820,6 +2051,7 @@
port->vdo_data[0] = port->vdo_retry;
port->vdo_count = 1;
port->vdm_state = VDM_STATE_READY;
+ tcpm_ams_finish(port);
break;
case VDM_STATE_BUSY:
port->vdm_state = VDM_STATE_ERR_TMOUT;
@@ -1860,6 +2092,7 @@
unsigned long timeout;
port->vdm_retries = 0;
+ port->vdo_data[0] = 0;
port->vdm_state = VDM_STATE_BUSY;
timeout = vdm_ready_timeout(vdo_hdr);
mod_vdm_delayed_work(port, timeout);
@@ -1888,7 +2121,7 @@
port->vdm_state != VDM_STATE_BUSY &&
port->vdm_state != VDM_STATE_SEND_MESSAGE);
- if (port->vdm_state == VDM_STATE_ERR_TMOUT)
+ if (port->vdm_state < VDM_STATE_READY)
port->vdm_sm_running = false;
mutex_unlock(&port->lock);
@@ -2014,101 +2247,46 @@
return 0;
}
-static int tcpm_altmode_enter(struct typec_altmode *altmode)
+static int tcpm_altmode_enter(struct typec_altmode *altmode, u32 *vdo)
{
struct tcpm_port *port = typec_altmode_get_drvdata(altmode);
+ int svdm_version;
u32 header;
- int ret = 0;
- mutex_lock(&port->pd_handler_lock);
- if (tcpm_port_is_disconnected(port)) {
- ret = -ENODEV;
- goto unlock;
- }
+ svdm_version = typec_get_negotiated_svdm_version(port->typec_port);
+ if (svdm_version < 0)
+ return svdm_version;
- mutex_lock(&port->lock);
- header = VDO(altmode->svid, 1, CMD_ENTER_MODE);
+ header = VDO(altmode->svid, vdo ? 2 : 1, svdm_version, CMD_ENTER_MODE);
header |= VDO_OPOS(altmode->mode);
- tcpm_queue_vdm(port, header, NULL, 0);
- mod_vdm_delayed_work(port, 0);
- mutex_unlock(&port->lock);
-unlock:
- mutex_unlock(&port->pd_handler_lock);
- return ret;
+ tcpm_queue_vdm_unlocked(port, header, vdo, vdo ? 1 : 0);
+ return 0;
}
static int tcpm_altmode_exit(struct typec_altmode *altmode)
{
struct tcpm_port *port = typec_altmode_get_drvdata(altmode);
+ int svdm_version;
u32 header;
- int ret = 0;
- mutex_lock(&port->pd_handler_lock);
- if (tcpm_port_is_disconnected(port)) {
- ret = -ENODEV;
- goto unlock;
- }
+ svdm_version = typec_get_negotiated_svdm_version(port->typec_port);
+ if (svdm_version < 0)
+ return svdm_version;
- mutex_lock(&port->lock);
- header = VDO(altmode->svid, 1, CMD_EXIT_MODE);
+ header = VDO(altmode->svid, 1, svdm_version, CMD_EXIT_MODE);
header |= VDO_OPOS(altmode->mode);
- tcpm_queue_vdm(port, header, NULL, 0);
- mod_vdm_delayed_work(port, 0);
- mutex_unlock(&port->lock);
-
-unlock:
- mutex_unlock(&port->pd_handler_lock);
- return ret;
+ tcpm_queue_vdm_unlocked(port, header, NULL, 0);
+ return 0;
}
static int tcpm_altmode_vdm(struct typec_altmode *altmode,
u32 header, const u32 *data, int count)
{
struct tcpm_port *port = typec_altmode_get_drvdata(altmode);
- int ret = 0;
- mutex_lock(&port->pd_handler_lock);
- if (tcpm_port_is_disconnected(port)) {
- ret = -ENODEV;
- goto unlock;
- }
-
- mutex_lock(&port->lock);
- tcpm_queue_vdm(port, header, data, count - 1);
- mod_vdm_delayed_work(port, 0);
- mutex_unlock(&port->lock);
-
-unlock:
- mutex_unlock(&port->pd_handler_lock);
- return ret;
-}
-
-static int tcpm_altmode_notify(struct typec_altmode *altmode,
- unsigned long conf, void *data)
-{
- struct tcpm_port *port = typec_altmode_get_drvdata(altmode);
- struct typec_displayport_data *dp_data = data;
-
- if ((conf >= TYPEC_DP_STATE_A) && (conf <= TYPEC_DP_STATE_F)) {
- if (port->dp_configured) {
- port->dp_status = dp_data->status;
- tcpm_send_dp_notify(port);
- dev_info(port->dev, "dp_status %x\n", dp_data->status);
- } else {
- port->dp_pin_assignment = dp_data->conf;
- port->dp_configured = true;
- dev_info(port->dev, "DP pin assignment 0x%x\n",
- port->dp_pin_assignment);
- }
- } else if ((conf == TYPEC_STATE_USB) && (port->dp_configured)) {
- /* may receive ufp device response Exit Mode Command Ack */
- port->dp_status = 0;
- port->dp_pin_assignment = 0;
- port->dp_configured = false;
- tcpm_send_dp_notify(port);
- }
+ tcpm_queue_vdm_unlocked(port, header, data, count - 1);
return 0;
}
@@ -2117,7 +2295,6 @@
.enter = tcpm_altmode_enter,
.exit = tcpm_altmode_exit,
.vdm = tcpm_altmode_vdm,
- .notify = tcpm_altmode_notify,
};
/*
@@ -2142,20 +2319,108 @@
if (!type) {
tcpm_log(port, "Alert message received with no type");
+ tcpm_queue_message(port, PD_MSG_CTRL_NOT_SUPP);
return;
}
/* Just handling non-battery alerts for now */
if (!(type & USB_PD_ADO_TYPE_BATT_STATUS_CHANGE)) {
- switch (port->state) {
- case SRC_READY:
- case SNK_READY:
+ if (port->pwr_role == TYPEC_SOURCE) {
+ port->upcoming_state = GET_STATUS_SEND;
+ tcpm_ams_start(port, GETTING_SOURCE_SINK_STATUS);
+ } else {
+ /*
+ * Do not check SinkTxOk here in case the Source doesn't set its Rp to
+ * SinkTxOk in time.
+ */
+ port->ams = GETTING_SOURCE_SINK_STATUS;
tcpm_set_state(port, GET_STATUS_SEND, 0);
- break;
- default:
- tcpm_queue_message(port, PD_MSG_CTRL_WAIT);
- break;
}
+ } else {
+ tcpm_queue_message(port, PD_MSG_CTRL_NOT_SUPP);
+ }
+}
+
+static int tcpm_set_auto_vbus_discharge_threshold(struct tcpm_port *port,
+ enum typec_pwr_opmode mode, bool pps_active,
+ u32 requested_vbus_voltage)
+{
+ int ret;
+
+ if (!port->tcpc->set_auto_vbus_discharge_threshold)
+ return 0;
+
+ ret = port->tcpc->set_auto_vbus_discharge_threshold(port->tcpc, mode, pps_active,
+ requested_vbus_voltage);
+ tcpm_log_force(port,
+ "set_auto_vbus_discharge_threshold mode:%d pps_active:%c vbus:%u ret:%d",
+ mode, pps_active ? 'y' : 'n', requested_vbus_voltage, ret);
+
+ return ret;
+}
+
+static void tcpm_pd_handle_state(struct tcpm_port *port,
+ enum tcpm_state state,
+ enum tcpm_ams ams,
+ unsigned int delay_ms)
+{
+ switch (port->state) {
+ case SRC_READY:
+ case SNK_READY:
+ port->ams = ams;
+ tcpm_set_state(port, state, delay_ms);
+ break;
+ /* 8.3.3.4.1.1 and 6.8.1 power transitioning */
+ case SNK_TRANSITION_SINK:
+ case SNK_TRANSITION_SINK_VBUS:
+ case SRC_TRANSITION_SUPPLY:
+ tcpm_set_state(port, HARD_RESET_SEND, 0);
+ break;
+ default:
+ if (!tcpm_ams_interruptible(port)) {
+ tcpm_set_state(port, port->pwr_role == TYPEC_SOURCE ?
+ SRC_SOFT_RESET_WAIT_SNK_TX :
+ SNK_SOFT_RESET,
+ 0);
+ } else {
+ /* process the Message 6.8.1 */
+ port->upcoming_state = state;
+ port->next_ams = ams;
+ tcpm_set_state(port, ready_state(port), delay_ms);
+ }
+ break;
+ }
+}
+
+static void tcpm_pd_handle_msg(struct tcpm_port *port,
+ enum pd_msg_request message,
+ enum tcpm_ams ams)
+{
+ switch (port->state) {
+ case SRC_READY:
+ case SNK_READY:
+ port->ams = ams;
+ tcpm_queue_message(port, message);
+ break;
+ /* PD 3.0 Spec 8.3.3.4.1.1 and 6.8.1 */
+ case SNK_TRANSITION_SINK:
+ case SNK_TRANSITION_SINK_VBUS:
+ case SRC_TRANSITION_SUPPLY:
+ tcpm_set_state(port, HARD_RESET_SEND, 0);
+ break;
+ default:
+ if (!tcpm_ams_interruptible(port)) {
+ tcpm_set_state(port, port->pwr_role == TYPEC_SOURCE ?
+ SRC_SOFT_RESET_WAIT_SNK_TX :
+ SNK_SOFT_RESET,
+ 0);
+ } else {
+ port->next_ams = ams;
+ tcpm_set_state(port, ready_state(port), 0);
+ /* 6.8.1 process the Message */
+ tcpm_queue_message(port, message);
+ }
+ break;
}
}
@@ -2166,15 +2431,18 @@
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;
+ enum frs_typec_current partner_frs_current;
bool frs_enable;
int ret;
+ if (tcpm_vdm_ams(port) && type != PD_DATA_VENDOR_DEF) {
+ port->vdm_state = VDM_STATE_ERR_BUSY;
+ tcpm_ams_finish(port);
+ mod_vdm_delayed_work(port, 0);
+ }
+
switch (type) {
case PD_DATA_SOURCE_CAP:
- if (port->pwr_role != TYPEC_SINK)
- break;
-
for (i = 0; i < cnt; i++)
port->source_caps[i] = le32_to_cpu(msg->payload[i]);
@@ -2185,17 +2453,34 @@
tcpm_validate_caps(port, port->source_caps,
port->nr_source_caps);
+ trace_android_vh_typec_store_partner_src_caps(port, &port->nr_source_caps,
+ &port->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)
+ if (rev == PD_REV10) {
+ if (port->ams == GET_SOURCE_CAPABILITIES)
+ tcpm_ams_finish(port);
break;
+ }
if (rev < PD_MAX_REV)
- port->negotiated_rev = rev;
+ port->negotiated_rev = min_t(u16, rev, port->negotiated_rev);
+ if (port->pwr_role == TYPEC_SOURCE) {
+ if (port->ams == GET_SOURCE_CAPABILITIES)
+ tcpm_pd_handle_state(port, SRC_READY, NONE_AMS, 0);
+ /* Unexpected Source Capabilities */
+ else
+ tcpm_pd_handle_msg(port,
+ port->negotiated_rev < PD_REV30 ?
+ PD_MSG_CTRL_REJECT :
+ PD_MSG_CTRL_NOT_SUPP,
+ NONE_AMS);
+ } else if (port->state == SNK_WAIT_CAPABILITIES) {
/*
* This message may be received even if VBUS is not
* present. This is quite unexpected; see USB PD
@@ -2209,48 +2494,68 @@
* but be prepared to keep waiting for VBUS after it was
* handled.
*/
- tcpm_set_state(port, SNK_NEGOTIATE_CAPABILITIES, 0);
+ port->ams = POWER_NEGOTIATION;
+ port->in_ams = true;
+ tcpm_set_state(port, SNK_NEGOTIATE_CAPABILITIES, 0);
+ } else {
+ if (port->ams == GET_SOURCE_CAPABILITIES)
+ tcpm_ams_finish(port);
+ tcpm_pd_handle_state(port, SNK_NEGOTIATE_CAPABILITIES,
+ POWER_NEGOTIATION, 0);
+ }
break;
case PD_DATA_REQUEST:
- if (port->pwr_role != TYPEC_SOURCE ||
- cnt != 1) {
- 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);
+ tcpm_pd_handle_msg(port,
+ port->negotiated_rev < PD_REV30 ?
+ PD_MSG_CTRL_REJECT :
+ PD_MSG_CTRL_NOT_SUPP,
+ NONE_AMS);
break;
}
if (rev < PD_MAX_REV)
- port->negotiated_rev = rev;
+ port->negotiated_rev = min_t(u16, rev, port->negotiated_rev);
+
+ if (port->pwr_role != TYPEC_SOURCE || cnt != 1) {
+ tcpm_pd_handle_msg(port,
+ port->negotiated_rev < PD_REV30 ?
+ PD_MSG_CTRL_REJECT :
+ PD_MSG_CTRL_NOT_SUPP,
+ NONE_AMS);
+ break;
+ }
port->sink_request = le32_to_cpu(msg->payload[0]);
if (port->vdm_sm_running && port->explicit_contract) {
- tcpm_queue_message(port, PD_MSG_CTRL_WAIT);
+ tcpm_pd_handle_msg(port, PD_MSG_CTRL_WAIT, port->ams);
break;
}
- tcpm_set_state(port, SRC_NEGOTIATE_CAPABILITIES, 0);
+ if (port->state == SRC_SEND_CAPABILITIES)
+ tcpm_set_state(port, SRC_NEGOTIATE_CAPABILITIES, 0);
+ else
+ tcpm_pd_handle_state(port, SRC_NEGOTIATE_CAPABILITIES,
+ POWER_NEGOTIATION, 0);
break;
case PD_DATA_SINK_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) >>
+ partner_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);
+ frs_enable = partner_frs_current && (partner_frs_current <=
+ port->new_source_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');
+ partner_frs_current, port->new_source_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);
@@ -2258,27 +2563,45 @@
port->nr_sink_caps = cnt;
port->sink_cap_done = true;
- tcpm_set_state(port, SNK_READY, 0);
+ if (port->ams == GET_SINK_CAPABILITIES)
+ tcpm_set_state(port, ready_state(port), 0);
+ /* Unexpected Sink Capabilities */
+ else
+ tcpm_pd_handle_msg(port,
+ port->negotiated_rev < PD_REV30 ?
+ PD_MSG_CTRL_REJECT :
+ PD_MSG_CTRL_NOT_SUPP,
+ NONE_AMS);
break;
case PD_DATA_VENDOR_DEF:
tcpm_handle_vdm_request(port, msg->payload, cnt);
break;
case PD_DATA_BIST:
- if (port->state == SRC_READY || port->state == SNK_READY) {
- port->bist_request = le32_to_cpu(msg->payload[0]);
- tcpm_set_state(port, BIST_RX, 0);
- }
+ port->bist_request = le32_to_cpu(msg->payload[0]);
+ tcpm_pd_handle_state(port, BIST_RX, BIST, 0);
break;
case PD_DATA_ALERT:
- tcpm_handle_alert(port, msg->payload, cnt);
+ if (port->state != SRC_READY && port->state != SNK_READY)
+ tcpm_pd_handle_state(port, port->pwr_role == TYPEC_SOURCE ?
+ SRC_SOFT_RESET_WAIT_SNK_TX : SNK_SOFT_RESET,
+ NONE_AMS, 0);
+ else
+ tcpm_handle_alert(port, msg->payload, cnt);
break;
case PD_DATA_BATT_STATUS:
case PD_DATA_GET_COUNTRY_INFO:
/* Currently unsupported */
- tcpm_queue_message(port, PD_MSG_CTRL_NOT_SUPP);
+ tcpm_pd_handle_msg(port, port->negotiated_rev < PD_REV30 ?
+ PD_MSG_CTRL_REJECT :
+ PD_MSG_CTRL_NOT_SUPP,
+ NONE_AMS);
break;
default:
- tcpm_log(port, "Unhandled data message type %#x", type);
+ tcpm_pd_handle_msg(port, port->negotiated_rev < PD_REV30 ?
+ PD_MSG_CTRL_REJECT :
+ PD_MSG_CTRL_NOT_SUPP,
+ NONE_AMS);
+ tcpm_log(port, "Unrecognized data message type %#x", type);
break;
}
}
@@ -2298,31 +2621,25 @@
enum pd_ctrl_msg_type type = pd_header_type_le(msg->header);
enum tcpm_state next_state;
+ /*
+ * Stop VDM state machine if interrupted by other Messages while NOT_SUPP is allowed in
+ * VDM AMS if waiting for VDM responses and will be handled later.
+ */
+ if (tcpm_vdm_ams(port) && type != PD_CTRL_NOT_SUPP && type != PD_CTRL_GOOD_CRC) {
+ port->vdm_state = VDM_STATE_ERR_BUSY;
+ tcpm_ams_finish(port);
+ mod_vdm_delayed_work(port, 0);
+ }
+
switch (type) {
case PD_CTRL_GOOD_CRC:
case PD_CTRL_PING:
break;
case PD_CTRL_GET_SOURCE_CAP:
- switch (port->state) {
- case SRC_READY:
- case SNK_READY:
- tcpm_queue_message(port, PD_MSG_DATA_SOURCE_CAP);
- break;
- default:
- tcpm_queue_message(port, PD_MSG_CTRL_REJECT);
- break;
- }
+ tcpm_pd_handle_msg(port, PD_MSG_DATA_SOURCE_CAP, GET_SOURCE_CAPABILITIES);
break;
case PD_CTRL_GET_SINK_CAP:
- switch (port->state) {
- case SRC_READY:
- case SNK_READY:
- tcpm_queue_message(port, PD_MSG_DATA_SINK_CAP);
- break;
- default:
- tcpm_queue_message(port, PD_MSG_CTRL_REJECT);
- break;
- }
+ tcpm_pd_handle_msg(port, PD_MSG_DATA_SINK_CAP, GET_SINK_CAPABILITIES);
break;
case PD_CTRL_GOTO_MIN:
break;
@@ -2331,13 +2648,13 @@
case SNK_TRANSITION_SINK:
if (port->vbus_present) {
tcpm_set_current_limit(port,
- port->current_limit,
- port->supply_voltage);
+ port->req_current_limit,
+ port->req_supply_voltage);
port->explicit_contract = true;
- /* Set VDM running flag ASAP */
- if (port->data_role == TYPEC_HOST &&
- port->send_discover)
- port->vdm_sm_running = true;
+ tcpm_set_auto_vbus_discharge_threshold(port,
+ TYPEC_PWR_MODE_PD,
+ port->pps_data.active,
+ port->supply_voltage);
tcpm_set_state(port, SNK_READY, 0);
} else {
/*
@@ -2361,6 +2678,11 @@
tcpm_set_state(port, FR_SWAP_SNK_SRC_NEW_SINK_READY, 0);
break;
default:
+ tcpm_pd_handle_state(port,
+ port->pwr_role == TYPEC_SOURCE ?
+ SRC_SOFT_RESET_WAIT_SNK_TX :
+ SNK_SOFT_RESET,
+ NONE_AMS, 0);
break;
}
break;
@@ -2370,26 +2692,28 @@
switch (port->state) {
case SNK_NEGOTIATE_CAPABILITIES:
/* USB PD specification, Figure 8-43 */
- if (port->explicit_contract) {
+ if (port->explicit_contract)
next_state = SNK_READY;
- if (port->data_role == TYPEC_HOST &&
- port->send_discover)
- port->vdm_sm_running = true;
- } else {
+ else
next_state = SNK_WAIT_CAPABILITIES;
- }
+
+ /* Threshold was relaxed before sending Request. Restore it back. */
+ tcpm_set_auto_vbus_discharge_threshold(port, TYPEC_PWR_MODE_PD,
+ port->pps_data.active,
+ port->supply_voltage);
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_data.req_out_volt = port->supply_voltage;
+ port->pps_data.req_op_curr = port->current_limit;
port->pps_status = (type == PD_CTRL_WAIT ?
-EAGAIN : -EOPNOTSUPP);
- if (port->data_role == TYPEC_HOST &&
- port->send_discover)
- port->vdm_sm_running = true;
+ /* Threshold was relaxed before sending Request. Restore it back. */
+ tcpm_set_auto_vbus_discharge_threshold(port, TYPEC_PWR_MODE_PD,
+ port->pps_data.active,
+ port->supply_voltage);
tcpm_set_state(port, SNK_READY, 0);
break;
@@ -2415,7 +2739,22 @@
port->sink_cap_done = true;
tcpm_set_state(port, ready_state(port), 0);
break;
+ case SRC_READY:
+ case SNK_READY:
+ if (port->vdm_state > VDM_STATE_READY) {
+ port->vdm_state = VDM_STATE_DONE;
+ if (tcpm_vdm_ams(port))
+ tcpm_ams_finish(port);
+ mod_vdm_delayed_work(port, 0);
+ break;
+ }
+ fallthrough;
default:
+ tcpm_pd_handle_state(port,
+ port->pwr_role == TYPEC_SOURCE ?
+ SRC_SOFT_RESET_WAIT_SNK_TX :
+ SNK_SOFT_RESET,
+ NONE_AMS, 0);
break;
}
break;
@@ -2427,13 +2766,15 @@
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;
+ port->pps_data.min_volt = port->pps_data.req_min_volt;
+ port->pps_data.max_volt = port->pps_data.req_max_volt;
+ port->pps_data.max_curr = port->pps_data.req_max_curr;
+ port->req_supply_voltage = port->pps_data.req_out_volt;
+ port->req_current_limit = port->pps_data.req_op_curr;
+ power_supply_changed(port->psy);
tcpm_set_state(port, SNK_TRANSITION_SINK, 0);
break;
case SOFT_RESET_SEND:
- port->message_id = 0;
- port->rx_msgid = -1;
if (port->ams == SOFT_RESET_AMS)
tcpm_ams_finish(port);
if (port->pwr_role == TYPEC_SOURCE) {
@@ -2444,10 +2785,6 @@
}
break;
case DR_SWAP_SEND:
- if (port->data_role == TYPEC_DEVICE &&
- port->send_discover)
- port->vdm_sm_running = true;
-
tcpm_set_state(port, DR_SWAP_CHANGE_DR, 0);
break;
case PR_SWAP_SEND:
@@ -2460,69 +2797,62 @@
tcpm_set_state(port, FR_SWAP_SNK_SRC_TRANSITION_TO_OFF, 0);
break;
default:
+ tcpm_pd_handle_state(port,
+ port->pwr_role == TYPEC_SOURCE ?
+ SRC_SOFT_RESET_WAIT_SNK_TX :
+ SNK_SOFT_RESET,
+ NONE_AMS, 0);
break;
}
break;
case PD_CTRL_SOFT_RESET:
+ port->ams = SOFT_RESET_AMS;
tcpm_set_state(port, SOFT_RESET, 0);
break;
case PD_CTRL_DR_SWAP:
- if (port->port_type != TYPEC_PORT_DRP) {
- tcpm_queue_message(port, PD_MSG_CTRL_REJECT);
- break;
- }
/*
* XXX
* 6.3.9: If an alternate mode is active, a request to swap
* alternate modes shall trigger a port reset.
*/
- switch (port->state) {
- case SRC_READY:
- case SNK_READY:
- if (port->vdm_sm_running) {
+ if (port->typec_caps.data != TYPEC_PORT_DRD) {
+ tcpm_pd_handle_msg(port,
+ port->negotiated_rev < PD_REV30 ?
+ PD_MSG_CTRL_REJECT :
+ PD_MSG_CTRL_NOT_SUPP,
+ NONE_AMS);
+ } else {
+ if (port->send_discover) {
tcpm_queue_message(port, PD_MSG_CTRL_WAIT);
break;
}
- tcpm_set_state(port, DR_SWAP_ACCEPT, 0);
- break;
- default:
- tcpm_queue_message(port, PD_MSG_CTRL_WAIT);
- break;
+
+ tcpm_pd_handle_state(port, DR_SWAP_ACCEPT, DATA_ROLE_SWAP, 0);
}
break;
case PD_CTRL_PR_SWAP:
if (port->port_type != TYPEC_PORT_DRP) {
- tcpm_queue_message(port, PD_MSG_CTRL_REJECT);
- break;
- }
- switch (port->state) {
- case SRC_READY:
- case SNK_READY:
- if (port->vdm_sm_running) {
+ tcpm_pd_handle_msg(port,
+ port->negotiated_rev < PD_REV30 ?
+ PD_MSG_CTRL_REJECT :
+ PD_MSG_CTRL_NOT_SUPP,
+ NONE_AMS);
+ } else {
+ if (port->send_discover) {
tcpm_queue_message(port, PD_MSG_CTRL_WAIT);
break;
}
- tcpm_set_state(port, PR_SWAP_ACCEPT, 0);
- break;
- default:
- tcpm_queue_message(port, PD_MSG_CTRL_WAIT);
- break;
+
+ tcpm_pd_handle_state(port, PR_SWAP_ACCEPT, POWER_ROLE_SWAP, 0);
}
break;
case PD_CTRL_VCONN_SWAP:
- switch (port->state) {
- case SRC_READY:
- case SNK_READY:
- if (port->vdm_sm_running) {
- tcpm_queue_message(port, PD_MSG_CTRL_WAIT);
- break;
- }
- tcpm_set_state(port, VCONN_SWAP_ACCEPT, 0);
- break;
- default:
+ if (port->send_discover) {
tcpm_queue_message(port, PD_MSG_CTRL_WAIT);
break;
}
+
+ tcpm_pd_handle_state(port, VCONN_SWAP_ACCEPT, VCONN_SWAP, 0);
break;
case PD_CTRL_GET_SOURCE_CAP_EXT:
case PD_CTRL_GET_STATUS:
@@ -2530,10 +2860,19 @@
case PD_CTRL_GET_PPS_STATUS:
case PD_CTRL_GET_COUNTRY_CODES:
/* Currently not supported */
- tcpm_queue_message(port, PD_MSG_CTRL_NOT_SUPP);
+ tcpm_pd_handle_msg(port,
+ port->negotiated_rev < PD_REV30 ?
+ PD_MSG_CTRL_REJECT :
+ PD_MSG_CTRL_NOT_SUPP,
+ NONE_AMS);
break;
default:
- tcpm_log(port, "Unhandled ctrl message type %#x", type);
+ tcpm_pd_handle_msg(port,
+ port->negotiated_rev < PD_REV30 ?
+ PD_MSG_CTRL_REJECT :
+ PD_MSG_CTRL_NOT_SUPP,
+ NONE_AMS);
+ tcpm_log(port, "Unrecognized ctrl message type %#x", type);
break;
}
}
@@ -2544,34 +2883,37 @@
enum pd_ext_msg_type type = pd_header_type_le(msg->header);
unsigned int data_size = pd_ext_header_data_size_le(msg->ext_msg.header);
- if (!(msg->ext_msg.header & PD_EXT_HDR_CHUNKED)) {
+ /* stopping VDM state machine if interrupted by other Messages */
+ if (tcpm_vdm_ams(port)) {
+ port->vdm_state = VDM_STATE_ERR_BUSY;
+ tcpm_ams_finish(port);
+ mod_vdm_delayed_work(port, 0);
+ }
+
+ if (!(le16_to_cpu(msg->ext_msg.header) & PD_EXT_HDR_CHUNKED)) {
+ tcpm_pd_handle_msg(port, PD_MSG_CTRL_NOT_SUPP, NONE_AMS);
tcpm_log(port, "Unchunked extended messages unsupported");
return;
}
if (data_size > PD_EXT_MAX_CHUNK_DATA) {
+ tcpm_pd_handle_state(port, CHUNK_NOT_SUPP, NONE_AMS, PD_T_CHUNK_NOT_SUPP);
tcpm_log(port, "Chunk handling not yet supported");
return;
}
switch (type) {
case PD_EXT_STATUS:
- /*
- * If PPS related events raised then get PPS status to clear
- * (see USB PD 3.0 Spec, 6.5.2.4)
- */
- if (msg->ext_msg.data[USB_PD_EXT_SDB_EVENT_FLAGS] &
- USB_PD_EXT_SDB_PPS_EVENTS)
- tcpm_set_state(port, GET_PPS_STATUS_SEND, 0);
- else
- tcpm_set_state(port, ready_state(port), 0);
- break;
case PD_EXT_PPS_STATUS:
- /*
- * For now the PPS status message is used to clear events
- * and nothing more.
- */
- tcpm_set_state(port, ready_state(port), 0);
+ if (port->ams == GETTING_SOURCE_SINK_STATUS) {
+ tcpm_ams_finish(port);
+ tcpm_set_state(port, ready_state(port), 0);
+ } else {
+ /* unexpected Status or PPS_Status Message */
+ tcpm_pd_handle_state(port, port->pwr_role == TYPEC_SOURCE ?
+ SRC_SOFT_RESET_WAIT_SNK_TX : SNK_SOFT_RESET,
+ NONE_AMS, 0);
+ }
break;
case PD_EXT_SOURCE_CAP_EXT:
case PD_EXT_GET_BATT_CAP:
@@ -2585,10 +2927,11 @@
case PD_EXT_FW_UPDATE_RESPONSE:
case PD_EXT_COUNTRY_INFO:
case PD_EXT_COUNTRY_CODES:
- tcpm_queue_message(port, PD_MSG_CTRL_NOT_SUPP);
+ tcpm_pd_handle_msg(port, PD_MSG_CTRL_NOT_SUPP, NONE_AMS);
break;
default:
- tcpm_log(port, "Unhandled extended message type %#x", type);
+ tcpm_pd_handle_msg(port, PD_MSG_CTRL_NOT_SUPP, NONE_AMS);
+ tcpm_log(port, "Unrecognized extended message type %#x", type);
break;
}
}
@@ -2633,7 +2976,7 @@
"Data role mismatch, initiating error recovery");
tcpm_set_state(port, ERROR_RECOVERY, 0);
} else {
- if (msg->header & PD_HEADER_EXT_HDR)
+ if (le16_to_cpu(msg->header) & PD_HEADER_EXT_HDR)
tcpm_pd_ext_msg_request(port, msg);
else if (cnt)
tcpm_pd_data_request(port, msg);
@@ -2684,6 +3027,7 @@
static bool tcpm_send_queued_message(struct tcpm_port *port)
{
enum pd_msg_request queued_message;
+ int ret;
do {
queued_message = port->queued_message;
@@ -2700,10 +3044,27 @@
tcpm_pd_send_control(port, PD_CTRL_NOT_SUPP);
break;
case PD_MSG_DATA_SINK_CAP:
- tcpm_pd_send_sink_caps(port);
+ ret = tcpm_pd_send_sink_caps(port);
+ if (ret < 0) {
+ tcpm_log(port, "Unable to send snk caps, ret=%d", ret);
+ tcpm_set_state(port, SNK_SOFT_RESET, 0);
+ }
+ tcpm_ams_finish(port);
break;
case PD_MSG_DATA_SOURCE_CAP:
- tcpm_pd_send_source_caps(port);
+ ret = tcpm_pd_send_source_caps(port);
+ if (ret < 0) {
+ tcpm_log(port,
+ "Unable to send src caps, ret=%d",
+ ret);
+ tcpm_set_state(port, SOFT_RESET_SEND, 0);
+ } else if (port->pwr_role == TYPEC_SOURCE) {
+ tcpm_ams_finish(port);
+ tcpm_set_state(port, HARD_RESET_SEND,
+ PD_T_SENDER_RESPONSE);
+ } else {
+ tcpm_ams_finish(port);
+ }
break;
default:
break;
@@ -2791,6 +3152,7 @@
port->pps_data.supported = false;
port->usb_type = POWER_SUPPLY_USB_TYPE_PD;
+ power_supply_changed(port->psy);
/*
* Select the source PDO providing the most power which has a
@@ -2815,6 +3177,7 @@
port->pps_data.supported = true;
port->usb_type =
POWER_SUPPLY_USB_TYPE_PD_PPS;
+ power_supply_changed(port->psy);
}
continue;
default:
@@ -2884,7 +3247,8 @@
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;
- u32 pdo;
+ unsigned int max_op_mv;
+ u32 pdo, src, snk;
unsigned int src_pdo = 0, snk_pdo = 0;
/*
@@ -2934,16 +3298,18 @@
continue;
}
- if (max_src_mv <= max_snk_mv &&
- min_src_mv >= min_snk_mv) {
+ if (min_src_mv <= max_snk_mv &&
+ max_src_mv >= min_snk_mv) {
+ max_op_mv = min(max_src_mv, max_snk_mv);
+ src_mw = (max_op_mv * src_ma) / 1000;
/* Prefer higher voltages if available */
if ((src_mw == max_mw &&
- min_src_mv > max_mv) ||
+ max_op_mv > max_mv) ||
src_mw > max_mw) {
src_pdo = i;
snk_pdo = j;
max_mw = src_mw;
- max_mv = max_src_mv;
+ max_mv = max_op_mv;
}
}
}
@@ -2956,16 +3322,19 @@
}
if (src_pdo) {
- pdo = port->source_caps[src_pdo];
+ src = port->source_caps[src_pdo];
+ snk = port->snk_pdo[snk_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);
+ port->pps_data.req_min_volt = max(pdo_pps_apdo_min_voltage(src),
+ pdo_pps_apdo_min_voltage(snk));
+ port->pps_data.req_max_volt = min(pdo_pps_apdo_max_voltage(src),
+ pdo_pps_apdo_max_voltage(snk));
+ port->pps_data.req_max_curr = min_pps_apdo_current(src, snk);
+ port->pps_data.req_out_volt = min(port->pps_data.req_max_volt,
+ max(port->pps_data.req_min_volt,
+ port->pps_data.req_out_volt));
+ port->pps_data.req_op_curr = min(port->pps_data.req_max_curr,
+ port->pps_data.req_op_curr);
}
return src_pdo;
@@ -3045,8 +3414,8 @@
flags & RDO_CAP_MISMATCH ? " [mismatch]" : "");
}
- port->current_limit = ma;
- port->supply_voltage = mv;
+ port->req_current_limit = ma;
+ port->req_supply_voltage = mv;
return 0;
}
@@ -3060,6 +3429,12 @@
ret = tcpm_pd_build_request(port, &rdo);
if (ret < 0)
return ret;
+
+ /*
+ * Relax the threshold as voltage will be adjusted after Accept Message plus tSrcTransition.
+ * It is safer to modify the threshold here.
+ */
+ tcpm_set_auto_vbus_discharge_threshold(port, TYPEC_PWR_MODE_USB, false, 0);
memset(&msg, 0, sizeof(msg));
msg.header = PD_HEADER_LE(PD_DATA_REQUEST,
@@ -3092,10 +3467,10 @@
tcpm_log(port, "Invalid APDO selected!");
return -EINVAL;
}
- 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;
+ max_mv = port->pps_data.req_max_volt;
+ max_ma = port->pps_data.req_max_curr;
+ out_mv = port->pps_data.req_out_volt;
+ op_ma = port->pps_data.req_op_curr;
break;
default:
tcpm_log(port, "Invalid PDO selected!");
@@ -3142,8 +3517,8 @@
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;
+ port->pps_data.req_op_curr = op_ma;
+ port->pps_data.req_out_volt = out_mv;
return 0;
}
@@ -3157,6 +3532,9 @@
ret = tcpm_pd_build_pps_request(port, &rdo);
if (ret < 0)
return ret;
+
+ /* Relax the threshold as voltage will be adjusted right after Accept Message. */
+ tcpm_set_auto_vbus_discharge_threshold(port, TYPEC_PWR_MODE_USB, false, 0);
memset(&msg, 0, sizeof(msg));
msg.header = PD_HEADER_LE(PD_DATA_REQUEST,
@@ -3178,10 +3556,6 @@
tcpm_log(port, "vbus:=%d charge=%d", enable, port->vbus_charge);
- ret = tcpm_send_vbus_notify(port, enable);
- if (ret)
- return ret;
-
ret = port->tcpc->set_vbus(port->tcpc, enable, port->vbus_charge);
if (ret < 0)
return ret;
@@ -3199,17 +3573,13 @@
if (charge != port->vbus_charge) {
tcpm_log(port, "vbus=%d charge:=%d", port->vbus_source, charge);
-
- ret = tcpm_send_vbus_notify(port, port->vbus_source);
- if (ret < 0)
- return ret;
-
ret = port->tcpc->set_vbus(port->tcpc, port->vbus_source,
charge);
if (ret < 0)
return ret;
}
port->vbus_charge = charge;
+ power_supply_changed(port->psy);
return 0;
}
@@ -3228,10 +3598,6 @@
static int tcpm_init_vbus(struct tcpm_port *port)
{
int ret;
-
- ret = tcpm_send_vbus_notify(port, false);
- if (ret)
- return ret;
ret = port->tcpc->set_vbus(port->tcpc, false, false);
port->vbus_source = false;
@@ -3280,7 +3646,9 @@
if (ret < 0)
return ret;
- ret = tcpm_set_roles(port, true, TYPEC_SOURCE, TYPEC_HOST);
+ tcpm_enable_auto_vbus_discharge(port, true);
+
+ ret = tcpm_set_roles(port, true, TYPEC_SOURCE, tcpm_data_role_for_source(port));
if (ret < 0)
return ret;
@@ -3309,12 +3677,8 @@
port->partner = NULL;
port->attached = true;
+ port->debouncing = false;
port->send_discover = true;
-
- dev_info(port->dev, "CC connected in %s as DFP\n",
- polarity ? "CC2" : "CC1");
- tcpm_send_orientation_notify(port);
- tcpm_send_data_role_notify(port, port->attached, port->data_role);
return 0;
@@ -3350,26 +3714,26 @@
memset(modep, 0, sizeof(*modep));
}
+static void tcpm_set_partner_usb_comm_capable(struct tcpm_port *port, bool capable)
+{
+ tcpm_log(port, "Setting usb_comm capable %s", capable ? "true" : "false");
+
+ if (port->tcpc->set_partner_usb_comm_capable)
+ port->tcpc->set_partner_usb_comm_capable(port->tcpc, capable);
+}
+
static void tcpm_reset_port(struct tcpm_port *port)
{
+ tcpm_enable_auto_vbus_discharge(port, false);
port->in_ams = false;
port->ams = NONE_AMS;
port->vdm_sm_running = false;
tcpm_unregister_altmodes(port);
tcpm_typec_disconnect(port);
- if (port->attached) {
- port->attached = false;
- if (port->dp_configured) {
- port->dp_configured = false;
- port->dp_pin_assignment = 0;
- port->dp_status = 0;
- tcpm_send_dp_notify(port);
- }
- tcpm_send_data_role_notify(port, port->attached,
- port->data_role);
- }
+ port->attached = false;
port->pd_capable = false;
port->pps_data.supported = false;
+ tcpm_set_partner_usb_comm_capable(port, false);
/*
* First Rx ID should be 0; set this to a sentinel of -1 so that
@@ -3388,12 +3752,11 @@
port->try_src_count = 0;
port->try_snk_count = 0;
port->usb_type = POWER_SUPPLY_USB_TYPE_C;
+ power_supply_changed(port->psy);
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);
}
static void tcpm_detach(struct tcpm_port *port)
@@ -3403,6 +3766,11 @@
if (!port->attached)
return;
+
+ if (port->tcpc->set_bist_data) {
+ tcpm_log(port, "disable BIST MODE TESTDATA");
+ port->tcpc->set_bist_data(port->tcpc, false);
+ }
tcpm_reset_port(port);
}
@@ -3424,7 +3792,9 @@
if (ret < 0)
return ret;
- ret = tcpm_set_roles(port, true, TYPEC_SINK, TYPEC_DEVICE);
+ tcpm_enable_auto_vbus_discharge(port, true);
+
+ ret = tcpm_set_roles(port, true, TYPEC_SINK, tcpm_data_role_for_sink(port));
if (ret < 0)
return ret;
@@ -3433,12 +3803,8 @@
port->partner = NULL;
port->attached = true;
+ port->debouncing = false;
port->send_discover = true;
-
- dev_info(port->dev, "CC connected in %s as UFP\n",
- port->cc1 != TYPEC_CC_OPEN ? "CC1" : "CC2");
- tcpm_send_orientation_notify(port);
- tcpm_send_data_role_notify(port, port->attached, port->data_role);
return 0;
}
@@ -3455,7 +3821,8 @@
if (port->attached)
return 0;
- ret = tcpm_set_roles(port, true, TYPEC_SOURCE, TYPEC_HOST);
+ ret = tcpm_set_roles(port, true, TYPEC_SOURCE,
+ tcpm_data_role_for_source(port));
if (ret < 0)
return ret;
@@ -3464,10 +3831,7 @@
tcpm_typec_connect(port);
port->attached = true;
-
- dev_info(port->dev, "CC connected as Audio Accessory\n");
- tcpm_send_orientation_notify(port);
- tcpm_send_data_role_notify(port, port->attached, port->data_role);
+ port->debouncing = false;
return 0;
}
@@ -3504,14 +3868,15 @@
return SNK_UNATTACHED;
}
-static void tcpm_check_send_discover(struct tcpm_port *port)
+bool tcpm_is_toggling(struct tcpm_port *port)
{
- if (port->data_role == TYPEC_HOST && port->send_discover &&
- port->pd_capable) {
- tcpm_send_vdm(port, USB_SID_PD, CMD_DISCOVER_IDENT, NULL, 0);
- port->send_discover = false;
- }
+ if (port->port_type == TYPEC_PORT_DRP)
+ return port->state == SRC_UNATTACHED || port->state == SNK_UNATTACHED ||
+ port->state == TOGGLING;
+
+ return false;
}
+EXPORT_SYMBOL_GPL(tcpm_is_toggling);
static void tcpm_swap_complete(struct tcpm_port *port, int result)
{
@@ -3540,8 +3905,11 @@
{
int ret;
enum typec_pwr_opmode opmode;
- unsigned int msecs;
+ unsigned int msecs, timer_val_msecs;
enum tcpm_state upcoming_state;
+ const char *state_name;
+ u32 current_limit;
+ bool adjust;
port->enter_state = port->state;
switch (port->state) {
@@ -3552,6 +3920,15 @@
if (!port->non_pd_role_swap)
tcpm_swap_complete(port, -ENOTCONN);
tcpm_src_detach(port);
+ if (port->debouncing) {
+ port->debouncing = false;
+ if (port->tcpc->check_contaminant &&
+ port->tcpc->check_contaminant(port->tcpc)) {
+ /* Contaminant detection would handle toggling */
+ tcpm_set_state(port, TOGGLING, 0);
+ break;
+ }
+ }
if (tcpm_start_toggling(port, tcpm_rp_cc(port))) {
tcpm_set_state(port, TOGGLING, 0);
break;
@@ -3561,20 +3938,25 @@
tcpm_set_state(port, SNK_UNATTACHED, PD_T_DRP_SNK);
break;
case SRC_ATTACH_WAIT:
+ port->debouncing = true;
+ timer_val_msecs = PD_T_CC_DEBOUNCE;
+ trace_android_vh_typec_tcpm_get_timer(tcpm_states[SRC_ATTACH_WAIT],
+ CC_DEBOUNCE, &timer_val_msecs);
if (tcpm_port_is_debug(port))
tcpm_set_state(port, DEBUG_ACC_ATTACHED,
- PD_T_CC_DEBOUNCE);
+ timer_val_msecs);
else if (tcpm_port_is_audio(port))
tcpm_set_state(port, AUDIO_ACC_ATTACHED,
- PD_T_CC_DEBOUNCE);
- else if (tcpm_port_is_source(port))
+ timer_val_msecs);
+ else if (tcpm_port_is_source(port) && port->vbus_vsafe0v)
tcpm_set_state(port,
tcpm_try_snk(port) ? SNK_TRY
: SRC_ATTACHED,
- PD_T_CC_DEBOUNCE);
+ timer_val_msecs);
break;
case SNK_TRY:
+ port->debouncing = false;
port->try_snk_count++;
/*
* Requirements:
@@ -3597,15 +3979,13 @@
break;
case SNK_TRY_WAIT_DEBOUNCE:
tcpm_set_state(port, SNK_TRY_WAIT_DEBOUNCE_CHECK_VBUS,
- PD_T_PD_DEBOUNCE);
+ PD_T_TRY_CC_DEBOUNCE);
break;
case SNK_TRY_WAIT_DEBOUNCE_CHECK_VBUS:
- if (port->vbus_present && tcpm_port_is_sink(port)) {
+ if (port->vbus_present && tcpm_port_is_sink(port))
tcpm_set_state(port, SNK_ATTACHED, 0);
- } else {
- tcpm_set_state(port, SRC_TRYWAIT, 0);
+ else
port->max_wait = 0;
- }
break;
case SRC_TRYWAIT:
tcpm_set_cc(port, tcpm_rp_cc(port));
@@ -3624,7 +4004,10 @@
}
break;
case SRC_TRYWAIT_DEBOUNCE:
- tcpm_set_state(port, SRC_ATTACHED, PD_T_CC_DEBOUNCE);
+ timer_val_msecs = PD_T_CC_DEBOUNCE;
+ trace_android_vh_typec_tcpm_get_timer(tcpm_states[SRC_TRYWAIT_DEBOUNCE],
+ CC_DEBOUNCE, &timer_val_msecs);
+ tcpm_set_state(port, SRC_ATTACHED, timer_val_msecs);
break;
case SRC_TRYWAIT_UNATTACHED:
tcpm_set_state(port, SNK_UNATTACHED, 0);
@@ -3640,7 +4023,7 @@
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->negotiated_rev = (((port->typec_caps.pd_revision >> 8) & 0xff) - 1);
port->message_id = 0;
port->rx_msgid = -1;
port->explicit_contract = false;
@@ -3710,6 +4093,8 @@
}
} else {
tcpm_pd_send_control(port, PD_CTRL_ACCEPT);
+ tcpm_set_partner_usb_comm_capable(port,
+ !!(port->sink_request & RDO_USB_COMM));
tcpm_set_state(port, SRC_TRANSITION_SUPPLY,
PD_T_SRC_TRANSITION);
}
@@ -3733,6 +4118,11 @@
if (port->ams != NONE_AMS)
tcpm_ams_finish(port);
+ if (port->next_ams != NONE_AMS) {
+ port->ams = port->next_ams;
+ port->next_ams = NONE_AMS;
+ }
+
/*
* If previous AMS is interrupted, switch to the upcoming
* state.
@@ -3744,7 +4134,18 @@
break;
}
- tcpm_check_send_discover(port);
+ /*
+ * 6.4.4.3.1 Discover Identity
+ * "The Discover Identity Command Shall only be sent to SOP when there is an
+ * Explicit Contract."
+ * For now, this driver only supports SOP for DISCOVER_IDENTITY, thus using
+ * port->explicit_contract to decide whether to send the command.
+ */
+ if (port->explicit_contract)
+ mod_send_discover_delayed_work(port, 0);
+ else
+ port->send_discover = false;
+
/*
* 6.3.5
* Sending ping messages is not necessary if
@@ -3769,6 +4170,15 @@
tcpm_swap_complete(port, -ENOTCONN);
tcpm_pps_complete(port, -ENOTCONN);
tcpm_snk_detach(port);
+ if (port->debouncing) {
+ port->debouncing = false;
+ if (port->tcpc->check_contaminant &&
+ port->tcpc->check_contaminant(port->tcpc)) {
+ /* Contaminant detection would handle toggling */
+ tcpm_set_state(port, TOGGLING, 0);
+ break;
+ }
+ }
if (tcpm_start_toggling(port, TYPEC_CC_RD)) {
tcpm_set_state(port, TOGGLING, 0);
break;
@@ -3778,25 +4188,33 @@
tcpm_set_state(port, SRC_UNATTACHED, PD_T_DRP_SRC);
break;
case SNK_ATTACH_WAIT:
+ port->debouncing = true;
+ timer_val_msecs = PD_T_CC_DEBOUNCE;
+ trace_android_vh_typec_tcpm_get_timer(tcpm_states[SNK_ATTACH_WAIT],
+ CC_DEBOUNCE, &timer_val_msecs);
if ((port->cc1 == TYPEC_CC_OPEN &&
port->cc2 != TYPEC_CC_OPEN) ||
(port->cc1 != TYPEC_CC_OPEN &&
port->cc2 == TYPEC_CC_OPEN))
tcpm_set_state(port, SNK_DEBOUNCED,
- PD_T_CC_DEBOUNCE);
+ timer_val_msecs);
else if (tcpm_port_is_disconnected(port))
tcpm_set_state(port, SNK_UNATTACHED,
- PD_T_PD_DEBOUNCE);
+ timer_val_msecs);
break;
case SNK_DEBOUNCED:
- if (tcpm_port_is_disconnected(port))
+ if (tcpm_port_is_disconnected(port)) {
tcpm_set_state(port, SNK_UNATTACHED,
PD_T_PD_DEBOUNCE);
- else if (port->vbus_present)
+ } else if (port->vbus_present) {
tcpm_set_state(port,
tcpm_try_src(port) ? SRC_TRY
: SNK_ATTACHED,
0);
+ port->debouncing = false;
+ } else {
+ port->debouncing = false;
+ }
break;
case SRC_TRY:
port->try_src_count++;
@@ -3822,8 +4240,11 @@
tcpm_set_state(port, SRC_ATTACHED, PD_T_PD_DEBOUNCE);
break;
case SNK_TRYWAIT:
+ timer_val_msecs = PD_T_CC_DEBOUNCE;
+ trace_android_vh_typec_tcpm_get_timer(tcpm_states[SNK_TRYWAIT],
+ CC_DEBOUNCE, &timer_val_msecs);
tcpm_set_cc(port, TYPEC_CC_RD);
- tcpm_set_state(port, SNK_TRYWAIT_VBUS, PD_T_CC_DEBOUNCE);
+ tcpm_set_state(port, SNK_TRYWAIT_VBUS, timer_val_msecs);
break;
case SNK_TRYWAIT_VBUS:
/*
@@ -3853,7 +4274,7 @@
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->negotiated_rev = (((port->typec_caps.pd_revision >> 8) & 0xff) - 1);
port->message_id = 0;
port->rx_msgid = -1;
port->explicit_contract = false;
@@ -3863,13 +4284,22 @@
/* SRC -> SNK POWER/FAST_ROLE_SWAP finished */
tcpm_ams_finish(port);
- tcpm_set_state(port, SNK_DISCOVERY, 0);
+ timer_val_msecs = 0;
+ trace_android_vh_typec_tcpm_get_timer(tcpm_states[SNK_STARTUP],
+ SINK_DISCOVERY_BC12, &timer_val_msecs);
+ tcpm_set_state(port, SNK_DISCOVERY, timer_val_msecs);
break;
case SNK_DISCOVERY:
if (port->vbus_present) {
- tcpm_set_current_limit(port,
- tcpm_get_current_limit(port),
- 5000);
+ current_limit = tcpm_get_current_limit(port);
+ trace_android_vh_typec_tcpm_adj_current_limit(tcpm_states[SNK_DISCOVERY],
+ port->current_limit,
+ port->supply_voltage,
+ port->pd_capable,
+ ¤t_limit, &adjust);
+ if (port->slow_charger_loop && (current_limit > PD_P_SNK_STDBY_MW / 5))
+ current_limit = PD_P_SNK_STDBY_MW / 5;
+ tcpm_set_current_limit(port, current_limit, 5000);
tcpm_set_charge(port, true);
tcpm_set_state(port, SNK_WAIT_CAPABILITIES, 0);
break;
@@ -3884,8 +4314,10 @@
PD_T_DB_DETECT : PD_T_NO_RESPONSE);
break;
case SNK_DISCOVERY_DEBOUNCE:
- tcpm_set_state(port, SNK_DISCOVERY_DEBOUNCE_DONE,
- PD_T_CC_DEBOUNCE);
+ timer_val_msecs = PD_T_CC_DEBOUNCE;
+ trace_android_vh_typec_tcpm_get_timer(tcpm_states[SNK_DISCOVERY_DEBOUNCE],
+ CC_DEBOUNCE, &timer_val_msecs);
+ tcpm_set_state(port, SNK_DISCOVERY_DEBOUNCE_DONE, timer_val_msecs);
break;
case SNK_DISCOVERY_DEBOUNCE_DONE:
if (!tcpm_port_is_disconnected(port) &&
@@ -3898,11 +4330,16 @@
tcpm_set_state(port, unattached_state(port), 0);
break;
case SNK_WAIT_CAPABILITIES:
- ret = port->tcpc->set_pd_rx(port->tcpc, true);
- if (ret < 0) {
- tcpm_set_state(port, SNK_READY, 0);
- break;
+ if (port->prev_state != SOFT_RESET_SEND) {
+ ret = port->tcpc->set_pd_rx(port->tcpc, true);
+ if (ret < 0) {
+ tcpm_set_state(port, SNK_READY, 0);
+ break;
+ }
}
+ timer_val_msecs = PD_T_SINK_WAIT_CAP;
+ trace_android_vh_typec_tcpm_get_timer(tcpm_states[SNK_WAIT_CAPABILITIES],
+ SINK_WAIT_CAP, &timer_val_msecs);
/*
* If VBUS has never been low, and we time out waiting
* for source cap, try a soft reset first, in case we
@@ -3912,17 +4349,23 @@
if (port->vbus_never_low) {
port->vbus_never_low = false;
tcpm_set_state(port, SNK_SOFT_RESET,
- PD_T_SINK_WAIT_CAP);
+ timer_val_msecs);
} else {
tcpm_set_state(port, hard_reset_state(port),
- PD_T_SINK_WAIT_CAP);
+ timer_val_msecs);
}
break;
case SNK_NEGOTIATE_CAPABILITIES:
port->pd_capable = true;
+ tcpm_set_partner_usb_comm_capable(port,
+ !!(port->source_caps[0] & PDO_FIXED_USB_COMM));
port->hard_reset_count = 0;
ret = tcpm_pd_send_request(port);
if (ret < 0) {
+ /* Restore back to the original state */
+ tcpm_set_auto_vbus_discharge_threshold(port, TYPEC_PWR_MODE_PD,
+ port->pps_data.active,
+ port->supply_voltage);
/* Let the Source send capabilities again. */
tcpm_set_state(port, SNK_WAIT_CAPABILITIES, 0);
} else {
@@ -3933,6 +4376,10 @@
case SNK_NEGOTIATE_PPS_CAPABILITIES:
ret = tcpm_pd_send_pps_request(port);
if (ret < 0) {
+ /* Restore back to the original state */
+ tcpm_set_auto_vbus_discharge_threshold(port, TYPEC_PWR_MODE_PD,
+ port->pps_data.active,
+ port->supply_voltage);
port->pps_status = ret;
/*
* If this was called due to updates to sink
@@ -3949,6 +4396,23 @@
}
break;
case SNK_TRANSITION_SINK:
+ /* From the USB PD spec:
+ * "The Sink Shall transition to Sink Standby before a positive or
+ * negative voltage transition of VBUS. During Sink Standby
+ * the Sink Shall reduce its power draw to pSnkStdby."
+ *
+ * This is not applicable to PPS though as the port can continue
+ * to draw negotiated power without switching to standby.
+ */
+ if (port->supply_voltage != port->req_supply_voltage && !port->pps_data.active &&
+ port->current_limit * port->supply_voltage / 1000 > PD_P_SNK_STDBY_MW) {
+ u32 stdby_ma = PD_P_SNK_STDBY_MW * 1000 / port->supply_voltage;
+
+ tcpm_log(port, "Setting standby current %u mV @ %u mA",
+ port->supply_voltage, stdby_ma);
+ tcpm_set_current_limit(port, stdby_ma, port->supply_voltage);
+ }
+ fallthrough;
case SNK_TRANSITION_SINK_VBUS:
tcpm_set_state(port, hard_reset_state(port),
PD_T_PS_TRANSITION);
@@ -3962,6 +4426,19 @@
port->pwr_opmode = TYPEC_PWR_MODE_PD;
}
+ current_limit = tcpm_get_current_limit(port);
+ adjust = false;
+ trace_android_vh_typec_tcpm_adj_current_limit(tcpm_states[SNK_READY],
+ port->current_limit,
+ port->supply_voltage,
+ port->pd_capable,
+ ¤t_limit,
+ &adjust);
+ if (adjust)
+ tcpm_set_current_limit(port, current_limit, 5000);
+
+ if (!port->pd_capable && port->slow_charger_loop)
+ tcpm_set_current_limit(port, tcpm_get_current_limit(port), 5000);
tcpm_swap_complete(port, 0);
tcpm_typec_connect(port);
mod_enable_frs_delayed_work(port, 0);
@@ -3969,6 +4446,11 @@
if (port->ams != NONE_AMS)
tcpm_ams_finish(port);
+ if (port->next_ams != NONE_AMS) {
+ port->ams = port->next_ams;
+ port->next_ams = NONE_AMS;
+ }
+
/*
* If previous AMS is interrupted, switch to the upcoming
* state.
@@ -3980,11 +4462,19 @@
break;
}
- tcpm_check_send_discover(port);
+ /*
+ * 6.4.4.3.1 Discover Identity
+ * "The Discover Identity Command Shall only be sent to SOP when there is an
+ * Explicit Contract."
+ * For now, this driver only supports SOP for DISCOVER_IDENTITY, thus using
+ * port->explicit_contract.
+ */
+ if (port->explicit_contract)
+ mod_send_discover_delayed_work(port, 0);
+ else
+ port->send_discover = false;
+
power_supply_changed(port->psy);
- if (port->usb_type == POWER_SUPPLY_USB_TYPE_PD ||
- port->usb_type == POWER_SUPPLY_USB_TYPE_PD_PPS)
- tcpm_send_power_change_notify(port);
break;
/* Accessory states */
@@ -3999,7 +4489,10 @@
tcpm_set_state(port, ACC_UNATTACHED, 0);
break;
case AUDIO_ACC_DEBOUNCE:
- tcpm_set_state(port, ACC_UNATTACHED, PD_T_CC_DEBOUNCE);
+ timer_val_msecs = PD_T_CC_DEBOUNCE;
+ trace_android_vh_typec_tcpm_get_timer(tcpm_states[AUDIO_ACC_DEBOUNCE],
+ CC_DEBOUNCE, &timer_val_msecs);
+ tcpm_set_state(port, ACC_UNATTACHED, timer_val_msecs);
break;
/* Hard_Reset states */
@@ -4029,13 +4522,31 @@
tcpm_set_state(port, SNK_HARD_RESET_SINK_OFF, 0);
break;
case SRC_HARD_RESET_VBUS_OFF:
- tcpm_set_vconn(port, true);
+ /*
+ * 7.1.5 Response to Hard Resets
+ * Hard Reset Signaling indicates a communication failure has occurred and the
+ * Source Shall stop driving VCONN, Shall remove Rp from the VCONN pin and Shall
+ * drive VBUS to vSafe0V as shown in Figure 7-9.
+ */
+ tcpm_set_vconn(port, false);
tcpm_set_vbus(port, false);
tcpm_set_roles(port, port->self_powered, TYPEC_SOURCE,
- TYPEC_HOST);
- tcpm_set_state(port, SRC_HARD_RESET_VBUS_ON, PD_T_SRC_RECOVER);
+ tcpm_data_role_for_source(port));
+ /*
+ * If tcpc fails to notify vbus off, TCPM will wait for PD_T_SAFE_0V +
+ * PD_T_SRC_RECOVER before turning vbus back on.
+ * From Table 7-12 Sequence Description for a Source Initiated Hard Reset:
+ * 4. Policy Engine waits tPSHardReset after sending Hard Reset Signaling and then
+ * tells the Device Policy Manager to instruct the power supply to perform a
+ * Hard Reset. The transition to vSafe0V Shall occur within tSafe0V (t2).
+ * 5. After tSrcRecover the Source applies power to VBUS in an attempt to
+ * re-establish communication with the Sink and resume USB Default Operation.
+ * The transition to vSafe5V Shall occur within tSrcTurnOn(t4).
+ */
+ tcpm_set_state(port, SRC_HARD_RESET_VBUS_ON, PD_T_SAFE_0V + PD_T_SRC_RECOVER);
break;
case SRC_HARD_RESET_VBUS_ON:
+ tcpm_set_vconn(port, true);
tcpm_set_vbus(port, true);
if (port->ams == HARD_RESET)
tcpm_ams_finish(port);
@@ -4044,12 +4555,14 @@
tcpm_set_state(port, SRC_UNATTACHED, PD_T_PS_SOURCE_ON);
break;
case SNK_HARD_RESET_SINK_OFF:
+ /* Do not discharge/disconnect during hard reseet */
+ tcpm_set_auto_vbus_discharge_threshold(port, TYPEC_PWR_MODE_USB, false, 0);
memset(&port->pps_data, 0, sizeof(port->pps_data));
tcpm_set_vconn(port, false);
if (port->pd_capable)
tcpm_set_charge(port, false);
tcpm_set_roles(port, port->self_powered, TYPEC_SINK,
- TYPEC_DEVICE);
+ tcpm_data_role_for_sink(port));
/*
* VBUS may or may not toggle, depending on the adapter.
* If it doesn't toggle, transition to SNK_HARD_RESET_SINK_ON
@@ -4090,6 +4603,7 @@
if (port->ams == HARD_RESET)
tcpm_ams_finish(port);
tcpm_set_attached_state(port, true);
+ tcpm_set_auto_vbus_discharge_threshold(port, TYPEC_PWR_MODE_USB, false, VSAFE5V);
tcpm_set_state(port, SNK_STARTUP, 0);
break;
@@ -4098,6 +4612,7 @@
port->message_id = 0;
port->rx_msgid = -1;
tcpm_pd_send_control(port, PD_CTRL_ACCEPT);
+ tcpm_ams_finish(port);
if (port->pwr_role == TYPEC_SOURCE) {
port->upcoming_state = SRC_SEND_CAPABILITIES;
tcpm_ams_start(port, POWER_NEGOTIATION);
@@ -4115,6 +4630,7 @@
case SOFT_RESET_SEND:
port->message_id = 0;
port->rx_msgid = -1;
+ port->tcpc->set_pd_rx(port->tcpc, true);
if (tcpm_pd_send_control(port, PD_CTRL_SOFT_RESET))
tcpm_set_state_cond(port, hard_reset_state(port), 0);
else
@@ -4125,52 +4641,33 @@
/* DR_Swap states */
case DR_SWAP_SEND:
tcpm_pd_send_control(port, PD_CTRL_DR_SWAP);
+ if (port->data_role == TYPEC_DEVICE || port->negotiated_rev > PD_REV20)
+ port->send_discover = true;
tcpm_set_state_cond(port, DR_SWAP_SEND_TIMEOUT,
PD_T_SENDER_RESPONSE);
break;
case DR_SWAP_ACCEPT:
tcpm_pd_send_control(port, PD_CTRL_ACCEPT);
- /* Set VDM state machine running flag ASAP */
- if (port->data_role == TYPEC_DEVICE && port->send_discover)
- port->vdm_sm_running = true;
+ if (port->data_role == TYPEC_DEVICE || port->negotiated_rev > PD_REV20)
+ port->send_discover = true;
tcpm_set_state_cond(port, DR_SWAP_CHANGE_DR, 0);
break;
case DR_SWAP_SEND_TIMEOUT:
tcpm_swap_complete(port, -ETIMEDOUT);
+ port->send_discover = false;
+ tcpm_ams_finish(port);
tcpm_set_state(port, ready_state(port), 0);
break;
case DR_SWAP_CHANGE_DR:
- /*
- * After the DR_swap process is executed, for the
- * Rockchip platform, the tcpm framework needs to
- * send two notifications to the dwc3 driver, the
- * two notifications require a time interval.
- *
- * (a) For Type-C devices with DP function,
- * a notification will be sent here, another
- * notification can be sent after receiving
- * Attention cmd.
- * (b) But for Type-C devices without DP function,
- * the tcpm framework will only send a notification once.
- *
- * Based on the above reasons, it is necessary to start
- * the timer here, wait for 20 ms to start work and send
- * the notification again.
- */
-
if (port->data_role == TYPEC_HOST) {
tcpm_unregister_altmodes(port);
tcpm_set_roles(port, true, port->pwr_role,
TYPEC_DEVICE);
- tcpm_send_data_role_notify(port, false, TYPEC_HOST);
- mod_data_role_swap_work(port, SEND_NEW_MODE_NOTIFY_MS);
} else {
tcpm_set_roles(port, true, port->pwr_role,
TYPEC_HOST);
- port->send_discover = true;
- tcpm_send_data_role_notify(port, false, TYPEC_DEVICE);
- mod_data_role_swap_work(port, SEND_NEW_MODE_NOTIFY_MS);
}
+ tcpm_ams_finish(port);
tcpm_set_state(port, ready_state(port), 0);
break;
@@ -4185,7 +4682,10 @@
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);
+ timer_val_msecs = PD_T_PS_SOURCE_OFF;
+ state_name = tcpm_states[FR_SWAP_SNK_SRC_TRANSITION_TO_OFF];
+ trace_android_vh_typec_tcpm_get_timer(state_name, SOURCE_OFF, &timer_val_msecs);
+ tcpm_set_state(port, ERROR_RECOVERY, timer_val_msecs);
break;
case FR_SWAP_SNK_SRC_NEW_SINK_READY:
if (port->vbus_source)
@@ -4218,6 +4718,7 @@
tcpm_set_state(port, ready_state(port), 0);
break;
case PR_SWAP_START:
+ tcpm_apply_rc(port);
if (port->pwr_role == TYPEC_SOURCE)
tcpm_set_state(port, PR_SWAP_SRC_SNK_TRANSITION_OFF,
PD_T_SRC_TRANSITION);
@@ -4225,6 +4726,10 @@
tcpm_set_state(port, PR_SWAP_SNK_SRC_SINK_OFF, 0);
break;
case PR_SWAP_SRC_SNK_TRANSITION_OFF:
+ /*
+ * Prevent vbus discharge circuit from turning on during PR_SWAP
+ * as this is not a disconnect.
+ */
tcpm_set_vbus(port, false);
port->explicit_contract = false;
/* allow time for Vbus discharge, must be < tSrcSwapStdby */
@@ -4232,10 +4737,13 @@
PD_T_SRCSWAPSTDBY);
break;
case PR_SWAP_SRC_SNK_SOURCE_OFF:
+ timer_val_msecs = PD_T_CC_DEBOUNCE;
+ trace_android_vh_typec_tcpm_get_timer(tcpm_states[PR_SWAP_SRC_SNK_SOURCE_OFF],
+ CC_DEBOUNCE, &timer_val_msecs);
tcpm_set_cc(port, TYPEC_CC_RD);
/* allow CC debounce */
tcpm_set_state(port, PR_SWAP_SRC_SNK_SOURCE_OFF_CC_DEBOUNCED,
- PD_T_CC_DEBOUNCE);
+ timer_val_msecs);
break;
case PR_SWAP_SRC_SNK_SOURCE_OFF_CC_DEBOUNCED:
/*
@@ -4250,17 +4758,29 @@
tcpm_set_state(port, ERROR_RECOVERY, 0);
break;
}
- tcpm_set_state_cond(port, SNK_UNATTACHED, PD_T_PS_SOURCE_ON);
+ tcpm_set_state(port, ERROR_RECOVERY, PD_T_PS_SOURCE_ON_PRS);
break;
case PR_SWAP_SRC_SNK_SINK_ON:
+ tcpm_enable_auto_vbus_discharge(port, true);
+ /* Set the vbus disconnect threshold for implicit contract */
+ tcpm_set_auto_vbus_discharge_threshold(port, TYPEC_PWR_MODE_USB, false, VSAFE5V);
tcpm_set_state(port, SNK_STARTUP, 0);
break;
case PR_SWAP_SNK_SRC_SINK_OFF:
+ timer_val_msecs = PD_T_PS_SOURCE_OFF;
+ trace_android_vh_typec_tcpm_get_timer(tcpm_states[PR_SWAP_SNK_SRC_SINK_OFF],
+ SOURCE_OFF, &timer_val_msecs);
+ /*
+ * Prevent vbus discharge circuit from turning on during PR_SWAP
+ * as this is not a disconnect.
+ */
+ tcpm_set_auto_vbus_discharge_threshold(port, TYPEC_PWR_MODE_USB,
+ port->pps_data.active, 0);
tcpm_set_charge(port, false);
- tcpm_set_state(port, hard_reset_state(port),
- PD_T_PS_SOURCE_OFF);
+ tcpm_set_state(port, hard_reset_state(port), timer_val_msecs);
break;
case PR_SWAP_SNK_SRC_SOURCE_ON:
+ tcpm_enable_auto_vbus_discharge(port, true);
tcpm_set_cc(port, tcpm_rp_cc(port));
tcpm_set_vbus(port, true);
/*
@@ -4286,6 +4806,7 @@
case VCONN_SWAP_ACCEPT:
tcpm_pd_send_control(port, PD_CTRL_ACCEPT);
+ tcpm_ams_finish(port);
tcpm_set_state(port, VCONN_SWAP_START, 0);
break;
case VCONN_SWAP_SEND:
@@ -4295,8 +4816,6 @@
break;
case VCONN_SWAP_SEND_TIMEOUT:
tcpm_swap_complete(port, -ETIMEDOUT);
- if (port->data_role == TYPEC_HOST && port->send_discover)
- port->vdm_sm_running = true;
tcpm_set_state(port, ready_state(port), 0);
break;
case VCONN_SWAP_START:
@@ -4312,14 +4831,10 @@
case VCONN_SWAP_TURN_ON_VCONN:
tcpm_set_vconn(port, true);
tcpm_pd_send_control(port, PD_CTRL_PS_RDY);
- if (port->data_role == TYPEC_HOST && port->send_discover)
- port->vdm_sm_running = true;
tcpm_set_state(port, ready_state(port), 0);
break;
case VCONN_SWAP_TURN_OFF_VCONN:
tcpm_set_vconn(port, false);
- if (port->data_role == TYPEC_HOST && port->send_discover)
- port->vdm_sm_running = true;
tcpm_set_state(port, ready_state(port), 0);
break;
@@ -4327,8 +4842,6 @@
case PR_SWAP_CANCEL:
case VCONN_SWAP_CANCEL:
tcpm_swap_complete(port, port->swap_status);
- if (port->data_role == TYPEC_HOST && port->send_discover)
- port->vdm_sm_running = true;
if (port->pwr_role == TYPEC_SOURCE)
tcpm_set_state(port, SRC_READY, 0);
else
@@ -4345,12 +4858,18 @@
switch (BDO_MODE_MASK(port->bist_request)) {
case BDO_MODE_CARRIER2:
tcpm_pd_transmit(port, TCPC_TX_BIST_MODE_2, NULL);
+ tcpm_set_state(port, unattached_state(port),
+ PD_T_BIST_CONT_MODE);
+ break;
+ case BDO_MODE_TESTDATA:
+ if (port->tcpc->set_bist_data) {
+ tcpm_log(port, "Enable BIST MODE TESTDATA");
+ port->tcpc->set_bist_data(port->tcpc, true);
+ }
break;
default:
break;
}
- /* Always switch to unattached state */
- tcpm_set_state(port, unattached_state(port), 0);
break;
case GET_STATUS_SEND:
tcpm_pd_send_control(port, PD_CTRL_GET_STATUS);
@@ -4388,9 +4907,12 @@
PD_T_ERROR_RECOVERY);
break;
case PORT_RESET_WAIT_OFF:
+ timer_val_msecs = PD_T_PS_SOURCE_OFF;
+ trace_android_vh_typec_tcpm_get_timer(tcpm_states[PORT_RESET_WAIT_OFF],
+ SOURCE_OFF, &timer_val_msecs);
tcpm_set_state(port,
tcpm_default_state(port),
- port->vbus_present ? PD_T_PS_SOURCE_OFF : 0);
+ port->vbus_present ? timer_val_msecs : 0);
break;
/* AMS intermediate state */
@@ -4404,6 +4926,12 @@
upcoming_state = port->upcoming_state;
port->upcoming_state = INVALID_STATE;
tcpm_set_state(port, upcoming_state, 0);
+ break;
+
+ /* Chunk state */
+ case CHUNK_NOT_SUPP:
+ tcpm_pd_send_control(port, PD_CTRL_NOT_SUPP);
+ tcpm_set_state(port, port->pwr_role == TYPEC_SOURCE ? SRC_READY : SNK_READY, 0);
break;
default:
WARN(1, "Unexpected port state %d\n", port->state);
@@ -4488,11 +5016,16 @@
tcpm_set_state(port, SRC_ATTACH_WAIT, 0);
break;
case SRC_ATTACHED:
+ case SRC_STARTUP:
case SRC_SEND_CAPABILITIES:
case SRC_READY:
if (tcpm_port_is_disconnected(port) ||
- !tcpm_port_is_source(port))
- tcpm_set_state(port, SRC_UNATTACHED, 0);
+ !tcpm_port_is_source(port)) {
+ if (port->port_type == TYPEC_PORT_SRC)
+ tcpm_set_state(port, SRC_UNATTACHED, tcpm_wait_for_discharge(port));
+ else
+ tcpm_set_state(port, SNK_UNATTACHED, tcpm_wait_for_discharge(port));
+ }
break;
case SNK_UNATTACHED:
if (tcpm_port_is_sink(port))
@@ -4522,7 +5055,23 @@
tcpm_set_state(port, SNK_DEBOUNCED, 0);
break;
case SNK_READY:
- if (tcpm_port_is_disconnected(port))
+ /*
+ * EXIT condition is based primarily on vbus disconnect and CC is secondary.
+ * "A port that has entered into USB PD communications with the Source and
+ * has seen the CC voltage exceed vRd-USB may monitor the CC pin to detect
+ * cable disconnect in addition to monitoring VBUS.
+ *
+ * A port that is monitoring the CC voltage for disconnect (but is not in
+ * the process of a USB PD PR_Swap or USB PD FR_Swap) shall transition to
+ * Unattached.SNK within tSinkDisconnect after the CC voltage remains below
+ * vRd-USB for tPDDebounce."
+ *
+ * When set_auto_vbus_discharge_threshold is enabled, CC pins go
+ * away before vbus decays to disconnect threshold. Allow
+ * disconnect to be driven by vbus disconnect when auto vbus
+ * discharge is enabled.
+ */
+ if (!port->auto_vbus_discharge_enabled && tcpm_port_is_disconnected(port))
tcpm_set_state(port, unattached_state(port), 0);
else if (!port->pd_capable &&
(cc1 != old_cc1 || cc2 != old_cc2))
@@ -4587,6 +5136,12 @@
if (!tcpm_port_is_sink(port))
tcpm_set_state(port, SNK_TRYWAIT_DEBOUNCE, 0);
break;
+ case SNK_TRY_WAIT_DEBOUNCE_CHECK_VBUS:
+ if (!tcpm_port_is_sink(port))
+ tcpm_set_state(port, SRC_TRYWAIT, PD_T_TRY_CC_DEBOUNCE);
+ else
+ tcpm_set_state(port, SNK_TRY_WAIT_DEBOUNCE_CHECK_VBUS, 0);
+ break;
case SNK_TRYWAIT:
/* Do nothing, waiting for tCCDebounce */
break;
@@ -4615,9 +5170,13 @@
* Ignore CC changes here.
*/
break;
-
default:
- if (tcpm_port_is_disconnected(port))
+ /*
+ * While acting as sink and auto vbus discharge is enabled, Allow disconnect
+ * to be driven by vbus disconnect.
+ */
+ if (tcpm_port_is_disconnected(port) && !(port->pwr_role == TYPEC_SINK &&
+ port->auto_vbus_discharge_enabled))
tcpm_set_state(port, unattached_state(port), 0);
break;
}
@@ -4627,12 +5186,15 @@
{
tcpm_log_force(port, "VBUS on");
port->vbus_present = true;
+ /*
+ * When vbus_present is true i.e. Voltage at VBUS is greater than VSAFE5V implicitly
+ * states that vbus is not at VSAFE0V, hence clear the vbus_vsafe0v flag here.
+ */
+ port->vbus_vsafe0v = false;
+
switch (port->state) {
case SNK_TRANSITION_SINK_VBUS:
port->explicit_contract = true;
- /* Set the VDM flag ASAP */
- if (port->data_role == TYPEC_HOST && port->send_discover)
- port->vdm_sm_running = true;
tcpm_set_state(port, SNK_READY, 0);
break;
case SNK_DISCOVERY:
@@ -4676,11 +5238,24 @@
case SNK_TRYWAIT_DEBOUNCE:
/* Do nothing, waiting for Rp */
break;
+ case SNK_TRY_WAIT_DEBOUNCE_CHECK_VBUS:
+ if (port->vbus_present && tcpm_port_is_sink(port))
+ tcpm_set_state(port, SNK_ATTACHED, 0);
+ break;
case SRC_TRY_WAIT:
case SRC_TRY_DEBOUNCE:
/* Do nothing, waiting for sink detection */
break;
+ case FR_SWAP_SEND:
+ case FR_SWAP_SEND_TIMEOUT:
+ case FR_SWAP_SNK_SRC_TRANSITION_TO_OFF:
+ case FR_SWAP_SNK_SRC_SOURCE_VBUS_APPLIED:
+ if (port->tcpc->frs_sourcing_vbus)
+ port->tcpc->frs_sourcing_vbus(port->tcpc);
+ break;
case FR_SWAP_SNK_SRC_NEW_SINK_READY:
+ if (port->tcpc->frs_sourcing_vbus)
+ port->tcpc->frs_sourcing_vbus(port->tcpc);
tcpm_set_state(port, FR_SWAP_SNK_SRC_SOURCE_VBUS_APPLIED, 0);
break;
@@ -4706,12 +5281,8 @@
case SNK_HARD_RESET_SINK_OFF:
tcpm_set_state(port, SNK_HARD_RESET_WAIT_VBUS, 0);
break;
- case SRC_HARD_RESET_VBUS_OFF:
- tcpm_set_state(port, SRC_HARD_RESET_VBUS_ON, 0);
- break;
case HARD_RESET_SEND:
break;
-
case SNK_TRY:
/* Do nothing, waiting for timeout */
break;
@@ -4729,6 +5300,7 @@
break;
case SNK_ATTACH_WAIT:
case SNK_DEBOUNCED:
+ port->debouncing = false;
/* Do nothing, as TCPM is still waiting for vbus to reaach VSAFE5V to connect */
break;
@@ -4743,6 +5315,14 @@
/* Do nothing, expected */
break;
+ case PR_SWAP_SNK_SRC_SOURCE_ON:
+ /*
+ * Do nothing when vbus off notification is received.
+ * TCPM can wait for PD_T_NEWSRC in PR_SWAP_SNK_SRC_SOURCE_ON
+ * for the vbus source to ramp up.
+ */
+ break;
+
case PORT_RESET_WAIT_OFF:
tcpm_set_state(port, tcpm_default_state(port), 0);
break;
@@ -4750,6 +5330,25 @@
case SRC_TRY_WAIT:
case SRC_TRY_DEBOUNCE:
/* Do nothing, waiting for sink detection */
+ break;
+
+ case SRC_STARTUP:
+ case SRC_SEND_CAPABILITIES:
+ case SRC_SEND_CAPABILITIES_TIMEOUT:
+ case SRC_NEGOTIATE_CAPABILITIES:
+ case SRC_TRANSITION_SUPPLY:
+ case SRC_READY:
+ case SRC_WAIT_NEW_CAPABILITIES:
+ /*
+ * Force to unattached state to re-initiate connection.
+ * DRP port should move to Unattached.SNK instead of Unattached.SRC if
+ * sink removed. Although sink removal here is due to source's vbus collapse,
+ * treat it the same way for consistency.
+ */
+ if (port->port_type == TYPEC_PORT_SRC)
+ tcpm_set_state(port, SRC_UNATTACHED, tcpm_wait_for_discharge(port));
+ else
+ tcpm_set_state(port, SNK_UNATTACHED, tcpm_wait_for_discharge(port));
break;
case PORT_RESET:
@@ -4768,8 +5367,58 @@
break;
default:
- if (port->pwr_role == TYPEC_SINK &&
- port->attached)
+ if (port->pwr_role == TYPEC_SINK && port->attached)
+ tcpm_set_state(port, SNK_UNATTACHED, tcpm_wait_for_discharge(port));
+ break;
+ }
+}
+
+static void _tcpm_pd_vbus_vsafe0v(struct tcpm_port *port)
+{
+ unsigned int timer_val_msecs;
+
+ tcpm_log_force(port, "VBUS VSAFE0V");
+ port->vbus_vsafe0v = true;
+ switch (port->state) {
+ case SRC_HARD_RESET_VBUS_OFF:
+ /*
+ * After establishing the vSafe0V voltage condition on VBUS, the Source Shall wait
+ * tSrcRecover before re-applying VCONN and restoring VBUS to vSafe5V.
+ */
+ tcpm_set_state(port, SRC_HARD_RESET_VBUS_ON, PD_T_SRC_RECOVER);
+ break;
+ case SRC_ATTACH_WAIT:
+ timer_val_msecs = PD_T_CC_DEBOUNCE;
+ trace_android_vh_typec_tcpm_get_timer(tcpm_states[SRC_ATTACH_WAIT],
+ CC_DEBOUNCE, &timer_val_msecs);
+ if (tcpm_port_is_source(port))
+ tcpm_set_state(port, tcpm_try_snk(port) ? SNK_TRY : SRC_ATTACHED,
+ timer_val_msecs);
+ break;
+ case SRC_STARTUP:
+ case SRC_SEND_CAPABILITIES:
+ case SRC_SEND_CAPABILITIES_TIMEOUT:
+ case SRC_NEGOTIATE_CAPABILITIES:
+ case SRC_TRANSITION_SUPPLY:
+ case SRC_READY:
+ case SRC_WAIT_NEW_CAPABILITIES:
+ if (port->auto_vbus_discharge_enabled) {
+ if (port->port_type == TYPEC_PORT_SRC)
+ tcpm_set_state(port, SRC_UNATTACHED, 0);
+ else
+ tcpm_set_state(port, SNK_UNATTACHED, 0);
+ }
+ break;
+ case PR_SWAP_SNK_SRC_SINK_OFF:
+ case PR_SWAP_SNK_SRC_SOURCE_ON:
+ /* Do nothing, vsafe0v is expected during transition */
+ break;
+ case SNK_ATTACH_WAIT:
+ case SNK_DEBOUNCED:
+ /*Do nothing, still waiting for VSAFE5V for connect */
+ break;
+ default:
+ if (port->pwr_role == TYPEC_SINK && port->auto_vbus_discharge_enabled)
tcpm_set_state(port, SNK_UNATTACHED, 0);
break;
}
@@ -4778,9 +5427,13 @@
static void _tcpm_pd_hard_reset(struct tcpm_port *port)
{
tcpm_log_force(port, "Received hard reset");
+ if (port->bist_request == BDO_MODE_TESTDATA && port->tcpc->set_bist_data)
+ port->tcpc->set_bist_data(port->tcpc, false);
if (port->ams != NONE_AMS)
port->ams = NONE_AMS;
+ if (port->hard_reset_count < PD_N_HARD_RESET_COUNT)
+ port->ams = HARD_RESET;
/*
* If we keep receiving hard reset requests, executing the hard reset
* must have failed. Revert to error recovery if that happens.
@@ -4797,7 +5450,9 @@
event_work);
u32 events;
+#ifdef CONFIG_NO_GKI
mutex_lock(&port->pd_handler_lock);
+#endif
mutex_lock(&port->lock);
spin_lock(&port->pd_event_lock);
@@ -4811,10 +5466,19 @@
bool vbus;
vbus = port->tcpc->get_vbus(port->tcpc);
- if (vbus)
+ if (vbus) {
_tcpm_pd_vbus_on(port);
- else
+ } else {
_tcpm_pd_vbus_off(port);
+ /*
+ * When TCPC does not support detecting vsafe0v voltage level,
+ * treat vbus absent as vsafe0v. Else invoke is_vbus_vsafe0v
+ * to see if vbus has discharge to VSAFE0V.
+ */
+ if (!port->tcpc->is_vbus_vsafe0v ||
+ port->tcpc->is_vbus_vsafe0v(port->tcpc))
+ _tcpm_pd_vbus_vsafe0v(port);
+ }
}
if (events & TCPM_CC_EVENT) {
enum typec_cc_status cc1, cc2;
@@ -4851,7 +5515,9 @@
}
spin_unlock(&port->pd_event_lock);
mutex_unlock(&port->lock);
+#ifdef CONFIG_NO_GKI
mutex_unlock(&port->pd_handler_lock);
+#endif
}
void tcpm_cc_change(struct tcpm_port *port)
@@ -4931,27 +5597,41 @@
mutex_unlock(&port->lock);
}
-static void tcpm_data_role_swap_work(struct kthread_work *work)
+static void tcpm_send_discover_work(struct kthread_work *work)
{
- struct tcpm_port *port =
- container_of(work, struct tcpm_port, data_role_swap);
+ struct tcpm_port *port = container_of(work, struct tcpm_port, send_discover_work);
- if (tcpm_port_is_disconnected(port))
- return;
+ mutex_lock(&port->lock);
+ /* No need to send DISCOVER_IDENTITY anymore */
+ if (!port->send_discover)
+ goto unlock;
- tcpm_send_data_role_notify(port, true, port->data_role);
+ if (port->data_role == TYPEC_DEVICE && port->negotiated_rev < PD_REV30) {
+ port->send_discover = false;
+ goto unlock;
+ }
+
+ /* Retry if the port is not idle */
+ if ((port->state != SRC_READY && port->state != SNK_READY) || port->vdm_sm_running) {
+ mod_send_discover_delayed_work(port, SEND_DISCOVER_RETRY_MS);
+ goto unlock;
+ }
+
+ tcpm_send_vdm(port, USB_SID_PD, CMD_DISCOVER_IDENT, NULL, 0);
+
+unlock:
+ mutex_unlock(&port->lock);
}
-static int tcpm_dr_set(const struct typec_capability *cap,
- enum typec_data_role data)
+static int tcpm_dr_set(struct typec_port *p, enum typec_data_role data)
{
- struct tcpm_port *port = typec_cap_to_tcpm(cap);
+ struct tcpm_port *port = typec_get_drvdata(p);
int ret;
mutex_lock(&port->swap_lock);
mutex_lock(&port->lock);
- if (port->port_type != TYPEC_PORT_DRP) {
+ if (port->typec_caps.data != TYPEC_PORT_DRD) {
ret = -EINVAL;
goto port_unlock;
}
@@ -5015,10 +5695,9 @@
return ret;
}
-static int tcpm_pr_set(const struct typec_capability *cap,
- enum typec_role role)
+static int tcpm_pr_set(struct typec_port *p, enum typec_role role)
{
- struct tcpm_port *port = typec_cap_to_tcpm(cap);
+ struct tcpm_port *port = typec_get_drvdata(p);
int ret;
mutex_lock(&port->swap_lock);
@@ -5065,10 +5744,9 @@
return ret;
}
-static int tcpm_vconn_set(const struct typec_capability *cap,
- enum typec_role role)
+static int tcpm_vconn_set(struct typec_port *p, enum typec_role role)
{
- struct tcpm_port *port = typec_cap_to_tcpm(cap);
+ struct tcpm_port *port = typec_get_drvdata(p);
int ret;
mutex_lock(&port->swap_lock);
@@ -5111,9 +5789,9 @@
return ret;
}
-static int tcpm_try_role(const struct typec_capability *cap, int role)
+static int tcpm_try_role(struct typec_port *p, int role)
{
- struct tcpm_port *port = typec_cap_to_tcpm(cap);
+ struct tcpm_port *port = typec_get_drvdata(p);
struct tcpc_dev *tcpc = port->tcpc;
int ret = 0;
@@ -5129,7 +5807,7 @@
return ret;
}
-static int tcpm_pps_set_op_curr(struct tcpm_port *port, u16 op_curr)
+static int tcpm_pps_set_op_curr(struct tcpm_port *port, u16 req_op_curr)
{
unsigned int target_mw;
int ret;
@@ -5147,12 +5825,12 @@
goto port_unlock;
}
- if (op_curr > port->pps_data.max_curr) {
+ if (req_op_curr > port->pps_data.max_curr) {
ret = -EINVAL;
goto port_unlock;
}
- target_mw = (op_curr * port->pps_data.out_volt) / 1000;
+ target_mw = (req_op_curr * port->supply_voltage) / 1000;
if (target_mw < port->operating_snk_mw) {
ret = -EINVAL;
goto port_unlock;
@@ -5166,10 +5844,10 @@
}
/* Round down operating current to align with PPS valid steps */
- op_curr = op_curr - (op_curr % RDO_PROG_CURR_MA_STEP);
+ req_op_curr = req_op_curr - (req_op_curr % RDO_PROG_CURR_MA_STEP);
reinit_completion(&port->pps_complete);
- port->pps_data.op_curr = op_curr;
+ port->pps_data.req_op_curr = req_op_curr;
port->pps_status = 0;
port->pps_pending = true;
mutex_unlock(&port->lock);
@@ -5190,7 +5868,7 @@
return ret;
}
-static int tcpm_pps_set_out_volt(struct tcpm_port *port, u16 out_volt)
+static int tcpm_pps_set_out_volt(struct tcpm_port *port, u16 req_out_volt)
{
unsigned int target_mw;
int ret;
@@ -5208,13 +5886,13 @@
goto port_unlock;
}
- if (out_volt < port->pps_data.min_volt ||
- out_volt > port->pps_data.max_volt) {
+ if (req_out_volt < port->pps_data.min_volt ||
+ req_out_volt > port->pps_data.max_volt) {
ret = -EINVAL;
goto port_unlock;
}
- target_mw = (port->pps_data.op_curr * out_volt) / 1000;
+ target_mw = (port->current_limit * req_out_volt) / 1000;
if (target_mw < port->operating_snk_mw) {
ret = -EINVAL;
goto port_unlock;
@@ -5228,10 +5906,10 @@
}
/* Round down output voltage to align with PPS valid steps */
- out_volt = out_volt - (out_volt % RDO_PROG_VOLT_MV_STEP);
+ req_out_volt = req_out_volt - (req_out_volt % RDO_PROG_VOLT_MV_STEP);
reinit_completion(&port->pps_complete);
- port->pps_data.out_volt = out_volt;
+ port->pps_data.req_out_volt = req_out_volt;
port->pps_status = 0;
port->pps_pending = true;
mutex_unlock(&port->lock);
@@ -5289,8 +5967,8 @@
/* 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;
+ port->pps_data.req_out_volt = port->supply_voltage;
+ port->pps_data.req_op_curr = port->current_limit;
}
mutex_unlock(&port->lock);
@@ -5327,6 +6005,24 @@
if (port->vbus_present)
port->vbus_never_low = true;
+ /*
+ * 1. When vbus_present is true, voltage on VBUS is already at VSAFE5V.
+ * So implicitly vbus_vsafe0v = false.
+ *
+ * 2. When vbus_present is false and TCPC does NOT support querying
+ * vsafe0v status, then, it's best to assume vbus is at VSAFE0V i.e.
+ * vbus_vsafe0v is true.
+ *
+ * 3. When vbus_present is false and TCPC does support querying vsafe0v,
+ * then, query tcpc for vsafe0v status.
+ */
+ if (port->vbus_present)
+ port->vbus_vsafe0v = false;
+ else if (!port->tcpc->is_vbus_vsafe0v)
+ port->vbus_vsafe0v = true;
+ else
+ port->vbus_vsafe0v = port->tcpc->is_vbus_vsafe0v(port->tcpc);
+
tcpm_set_state(port, tcpm_default_state(port), 0);
if (port->tcpc->get_cc(port->tcpc, &cc1, &cc2) == 0)
@@ -5339,10 +6035,9 @@
tcpm_set_state(port, PORT_RESET, 0);
}
-static int tcpm_port_type_set(const struct typec_capability *cap,
- enum typec_port_type type)
+static int tcpm_port_type_set(struct typec_port *p, enum typec_port_type type)
{
- struct tcpm_port *port = typec_cap_to_tcpm(cap);
+ struct tcpm_port *port = typec_get_drvdata(p);
mutex_lock(&port->lock);
if (type == port->port_type)
@@ -5367,6 +6062,14 @@
return 0;
}
+static const struct typec_operations tcpm_ops = {
+ .try_role = tcpm_try_role,
+ .dr_set = tcpm_dr_set,
+ .pr_set = tcpm_pr_set,
+ .vconn_set = tcpm_vconn_set,
+ .port_type_set = tcpm_port_type_set
+};
+
void tcpm_tcpc_reset(struct tcpm_port *port)
{
mutex_lock(&port->lock);
@@ -5376,63 +6079,48 @@
}
EXPORT_SYMBOL_GPL(tcpm_tcpc_reset);
-static int tcpm_copy_pdos(u32 *dest_pdo, const u32 *src_pdo,
- unsigned int nr_pdo)
-{
- unsigned int i;
-
- if (nr_pdo > PDO_MAX_OBJECTS)
- nr_pdo = PDO_MAX_OBJECTS;
-
- for (i = 0; i < nr_pdo; i++)
- dest_pdo[i] = src_pdo[i];
-
- return nr_pdo;
-}
-
static int tcpm_fw_get_caps(struct tcpm_port *port,
struct fwnode_handle *fwnode)
{
const char *cap_str;
int ret;
- u32 mw, frs_current;
+ u32 mw, frs_current, pd_revision;
if (!fwnode)
return -EINVAL;
- /* get vbus regulator */
- port->vbus = devm_regulator_get_optional(port->dev, "vbus");
- if (IS_ERR(port->vbus)) {
- if (PTR_ERR(port->vbus) == -EPROBE_DEFER)
- return PTR_ERR(port->vbus);
-
- dev_info(port->dev, "may no need vbus regulator\n");
- port->vbus = NULL;
- }
+ ret = fwnode_property_read_u32(fwnode, "pd-revision",
+ &pd_revision);
+ if (ret < 0)
+ port->typec_caps.pd_revision = 0x0300;
+ else
+ port->typec_caps.pd_revision = pd_revision & 0xffff;
/* USB data support is optional */
ret = fwnode_property_read_string(fwnode, "data-role", &cap_str);
if (ret == 0) {
- port->typec_caps.data = typec_find_port_data_role(cap_str);
- if (port->typec_caps.data < 0)
- return -EINVAL;
+ ret = typec_find_port_data_role(cap_str);
+ if (ret < 0)
+ return ret;
+ port->typec_caps.data = ret;
}
ret = fwnode_property_read_string(fwnode, "power-role", &cap_str);
if (ret < 0)
return ret;
- port->typec_caps.type = typec_find_port_power_role(cap_str);
- if (port->typec_caps.type < 0)
- return -EINVAL;
+ ret = typec_find_port_power_role(cap_str);
+ if (ret < 0)
+ return ret;
+ port->typec_caps.type = ret;
port->port_type = port->typec_caps.type;
+ port->slow_charger_loop = fwnode_property_read_bool(fwnode, "slow-charger-loop");
if (port->port_type == TYPEC_PORT_SNK)
goto sink;
/* Get source pdos */
- ret = fwnode_property_read_u32_array(fwnode, "source-pdos",
- NULL, 0);
+ ret = fwnode_property_count_u32(fwnode, "source-pdos");
if (ret <= 0)
return -EINVAL;
@@ -5456,8 +6144,7 @@
return -EINVAL;
sink:
/* Get sink pdos */
- ret = fwnode_property_read_u32_array(fwnode, "sink-pdos",
- NULL, 0);
+ ret = fwnode_property_count_u32(fwnode, "sink-pdos");
if (ret <= 0)
return -EINVAL;
@@ -5476,9 +6163,10 @@
/* 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);
+ ret = fwnode_property_read_u32(fwnode, "new-source-frs-typec-current",
+ &frs_current);
if (ret >= 0 && frs_current <= FRS_5V_3A)
- port->frs_current = frs_current;
+ port->new_source_frs_current = frs_current;
}
/* sink-vdos is optional */
@@ -5495,49 +6183,49 @@
return ret;
}
- return 0;
-}
+ /* If sink-vdos is found, sink-vdos-v1 is expected for backward compatibility. */
+ if (port->nr_snk_vdo) {
+ ret = fwnode_property_count_u32(fwnode, "sink-vdos-v1");
+ if (ret < 0)
+ return ret;
+ else if (ret == 0)
+ return -ENODATA;
-int tcpm_update_source_capabilities(struct tcpm_port *port, const u32 *pdo,
- unsigned int nr_pdo)
-{
- if (tcpm_validate_caps(port, pdo, nr_pdo))
- return -EINVAL;
-
- mutex_lock(&port->lock);
- port->nr_src_pdo = tcpm_copy_pdos(port->src_pdo, pdo, nr_pdo);
- switch (port->state) {
- case SRC_UNATTACHED:
- case SRC_ATTACH_WAIT:
- case SRC_TRYWAIT:
- tcpm_set_cc(port, tcpm_rp_cc(port));
- break;
- case SRC_SEND_CAPABILITIES:
- case SRC_NEGOTIATE_CAPABILITIES:
- case SRC_READY:
- case SRC_WAIT_NEW_CAPABILITIES:
- tcpm_set_cc(port, tcpm_rp_cc(port));
- tcpm_set_state(port, SRC_SEND_CAPABILITIES, 0);
- break;
- default:
- break;
+ port->nr_snk_vdo_v1 = min(ret, VDO_MAX_OBJECTS);
+ ret = fwnode_property_read_u32_array(fwnode, "sink-vdos-v1",
+ port->snk_vdo_v1,
+ port->nr_snk_vdo_v1);
+ if (ret < 0)
+ return ret;
}
- mutex_unlock(&port->lock);
+
return 0;
}
-EXPORT_SYMBOL_GPL(tcpm_update_source_capabilities);
-int tcpm_update_sink_capabilities(struct tcpm_port *port, const u32 *pdo,
- unsigned int nr_pdo,
+static int tcpm_copy_pdos(u32 *dest_pdo, const u32 *src_pdo, unsigned int nr_pdo)
+{
+ unsigned int i;
+
+ if (nr_pdo > PDO_MAX_OBJECTS)
+ nr_pdo = PDO_MAX_OBJECTS;
+
+ for (i = 0; i < nr_pdo; i++)
+ dest_pdo[i] = src_pdo[i];
+
+ return nr_pdo;
+}
+
+int tcpm_update_sink_capabilities(struct tcpm_port *port, const u32 *pdo, unsigned int nr_pdo,
unsigned int operating_snk_mw)
{
+ int ret = 0;
+
if (tcpm_validate_caps(port, pdo, nr_pdo))
return -EINVAL;
mutex_lock(&port->lock);
port->nr_snk_pdo = tcpm_copy_pdos(port->snk_pdo, pdo, nr_pdo);
port->operating_snk_mw = operating_snk_mw;
- port->update_sink_caps = true;
switch (port->state) {
case SNK_NEGOTIATE_CAPABILITIES:
@@ -5546,15 +6234,25 @@
case SNK_TRANSITION_SINK:
case SNK_TRANSITION_SINK_VBUS:
if (port->pps_data.active)
- tcpm_set_state(port, SNK_NEGOTIATE_PPS_CAPABILITIES, 0);
+ port->upcoming_state = SNK_NEGOTIATE_PPS_CAPABILITIES;
+ else if (port->pd_capable)
+ port->upcoming_state = SNK_NEGOTIATE_CAPABILITIES;
else
- tcpm_set_state(port, SNK_NEGOTIATE_CAPABILITIES, 0);
+ break;
+
+ port->update_sink_caps = true;
+
+ ret = tcpm_ams_start(port, POWER_NEGOTIATION);
+ if (ret == -EAGAIN) {
+ port->upcoming_state = INVALID_STATE;
+ break;
+ }
break;
default:
break;
}
mutex_unlock(&port->lock);
- return 0;
+ return ret;
}
EXPORT_SYMBOL_GPL(tcpm_update_sink_capabilities);
@@ -5639,6 +6337,27 @@
return 0;
}
+static int tcpm_psy_get_input_power_limit(struct tcpm_port *port,
+ union power_supply_propval *val)
+{
+ unsigned int src_mv, src_ma, max_src_mw = 0;
+ unsigned int i, tmp;
+
+ for (i = 0; i < port->nr_source_caps; i++) {
+ u32 pdo = port->source_caps[i];
+
+ if (pdo_type(pdo) == PDO_TYPE_FIXED) {
+ src_mv = pdo_fixed_voltage(pdo);
+ src_ma = pdo_max_current(pdo);
+ tmp = src_mv * src_ma / 1000;
+ max_src_mw = tmp > max_src_mw ? tmp : max_src_mw;
+ }
+ }
+
+ val->intval = max_src_mw;
+ return 0;
+}
+
static int tcpm_psy_get_prop(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
@@ -5668,11 +6387,13 @@
case POWER_SUPPLY_PROP_CURRENT_NOW:
ret = tcpm_psy_get_current_now(port, val);
break;
+ case POWER_SUPPLY_PROP_INPUT_POWER_LIMIT:
+ tcpm_psy_get_input_power_limit(port, val);
+ break;
default:
ret = -EINVAL;
break;
}
-
return ret;
}
@@ -5724,7 +6445,7 @@
ret = -EINVAL;
break;
}
-
+ power_supply_changed(port->psy);
return ret;
}
@@ -5807,92 +6528,18 @@
return HRTIMER_NORESTART;
}
-static enum hrtimer_restart data_role_swap_timer_handler(struct hrtimer *timer)
+static enum hrtimer_restart send_discover_timer_handler(struct hrtimer *timer)
{
- struct tcpm_port *port =
- container_of(timer, struct tcpm_port, data_role_swap_timer);
+ struct tcpm_port *port = container_of(timer, struct tcpm_port, send_discover_timer);
- kthread_queue_work(port->wq, &port->data_role_swap);
+ kthread_queue_work(port->wq, &port->send_discover_work);
return HRTIMER_NORESTART;
-}
-
-static int tcpm_extcon_register(struct tcpm_port *port)
-{
- struct extcon_dev *extcon;
- int ret;
-
- extcon = devm_extcon_dev_allocate(port->dev, tcpm_cable);
- if (IS_ERR(extcon)) {
- dev_err(port->dev, "allocate extcon failed\n");
- return -ENOMEM;
- }
-
- ret = devm_extcon_dev_register(port->dev, extcon);
- if (ret) {
- dev_err(port->dev, "failed to register extcon: %d\n", ret);
- goto init_err;
- }
-
- ret = extcon_set_property_capability(extcon, EXTCON_USB,
- EXTCON_PROP_USB_TYPEC_POLARITY);
- if (ret) {
- dev_err(port->dev, "failed to set USB property capability: %d\n", ret);
- goto init_err;
- }
-
- ret = extcon_set_property_capability(extcon, EXTCON_USB_HOST,
- EXTCON_PROP_USB_TYPEC_POLARITY);
- if (ret) {
- dev_err(port->dev, "failed to set USB_HOST property capability: %d\n", ret);
- goto init_err;
- }
-
- ret = extcon_set_property_capability(extcon, EXTCON_DISP_DP,
- EXTCON_PROP_USB_TYPEC_POLARITY);
- if (ret) {
- dev_err(port->dev, "failed to set DISP_DP property capability: %d\n", ret);
- goto init_err;
- }
-
- ret = extcon_set_property_capability(extcon, EXTCON_USB, EXTCON_PROP_USB_SS);
- if (ret) {
- dev_err(port->dev, "failed to set USB USB_SS property capability: %d\n", ret);
- goto init_err;
- }
-
- ret = extcon_set_property_capability(extcon, EXTCON_USB_HOST, EXTCON_PROP_USB_SS);
- if (ret) {
- dev_err(port->dev, "failed to set USB_HOST USB_SS property capability: %d", ret);
- goto init_err;
- }
-
- ret = extcon_set_property_capability(extcon, EXTCON_DISP_DP, EXTCON_PROP_USB_SS);
- if (ret) {
- dev_err(port->dev, "failed to set DISP_DP USB_SS property capability: %d", ret);
- goto init_err;
- }
-
- ret = extcon_set_property_capability(extcon, EXTCON_CHG_USB_FAST,
- EXTCON_PROP_USB_TYPEC_POLARITY);
- if (ret) {
- dev_err(port->dev, "failed to set USB_PD property capability: %d\n", ret);
- goto init_err;
- }
-
- port->extcon = extcon;
- tcpm_log(port, "init extcon finished\n");
-
- return 0;
-
-init_err:
- return ret;
}
struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc)
{
struct tcpm_port *port;
int err;
- struct sched_param param = { .sched_priority = MAX_RT_PRIO - 1 };
if (!dev || !tcpc ||
!tcpc->get_vbus || !tcpc->set_cc || !tcpc->get_cc ||
@@ -5909,26 +6556,28 @@
mutex_init(&port->lock);
mutex_init(&port->swap_lock);
+#ifdef CONFIG_NO_GKI
mutex_init(&port->pd_handler_lock);
+#endif
port->wq = kthread_create_worker(0, dev_name(dev));
if (IS_ERR(port->wq))
return ERR_CAST(port->wq);
- sched_setscheduler_nocheck(port->wq->task, SCHED_FIFO, ¶m);
+ sched_set_fifo(port->wq->task);
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);
- kthread_init_work(&port->data_role_swap, tcpm_data_role_swap_work);
+ kthread_init_work(&port->send_discover_work, tcpm_send_discover_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;
- hrtimer_init(&port->data_role_swap_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
- port->data_role_swap_timer.function = data_role_swap_timer_handler;
+ hrtimer_init(&port->send_discover_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+ port->send_discover_timer.function = send_discover_timer_handler;
spin_lock_init(&port->pd_event_lock);
@@ -5936,11 +6585,6 @@
init_completion(&port->swap_complete);
init_completion(&port->pps_complete);
tcpm_debugfs_init(port);
-
- /* init extcon */
- err = tcpm_extcon_register(port);
- if (err)
- goto out_destroy_wq;
err = tcpm_fw_get_caps(port, tcpc->fwnode);
if (err < 0)
@@ -5950,12 +6594,10 @@
port->typec_caps.fwnode = tcpc->fwnode;
port->typec_caps.revision = 0x0120; /* Type-C spec release 1.2 */
- 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;
- port->typec_caps.try_role = tcpm_try_role;
- port->typec_caps.port_type_set = tcpm_port_type_set;
+ port->typec_caps.svdm_version = SVDM_VER_2_0;
+ port->typec_caps.driver_data = port;
+ port->typec_caps.ops = &tcpm_ops;
+ port->typec_caps.orientation_aware = 1;
port->partner_desc.identity = &port->partner_ident;
port->port_type = port->typec_caps.type;
@@ -5968,12 +6610,13 @@
err = devm_tcpm_psy_register(port);
if (err)
- goto out_destroy_wq;
+ goto out_role_sw_put;
+ power_supply_changed(port->psy);
port->typec_port = typec_register_port(port->dev, &port->typec_caps);
if (IS_ERR(port->typec_port)) {
err = PTR_ERR(port->typec_port);
- goto out_destroy_wq;
+ goto out_role_sw_put;
}
typec_port_register_altmodes(port->typec_port,
@@ -5987,8 +6630,10 @@
tcpm_log(port, "%s: registered", dev_name(dev));
return port;
-out_destroy_wq:
+out_role_sw_put:
usb_role_switch_put(port->role_sw);
+out_destroy_wq:
+ tcpm_debugfs_exit(port);
kthread_destroy_worker(port->wq);
return ERR_PTR(err);
}
@@ -5998,10 +6643,10 @@
{
int i;
+ hrtimer_cancel(&port->send_discover_timer);
hrtimer_cancel(&port->enable_frs_timer);
hrtimer_cancel(&port->vdm_state_machine_timer);
hrtimer_cancel(&port->state_machine_timer);
- hrtimer_cancel(&port->data_role_swap_timer);
tcpm_reset_port(port);
for (i = 0; i < ARRAY_SIZE(port->port_altmode); i++)
--
Gitblit v1.6.2