static void ucsi_connector_change(struct work_struct *work) { struct ucsi_connector *con = container_of(work, struct ucsi_connector, work); struct ucsi_connector_status constat; struct ucsi *ucsi = con->ucsi; struct ucsi_control ctrl; int ret; mutex_lock(&ucsi->ppm_lock); ctrl.cmd.cmd = UCSI_GET_CONNECTOR_STATUS; ctrl.cmd.length = 0; ctrl.cmd.data = con->num; ret = ucsi_run_cmd(con->ucsi, &ctrl, &constat, sizeof(constat)); if (ret) { dev_err(ucsi->dev, "%s: failed to read connector status (%d)\n", __func__, ret); goto out_ack_event; } /* Ignoring disconnections and Alternate Modes */ if (!constat.connected || !(constat.change & (UCSI_CONSTAT_PARTNER_CHANGE | UCSI_CONSTAT_CONNECT_CHANGE)) || constat.partner_flags & UCSI_CONSTAT_PARTNER_FLAG_ALT_MODE) goto out_ack_event; /* If the partner got USB Host role, attempting swap */ if (constat.partner_type & UCSI_CONSTAT_PARTNER_TYPE_DFP) { ctrl.uor.cmd = UCSI_SET_UOR; ctrl.uor.con_num = con->num; ctrl.uor.role = UCSI_UOR_ROLE_DFP; ret = ucsi_run_cmd(con->ucsi, &ctrl, NULL, 0); if (ret) dev_err(ucsi->dev, "%s: failed to swap role (%d)\n", __func__, ret); } out_ack_event: ucsi_ack(ucsi, UCSI_ACK_EVENT); clear_bit(EVENT_PENDING, &ucsi->flags); mutex_unlock(&ucsi->ppm_lock); }
static void ucsi_connector_change(struct work_struct *work) { struct ucsi_connector *con = container_of(work, struct ucsi_connector, work); struct ucsi *ucsi = con->ucsi; struct ucsi_control ctrl; int ret; mutex_lock(&ucsi->ppm_lock); UCSI_CMD_GET_CONNECTOR_STATUS(ctrl, con->num); ret = ucsi_run_command(ucsi, &ctrl, &con->status, sizeof(con->status)); if (ret < 0) { dev_err(ucsi->dev, "%s: GET_CONNECTOR_STATUS failed (%d)\n", __func__, ret); goto out_unlock; } if (con->status.change & UCSI_CONSTAT_POWER_OPMODE_CHANGE) ucsi_pwr_opmode_change(con); if (con->status.change & UCSI_CONSTAT_POWER_DIR_CHANGE) { typec_set_pwr_role(con->port, con->status.pwr_dir); /* Complete pending power role swap */ if (!completion_done(&con->complete)) 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) { if (con->status.connected) ucsi_register_partner(con); else ucsi_unregister_partner(con); } ret = ucsi_ack(ucsi, UCSI_ACK_EVENT); if (ret) dev_err(ucsi->dev, "%s: ACK failed (%d)", __func__, ret); trace_ucsi_connector_change(con->num, &con->status); out_unlock: clear_bit(EVENT_PENDING, &ucsi->flags); mutex_unlock(&ucsi->ppm_lock); }
static int ucsi_run_cmd(struct ucsi *ucsi, struct ucsi_control *ctrl, void *data, size_t size) { u16 err_value = 0; int ret; set_bit(COMMAND_PENDING, &ucsi->flags); ret = ucsi_acpi_cmd(ucsi, ctrl); if (ret) goto err_clear_flag; ret = wait_for_completion_timeout(&ucsi->complete, msecs_to_jiffies(UCSI_TIMEOUT_MS)); if (!ret) { ret = -ETIMEDOUT; goto err_clear_flag; } switch (ucsi->status) { case UCSI_IDLE: if (data) memcpy(data, ucsi->data->message_in, size); ret = ucsi_ack(ucsi, UCSI_ACK_CMD); break; case UCSI_BUSY: /* The caller decides whether to cancel or not */ ret = -EBUSY; goto err_clear_flag; case UCSI_ERROR: ret = ucsi_ack(ucsi, UCSI_ACK_CMD); if (ret) goto err_clear_flag; ctrl->cmd.cmd = UCSI_GET_ERROR_STATUS; ctrl->cmd.length = 0; ctrl->cmd.data = 0; ret = ucsi_acpi_cmd(ucsi, ctrl); if (ret) goto err_clear_flag; ret = wait_for_completion_timeout(&ucsi->complete, msecs_to_jiffies(UCSI_TIMEOUT_MS)); if (!ret) { ret = -ETIMEDOUT; goto err_clear_flag; } memcpy(&err_value, ucsi->data->message_in, sizeof(err_value)); /* Something has really gone wrong */ if (WARN_ON(ucsi->status == UCSI_ERROR)) { ret = -ENODEV; goto err_clear_flag; } ret = ucsi_ack(ucsi, UCSI_ACK_CMD); if (ret) goto err_clear_flag; switch (err_value) { case UCSI_ERROR_INCOMPATIBLE_PARTNER: ret = -EOPNOTSUPP; break; case UCSI_ERROR_CC_COMMUNICATION_ERR: ret = -ECOMM; break; case UCSI_ERROR_CONTRACT_NEGOTIATION_FAIL: ret = -EIO; break; case UCSI_ERROR_DEAD_BATTERY: dev_warn(ucsi->dev, "Dead battery condition!\n"); ret = -EPERM; break; /* The following mean a bug in this driver */ case UCSI_ERROR_INVALID_CON_NUM: case UCSI_ERROR_UNREGONIZED_CMD: case UCSI_ERROR_INVALID_CMD_ARGUMENT: default: dev_warn(ucsi->dev, "%s: possible UCSI driver bug - error %hu\n", __func__, err_value); ret = -EINVAL; break; } break; } ctrl->raw_cmd = 0; err_clear_flag: clear_bit(COMMAND_PENDING, &ucsi->flags); return ret; }
static int ucsi_run_command(struct ucsi *ucsi, struct ucsi_control *ctrl, void *data, size_t size) { struct ucsi_control _ctrl; u8 data_length; u16 error; int ret; ret = ucsi_command(ucsi, ctrl); if (ret) goto err; switch (ucsi->status) { case UCSI_IDLE: ret = ucsi_sync(ucsi); if (ret) dev_warn(ucsi->dev, "%s: sync failed\n", __func__); if (data) memcpy(data, ucsi->ppm->data->message_in, size); data_length = ucsi->ppm->data->cci.data_length; ret = ucsi_ack(ucsi, UCSI_ACK_CMD); if (!ret) ret = data_length; break; case UCSI_BUSY: /* The caller decides whether to cancel or not */ ret = -EBUSY; break; case UCSI_ERROR: ret = ucsi_ack(ucsi, UCSI_ACK_CMD); if (ret) break; _ctrl.raw_cmd = 0; _ctrl.cmd.cmd = UCSI_GET_ERROR_STATUS; ret = ucsi_command(ucsi, &_ctrl); if (ret) { dev_err(ucsi->dev, "reading error failed!\n"); break; } memcpy(&error, ucsi->ppm->data->message_in, sizeof(error)); /* Something has really gone wrong */ if (WARN_ON(ucsi->status == UCSI_ERROR)) { ret = -ENODEV; break; } ret = ucsi_ack(ucsi, UCSI_ACK_CMD); if (ret) break; switch (error) { case UCSI_ERROR_INCOMPATIBLE_PARTNER: ret = -EOPNOTSUPP; break; case UCSI_ERROR_CC_COMMUNICATION_ERR: ret = -ECOMM; break; case UCSI_ERROR_CONTRACT_NEGOTIATION_FAIL: ret = -EPROTO; break; case UCSI_ERROR_DEAD_BATTERY: dev_warn(ucsi->dev, "Dead battery condition!\n"); ret = -EPERM; break; /* The following mean a bug in this driver */ case UCSI_ERROR_INVALID_CON_NUM: case UCSI_ERROR_UNREGONIZED_CMD: case UCSI_ERROR_INVALID_CMD_ARGUMENT: dev_warn(ucsi->dev, "%s: possible UCSI driver bug - error 0x%x\n", __func__, error); ret = -EINVAL; break; default: dev_warn(ucsi->dev, "%s: error without status\n", __func__); ret = -EIO; break; } break; } err: trace_ucsi_run_command(ctrl, ret); return ret; }