Add an i2c listener mechanism to the i2c-subsystem. This mechanism lets device drivers be notified of addition and removal of adapters, and gives them a chance to detect devices they would support and to ask i2c-core to instantiate them.
Signed-off-by: Jean Delvare <[EMAIL PROTECTED]> --- This patch depends on: http://khali.linux-fr.org/devel/linux-2.6/jdelvare-i2c/i2c-use-class_for_each_device.patch Implementation note: i2c_detect and i2c_detect_address are basically clones of i2c_probe and i2c_probe_address but operating on a callback with a slightly different prototype. This duplicated code accounts for a large part of the patch, but once legacy drivers are gone, i2c_probe and i2c_probe_address will be removed, so thus is no long-term duplication. drivers/i2c/i2c-core.c | 235 ++++++++++++++++++++++++++++++++++++++++++++++++ include/linux/i2c.h | 34 ++++++ 2 files changed, 269 insertions(+) --- linux-2.6.26-rc4.orig/include/linux/i2c.h 2008-06-04 08:26:05.000000000 +0200 +++ linux-2.6.26-rc4/include/linux/i2c.h 2008-06-04 08:26:05.000000000 +0200 @@ -43,6 +43,7 @@ struct i2c_adapter; struct i2c_client; struct i2c_driver; union i2c_smbus_data; +struct i2c_board_info; /* * The master routines are the ones normally used to transmit data to devices @@ -92,6 +93,34 @@ extern s32 i2c_smbus_write_i2c_block_dat u8 command, u8 length, const u8 *values); +/** + * struct i2c_listener - Be informed of adapter addition and removal + * @class: What kind of i2c device the listener may instantiate + * @attach_adapter: Called after adapter addition (unimplemented) + * @detach_adapter: Called before adapter removal (unimplemented) + * @address_data: The I2C addresses to probe, ignore or force + * @detect: Callback for device detection + * @clients: List of detected clients we created + * + * The @detect callback returns an i2c_board_info structure for device + * creation if the detection was successful, NULL otherwise. + * + * The @clients list is handled by i2c-core. + */ +struct i2c_listener { + unsigned int class; + + int (*attach_adapter)(struct i2c_adapter *); + int (*detach_adapter)(struct i2c_adapter *); + + const struct i2c_client_address_data *address_data; + int (*detect)(struct i2c_adapter *, int address, int kind, + struct i2c_board_info *); + struct list_head clients; + + struct list_head list; +}; + /* * A driver is capable of handling one or more physical devices present on * I2C adapters. This information is used to inform the driver of adapter @@ -155,6 +184,7 @@ struct i2c_driver { * @dev: Driver model device node for the slave. * @irq: indicates the IRQ generated by this device (if any) * @list: list of active/busy clients (DEPRECATED) + * @detected: member of i2c_listener.clients * @released: used to synchronize client releases & detaches and references * * An i2c_client identifies a single device (i.e. chip) connected to an @@ -172,6 +202,7 @@ struct i2c_client { struct device dev; /* the device structure */ int irq; /* irq issued by device (or -1) */ struct list_head list; /* DEPRECATED */ + struct list_head detected; struct completion released; }; #define to_i2c_client(d) container_of(d, struct i2c_client, dev) @@ -382,6 +413,9 @@ extern int i2c_add_adapter(struct i2c_ad extern int i2c_del_adapter(struct i2c_adapter *); extern int i2c_add_numbered_adapter(struct i2c_adapter *); +extern void i2c_add_listener(struct i2c_listener *); +extern void i2c_del_listener(struct i2c_listener *); + extern int i2c_register_driver(struct module *, struct i2c_driver *); extern void i2c_del_driver(struct i2c_driver *); --- linux-2.6.26-rc4.orig/drivers/i2c/i2c-core.c 2008-06-04 08:26:05.000000000 +0200 +++ linux-2.6.26-rc4/drivers/i2c/i2c-core.c 2008-06-04 11:38:02.000000000 +0200 @@ -42,8 +42,16 @@ static DEFINE_MUTEX(core_lock); static DEFINE_IDR(i2c_adapter_idr); +/* listeners_lock protects the listeners list. If you need both core_lock + and listeners_lock, core_lock must be taken first. */ +static DEFINE_MUTEX(listeners_lock); +static LIST_HEAD(listeners); + #define is_newstyle_driver(d) ((d)->probe || (d)->remove) +static int i2c_detect(struct i2c_adapter *adapter, + struct i2c_listener *listener); + /* ------------------------------------------------------------------------- */ static const struct i2c_device_id *i2c_match_id(const struct i2c_device_id *id, @@ -418,6 +426,7 @@ static int i2c_do_add_adapter(struct dev static int i2c_register_adapter(struct i2c_adapter *adap) { + struct i2c_listener *listener; int res = 0, dummy; mutex_init(&adap->bus_lock); @@ -448,6 +457,13 @@ static int i2c_register_adapter(struct i if (adap->nr < __i2c_first_dynamic_bus_num) i2c_scan_static_board_info(adap); + /* let listeners know that a bus has arrived */ + mutex_lock(&listeners_lock); + list_for_each_entry(listener, &listeners, list) { + i2c_detect(adap, listener); + } + mutex_unlock(&listeners_lock); + /* let legacy drivers scan this bus for matching devices */ dummy = bus_for_each_drv(&i2c_bus_type, NULL, adap, i2c_do_add_adapter); @@ -576,6 +592,7 @@ static int i2c_do_del_adapter(struct dev int i2c_del_adapter(struct i2c_adapter *adap) { struct i2c_client *client, *_n; + struct i2c_listener *listener; int res = 0; mutex_lock(&core_lock); @@ -588,6 +605,21 @@ int i2c_del_adapter(struct i2c_adapter * goto out_unlock; } + /* Walk the listeners to remove the devices we created ourselves */ + mutex_lock(&listeners_lock); + list_for_each_entry(listener, &listeners, list) { + list_for_each_entry_safe(client, _n, &listener->clients, + detected) { + if (client->adapter == adap) { + dev_dbg(&adap->dev, "Removing %s at 0x%x\n", + client->name, client->addr); + list_del(&client->detected); + i2c_unregister_device(client); + } + } + } + mutex_unlock(&listeners_lock); + /* Tell drivers about this removal */ res = bus_for_each_drv(&i2c_bus_type, NULL, adap, i2c_do_del_adapter); @@ -752,6 +784,53 @@ EXPORT_SYMBOL(i2c_del_driver); /* ------------------------------------------------------------------------- */ +static int __i2c_detect(struct device *dev, void *data) +{ + struct i2c_adapter *adapter = to_i2c_adapter(dev); + struct i2c_listener *listener = data; + + i2c_detect(adapter, listener); + + return 0; +} + +void i2c_add_listener(struct i2c_listener *listener) +{ + mutex_lock(&core_lock); + mutex_lock(&listeners_lock); + INIT_LIST_HEAD(&listener->clients); + list_add_tail(&listener->list, &listeners); + pr_debug("i2c: Listener %p added\n", listener); + + /* let this listener know about buses that are already there */ + class_for_each_device(&i2c_adapter_class, listener, __i2c_detect); + mutex_unlock(&listeners_lock); + mutex_unlock(&core_lock); +} +EXPORT_SYMBOL_GPL(i2c_add_listener); + +void i2c_del_listener(struct i2c_listener *listener) +{ + struct i2c_client *client, *_n; + + mutex_lock(&listeners_lock); + /* remove all clients that originate from this listener */ + list_for_each_entry_safe(client, _n, &listener->clients, detected) { + dev_dbg(&client->adapter->dev, "Removing %s at 0x%x\n", + client->name, client->addr); + list_del(&client->detected); + i2c_unregister_device(client); + } + + list_del(&listener->list); + pr_debug("i2c: Listener %p deleted\n", listener); + mutex_unlock(&listeners_lock); + +} +EXPORT_SYMBOL_GPL(i2c_del_listener); + +/* ------------------------------------------------------------------------- */ + static int __i2c_check_addr(struct device *dev, void *addrp) { struct i2c_client *client = i2c_verify_client(dev); @@ -1196,6 +1275,162 @@ int i2c_probe(struct i2c_adapter *adapte } EXPORT_SYMBOL(i2c_probe); +/* Separate detection function for listeners */ +static int i2c_detect_address(struct i2c_adapter *adapter, + int addr, int kind, + struct i2c_listener *listener) +{ + struct i2c_board_info info; + struct i2c_client *client; + int err; + + /* Make sure the address is valid */ + if (addr < 0x03 || addr > 0x77) { + dev_warn(&adapter->dev, "Invalid probe address 0x%02x\n", + addr); + return -EINVAL; + } + + /* Skip if already in use */ + if (i2c_check_addr(adapter, addr)) + return 0; + + /* Make sure there is something at this address, unless forced */ + if (kind < 0) { + if (i2c_smbus_xfer(adapter, addr, 0, 0, 0, + I2C_SMBUS_QUICK, NULL) < 0) + return 0; + + /* prevent 24RF08 corruption */ + if ((addr & ~0x0f) == 0x50) + i2c_smbus_xfer(adapter, addr, 0, 0, 0, + I2C_SMBUS_QUICK, NULL); + } + + /* Finally call the custom detection function */ + memset(&info, 0, sizeof(struct i2c_board_info)); + err = listener->detect(adapter, addr, kind, &info); + if (err) { + /* -ENODEV can be returned if there is a chip at the given address + but it isn't supported by this chip driver. We catch it here as + this isn't an error. */ + return err == -ENODEV ? 0 : err; + } + + /* Consistency check */ + if (info.type[0] == '\0' || info.addr != addr) { + dev_err(&adapter->dev, "Detection function returned " + "inconsistent data for 0x%x\n", addr); + } else { + /* Detection succeeded, instantiate the device */ + dev_dbg(&adapter->dev, "Creating %s at 0x%x\n", + info.type, info.addr); + client = i2c_new_device(adapter, &info); + list_add_tail(&client->detected, &listener->clients); + } + return 0; +} + +static int i2c_detect(struct i2c_adapter *adapter, + struct i2c_listener *listener) +{ + const struct i2c_client_address_data *address_data; + int i, err; + int adap_id = i2c_adapter_id(adapter); + + address_data = listener->address_data; + + /* Force entries are done first, and are not affected by ignore + entries */ + if (address_data->forces) { + const unsigned short * const *forces = address_data->forces; + int kind; + + for (kind = 0; forces[kind]; kind++) { + for (i = 0; forces[kind][i] != I2C_CLIENT_END; + i += 2) { + if (forces[kind][i] == adap_id + || forces[kind][i] == ANY_I2C_BUS) { + dev_dbg(&adapter->dev, "found force " + "parameter for adapter %d, " + "addr 0x%02x, kind %d\n", + adap_id, forces[kind][i + 1], + kind); + err = i2c_detect_address(adapter, + forces[kind][i + 1], + kind, listener); + if (err) + return err; + } + } + } + } + + /* Stop here if we can't use SMBUS_QUICK */ + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_QUICK)) { + if (address_data->probe[0] == I2C_CLIENT_END + && address_data->normal_i2c[0] == I2C_CLIENT_END) + return 0; + + dev_warn(&adapter->dev, "SMBus Quick command not supported, " + "can't probe for chips\n"); + return -EOPNOTSUPP; + } + + /* Probe entries are done second, and are not affected by ignore + entries either */ + for (i = 0; address_data->probe[i] != I2C_CLIENT_END; i += 2) { + if (address_data->probe[i] == adap_id + || address_data->probe[i] == ANY_I2C_BUS) { + dev_dbg(&adapter->dev, "found probe parameter for " + "adapter %d, addr 0x%02x\n", adap_id, + address_data->probe[i + 1]); + err = i2c_detect_address(adapter, + address_data->probe[i + 1], + -1, listener); + if (err) + return err; + } + } + + /* Stop here if the classes do not match */ + if (!(adapter->class & listener->class)) + return 0; + + /* Normal entries are done last, unless shadowed by an ignore entry */ + for (i = 0; address_data->normal_i2c[i] != I2C_CLIENT_END; i += 1) { + int j, ignore; + + ignore = 0; + for (j = 0; address_data->ignore[j] != I2C_CLIENT_END; + j += 2) { + if ((address_data->ignore[j] == adap_id || + address_data->ignore[j] == ANY_I2C_BUS) + && address_data->ignore[j + 1] + == address_data->normal_i2c[i]) { + dev_dbg(&adapter->dev, "found ignore " + "parameter for adapter %d, " + "addr 0x%02x\n", adap_id, + address_data->ignore[j + 1]); + ignore = 1; + break; + } + } + if (ignore) + continue; + + dev_dbg(&adapter->dev, "found normal entry for adapter %d, " + "addr 0x%02x\n", adap_id, + address_data->normal_i2c[i]); + err = i2c_detect_address(adapter, address_data->normal_i2c[i], + -1, listener); + if (err) + return err; + } + + return 0; +} + struct i2c_client * i2c_new_probed_device(struct i2c_adapter *adap, struct i2c_board_info *info, -- Jean Delvare _______________________________________________ i2c mailing list [email protected] http://lists.lm-sensors.org/mailman/listinfo/i2c
