ACPI specification allows multiple i2c addresses defined under one ACPI device object. These addresses are defined using _CRS method. The current implementation will pickup the last entry in _CRS, and create an i2C device, ignoring all other previous entries for addresses. The ACPI specification does not define, whether these addresses for one i2c device or for multiple i2c devices, which are defined under the same ACPI device object. We need some appoach where i2c clients can enumerate on each of the i2C address and/or have access to those extra addresses. This change addresses both cases: - Create a new i2c device for each i2c address - Allow each i2 client to get addresses of all other devices under the same ACPI device object (companions or siblings) Example: Here in the following example, there are two i2C address in _CRS. They belong to two different physical chipsets, with two different i2c address but part of a module. Method (_CRS, 0, NotSerialized) // _CRS: Current Resource Settings { Name (RBUF, ResourceTemplate () { I2cSerialBus (0x0068, ControllerInitiated, 0x00061A80, AddressingMode7Bit, "\\_SB.I2C5", 0x00, ResourceConsumer, , ) I2cSerialBus (0x000C, ControllerInitiated, 0x00061A80, AddressingMode7Bit, "\\_SB.I2C5", 0x00, ResourceConsumer, , ) Interrupt (ResourceConsumer, Level, ActiveHigh, Shared, ,, ) { 0x00000044, } }) Return (RBUF) } This change adds i2c_new_device for each i2c address. Here contents of /sys/bus/i2c/devices will i2c-some_acpi_dev_name:00:068 i2c-some_acpi_dev_name::00:00c Here any i2c client driver simply need to add ACPI bindings. They will be probed multiple times, the client will bind to correct i2c device, based on checking its identity and returning error for other. At the same time, this change also gives any device to see presence of multiple devices, by addition of comp_addrs in the i2c_client struct. This list of additional address is referred as companion addresses. So any client, which needs specification of multiple addresses, they can use all available addresses. To get i2c_client instance, they can use i2c_get_comp_client() for an companion address. By using this i2c_client instance, a client can use i2c interface to send/receive over this companion address. Signed-off-by: Srinivas Pandruvada <srinivas.pandruvada@xxxxxxxxxxxxxxx> --- drivers/i2c/i2c-core.c | 105 +++++++++++++++++++++++++++++++++++++++++-------- include/linux/i2c.h | 30 ++++++++++++++ 2 files changed, 119 insertions(+), 16 deletions(-) diff --git a/drivers/i2c/i2c-core.c b/drivers/i2c/i2c-core.c index 5fb80b8..2d9b4bd 100644 --- a/drivers/i2c/i2c-core.c +++ b/drivers/i2c/i2c-core.c @@ -395,7 +395,9 @@ static int i2c_device_pm_restore(struct device *dev) static void i2c_client_dev_release(struct device *dev) { - kfree(to_i2c_client(dev)); + struct i2c_client *client = to_i2c_client(dev); + kfree(client->comp_addrs); + kfree(client); } static ssize_t @@ -627,7 +629,7 @@ static void i2c_dev_set_name(struct i2c_adapter *adap, struct acpi_device *adev = ACPI_COMPANION(&client->dev); if (adev) { - dev_set_name(&client->dev, "i2c-%s", acpi_dev_name(adev)); + dev_set_name(&client->dev, "i2c-%s", client->name); return; } @@ -673,6 +675,8 @@ i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info) client->flags = info->flags; client->addr = info->addr; client->irq = info->irq; + client->comp_addr_count = info->comp_addr_count; + client->comp_addrs = info->comp_addrs; strlcpy(client->name, info->type, sizeof(client->name)); @@ -726,6 +730,36 @@ void i2c_unregister_device(struct i2c_client *client) } EXPORT_SYMBOL_GPL(i2c_unregister_device); +static int i2c_find_client_addr(struct device *dev, void *addrp) +{ + struct i2c_client *client = i2c_verify_client(dev); + unsigned short addr = *(unsigned short *)addrp; + + if (client && client->addr == addr) + return 1; + + return 0; +} + +struct i2c_client *i2c_get_comp_client(struct i2c_client *client, + unsigned short addr) +{ + struct device *dev = NULL; + + if (!client) + return NULL; + + if (client->addr == addr) + return client; + + dev = device_find_child(&client->adapter->dev, &addr, + i2c_find_client_addr); + if (dev) + return to_i2c_client(dev); + + return NULL; +} +EXPORT_SYMBOL_GPL(i2c_get_comp_client); static const struct i2c_device_id dummy_id[] = { { "dummy", 0 }, @@ -1080,24 +1114,37 @@ static void of_i2c_register_devices(struct i2c_adapter *adap) { } /* ACPI support code */ #if IS_ENABLED(CONFIG_ACPI) +/* Using some arbitary limit for max adresses in resource */ +#define MAX_CRS_ELEMENTS 20 +struct i2c_resource_info { + struct i2c_comp_address addrs[MAX_CRS_ELEMENTS]; + int count; + int common_irq; +}; + static int acpi_i2c_add_resource(struct acpi_resource *ares, void *data) { - struct i2c_board_info *info = data; + struct i2c_resource_info *rcs_info = data; if (ares->type == ACPI_RESOURCE_TYPE_SERIAL_BUS) { struct acpi_resource_i2c_serialbus *sb; sb = &ares->data.i2c_serial_bus; if (sb->type == ACPI_RESOURCE_SERIAL_TYPE_I2C) { - info->addr = sb->slave_address; + if (rcs_info->count >= MAX_CRS_ELEMENTS) + return 1; + rcs_info->addrs[rcs_info->count].addr = + sb->slave_address; if (sb->access_mode == ACPI_I2C_10BIT_MODE) - info->flags |= I2C_CLIENT_TEN; + rcs_info->addrs[rcs_info->count].flags = + I2C_CLIENT_TEN; + rcs_info->count++; } - } else if (info->irq < 0) { + } else if (rcs_info->common_irq < 0) { struct resource r; if (acpi_dev_resource_interrupt(ares, 0, &r)) - info->irq = r.start; + rcs_info->common_irq = r.start; } /* Tell the ACPI core to skip this resource */ @@ -1111,7 +1158,10 @@ static acpi_status acpi_i2c_add_device(acpi_handle handle, u32 level, struct list_head resource_list; struct i2c_board_info info; struct acpi_device *adev; + struct i2c_resource_info rcs_info; + struct i2c_client *i2c_client; int ret; + int i; if (acpi_bus_get_device(handle, &adev)) return AE_OK; @@ -1120,23 +1170,46 @@ static acpi_status acpi_i2c_add_device(acpi_handle handle, u32 level, memset(&info, 0, sizeof(info)); info.acpi_node.companion = adev; - info.irq = -1; + + memset(&rcs_info, 0, sizeof(rcs_info)); + rcs_info.common_irq = -1; INIT_LIST_HEAD(&resource_list); ret = acpi_dev_get_resources(adev, &resource_list, - acpi_i2c_add_resource, &info); + acpi_i2c_add_resource, &rcs_info); acpi_dev_free_resource_list(&resource_list); - if (ret < 0 || !info.addr) + if (ret < 0) return AE_OK; adev->power.flags.ignore_parent = true; - strlcpy(info.type, dev_name(&adev->dev), sizeof(info.type)); - 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)); + info.irq = rcs_info.common_irq; + info.comp_addr_count = rcs_info.count; + for (i = 0; i < rcs_info.count; ++i) { + if (!rcs_info.addrs[i].addr) + continue; + info.addr = rcs_info.addrs[i].addr; + info.flags = rcs_info.addrs[i].flags; + snprintf(info.type, sizeof(info.type), "%s:%03x", + dev_name(&adev->dev), + info.addr); + info.comp_addrs = kmemdup(rcs_info.addrs, + rcs_info.count * + sizeof(struct i2c_comp_address), + GFP_ATOMIC); + if (!info.comp_addrs) + return AE_OK; /* silently ignore */ + + i2c_client = i2c_new_device(adapter, &info); + if (!i2c_client) { + if (!i) + adev->power.flags.ignore_parent = false; + dev_err(&adapter->dev, + "failed to add I2C device %s from ACPI\n", + dev_name(&adev->dev)); + kfree(info.comp_addrs); + break; + } } return AE_OK; diff --git a/include/linux/i2c.h b/include/linux/i2c.h index deddeb8..4197a0d 100644 --- a/include/linux/i2c.h +++ b/include/linux/i2c.h @@ -198,6 +198,19 @@ struct i2c_driver { #define to_i2c_driver(d) container_of(d, struct i2c_driver, driver) /** + * struct i2c_comp_address - represent an I2C companion address + * @addr: i2c 7/10 bit address + * @flags: Same meaning as i2c_client flags field + * + * Some i2c devices specifies multiple addresses on which it can operate. + * This structure is used to represent such address along with flags. + */ +struct i2c_comp_address { + unsigned short addr; + unsigned short flags; +}; + +/** * struct i2c_client - represent an I2C slave device * @flags: I2C_CLIENT_TEN indicates the device uses a ten bit chip address; * I2C_CLIENT_PEC indicates it uses SMBus Packet Error Checking @@ -209,6 +222,9 @@ struct i2c_driver { * @irq: indicates the IRQ generated by this device (if any) * @detected: member of an i2c_driver.clients list or i2c-core's * userspace_devices list + * @comp_addr_count: Number of addresses in comp_addrs pointer + * @comp_addrs: Pointer to companion list of i2c addresses and + * associated flags. * * An i2c_client identifies a single device (i.e. chip) connected to an * i2c bus. The behaviour exposed to Linux is defined by the driver @@ -224,6 +240,8 @@ struct i2c_client { struct device dev; /* the device structure */ int irq; /* irq issued by device */ struct list_head detected; + int comp_addr_count; + struct i2c_comp_address *comp_addrs; }; #define to_i2c_client(d) container_of(d, struct i2c_client, dev) @@ -256,6 +274,9 @@ static inline void i2c_set_clientdata(struct i2c_client *dev, void *data) * @of_node: pointer to OpenFirmware device node * @acpi_node: ACPI device node * @irq: stored in i2c_client.irq + * @comp_addr_count: Number of addresses in comp_addrs pointer + * @comp_addrs: Pointer to companion list of i2c addresses and + * associated flags. * * I2C doesn't actually support hardware probing, although controllers and * devices may be able to use I2C_SMBUS_QUICK to tell whether or not there's @@ -277,6 +298,8 @@ struct i2c_board_info { struct device_node *of_node; struct acpi_dev_node acpi_node; int irq; + int comp_addr_count; + struct i2c_comp_address *comp_addrs; }; /** @@ -323,6 +346,13 @@ extern struct i2c_client * i2c_new_dummy(struct i2c_adapter *adap, u16 address); extern void i2c_unregister_device(struct i2c_client *); + +/* Get i2c_client instance from an address, used for getting companion + * i2c_client instances from the same i2c adapter + */ +extern struct i2c_client *i2c_get_comp_client(struct i2c_client *client, + unsigned short addr); + #endif /* I2C */ /* Mainboard arch_initcall() code should register all its I2C devices. -- 1.8.3.2 -- To unsubscribe from this list: send the line "unsubscribe linux-acpi" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html