summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--drivers/usb/pd/policy_engine.c480
-rw-r--r--include/linux/usb/usbpd.h156
2 files changed, 628 insertions, 8 deletions
diff --git a/drivers/usb/pd/policy_engine.c b/drivers/usb/pd/policy_engine.c
index b7b978b8d306..8dd65884067f 100644
--- a/drivers/usb/pd/policy_engine.c
+++ b/drivers/usb/pd/policy_engine.c
@@ -16,12 +16,13 @@
#include <linux/list.h>
#include <linux/module.h>
#include <linux/of.h>
-#include <linux/platform_device.h>
+#include <linux/of_platform.h>
#include <linux/power_supply.h>
#include <linux/regulator/consumer.h>
#include <linux/slab.h>
#include <linux/workqueue.h>
#include <linux/extcon.h>
+#include <linux/usb/usbpd.h>
#include "usbpd.h"
enum usbpd_state {
@@ -119,10 +120,13 @@ enum usbpd_data_msg_type {
MSG_VDM = 0xF,
};
-enum plug_orientation {
- ORIENTATION_NONE,
- ORIENTATION_CC1,
- ORIENTATION_CC2,
+enum vdm_state {
+ VDM_NONE,
+ DISCOVERED_ID,
+ DISCOVERED_SVIDS,
+ DISCOVERED_MODES,
+ MODE_ENTERED,
+ MODE_EXITED,
};
static void *usbpd_ipc_log;
@@ -162,6 +166,7 @@ static void *usbpd_ipc_log;
#define PS_HARD_RESET_TIME 35
#define PS_SOURCE_ON 400
#define PS_SOURCE_OFF 900
+#define VDM_BUSY_TIME 50
#define PD_CAPS_COUNT 50
@@ -205,6 +210,33 @@ static void *usbpd_ipc_log;
#define PD_SRC_PDO_VAR_BATT_MIN_VOLT(pdo) (((pdo) >> 10) & 0x3FF)
#define PD_SRC_PDO_VAR_BATT_MAX(pdo) ((pdo) & 0x3FF)
+/* Vendor Defined Messages */
+#define MAX_CRC_RECEIVE_TIME 9 /* ~(2 * tReceive_max(1.1ms) * # retry 4) */
+#define MAX_VDM_RESPONSE_TIME 60 /* 2 * tVDMSenderResponse_max(30ms) */
+#define MAX_VDM_BUSY_TIME 100 /* 2 * tVDMBusy (50ms) */
+
+/* VDM header is the first 32-bit object following the 16-bit PD header */
+#define VDM_HDR_SVID(hdr) ((hdr) >> 16)
+#define VDM_HDR_TYPE(hdr) ((hdr) & 0x8000)
+#define VDM_HDR_CMD_TYPE(hdr) (((hdr) >> 6) & 0x3)
+#define VDM_HDR_CMD(hdr) ((hdr) & 0x1f)
+
+#define SVDM_HDR(svid, ver, obj, cmd_type, cmd) \
+ (((svid) << 16) | (1 << 15) | ((ver) << 13) \
+ | ((obj) << 8) | ((cmd_type) << 6) | (cmd))
+
+/* discover id response vdo bit fields */
+#define ID_HDR_USB_HOST BIT(31)
+#define ID_HDR_USB_DEVICE BIT(30)
+#define ID_HDR_MODAL_OPR BIT(26)
+#define ID_HDR_PRODUCT_TYPE(n) ((n) >> 27)
+#define ID_HDR_PRODUCT_PER_MASK (2 << 27)
+#define ID_HDR_PRODUCT_HUB 1
+#define ID_HDR_PRODUCT_PER 2
+#define ID_HDR_PRODUCT_AMA 5
+#define ID_HDR_VID 0x05c6 /* qcom */
+#define PROD_VDO_PID 0x0a00 /* TBD */
+
static int min_sink_current = 900;
module_param(min_sink_current, int, S_IRUSR | S_IWUSR);
@@ -217,6 +249,12 @@ static const u32 default_snk_caps[] = { 0x2601905A, /* 5V @ 900mA */
0x0002D096, /* 9V @ 1.5A */
0x0003C064 }; /* 12V @ 1A */
+struct vdm_tx {
+ u32 data[7];
+ int size;
+ struct list_head entry;
+};
+
struct usbpd {
struct device dev;
struct workqueue_struct *wq;
@@ -265,6 +303,12 @@ struct usbpd {
int caps_count;
int hard_reset_count;
+ enum vdm_state vdm_state;
+ u16 *discovered_svids;
+ struct vdm_tx *vdm_tx_retry;
+ struct list_head vdm_tx_queue;
+ struct list_head svid_handlers;
+
struct list_head instance;
};
@@ -280,7 +324,7 @@ static const unsigned int usbpd_extcon_cable[] = {
/* EXTCON_USB and EXTCON_USB_HOST are mutually exclusive */
static const u32 usbpd_extcon_exclusive[] = {0x3, 0};
-static enum plug_orientation usbpd_get_plug_orientation(struct usbpd *pd)
+enum plug_orientation usbpd_get_plug_orientation(struct usbpd *pd)
{
int ret;
union power_supply_propval val;
@@ -292,6 +336,7 @@ static enum plug_orientation usbpd_get_plug_orientation(struct usbpd *pd)
return val.intval;
}
+EXPORT_SYMBOL(usbpd_get_plug_orientation);
static bool is_cable_flipped(struct usbpd *pd)
{
@@ -329,6 +374,17 @@ static int set_power_role(struct usbpd *pd, enum power_role pr)
POWER_SUPPLY_PROP_TYPEC_POWER_ROLE, &val);
}
+static struct usbpd_svid_handler *find_svid_handler(struct usbpd *pd, u16 svid)
+{
+ struct usbpd_svid_handler *handler;
+
+ list_for_each_entry(handler, &pd->svid_handlers, entry)
+ if (svid == handler->svid)
+ return handler;
+
+ return NULL;
+}
+
static int pd_send_msg(struct usbpd *pd, u8 hdr_type, const u32 *data,
size_t num_data, enum pd_msg_type type)
{
@@ -604,9 +660,17 @@ static void usbpd_set_state(struct usbpd *pd, enum usbpd_state next_state)
break;
case PE_SRC_READY:
- if (pd->current_dr == DR_DFP)
- extcon_set_cable_state_(pd->extcon, EXTCON_USB_HOST, 1);
pd->in_explicit_contract = true;
+ if (pd->current_dr == DR_DFP) {
+ if (pd->vdm_state == VDM_NONE)
+ usbpd_send_svdm(pd, USBPD_SID,
+ USBPD_SVDM_DISCOVER_IDENTITY,
+ SVDM_CMD_TYPE_INITIATOR, 0,
+ NULL, 0);
+
+ extcon_set_cable_state_(pd->extcon, EXTCON_USB_HOST, 1);
+ }
+
kobject_uevent(&pd->dev.kobj, KOBJ_CHANGE);
break;
@@ -799,6 +863,315 @@ static void usbpd_set_state(struct usbpd *pd, enum usbpd_state next_state)
}
}
+int usbpd_register_svid(struct usbpd *pd, struct usbpd_svid_handler *hdlr)
+{
+ if (find_svid_handler(pd, hdlr->svid)) {
+ usbpd_err(&pd->dev, "SVID 0x%04x already registered\n",
+ hdlr->svid);
+ return -EINVAL;
+ }
+
+ usbpd_dbg(&pd->dev, "registered handler for SVID 0x%04x\n", hdlr->svid);
+
+ list_add_tail(&hdlr->entry, &pd->svid_handlers);
+
+ /* already connected with this SVID discovered? */
+ if (pd->vdm_state >= DISCOVERED_SVIDS) {
+ u16 *psvid;
+
+ for (psvid = pd->discovered_svids; *psvid; psvid++) {
+ if (*psvid == hdlr->svid) {
+ if (hdlr->connect)
+ hdlr->connect(hdlr);
+ break;
+ }
+ }
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL(usbpd_register_svid);
+
+void usbpd_unregister_svid(struct usbpd *pd, struct usbpd_svid_handler *hdlr)
+{
+ list_del_init(&hdlr->entry);
+}
+EXPORT_SYMBOL(usbpd_unregister_svid);
+
+int usbpd_send_vdm(struct usbpd *pd, u32 vdm_hdr, const u32 *vdos, int num_vdos)
+{
+ struct vdm_tx *vdm_tx;
+
+ if (!pd->in_explicit_contract)
+ return -EBUSY;
+
+ vdm_tx = kzalloc(sizeof(*vdm_tx), GFP_KERNEL);
+ if (!vdm_tx)
+ return -ENOMEM;
+
+ vdm_tx->data[0] = vdm_hdr;
+ memcpy(&vdm_tx->data[1], vdos, num_vdos * sizeof(u32));
+ vdm_tx->size = num_vdos + 1; /* include the header */
+
+ /* VDM will get sent in PE_SRC/SNK_READY state handling */
+ list_add_tail(&vdm_tx->entry, &pd->vdm_tx_queue);
+ queue_delayed_work(pd->wq, &pd->sm_work, 0);
+
+ return 0;
+}
+EXPORT_SYMBOL(usbpd_send_vdm);
+
+int usbpd_send_svdm(struct usbpd *pd, u16 svid, u8 cmd,
+ enum usbpd_svdm_cmd_type cmd_type, int obj_pos,
+ const u32 *vdos, int num_vdos)
+{
+ u32 svdm_hdr = SVDM_HDR(svid, 0, obj_pos, cmd_type, cmd);
+
+ usbpd_dbg(&pd->dev, "VDM tx: svid:%x cmd:%x cmd_type:%x svdm_hdr:%x\n",
+ svid, cmd, cmd_type, svdm_hdr);
+
+ return usbpd_send_vdm(pd, svdm_hdr, vdos, num_vdos);
+}
+EXPORT_SYMBOL(usbpd_send_svdm);
+
+static void handle_vdm_rx(struct usbpd *pd)
+{
+ u32 vdm_hdr = pd->rx_payload[0];
+ u32 *vdos = &pd->rx_payload[1];
+ u16 svid = VDM_HDR_SVID(vdm_hdr);
+ u16 *psvid;
+ u8 i, num_vdos = pd->rx_msg_len - 1; /* num objects minus header */
+ u8 cmd = VDM_HDR_CMD(vdm_hdr);
+ u8 cmd_type = VDM_HDR_CMD_TYPE(vdm_hdr);
+ struct usbpd_svid_handler *handler;
+
+ usbpd_dbg(&pd->dev, "VDM rx: svid:%x cmd:%x cmd_type:%x vdm_hdr:%x\n",
+ svid, cmd, cmd_type, vdm_hdr);
+
+ /* if it's a supported SVID, pass the message to the handler */
+ handler = find_svid_handler(pd, svid);
+
+ /* Unstructured VDM */
+ if (!VDM_HDR_TYPE(vdm_hdr)) {
+ if (handler && handler->vdm_received)
+ handler->vdm_received(handler, vdm_hdr, vdos, num_vdos);
+ return;
+ }
+
+ if (handler && handler->svdm_received)
+ handler->svdm_received(handler, cmd, cmd_type, vdos, num_vdos);
+
+ switch (cmd_type) {
+ case SVDM_CMD_TYPE_INITIATOR:
+ if (cmd == USBPD_SVDM_DISCOVER_IDENTITY) {
+ u32 tx_vdos[3] = {
+ ID_HDR_USB_HOST | ID_HDR_USB_DEVICE |
+ ID_HDR_PRODUCT_PER_MASK | ID_HDR_VID,
+ 0x0, /* TBD: Cert Stat VDO */
+ (PROD_VDO_PID << 16),
+ /* TBD: Get these from gadget */
+ };
+
+ usbpd_send_svdm(pd, USBPD_SID, cmd,
+ SVDM_CMD_TYPE_RESP_ACK, 0, tx_vdos, 3);
+ } else {
+ usbpd_send_svdm(pd, USBPD_SID, cmd,
+ SVDM_CMD_TYPE_RESP_NAK, 0, NULL, 0);
+ }
+ break;
+
+ case SVDM_CMD_TYPE_RESP_ACK:
+ switch (cmd) {
+ case USBPD_SVDM_DISCOVER_IDENTITY:
+ if (svid != USBPD_SID) {
+ usbpd_err(&pd->dev, "invalid VID:0x%x\n", svid);
+ break;
+ }
+
+ kfree(pd->vdm_tx_retry);
+ pd->vdm_tx_retry = NULL;
+
+ pd->vdm_state = DISCOVERED_ID;
+ usbpd_send_svdm(pd, USBPD_SID,
+ USBPD_SVDM_DISCOVER_SVIDS,
+ SVDM_CMD_TYPE_INITIATOR, 0, NULL, 0);
+ break;
+
+ case USBPD_SVDM_DISCOVER_SVIDS:
+ if (svid != USBPD_SID) {
+ usbpd_err(&pd->dev, "invalid VID:0x%x\n", svid);
+ break;
+ }
+
+ pd->vdm_state = DISCOVERED_SVIDS;
+
+ kfree(pd->vdm_tx_retry);
+ pd->vdm_tx_retry = NULL;
+
+ kfree(pd->discovered_svids);
+
+ /* TODO: handle > 12 SVIDs */
+ pd->discovered_svids = kzalloc((2 * num_vdos + 1) *
+ sizeof(u16),
+ GFP_KERNEL);
+ if (!pd->discovered_svids) {
+ usbpd_err(&pd->dev, "unable to allocate SVIDs\n");
+ break;
+ }
+
+ /* convert 32-bit VDOs to list of 16-bit SVIDs */
+ psvid = pd->discovered_svids;
+ for (i = 0; i < num_vdos * 2; i++) {
+ /*
+ * Within each 32-bit VDO,
+ * SVID[i]: upper 16-bits
+ * SVID[i+1]: lower 16-bits
+ * where i is even.
+ */
+ if (!(i & 1))
+ svid = vdos[i >> 1] >> 16;
+ else
+ svid = vdos[i >> 1] & 0xFFFF;
+
+ /*
+ * There are some devices that incorrectly
+ * swap the order of SVIDs within a VDO. So in
+ * case of an odd-number of SVIDs it could end
+ * up with SVID[i] as 0 while SVID[i+1] is
+ * non-zero. Just skip over the zero ones.
+ */
+ if (svid) {
+ usbpd_dbg(&pd->dev, "Discovered SVID: 0x%04x\n",
+ svid);
+ *psvid++ = svid;
+
+ /* if SVID supported notify handler */
+ handler = find_svid_handler(pd, svid);
+ if (handler && handler->connect)
+ handler->connect(handler);
+ }
+ }
+
+ break;
+
+ case USBPD_SVDM_DISCOVER_MODES:
+ usbpd_info(&pd->dev, "SVID:0x%04x VDM Modes discovered\n",
+ svid);
+ pd->vdm_state = DISCOVERED_MODES;
+ break;
+
+ case USBPD_SVDM_ENTER_MODE:
+ usbpd_info(&pd->dev, "SVID:0x%04x VDM Mode entered\n",
+ svid);
+ pd->vdm_state = MODE_ENTERED;
+ kobject_uevent(&pd->dev.kobj, KOBJ_CHANGE);
+ break;
+
+ case USBPD_SVDM_EXIT_MODE:
+ usbpd_info(&pd->dev, "SVID:0x%04x VDM Mode exited\n",
+ svid);
+ pd->vdm_state = MODE_EXITED;
+ kobject_uevent(&pd->dev.kobj, KOBJ_CHANGE);
+ break;
+
+ default:
+ break;
+ }
+ break;
+
+ case SVDM_CMD_TYPE_RESP_NAK:
+ usbpd_info(&pd->dev, "VDM NAK received for SVID:0x%04x command:%d\n",
+ svid, cmd);
+ break;
+
+ case SVDM_CMD_TYPE_RESP_BUSY:
+ switch (cmd) {
+ case USBPD_SVDM_DISCOVER_IDENTITY:
+ case USBPD_SVDM_DISCOVER_SVIDS:
+ if (!pd->vdm_tx_retry) {
+ usbpd_err(&pd->dev, "Discover command %d VDM was unexpectedly freed\n",
+ cmd);
+ break;
+ }
+
+ /* wait tVDMBusy, then retry */
+ list_move(&pd->vdm_tx_retry->entry, &pd->vdm_tx_queue);
+ pd->vdm_tx_retry = NULL;
+ queue_delayed_work(pd->wq, &pd->sm_work,
+ msecs_to_jiffies(VDM_BUSY_TIME));
+ break;
+ default:
+ break;
+ }
+ break;
+ }
+}
+
+static void handle_vdm_tx(struct usbpd *pd)
+{
+ int ret;
+
+ /* only send one VDM at a time */
+ if (!list_empty(&pd->vdm_tx_queue)) {
+ struct vdm_tx *vdm_tx = list_first_entry(&pd->vdm_tx_queue,
+ struct vdm_tx, entry);
+ u32 vdm_hdr = vdm_tx->data[0];
+
+ ret = pd_send_msg(pd, MSG_VDM, vdm_tx->data, vdm_tx->size,
+ SOP_MSG);
+ if (ret) {
+ usbpd_err(&pd->dev, "Error sending VDM command %d\n",
+ VDM_HDR_CMD(vdm_tx->data[0]));
+ 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;
+ }
+
+ list_del(&vdm_tx->entry);
+
+ /*
+ * special case: keep initiated Discover ID/SVIDs
+ * around in case we need to re-try when receiving BUSY
+ */
+ if (VDM_HDR_TYPE(vdm_hdr) &&
+ VDM_HDR_CMD_TYPE(vdm_hdr) == SVDM_CMD_TYPE_INITIATOR &&
+ VDM_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",
+ VDM_HDR_CMD(pd->vdm_tx_retry->data[0]));
+ kfree(pd->vdm_tx_retry);
+ }
+ pd->vdm_tx_retry = vdm_tx;
+ } else {
+ kfree(vdm_tx);
+ }
+ }
+}
+
+static void reset_vdm_state(struct usbpd *pd)
+{
+ struct usbpd_svid_handler *handler;
+
+ pd->vdm_state = VDM_NONE;
+ list_for_each_entry(handler, &pd->svid_handlers, entry)
+ if (handler->disconnect)
+ handler->disconnect(handler);
+ kfree(pd->vdm_tx_retry);
+ pd->vdm_tx_retry = NULL;
+ kfree(pd->discovered_svids);
+ pd->discovered_svids = NULL;
+ while (!list_empty(&pd->vdm_tx_queue)) {
+ struct vdm_tx *vdm_tx =
+ list_first_entry(&pd->vdm_tx_queue,
+ struct vdm_tx, entry);
+ list_del(&vdm_tx->entry);
+ kfree(vdm_tx);
+ }
+}
+
static void dr_swap(struct usbpd *pd)
{
if (pd->current_dr == DR_DFP) {
@@ -813,6 +1186,12 @@ static void dr_swap(struct usbpd *pd)
is_cable_flipped(pd));
extcon_set_cable_state_(pd->extcon, EXTCON_USB_HOST, 1);
pd->current_dr = DR_DFP;
+
+ if (pd->vdm_state == VDM_NONE)
+ usbpd_send_svdm(pd, USBPD_SID,
+ USBPD_SVDM_DISCOVER_IDENTITY,
+ SVDM_CMD_TYPE_INITIATOR, 0,
+ NULL, 0);
}
pd_phy_update_roles(pd->current_dr, pd->current_pr);
@@ -873,6 +1252,8 @@ static void usbpd_sm(struct work_struct *w)
pd->current_pr = PR_NONE;
pd->current_dr = DR_NONE;
+ reset_vdm_state(pd);
+
/* Set CC back to DRP toggle */
val.intval = POWER_SUPPLY_TYPEC_PR_DUAL;
power_supply_set_property(pd->usb_psy,
@@ -884,6 +1265,8 @@ static void usbpd_sm(struct work_struct *w)
/* Hard reset? */
if (pd->hard_reset) {
+ reset_vdm_state(pd);
+
if (pd->current_pr == PR_SINK)
usbpd_set_state(pd, PE_SNK_TRANSITION_TO_DEFAULT);
else
@@ -1001,6 +1384,11 @@ static void usbpd_sm(struct work_struct *w)
pd->rdo = pd->rx_payload[0];
usbpd_set_state(pd, PE_SRC_NEGOTIATE_CAPABILITY);
} else if (ctrl_recvd == MSG_DR_SWAP) {
+ if (pd->vdm_state == MODE_ENTERED) {
+ usbpd_set_state(pd, PE_SRC_HARD_RESET);
+ break;
+ }
+
ret = pd_send_msg(pd, MSG_ACCEPT, NULL, 0, SOP_MSG);
if (ret) {
usbpd_err(&pd->dev, "Error sending Accept\n");
@@ -1022,12 +1410,18 @@ static void usbpd_sm(struct work_struct *w)
pd->current_state = PE_PRS_SRC_SNK_TRANSITION_TO_OFF;
queue_delayed_work(pd->wq, &pd->sm_work, 0);
break;
+ } else {
+ if (data_recvd == MSG_VDM)
+ handle_vdm_rx(pd);
+ else
+ handle_vdm_tx(pd);
}
break;
case PE_SRC_HARD_RESET:
pd_send_hard_reset(pd);
pd->in_explicit_contract = false;
+ reset_vdm_state(pd);
msleep(PS_HARD_RESET_TIME);
usbpd_set_state(pd, PE_SRC_TRANSITION_TO_DEFAULT);
@@ -1133,6 +1527,11 @@ static void usbpd_sm(struct work_struct *w)
usbpd_set_state(pd, PE_SNK_SEND_SOFT_RESET);
}
} else if (ctrl_recvd == MSG_DR_SWAP) {
+ if (pd->vdm_state == MODE_ENTERED) {
+ usbpd_set_state(pd, PE_SNK_HARD_RESET);
+ break;
+ }
+
ret = pd_send_msg(pd, MSG_ACCEPT, NULL, 0, SOP_MSG);
if (ret) {
usbpd_err(&pd->dev, "Error sending Accept\n");
@@ -1166,6 +1565,11 @@ static void usbpd_sm(struct work_struct *w)
queue_delayed_work(pd->wq, &pd->sm_work,
msecs_to_jiffies(PS_SOURCE_OFF));
break;
+ } else {
+ if (data_recvd == MSG_VDM)
+ handle_vdm_rx(pd);
+ else
+ handle_vdm_tx(pd);
}
break;
@@ -1218,6 +1622,7 @@ static void usbpd_sm(struct work_struct *w)
pd_send_hard_reset(pd);
pd->in_explicit_contract = false;
+ reset_vdm_state(pd);
usbpd_set_state(pd, PE_SNK_TRANSITION_TO_DEFAULT);
break;
@@ -1480,6 +1885,7 @@ static int usbpd_uevent(struct device *dev, struct kobj_uevent_env *env)
add_uevent_var(env, "RDO=%08x", pd->rdo);
add_uevent_var(env, "CONTRACT=%s", pd->in_explicit_contract ?
"explicit" : "implicit");
+ add_uevent_var(env, "ALT_MODE=%d", pd->vdm_state == MODE_ENTERED);
return 0;
}
@@ -1782,6 +2188,61 @@ static struct class usbpd_class = {
.dev_groups = usbpd_groups,
};
+static int match_usbpd_device(struct device *dev, const void *data)
+{
+ return dev->parent == data;
+}
+
+static void devm_usbpd_put(struct device *dev, void *res)
+{
+ struct usbpd **ppd = res;
+
+ put_device(&(*ppd)->dev);
+}
+
+struct usbpd *devm_usbpd_get_by_phandle(struct device *dev, const char *phandle)
+{
+ struct usbpd **ptr, *pd = NULL;
+ struct device_node *pd_np;
+ struct platform_device *pdev;
+ struct device *pd_dev;
+
+ if (!dev->of_node)
+ return ERR_PTR(-ENODEV);
+
+ pd_np = of_parse_phandle(dev->of_node, phandle, 0);
+ if (!pd_np)
+ return ERR_PTR(-ENODEV);
+
+ pdev = of_find_device_by_node(pd_np);
+ if (!pdev)
+ return ERR_PTR(-ENODEV);
+
+ pd_dev = class_find_device(&usbpd_class, NULL, &pdev->dev,
+ match_usbpd_device);
+ if (!pd_dev) {
+ platform_device_put(pdev);
+ return ERR_PTR(-ENODEV);
+ }
+
+ ptr = devres_alloc(devm_usbpd_put, sizeof(*ptr), GFP_KERNEL);
+ if (!ptr) {
+ put_device(pd_dev);
+ platform_device_put(pdev);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ pd = dev_get_drvdata(pd_dev);
+ if (!pd)
+ return ERR_PTR(-ENODEV);
+
+ *ptr = pd;
+ devres_add(dev, ptr);
+
+ return pd;
+}
+EXPORT_SYMBOL(devm_usbpd_get_by_phandle);
+
static int num_pd_instances;
/**
@@ -1869,6 +2330,9 @@ struct usbpd *usbpd_create(struct device *parent)
pd->current_dr = DR_NONE;
list_add_tail(&pd->instance, &_usbpd);
+ INIT_LIST_HEAD(&pd->vdm_tx_queue);
+ INIT_LIST_HEAD(&pd->svid_handlers);
+
/* force read initial power_supply values */
psy_changed(&pd->psy_nb, PSY_EVENT_PROP_CHANGED, pd->usb_psy);
diff --git a/include/linux/usb/usbpd.h b/include/linux/usb/usbpd.h
new file mode 100644
index 000000000000..c2c1025feb8e
--- /dev/null
+++ b/include/linux/usb/usbpd.h
@@ -0,0 +1,156 @@
+/* Copyright (c) 2016, Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef __LINUX_USB_USBPD_H
+#define __LINUX_USB_USBPD_H
+
+#include <linux/list.h>
+
+struct usbpd;
+
+/* Standard IDs */
+#define USBPD_SID 0xff00
+
+/* Structured VDM Command Type */
+enum usbpd_svdm_cmd_type {
+ SVDM_CMD_TYPE_INITIATOR,
+ SVDM_CMD_TYPE_RESP_ACK,
+ SVDM_CMD_TYPE_RESP_NAK,
+ SVDM_CMD_TYPE_RESP_BUSY,
+};
+
+/* Structured VDM Commands */
+#define USBPD_SVDM_DISCOVER_IDENTITY 0x1
+#define USBPD_SVDM_DISCOVER_SVIDS 0x2
+#define USBPD_SVDM_DISCOVER_MODES 0x3
+#define USBPD_SVDM_ENTER_MODE 0x4
+#define USBPD_SVDM_EXIT_MODE 0x5
+#define USBPD_SVDM_ATTENTION 0x6
+
+/*
+ * Implemented by client
+ */
+struct usbpd_svid_handler {
+ u16 svid;
+
+ void (*connect)(struct usbpd_svid_handler *hdlr);
+ void (*disconnect)(struct usbpd_svid_handler *hdlr);
+
+ /* Unstructured VDM */
+ void (*vdm_received)(struct usbpd_svid_handler *hdlr, u32 vdm_hdr,
+ const u32 *vdos, int num_vdos);
+
+ /* Structured VDM */
+ void (*svdm_received)(struct usbpd_svid_handler *hdlr, u8 cmd,
+ enum usbpd_svdm_cmd_type cmd_type, const u32 *vdos,
+ int num_vdos);
+
+ struct list_head entry;
+};
+
+enum plug_orientation {
+ ORIENTATION_NONE,
+ ORIENTATION_CC1,
+ ORIENTATION_CC2,
+};
+
+#if IS_ENABLED(CONFIG_USB_PD_POLICY)
+/*
+ * Obtains an instance of usbpd from a DT phandle
+ */
+struct usbpd *devm_usbpd_get_by_phandle(struct device *dev,
+ const char *phandle);
+
+/*
+ * Called by client to handle specific SVID messages.
+ * Specify callback functions in the usbpd_svid_handler argument
+ */
+int usbpd_register_svid(struct usbpd *pd, struct usbpd_svid_handler *hdlr);
+
+void usbpd_unregister_svid(struct usbpd *pd, struct usbpd_svid_handler *hdlr);
+
+/*
+ * Transmit a VDM message.
+ */
+int usbpd_send_vdm(struct usbpd *pd, u32 vdm_hdr, const u32 *vdos,
+ int num_vdos);
+
+/*
+ * Transmit a Structured VDM message.
+ */
+int usbpd_send_svdm(struct usbpd *pd, u16 svid, u8 cmd,
+ enum usbpd_svdm_cmd_type cmd_type, int obj_pos,
+ const u32 *vdos, int num_vdos);
+
+/*
+ * Get current status of CC pin orientation.
+ *
+ * Return: ORIENTATION_CC1 or ORIENTATION_CC2 if attached,
+ * otherwise ORIENTATION_NONE if not attached
+ */
+enum plug_orientation usbpd_get_plug_orientation(struct usbpd *pd);
+#else
+static inline struct usbpd *devm_usbpd_get_by_phandle(struct device *dev,
+ const char *phandle)
+{
+ return ERR_PTR(-ENODEV);
+}
+
+static inline int usbpd_register_svid(struct usbpd *pd,
+ struct usbpd_svid_handler *hdlr)
+{
+ return -EINVAL;
+}
+
+static inline void usbpd_unregister_svid(struct usbpd *pd,
+ struct usbpd_svid_handler *hdlr)
+{
+}
+
+static inline int usbpd_send_vdm(struct usbpd *pd, u32 vdm_hdr, const u32 *vdos,
+ int num_vdos)
+{
+ return -EINVAL;
+}
+
+static inline int usbpd_send_svdm(struct usbpd *pd, u16 svid, u8 cmd,
+ enum usbpd_svdm_cmd_type cmd_type, int obj_pos,
+ const u32 *vdos, int num_vdos)
+{
+ return -EINVAL;
+}
+
+static inline enum plug_orientation usbpd_get_plug_orientation(struct usbpd *pd)
+{
+ return ORIENTATION_NONE;
+}
+#endif /* IS_ENABLED(CONFIG_USB_PD_POLICY) */
+
+/*
+ * Additional helpers for Enter/Exit Mode commands
+ */
+
+static inline int usbpd_enter_mode(struct usbpd *pd, u16 svid, int mode,
+ const u32 *vdo)
+{
+ return usbpd_send_svdm(pd, svid, USBPD_SVDM_ENTER_MODE,
+ SVDM_CMD_TYPE_INITIATOR, mode, vdo, vdo ? 1 : 0);
+}
+
+static inline int usbpd_exit_mode(struct usbpd *pd, u16 svid, int mode,
+ const u32 *vdo)
+{
+ return usbpd_send_svdm(pd, svid, USBPD_SVDM_EXIT_MODE,
+ SVDM_CMD_TYPE_INITIATOR, mode, vdo, vdo ? 1 : 0);
+}
+
+#endif /* __LINUX_USB_USBPD_H */