From patchwork Fri Sep 5 15:21:06 2014 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: srinivas pandruvada X-Patchwork-Id: 386409 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 0A3A71400B9 for ; Sat, 6 Sep 2014 01:22:52 +1000 (EST) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1756275AbaIEPWm (ORCPT ); Fri, 5 Sep 2014 11:22:42 -0400 Received: from mga09.intel.com ([134.134.136.24]:57704 "EHLO mga09.intel.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1754925AbaIEPWl (ORCPT ); Fri, 5 Sep 2014 11:22:41 -0400 Received: from orsmga002.jf.intel.com ([10.7.209.21]) by orsmga102.jf.intel.com with ESMTP; 05 Sep 2014 08:16:21 -0700 X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="5.04,473,1406617200"; d="scan'208";a="598561543" Received: from spandruv-desktop.jf.intel.com ([10.7.199.74]) by orsmga002.jf.intel.com with ESMTP; 05 Sep 2014 08:21:04 -0700 From: Srinivas Pandruvada To: wsa@the-dreams.de Cc: mika.westerberg@linux.intel.com, linux-i2c@vger.kernel.org, Srinivas Pandruvada Subject: [PATCH v2 2/2] i2c / ACPI: Add support for extracting multiple I2C addresses Date: Fri, 5 Sep 2014 08:21:06 -0700 Message-Id: <1409930466-31746-3-git-send-email-srinivas.pandruvada@linux.intel.com> X-Mailer: git-send-email 1.9.3 In-Reply-To: <1409930466-31746-1-git-send-email-srinivas.pandruvada@linux.intel.com> References: <1409930466-31746-1-git-send-email-srinivas.pandruvada@linux.intel.com> Sender: linux-i2c-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-i2c@vger.kernel.org From: Mika Westerberg Some I2C devices can have multiple addresses and this is allowed by the ACPI specification as well. However, it doesn't specify how these addresses should be interpreted but leaves it up to the OS/drivers to deal with. Since we can't tell in a generic code whether it is a single device with multiple addresses (like I2C attached EEPROM) or a multifunction device, we let the corresponding first level device's driver to decide how to best handle the addresses assigned to it. To make driver writers life a bit easier, we add two new helper functions: i2c_address_by_index() and i2c_num_addressess() which can be used to extract all addresses for a given device. Signed-off-by: Mika Westerberg Signed-off-by: Srinivas Pandruvada --- drivers/i2c/i2c-acpi.c | 107 +++++++++++++++++++++++++++++++++++++++++++++++++ drivers/i2c/i2c-core.c | 61 ++++++++++++++++++++++++++++ include/linux/i2c.h | 8 ++++ 3 files changed, 176 insertions(+) diff --git a/drivers/i2c/i2c-acpi.c b/drivers/i2c/i2c-acpi.c index 83db880..ad4b9f4 100644 --- a/drivers/i2c/i2c-acpi.c +++ b/drivers/i2c/i2c-acpi.c @@ -37,6 +37,113 @@ struct gsb_buffer { }; } __packed; +struct acpi_i2c_lookup { + struct i2c_board_info *info; + struct i2c_adapter *adapter; + bool found; + int index; + int n; +}; + +static int acpi_i2c_match_adapter(struct device *dev, void *data) +{ + return !!i2c_verify_adapter(dev); +} + +static int acpi_i2c_find(struct acpi_resource *ares, void *data) +{ + struct acpi_i2c_lookup *lookup = data; + struct acpi_resource_i2c_serialbus *sb; + struct acpi_device_physical_node *phys; + struct acpi_device *adev; + acpi_status status; + acpi_handle handle; + char *path; + + if (ares->type != ACPI_RESOURCE_TYPE_SERIAL_BUS) + return 1; + + sb = &ares->data.i2c_serial_bus; + if (sb->type != ACPI_RESOURCE_SERIAL_TYPE_I2C) + return 1; + + if (lookup->n++ != lookup->index) + return 1; + + /* + * Need to look up the adapter from ACPI namespace and translate it + * to a physical device. That device should be existing I2C + * adapter. + */ + path = sb->resource_source.string_ptr; + status = acpi_get_handle(ACPI_ROOT_OBJECT, path, &handle); + if (ACPI_FAILURE(status) || acpi_bus_get_device(handle, &adev)) + return 1; + + mutex_lock(&adev->physical_node_lock); + list_for_each_entry(phys, &adev->physical_node_list, node) { + struct device *dev; + + dev = device_find_child(phys->dev, NULL, + acpi_i2c_match_adapter); + if (dev) { + lookup->adapter = to_i2c_adapter(dev); + break; + } + } + mutex_unlock(&adev->physical_node_lock); + + if (!lookup->adapter) + return 1; + + if (lookup->info) { + lookup->info->addr = sb->slave_address; + if (sb->access_mode == ACPI_I2C_10BIT_MODE) + lookup->info->flags |= I2C_CLIENT_TEN; + } + + lookup->found = true; + return 1; +} + +int acpi_i2c_address_by_index(struct i2c_client *client, int index, + struct i2c_board_info *info, + struct i2c_adapter **adapter) +{ + struct acpi_i2c_lookup lookup; + struct acpi_device *adev; + LIST_HEAD(resources); + int ret; + + adev = ACPI_COMPANION(&client->dev); + if (!adev) + return -ENODEV; + + memset(&lookup, 0, sizeof(lookup)); + lookup.index = index; + lookup.info = info; + + ret = acpi_dev_get_resources(adev, &resources, acpi_i2c_find, &lookup); + acpi_dev_free_resource_list(&resources); + if (ret < 0) + return ret; + + if (lookup.found) { + if (!adapter) { + /* + * Caller is not interested in the adapter so we + * will release it now. Otherwise it is up to the + * caller to call put_device() for it. + */ + put_device(&lookup.adapter->dev); + } else { + *adapter = lookup.adapter; + } + } + + return lookup.found ? 0 : -EADDRNOTAVAIL; +} + static int acpi_i2c_smbus_match_reserved_addr(unsigned short addr) { /* diff --git a/drivers/i2c/i2c-core.c b/drivers/i2c/i2c-core.c index 632057a..d024f4a 100644 --- a/drivers/i2c/i2c-core.c +++ b/drivers/i2c/i2c-core.c @@ -1097,6 +1097,67 @@ EXPORT_SYMBOL(of_find_i2c_adapter_by_node); static void of_i2c_register_devices(struct i2c_adapter *adap) { } #endif /* CONFIG_OF */ +/* + * i2c_address_by_index - Return an I2C address for a device + * @client: the client with multiple addresses + * @index: index of the additional address + * @info: pointer to board info which is filled in + * @adapter: adapter on which bus the address is is stored here + * + * In case of an I2C device with multiple addresses this function can be + * used to look up those additional addresses. Returns %0 if the address + * with given @index is found or %-EADDRNOTAVAIL if no such address is + * found. Note that the function returns address of the current @client as + * well so callers need to check against that if necessary. + * + * Note that the caller must do put_device() to the @adapter->dev once it + * is not used anymore. + */ +int i2c_address_by_index(struct i2c_client *client, int index, + struct i2c_board_info *info, + struct i2c_adapter **adapter) +{ + if (IS_ENABLED(CONFIG_ACPI)) + return acpi_i2c_address_by_index(client, index, info, adapter); + + /* + * Normal case means that the address at index zero is the address + * of the current client device. + */ + if (!index) { + if (info) { + info->addr = client->addr; + info->flags = client->flags; + info->irq = client->irq; + } + if (adapter) + *adapter = client->adapter; + + return 0; + } + + return -EADDRNOTAVAIL; +} +EXPORT_SYMBOL_GPL(i2c_address_by_index); + +/* + * i2c_num_addresses - Returns number of addresses given device has + * @client: the client with multiple addresses + * + * An I2C device that has multiple addresses can use this function to find + * out how many of them it has. For single address devices the function + * always returns %1. + */ +size_t i2c_num_addresses(struct i2c_client *client) +{ + size_t n = 0; + + while (!i2c_address_by_index(client, n, NULL, NULL)) + n++; + return n; +} +EXPORT_SYMBOL_GPL(i2c_num_addresses); + static int i2c_do_add_adapter(struct i2c_driver *driver, struct i2c_adapter *adap) { diff --git a/include/linux/i2c.h b/include/linux/i2c.h index a95efeb..6dc917a 100644 --- a/include/linux/i2c.h +++ b/include/linux/i2c.h @@ -322,6 +322,11 @@ extern int i2c_probe_func_quick_read(struct i2c_adapter *, unsigned short addr); extern struct i2c_client * i2c_new_dummy(struct i2c_adapter *adap, u16 address); +extern int i2c_address_by_index(struct i2c_client *client, int index, + struct i2c_board_info *info, + struct i2c_adapter **adapter); +extern size_t i2c_num_addresses(struct i2c_client *client); + extern void i2c_unregister_device(struct i2c_client *); #endif /* I2C */ @@ -579,6 +584,9 @@ static inline struct i2c_adapter *of_find_i2c_adapter_by_node(struct device_node #ifdef CONFIG_ACPI void acpi_i2c_register_devices(struct i2c_adapter *adap); +int acpi_i2c_address_by_index(struct i2c_client *client, int index, + struct i2c_board_info *info, + struct i2c_adapter **adapter); #else static inline void acpi_i2c_register_devices(struct i2c_adapter *adap) { } #endif /* CONFIG_ACPI */