summaryrefslogtreecommitdiffstats
path: root/drivers/usb/typec/ucsi/ucsi.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/typec/ucsi/ucsi.c')
-rw-r--r--drivers/usb/typec/ucsi/ucsi.c232
1 files changed, 124 insertions, 108 deletions
diff --git a/drivers/usb/typec/ucsi/ucsi.c b/drivers/usb/typec/ucsi/ucsi.c
index bd6ae92aa3..2cc7aedd49 100644
--- a/drivers/usb/typec/ucsi/ucsi.c
+++ b/drivers/usb/typec/ucsi/ucsi.c
@@ -49,22 +49,16 @@ static int ucsi_read_message_in(struct ucsi *ucsi, void *buf,
return ucsi->ops->read(ucsi, UCSI_MESSAGE_IN, buf, buf_size);
}
-static int ucsi_acknowledge_command(struct ucsi *ucsi)
+static int ucsi_acknowledge(struct ucsi *ucsi, bool conn_ack)
{
u64 ctrl;
ctrl = UCSI_ACK_CC_CI;
ctrl |= UCSI_ACK_COMMAND_COMPLETE;
-
- return ucsi->ops->sync_write(ucsi, UCSI_CONTROL, &ctrl, sizeof(ctrl));
-}
-
-static int ucsi_acknowledge_connector_change(struct ucsi *ucsi)
-{
- u64 ctrl;
-
- ctrl = UCSI_ACK_CC_CI;
- ctrl |= UCSI_ACK_CONNECTOR_CHANGE;
+ if (conn_ack) {
+ clear_bit(EVENT_PENDING, &ucsi->flags);
+ ctrl |= UCSI_ACK_CONNECTOR_CHANGE;
+ }
return ucsi->ops->sync_write(ucsi, UCSI_CONTROL, &ctrl, sizeof(ctrl));
}
@@ -77,7 +71,7 @@ static int ucsi_read_error(struct ucsi *ucsi)
int ret;
/* Acknowledge the command that failed */
- ret = ucsi_acknowledge_command(ucsi);
+ ret = ucsi_acknowledge(ucsi, false);
if (ret)
return ret;
@@ -89,7 +83,7 @@ static int ucsi_read_error(struct ucsi *ucsi)
if (ret)
return ret;
- ret = ucsi_acknowledge_command(ucsi);
+ ret = ucsi_acknowledge(ucsi, false);
if (ret)
return ret;
@@ -152,28 +146,33 @@ static int ucsi_exec_command(struct ucsi *ucsi, u64 cmd)
return -EIO;
if (cci & UCSI_CCI_NOT_SUPPORTED) {
- if (ucsi_acknowledge_command(ucsi) < 0)
+ if (ucsi_acknowledge(ucsi, false) < 0)
dev_err(ucsi->dev,
"ACK of unsupported command failed\n");
return -EOPNOTSUPP;
}
if (cci & UCSI_CCI_ERROR) {
- if (cmd == UCSI_GET_ERROR_STATUS)
+ if (cmd == UCSI_GET_ERROR_STATUS) {
+ ret = ucsi_acknowledge(ucsi, false);
+ if (ret)
+ return ret;
+
return -EIO;
+ }
return ucsi_read_error(ucsi);
}
if (cmd == UCSI_CANCEL && cci & UCSI_CCI_CANCEL_COMPLETE) {
- ret = ucsi_acknowledge_command(ucsi);
+ ret = ucsi_acknowledge(ucsi, false);
return ret ? ret : -EBUSY;
}
return UCSI_CCI_LENGTH(cci);
}
-int ucsi_send_command(struct ucsi *ucsi, u64 command,
- void *data, size_t size)
+static int ucsi_send_command_common(struct ucsi *ucsi, u64 command,
+ void *data, size_t size, bool conn_ack)
{
u8 length;
int ret;
@@ -192,7 +191,7 @@ int ucsi_send_command(struct ucsi *ucsi, u64 command,
goto out;
}
- ret = ucsi_acknowledge_command(ucsi);
+ ret = ucsi_acknowledge(ucsi, conn_ack);
if (ret)
goto out;
@@ -201,6 +200,12 @@ out:
mutex_unlock(&ucsi->ppm_lock);
return ret;
}
+
+int ucsi_send_command(struct ucsi *ucsi, u64 command,
+ void *data, size_t size)
+{
+ return ucsi_send_command_common(ucsi, command, data, size, false);
+}
EXPORT_SYMBOL_GPL(ucsi_send_command);
/* -------------------------------------------------------------------------- */
@@ -619,7 +624,10 @@ static int ucsi_read_pdos(struct ucsi_connector *con,
u64 command;
int ret;
- if (ucsi->quirks & UCSI_NO_PARTNER_PDOS)
+ if (is_partner &&
+ ucsi->quirks & UCSI_NO_PARTNER_PDOS &&
+ ((con->status.flags & UCSI_CONSTAT_PWR_DIR) ||
+ !is_source(role)))
return 0;
command = UCSI_COMMAND(UCSI_GET_PDOS) | UCSI_CONNECTOR_NUMBER(con->num);
@@ -674,6 +682,26 @@ static int ucsi_get_src_pdos(struct ucsi_connector *con)
return ret;
}
+static struct usb_power_delivery_capabilities *ucsi_get_pd_caps(struct ucsi_connector *con,
+ enum typec_role role,
+ bool is_partner)
+{
+ struct usb_power_delivery_capabilities_desc pd_caps;
+ int ret;
+
+ ret = ucsi_get_pdos(con, role, is_partner, pd_caps.pdo);
+ if (ret <= 0)
+ return ERR_PTR(ret);
+
+ if (ret < PDO_MAX_OBJECTS)
+ pd_caps.pdo[ret] = 0;
+
+ pd_caps.role = role;
+
+ return usb_power_delivery_register_capabilities(is_partner ? con->partner_pd : con->pd,
+ &pd_caps);
+}
+
static int ucsi_read_identity(struct ucsi_connector *con, u8 recipient,
u8 offset, u8 bytes, void *resp)
{
@@ -798,60 +826,53 @@ static int ucsi_check_altmodes(struct ucsi_connector *con)
return ret;
}
+static void ucsi_register_device_pdos(struct ucsi_connector *con)
+{
+ struct ucsi *ucsi = con->ucsi;
+ struct usb_power_delivery_desc desc = { ucsi->cap.pd_version };
+ struct usb_power_delivery_capabilities *pd_cap;
+
+ if (con->pd)
+ return;
+
+ con->pd = usb_power_delivery_register(ucsi->dev, &desc);
+
+ pd_cap = ucsi_get_pd_caps(con, TYPEC_SOURCE, false);
+ if (!IS_ERR(pd_cap))
+ con->port_source_caps = pd_cap;
+
+ pd_cap = ucsi_get_pd_caps(con, TYPEC_SINK, false);
+ if (!IS_ERR(pd_cap))
+ con->port_sink_caps = pd_cap;
+
+ typec_port_set_usb_power_delivery(con->port, con->pd);
+}
+
static int ucsi_register_partner_pdos(struct ucsi_connector *con)
{
struct usb_power_delivery_desc desc = { con->ucsi->cap.pd_version };
- struct usb_power_delivery_capabilities_desc caps;
struct usb_power_delivery_capabilities *cap;
- int ret;
if (con->partner_pd)
return 0;
- con->partner_pd = usb_power_delivery_register(NULL, &desc);
+ con->partner_pd = typec_partner_usb_power_delivery_register(con->partner, &desc);
if (IS_ERR(con->partner_pd))
return PTR_ERR(con->partner_pd);
- ret = ucsi_get_pdos(con, TYPEC_SOURCE, 1, caps.pdo);
- if (ret > 0) {
- if (ret < PDO_MAX_OBJECTS)
- caps.pdo[ret] = 0;
-
- caps.role = TYPEC_SOURCE;
- cap = usb_power_delivery_register_capabilities(con->partner_pd, &caps);
- if (IS_ERR(cap))
- return PTR_ERR(cap);
+ cap = ucsi_get_pd_caps(con, TYPEC_SOURCE, true);
+ if (IS_ERR(cap))
+ return PTR_ERR(cap);
- con->partner_source_caps = cap;
+ con->partner_source_caps = cap;
- ret = typec_partner_set_usb_power_delivery(con->partner, con->partner_pd);
- if (ret) {
- usb_power_delivery_unregister_capabilities(con->partner_source_caps);
- return ret;
- }
- }
+ cap = ucsi_get_pd_caps(con, TYPEC_SINK, true);
+ if (IS_ERR(cap))
+ return PTR_ERR(cap);
- ret = ucsi_get_pdos(con, TYPEC_SINK, 1, caps.pdo);
- if (ret > 0) {
- if (ret < PDO_MAX_OBJECTS)
- caps.pdo[ret] = 0;
+ con->partner_sink_caps = cap;
- caps.role = TYPEC_SINK;
-
- cap = usb_power_delivery_register_capabilities(con->partner_pd, &caps);
- if (IS_ERR(cap))
- return PTR_ERR(cap);
-
- con->partner_sink_caps = cap;
-
- ret = typec_partner_set_usb_power_delivery(con->partner, con->partner_pd);
- if (ret) {
- usb_power_delivery_unregister_capabilities(con->partner_sink_caps);
- return ret;
- }
- }
-
- return 0;
+ return typec_partner_set_usb_power_delivery(con->partner, con->partner_pd);
}
static void ucsi_unregister_partner_pdos(struct ucsi_connector *con)
@@ -987,6 +1008,9 @@ static int ucsi_register_partner(struct ucsi_connector *con)
break;
}
+ if (pwr_opmode == UCSI_CONSTAT_PWR_OPMODE_PD)
+ ucsi_register_device_pdos(con);
+
desc.identity = &con->partner_identity;
desc.usb_pd = pwr_opmode == UCSI_CONSTAT_PWR_OPMODE_PD;
desc.pd_revision = UCSI_CONCAP_FLAG_PARTNER_PD_MAJOR_REV_AS_BCD(con->cap.flags);
@@ -1168,7 +1192,9 @@ static void ucsi_handle_connector_change(struct work_struct *work)
mutex_lock(&con->lock);
command = UCSI_GET_CONNECTOR_STATUS | UCSI_CONNECTOR_NUMBER(con->num);
- ret = ucsi_send_command(ucsi, command, &con->status, sizeof(con->status));
+
+ ret = ucsi_send_command_common(ucsi, command, &con->status,
+ sizeof(con->status), true);
if (ret < 0) {
dev_err(ucsi->dev, "%s: GET_CONNECTOR_STATUS failed (%d)\n",
__func__, ret);
@@ -1178,6 +1204,9 @@ static void ucsi_handle_connector_change(struct work_struct *work)
trace_ucsi_connector_change(con->num, &con->status);
+ if (ucsi->ops->connector_status)
+ ucsi->ops->connector_status(con);
+
role = !!(con->status.flags & UCSI_CONSTAT_PWR_DIR);
if (con->status.change & UCSI_CONSTAT_POWER_DIR_CHANGE) {
@@ -1225,14 +1254,6 @@ static void ucsi_handle_connector_change(struct work_struct *work)
if (con->status.change & UCSI_CONSTAT_CAM_CHANGE)
ucsi_partner_task(con, ucsi_check_altmodes, 1, 0);
- mutex_lock(&ucsi->ppm_lock);
- clear_bit(EVENT_PENDING, &con->ucsi->flags);
- ret = ucsi_acknowledge_connector_change(ucsi);
- mutex_unlock(&ucsi->ppm_lock);
-
- if (ret)
- dev_err(ucsi->dev, "%s: ACK failed (%d)", __func__, ret);
-
out_unlock:
mutex_unlock(&con->lock);
}
@@ -1324,6 +1345,9 @@ static int ucsi_reset_ppm(struct ucsi *ucsi)
goto out;
}
+ /* Give the PPM time to process a reset before reading CCI */
+ msleep(20);
+
ret = ucsi->ops->read(ucsi, UCSI_CCI, &cci, sizeof(cci));
if (ret)
goto out;
@@ -1337,7 +1361,6 @@ static int ucsi_reset_ppm(struct ucsi *ucsi)
goto out;
}
- msleep(20);
} while (!(cci & UCSI_CCI_RESET_COMPLETE));
out:
@@ -1477,9 +1500,6 @@ static struct fwnode_handle *ucsi_find_fwnode(struct ucsi_connector *con)
static int ucsi_register_port(struct ucsi *ucsi, struct ucsi_connector *con)
{
- struct usb_power_delivery_desc desc = { ucsi->cap.pd_version};
- struct usb_power_delivery_capabilities_desc pd_caps;
- struct usb_power_delivery_capabilities *pd_cap;
struct typec_capability *cap = &con->typec_cap;
enum typec_accessory *accessory = cap->accessory;
enum usb_role u_role = USB_ROLE_NONE;
@@ -1546,6 +1566,9 @@ static int ucsi_register_port(struct ucsi *ucsi, struct ucsi_connector *con)
cap->driver_data = con;
cap->ops = &ucsi_ops;
+ if (ucsi->ops->update_connector)
+ ucsi->ops->update_connector(con);
+
ret = ucsi_register_port_psy(con);
if (ret)
goto out;
@@ -1557,40 +1580,8 @@ static int ucsi_register_port(struct ucsi *ucsi, struct ucsi_connector *con)
goto out;
}
- con->pd = usb_power_delivery_register(ucsi->dev, &desc);
-
- ret = ucsi_get_pdos(con, TYPEC_SOURCE, 0, pd_caps.pdo);
- if (ret > 0) {
- if (ret < PDO_MAX_OBJECTS)
- pd_caps.pdo[ret] = 0;
-
- pd_caps.role = TYPEC_SOURCE;
- pd_cap = usb_power_delivery_register_capabilities(con->pd, &pd_caps);
- if (IS_ERR(pd_cap)) {
- ret = PTR_ERR(pd_cap);
- goto out;
- }
-
- con->port_source_caps = pd_cap;
- typec_port_set_usb_power_delivery(con->port, con->pd);
- }
-
- memset(&pd_caps, 0, sizeof(pd_caps));
- ret = ucsi_get_pdos(con, TYPEC_SINK, 0, pd_caps.pdo);
- if (ret > 0) {
- if (ret < PDO_MAX_OBJECTS)
- pd_caps.pdo[ret] = 0;
-
- pd_caps.role = TYPEC_SINK;
- pd_cap = usb_power_delivery_register_capabilities(con->pd, &pd_caps);
- if (IS_ERR(pd_cap)) {
- ret = PTR_ERR(pd_cap);
- goto out;
- }
-
- con->port_sink_caps = pd_cap;
- typec_port_set_usb_power_delivery(con->port, con->pd);
- }
+ if (!(ucsi->quirks & UCSI_DELAY_DEVICE_PDOS))
+ ucsi_register_device_pdos(con);
/* Alternate modes */
ret = ucsi_register_altmodes(con, UCSI_RECIPIENT_CON);
@@ -1610,6 +1601,9 @@ static int ucsi_register_port(struct ucsi *ucsi, struct ucsi_connector *con)
}
ret = 0; /* ucsi_send_command() returns length on success */
+ if (ucsi->ops->connector_status)
+ ucsi->ops->connector_status(con);
+
switch (UCSI_CONSTAT_PARTNER_TYPE(con->status.flags)) {
case UCSI_CONSTAT_PARTNER_TYPE_UFP:
case UCSI_CONSTAT_PARTNER_TYPE_CABLE_AND_UFP:
@@ -1653,6 +1647,7 @@ static int ucsi_register_port(struct ucsi *ucsi, struct ucsi_connector *con)
if (con->partner &&
UCSI_CONSTAT_PWR_OPMODE(con->status.flags) ==
UCSI_CONSTAT_PWR_OPMODE_PD) {
+ ucsi_register_device_pdos(con);
ucsi_get_src_pdos(con);
ucsi_check_altmodes(con);
}
@@ -1672,6 +1667,27 @@ out_unlock:
return ret;
}
+static u64 ucsi_get_supported_notifications(struct ucsi *ucsi)
+{
+ u8 features = ucsi->cap.features;
+ u64 ntfy = UCSI_ENABLE_NTFY_ALL;
+
+ if (!(features & UCSI_CAP_ALT_MODE_DETAILS))
+ ntfy &= ~UCSI_ENABLE_NTFY_CAM_CHANGE;
+
+ if (!(features & UCSI_CAP_PDO_DETAILS))
+ ntfy &= ~(UCSI_ENABLE_NTFY_PWR_LEVEL_CHANGE |
+ UCSI_ENABLE_NTFY_CAP_CHANGE);
+
+ if (!(features & UCSI_CAP_EXT_SUPPLY_NOTIFICATIONS))
+ ntfy &= ~UCSI_ENABLE_NTFY_EXT_PWR_SRC_CHANGE;
+
+ if (!(features & UCSI_CAP_PD_RESET))
+ ntfy &= ~UCSI_ENABLE_NTFY_PD_RESET_COMPLETE;
+
+ return ntfy;
+}
+
/**
* ucsi_init - Initialize UCSI interface
* @ucsi: UCSI to be initialized
@@ -1726,8 +1742,8 @@ static int ucsi_init(struct ucsi *ucsi)
goto err_unregister;
}
- /* Enable all notifications */
- ntfy = UCSI_ENABLE_NTFY_ALL;
+ /* Enable all supported notifications */
+ ntfy = ucsi_get_supported_notifications(ucsi);
command = UCSI_SET_NOTIFICATION_ENABLE | ntfy;
ret = ucsi_send_command(ucsi, command, NULL, 0);
if (ret < 0)