Skip to content

Commit

Permalink
usb: typec: tcpm: Support for Alternate Modes
Browse files Browse the repository at this point in the history
This adds more complete handling of VDMs and registration of
partner alternate modes, and introduces callbacks for
alternate mode operations.

Only DFP role is supported for now.

Signed-off-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Tested-by: Hans de Goede <hdegoede@redhat.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
  • Loading branch information
Heikki Krogerus authored and gregkh committed Jul 2, 2018
1 parent 49cbb33 commit e9576fe
Show file tree
Hide file tree
Showing 2 changed files with 135 additions and 39 deletions.
165 changes: 135 additions & 30 deletions drivers/usb/typec/tcpm.c
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
#include <linux/usb/pd_vdo.h>
#include <linux/usb/role.h>
#include <linux/usb/tcpm.h>
#include <linux/usb/typec.h>
#include <linux/usb/typec_altmode.h>
#include <linux/workqueue.h>

#define FOREACH_STATE(S) \
Expand Down Expand Up @@ -169,13 +169,14 @@ enum pd_msg_request {
/* Alternate mode support */

#define SVID_DISCOVERY_MAX 16
#define ALTMODE_DISCOVERY_MAX (SVID_DISCOVERY_MAX * MODE_DISCOVERY_MAX)

struct pd_mode_data {
int svid_index; /* current SVID index */
int nsvids;
u16 svids[SVID_DISCOVERY_MAX];
int altmodes; /* number of alternate modes */
struct typec_altmode_desc altmode_desc[SVID_DISCOVERY_MAX];
struct typec_altmode_desc altmode_desc[ALTMODE_DISCOVERY_MAX];
};

struct pd_pps_data {
Expand Down Expand Up @@ -310,8 +311,8 @@ struct tcpm_port {

/* Alternate mode data */
struct pd_mode_data mode_data;
struct typec_altmode *partner_altmode[SVID_DISCOVERY_MAX * 6];
struct typec_altmode *port_altmode[SVID_DISCOVERY_MAX * 6];
struct typec_altmode *partner_altmode[ALTMODE_DISCOVERY_MAX];
struct typec_altmode *port_altmode[ALTMODE_DISCOVERY_MAX];

/* Deadline in jiffies to exit src_try_wait state */
unsigned long max_wait;
Expand Down Expand Up @@ -641,14 +642,14 @@ void tcpm_pd_transmit_complete(struct tcpm_port *port,
}
EXPORT_SYMBOL_GPL(tcpm_pd_transmit_complete);

static int tcpm_mux_set(struct tcpm_port *port, enum tcpc_mux_mode mode,
static int tcpm_mux_set(struct tcpm_port *port, int state,
enum usb_role usb_role,
enum typec_orientation orientation)
{
int ret;

tcpm_log(port, "Requesting mux mode %d, usb-role %d, orientation %d",
mode, usb_role, orientation);
tcpm_log(port, "Requesting mux state %d, usb-role %d, orientation %d",
state, usb_role, orientation);

ret = typec_set_orientation(port->typec_port, orientation);
if (ret)
Expand All @@ -660,7 +661,7 @@ static int tcpm_mux_set(struct tcpm_port *port, enum tcpc_mux_mode mode,
return ret;
}

return typec_set_mode(port->typec_port, mode);
return typec_set_mode(port->typec_port, state);
}

static int tcpm_set_polarity(struct tcpm_port *port,
Expand Down Expand Up @@ -787,7 +788,7 @@ static int tcpm_set_roles(struct tcpm_port *port, bool attached,
else
usb_role = USB_ROLE_DEVICE;

ret = tcpm_mux_set(port, TYPEC_MUX_USB, usb_role, orientation);
ret = tcpm_mux_set(port, TYPEC_STATE_USB, usb_role, orientation);
if (ret < 0)
return ret;

Expand Down Expand Up @@ -1014,36 +1015,57 @@ static void svdm_consume_modes(struct tcpm_port *port, const __le32 *payload,
pmdata->altmodes, paltmode->svid,
paltmode->mode, paltmode->vdo);

port->partner_altmode[pmdata->altmodes] =
typec_partner_register_altmode(port->partner, paltmode);
if (!port->partner_altmode[pmdata->altmodes]) {
tcpm_log(port,
"Failed to register modes for SVID 0x%04x",
paltmode->svid);
return;
}
pmdata->altmodes++;
}
}

static void tcpm_register_partner_altmodes(struct tcpm_port *port)
{
struct pd_mode_data *modep = &port->mode_data;
struct typec_altmode *altmode;
int i;

for (i = 0; i < modep->altmodes; i++) {
altmode = typec_partner_register_altmode(port->partner,
&modep->altmode_desc[i]);
if (!altmode)
tcpm_log(port, "Failed to register partner SVID 0x%04x",
modep->altmode_desc[i].svid);
port->partner_altmode[i] = altmode;
}
}

#define supports_modal(port) PD_IDH_MODAL_SUPP((port)->partner_ident.id_header)

static int tcpm_pd_svdm(struct tcpm_port *port, const __le32 *payload, int cnt,
u32 *response)
{
u32 p0 = le32_to_cpu(payload[0]);
int cmd_type = PD_VDO_CMDT(p0);
int cmd = PD_VDO_CMD(p0);
struct typec_altmode *adev;
struct typec_altmode *pdev;
struct pd_mode_data *modep;
u32 p[PD_MAX_PAYLOAD];
int rlen = 0;
u16 svid;
int cmd_type;
int cmd;
int i;

for (i = 0; i < cnt; i++)
p[i] = le32_to_cpu(payload[i]);

cmd_type = PD_VDO_CMDT(p[0]);
cmd = PD_VDO_CMD(p[0]);

tcpm_log(port, "Rx VDM cmd 0x%x type %d cmd %d len %d",
p0, cmd_type, cmd, cnt);
p[0], cmd_type, cmd, cnt);

modep = &port->mode_data;

adev = typec_match_altmode(port->port_altmode, ALTMODE_DISCOVERY_MAX,
PD_VDO_VID(p[0]), PD_VDO_OPOS(p[0]));

pdev = typec_match_altmode(port->partner_altmode, ALTMODE_DISCOVERY_MAX,
PD_VDO_VID(p[0]), PD_VDO_OPOS(p[0]));

switch (cmd_type) {
case CMDT_INIT:
switch (cmd) {
Expand All @@ -1065,17 +1087,19 @@ static int tcpm_pd_svdm(struct tcpm_port *port, const __le32 *payload, int cnt,
case CMD_EXIT_MODE:
break;
case CMD_ATTENTION:
break;
/* Attention command does not have response */
typec_altmode_attention(adev, p[1]);
return 0;
default:
break;
}
if (rlen >= 1) {
response[0] = p0 | VDO_CMDT(CMDT_RSP_ACK);
response[0] = p[0] | VDO_CMDT(CMDT_RSP_ACK);
} else if (rlen == 0) {
response[0] = p0 | VDO_CMDT(CMDT_RSP_NAK);
response[0] = p[0] | VDO_CMDT(CMDT_RSP_NAK);
rlen = 1;
} else {
response[0] = p0 | VDO_CMDT(CMDT_RSP_BUSY);
response[0] = p[0] | VDO_CMDT(CMDT_RSP_BUSY);
rlen = 1;
}
break;
Expand Down Expand Up @@ -1108,14 +1132,39 @@ static int tcpm_pd_svdm(struct tcpm_port *port, const __le32 *payload, int cnt,
svdm_consume_modes(port, payload, cnt);
modep->svid_index++;
if (modep->svid_index < modep->nsvids) {
svid = modep->svids[modep->svid_index];
u16 svid = modep->svids[modep->svid_index];
response[0] = VDO(svid, 1, CMD_DISCOVER_MODES);
rlen = 1;
} else {
/* enter alternate mode if/when implemented */
tcpm_register_partner_altmodes(port);
}
break;
case CMD_ENTER_MODE:
typec_altmode_update_active(pdev, true);

if (typec_altmode_vdm(adev, p[0], &p[1], cnt)) {
response[0] = VDO(adev->svid, 1, CMD_EXIT_MODE);
response[0] |= VDO_OPOS(adev->mode);
return 1;
}
return 0;
case CMD_EXIT_MODE:
typec_altmode_update_active(pdev, false);

/* Back to USB Operation */
WARN_ON(typec_altmode_notify(adev, TYPEC_STATE_USB,
NULL));
break;
default:
break;
}
break;
case CMDT_RSP_NAK:
switch (cmd) {
case CMD_ENTER_MODE:
/* Back to USB Operation */
WARN_ON(typec_altmode_notify(adev, TYPEC_STATE_USB,
NULL));
break;
default:
break;
Expand All @@ -1125,6 +1174,9 @@ static int tcpm_pd_svdm(struct tcpm_port *port, const __le32 *payload, int cnt,
break;
}

/* Informing the alternate mode drivers about everything */
typec_altmode_vdm(adev, p[0], &p[1], cnt);

return rlen;
}

Expand Down Expand Up @@ -1408,6 +1460,57 @@ static int tcpm_validate_caps(struct tcpm_port *port, const u32 *pdo,
return 0;
}

static int tcpm_altmode_enter(struct typec_altmode *altmode)
{
struct tcpm_port *port = typec_altmode_get_drvdata(altmode);
u32 header;

mutex_lock(&port->lock);
header = VDO(altmode->svid, 1, CMD_ENTER_MODE);
header |= VDO_OPOS(altmode->mode);

tcpm_queue_vdm(port, header, NULL, 0);
mod_delayed_work(port->wq, &port->vdm_state_machine, 0);
mutex_unlock(&port->lock);

return 0;
}

static int tcpm_altmode_exit(struct typec_altmode *altmode)
{
struct tcpm_port *port = typec_altmode_get_drvdata(altmode);
u32 header;

mutex_lock(&port->lock);
header = VDO(altmode->svid, 1, CMD_EXIT_MODE);
header |= VDO_OPOS(altmode->mode);

tcpm_queue_vdm(port, header, NULL, 0);
mod_delayed_work(port->wq, &port->vdm_state_machine, 0);
mutex_unlock(&port->lock);

return 0;
}

static int tcpm_altmode_vdm(struct typec_altmode *altmode,
u32 header, const u32 *data, int count)
{
struct tcpm_port *port = typec_altmode_get_drvdata(altmode);

mutex_lock(&port->lock);
tcpm_queue_vdm(port, header, data, count - 1);
mod_delayed_work(port->wq, &port->vdm_state_machine, 0);
mutex_unlock(&port->lock);

return 0;
}

static const struct typec_altmode_ops tcpm_altmode_ops = {
.enter = tcpm_altmode_enter,
.exit = tcpm_altmode_exit,
.vdm = tcpm_altmode_vdm,
};

/*
* PD (data, control) command handling functions
*/
Expand Down Expand Up @@ -2539,7 +2642,7 @@ static int tcpm_src_attach(struct tcpm_port *port)
out_disable_pd:
port->tcpc->set_pd_rx(port->tcpc, false);
out_disable_mux:
tcpm_mux_set(port, TYPEC_MUX_NONE, USB_ROLE_NONE,
tcpm_mux_set(port, TYPEC_STATE_SAFE, USB_ROLE_NONE,
TYPEC_ORIENTATION_NONE);
return ret;
}
Expand Down Expand Up @@ -2585,7 +2688,7 @@ static void tcpm_reset_port(struct tcpm_port *port)
tcpm_init_vconn(port);
tcpm_set_current_limit(port, 0, 0);
tcpm_set_polarity(port, TYPEC_POLARITY_CC1);
tcpm_mux_set(port, TYPEC_MUX_NONE, USB_ROLE_NONE,
tcpm_mux_set(port, TYPEC_STATE_SAFE, USB_ROLE_NONE,
TYPEC_ORIENTATION_NONE);
tcpm_set_attached_state(port, false);
port->try_src_count = 0;
Expand Down Expand Up @@ -4706,6 +4809,8 @@ struct tcpm_port *tcpm_register_port(struct device *dev, struct tcpc_dev *tcpc)
dev_name(dev), paltmode->svid);
break;
}
typec_altmode_set_drvdata(alt, port);
alt->ops = &tcpm_altmode_ops;
port->port_altmode[i] = alt;
i++;
paltmode++;
Expand Down
9 changes: 0 additions & 9 deletions include/linux/usb/tcpm.h
Original file line number Diff line number Diff line change
Expand Up @@ -98,15 +98,6 @@ struct tcpc_config {
#define TCPC_MUX_DP_ENABLED BIT(1) /* DP enabled */
#define TCPC_MUX_POLARITY_INVERTED BIT(2) /* Polarity inverted */

/* Mux modes, decoded to attributes */
enum tcpc_mux_mode {
TYPEC_MUX_NONE = 0, /* Open switch */
TYPEC_MUX_USB = TCPC_MUX_USB_ENABLED, /* USB only */
TYPEC_MUX_DP = TCPC_MUX_DP_ENABLED, /* DP only */
TYPEC_MUX_DOCK = TCPC_MUX_USB_ENABLED | /* Both USB and DP */
TCPC_MUX_DP_ENABLED,
};

/**
* struct tcpc_dev - Port configuration and callback functions
* @config: Pointer to port configuration
Expand Down

0 comments on commit e9576fe

Please sign in to comment.