#include <linux/module.h>
#include <linux/delay.h>
#include <linux/slab.h>
-#include <linux/usb/typec.h>
+#include <linux/usb/typec_altmode.h>
#include "ucsi.h"
#include "trace.h"
*/
#define UCSI_SWAP_TIMEOUT_MS 5000
-enum ucsi_status {
- UCSI_IDLE = 0,
- UCSI_BUSY,
- UCSI_ERROR,
-};
-
-struct ucsi_connector {
- int num;
-
- struct ucsi *ucsi;
- struct work_struct work;
- struct completion complete;
-
- struct typec_port *port;
- struct typec_partner *partner;
-
- struct typec_capability typec_cap;
-
- struct ucsi_connector_status status;
- struct ucsi_connector_capability cap;
-};
-
-struct ucsi {
- struct device *dev;
- struct ucsi_ppm *ppm;
-
- enum ucsi_status status;
- struct completion complete;
- struct ucsi_capability cap;
- struct ucsi_connector *connector;
-
- struct work_struct work;
-
- /* PPM Communication lock */
- struct mutex ppm_lock;
-
- /* PPM communication flags */
- unsigned long flags;
-#define EVENT_PENDING 0
-#define COMMAND_PENDING 1
-#define ACK_PENDING 2
-};
-
static inline int ucsi_sync(struct ucsi *ucsi)
{
if (ucsi->ppm && ucsi->ppm->sync)
return ret;
}
+int ucsi_send_command(struct ucsi *ucsi, struct ucsi_control *ctrl,
+ void *retval, size_t size)
+{
+ int ret;
+
+ mutex_lock(&ucsi->ppm_lock);
+ ret = ucsi_run_command(ucsi, ctrl, retval, size);
+ mutex_unlock(&ucsi->ppm_lock);
+
+ return ret;
+}
+
/* -------------------------------------------------------------------------- */
+void ucsi_altmode_update_active(struct ucsi_connector *con)
+{
+ const struct typec_altmode *altmode = NULL;
+ struct ucsi_control ctrl;
+ int ret;
+ u8 cur;
+ int i;
+
+ UCSI_CMD_GET_CURRENT_CAM(ctrl, con->num);
+ ret = ucsi_run_command(con->ucsi, &ctrl, &cur, sizeof(cur));
+ if (ret < 0) {
+ if (con->ucsi->ppm->data->version > 0x0100) {
+ dev_err(con->ucsi->dev,
+ "GET_CURRENT_CAM command failed\n");
+ return;
+ }
+ cur = 0xff;
+ }
+
+ if (cur < UCSI_MAX_ALTMODES)
+ altmode = typec_altmode_get_partner(con->port_altmode[cur]);
+
+ for (i = 0; con->partner_altmode[i]; i++)
+ typec_altmode_update_active(con->partner_altmode[i],
+ con->partner_altmode[i] == altmode);
+}
+
+static u8 ucsi_altmode_next_mode(struct typec_altmode **alt, u16 svid)
+{
+ u8 mode = 1;
+ int i;
+
+ for (i = 0; alt[i]; i++)
+ if (alt[i]->svid == svid)
+ mode++;
+
+ return mode;
+}
+
+static int ucsi_next_altmode(struct typec_altmode **alt)
+{
+ int i = 0;
+
+ for (i = 0; i < UCSI_MAX_ALTMODES; i++)
+ if (!alt[i])
+ return i;
+
+ return -ENOENT;
+}
+
+static int ucsi_register_altmode(struct ucsi_connector *con,
+ struct typec_altmode_desc *desc,
+ u8 recipient)
+{
+ struct typec_altmode *alt;
+ int ret;
+ int i;
+
+ switch (recipient) {
+ case UCSI_RECIPIENT_CON:
+ i = ucsi_next_altmode(con->port_altmode);
+ if (i < 0) {
+ ret = i;
+ goto err;
+ }
+
+ desc->mode = ucsi_altmode_next_mode(con->port_altmode,
+ desc->svid);
+
+ alt = typec_port_register_altmode(con->port, desc);
+ if (IS_ERR(alt)) {
+ ret = PTR_ERR(alt);
+ goto err;
+ }
+
+ con->port_altmode[i] = alt;
+ break;
+ case UCSI_RECIPIENT_SOP:
+ i = ucsi_next_altmode(con->partner_altmode);
+ if (i < 0) {
+ ret = i;
+ goto err;
+ }
+
+ desc->mode = ucsi_altmode_next_mode(con->partner_altmode,
+ desc->svid);
+
+ alt = typec_partner_register_altmode(con->partner, desc);
+ if (IS_ERR(alt)) {
+ ret = PTR_ERR(alt);
+ goto err;
+ }
+
+ con->partner_altmode[i] = alt;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ trace_ucsi_register_altmode(recipient, alt);
+
+ return 0;
+
+err:
+ dev_err(con->ucsi->dev, "failed to registers svid 0x%04x mode %d\n",
+ desc->svid, desc->mode);
+
+ return ret;
+}
+
+static int ucsi_register_altmodes(struct ucsi_connector *con, u8 recipient)
+{
+ int max_altmodes = UCSI_MAX_ALTMODES;
+ struct typec_altmode_desc desc;
+ struct ucsi_altmode alt[2];
+ struct ucsi_control ctrl;
+ int num = 1;
+ int ret;
+ int len;
+ int j;
+ int i;
+
+ if (!(con->ucsi->cap.features & UCSI_CAP_ALT_MODE_DETAILS))
+ return 0;
+
+ if (recipient == UCSI_RECIPIENT_SOP && con->partner_altmode[0])
+ return 0;
+
+ if (recipient == UCSI_RECIPIENT_CON)
+ max_altmodes = con->ucsi->cap.num_alt_modes;
+
+ for (i = 0; i < max_altmodes;) {
+ memset(alt, 0, sizeof(alt));
+ UCSI_CMD_GET_ALTERNATE_MODES(ctrl, recipient, con->num, i, 1);
+ len = ucsi_run_command(con->ucsi, &ctrl, alt, sizeof(alt));
+ if (len <= 0)
+ return len;
+
+ /*
+ * This code is requesting one alt mode at a time, but some PPMs
+ * may still return two. If that happens both alt modes need be
+ * registered and the offset for the next alt mode has to be
+ * incremented.
+ */
+ num = len / sizeof(alt[0]);
+ i += num;
+
+ for (j = 0; j < num; j++) {
+ if (!alt[j].svid)
+ return 0;
+
+ memset(&desc, 0, sizeof(desc));
+ desc.vdo = alt[j].mid;
+ desc.svid = alt[j].svid;
+ desc.roles = TYPEC_PORT_DRD;
+
+ ret = ucsi_register_altmode(con, &desc, recipient);
+ if (ret)
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static void ucsi_unregister_altmodes(struct ucsi_connector *con, u8 recipient)
+{
+ struct typec_altmode **adev;
+ int i = 0;
+
+ switch (recipient) {
+ case UCSI_RECIPIENT_CON:
+ adev = con->port_altmode;
+ break;
+ case UCSI_RECIPIENT_SOP:
+ adev = con->partner_altmode;
+ break;
+ default:
+ return;
+ }
+
+ while (adev[i]) {
+ typec_unregister_altmode(adev[i]);
+ adev[i++] = NULL;
+ }
+}
+
static void ucsi_pwr_opmode_change(struct ucsi_connector *con)
{
switch (con->status.pwr_op_mode) {
if (!con->partner)
return;
+ ucsi_unregister_altmodes(con, UCSI_RECIPIENT_SOP);
typec_unregister_partner(con->partner);
con->partner = NULL;
}
+static void ucsi_partner_change(struct ucsi_connector *con)
+{
+ int ret;
+
+ if (!con->partner)
+ return;
+
+ switch (con->status.partner_type) {
+ case UCSI_CONSTAT_PARTNER_TYPE_UFP:
+ typec_set_data_role(con->port, TYPEC_HOST);
+ break;
+ case UCSI_CONSTAT_PARTNER_TYPE_DFP:
+ typec_set_data_role(con->port, TYPEC_DEVICE);
+ break;
+ default:
+ break;
+ }
+
+ /* Complete pending data role swap */
+ if (!completion_done(&con->complete))
+ complete(&con->complete);
+
+ /* Can't rely on Partner Flags field. Always checking the alt modes. */
+ ret = ucsi_register_altmodes(con, UCSI_RECIPIENT_SOP);
+ if (ret)
+ dev_err(con->ucsi->dev,
+ "con%d: failed to register partner alternate modes\n",
+ con->num);
+ else
+ ucsi_altmode_update_active(con);
+}
+
static void ucsi_connector_change(struct work_struct *work)
{
struct ucsi_connector *con = container_of(work, struct ucsi_connector,
struct ucsi_control ctrl;
int ret;
- mutex_lock(&ucsi->ppm_lock);
+ mutex_lock(&con->lock);
UCSI_CMD_GET_CONNECTOR_STATUS(ctrl, con->num);
- ret = ucsi_run_command(ucsi, &ctrl, &con->status, sizeof(con->status));
+ ret = ucsi_send_command(ucsi, &ctrl, &con->status, sizeof(con->status));
if (ret < 0) {
dev_err(ucsi->dev, "%s: GET_CONNECTOR_STATUS failed (%d)\n",
__func__, ret);
complete(&con->complete);
}
- if (con->status.change & UCSI_CONSTAT_PARTNER_CHANGE) {
- switch (con->status.partner_type) {
- case UCSI_CONSTAT_PARTNER_TYPE_UFP:
- typec_set_data_role(con->port, TYPEC_HOST);
- break;
- case UCSI_CONSTAT_PARTNER_TYPE_DFP:
- typec_set_data_role(con->port, TYPEC_DEVICE);
- break;
- default:
- break;
- }
-
- /* Complete pending data role swap */
- if (!completion_done(&con->complete))
- complete(&con->complete);
- }
-
if (con->status.change & UCSI_CONSTAT_CONNECT_CHANGE) {
typec_set_pwr_role(con->port, con->status.pwr_dir);
ucsi_unregister_partner(con);
}
+ if (con->status.change & UCSI_CONSTAT_CAM_CHANGE) {
+ /*
+ * We don't need to know the currently supported alt modes here.
+ * Running GET_CAM_SUPPORTED command just to make sure the PPM
+ * does not get stuck in case it assumes we do so.
+ */
+ UCSI_CMD_GET_CAM_SUPPORTED(ctrl, con->num);
+ ucsi_run_command(con->ucsi, &ctrl, NULL, 0);
+ }
+
+ if (con->status.change & UCSI_CONSTAT_PARTNER_CHANGE)
+ ucsi_partner_change(con);
+
ret = ucsi_ack(ucsi, UCSI_ACK_EVENT);
if (ret)
dev_err(ucsi->dev, "%s: ACK failed (%d)", __func__, ret);
out_unlock:
clear_bit(EVENT_PENDING, &ucsi->flags);
- mutex_unlock(&ucsi->ppm_lock);
+ mutex_unlock(&con->lock);
}
/**
UCSI_CMD_CONNECTOR_RESET(ctrl, con, hard);
- return ucsi_run_command(con->ucsi, &ctrl, NULL, 0);
+ return ucsi_send_command(con->ucsi, &ctrl, NULL, 0);
}
static int ucsi_reset_ppm(struct ucsi *ucsi)
{
int ret;
- ret = ucsi_run_command(con->ucsi, ctrl, NULL, 0);
+ ret = ucsi_send_command(con->ucsi, ctrl, NULL, 0);
if (ret == -ETIMEDOUT) {
struct ucsi_control c;
/* PPM most likely stopped responding. Resetting everything. */
+ mutex_lock(&con->ucsi->ppm_lock);
ucsi_reset_ppm(con->ucsi);
+ mutex_unlock(&con->ucsi->ppm_lock);
UCSI_CMD_SET_NTFY_ENABLE(c, UCSI_ENABLE_NTFY_ALL);
- ucsi_run_command(con->ucsi, &c, NULL, 0);
+ ucsi_send_command(con->ucsi, &c, NULL, 0);
ucsi_reset_connector(con, true);
}
struct ucsi_control ctrl;
int ret = 0;
- if (!con->partner)
- return -ENOTCONN;
+ mutex_lock(&con->lock);
- mutex_lock(&con->ucsi->ppm_lock);
+ if (!con->partner) {
+ ret = -ENOTCONN;
+ goto out_unlock;
+ }
if ((con->status.partner_type == UCSI_CONSTAT_PARTNER_TYPE_DFP &&
role == TYPEC_DEVICE) ||
if (ret < 0)
goto out_unlock;
- mutex_unlock(&con->ucsi->ppm_lock);
-
if (!wait_for_completion_timeout(&con->complete,
msecs_to_jiffies(UCSI_SWAP_TIMEOUT_MS)))
- return -ETIMEDOUT;
-
- return 0;
+ ret = -ETIMEDOUT;
out_unlock:
- mutex_unlock(&con->ucsi->ppm_lock);
+ mutex_unlock(&con->lock);
- return ret;
+ return ret < 0 ? ret : 0;
}
static int
struct ucsi_control ctrl;
int ret = 0;
- if (!con->partner)
- return -ENOTCONN;
+ mutex_lock(&con->lock);
- mutex_lock(&con->ucsi->ppm_lock);
+ if (!con->partner) {
+ ret = -ENOTCONN;
+ goto out_unlock;
+ }
if (con->status.pwr_dir == role)
goto out_unlock;
if (ret < 0)
goto out_unlock;
- mutex_unlock(&con->ucsi->ppm_lock);
-
if (!wait_for_completion_timeout(&con->complete,
- msecs_to_jiffies(UCSI_SWAP_TIMEOUT_MS)))
- return -ETIMEDOUT;
-
- mutex_lock(&con->ucsi->ppm_lock);
+ msecs_to_jiffies(UCSI_SWAP_TIMEOUT_MS))) {
+ ret = -ETIMEDOUT;
+ goto out_unlock;
+ }
/* Something has gone wrong while swapping the role */
if (con->status.pwr_op_mode != UCSI_CONSTAT_PWR_OPMODE_PD) {
}
out_unlock:
- mutex_unlock(&con->ucsi->ppm_lock);
+ mutex_unlock(&con->lock);
return ret;
}
INIT_WORK(&con->work, ucsi_connector_change);
init_completion(&con->complete);
+ mutex_init(&con->lock);
con->num = index + 1;
con->ucsi = ucsi;
if (IS_ERR(con->port))
return PTR_ERR(con->port);
+ /* Alternate modes */
+ ret = ucsi_register_altmodes(con, UCSI_RECIPIENT_CON);
+ if (ret)
+ dev_err(ucsi->dev, "con%d: failed to register alt modes\n",
+ con->num);
+
/* Get the status */
UCSI_CMD_GET_CONNECTOR_STATUS(ctrl, con->num);
ret = ucsi_run_command(ucsi, &ctrl, &con->status, sizeof(con->status));
if (con->status.connected)
ucsi_register_partner(con);
+ if (con->partner) {
+ ret = ucsi_register_altmodes(con, UCSI_RECIPIENT_SOP);
+ if (ret)
+ dev_err(ucsi->dev,
+ "con%d: failed to register alternate modes\n",
+ con->num);
+ else
+ ucsi_altmode_update_active(con);
+ }
+
trace_ucsi_register_port(con->num, &con->status);
return 0;
err_unregister:
for (con = ucsi->connector; con->port; con++) {
ucsi_unregister_partner(con);
+ ucsi_unregister_altmodes(con, UCSI_RECIPIENT_CON);
typec_unregister_port(con->port);
con->port = NULL;
}
/* Make sure that we are not in the middle of driver initialization */
cancel_work_sync(&ucsi->work);
- mutex_lock(&ucsi->ppm_lock);
-
/* Disable everything except command complete notification */
UCSI_CMD_SET_NTFY_ENABLE(ctrl, UCSI_ENABLE_NTFY_CMD_COMPLETE)
- ucsi_run_command(ucsi, &ctrl, NULL, 0);
-
- mutex_unlock(&ucsi->ppm_lock);
+ ucsi_send_command(ucsi, &ctrl, NULL, 0);
for (i = 0; i < ucsi->cap.num_connectors; i++) {
cancel_work_sync(&ucsi->connector[i].work);
ucsi_unregister_partner(&ucsi->connector[i]);
+ ucsi_unregister_altmodes(&ucsi->connector[i],
+ UCSI_RECIPIENT_CON);
typec_unregister_port(ucsi->connector[i].port);
}
#include <linux/bitops.h>
#include <linux/device.h>
#include <linux/types.h>
+#include <linux/usb/typec.h>
/* -------------------------------------------------------------------------- */
u16:6; /* reserved */
} __packed;
+/* Get Alternate Modes Command structure */
+struct ucsi_altmode_cmd {
+ u8 cmd;
+ u8 length;
+ u8 recipient;
+#define UCSI_RECIPIENT_CON 0
+#define UCSI_RECIPIENT_SOP 1
+#define UCSI_RECIPIENT_SOP_P 2
+#define UCSI_RECIPIENT_SOP_PP 3
+ u8 con_num;
+ u8 offset;
+ u8 num_altmodes;
+} __packed;
+
struct ucsi_control {
union {
u64 raw_cmd;
struct ucsi_uor_cmd uor;
struct ucsi_ack_cmd ack;
struct ucsi_con_rst con_rst;
+ struct ucsi_altmode_cmd alt;
};
};
(_ctrl_).cmd.data = _con_; \
}
+/* Helper for preparing ucsi_control for GET_ALTERNATE_MODES command. */
+#define UCSI_CMD_GET_ALTERNATE_MODES(_ctrl_, _r_, _con_num_, _o_, _num_)\
+{ \
+ __UCSI_CMD((_ctrl_), UCSI_GET_ALTERNATE_MODES) \
+ _ctrl_.alt.recipient = (_r_); \
+ _ctrl_.alt.con_num = (_con_num_); \
+ _ctrl_.alt.offset = (_o_); \
+ _ctrl_.alt.num_altmodes = (_num_) - 1; \
+}
+
+/* Helper for preparing ucsi_control for GET_CAM_SUPPORTED command. */
+#define UCSI_CMD_GET_CAM_SUPPORTED(_ctrl_, _con_) \
+{ \
+ __UCSI_CMD((_ctrl_), UCSI_GET_CAM_SUPPORTED) \
+ _ctrl_.cmd.data = (_con_); \
+}
+
+/* Helper for preparing ucsi_control for GET_CAM_SUPPORTED command. */
+#define UCSI_CMD_GET_CURRENT_CAM(_ctrl_, _con_) \
+{ \
+ __UCSI_CMD((_ctrl_), UCSI_GET_CURRENT_CAM) \
+ _ctrl_.cmd.data = (_con_); \
+}
+
/* Helper for preparing ucsi_control for GET_CONNECTOR_STATUS command. */
#define UCSI_CMD_GET_CONNECTOR_STATUS(_ctrl_, _con_) \
{ \
void ucsi_unregister_ppm(struct ucsi *ucsi);
void ucsi_notify(struct ucsi *ucsi);
+/* -------------------------------------------------------------------------- */
+
+enum ucsi_status {
+ UCSI_IDLE = 0,
+ UCSI_BUSY,
+ UCSI_ERROR,
+};
+
+struct ucsi {
+ struct device *dev;
+ struct ucsi_ppm *ppm;
+
+ enum ucsi_status status;
+ struct completion complete;
+ struct ucsi_capability cap;
+ struct ucsi_connector *connector;
+
+ struct work_struct work;
+
+ /* PPM Communication lock */
+ struct mutex ppm_lock;
+
+ /* PPM communication flags */
+ unsigned long flags;
+#define EVENT_PENDING 0
+#define COMMAND_PENDING 1
+#define ACK_PENDING 2
+};
+
+#define UCSI_MAX_SVID 5
+#define UCSI_MAX_ALTMODES (UCSI_MAX_SVID * 6)
+
+struct ucsi_connector {
+ int num;
+
+ struct ucsi *ucsi;
+ struct mutex lock; /* port lock */
+ struct work_struct work;
+ struct completion complete;
+
+ struct typec_port *port;
+ struct typec_partner *partner;
+
+ struct typec_altmode *port_altmode[UCSI_MAX_ALTMODES];
+ struct typec_altmode *partner_altmode[UCSI_MAX_ALTMODES];
+
+ struct typec_capability typec_cap;
+
+ struct ucsi_connector_status status;
+ struct ucsi_connector_capability cap;
+};
+
+int ucsi_send_command(struct ucsi *ucsi, struct ucsi_control *ctrl,
+ void *retval, size_t size);
+
+void ucsi_altmode_update_active(struct ucsi_connector *con);
+
#endif /* __DRIVER_USB_TYPEC_UCSI_H */