Patchwork [RFC,1/2] i2c/ACPI: Support for multiple serial bus addresses

login
register
mail settings
Submitter Srinivas Pandruvada
Date March 18, 2014, 8:59 p.m.
Message ID <1395176361-3956-1-git-send-email-srinivas.pandruvada@linux.intel.com>
Download mbox | patch
Permalink /patch/331536/
State Superseded
Headers show

Comments

Srinivas Pandruvada - March 18, 2014, 8:59 p.m.
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@linux.intel.com>
---
 drivers/i2c/i2c-core.c | 105 +++++++++++++++++++++++++++++++++++++++++--------
 include/linux/i2c.h    |  30 ++++++++++++++
 2 files changed, 119 insertions(+), 16 deletions(-)

Patch

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.