[lm-sensors] at24c i2c EEPROM driver

david-b at pacbell.net david-b at pacbell.net
Sat Jul 16 03:21:30 CEST 2005


> Date: Wed, 13 Jul 2005 16:06:48 -0700
> From: david-b at pacbell.net
>
> > > However, the REALLY nice way to do this would be if the I2C bus
> > > framework had a clean way to handle static device configuration
> > > (like "platform_bus" does).  Then such a flag could be just one
> > > of several bits of information provided to driver using driver
> > > model infrastructure like platform_data.
> >
> > Where are the patches? ;)
>
> The only bright idea I've had so far involves board-specific code
> providing a platform pseudo-device whose platform_data would be
> morphed (by a "pseudo-driver", registered and deregistered before
> the "at24c" i2c driver) into the addr_data (and coupled fields) that
> are currently used to init that i2c driver.
>
> That's a bit hacky though.  Better if there were some real device
> node that the at24c driver would bind to ... which would sort of
> turn the current I2C model on its head.  
>
> Or maybe not ... maybe a static I2C device tree could just be set
> up early by board-specific code, and the I2C infrastructure could
> view it as an alternate way to specify more "probe" or "force"
> entries, without using module params.

Yeah, the best way to do that is really going to involve turning
the I2C stack a bit on its head ... making it work more like most
other Linux driver stacks.

That is, significant API surgery ... stuff I suspect folk believe
is needed in any case, but still it's a lot of change.

One way to start thinking about that is to look at the attached
patch (NOT suitable for merging!  Though it does solve the problem)
and see how the new probe() gets used.  And then observe, "hey, if
i2c core creates and registers the i2c client, like it would in
other Linux bus frameworks, and then passed it into this routine...
then the second two parameters would be implicit."   A significant
change to the driver's role.

- Dave


This is an experimental patch for system-specific static config
support in the I2C framework.

    - New i2c core interfaces:
	* New driver probe() method, with platform string data
	* Board-specific data storage for i2c
    - OSK board init provides static i2c config data
    - at24c implements the new probe()
    - i2c-core uses it
	* retrieves the new device descriptions
	* new probe only after attach() gets a first whack
    - new static i2c code implements it
	* optional, CONFIG_I2C_BOOTDATA
	* get/set device record array

It might be better push the driver model deeper into this patch, but
that would involve formally adding a lifecycle where the device
drivers are no longer responsible for creating device nodes (which
are embedded in a "struct i2c_client").

--- h2.orig/include/linux/i2c.h	2005-07-15 17:50:08.000000000 -0700
+++ h2/include/linux/i2c.h	2005-07-15 17:50:14.000000000 -0700
@@ -129,6 +129,11 @@
 	 */
 	int (*command)(struct i2c_client *client,unsigned int cmd, void *arg);
 
+	/* set up devices and drivers according to system description
+	 * tables; see <linux/i2c-config.h>
+	 */
+	void (*probe)(struct i2c_adapter *adapter, int address, char *data);
+
 	struct device_driver driver;
 	struct list_head list;
 };
--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ h2/include/linux/i2c-config.h	2005-07-15 17:50:14.000000000 -0700
@@ -0,0 +1,56 @@
+#ifndef	__I2C_CONFIG_H
+#define	__I2C_CONFIG_H
+
+
+	/* DRAFT/RFC for some static config support for I2C
+	 *
+	 * This preserves current I2C probe model characteristic that
+	 * i2c device drivers are responsible for creating devices.
+	 *
+	 * Otherwise, platform_device.dev.platform_data is the usage
+	 * model for the new data being provided to drivers from
+	 * board-specific initialization.
+	 */
+
+/* Board init code can supply i2c core a table describing devices,
+ * and providing driver-specific setup data for a given device.
+ *
+ * These act like "probe" entries in addr_data, except that they
+ * include device-specific configuration data.
+ */
+struct i2c_device_record {
+	char		driver_name[32];
+	int		adapter_id;
+	u16		dev_num;
+
+	/* data[] will be passed to a new probe() driver entrypoint.
+	 *  - if probe of address dev_num at adapter_id succeeds
+	 *  - and the driver named "driver_name" is registered
+	 */
+	char		data[26];
+
+	/* REVISIT:  for passing a string, this is probably enough.
+	 * But functions and binary data will be needed too...
+	 */
+};
+
+#ifdef	CONFIG_I2C_BOOTDATA
+extern int i2c_set_boot_data(const struct i2c_device_record *, unsigned);
+extern unsigned i2c_get_boot_data(struct i2c_device_record **);
+
+#else
+static inline int
+i2c_set_boot_data(const struct i2c_device_record *data, unsigned n)
+{
+	return -ENOSYS;
+}
+
+static inline unsigned i2c_get_boot_data(struct i2c_device_record **pp)
+{
+	*pp = NULL;
+	return 0;
+}
+
+#endif	/* I2C_BOOTDATA */
+
+#endif	/* __I2C_CONFIG_H */
--- h2.orig/drivers/i2c/chips/at24c.c	2005-07-15 17:50:08.000000000 -0700
+++ h2/drivers/i2c/chips/at24c.c	2005-07-15 17:50:14.000000000 -0700
@@ -639,6 +639,7 @@
 }
 
 
+/* "legacy" i2c driver api entry:  bind to bus, not device */
 static int __devinit at24c_attach_adapter(struct i2c_adapter *adapter)
 {
 	/* REVISIT: using SMBUS calls would improve portability, though
@@ -652,6 +653,20 @@
 	return i2c_probe(adapter, &addr_data, at24c_old_probe);
 }
 
+/* experimental static config support: bind to device */
+static void __devinit
+at24c_probe(struct i2c_adapter *adapter, int address, char *data)
+{
+	const struct chip_desc		*chip;
+
+	chip = find_chip(data);
+	if (!chip)
+		dev_err(&adapter->dev, "unknown chipname %s\n", data);
+	else
+		at24c_activate(adapter, address, chip,
+				!(chip->flags & EE_READONLY));
+}
+
 static int __devexit at24c_detach_client(struct i2c_client *client)
 {
 	int			err;
@@ -678,11 +693,13 @@
 	.flags		= I2C_DF_NOTIFY,
 	.attach_adapter	= at24c_attach_adapter,
 	.detach_client	= __devexit_p(at24c_detach_client),
+	.probe		= at24c_probe,
 };
 
 static int __init at24c_init(void)
 {
-#ifdef CONFIG_OMAP_OSK_MISTRAL
+#if 0
+//#ifdef CONFIG_OMAP_OSK_MISTRAL
 	/* REVISIT ...  */
 	if (machine_is_omap_osk()) {
 		n_chip_names = 1;
--- h2.orig/arch/arm/mach-omap1/board-osk.c	2005-07-15 17:50:08.000000000 -0700
+++ h2/arch/arm/mach-omap1/board-osk.c	2005-07-15 17:50:14.000000000 -0700
@@ -34,6 +34,8 @@
 #include <linux/mtd/mtd.h>
 #include <linux/mtd/partitions.h>
 
+#include <linux/i2c-config.h>
+
 #include <asm/hardware.h>
 #include <asm/mach-types.h>
 #include <asm/mach/arch.h>
@@ -191,6 +193,31 @@
 	{ OMAP_TAG_USB,           &osk_usb_config },
 };
 
+
+static struct i2c_device_record i2c_devs[] __initdata = {
+{
+	/* this autprobes OK, but there are several chip variants */
+	.driver_name	= "tps65010",
+	.adapter_id	= 0,
+	.dev_num	= 0x48,
+	.data 		= "tps65010",	/* vs 65013 etc */
+}, {
+	.driver_name	= "tlv320aic23",
+	.adapter_id	= 0,
+	.dev_num	= 0x1b,
+	// clock setup is board-specific ...
+},
+#ifdef	CONFIG_OMAP_OSK_MISTRAL
+{
+	.driver_name	= "at24c",
+	.adapter_id	= 0,
+	.dev_num	= 0x50,
+	.data 		= "24c04",
+},
+	/* the camera connector may also have an ov9640 sensor */
+#endif
+};
+
 #ifdef	CONFIG_OMAP_OSK_MISTRAL
 
 #ifdef	CONFIG_PM
@@ -248,6 +275,8 @@
 	omap_board_config_size = ARRAY_SIZE(osk_config);
 	USB_TRANSCEIVER_CTRL_REG |= (3 << 1);
 
+	i2c_set_boot_data(i2c_devs, ARRAY_SIZE(i2c_devs));
+
 	osk_mistral_init();
 }
 
--- h2.orig/drivers/i2c/i2c-core.c	2005-07-15 17:50:08.000000000 -0700
+++ h2/drivers/i2c/i2c-core.c	2005-07-15 17:50:14.000000000 -0700
@@ -26,6 +26,7 @@
 #include <linux/errno.h>
 #include <linux/slab.h>
 #include <linux/i2c.h>
+#include <linux/i2c-config.h>
 #include <linux/init.h>
 #include <linux/idr.h>
 #include <linux/seq_file.h>
@@ -138,6 +139,47 @@
  * --------------------------------------------------- 
  */
 
+#ifdef	CONFIG_I2C_BOOTDATA
+
+/* static board config data -- e.g. what type of eeprom chip, how
+ * the chip's irqs are wired, etc -- can be passed to drivers.
+ */
+static struct i2c_device_record		*records;
+static unsigned				nrecords;
+
+static void maybe_probe(struct i2c_adapter *adap, struct i2c_driver *driver)
+{
+	unsigned	i;
+	u16		addr;
+
+	for (i = 0; i < nrecords; i++) {
+		if (records[i].adapter_id != i2c_adapter_id(adap))
+			continue;
+		if (strncmp(records[i].driver_name, driver->name,
+					sizeof driver->name) != 0)
+			continue;
+
+		/* Skip if already in use */
+		addr = records[i].dev_num;
+		if (i2c_check_addr(adap, addr))
+			continue;
+
+		/* if hardware probe succeeds, then tell the software */
+		if (i2c_smbus_xfer(adap,addr,0,0,0,I2C_SMBUS_QUICK,NULL) >= 0)
+			driver->probe(adap, addr, records[i].data);
+	}
+}
+
+#else
+
+static inline void
+maybe_probe(struct i2c_adapter *adap, struct i2c_driver *driver)
+{
+	/* NOP */
+}
+
+#endif
+
 /* -----
  * i2c_add_adapter is called from within the algorithm layer,
  * when a new hw adapter registers. A new device is register to be
@@ -194,6 +236,11 @@
 		if (driver->flags & I2C_DF_NOTIFY)
 			/* We ignore the return code; if it fails, too bad */
 			driver->attach_adapter(adap);
+
+		/* fallback to static config records, where available */
+		if (!driver->probe)
+			continue;
+		maybe_probe(adap, driver);
 	}
 
 	dev_dbg(&adap->dev, "registered as adapter #%d\n", adap->nr);
@@ -313,6 +360,14 @@
 		}
 	}
 
+	/* fallback to static config records, where relevant */
+	if (driver->probe) {
+		list_for_each(item,&adapters) {
+			adapter = list_entry(item, struct i2c_adapter, list);
+			maybe_probe(adapter, driver);
+		}
+	}
+
  out_unlock:
 	up(&core_lists);
 	return res;
@@ -557,6 +612,10 @@
 {
 	int retval;
 
+#ifdef	CONFIG_I2C_BOOTDATA
+	nrecords = i2c_get_boot_data(&records);
+#endif
+
 	retval = bus_register(&i2c_bus_type);
 	if (retval)
 		return retval;
--- h2.orig/drivers/i2c/Kconfig	2005-07-15 17:50:08.000000000 -0700
+++ h2/drivers/i2c/Kconfig	2005-07-15 17:50:14.000000000 -0700
@@ -74,5 +74,13 @@
 	  a problem with I2C support and want to see more of what is going
 	  on.
 
+# for testing, this is always forced on ...  systems that want to
+# pass board data this way should eventually enable it selectively
+
+config I2C_BOOTDATA
+	bool
+	depends on I2C
+	default y
+
 endmenu
 
--- h2.orig/drivers/i2c/Makefile	2005-07-15 17:50:08.000000000 -0700
+++ h2/drivers/i2c/Makefile	2005-07-15 17:50:14.000000000 -0700
@@ -5,6 +5,7 @@
 obj-$(CONFIG_I2C)		+= i2c-core.o
 obj-$(CONFIG_I2C_CHARDEV)	+= i2c-dev.o
 obj-$(CONFIG_I2C_SENSOR)	+= i2c-sensor.o
+obj-$(CONFIG_I2C_BOOTDATA)	+= i2c-boot.o
 obj-y				+= busses/ chips/ algos/
 
 i2c-sensor-objs := i2c-sensor-detect.o i2c-sensor-vid.o
--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ h2/drivers/i2c/i2c-boot.c	2005-07-15 17:57:23.000000000 -0700
@@ -0,0 +1,45 @@
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/i2c-config.h>
+
+static struct i2c_device_record *records;
+static unsigned nrecords;
+
+int __init i2c_set_boot_data(const struct i2c_device_record *data, unsigned len)
+{
+	if (nrecords)
+		return -EBUSY;
+
+	/* boot code should call this with __initdata; must copy */
+	records = kcalloc(nrecords, sizeof *records, SLAB_KERNEL);
+	if (!records)
+		return -ENOMEM;
+	nrecords = len;
+	memcpy(records, data, nrecords * sizeof (*records));
+	return 0;
+}
+EXPORT_SYMBOL(i2c_set_boot_data);
+
+unsigned i2c_get_boot_data(struct i2c_device_record **datap)
+{
+	*datap = records;
+	return nrecords;
+}
+EXPORT_SYMBOL(i2c_get_boot_data);
+
+/*
+ * That's not much code, but it could be generalized something like
+ *
+ * static inline unsigned i2c_get_boot_data(struct ... **datap)
+ * {
+ *	return bus_get_boot_data("i2c", datap);
+ * }
+ *
+ * and likewise for setting it.  Another approach would use an
+ * add_boot_data() model.  Maybe the best approach would be to
+ * use the driver model in more typical ways, and store this
+ * information in i2c_client.dev.platform_data from the moment
+ * the board-specific code creates that i2c_client node.
+ */




More information about the lm-sensors mailing list