aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/usb/typec/ucsi
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/typec/ucsi')
-rw-r--r--drivers/usb/typec/ucsi/psy.c15
-rw-r--r--drivers/usb/typec/ucsi/ucsi.c130
-rw-r--r--drivers/usb/typec/ucsi/ucsi.h4
-rw-r--r--drivers/usb/typec/ucsi/ucsi_acpi.c5
4 files changed, 129 insertions, 25 deletions
diff --git a/drivers/usb/typec/ucsi/psy.c b/drivers/usb/typec/ucsi/psy.c
index 26ed0b520749..56bf56517f75 100644
--- a/drivers/usb/typec/ucsi/psy.c
+++ b/drivers/usb/typec/ucsi/psy.c
@@ -220,11 +220,11 @@ int ucsi_register_port_psy(struct ucsi_connector *con)
return -ENOMEM;
con->psy_desc.name = psy_name;
- con->psy_desc.type = POWER_SUPPLY_TYPE_USB,
+ con->psy_desc.type = POWER_SUPPLY_TYPE_USB;
con->psy_desc.usb_types = ucsi_psy_usb_types;
con->psy_desc.num_usb_types = ARRAY_SIZE(ucsi_psy_usb_types);
- con->psy_desc.properties = ucsi_psy_props,
- con->psy_desc.num_properties = ARRAY_SIZE(ucsi_psy_props),
+ con->psy_desc.properties = ucsi_psy_props;
+ con->psy_desc.num_properties = ARRAY_SIZE(ucsi_psy_props);
con->psy_desc.get_property = ucsi_psy_get_prop;
con->psy = power_supply_register(dev, &con->psy_desc, &psy_cfg);
@@ -238,4 +238,13 @@ void ucsi_unregister_port_psy(struct ucsi_connector *con)
return;
power_supply_unregister(con->psy);
+ con->psy = NULL;
+}
+
+void ucsi_port_psy_changed(struct ucsi_connector *con)
+{
+ if (IS_ERR_OR_NULL(con->psy))
+ return;
+
+ power_supply_changed(con->psy);
}
diff --git a/drivers/usb/typec/ucsi/ucsi.c b/drivers/usb/typec/ucsi/ucsi.c
index 758b988ac518..f02958927cbd 100644
--- a/drivers/usb/typec/ucsi/ucsi.c
+++ b/drivers/usb/typec/ucsi/ucsi.c
@@ -53,7 +53,7 @@ static int ucsi_acknowledge_connector_change(struct ucsi *ucsi)
ctrl = UCSI_ACK_CC_CI;
ctrl |= UCSI_ACK_CONNECTOR_CHANGE;
- return ucsi->ops->async_write(ucsi, UCSI_CONTROL, &ctrl, sizeof(ctrl));
+ return ucsi->ops->sync_write(ucsi, UCSI_CONTROL, &ctrl, sizeof(ctrl));
}
static int ucsi_exec_command(struct ucsi *ucsi, u64 command);
@@ -625,26 +625,120 @@ 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;
+ 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 */
+ clear_bit(EVENT_PENDING, &ucsi->flags);
+ ret = ucsi_acknowledge_connector_change(ucsi);
+ 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, &con->status,
- sizeof(con->status));
+ ret = ucsi_send_command(ucsi, command, &post_ack_status,
+ sizeof(post_ack_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;
+
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)
+ 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);
@@ -674,30 +768,23 @@ static void ucsi_handle_connector_change(struct work_struct *work)
ucsi_register_partner(con);
else
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.
- */
- command = UCSI_GET_CAM_SUPPORTED;
- command |= UCSI_CONNECTOR_NUMBER(con->num);
- ucsi_send_command(con->ucsi, command, NULL, 0);
+ ucsi_port_psy_changed(con);
}
if (con->status.change & UCSI_CONSTAT_PARTNER_CHANGE)
ucsi_partner_change(con);
- ret = ucsi_acknowledge_connector_change(ucsi);
- 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);
+ if (test_and_clear_bit(EVENT_PENDING, &ucsi->flags)) {
+ schedule_work(&con->work);
+ mutex_unlock(&con->lock);
+ return;
+ }
+
+ clear_bit(EVENT_PROCESSING, &ucsi->flags);
mutex_unlock(&con->lock);
}
@@ -715,7 +802,9 @@ void ucsi_connector_change(struct ucsi *ucsi, u8 num)
return;
}
- if (!test_and_set_bit(EVENT_PENDING, &ucsi->flags))
+ set_bit(EVENT_PENDING, &ucsi->flags);
+
+ if (!test_and_set_bit(EVENT_PROCESSING, &ucsi->flags))
schedule_work(&con->work);
}
EXPORT_SYMBOL_GPL(ucsi_connector_change);
@@ -994,6 +1083,7 @@ static int ucsi_register_port(struct ucsi *ucsi, int index)
!!(con->status.flags & UCSI_CONSTAT_PWR_DIR));
ucsi_pwr_opmode_change(con);
ucsi_register_partner(con);
+ ucsi_port_psy_changed(con);
}
if (con->partner) {
diff --git a/drivers/usb/typec/ucsi/ucsi.h b/drivers/usb/typec/ucsi/ucsi.h
index cba6f77bea61..dd9ba60ab4a3 100644
--- a/drivers/usb/typec/ucsi/ucsi.h
+++ b/drivers/usb/typec/ucsi/ucsi.h
@@ -296,6 +296,7 @@ struct ucsi {
#define EVENT_PENDING 0
#define COMMAND_PENDING 1
#define ACK_PENDING 2
+#define EVENT_PROCESSING 3
};
#define UCSI_MAX_SVID 5
@@ -322,6 +323,7 @@ struct ucsi_connector {
struct typec_capability typec_cap;
+ u16 unprocessed_changes;
struct ucsi_connector_status status;
struct ucsi_connector_capability cap;
struct power_supply *psy;
@@ -340,9 +342,11 @@ int ucsi_resume(struct ucsi *ucsi);
#if IS_ENABLED(CONFIG_POWER_SUPPLY)
int ucsi_register_port_psy(struct ucsi_connector *con);
void ucsi_unregister_port_psy(struct ucsi_connector *con);
+void ucsi_port_psy_changed(struct ucsi_connector *con);
#else
static inline int ucsi_register_port_psy(struct ucsi_connector *con) { return 0; }
static inline void ucsi_unregister_port_psy(struct ucsi_connector *con) { }
+static inline void ucsi_port_psy_changed(struct ucsi_connector *con) { }
#endif /* CONFIG_POWER_SUPPLY */
#if IS_ENABLED(CONFIG_TYPEC_DP_ALTMODE)
diff --git a/drivers/usb/typec/ucsi/ucsi_acpi.c b/drivers/usb/typec/ucsi/ucsi_acpi.c
index fbfe8f5933af..04976435ad73 100644
--- a/drivers/usb/typec/ucsi/ucsi_acpi.c
+++ b/drivers/usb/typec/ucsi/ucsi_acpi.c
@@ -103,11 +103,12 @@ static void ucsi_acpi_notify(acpi_handle handle, u32 event, void *data)
if (ret)
return;
+ if (UCSI_CCI_CONNECTOR(cci))
+ ucsi_connector_change(ua->ucsi, UCSI_CCI_CONNECTOR(cci));
+
if (test_bit(COMMAND_PENDING, &ua->flags) &&
cci & (UCSI_CCI_ACK_COMPLETE | UCSI_CCI_COMMAND_COMPLETE))
complete(&ua->complete);
- else if (UCSI_CCI_CONNECTOR(cci))
- ucsi_connector_change(ua->ucsi, UCSI_CCI_CONNECTOR(cci));
}
static int ucsi_acpi_probe(struct platform_device *pdev)