summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
authorLinux Build Service Account <lnxbuild@localhost>2016-11-26 14:26:44 -0800
committerGerrit - the friendly Code Review server <code-review@localhost>2016-11-26 14:26:44 -0800
commitb4f023e5a2fbd544b7c32b4276be8c430d5b6c7a (patch)
tree4aba4a150b6ef388de2e4b0b834ada99f8d3b7a5 /drivers
parentb23cd82dba85b13ace45d0c638452f1b641039cd (diff)
parentdd1a207e6ee09ae399ab2abfc9498f23bfb8d9f3 (diff)
Merge "usb: pd: Fix VDM and misc timing issues"
Diffstat (limited to 'drivers')
-rw-r--r--drivers/usb/pd/policy_engine.c224
1 files changed, 140 insertions, 84 deletions
diff --git a/drivers/usb/pd/policy_engine.c b/drivers/usb/pd/policy_engine.c
index aa2e1d9f3c70..e03a649e847c 100644
--- a/drivers/usb/pd/policy_engine.c
+++ b/drivers/usb/pd/policy_engine.c
@@ -163,11 +163,11 @@ static void *usbpd_ipc_log;
/* Timeouts (in ms) */
#define ERROR_RECOVERY_TIME 25
#define SENDER_RESPONSE_TIME 26
-#define SINK_WAIT_CAP_TIME 620
+#define SINK_WAIT_CAP_TIME 500
#define PS_TRANSITION_TIME 450
#define SRC_CAP_TIME 120
#define SRC_TRANSITION_TIME 25
-#define SRC_RECOVER_TIME 660
+#define SRC_RECOVER_TIME 750
#define PS_HARD_RESET_TIME 25
#define PS_SOURCE_ON 400
#define PS_SOURCE_OFF 750
@@ -175,8 +175,11 @@ static void *usbpd_ipc_log;
#define VDM_BUSY_TIME 50
#define VCONN_ON_TIME 100
-/* tPSHardReset + tSafe0V + tSrcRecover + tSrcTurnOn */
-#define SNK_HARD_RESET_RECOVER_TIME (35 + 650 + 1000 + 275)
+/* tPSHardReset + tSafe0V */
+#define SNK_HARD_RESET_VBUS_OFF_TIME (35 + 650)
+
+/* tSrcRecover + tSrcTurnOn */
+#define SNK_HARD_RESET_VBUS_ON_TIME (1000 + 275)
#define PD_CAPS_COUNT 50
@@ -317,6 +320,7 @@ struct usbpd {
struct regulator *vbus;
struct regulator *vconn;
+ bool vbus_enabled;
bool vconn_enabled;
bool vconn_is_external;
@@ -409,6 +413,17 @@ static struct usbpd_svid_handler *find_svid_handler(struct usbpd *pd, u16 svid)
return NULL;
}
+/* Reset protocol layer */
+static inline void pd_reset_protocol(struct usbpd *pd)
+{
+ /*
+ * first Rx ID should be 0; set this to a sentinel of -1 so that in
+ * phy_msg_received() we can check if we had seen it before.
+ */
+ pd->rx_msgid = -1;
+ pd->tx_msgid = 0;
+}
+
static int pd_send_msg(struct usbpd *pd, u8 hdr_type, const u32 *data,
size_t num_data, enum pd_msg_type type)
{
@@ -423,7 +438,9 @@ static int pd_send_msg(struct usbpd *pd, u8 hdr_type, const u32 *data,
/* MessageID incremented regardless of Tx error */
pd->tx_msgid = (pd->tx_msgid + 1) & PD_MAX_MSG_ID;
- if (ret != num_data * sizeof(u32))
+ if (ret < 0)
+ return ret;
+ else if (ret != num_data * sizeof(u32))
return -EIO;
return 0;
}
@@ -631,6 +648,7 @@ static void usbpd_set_state(struct usbpd *pd, enum usbpd_state next_state)
switch (next_state) {
case PE_ERROR_RECOVERY: /* perform hard disconnect/reconnect */
pd->in_pr_swap = false;
+ pd->current_pr = PR_NONE;
set_power_role(pd, PR_NONE);
pd->typec_mode = POWER_SUPPLY_TYPEC_NONE;
kick_sm(pd, 0);
@@ -651,7 +669,7 @@ static void usbpd_set_state(struct usbpd *pd, enum usbpd_state next_state)
power_supply_set_property(pd->usb_psy,
POWER_SUPPLY_PROP_TYPEC_POWER_ROLE, &val);
- pd->rx_msgid = -1;
+ pd_reset_protocol(pd);
if (!pd->in_pr_swap) {
if (pd->pd_phy_opened) {
@@ -677,6 +695,7 @@ static void usbpd_set_state(struct usbpd *pd, enum usbpd_state next_state)
pd->current_state = PE_SRC_SEND_CAPABILITIES;
if (pd->in_pr_swap) {
kick_sm(pd, SWAP_SOURCE_START_TIME);
+ pd->in_pr_swap = false;
break;
}
@@ -768,9 +787,7 @@ static void usbpd_set_state(struct usbpd *pd, enum usbpd_state next_state)
case PE_SRC_SEND_SOFT_RESET:
case PE_SNK_SEND_SOFT_RESET:
- /* Reset protocol layer */
- pd->tx_msgid = 0;
- pd->rx_msgid = -1;
+ pd_reset_protocol(pd);
ret = pd_send_msg(pd, MSG_SOFT_RESET, NULL, 0, SOP_MSG);
if (ret) {
@@ -812,9 +829,7 @@ static void usbpd_set_state(struct usbpd *pd, enum usbpd_state next_state)
if (!val.intval)
break;
- /* Reset protocol layer */
- pd->tx_msgid = 0;
- pd->rx_msgid = -1;
+ pd_reset_protocol(pd);
if (!pd->in_pr_swap) {
if (pd->pd_phy_opened) {
@@ -837,7 +852,17 @@ static void usbpd_set_state(struct usbpd *pd, enum usbpd_state next_state)
pd->pd_phy_opened = true;
}
- pd->current_voltage = 5000000;
+ pd->current_voltage = pd->requested_voltage = 5000000;
+ val.intval = pd->requested_voltage; /* set max range to 5V */
+ power_supply_set_property(pd->usb_psy,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX, &val);
+
+ if (!pd->vbus_present) {
+ pd->current_state = PE_SNK_DISCOVERY;
+ /* max time for hard reset to turn vbus back on */
+ kick_sm(pd, SNK_HARD_RESET_VBUS_ON_TIME);
+ break;
+ }
pd->current_state = PE_SNK_WAIT_FOR_CAPABILITIES;
/* fall-through */
@@ -901,13 +926,8 @@ static void usbpd_set_state(struct usbpd *pd, enum usbpd_state next_state)
pd->vconn_enabled = false;
}
- val.intval = pd->requested_voltage; /* set range back to 5V */
- power_supply_set_property(pd->usb_psy,
- POWER_SUPPLY_PROP_VOLTAGE_MAX, &val);
- pd->current_voltage = pd->requested_voltage;
-
- /* max time for hard reset to toggle vbus off/on */
- kick_sm(pd, SNK_HARD_RESET_RECOVER_TIME);
+ /* max time for hard reset to turn vbus off */
+ kick_sm(pd, SNK_HARD_RESET_VBUS_OFF_TIME);
break;
case PE_PRS_SNK_SRC_TRANSITION_TO_OFF:
@@ -996,7 +1016,7 @@ int usbpd_send_vdm(struct usbpd *pd, u32 vdm_hdr, const u32 *vdos, int num_vdos)
pd->vdm_tx = vdm_tx;
/* slight delay before queuing to prioritize handling of incoming VDM */
- kick_sm(pd, 5);
+ kick_sm(pd, 2);
return 0;
}
@@ -1214,21 +1234,32 @@ static void handle_vdm_rx(struct usbpd *pd, struct rx_msg *rx_msg)
static void handle_vdm_tx(struct usbpd *pd)
{
int ret;
+ unsigned long flags;
/* only send one VDM at a time */
if (pd->vdm_tx) {
u32 vdm_hdr = pd->vdm_tx->data[0];
+ /* bail out and try again later if a message just arrived */
+ spin_lock_irqsave(&pd->rx_lock, flags);
+ if (!list_empty(&pd->rx_q)) {
+ spin_unlock_irqrestore(&pd->rx_lock, flags);
+ return;
+ }
+ spin_unlock_irqrestore(&pd->rx_lock, flags);
+
ret = pd_send_msg(pd, MSG_VDM, pd->vdm_tx->data,
pd->vdm_tx->size, SOP_MSG);
if (ret) {
- usbpd_err(&pd->dev, "Error sending VDM command %d\n",
- SVDM_HDR_CMD(pd->vdm_tx->data[0]));
- usbpd_set_state(pd, pd->current_pr == PR_SRC ?
+ usbpd_err(&pd->dev, "Error (%d) sending VDM command %d\n",
+ ret, SVDM_HDR_CMD(pd->vdm_tx->data[0]));
+
+ /* retry when hitting PE_SRC/SNK_Ready again */
+ if (ret != -EBUSY)
+ usbpd_set_state(pd, pd->current_pr == PR_SRC ?
PE_SRC_SEND_SOFT_RESET :
PE_SNK_SEND_SOFT_RESET);
- /* retry when hitting PE_SRC/SNK_Ready again */
return;
}
@@ -1240,7 +1271,7 @@ static void handle_vdm_tx(struct usbpd *pd)
SVDM_HDR_CMD_TYPE(vdm_hdr) == SVDM_CMD_TYPE_INITIATOR &&
SVDM_HDR_CMD(vdm_hdr) <= USBPD_SVDM_DISCOVER_SVIDS) {
if (pd->vdm_tx_retry) {
- usbpd_err(&pd->dev, "Previous Discover VDM command %d not ACKed/NAKed\n",
+ usbpd_dbg(&pd->dev, "Previous Discover VDM command %d not ACKed/NAKed\n",
SVDM_HDR_CMD(
pd->vdm_tx_retry->data[0]));
kfree(pd->vdm_tx_retry);
@@ -1319,6 +1350,13 @@ static void vconn_swap(struct usbpd *pd)
pd->vconn_enabled = true;
+ /*
+ * Small delay to ensure Vconn has ramped up. This is well
+ * below tVCONNSourceOn (100ms) so we still send PS_RDY within
+ * the allowed time.
+ */
+ usleep_range(5000, 10000);
+
ret = pd_send_msg(pd, MSG_PS_RDY, NULL, 0, SOP_MSG);
if (ret) {
usbpd_err(&pd->dev, "Error sending PS_RDY\n");
@@ -1366,7 +1404,7 @@ static void usbpd_sm(struct work_struct *w)
spin_unlock_irqrestore(&pd->rx_lock, flags);
/* Disconnect? */
- if (pd->typec_mode == POWER_SUPPLY_TYPEC_NONE && !pd->in_pr_swap) {
+ if (pd->current_pr == PR_NONE) {
if (pd->current_state == PE_UNKNOWN)
goto sm_done;
@@ -1400,8 +1438,10 @@ static void usbpd_sm(struct work_struct *w)
power_supply_set_property(pd->usb_psy,
POWER_SUPPLY_PROP_PD_ACTIVE, &val);
- if (pd->current_pr == PR_SRC)
+ if (pd->vbus_enabled) {
regulator_disable(pd->vbus);
+ pd->vbus_enabled = false;
+ }
if (pd->vconn_enabled) {
regulator_disable(pd->vconn);
@@ -1473,6 +1513,8 @@ static void usbpd_sm(struct work_struct *w)
ret = regulator_enable(pd->vbus);
if (ret)
usbpd_err(&pd->dev, "Unable to enable vbus\n");
+ else
+ pd->vbus_enabled = true;
if (!pd->vconn_enabled &&
pd->typec_mode ==
@@ -1521,10 +1563,6 @@ static void usbpd_sm(struct work_struct *w)
break;
}
- val.intval = 1;
- power_supply_set_property(pd->usb_psy,
- POWER_SUPPLY_PROP_PD_ACTIVE, &val);
-
/* transmit was successful if GoodCRC was received */
pd->caps_count = 0;
pd->hard_reset_count = 0;
@@ -1533,6 +1571,10 @@ static void usbpd_sm(struct work_struct *w)
/* wait for REQUEST */
pd->current_state = PE_SRC_SEND_CAPABILITIES_WAIT;
kick_sm(pd, SENDER_RESPONSE_TIME);
+
+ val.intval = 1;
+ power_supply_set_property(pd->usb_psy,
+ POWER_SUPPLY_PROP_PD_ACTIVE, &val);
break;
case PE_SRC_SEND_CAPABILITIES_WAIT:
@@ -1618,7 +1660,8 @@ static void usbpd_sm(struct work_struct *w)
case PE_SRC_TRANSITION_TO_DEFAULT:
if (pd->vconn_enabled)
regulator_disable(pd->vconn);
- regulator_disable(pd->vbus);
+ if (pd->vbus_enabled)
+ regulator_disable(pd->vbus);
if (pd->current_dr != DR_DFP) {
extcon_set_cable_state_(pd->extcon, EXTCON_USB, 0);
@@ -1628,9 +1671,12 @@ static void usbpd_sm(struct work_struct *w)
msleep(SRC_RECOVER_TIME);
+ pd->vbus_enabled = false;
ret = regulator_enable(pd->vbus);
if (ret)
usbpd_err(&pd->dev, "Unable to enable vbus\n");
+ else
+ pd->vbus_enabled = true;
if (pd->vconn_enabled) {
ret = regulator_enable(pd->vconn);
@@ -1665,23 +1711,48 @@ static void usbpd_sm(struct work_struct *w)
usbpd_set_state(pd, PE_SNK_STARTUP);
break;
+ case PE_SNK_DISCOVERY:
+ if (!rx_msg) {
+ if (pd->vbus_present)
+ usbpd_set_state(pd,
+ PE_SNK_WAIT_FOR_CAPABILITIES);
+
+ /*
+ * Handle disconnection in the middle of PR_Swap.
+ * Since in psy_changed() if pd->in_pr_swap is true
+ * we ignore the typec_mode==NONE change since that is
+ * expected to happen. However if the cable really did
+ * get disconnected we need to check for it here after
+ * waiting for VBUS presence times out.
+ */
+ if (!pd->typec_mode) {
+ pd->current_pr = PR_NONE;
+ kick_sm(pd, 0);
+ }
+
+ break;
+ }
+ /* else fall-through */
+
case PE_SNK_WAIT_FOR_CAPABILITIES:
+ pd->in_pr_swap = false;
+
if (IS_DATA(rx_msg, MSG_SOURCE_CAPABILITIES)) {
val.intval = 0;
power_supply_set_property(pd->usb_psy,
POWER_SUPPLY_PROP_PD_IN_HARD_RESET,
&val);
- val.intval = 1;
- power_supply_set_property(pd->usb_psy,
- POWER_SUPPLY_PROP_PD_ACTIVE, &val);
-
/* save the PDOs so userspace can further evaluate */
memcpy(&pd->received_pdos, rx_msg->payload,
sizeof(pd->received_pdos));
pd->src_cap_id++;
usbpd_set_state(pd, PE_SNK_EVALUATE_CAPABILITY);
+
+ val.intval = 1;
+ power_supply_set_property(pd->usb_psy,
+ POWER_SUPPLY_PROP_PD_ACTIVE, &val);
} else if (pd->hard_reset_count < 3) {
usbpd_set_state(pd, PE_SNK_HARD_RESET);
} else if (pd->pd_connected) {
@@ -1872,28 +1943,12 @@ static void usbpd_sm(struct work_struct *w)
break;
case PE_SNK_TRANSITION_TO_DEFAULT:
- val.intval = 0;
- power_supply_set_property(pd->usb_psy,
- POWER_SUPPLY_PROP_PD_IN_HARD_RESET, &val);
-
- if (pd->vbus_present) {
- usbpd_set_state(pd, PE_SNK_STARTUP);
- } else {
- /* Hard reset and VBUS didn't come back? */
- power_supply_get_property(pd->usb_psy,
- POWER_SUPPLY_PROP_TYPEC_MODE, &val);
- if (val.intval == POWER_SUPPLY_TYPEC_NONE) {
- pd->typec_mode = POWER_SUPPLY_TYPEC_NONE;
- kick_sm(pd, 0);
- }
- }
+ usbpd_set_state(pd, PE_SNK_STARTUP);
break;
case PE_SRC_SOFT_RESET:
case PE_SNK_SOFT_RESET:
- /* Reset protocol layer */
- pd->tx_msgid = 0;
- pd->rx_msgid = -1;
+ pd_reset_protocol(pd);
ret = pd_send_msg(pd, MSG_ACCEPT, NULL, 0, SOP_MSG);
if (ret) {
@@ -1969,7 +2024,10 @@ static void usbpd_sm(struct work_struct *w)
pd->in_pr_swap = true;
pd->in_explicit_contract = false;
- regulator_disable(pd->vbus);
+ if (pd->vbus_enabled) {
+ regulator_disable(pd->vbus);
+ pd->vbus_enabled = false;
+ }
/* PE_PRS_SRC_SNK_Assert_Rd */
pd->current_pr = PR_SINK;
@@ -2024,6 +2082,8 @@ static void usbpd_sm(struct work_struct *w)
ret = regulator_enable(pd->vbus);
if (ret)
usbpd_err(&pd->dev, "Unable to enable vbus\n");
+ else
+ pd->vbus_enabled = true;
msleep(200); /* allow time VBUS ramp-up, must be < tNewSrc */
@@ -2134,6 +2194,21 @@ static int psy_changed(struct notifier_block *nb, unsigned long evt, void *ptr)
pd->psy_type = val.intval;
+ /*
+ * For sink hard reset, state machine needs to know when VBUS changes
+ * - when in PE_SNK_TRANSITION_TO_DEFAULT, notify when VBUS falls
+ * - when in PE_SNK_DISCOVERY, notify when VBUS rises
+ */
+ if (typec_mode && ((!pd->vbus_present &&
+ pd->current_state == PE_SNK_TRANSITION_TO_DEFAULT) ||
+ (pd->vbus_present && pd->current_state == PE_SNK_DISCOVERY))) {
+ usbpd_dbg(&pd->dev, "hard reset: typec mode:%d present:%d\n",
+ typec_mode, pd->vbus_present);
+ pd->typec_mode = typec_mode;
+ kick_sm(pd, 0);
+ return 0;
+ }
+
if (pd->typec_mode == typec_mode)
return 0;
@@ -2151,32 +2226,7 @@ static int psy_changed(struct notifier_block *nb, unsigned long evt, void *ptr)
return 0;
}
- /*
- * Workaround for PMIC HW bug.
- *
- * During hard reset when VBUS goes to 0 the CC logic
- * will report this as a disconnection. In those cases
- * it can be ignored, however the downside is that
- * we can also happen to be in the SNK_Transition_to_default
- * state due to a hard reset attempt even with a non-PD
- * capable source, in which a physical disconnect may get
- * masked. In that case, allow for the common case of
- * disconnecting from an SDP.
- *
- * The less common case is a PD-capable SDP which will
- * result in a hard reset getting treated like a
- * disconnect. We can live with this until the HW bug
- * is fixed: in which disconnection won't be reported
- * on VBUS loss alone unless pullup is also removed
- * from CC.
- */
- if (pd->psy_type != POWER_SUPPLY_TYPE_USB &&
- pd->current_state ==
- PE_SNK_TRANSITION_TO_DEFAULT) {
- usbpd_dbg(&pd->dev, "Ignoring disconnect due to hard reset\n");
- return 0;
- }
-
+ pd->current_pr = PR_NONE;
break;
/* Sink states */
@@ -2185,8 +2235,11 @@ static int psy_changed(struct notifier_block *nb, unsigned long evt, void *ptr)
case POWER_SUPPLY_TYPEC_SOURCE_HIGH:
usbpd_info(&pd->dev, "Type-C Source (%s) connected\n",
src_current(typec_mode));
+
+ if (pd->current_pr == PR_SINK)
+ return 0;
+
pd->current_pr = PR_SINK;
- pd->in_pr_swap = false;
break;
/* Source states */
@@ -2195,8 +2248,11 @@ static int psy_changed(struct notifier_block *nb, unsigned long evt, void *ptr)
usbpd_info(&pd->dev, "Type-C Sink%s connected\n",
typec_mode == POWER_SUPPLY_TYPEC_SINK ?
"" : " (powered)");
+
+ if (pd->current_pr == PR_SRC)
+ return 0;
+
pd->current_pr = PR_SRC;
- pd->in_pr_swap = false;
break;
case POWER_SUPPLY_TYPEC_SINK_DEBUG_ACCESSORY: