From: Kumar Gala Driver for the Phillips pca954x I2C mux/switches devices. These devices handle the fact that a number of I2C devices have limited address selection capablities and systems may end up having to mux to access all the I2C devices. The driver uses the i2c virtual adapter support to make each mux/switch port look like its own i2c bus. Signed-off-by: Kumar Gala Cc: Jean Delvare Cc: Greg KH Signed-off-by: Andrew Morton --- drivers/i2c/chips/Kconfig | 10 + drivers/i2c/chips/Makefile | 1 drivers/i2c/chips/pca954x.c | 320 ++++++++++++++++++++++++++++++++++ include/linux/i2c-id.h | 1 4 files changed, 332 insertions(+) diff -puN drivers/i2c/chips/Kconfig~i2c-pca954x-i2c-mux-driver drivers/i2c/chips/Kconfig --- devel/drivers/i2c/chips/Kconfig~i2c-pca954x-i2c-mux-driver 2006-04-16 17:23:40.000000000 -0700 +++ devel-akpm/drivers/i2c/chips/Kconfig 2006-04-16 17:23:40.000000000 -0700 @@ -56,6 +56,16 @@ config SENSORS_PCA9539 This driver can also be built as a module. If so, the module will be called pca9539. +config SENSORS_PCA954x + tristate "Philips PCA954x I2C Mux/switches" + depends on I2C && I2C_VIRT && EXPERIMENTAL + help + If you say yes here you get support for the Philips PCA954x + I2C mux/switch devices. + + This driver can also be built as a module. If so, the module + will be called pca954x. + config SENSORS_PCF8591 tristate "Philips PCF8591" depends on I2C && EXPERIMENTAL diff -puN drivers/i2c/chips/Makefile~i2c-pca954x-i2c-mux-driver drivers/i2c/chips/Makefile --- devel/drivers/i2c/chips/Makefile~i2c-pca954x-i2c-mux-driver 2006-04-16 17:23:40.000000000 -0700 +++ devel-akpm/drivers/i2c/chips/Makefile 2006-04-16 17:23:40.000000000 -0700 @@ -8,6 +8,7 @@ obj-$(CONFIG_SENSORS_EEPROM) += eeprom.o obj-$(CONFIG_SENSORS_MAX6875) += max6875.o obj-$(CONFIG_SENSORS_M41T00) += m41t00.o obj-$(CONFIG_SENSORS_PCA9539) += pca9539.o +obj-$(CONFIG_SENSORS_PCA954x) += pca954x.o obj-$(CONFIG_SENSORS_PCF8574) += pcf8574.o obj-$(CONFIG_SENSORS_PCF8591) += pcf8591.o obj-$(CONFIG_ISP1301_OMAP) += isp1301_omap.o diff -puN /dev/null drivers/i2c/chips/pca954x.c --- /dev/null 2003-09-15 06:40:47.000000000 -0700 +++ devel-akpm/drivers/i2c/chips/pca954x.c 2006-04-16 17:23:40.000000000 -0700 @@ -0,0 +1,320 @@ +/* + * pca954x.c - Part of lm_sensors, Linux kernel modules for hardware + * monitoring + * This module supports the PCA954x series of I2C multiplexer/switch chips + * made by Philips Semiconductors. This includes the + * PCA9540, PCA9542, PCA9543, PCA9544, PCA9545, PCA9546, PCA9547 and PCA9548. + * + * These chips are all controlled via the I2C bus itself, and all have a + * single 8-bit register (normally at 0x70). The upstream "parent" bus fans + * out to two, four, or eight downstream busses or channels; which of these + * are selected is determined by the chip type and register contents. A + * mux can select only one sub-bus at a time; a switch can select any + * combination simultaneously. + * + * Based on: + * pca954x.c from Ken Harrenstien + * Copyright (C) 2004 Google, Inc. (Ken Harrenstien) + * + * Based on: + * i2c-virtual_cb.c from Brian Kuschak + * and + * pca9540.c from Jean Delvare , which was + * based on pcf8574.c from the same project by Frodo Looijaard, + * Philip Edelbrock, Dan Eaton and Aurelien Jarno. + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#include +#include +#include + +#define PCA954X_MAX_NCHANS 8 + +static struct i2c_driver pca954x_driver; + +/* Addresses to scan: none. These chip cannot be detected. */ +static unsigned short normal_i2c[] = { I2C_CLIENT_END }; + +/* Chip type must normally be specified using a parameter of the form + "force_pca9544=0,0x70" + The following declares the possible types. +*/ +I2C_CLIENT_INSMOD_8(pca9540, pca9542, pca9543, pca9544, + pca9545, pca9546, pca9547, pca9548); + +struct pca954x_chipdef { + enum chips type; + const char *name; + u8 nchans; + u8 enable; /* used for muxes only */ + enum muxtype { pca954x_ismux = 0, pca954x_isswi } muxtype; +}; + +/* Provide specs for the PCA954x types we know about */ +static struct pca954x_chipdef pca954x_chipdefs[] = { + { + .type = pca9540, + .name = "pca9540", + .nchans = 2, + .enable = 0x4, + .muxtype = pca954x_ismux, + }, + { + .type = pca9542, + .name = "pca9542", + .nchans = 2, + .enable = 0x4, + .muxtype = pca954x_ismux, + }, + { + .type = pca9543, + .name = "pca9543", + .nchans = 2, + .enable = 0x0, + .muxtype = pca954x_isswi, + }, + { + .type = pca9544, + .name = "pca9544", + .nchans = 4, + .enable = 0x4, + .muxtype = pca954x_ismux, + }, + { + .type = pca9545, + .name = "pca9545", + .nchans = 4, + .enable = 0x0, + .muxtype = pca954x_isswi, + }, + { + .type = pca9546, + .name = "pca9546", + .nchans = 4, + .enable = 0x0, + .muxtype = pca954x_isswi, + }, + { + .type = pca9547, + .name = "pca9547", + .nchans = 8, + .enable = 0x8, + .muxtype = pca954x_ismux, + }, + { + .type = pca9548, + .name = "pca9548", + .nchans = 8, + .enable = 0x0, + .muxtype = pca954x_isswi, + }, +}; + +struct pca954x_data { + struct i2c_client client; + unsigned int chip_offset; + u8 last_chan; + struct i2c_adapter *virt_adapters[PCA954X_MAX_NCHANS]; +}; + +static int pca954x_xfer(struct i2c_adapter *adap, + struct i2c_client *client, int read_write, u8 * val) +{ + int ret = -ENODEV; + + if (adap->algo->master_xfer) { + struct i2c_msg msg; + char buf[1]; + + msg.addr = client->addr; + msg.flags = (read_write == I2C_SMBUS_READ ? I2C_M_RD : 0); + msg.len = 1; + buf[0] = *val; + msg.buf = buf; + ret = adap->algo->master_xfer(adap, &msg, 1); + } else if (adap->algo->smbus_xfer) { + union i2c_smbus_data data; + ret = adap->algo->smbus_xfer(adap, + client->addr, + client->flags & I2C_M_TEN, + read_write, + *val, I2C_SMBUS_BYTE, &data); + } + + return ret; +} + +static int pca954x_select_chan(struct i2c_adapter *adap, + struct i2c_client *client, u32 chan) +{ + struct pca954x_data *data = i2c_get_clientdata(client); + struct pca954x_chipdef *chip = &pca954x_chipdefs[data->chip_offset]; + u8 regval = 0; + int ret = 0; + + /* we make switches look like muxes, not sure how to be smarter */ + if (chip->muxtype == pca954x_ismux) + regval = chan | chip->enable; + else + regval = 1 << chan; + + /* Only select the channel if its different from the last channel */ + if (data->last_chan != chan) { + ret = pca954x_xfer(adap, client, I2C_SMBUS_WRITE, ®val); + data->last_chan = chan; + } + + return ret; +} + +static int pca954x_deselect_mux(struct i2c_adapter *adap, + struct i2c_client *client, u32 value) +{ + /* We never deselect, just stay on the last channel we selected */ + return 0; +} + +static int pca954x_detect(struct i2c_adapter *bus, int address, int kind) +{ + int i, n; + struct i2c_client *client; + struct pca954x_data *data; + int ret = -ENODEV; + + if (!i2c_check_functionality(bus, I2C_FUNC_SMBUS_BYTE)) + goto err; + + if (!(data = kzalloc(sizeof(struct pca954x_data), GFP_KERNEL))) { + ret = -ENOMEM; + goto err; + } + + client = &data->client; + client->addr = address; + client->adapter = bus; + client->driver = &pca954x_driver; + client->flags = 0; + i2c_set_clientdata(client, data); + if (kind < 0) { + dev_err(&bus->dev, "Attempted ill-advised probe at addr 0x%x\n", + address); + goto exit_free; + } + + /* Read the mux register at addr. This does two things: it verifies + that the mux is in fact present, and fetches its current + contents for possible use with a future deselect algorithm. + */ + if ((i = i2c_smbus_read_byte(client)) < 0) { + dev_warn(&bus->dev, "pca954x failed to read reg at 0x%x\n", + address); + goto exit_free; + } + + if (kind == any_chip) { + dev_warn(&bus->dev, "pca954x needs advice on chip type - " + "wildly guessing 0x%x is a PCA9540\n", address); + kind = pca9540; + } + + /* Look up in table */ + for (i = sizeof(pca954x_chipdefs) / sizeof(pca954x_chipdefs[0]); + --i >= 0;) { + if (pca954x_chipdefs[i].type == kind) + break; + } + if (i < 0) { + dev_err(&bus->dev, "Internal error: unknown kind (%d)\n", kind); + goto exit_free; + } + + data->chip_offset = i; + + if ((ret = i2c_attach_client(client))) + goto exit_free; + + /* Now create virtual busses and adapters for them */ + for (i = 0; i < pca954x_chipdefs[data->chip_offset].nchans; i++) { + data->virt_adapters[i] = + i2c_add_virt_adapter(bus, client, i, + &pca954x_select_chan, + &pca954x_deselect_mux); + if (data->virt_adapters[i] == NULL) { + ret = -ENODEV; + goto virt_reg_failed; + } + } + + dev_info(&client->dev, + "Registered %d virtual busses for I2C %s %s\n", i, + pca954x_chipdefs[data->chip_offset].muxtype == pca954x_ismux ? + "mux" : "switch", pca954x_chipdefs[data->chip_offset].name); + + return 0; + +virt_reg_failed: + for (n = 0; n < i; n++) + i2c_del_virt_adapter(data->virt_adapters[n]); + i2c_detach_client(client); +exit_free: + kfree(data); +err: + return ret; +} + +static int pca954x_attach_adapter(struct i2c_adapter *adapter) +{ + return i2c_probe(adapter, &addr_data, pca954x_detect); +} + +static int pca954x_detach_client(struct i2c_client *client) +{ + struct pca954x_data *data = i2c_get_clientdata(client); + struct pca954x_chipdef *chip = &pca954x_chipdefs[data->chip_offset]; + int i, err; + + for (i = 0; i < chip->nchans; ++i) { + if (data->virt_adapters[i]) { + if ((err = + i2c_del_virt_adapter(data->virt_adapters[i]))) + return err; + data->virt_adapters[i] = NULL; + } + } + + if ((err = i2c_detach_client(client))) + return err; + + kfree(data); + return 0; +} + +static struct i2c_driver pca954x_driver = { + .driver = { + .name = "pca954x", + }, + .id = I2C_DRIVERID_PCA954X, + .attach_adapter = pca954x_attach_adapter, + .detach_client = pca954x_detach_client, +}; + +static int __init pca954x_init(void) +{ + return i2c_add_driver(&pca954x_driver); +} + +static void __exit pca954x_exit(void) +{ + i2c_del_driver(&pca954x_driver); +} + +module_init(pca954x_init); +module_exit(pca954x_exit); + +MODULE_AUTHOR("Kumar Gala "); +MODULE_DESCRIPTION("PCA954X I2C mux/switch driver"); +MODULE_LICENSE("GPL"); diff -puN include/linux/i2c-id.h~i2c-pca954x-i2c-mux-driver include/linux/i2c-id.h --- devel/include/linux/i2c-id.h~i2c-pca954x-i2c-mux-driver 2006-04-16 17:23:40.000000000 -0700 +++ devel-akpm/include/linux/i2c-id.h 2006-04-16 17:23:40.000000000 -0700 @@ -114,6 +114,7 @@ #define I2C_DRIVERID_RS5C372 84 /* Ricoh RS5C372 RTC */ #define I2C_DRIVERID_BT866 85 /* Conexant bt866 video encoder */ #define I2C_DRIVERID_KS0127 86 /* Samsung ks0127 video decoder */ +#define I2C_DRIVERID_PCA954X 87 /* pca954x I2C mux/switch */ #define I2C_DRIVERID_I2CDEV 900 #define I2C_DRIVERID_ARP 902 /* SMBus ARP Client */ _