This patch adds supports for I2C device enumeration and removal via ACPI reconfiguration notifications that are send as a result of an ACPI table load or unload operation. The code is very similar with the device tree reconfiguration code with only small differences in the way we test and set the enumerated state of the device: * the equivalent of device tree's OF_POPULATED flag is the flags.visited field in the ACPI device and the following wrappers are used to manipulate it: acpi_device_enumerated(), acpi_device_set_enumerated() and acpi_device_clear_enumerated() * the device tree code checks of status of the OF_POPULATED flag to avoid trying to create duplicate Linux devices in two places: once when the controller is probed, and once when the reconfigure event is received; in the ACPI code the check is performed only once when the ACPI namespace is searched because this code path is invoked in both of the two mentioned cases The rest of the enumeration handling is similar with device tree: when the Linux device is unregistered the ACPI device is marked as not enumerated; also, when a device remove notification is received we check that the device is in the enumerated state before continuing with the removal of the Linux device. Signed-off-by: Octavian Purdila <octavian.purdila@xxxxxxxxx> Reviewed-by: Mika Westerberg <mika.westerberg@xxxxxxxxxxxxxxx> --- drivers/i2c/i2c-core.c | 175 ++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 137 insertions(+), 38 deletions(-) diff --git a/drivers/i2c/i2c-core.c b/drivers/i2c/i2c-core.c index af11b65..74e5aea 100644 --- a/drivers/i2c/i2c-core.c +++ b/drivers/i2c/i2c-core.c @@ -107,12 +107,11 @@ struct acpi_i2c_lookup { acpi_handle device_handle; }; -static int acpi_i2c_find_address(struct acpi_resource *ares, void *data) +static int acpi_i2c_fill_info(struct acpi_resource *ares, void *data) { struct acpi_i2c_lookup *lookup = data; struct i2c_board_info *info = lookup->info; struct acpi_resource_i2c_serialbus *sb; - acpi_handle adapter_handle; acpi_status status; if (info->addr || ares->type != ACPI_RESOURCE_TYPE_SERIAL_BUS) @@ -122,80 +121,102 @@ static int acpi_i2c_find_address(struct acpi_resource *ares, void *data) if (sb->type != ACPI_RESOURCE_SERIAL_TYPE_I2C) return 1; - /* - * Extract the ResourceSource and make sure that the handle matches - * with the I2C adapter handle. - */ status = acpi_get_handle(lookup->device_handle, sb->resource_source.string_ptr, - &adapter_handle); - if (ACPI_SUCCESS(status) && adapter_handle == lookup->adapter_handle) { - info->addr = sb->slave_address; - if (sb->access_mode == ACPI_I2C_10BIT_MODE) - info->flags |= I2C_CLIENT_TEN; - } + &lookup->adapter_handle); + if (!ACPI_SUCCESS(status)) + return 1; + + info->addr = sb->slave_address; + if (sb->access_mode == ACPI_I2C_10BIT_MODE) + info->flags |= I2C_CLIENT_TEN; return 1; } -static acpi_status acpi_i2c_add_device(acpi_handle handle, u32 level, - void *data, void **return_value) +static int acpi_i2c_get_info(struct acpi_device *adev, + struct i2c_board_info *info, + acpi_handle *adapter_handle) { - struct i2c_adapter *adapter = data; struct list_head resource_list; - struct acpi_i2c_lookup lookup; struct resource_entry *entry; - struct i2c_board_info info; - struct acpi_device *adev; + struct acpi_i2c_lookup lookup; int ret; - if (acpi_bus_get_device(handle, &adev)) - return AE_OK; - if (acpi_bus_get_status(adev) || !adev->status.present) - return AE_OK; + if (acpi_bus_get_status(adev) || !adev->status.present || + acpi_device_enumerated(adev)) + return -EINVAL; - memset(&info, 0, sizeof(info)); - info.fwnode = acpi_fwnode_handle(adev); + memset(info, 0, sizeof(*info)); + info->fwnode = acpi_fwnode_handle(adev); memset(&lookup, 0, sizeof(lookup)); - lookup.adapter_handle = ACPI_HANDLE(&adapter->dev); - lookup.device_handle = handle; - lookup.info = &info; + lookup.device_handle = acpi_device_handle(adev); + lookup.info = info; - /* - * Look up for I2cSerialBus resource with ResourceSource that - * matches with this adapter. - */ + /* Look up for I2cSerialBus resource */ INIT_LIST_HEAD(&resource_list); ret = acpi_dev_get_resources(adev, &resource_list, - acpi_i2c_find_address, &lookup); + acpi_i2c_fill_info, &lookup); acpi_dev_free_resource_list(&resource_list); - if (ret < 0 || !info.addr) - return AE_OK; + if (ret < 0 || !info->addr) + return -EINVAL; + + *adapter_handle = lookup.adapter_handle; /* Then fill IRQ number if any */ ret = acpi_dev_get_resources(adev, &resource_list, NULL, NULL); if (ret < 0) - return AE_OK; + return -EINVAL; resource_list_for_each_entry(entry, &resource_list) { if (resource_type(entry->res) == IORESOURCE_IRQ) { - info.irq = entry->res->start; + info->irq = entry->res->start; break; } } acpi_dev_free_resource_list(&resource_list); + strlcpy(info->type, dev_name(&adev->dev), sizeof(info->type)); + + return 0; +} + +static void acpi_i2c_register_device(struct i2c_adapter *adapter, + struct acpi_device *adev, + struct i2c_board_info *info) +{ adev->power.flags.ignore_parent = true; - strlcpy(info.type, dev_name(&adev->dev), sizeof(info.type)); - if (!i2c_new_device(adapter, &info)) { + acpi_device_set_enumerated(adev); + + if (!i2c_new_device(adapter, info)) { adev->power.flags.ignore_parent = false; dev_err(&adapter->dev, "failed to add I2C device %s from ACPI\n", dev_name(&adev->dev)); } +} + +static acpi_status acpi_i2c_add_device(acpi_handle handle, u32 level, + void *data, void **return_value) +{ + struct i2c_adapter *adapter = data; + struct acpi_device *adev; + acpi_handle adapter_handle; + struct i2c_board_info info; + + if (acpi_bus_get_device(handle, &adev)) + return AE_OK; + + if (acpi_i2c_get_info(adev, &info, &adapter_handle)) + return AE_OK; + + if (adapter_handle != ACPI_HANDLE(&adapter->dev)) + return AE_OK; + + acpi_i2c_register_device(adapter, adev, &info); return AE_OK; } @@ -225,8 +246,80 @@ static void acpi_i2c_register_devices(struct i2c_adapter *adap) dev_warn(&adap->dev, "failed to enumerate I2C slaves\n"); } +static int acpi_i2c_match_adapter(struct device *dev, void *data) +{ + struct i2c_adapter *adapter = i2c_verify_adapter(dev); + + if (!adapter) + return 0; + + return ACPI_HANDLE(dev) == (acpi_handle)data; +} + +static int acpi_i2c_match_device(struct device *dev, void *data) +{ + return ACPI_COMPANION(dev) == data; +} + +static struct i2c_adapter *acpi_i2c_find_adapter_by_handle(acpi_handle handle) +{ + struct device *dev; + + dev = bus_find_device(&i2c_bus_type, NULL, handle, + acpi_i2c_match_adapter); + return dev ? i2c_verify_adapter(dev) : NULL; +} + +static struct i2c_client *acpi_i2c_find_client_by_adev(struct acpi_device *adev) +{ + struct device *dev; + + dev = bus_find_device(&i2c_bus_type, NULL, adev, acpi_i2c_match_device); + return dev ? i2c_verify_client(dev) : NULL; +} + +static int acpi_i2c_notify(struct notifier_block *nb, unsigned long value, + void *arg) +{ + struct acpi_device *adev = arg; + struct i2c_board_info info; + acpi_handle adapter_handle; + struct i2c_adapter *adapter; + struct i2c_client *client; + + switch (value) { + case ACPI_RECONFIG_DEVICE_ADD: + if (acpi_i2c_get_info(adev, &info, &adapter_handle)) + break; + + adapter = acpi_i2c_find_adapter_by_handle(adapter_handle); + if (!adapter) + break; + + acpi_i2c_register_device(adapter, adev, &info); + break; + case ACPI_RECONFIG_DEVICE_REMOVE: + if (!acpi_device_enumerated(adev)) + break; + + client = acpi_i2c_find_client_by_adev(adev); + if (!client) + break; + + i2c_unregister_device(client); + put_device(&client->dev); + break; + } + + return NOTIFY_OK; +} + +static struct notifier_block i2c_acpi_notifier = { + .notifier_call = acpi_i2c_notify, +}; #else /* CONFIG_ACPI */ static inline void acpi_i2c_register_devices(struct i2c_adapter *adap) { } +extern struct notifier_block i2c_acpi_notifier; #endif /* CONFIG_ACPI */ #ifdef CONFIG_ACPI_I2C_OPREGION @@ -1089,6 +1182,8 @@ void i2c_unregister_device(struct i2c_client *client) { if (client->dev.of_node) of_node_clear_flag(client->dev.of_node, OF_POPULATED); + if (ACPI_COMPANION(&client->dev)) + acpi_device_clear_enumerated(ACPI_COMPANION(&client->dev)); device_unregister(&client->dev); } EXPORT_SYMBOL_GPL(i2c_unregister_device); @@ -2117,6 +2212,8 @@ static int __init i2c_init(void) if (IS_ENABLED(CONFIG_OF_DYNAMIC)) WARN_ON(of_reconfig_notifier_register(&i2c_of_notifier)); + if (IS_ENABLED(CONFIG_ACPI)) + WARN_ON(acpi_reconfig_notifier_register(&i2c_acpi_notifier)); return 0; @@ -2132,6 +2229,8 @@ bus_err: static void __exit i2c_exit(void) { + if (IS_ENABLED(CONFIG_ACPI)) + WARN_ON(acpi_reconfig_notifier_unregister(&i2c_acpi_notifier)); if (IS_ENABLED(CONFIG_OF_DYNAMIC)) WARN_ON(of_reconfig_notifier_unregister(&i2c_of_notifier)); i2c_del_driver(&dummy_driver); -- 2.7.4 -- To unsubscribe from this list: send the line "unsubscribe linux-spi" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html