Merge "usb: pd: Add vendor defined message handling"
This commit is contained in:
commit
3fd60d9501
2 changed files with 628 additions and 8 deletions
|
@ -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);
|
||||
|
||||
|
|
156
include/linux/usb/usbpd.h
Normal file
156
include/linux/usb/usbpd.h
Normal file
|
@ -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 */
|
Loading…
Add table
Reference in a new issue