diff options
Diffstat (limited to 'drivers/usb/typec/ucsi/ucsi.c')
-rw-r--r-- | drivers/usb/typec/ucsi/ucsi.c | 232 |
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) |