diff options
Diffstat (limited to 'drivers/usb/typec/ucsi/ucsi.c')
-rw-r--r-- | drivers/usb/typec/ucsi/ucsi.c | 337 |
1 files changed, 165 insertions, 172 deletions
diff --git a/drivers/usb/typec/ucsi/ucsi.c b/drivers/usb/typec/ucsi/ucsi.c index 5ef5bd0e87cf..6aa28384f77f 100644 --- a/drivers/usb/typec/ucsi/ucsi.c +++ b/drivers/usb/typec/ucsi/ucsi.c @@ -128,8 +128,10 @@ static int ucsi_exec_command(struct ucsi *ucsi, u64 cmd) if (ret) return ret; - if (cci & UCSI_CCI_BUSY) + if (cci & UCSI_CCI_BUSY) { + ucsi->ops->async_write(ucsi, UCSI_CANCEL, NULL, 0); return -EBUSY; + } if (!(cci & UCSI_CCI_COMMAND_COMPLETE)) return -EIO; @@ -189,6 +191,64 @@ int ucsi_resume(struct ucsi *ucsi) EXPORT_SYMBOL_GPL(ucsi_resume); /* -------------------------------------------------------------------------- */ +struct ucsi_work { + struct delayed_work work; + unsigned long delay; + unsigned int count; + struct ucsi_connector *con; + int (*cb)(struct ucsi_connector *); +}; + +static void ucsi_poll_worker(struct work_struct *work) +{ + struct ucsi_work *uwork = container_of(work, struct ucsi_work, work.work); + struct ucsi_connector *con = uwork->con; + int ret; + + mutex_lock(&con->lock); + + if (!con->partner) { + mutex_unlock(&con->lock); + kfree(uwork); + return; + } + + ret = uwork->cb(con); + + if (uwork->count-- && (ret == -EBUSY || ret == -ETIMEDOUT)) + queue_delayed_work(con->wq, &uwork->work, uwork->delay); + else + kfree(uwork); + + mutex_unlock(&con->lock); +} + +static int ucsi_partner_task(struct ucsi_connector *con, + int (*cb)(struct ucsi_connector *), + int retries, unsigned long delay) +{ + struct ucsi_work *uwork; + + if (!con->partner) + return 0; + + uwork = kzalloc(sizeof(*uwork), GFP_KERNEL); + if (!uwork) + return -ENOMEM; + + INIT_DELAYED_WORK(&uwork->work, ucsi_poll_worker); + uwork->count = retries; + uwork->delay = delay; + uwork->con = con; + uwork->cb = cb; + + queue_delayed_work(con->wq, &uwork->work, delay); + + return 0; +} + +/* -------------------------------------------------------------------------- */ + void ucsi_altmode_update_active(struct ucsi_connector *con) { const struct typec_altmode *altmode = NULL; @@ -435,6 +495,8 @@ static int ucsi_register_altmodes(struct ucsi_connector *con, u8 recipient) command |= UCSI_GET_ALTMODE_CONNECTOR_NUMBER(con->num); command |= UCSI_GET_ALTMODE_OFFSET(i); len = ucsi_send_command(con->ucsi, command, alt, sizeof(alt)); + if (len == -EBUSY) + continue; if (len <= 0) return len; @@ -509,7 +571,7 @@ static int ucsi_get_pdos(struct ucsi_connector *con, int is_partner, command |= UCSI_GET_PDOS_SRC_PDOS; ret = ucsi_send_command(ucsi, command, pdos + offset, num_pdos * sizeof(u32)); - if (ret < 0) + if (ret < 0 && ret != -ETIMEDOUT) dev_err(ucsi->dev, "UCSI_GET_PDOS failed (%d)\n", ret); if (ret == 0 && offset == 0) dev_warn(ucsi->dev, "UCSI_GET_PDOS returned 0 bytes\n"); @@ -517,26 +579,49 @@ static int ucsi_get_pdos(struct ucsi_connector *con, int is_partner, return ret; } -static void ucsi_get_src_pdos(struct ucsi_connector *con, int is_partner) +static int ucsi_get_src_pdos(struct ucsi_connector *con) { int ret; /* UCSI max payload means only getting at most 4 PDOs at a time */ ret = ucsi_get_pdos(con, 1, con->src_pdos, 0, UCSI_MAX_PDOS); if (ret < 0) - return; + return ret; con->num_pdos = ret / sizeof(u32); /* number of bytes to 32-bit PDOs */ if (con->num_pdos < UCSI_MAX_PDOS) - return; + return 0; /* get the remaining PDOs, if any */ ret = ucsi_get_pdos(con, 1, con->src_pdos, UCSI_MAX_PDOS, PDO_MAX_OBJECTS - UCSI_MAX_PDOS); if (ret < 0) - return; + return ret; con->num_pdos += ret / sizeof(u32); + + ucsi_port_psy_changed(con); + + return 0; +} + +static int ucsi_check_altmodes(struct ucsi_connector *con) +{ + int ret; + + ret = ucsi_register_altmodes(con, UCSI_RECIPIENT_SOP); + if (ret && ret != -ETIMEDOUT) + dev_err(con->ucsi->dev, + "con%d: failed to register partner alt modes (%d)\n", + con->num, ret); + + /* Ignoring the errors in this case. */ + if (con->partner_altmode[0]) { + ucsi_altmode_update_active(con); + return 0; + } + + return ret; } static void ucsi_pwr_opmode_change(struct ucsi_connector *con) @@ -545,7 +630,8 @@ static void ucsi_pwr_opmode_change(struct ucsi_connector *con) case UCSI_CONSTAT_PWR_OPMODE_PD: con->rdo = con->status.request_data_obj; typec_set_pwr_opmode(con->port, TYPEC_PWR_MODE_PD); - ucsi_get_src_pdos(con, 1); + ucsi_partner_task(con, ucsi_get_src_pdos, 30, 0); + ucsi_partner_task(con, ucsi_check_altmodes, 30, 0); break; case UCSI_CONSTAT_PWR_OPMODE_TYPEC1_5: con->rdo = 0; @@ -614,9 +700,6 @@ static void ucsi_partner_change(struct ucsi_connector *con) enum usb_role u_role = USB_ROLE_NONE; int ret; - if (!con->partner) - return; - switch (UCSI_CONSTAT_PARTNER_TYPE(con->status.flags)) { case UCSI_CONSTAT_PARTNER_TYPE_UFP: case UCSI_CONSTAT_PARTNER_TYPE_CABLE_AND_UFP: @@ -633,10 +716,6 @@ static void ucsi_partner_change(struct ucsi_connector *con) break; } - /* Complete pending data role swap */ - if (!completion_done(&con->complete)) - complete(&con->complete); - /* Only notify USB controller if partner supports USB data */ if (!(UCSI_CONSTAT_PARTNER_FLAGS(con->status.flags) & UCSI_CONSTAT_PARTNER_FLAG_USB)) u_role = USB_ROLE_NONE; @@ -645,15 +724,31 @@ static void ucsi_partner_change(struct ucsi_connector *con) if (ret) dev_err(con->ucsi->dev, "con:%d: failed to set usb role:%d\n", con->num, u_role); +} - /* 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 int ucsi_check_connection(struct ucsi_connector *con) +{ + u64 command; + int ret; + + command = UCSI_GET_CONNECTOR_STATUS | UCSI_CONNECTOR_NUMBER(con->num); + ret = ucsi_send_command(con->ucsi, command, &con->status, sizeof(con->status)); + if (ret < 0) { + dev_err(con->ucsi->dev, "GET_CONNECTOR_STATUS failed (%d)\n", ret); + return ret; + } + + if (con->status.flags & UCSI_CONSTAT_CONNECTED) { + if (UCSI_CONSTAT_PWR_OPMODE(con->status.flags) == + UCSI_CONSTAT_PWR_OPMODE_PD) + ucsi_partner_task(con, ucsi_check_altmodes, 30, 0); + } else { + ucsi_partner_change(con); + ucsi_port_psy_changed(con); + ucsi_unregister_partner(con); + } + + return 0; } static void ucsi_handle_connector_change(struct work_struct *work) @@ -661,122 +756,24 @@ static void ucsi_handle_connector_change(struct work_struct *work) struct ucsi_connector *con = container_of(work, struct ucsi_connector, work); struct ucsi *ucsi = con->ucsi; - struct ucsi_connector_status pre_ack_status; - struct ucsi_connector_status post_ack_status; enum typec_role role; - enum usb_role u_role = USB_ROLE_NONE; - u16 inferred_changes; - u16 changed_flags; u64 command; int ret; mutex_lock(&con->lock); - /* - * Some/many PPMs have an issue where all fields in the change bitfield - * are cleared when an ACK is send. This will causes any change - * between GET_CONNECTOR_STATUS and ACK to be lost. - * - * We work around this by re-fetching the connector status afterwards. - * We then infer any changes that we see have happened but that may not - * be represented in the change bitfield. - * - * Also, even though we don't need to know the currently supported alt - * modes, we run the GET_CAM_SUPPORTED command to ensure the PPM does - * not get stuck in case it assumes we do. - * Always do this, rather than relying on UCSI_CONSTAT_CAM_CHANGE to be - * set in the change bitfield. - * - * We end up with the following actions: - * 1. UCSI_GET_CONNECTOR_STATUS, store result, update unprocessed_changes - * 2. UCSI_GET_CAM_SUPPORTED, discard result - * 3. ACK connector change - * 4. UCSI_GET_CONNECTOR_STATUS, store result - * 5. Infere lost changes by comparing UCSI_GET_CONNECTOR_STATUS results - * 6. If PPM reported a new change, then restart in order to ACK - * 7. Process everything as usual. - * - * We may end up seeing a change twice, but we can only miss extremely - * short transitional changes. - */ - - /* 1. First UCSI_GET_CONNECTOR_STATUS */ command = UCSI_GET_CONNECTOR_STATUS | UCSI_CONNECTOR_NUMBER(con->num); - ret = ucsi_send_command(ucsi, command, &pre_ack_status, - sizeof(pre_ack_status)); - if (ret < 0) { - dev_err(ucsi->dev, "%s: GET_CONNECTOR_STATUS failed (%d)\n", - __func__, ret); - goto out_unlock; - } - con->unprocessed_changes |= pre_ack_status.change; - - /* 2. Run UCSI_GET_CAM_SUPPORTED and discard the result. */ - command = UCSI_GET_CAM_SUPPORTED; - command |= UCSI_CONNECTOR_NUMBER(con->num); - ucsi_send_command(con->ucsi, command, NULL, 0); - - /* 3. ACK connector change */ - ret = ucsi_acknowledge_connector_change(ucsi); - clear_bit(EVENT_PENDING, &ucsi->flags); - if (ret) { - dev_err(ucsi->dev, "%s: ACK failed (%d)", __func__, ret); - goto out_unlock; - } - - /* 4. Second UCSI_GET_CONNECTOR_STATUS */ - command = UCSI_GET_CONNECTOR_STATUS | UCSI_CONNECTOR_NUMBER(con->num); - ret = ucsi_send_command(ucsi, command, &post_ack_status, - sizeof(post_ack_status)); + ret = ucsi_send_command(ucsi, command, &con->status, sizeof(con->status)); if (ret < 0) { dev_err(ucsi->dev, "%s: GET_CONNECTOR_STATUS failed (%d)\n", __func__, ret); goto out_unlock; } - /* 5. Inferre any missing changes */ - changed_flags = pre_ack_status.flags ^ post_ack_status.flags; - inferred_changes = 0; - if (UCSI_CONSTAT_PWR_OPMODE(changed_flags) != 0) - inferred_changes |= UCSI_CONSTAT_POWER_OPMODE_CHANGE; - - if (changed_flags & UCSI_CONSTAT_CONNECTED) - inferred_changes |= UCSI_CONSTAT_CONNECT_CHANGE; - - if (changed_flags & UCSI_CONSTAT_PWR_DIR) - inferred_changes |= UCSI_CONSTAT_POWER_DIR_CHANGE; - - if (UCSI_CONSTAT_PARTNER_FLAGS(changed_flags) != 0) - inferred_changes |= UCSI_CONSTAT_PARTNER_CHANGE; - - if (UCSI_CONSTAT_PARTNER_TYPE(changed_flags) != 0) - inferred_changes |= UCSI_CONSTAT_PARTNER_CHANGE; - - /* Mask out anything that was correctly notified in the later call. */ - inferred_changes &= ~post_ack_status.change; - if (inferred_changes) - dev_dbg(ucsi->dev, "%s: Inferred changes that would have been lost: 0x%04x\n", - __func__, inferred_changes); - - con->unprocessed_changes |= inferred_changes; - - /* 6. If PPM reported a new change, then restart in order to ACK */ - if (post_ack_status.change) - goto out_unlock; - - /* 7. Continue as if nothing happened */ - con->status = post_ack_status; - con->status.change = con->unprocessed_changes; - con->unprocessed_changes = 0; + trace_ucsi_connector_change(con->num, &con->status); role = !!(con->status.flags & UCSI_CONSTAT_PWR_DIR); - if (con->status.change & UCSI_CONSTAT_POWER_OPMODE_CHANGE || - con->status.change & UCSI_CONSTAT_POWER_LEVEL_CHANGE) { - ucsi_pwr_opmode_change(con); - ucsi_port_psy_changed(con); - } - if (con->status.change & UCSI_CONSTAT_POWER_DIR_CHANGE) { typec_set_pwr_role(con->port, role); @@ -787,54 +784,39 @@ static void ucsi_handle_connector_change(struct work_struct *work) if (con->status.change & UCSI_CONSTAT_CONNECT_CHANGE) { typec_set_pwr_role(con->port, role); + ucsi_port_psy_changed(con); + ucsi_partner_change(con); - switch (UCSI_CONSTAT_PARTNER_TYPE(con->status.flags)) { - case UCSI_CONSTAT_PARTNER_TYPE_UFP: - case UCSI_CONSTAT_PARTNER_TYPE_CABLE_AND_UFP: - u_role = USB_ROLE_HOST; - fallthrough; - case UCSI_CONSTAT_PARTNER_TYPE_CABLE: - typec_set_data_role(con->port, TYPEC_HOST); - break; - case UCSI_CONSTAT_PARTNER_TYPE_DFP: - u_role = USB_ROLE_DEVICE; - typec_set_data_role(con->port, TYPEC_DEVICE); - break; - default: - break; - } - - if (con->status.flags & UCSI_CONSTAT_CONNECTED) + if (con->status.flags & UCSI_CONSTAT_CONNECTED) { ucsi_register_partner(con); - else + ucsi_partner_task(con, ucsi_check_connection, 1, HZ); + } else { ucsi_unregister_partner(con); + } + } - ucsi_port_psy_changed(con); + if (con->status.change & UCSI_CONSTAT_POWER_OPMODE_CHANGE || + con->status.change & UCSI_CONSTAT_POWER_LEVEL_CHANGE) + ucsi_pwr_opmode_change(con); - /* Only notify USB controller if partner supports USB data */ - if (!(UCSI_CONSTAT_PARTNER_FLAGS(con->status.flags) & - UCSI_CONSTAT_PARTNER_FLAG_USB)) - u_role = USB_ROLE_NONE; + if (con->partner && con->status.change & UCSI_CONSTAT_PARTNER_CHANGE) { + ucsi_partner_change(con); - ret = usb_role_switch_set_role(con->usb_role_sw, u_role); - if (ret) - dev_err(ucsi->dev, "con:%d: failed to set usb role:%d\n", - con->num, u_role); + /* Complete pending data role swap */ + if (!completion_done(&con->complete)) + complete(&con->complete); } - if (con->status.change & UCSI_CONSTAT_PARTNER_CHANGE) - ucsi_partner_change(con); + if (con->status.change & UCSI_CONSTAT_CAM_CHANGE) + ucsi_partner_task(con, ucsi_check_altmodes, 1, 0); - trace_ucsi_connector_change(con->num, &con->status); + clear_bit(EVENT_PENDING, &con->ucsi->flags); -out_unlock: - if (test_and_clear_bit(EVENT_PENDING, &ucsi->flags)) { - schedule_work(&con->work); - mutex_unlock(&con->lock); - return; - } + ret = ucsi_acknowledge_connector_change(ucsi); + if (ret) + dev_err(ucsi->dev, "%s: ACK failed (%d)", __func__, ret); - clear_bit(EVENT_PROCESSING, &ucsi->flags); +out_unlock: mutex_unlock(&con->lock); } @@ -852,9 +834,7 @@ void ucsi_connector_change(struct ucsi *ucsi, u8 num) return; } - set_bit(EVENT_PENDING, &ucsi->flags); - - if (!test_and_set_bit(EVENT_PROCESSING, &ucsi->flags)) + if (!test_and_set_bit(EVENT_PENDING, &ucsi->flags)) schedule_work(&con->work); } EXPORT_SYMBOL_GPL(ucsi_connector_change); @@ -1041,8 +1021,18 @@ static int ucsi_register_port(struct ucsi *ucsi, int index) enum typec_accessory *accessory = cap->accessory; enum usb_role u_role = USB_ROLE_NONE; u64 command; + char *name; int ret; + name = kasprintf(GFP_KERNEL, "%s-con%d", dev_name(ucsi->dev), con->num); + if (!name) + return -ENOMEM; + + con->wq = create_singlethread_workqueue(name); + kfree(name); + if (!con->wq) + return -ENOMEM; + INIT_WORK(&con->work, ucsi_handle_connector_change); init_completion(&con->complete); mutex_init(&con->lock); @@ -1160,16 +1150,9 @@ static int ucsi_register_port(struct ucsi *ucsi, int index) ret = 0; } - 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); - ret = 0; - } else { - ucsi_altmode_update_active(con); - } + if (UCSI_CONSTAT_PWR_OPMODE(con->status.flags) == UCSI_CONSTAT_PWR_OPMODE_PD) { + ucsi_get_src_pdos(con); + ucsi_check_altmodes(con); } trace_ucsi_register_port(con->num, &con->status); @@ -1178,6 +1161,12 @@ out: fwnode_handle_put(cap->fwnode); out_unlock: mutex_unlock(&con->lock); + + if (ret && con->wq) { + destroy_workqueue(con->wq); + con->wq = NULL; + } + return ret; } @@ -1248,6 +1237,8 @@ err_unregister: ucsi_unregister_partner(con); ucsi_unregister_altmodes(con, UCSI_RECIPIENT_CON); ucsi_unregister_port_psy(con); + if (con->wq) + destroy_workqueue(con->wq); typec_unregister_port(con->port); con->port = NULL; } @@ -1370,6 +1361,8 @@ void ucsi_unregister(struct ucsi *ucsi) ucsi_unregister_altmodes(&ucsi->connector[i], UCSI_RECIPIENT_CON); ucsi_unregister_port_psy(&ucsi->connector[i]); + if (ucsi->connector[i].wq) + destroy_workqueue(ucsi->connector[i].wq); typec_unregister_port(ucsi->connector[i].port); } |