From patchwork Fri Jul 1 20:19:08 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Octavian Purdila X-Patchwork-Id: 643218 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 3rh78r3qtNz9t0G for ; Sat, 2 Jul 2016 06:21:16 +1000 (AEST) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1752565AbcGAUUg (ORCPT ); Fri, 1 Jul 2016 16:20:36 -0400 Received: from mail-wm0-f65.google.com ([74.125.82.65]:33379 "EHLO mail-wm0-f65.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1752038AbcGAUUH (ORCPT ); Fri, 1 Jul 2016 16:20:07 -0400 Received: by mail-wm0-f65.google.com with SMTP id r201so7592047wme.0; Fri, 01 Jul 2016 13:19:41 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20130820; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=BoH1VwOQd251sHRE7eTE6xIXW6L25dnk+glkRIjkimY=; b=O80zIW5zf9qx2/N7PH+NUAyf7A5QjAQ2Oks47mBDOMFKt+gM3EkC0CepRwtQjq4wyi Y/aVHX4V4rb1WKSvuTpiJ2+alL9/hQtlghR5q/XutEzyzsezcvnfUiwB6NatYYSoX1o/ 5p049AByoWNei8uJtwld0NFt5B/JPFr4MbPivWc1d2c1odq/b/473qyDrwCyH0eqc8a2 tj0Dgy7msYX/l0/bdk708XvJg3Ulm5LCpIa1rkRSwoC2q0SYQ8YgUyhzZkaa5hif4ISq jf4xlmbkF1qOjfYN2QPvCby3Bjkd0v68yv1KwUapqVjK/7ClTrCD0dSJDrszK8QVXhco Fzjw== X-Gm-Message-State: ALyK8tJzP31GUPESEqIKOXtdf2rc+j6E3z9H2VP1D/1fhu1u6tgIGRyTsEL1/BzA+9+uIQ== X-Received: by 10.195.10.131 with SMTP id ea3mr76736wjd.86.1467404380296; Fri, 01 Jul 2016 13:19:40 -0700 (PDT) Received: from localhost.localdomain ([109.98.36.115]) by smtp.gmail.com with ESMTPSA id zb9sm3890411wjc.34.2016.07.01.13.19.38 (version=TLS1_2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Fri, 01 Jul 2016 13:19:39 -0700 (PDT) From: Octavian Purdila To: "Rafael J . Wysocki" , Len Brown , Matt Fleming , Mark Brown , Wolfram Sang Cc: Joel Becker , linux-acpi@vger.kernel.org, linux-efi@vger.kernel.org, linux-i2c@vger.kernel.org, linux-spi@vger.kernel.org, linux-kernel@vger.kernel.org, irina.tirdea@intel.com, leonard.crestez@intel.com, Octavian Purdila Subject: [PATCH v5 4/8] i2c: add support for ACPI reconfigure notifications Date: Fri, 1 Jul 2016 23:19:08 +0300 Message-Id: <1467404352-27101-5-git-send-email-octavian.purdila@intel.com> X-Mailer: git-send-email 2.7.4 In-Reply-To: <1467404352-27101-1-git-send-email-octavian.purdila@intel.com> References: <1467404352-27101-1-git-send-email-octavian.purdila@intel.com> Sender: linux-i2c-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-i2c@vger.kernel.org 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 Reviewed-by: Mika Westerberg Acked-by: Wolfram Sang --- 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);