usb: pd: Support VCONN Swap

Support incoming VCONN Swap requests by accepting and turning
off/on VCONN.

Due to HW board limitations, if VCONN is being sourced from the
VBUS input we cannot support enabling VCONN while as a sink and
greater than 5V has been negotiated on VBUS. In that case, reject
the request. Add a device tree property that indicates whether
the board is configured for separate VCONN supply.

Change-Id: If3a9aa316ae08a80468631f3d536a1b345e21b18
Signed-off-by: Jack Pham <jackp@codeaurora.org>
This commit is contained in:
Jack Pham 2016-09-29 23:50:40 -07:00
parent b08f588476
commit d1d42ada3d
2 changed files with 99 additions and 0 deletions

View file

@ -40,6 +40,9 @@ Optional properties:
- vconn-supply: Regulator that enables VCONN source output. This will
be supplied on the USB CC line that is not used for
communication when Ra resistance is detected.
- qcom,vconn-uses-external-source: Indicates whether VCONN supply is sourced
from an external regulator. If omitted, then it is
assumed it is connected to VBUS.
Example:
qcom,qpnp-pdphy@1700 {

View file

@ -59,6 +59,7 @@ enum usbpd_state {
PE_PRS_SRC_SNK_SEND_SWAP,
PE_PRS_SRC_SNK_TRANSITION_TO_OFF,
PE_PRS_SRC_SNK_WAIT_SOURCE_ON,
PE_VCS_WAIT_FOR_VCONN,
};
static const char * const usbpd_state_strings[] = {
@ -94,6 +95,7 @@ static const char * const usbpd_state_strings[] = {
"PRS_SRC_SNK_Send_Swap",
"PRS_SRC_SNK_Transition_to_off",
"PRS_SRC_SNK_Wait_Source_on",
"VCS_Wait_for_VCONN",
};
enum usbpd_control_msg_type {
@ -170,6 +172,7 @@ static void *usbpd_ipc_log;
#define PS_SOURCE_OFF 750
#define SWAP_SOURCE_START_TIME 20
#define VDM_BUSY_TIME 50
#define VCONN_ON_TIME 100
/* tPSHardReset + tSafe0V + tSrcRecover + tSrcTurnOn */
#define SNK_HARD_RESET_RECOVER_TIME (35 + 650 + 1000 + 275)
@ -305,6 +308,7 @@ struct usbpd {
struct regulator *vbus;
struct regulator *vconn;
bool vconn_enabled;
bool vconn_is_external;
u8 tx_msgid;
u8 rx_msgid;
@ -439,6 +443,12 @@ static int pd_select_pdo(struct usbpd *pd, int pdo_pos)
}
pd->requested_voltage = PD_SRC_PDO_FIXED_VOLTAGE(pdo) * 50 * 1000;
/* Can't sink more than 5V if VCONN is sourced from the VBUS input */
if (pd->vconn_enabled && !pd->vconn_is_external &&
pd->requested_voltage > 5000000)
return -ENOTSUPP;
pd->requested_current = curr;
pd->requested_pdo = pdo_pos;
pd->rdo = PD_RDO_FIXED(pdo_pos, 0, mismatch, 1, 1, curr / 10,
@ -1314,6 +1324,34 @@ static void dr_swap(struct usbpd *pd)
pd_phy_update_roles(pd->current_dr, pd->current_pr);
}
static void vconn_swap(struct usbpd *pd)
{
int ret;
if (pd->vconn_enabled) {
pd->current_state = PE_VCS_WAIT_FOR_VCONN;
kick_sm(pd, VCONN_ON_TIME);
} else {
ret = regulator_enable(pd->vconn);
if (ret) {
usbpd_err(&pd->dev, "Unable to enable vconn\n");
return;
}
pd->vconn_enabled = true;
ret = pd_send_msg(pd, MSG_PS_RDY, NULL, 0, SOP_MSG);
if (ret) {
usbpd_err(&pd->dev, "Error sending PS_RDY\n");
usbpd_set_state(pd, pd->current_pr == PR_SRC ?
PE_SRC_SEND_SOFT_RESET :
PE_SNK_SEND_SOFT_RESET);
return;
}
}
}
/* Handles current state and determines transitions */
static void usbpd_sm(struct work_struct *w)
{
@ -1545,6 +1583,15 @@ static void usbpd_sm(struct work_struct *w)
pd->current_state = PE_PRS_SRC_SNK_TRANSITION_TO_OFF;
kick_sm(pd, SRC_TRANSITION_TIME);
break;
} else if (ctrl_recvd == MSG_VCONN_SWAP) {
ret = pd_send_msg(pd, MSG_ACCEPT, NULL, 0, SOP_MSG);
if (ret) {
usbpd_err(&pd->dev, "Error sending Accept\n");
usbpd_set_state(pd, PE_SRC_SEND_SOFT_RESET);
break;
}
vconn_swap(pd);
} else {
if (data_recvd == MSG_VDM)
handle_vdm_rx(pd);
@ -1689,6 +1736,32 @@ static void usbpd_sm(struct work_struct *w)
pd->in_pr_swap = true;
usbpd_set_state(pd, PE_PRS_SNK_SRC_TRANSITION_TO_OFF);
break;
} else if (ctrl_recvd == MSG_VCONN_SWAP) {
/*
* if VCONN is connected to VBUS, make sure we are
* not in high voltage contract, otherwise reject.
*/
if (!pd->vconn_is_external &&
(pd->requested_voltage > 5000000)) {
ret = pd_send_msg(pd, MSG_REJECT, NULL, 0,
SOP_MSG);
if (ret) {
usbpd_err(&pd->dev, "Error sending Reject\n");
usbpd_set_state(pd,
PE_SNK_SEND_SOFT_RESET);
}
break;
}
ret = pd_send_msg(pd, MSG_ACCEPT, NULL, 0, SOP_MSG);
if (ret) {
usbpd_err(&pd->dev, "Error sending Accept\n");
usbpd_set_state(pd, PE_SNK_SEND_SOFT_RESET);
break;
}
vconn_swap(pd);
} else {
if (data_recvd == MSG_VDM)
handle_vdm_rx(pd);
@ -1859,6 +1932,26 @@ static void usbpd_sm(struct work_struct *w)
usbpd_set_state(pd, PE_SRC_STARTUP);
break;
case PE_VCS_WAIT_FOR_VCONN:
if (ctrl_recvd == MSG_PS_RDY) {
/*
* hopefully redundant check but in case not enabled
* avoids unbalanced regulator disable count
*/
if (pd->vconn_enabled)
regulator_disable(pd->vconn);
pd->vconn_enabled = false;
pd->current_state = pd->current_pr == PR_SRC ?
PE_SRC_READY : PE_SNK_READY;
} else {
/* timed out; go to hard reset */
usbpd_set_state(pd, pd->current_pr == PR_SRC ?
PE_SRC_HARD_RESET : PE_SNK_HARD_RESET);
}
break;
default:
usbpd_err(&pd->dev, "Unhandled state %s\n",
usbpd_state_strings[pd->current_state]);
@ -2503,6 +2596,9 @@ struct usbpd *usbpd_create(struct device *parent)
goto unreg_psy;
}
pd->vconn_is_external = device_property_present(parent,
"qcom,vconn-uses-external-source");
pd->current_pr = PR_NONE;
pd->current_dr = DR_NONE;
list_add_tail(&pd->instance, &_usbpd);