On some old IBM workstations and desktop computers, the BIOS presents in the DSDT an SMBus object that is missing the HID identifier that the i2c-scmi driver looks for. It also omits the leading "_" in the method names (it should be _SBR, not SBR_). Modify the ACPI device scan code to insert the missing HID if it finds an IBM system with such an object, and modify the i2c-scmi driver to handle the odd method names. Affected machines: IntelliStation Z20/Z30. Note that the i2c-i801 driver no longer works on these machines because of ACPI resource conflicts. Signed-off-by: Darrick J. Wong <djwong@xxxxxxxxxx> --- drivers/acpi/scan.c | 38 ++++++++++++++++++++++++++++++++ drivers/i2c/busses/i2c-scmi.c | 49 ++++++++++++++++++++++++++++++++--------- include/acpi/acpi_drivers.h | 1 + 3 files changed, 77 insertions(+), 11 deletions(-) diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c index 14a7481..58cb324 100644 --- a/drivers/acpi/scan.c +++ b/drivers/acpi/scan.c @@ -8,6 +8,7 @@ #include <linux/acpi.h> #include <linux/signal.h> #include <linux/kthread.h> +#include <linux/dmi.h> #include <acpi/acpi_drivers.h> @@ -1014,6 +1015,41 @@ static void acpi_add_id(struct acpi_device *device, const char *dev_id) list_add_tail(&id->list, &device->pnp.ids); } +/* + * Old IBM workstations have a DSDT bug wherein the SMBus object + * lacks the SMBUS01 HID and the methods do not have the necessary "_" + * prefix. Work around this. + */ +static int probe_ibm_smbus_device(struct acpi_device *device) +{ + acpi_handle h_dummy; + struct acpi_buffer path = {ACPI_ALLOCATE_BUFFER, NULL}; + int result; + + if (!dmi_name_in_vendors("IBM")) + return -ENODEV; + + /* Look for SMBS object */ + result = acpi_get_name(device->handle, ACPI_SINGLE_NAME, &path); + if (result) + return result; + + if (strcmp("SMBS", path.pointer)) { + result = -ENODEV; + goto out; + } + + /* Does it have the necessary (but misnamed) methods? */ + result = -ENODEV; + if (ACPI_SUCCESS(acpi_get_handle(device->handle, "SBI", &h_dummy)) && + ACPI_SUCCESS(acpi_get_handle(device->handle, "SBR", &h_dummy)) && + ACPI_SUCCESS(acpi_get_handle(device->handle, "SBW", &h_dummy))) + result = 0; +out: + kfree(path.pointer); + return result; +} + static void acpi_device_set_id(struct acpi_device *device) { acpi_status status; @@ -1064,6 +1100,8 @@ static void acpi_device_set_id(struct acpi_device *device) acpi_add_id(device, ACPI_BAY_HID); else if (ACPI_SUCCESS(acpi_dock_match(device))) acpi_add_id(device, ACPI_DOCK_HID); + else if (!probe_ibm_smbus_device(device)) + acpi_add_id(device, ACPI_SMBUS_HID); break; case ACPI_BUS_TYPE_POWER: diff --git a/drivers/i2c/busses/i2c-scmi.c b/drivers/i2c/busses/i2c-scmi.c index b4a55d4..f0ad03d 100644 --- a/drivers/i2c/busses/i2c-scmi.c +++ b/drivers/i2c/busses/i2c-scmi.c @@ -33,6 +33,7 @@ struct acpi_smbus_cmi { u8 cap_info:1; u8 cap_read:1; u8 cap_write:1; + struct smbus_methods_t methods; }; static const struct smbus_methods_t smbus_methods = { @@ -41,8 +42,15 @@ static const struct smbus_methods_t smbus_methods = { .mt_sbw = "_SBW", }; +/* Some IBM BIOSes omit the leading underscore */ +static const struct smbus_methods_t ibm_smbus_methods = { + .mt_info = "SBI_", + .mt_sbr = "SBR_", + .mt_sbw = "SBW_", +}; + static const struct acpi_device_id acpi_smbus_cmi_ids[] = { - {"SMBUS01", 0}, + {ACPI_SMBUS_HID, 0}, {"", 0} }; @@ -150,11 +158,11 @@ acpi_smbus_cmi_access(struct i2c_adapter *adap, u16 addr, unsigned short flags, if (read_write == I2C_SMBUS_READ) { protocol |= ACPI_SMBUS_PRTCL_READ; - method = smbus_methods.mt_sbr; + method = smbus_cmi->methods.mt_sbr; input.count = 3; } else { protocol |= ACPI_SMBUS_PRTCL_WRITE; - method = smbus_methods.mt_sbw; + method = smbus_cmi->methods.mt_sbw; input.count = 5; } @@ -283,20 +291,21 @@ static const struct i2c_algorithm acpi_smbus_cmi_algorithm = { }; -static int acpi_smbus_cmi_add_cap(struct acpi_smbus_cmi *smbus_cmi, - const char *name) +static int acpi_smbus_cmi_probe_cap(struct acpi_smbus_cmi *smbus_cmi, + const char *name, + const struct smbus_methods_t *methods) { struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; union acpi_object *obj; acpi_status status; - if (!strcmp(name, smbus_methods.mt_info)) { + if (!strcmp(name, methods->mt_info)) { status = acpi_evaluate_object(smbus_cmi->handle, - smbus_methods.mt_info, + methods->mt_info, NULL, &buffer); if (ACPI_FAILURE(status)) { ACPI_ERROR((AE_INFO, "Evaluating %s: %i", - smbus_methods.mt_info, status)); + methods->mt_info, status)); return -EIO; } @@ -319,17 +328,35 @@ static int acpi_smbus_cmi_add_cap(struct acpi_smbus_cmi *smbus_cmi, kfree(buffer.pointer); smbus_cmi->cap_info = 1; - } else if (!strcmp(name, smbus_methods.mt_sbr)) + smbus_cmi->methods.mt_info = methods->mt_info; + } else if (!strcmp(name, methods->mt_sbr)) { smbus_cmi->cap_read = 1; - else if (!strcmp(name, smbus_methods.mt_sbw)) + smbus_cmi->methods.mt_sbr = methods->mt_sbr; + } else if (!strcmp(name, methods->mt_sbw)) { smbus_cmi->cap_write = 1; - else + smbus_cmi->methods.mt_sbw = methods->mt_sbw; + } else { ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Unsupported CMI method: %s\n", name)); + return -ENODEV; + } return 0; } +static int acpi_smbus_cmi_add_cap(struct acpi_smbus_cmi *smbus_cmi, + const char *name) +{ + int res; + + res = acpi_smbus_cmi_probe_cap(smbus_cmi, name, &smbus_methods); + if (!res) + return 0; + + res = acpi_smbus_cmi_probe_cap(smbus_cmi, name, &ibm_smbus_methods); + return res; +} + static acpi_status acpi_smbus_cmi_query_methods(acpi_handle handle, u32 level, void *context, void **return_value) { diff --git a/include/acpi/acpi_drivers.h b/include/acpi/acpi_drivers.h index f4906f6..c724a08 100644 --- a/include/acpi/acpi_drivers.h +++ b/include/acpi/acpi_drivers.h @@ -65,6 +65,7 @@ #define ACPI_VIDEO_HID "LNXVIDEO" #define ACPI_BAY_HID "LNXIOBAY" #define ACPI_DOCK_HID "LNXDOCK" +#define ACPI_SMBUS_HID "SMBUS01" /* * For fixed hardware buttons, we fabricate acpi_devices with HID -- To unsubscribe from this list: send the line "unsubscribe linux-i2c" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html