usb: ucsi_acpi: Quirk to ack a connector change ack cmd

The PPM on some Dell laptops seems to expect that the ACK_CC_CI
command to clear the connector change notification is in turn
followed by another ACK_CC_CI to acknowledge the ACK_CC_CI command
itself. This is in violation of the UCSI spec that states:

    "The only notification that is not acknowledged by the OPM is
     the command completion notification for the ACK_CC_CI or the
     PPM_RESET command."

Add a quirk to send this ack anyway.
Apply the quirk to all Dell systems.

On the first command that acks a connector change send a dummy
command to determine if it runs into a timeout. Only activate
the quirk if it does. This ensure that we do not break Dell
systems that do not need the quirk.

Signed-off-by: "Christian A. Ehrhardt" <lk@c--e.de>
Reviewed-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Link: https://lore.kernel.org/r/20240121204123.275441-4-lk@c--e.de
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
Christian A. Ehrhardt 2024-01-21 21:41:23 +01:00 committed by Greg Kroah-Hartman
parent 2840143e39
commit f3be347ea4
1 changed files with 68 additions and 3 deletions

View File

@ -25,6 +25,8 @@ struct ucsi_acpi {
unsigned long flags;
guid_t guid;
u64 cmd;
bool dell_quirk_probed;
bool dell_quirk_active;
};
static int ucsi_acpi_dsm(struct ucsi_acpi *ua, int func)
@ -126,12 +128,73 @@ static const struct ucsi_operations ucsi_zenbook_ops = {
.async_write = ucsi_acpi_async_write
};
static const struct dmi_system_id zenbook_dmi_id[] = {
/*
* Some Dell laptops expect that an ACK command with the
* UCSI_ACK_CONNECTOR_CHANGE bit set is followed by a (separate)
* ACK command that only has the UCSI_ACK_COMMAND_COMPLETE bit set.
* If this is not done events are not delivered to OSPM and
* subsequent commands will timeout.
*/
static int
ucsi_dell_sync_write(struct ucsi *ucsi, unsigned int offset,
const void *val, size_t val_len)
{
struct ucsi_acpi *ua = ucsi_get_drvdata(ucsi);
u64 cmd = *(u64 *)val, ack = 0;
int ret;
if (UCSI_COMMAND(cmd) == UCSI_ACK_CC_CI &&
cmd & UCSI_ACK_CONNECTOR_CHANGE)
ack = UCSI_ACK_CC_CI | UCSI_ACK_COMMAND_COMPLETE;
ret = ucsi_acpi_sync_write(ucsi, offset, val, val_len);
if (ret != 0)
return ret;
if (ack == 0)
return ret;
if (!ua->dell_quirk_probed) {
ua->dell_quirk_probed = true;
cmd = UCSI_GET_CAPABILITY;
ret = ucsi_acpi_sync_write(ucsi, UCSI_CONTROL, &cmd,
sizeof(cmd));
if (ret == 0)
return ucsi_acpi_sync_write(ucsi, UCSI_CONTROL,
&ack, sizeof(ack));
if (ret != -ETIMEDOUT)
return ret;
ua->dell_quirk_active = true;
dev_err(ua->dev, "Firmware bug: Additional ACK required after ACKing a connector change.\n");
dev_err(ua->dev, "Firmware bug: Enabling workaround\n");
}
if (!ua->dell_quirk_active)
return ret;
return ucsi_acpi_sync_write(ucsi, UCSI_CONTROL, &ack, sizeof(ack));
}
static const struct ucsi_operations ucsi_dell_ops = {
.read = ucsi_acpi_read,
.sync_write = ucsi_dell_sync_write,
.async_write = ucsi_acpi_async_write
};
static const struct dmi_system_id ucsi_acpi_quirks[] = {
{
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
DMI_MATCH(DMI_PRODUCT_NAME, "ZenBook UX325UA_UM325UA"),
},
.driver_data = (void *)&ucsi_zenbook_ops,
},
{
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
},
.driver_data = (void *)&ucsi_dell_ops,
},
{ }
};
@ -160,6 +223,7 @@ static int ucsi_acpi_probe(struct platform_device *pdev)
{
struct acpi_device *adev = ACPI_COMPANION(&pdev->dev);
const struct ucsi_operations *ops = &ucsi_acpi_ops;
const struct dmi_system_id *id;
struct ucsi_acpi *ua;
struct resource *res;
acpi_status status;
@ -189,8 +253,9 @@ static int ucsi_acpi_probe(struct platform_device *pdev)
init_completion(&ua->complete);
ua->dev = &pdev->dev;
if (dmi_check_system(zenbook_dmi_id))
ops = &ucsi_zenbook_ops;
id = dmi_first_match(ucsi_acpi_quirks);
if (id)
ops = id->driver_data;
ua->ucsi = ucsi_create(&pdev->dev, ops);
if (IS_ERR(ua->ucsi))