diff --git a/doc/busses/i2c-virtual b/doc/busses/i2c-virtual new file mode 100644 index 00000000..45341505 --- /dev/null +++ b/doc/busses/i2c-virtual @@ -0,0 +1,102 @@ +Virtual i2c bus +Brian Kuschak + + +I2C devices must have a unique 7 bit address on the bus to which they +are attached. Many I2C chips have one or more input lines which can be +used to configure one or more of their address bits. This allows the +hardware designer to include multiple chips on a single bus by selecting +different addresses for each one. However, some chips have fixed addresses +which present a problem if more than one of them are required on a board. +Also, sometimes there are more devices than there are selectable addresses. + +A common solution is to break the i2c bus into disjoint segments, by using +a multiplexor or i2c bus switch. This electrically isolates the individual +bus segments. Then the devices with duplicate addresses are placed on +separate bus segments, to prevent address conflicts. + +This seems to be a very common approach in certain types of applications, +such as bladed chassis, where many identical 'blades' or cards are inserted +into a common backplane. The I2C bus on each blade is then located behind +a mux or i2c bus switch. This solution works great from the hardware +perspective, but presents some complexities for software. + +The 'virtual i2c adapter' driver is an attempt to provide a flexible software +abstraction to simiplify these issues, while minimizing the impact on +existing code and fitting nicely into the i2c driver framework. + + +What is a 'virtual i2c adapter' +=============================== + +Think of this adapter as a layer above the existing algorithm and adapter +drivers. The virtual i2c adapter is the handle by which you access a +specific multiplexed bus segment. Each disjoint bus segment becomes a new +virtual bus. The virtual adapter is associated with a 'real' adapter and a +set of multiplexor control variables. + +For example: you have an i2c-0 bus with one multiplexor or i2c bus switch, +and the mux has four outputs which can be electrically connected to the +i2c-0 bus. You would register four virtual i2c busses, one for each position +of the mux. As part of the registration, you provide callback functions to +select and deselect the mux as well as the selector values to be passed to +these functions. + + --------- <--------> i2c-1 virtual + | i2c mux | <--------> i2c-2 virtual + i2c-0 <-------> | | <--------> i2c-3 virtual + ibm_ocp | 0x74 | <--------> i2c-4 virtual + --------- + ^ + | + +--- (or externally controlled) + +The mux may be i2c-controlled, in which case the mux_addr is set to the +slave address (0x74) and the mux_val would be the port number to select (0-3). +Alternatively, the mux could be controlled by some fixed memory-mapped register +in which case only the mux_val would be needed. The callback functions +are hardware specific and written by the user. + +Once the virtual busses are registered, you may talk to any devices behind +the mux simply the using the i2c-1, i2c-2, i2c-3, etc. adapters instead of +the i2c-0 adapter. This makes the mux essentially transparent to the +i2c_client. + +The great advantage of course is that all the existing sensor drivers and +other i2c client drivers automatically work, and the mux becomes transparent +to them. + + +Additional capabilities +======================= + +This driver also supports multiple levels of muxing, by allowing the +user to register a virtual bus which has a different virtual bus as its +'real' adapter. In addition, each virtual bus may have different kinds of +multiplexors, with each bus having its own set of callback functions. +This allows you to easily support some very complex topologies. + +In addition to multiplexing, some systems require that you obtain hardware +access to the bus behind the mux before talking on it. (Again, bladed chassis +typically 'fence' other blades off the bus to prevent a crashed blade from +bringing down the whole backplane.) Before talking on this bus, you have to +arbitrate with the other blades to acquire exclusive access to the bus. This +requirement is met by the addition of two new function pointers to the +algorithm drivers, acquire_exclusive() and release_exclusive(). Traditional +adapters will not use these and they can safely be left NULL. If they are +non-null, the virtual bus driver will call the acquire_exclusive() function +before attempting any operations on that virtual bus. This function may block, +and this is done before acquiring the lock on the underlying 'real' bus to +allow transactions on the real bus to continue unimpeded while waiting for +arbitrartion. In addition the ownership is only held for as long as it takes +to complete the transfer. + + +Files +===== + +drivers/i2c/i2c-virtual.c: The 'algorithm' and 'adapter' driver +include/linux/i2c-virtual.h: Header file with the mux control structure + and callback prototypes. + + diff --git a/kernel/busses/i2c-virtual.c b/kernel/busses/i2c-virtual.c new file mode 100644 index 00000000..681fbf19 --- /dev/null +++ b/kernel/busses/i2c-virtual.c @@ -0,0 +1,431 @@ +/* + * i2c-virtual.c - Virtual I2C bus driver. + * + * Simplifies access to complex multiplexed I2C bus topologies, by presenting + * each multiplexed bus segment as a virtual I2C adapter. Supports multi-level + * mux'ing (mux behind a mux), as well as arbitration for exclusive bus access + * for those systems which require it (some bladed chassis for example). + * + * Copyright (c) 2004 Google, Inc. (Ken Harrenstien) + * + * Based on: + * i2c-virtual.c from Brian Kuschak + * which was: + * Adapted from i2c-adap-ibm_ocp.c + * Original file Copyright 2000-2002 MontaVista Software Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "i2c-virtual.h" +#include +#include +#include + +#include +#include +#include + +#ifdef DEBUG +# define DBG(x) x +#else +# define DBG(x) {} +#endif + +#define I2C_VIRT_PROCMAP 0 /* 1 for /proc/driver/i2c/virtual_i2c_map */ + +#define VIRT_TIMEOUT (HZ/2) /* 500msec */ +#define VIRT_RETRIES 3 + +/* exclusive access to the bus */ +#define I2C_LOCK(adap) down(&adap->bus) +#define I2C_UNLOCK(adap) up(&adap->bus) + +#if I2C_VIRT_PROCMAP +/* pointer to the list of i2c_bus_mappings */ +static LIST_HEAD(i2c_map_list); +struct i2c_bus_mapping { + struct i2c_adapter *parent; + struct i2c_adapter *virt; + unsigned long mux_addr; + unsigned long mux_val; + struct list_head list; +}; +static void *i2c_virt_add_map(struct i2c_adapter *parent, + struct i2c_adapter *virt, + unsigned long mux_addr, + unsigned long mux_val); +#endif + +/* First the I2C 'algorithm' driver code: */ + +/* + * Description: acquire exclusive access (if needed), select the mux, + * perform the transfer on the parent i2c adapter, deselect mux and drop + * exclusive access. + */ +static int +i2c_virt_master_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num) +{ + struct i2c_virt_priv *priv = (struct i2c_virt_priv *)adap->data; + struct i2c_adapter *parent = priv->parent_adap; + int ret; + + +#ifdef I2C_REQUIRE_ARBITRATION + /* Acquire exclusive access to a shared I2C bus _before_ taking the + local I2C lock to prevent stalling local I2C transactions while + waiting for exclusive access. + */ + if(parent->algo->acquire_exclusive) + if ((ret = parent->algo->acquire_exclusive(parent)) < 0) + return ret; +#endif + /* Grab the lock for the parent adapter. We already hold the lock for + the virtual adapter. Then select the right mux port and perform + the transfer. + */ + DBG(printk(KERN_DEBUG "%s: Locking parent bus %0lX\n", + __FUNCTION__, (unsigned long)parent)); + I2C_LOCK(parent); + if ((ret = priv->mux.select(parent, &priv->mux)) == 0) { + ret = parent->algo->master_xfer(parent, msgs, num); + } + (void) priv->mux.deselect(parent, &priv->mux); + I2C_UNLOCK(parent); + DBG(printk(KERN_DEBUG "%s: Unlocked parent bus %0lX\n", + __FUNCTION__, (unsigned long)parent)); + +#ifdef I2C_REQUIRE_ARBITRATION + { + int ret2; + /* Release exclusive bus access */ + if(parent->algo->release_exclusive) + if ((ret2 = parent->algo->release_exclusive(parent)) < 0) + return ret2; + } +#endif + return ret; +} + +static int +i2c_virt_smbus_xfer(struct i2c_adapter *adap, u16 addr, + unsigned short flags, char read_write, + u8 command, int size, union i2c_smbus_data * data) +{ + struct i2c_virt_priv *priv = (struct i2c_virt_priv *)adap->data; + struct i2c_adapter *parent = priv->parent_adap; + int ret; + +#ifdef I2C_REQUIRE_ARBITRATION + /* Acquire exclusive access to a shared I2C bus _before_ taking the + local I2C lock to prevent stalling local I2C transactions while + waiting for exclusive access. + */ + if(parent->algo->acquire_exclusive) + if ((ret = parent->algo->acquire_exclusive(parent)) < 0) + return ret; +#endif + /* Grab the lock for the parent adapter. We already hold the lock for + the virtual adapter. Then select the right mux port and perform + the transfer. + */ + DBG(printk(KERN_DEBUG "%s: Locking parent bus %0lX\n", + __FUNCTION__, (unsigned long)parent)); + I2C_LOCK(parent); + if ((ret = priv->mux.select(parent, &priv->mux)) == 0) { + ret = parent->algo->smbus_xfer(parent, addr, flags, + read_write, command, size, data); + } + (void) priv->mux.deselect(parent, &priv->mux); + I2C_UNLOCK(parent); + DBG(printk(KERN_DEBUG "%s: Unlocked parent bus %0lX\n", + __FUNCTION__, (unsigned long)parent)); + +#ifdef I2C_REQUIRE_ARBITRATION + { + int ret2; + /* Release exclusive bus access */ + if(parent->algo->release_exclusive) + if ((ret2 = parent->algo->release_exclusive(parent)) < 0) + return ret2; + } +#endif + return ret; +} + +/* + * Description: Implements device specific ioctls. + * We don't support any yet. + */ +static int +i2c_virt_algo_control(struct i2c_adapter *adap, unsigned int cmd, + unsigned long arg) +{ + return 0; +} + +/* Virtual adapter functionality; returns functionality of parent adapter. + * OK if parent itself is virtual, as this will go all the way up to the + * real adapter. + */ +static u32 +i2c_virt_functionality(struct i2c_adapter *adap) +{ + struct i2c_virt_priv *priv = (struct i2c_virt_priv *)adap->data; + struct i2c_adapter *parent = priv->parent_adap; + + return parent->algo->functionality(parent); +} + +/* ===================================================================== */ + +/* The 'adapter' driver code: */ + +static int +i2c_virt_reg(struct i2c_client *client) +{ + return 0; +} +static int +i2c_virt_unreg(struct i2c_client *client) +{ + return 0; +} + + +/* + * Called to create a 'virtual' i2c bus which represents a multiplexed bus + * segment. Client and mux_val are passed to the select and deselect + * callback functions to perform hardware-specific mux control. + */ +struct i2c_adapter *i2c_virt_create_adapter(struct i2c_adapter *parent_adap, + struct i2c_client *client, + unsigned long mux_val, + void *select_cb, + void *deselect_cb) +{ + struct i2c_adapter *adap; + struct i2c_virt_priv *priv; + struct i2c_algorithm *algo; + + if (!(adap = kmalloc(sizeof (struct i2c_adapter) + + sizeof(struct i2c_virt_priv) + + sizeof(struct i2c_algorithm), + GFP_KERNEL))) { + printk(KERN_ERR "i2c_virt_register_adap: Failed allocation\n"); + return NULL; + } + + memset(adap, 0, sizeof(struct i2c_adapter) + + sizeof(struct i2c_virt_priv) + + sizeof(struct i2c_algorithm)); + priv = (struct i2c_virt_priv *) (adap+1); + algo = (struct i2c_algorithm *) (priv+1); + + /* Set up private adapter data */ + priv->parent_adap = parent_adap; + priv->mux.client = client; + priv->mux.addr = client->addr; + priv->mux.value = mux_val; + priv->mux.select = select_cb; + priv->mux.deselect = deselect_cb; + + /* Need to do algo dynamically because we don't know ahead + of time what sort of physical adapter we'll be dealing with. + */ + algo->owner = NULL; + algo->id = I2C_ALGO_VIRT; + strcpy(algo->name, "Virtual I2C algorithm driver"); + algo->master_xfer = (parent_adap->algo->master_xfer + ? i2c_virt_master_xfer : NULL); + algo->smbus_xfer = (parent_adap->algo->smbus_xfer + ? i2c_virt_smbus_xfer : NULL); + algo->slave_send = NULL; + algo->slave_recv = NULL; + algo->algo_control = i2c_virt_algo_control; + algo->functionality = i2c_virt_functionality; +#ifdef I2C_REQUIRE_ARBITRATION + algo->acquire_exclusive = NULL; + algo->release_exclusive = NULL; +#endif + + /* Now fill out new adapter structure */ + adap->owner = NULL; + snprintf(adap->name, sizeof(adap->name), + "Virtual I2C (i2c-%d, mux %02lx:%02lx)", + i2c_adapter_id(parent_adap), + (unsigned long)client->addr, mux_val); + adap->id = I2C_HW_VIRT | algo->id; + adap->algo = algo; + adap->algo_data = NULL; /* XXX: Use this?? */ + adap->client_register = i2c_virt_reg; + adap->client_unregister = i2c_virt_unreg; + adap->data = priv; + adap->flags = 0; + adap->timeout = VIRT_TIMEOUT; + adap->retries = VIRT_RETRIES; + + DBG(printk(KERN_DEBUG "%s: Adding virt bus %0lX\n", + __FUNCTION__, (unsigned long)adap)); + + if (i2c_add_adapter_nolock(adap) < 0) { + kfree(adap); + return NULL; + } + + printk(KERN_NOTICE "i2c%d: Virtual I2C bus " + "(Physical bus i2c%d, multiplexer %02lx port %ld)\n", + i2c_adapter_id(adap), i2c_adapter_id(parent_adap), + priv->mux.addr, mux_val); + +#if I2C_VIRT_PROCMAP + i2c_virt_add_map(priv->parent_adap, adap, + priv->mux.addr, priv->value); +#endif + + MOD_INC_USE_COUNT; + DBG(printk(KERN_DEBUG "%s: Added virt bus %0lX\n", + __FUNCTION__, (unsigned long)adap)); + + return adap; +} + +int i2c_virt_remove_adapter(struct i2c_adapter *adap) +{ + int ret; + + DBG(printk(KERN_DEBUG "%s: Removing virt bus %0lX\n", + __FUNCTION__, (unsigned long)adap)); + + if ((ret = i2c_del_adapter_nolock(adap)) < 0) + return ret; + kfree(adap); + + MOD_DEC_USE_COUNT; + return 0; +} + + +/* ------------------------------------------------------------------ */ + +#if I2C_VIRT_PROCMAP +/* Dubious stuff to implement a /proc/driver/i2c/virtual_i2c_map + * listing of all virtual busses. + * + * XXX: LOCKING??? + */ +static int i2c_virt_read_proc(char *page, char **start, off_t off, + int count, int *eof, void *data) +{ + int len; + char *buf = page; + struct list_head *l; + struct i2c_bus_mapping *pmap; + + /* XXX: BUFFER BOUNDS CHECKING? */ + + buf += sprintf(buf, "i2c bus_mapping\n"); + + list_for_each(l, &i2c_map_list) { + pmap = list_entry(l, struct i2c_bus_mapping, list); + + if(pmap) + buf += sprintf(buf, "base=iic%d mux_addr=%02hx, mux_val=%02hx => iic%d\n", + i2c_adapter_id(pmap->parent), + pmap->mux_addr, pmap->mux_val, + i2c_adapter_id(pmap->virt)); + } + + len = buf - page; + if (len <= off+count) *eof = 1; + *start = page + off; + len -= off; + if (len>count) len = count; + if (len<0) len = 0; + return len; +} + + +static void *i2c_virt_add_map(struct i2c_adapter *parent, + struct i2c_adapter *virt, + unsigned long mux_addr, + unsigned long mux_val) +{ + struct i2c_bus_mapping *pmap; + + if((pmap = kmalloc(sizeof(struct i2c_bus_mapping), GFP_KERNEL)) + == NULL) { + printk(KERN_ERR "%s: Failed to allocate bus mapping\n", + __FUNCTION__); + return; + } + pmap->virt = virt; + pmap->parent = parent; + pmap->mux_addr = mux_addr; + pmap->mux_val = mux_val; + + if (list_empty(&i2c_map_list)) { + /* If first time, ensure proc entry exists */ + /* XXX: Maybe do this with module_init() */ + create_proc_read_entry("driver/i2c/virtual_i2c_map", 0, NULL, + &i2c_virt_read_proc, NULL); + } + list_add_tail(&pmap->list, &i2c_map_list); +} + + +#if 0 /* Not used by anything, but kept for a while in case */ + +/* Called to find out which i2c bus to use to get to a specific bus segment. + */ +struct i2c_adapter *i2c_lookup_adapter(struct i2c_adapter *base, + unsigned long mux_addr, + unsigned long mux_val) +{ + struct list_head *l; + struct i2c_bus_mapping *pmap; + + list_for_each(l, &i2c_map_list) { + pmap = list_entry(l, struct i2c_bus_mapping, list); + if(pmap->parent == base + && pmap->mux_addr == mux_addr + && pmap->mux_val == mux_val) + return pmap->virt; + } + return NULL; /* none found */ +} + +EXPORT_SYMBOL(i2c_lookup_adapter); +#endif /* 0 */ + + +#endif /* I2C_VIRT_PROCMAP */ + +EXPORT_SYMBOL(i2c_virt_create_adapter); +EXPORT_SYMBOL(i2c_virt_remove_adapter); + +MODULE_AUTHOR("Ken Harrenstien (from Brian Kuschak)"); +MODULE_DESCRIPTION("Virtual I2C driver for multiplexed I2C busses"); +MODULE_LICENSE("GPL"); diff --git a/kernel/chips/pca954x.c b/kernel/chips/pca954x.c new file mode 100644 index 00000000..f47a2d29 --- /dev/null +++ b/kernel/chips/pca954x.c @@ -0,0 +1,837 @@ +/* + * 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, and PCA9548. + * + * 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. + * + * 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. + * + * See Documentation/i2c/virtual_i2c for details on the virtual bus/adapter + * mechanism. + * + ********************************************************************* + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include "i2c-virtual.h" +#include "version.h" + +MODULE_LICENSE("GPL"); + +#define DEBUG 1 /* XXX: Comment out before release? */ +#ifdef DEBUG +# define DBG(x) if (pca954x_debug > 0) { x; } else +#else +# define DBG(x) {} +#endif + +#define PCA954X_NCHANS 8 /* Max # of channels (PCA9548) */ + +/* Addresses to scan */ +static unsigned short normal_i2c[] = { /*0x70,*/ SENSORS_I2C_END }; +static unsigned short normal_i2c_range[] = { SENSORS_I2C_END }; +static unsigned int normal_isa[] = { SENSORS_ISA_END }; +static unsigned int normal_isa_range[] = { SENSORS_ISA_END }; + +/* Insmod/modprobe parameters */ + +/* Chip type must normally be specified using a parameter of the form + "force_pca9544=0,0x70" + The following declares the possible types. +*/ +SENSORS_INSMOD_7(pca9540,pca9542,pca9543,pca9544,pca9545,pca9546,pca9548); + +static int pca954x_debug; +MODULE_PARM(pca954x_debug, "i"); +MODULE_PARM_DESC(pca954x_debug, "Debug output level (0 = none)"); + +#if 0 /* Considered adding this, but too many issues. See doc. */ +static int pca954x_selmask[32]; /* Max # chips supported, sigh */ +MODULE_PARM(pca954x_selmask, "1-32i"); +MODULE_PARM_DESC(pca954x_selmask, "List of selmask settings for each mux/swi"); +#endif + +/* Provide specs for the PCA954x types we know about + */ +static struct pca954x_chipdef { + enum chips type; + const char *type_name; + int pcanum; + int nchans; + enum muxtype { pca954x_ismux=0, pca954x_isswi } muxtype; +} pca954x_chipdefs[] = { + { pca9540, "pca9540", 9540, 2, pca954x_ismux }, + { pca9542, "pca9542", 9542, 2, pca954x_ismux }, + { pca9543, "pca9543", 9543, 2, pca954x_isswi }, + { pca9544, "pca9544", 9544, 4, pca954x_ismux }, + { pca9545, "pca9545", 9545, 4, pca954x_isswi }, + { pca9546, "pca9546", 9546, 4, pca954x_isswi }, + { pca9548, "pca9548", 9548, 8, pca954x_isswi }, +}; +#define PCA954X_MUX_ENA 0x04 /* Mux enable bit */ + +/* Each client has this additional data. + Note the two conventions for identifying a chip's channel (sub-bus): + "channel" 0 to N-1, where N is the number of channels supported + "bitmask" 1 to 1 << N-1, so bit 0 is channel 0. + This use of "channel" is consistent with that in the Philips PCA954X doc. + */ +struct pca954x_data { + struct i2c_client client; + int sysctl_id; + enum chips type; + enum muxtype muxtype; + int pcanum; /* PCA chip number */ + int nchans; /* # of channels (busses) this chip controls */ + int nchmask; /* Mask of all channels */ + + struct semaphore update_lock; + int selmask; /* Bitmask of chans OK to leave on (chn1 == bit0) */ + int curmask; /* Bitmask of chans currently selected */ + + u8 regval; /* Current chip register value */ + u8 origval; /* Original value at initialization */ + + struct i2c_adapter *virt_adapters[PCA954X_NCHANS]; +}; + +static int pca954x_attach_adapter(struct i2c_adapter *adapter); +static int pca954x_detect(struct i2c_adapter *adapter, int address, + unsigned short flags, int kind); +static int pca954x_detach_client(struct i2c_client *client); +static void pca954x_pe_busses(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void pca954x_pe_debug(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void pca954x_pe_regval(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void pca954x_pe_selbus(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void pca954x_pe_selmask(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void pca954x_pe_type(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results); +static void pca954x_set_selmask(struct i2c_client *client, int mask); +static u8 pca954x_mask2val(struct i2c_client *client, int mask); +static int pca954x_val2mask(struct i2c_client *client, u8 val); + +static int pca954x_select_mux(struct i2c_adapter *adap, + struct i2c_mux_ctrl *mux); +static int pca954x_deselect_mux(struct i2c_adapter *adap, + struct i2c_mux_ctrl *mux); +static int pca954x_select_chan(struct i2c_adapter *adap, + struct i2c_client *client, unsigned long chan); +static int pca954x_xfer(struct i2c_adapter *adap, + struct i2c_client *client, + int read_write, u8 *val); + + +/* This is the driver that will be inserted */ +static struct i2c_driver pca954x_driver = { + .owner = THIS_MODULE, + .name = "PCA954X I2C mux/switch driver", + .flags = I2C_DF_NOTIFY, + .attach_adapter = pca954x_attach_adapter, + .detach_client = pca954x_detach_client, +}; + + +/* -- SENSORS SYSCTL START -- */ + +#define PCA954X_SYSCTL_BUSSES 1000 +#define PCA954X_SYSCTL_DEBUG 1001 +#define PCA954X_SYSCTL_REGVAL 1002 +#define PCA954X_SYSCTL_SELBUS 1003 +#define PCA954X_SYSCTL_SELMASK 1004 +#define PCA954X_SYSCTL_TYPE 1005 + +/* -- SENSORS SYSCTL END -- */ + +static ctl_table pca954x_dir_table_template[] = { + {PCA954X_SYSCTL_BUSSES, "busses", NULL, 0, 0444, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &pca954x_pe_busses}, + {PCA954X_SYSCTL_DEBUG, "debug", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &pca954x_pe_debug}, + {PCA954X_SYSCTL_REGVAL, "regval", NULL, 0, 0444, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &pca954x_pe_regval}, + {PCA954X_SYSCTL_SELBUS, "selbus", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &pca954x_pe_selbus}, + {PCA954X_SYSCTL_SELMASK, "selmask", NULL, 0, 0644, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &pca954x_pe_selmask}, + {PCA954X_SYSCTL_TYPE, "type", NULL, 0, 0444, NULL, + &i2c_proc_real, &i2c_sysctl_real, NULL, &pca954x_pe_type}, + {0} +}; + +static int pca954x_id = 0; + +static int __init pca954x_init(void) +{ + printk("pca954x.o version %s (%s)\n", LM_VERSION, LM_DATE); + + /* Because we've set I2C_DF_NOTIFY in our driver struct, + * this call results in calling pca954x_attach_adapter() for + * every adapter currently known, both real and virtual. + */ + return i2c_add_driver(&pca954x_driver); +} + +static void __exit pca954x_exit(void) +{ + i2c_del_driver(&pca954x_driver); +} + +/* This function is called whenever a new bus/adapter is added, OR when + the driver itself is added (for all existing adapters), to see if + the driver can find a chip address it's responsible for. + */ +static int pca954x_attach_adapter(struct i2c_adapter *adapter) +{ + DBG(printk(KERN_DEBUG "%s: %0lX\n", + __FUNCTION__, (unsigned long)adapter)); + + /* Apply standard lookup via parameters or probing. + "addr_data" is defined by the SENSORS_INSMOD_n macro. + */ + return i2c_detect(adapter, &addr_data, pca954x_detect); +} + +/* This function is called by i2c_detect and must be of type + "i2c_found_addr_proc" as defined in i2c-proc.h. + For the PCA954x series this should only happen due to "force" parameters + given to insmod/modprobe, as there is no way to autodetect what flavor + of chip we have (and the differences matter!) + + Thus, we should never get a "kind" of -1. + + Note the unique potential for infinite recursion here because we are + creating new adapters (one for each muxed virtual bus). This will cause + our attach_adapter() function to be called twice for each new adapter, + because: + + (1) The call to i2c_add_adapter results in calling the attach_adapter() + function of all known chip drivers, including this one. + + (2) Because normally this is first called via i2c_add_driver calling + i2c_add_adapter, each new virtual bus/adapter created here will create + a new entry in i2c-core's master adapter list at a point that hasn't + yet been reached by i2c_add_driver's scan, and thus i2c_add_driver + will cause yet another call to our attach_adapter for each of these + new bus/adapters. + + Each call to our attach_adapter() causes another call to i2c_detect, + which would detect the mux all over again if an actual probe was made. + + This is prevented in two ways. The first line of defense is + i2c_check_addr() which is called by both i2c-proc.c:i2c_detect() + and i2c-core.c:i2c_probe() to make sure a bus/addr pair has not already + been seen; that function now knows how to verify this on all parent + busses as well. + + The second line of defense is to never put anything in the "normal_i2c" + or "normal_i2c_range" tables that would induce i2c_detect() to probe + a PCA954x address. + */ +int pca954x_detect(struct i2c_adapter *adapter, int address, + unsigned short flags, int kind) +{ + int i, n; + struct i2c_client *client; + struct pca954x_data *data; + const char *type_name = ""; + int err = 0; + + DBG(printk(KERN_DEBUG "%s: %0lX %0x %0x %d\n", + __FUNCTION__, (unsigned long)adapter, + address, flags, kind)); + + /* XXX: Remove this after verifying that no more recursion + is happening! + */ + if (adapter->algo->id == I2C_ALGO_VIRT) { + + printk(KERN_ERR "%s: Avoiding recursion! %0lX %0x %0x %d\n", + __FUNCTION__, (unsigned long)adapter, + address, flags, kind); + goto ERROR0; + } + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE)) + goto ERROR0; + + /* OK. For now, we presume we have a valid client. We now create the + client structure, even though we cannot fill it completely yet. */ + if (!(data = kmalloc(sizeof(struct pca954x_data), GFP_KERNEL))) { + err = -ENOMEM; + goto ERROR0; + } + memset(data, 0, sizeof(struct pca954x_data)); + + client = &data->client; + client->addr = address; + client->data = data; + client->adapter = adapter; + client->driver = &pca954x_driver; + client->flags = 0; + + /* The detection is very weak. In fact, we have no way of + * telling what kind of mux/swi is there, and the differences matter, + * so if asked to figure it out we just complain instead. + */ + if (kind < 0) { + printk(KERN_ERR "%s: Attempted ill-advised probe at addr %0x", + __FUNCTION__, address); + goto ERROR1; + } + + /* 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) { + printk(KERN_WARNING + "i2c-%d: pca954x.o failed to read reg at %0x", + i2c_adapter_id(adapter), address); + err = -ENODEV; + goto ERROR1; + } + data->origval = i; + + if (kind == any_chip) { + printk(KERN_WARNING + "i2c-%d: pca954x.o needs advice on chip type -" + " wildly guessing %0x is a PCA9540", + i2c_adapter_id(adapter), address); + kind = pca9540; /* Make "any" default to 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) { + printk(KERN_ERR "%s: Internal error: unknown kind (%d)", + __FUNCTION__, kind); + goto ERROR1; + } + data->type = kind; + type_name = pca954x_chipdefs[i].type_name; + data->pcanum = pca954x_chipdefs[i].pcanum; + data->nchans = pca954x_chipdefs[i].nchans; + data->muxtype = pca954x_chipdefs[i].muxtype; + data->nchmask = (1 << data->nchans) - 1; + + /* Now that we know the mux/swi type, we can analyze the + pca954x_selmask parameter, if one was specified. + + XXX: Not yet. Still uncertain whether there is a good way + to initialize selmask and still continue to detect other chips + correctly on the right busses, or whether the original reg + setting (origval) should be retained in case BIOS depends on it. + */ + + /* Fill in remaining client fields and put it into the global list */ + snprintf(client->name, sizeof(client->name), + "PCA%d I2C %d-chan %s chip", + data->pcanum, data->nchans, + (data->muxtype == pca954x_ismux ? "mux" : "switch")); + client->id = pca954x_id++; + init_MUTEX(&data->update_lock); + + /* Tell the I2C layer a new client has arrived */ + if ((err = i2c_attach_client(client))) + goto ERROR1; + + /* Register new /proc directory entries with module sensors */ + if ((i = i2c_register_entry(client, type_name, + pca954x_dir_table_template)) < 0) { + err = i; + goto ERROR2; + } + data->sysctl_id = i; + + /* Initialize the PCA954X chip. + For now, force unselected. This had better happen BEFORE any + other sensors modules are loaded, or their chip addresses will + stop working if they were actually behind the mux chip. + */ + pca954x_set_selmask(client, 0); + + /* Now create virtual busses and adapters for them */ + +#ifdef I2C_REQUIRE_ARBITRATION + /* This doesn't actually do anything yet; kept as a + placeholder from the original i2c-virtual_cb.c + */ + adapter->algo->acquire_exclusive = &acquire_shared_bus; + adapter->algo->release_exclusive = &release_shared_bus; +#endif + + DBG(printk(KERN_DEBUG "%s: creating %d virt busses\n", + __FUNCTION__, data->nchans)); + + for (i = 0, n = 0; i < data->nchans; ++i) { + struct i2c_adapter *virt; + virt = i2c_virt_create_adapter(adapter, client, i, + &pca954x_select_mux, + &pca954x_deselect_mux); + if ((data->virt_adapters[i] = virt) != NULL) + ++n; + } + printk("i2c-%d: Registered %d of %d virtual busses for I2C mux %s\n", + i2c_adapter_id(adapter), n, i, type_name); + + return 0; + + ERROR2: + i2c_detach_client(client); + ERROR1: + kfree(data); + ERROR0: + return err; +} + +static int pca954x_detach_client(struct i2c_client *client) +{ + struct pca954x_data *data = (struct pca954x_data *)(client->data); + int i, err; + + DBG(printk(KERN_DEBUG "%s: %0lX\n", + __FUNCTION__, (unsigned long)client)); + + /* Must flush everything attached to our channels! + */ + for (i = 0; i < data->nchans; ++i) { + if (data->virt_adapters[i]) { + err = i2c_virt_remove_adapter(data->virt_adapters[i]); + if (err != 0) { + printk(KERN_ERR "pca954x.o: Bus deregistration failed, " + "client not detached.\n"); + return err; + } + data->virt_adapters[i] = NULL; + } + } + + i2c_deregister_entry(data->sysctl_id); + + if ((err = i2c_detach_client(client))) { + printk(KERN_ERR "pca954x.o: Client deregistration failed, " + "client not detached.\n"); + return err; + } + + kfree(client->data); + + return 0; +} + + +/* Implement /proc "regval" - the actual contents of the chip register. + * This is READ-ONLY. + */ +void pca954x_pe_regval(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct pca954x_data *data = client->data; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { +#if 0 /* XXX: For now, always use cached value */ + static void pca954x_update_client(struct i2c_client *client); + pca954x_update_client(client); +#endif + results[0] = data->regval; + *nrels_mag = 1; + } +} + +/* Implement /proc "debug" - a non-zero value enables debug output, if + * the code was compiled with it. This actually applies to all mux chips, + * not just a single instance, but that's OK. + */ +void pca954x_pe_debug(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + results[0] = pca954x_debug; + *nrels_mag = 1; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + pca954x_debug = results[0]; + } + } +} + +/* Implement /proc "busses" - list of (virtual) adapter IDs that constitute + the busses that this chip connects to. + This is READ-ONLY. + */ +void pca954x_pe_busses(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct pca954x_data *data = client->data; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + int i, lasti; + if ((lasti = data->nchans) > *nrels_mag) + lasti = *nrels_mag; + for (i = 0; i < lasti; ++i) { + results[i] = i2c_adapter_id(data->virt_adapters[i]); + } + *nrels_mag = lasti; + } +} + +/* Implement /proc "selmask" - the value specifies the current default + * channel mask. Sub-bus 0 == bit 0. + # A value of 0 means "none" + */ +void pca954x_pe_selmask(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct pca954x_data *data = client->data; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + results[0] = data->selmask; + *nrels_mag = 1; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + if (*nrels_mag >= 1) { + pca954x_set_selmask(client, results[0]); + } + } +} + + +/* Implement /proc "selbus" - the list of values is considered to be virtual + adapter IDs, as an alternative representation of the select mask. + No values at all means "none". + */ +void pca954x_pe_selbus(struct i2c_client *client, int operation, + int ctl_name, int *nrels_mag, long *results) +{ + struct pca954x_data *data = client->data; + int i, nres; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + for (i = 0, nres = 0; i < data->nchans; ++i) { + if ((data->selmask & (1<virt_adapters[i]); + ++nres; + } + } + *nrels_mag = nres; + } else if (operation == SENSORS_PROC_REAL_WRITE) { + int newmask = 0; + for (nres = 0; nres < *nrels_mag; ++nres) { + /* Ugly, but shouldn't happen very often */ + for (i = 0; i < data->nchans; ++i) { + if (results[nres] + == i2c_adapter_id(data->virt_adapters[i])) { + newmask |= (1<data; + + if (operation == SENSORS_PROC_REAL_INFO) + *nrels_mag = 0; + else if (operation == SENSORS_PROC_REAL_READ) { + if (*nrels_mag >= 3) { + results[0] = data->pcanum; + results[1] = data->muxtype; + results[2] = data->nchans; + *nrels_mag = 3; + } + } +} + + +/* Set default select mask. + Since this can be executed while something else is using the mux or + one of its sub-busses, the main consideration is making sure we don't + mess up anything in progress. + + This is accomplished by locking the parent bus that the mux resides on, + and then calling our internal write function. + + It would not work to just call i2c_smbus_write_byte() because + by the time that returns, the values we're trying to update + could already be wrong. This can happen if something else grabs + the bus and writes to the mux (eg for doing I/O to a sub-bus + device), leading to a race condition for updating + selmask, regval, and curmask. + + It doesn't help to use an update_lock for those values because + you still have to contend with other threads trying to select + sub-busses and would encounter deadlocks. + + */ +static void pca954x_set_selmask(struct i2c_client *client, int mask) +{ + struct pca954x_data *data = client->data; + + DBG(printk(KERN_DEBUG + "%s: Locking bus %d mux %d (%0lX) " + "for write of selmask %0x\n", + __FUNCTION__, i2c_adapter_id(client->adapter), + client->id, (unsigned long)client, mask)); + + down(&client->adapter->bus); + DBG(printk(KERN_DEBUG "%s: Starting write.\n", __FUNCTION__)); + data->selmask = (mask & data->nchmask); + (void) pca954x_select_chan(client->adapter, client, -1); + up(&client->adapter->bus); + + DBG(printk(KERN_DEBUG "%s: Unlocked mux (%0lX) for write.\n", + __FUNCTION__, (unsigned long)client)); +} + +#if 0 /* Not needed at the moment; see pca954x_pe_regval() */ + +/* Read pca954x register into cached value + */ +static void pca954x_update_client(struct i2c_client *client) +{ + struct pca954x_data *data = client->data; + + DBG(printk(KERN_DEBUG + "%s: Locking bus %d mux %d (%0lX) for read\n", + __FUNCTION__, i2c_adapter_id(client->adapter), + client->id, (unsigned long)client)); + + down(&client->adapter->bus); + DBG(printk(KERN_DEBUG "%s: Starting read.\n", __FUNCTION__)); + pca954x_xfer(client->adapter, client, I2C_SMBUS_READ, &data->regval); + data->curmask = pca954x_val2mask(client, data->regval); + up(&client->adapter->bus); + + DBG(printk(KERN_DEBUG "%s: Unlocked mux (%0lX) for read.\n", + __FUNCTION__, (unsigned long)client)); +} +#endif + + +static u8 pca954x_mask2val(struct i2c_client *client, int mask) +{ + struct pca954x_data *data = (struct pca954x_data *)(client->data); + int i; + + mask &= data->nchmask; /* Sanitize mask */ + if (mask == 0) + return 0; /* Deselecting all, turn off */ + if (data->muxtype == pca954x_isswi) + return mask; /* Switch, can enable all at once! */ + + /* Mux, can only pick one. Select lowest chan bit */ + for (i=0; (mask & (1<data); + + if (data->muxtype == pca954x_isswi) + return val & data->nchmask; /* Switch val is == mask */ + + /* Mux - mask off low bits to get 0-based chan # as bit shift */ + if (val & PCA954X_MUX_ENA) + return 1 << (val & (data->nchans-1)); + return 0; +} + +/***************************************************************************** + * All of the following functions should only be called after the parent + * adapter bus semaphore (adapter->bus) has been acquired, so we cannot + * call the high-level i2c_smbus_write_byte() function -- it will deadlock + * attempting to acquire the same bus lock. Instead do directly what it + * would do, by means of pca954x_xfer(). Sigh! + * + * These functions are allowed to block. + * + *****************************************************************************/ + + +static int pca954x_select_mux(struct i2c_adapter *adap, + struct i2c_mux_ctrl *mux) +{ + return pca954x_select_chan(adap, mux->client, mux->value); +} + +static int pca954x_deselect_mux(struct i2c_adapter *adap, + struct i2c_mux_ctrl *mux) +{ + return pca954x_select_chan(adap, mux->client, -1); +} + +/* Select channel. + * PCA954x I2C-controlled bus mux/switch chips mainly differ in + * whether the chip is a mux or a switch: + * Mux: value addressed, only 1 bus can be selected. + * Swi: bitmask selected, any/all busses can be selected. + */ +static int pca954x_select_chan(struct i2c_adapter *adap, + struct i2c_client *client, + unsigned long chan) +{ + struct pca954x_data *data = (struct pca954x_data *)(client->data); + int maskbit; + u8 newregval; + int ret; + + if (chan == -1) { /* Op: clever deselect (to selmask) */ + if ((data->muxtype == pca954x_ismux) + && (data->curmask & data->selmask)) { + /* If mux, and current chan allowed by selmask, do nothing */ + newregval = data->regval; /* No change */ + } else { + /* Anything else just reverts to selmask */ + newregval = pca954x_mask2val(client, data->selmask); + } + + } else { /* Op: select chan */ + maskbit = 1 << chan; + if (maskbit & data->curmask) { /* If already selected, */ + newregval = data->regval; /* no change */ + } else if ((data->muxtype == pca954x_ismux) + || !(maskbit & data->selmask)) { + newregval = pca954x_mask2val(client, maskbit); + } else + newregval = pca954x_mask2val(client, data->selmask); + } + + /* Only clobber control reg when value changes, to avoid + unnecessary mux writes. For safety and initialization, + ALWAYS force the write if 0 is desired. + */ + ret = (!newregval || (data->regval != newregval)); + DBG(printk(KERN_DEBUG + "%s: Mux %d: chan %d, val %02x - %s\n", + __FUNCTION__, + client->id, (int)chan, newregval, + (ret ? "writing" : "skipping" ) )); + if (ret) { + ret = pca954x_xfer(adap, client, I2C_SMBUS_WRITE, &newregval); + + if (ret < 0) { + printk(KERN_ERR "%s: I2C mux %d select failed " + "(addr=%02x, val=%02x, err=%d)!\n", + __FUNCTION__, client->id, + client->addr, newregval, ret); + } else { + data->regval = newregval; + data->curmask = pca954x_val2mask(client, newregval); + } + } + return ret; +} + + +static int pca954x_xfer(struct i2c_adapter *adap, + struct i2c_client *client, + int read_write, u8 *val) +{ + int ret; + + if (adap->algo->master_xfer) { + /* Use I2C transfer */ + 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); + if (!ret && (read_write == I2C_SMBUS_READ)) + *val = buf[0]; + + } else if (adap->algo->smbus_xfer) { + /* Use SMBus transfer */ + 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); + if (!ret && (read_write == I2C_SMBUS_READ)) + *val = data.byte; + } else { + printk(KERN_ERR "%s: no smbus_xfer or master_xfer for " + "adapter %0lX, client %0lX\n", + __FUNCTION__, (unsigned long)adap, (unsigned long)client); + ret = -1; + } + return ret; +} + + +/* ------------------ */ + +MODULE_AUTHOR("Ken Harrenstien "); +MODULE_DESCRIPTION("PCA954X I2C mux/switch driver"); + +module_init(pca954x_init); +module_exit(pca954x_exit); diff --git a/kernel/include/i2c-virtual.h b/kernel/include/i2c-virtual.h new file mode 100644 index 00000000..4bca2f1a --- /dev/null +++ b/kernel/include/i2c-virtual.h @@ -0,0 +1,71 @@ +/* + * i2c-virtual.h - Header file for the 'virtual i2c' adapter driver. + * + * Copyright (c) 2004 Google, Inc. + * + * Based on: + * i2c-virtual.h from Brian Kuschak + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +#ifndef __I2C_VIRTUAL_H +#define __I2C_VIRTUAL_H + +#include + +struct i2c_mux_ctrl { + struct i2c_client *client; /* The mux chip/device */ + + unsigned long addr; + unsigned long value; /* Channel # */ + + /* fn which enables the mux */ + int (*select)(struct i2c_adapter *adap, struct i2c_mux_ctrl *mux); + + /* fn which disables the mux */ + int (*deselect)(struct i2c_adapter *adap, struct i2c_mux_ctrl *mux); +}; + +/* This has to be exposed, since the code which assigns the callbacks must + use it. + */ +struct i2c_virt_priv { + struct i2c_adapter *parent_adap; /* pointer to parent adapter */ + struct i2c_mux_ctrl mux; /* MUX settings for this adapter */ +}; + + +static inline struct i2c_adapter *i2c_virt_parent(struct i2c_adapter *adap) +{ + if (adap->algo && (adap->algo->id == I2C_ALGO_VIRT)) { + return ((struct i2c_virt_priv *)(adap->data))->parent_adap; + } + return NULL; +} + +/* + * Called to create a 'virtual' i2c bus which represents a multiplexed bus + * segment. The client and mux_val are passed to the select and deselect + * callback functions to perform hardware-specific mux control. + */ +struct i2c_adapter *i2c_virt_create_adapter(struct i2c_adapter *parent_adap, + struct i2c_client *client, + unsigned long mux_val, + void *select_cb, + void *deselect_cb); +int i2c_virt_remove_adapter(struct i2c_adapter *adap); + + +#endif /* __I2C_VIRTUAL_H */