Merge "usb: pd: Fix VDM and misc timing issues"
This commit is contained in:
commit
b4f023e5a2
1 changed files with 140 additions and 84 deletions
|
@ -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:
|
||||
|
|
Loading…
Add table
Reference in a new issue