Merge "usb: pd: Fix VDM and misc timing issues"

This commit is contained in:
Linux Build Service Account 2016-11-26 14:26:44 -08:00 committed by Gerrit - the friendly Code Review server
commit b4f023e5a2

View file

@ -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: