This is a design for version 2 of our smbus and lm_sensors module. This document is Copyright (c) 1998, 1999 by Frodo Looijaard. You may freely copy and distribute it, as long as you recognize me as the author, and mark any changes you make as being your own, and distribute this notice with it. Document version 1.0, 19981101. 1.1, 19981111. 1.2, 19981118. 1.3, 19981126. NOTE: THIS IS BY NOW SOMEWHAT OUTDATED. SORRY. Object oriented approach ======================== The i2c module structs contain callback functions and data fields. In the i2c module, these structures are only referenced by pointers. This makes it easy to extend these structs to contain additional information or callback functions. For those familiar with object oriented languages, you can see the smbus and isa structures as an object extension of the i2c structures. To make this clearer, I will show in an example how this is done. Note that I have simplified some things here, so this example does not correspond to an actual struct found in one of the modules. In the i2c module, you can find a struct somewhat like this: struct i2c_adapter { char name[32]; int (*detach_client) (struct i2c_client *); struct i2c_adapter *next; } We have a plain data field (name), a call-back function which needs one parameter (a pointer to a i2c_client struct), and a data field which is a pointer to the next adapter. Now we want to extend this structure. We need another data field, containing a number of flags. We will call this new structure smbus_adapter. A few other things change too: struct smbus_adapter { char name[32]; int (*detach_client) (struct smbus_client *); struct smbus_adapter *next; unsigned int flags; } So we copy all existing fields. The next field still points to the next adapter - but it is now of type smbus_adapter, not i2c_adapter. And the call-back function takes now a parameter of type pointer to smbus_client. And there is a new data field, called flags. Now examine this function definition: int fully_detach_i2c_client (struct i2c_client * client) { res = 0; struct i2c_adapter *current_adapter = first_adapter; /* a global var. */ while (current_adapter) { res |= (current_adapter -> detach_client) (client); current_adapter = current_adapter -> next; } return res; } This function detaches a client from all adapters. Nice. But now comes the Big Trick(tm): we can still use this function for smbus_adapters! int fully_detach_smbus_client (struct smbus_client * client) { return fully_detach_i2c_client( (struct i2c_client *) client); } All we needed here was a simple typecast. Datapointers remain datapointers, so this can safely be done. And because we use call-back functions, the actual function called to detach a client from a single adapter might be the same, or different, depending on what function was put in client->detach_client! It gets even better. The i2c module stores internally all registered adapters. But it stores pointers to the i2c_adapter struct, not the structs themselves! So there is an array: #define I2C_ADAPTER_MAX 32 struct i2c_adapter *adapters[I2C_ADAPTER_MAX]; /* this is an array of pointers to structs, not vice versa! */ So, an element of this array might in fact point to a smbus_adapter, instead of an i2c_adapter! If you know this for sure, you might use a typecast and access the additional fields. In the meantime, the i2c internal administration remains valid. We have to thank Simon Vogl and Gerd Knorr for the way they implemented their i2c module. Any other way would have made this approach impossible, and basing anything upon their module much more difficult. Limitations ----------- Extending the adapter and algorithm structures in this way is quite safe. They are only allocated on places where the code knows that they are 'special'. Extending the driver or client structures depending on a specific adapter/algorithm type is *very* *dangerous*. A driver/client module would need to be aware of every special adapter/algorithm in order to allocate itself! For the ISA bus, it has to be aware of this anyway, so it is safe to do; on other places, think twice first! Module overview =============== All in all, lots of modules will be stacked on each other. Too bad, but that is the only way to cleanly implement everything. Note that in a specific situation, only a few modules may need to be loaded. isa.o, for example, does not depend on smbus.o (in the sense that you can load sensor.o without loading smbus.o). A specific bus driver, though, will depend on many of them. Generally: isa.o depends on nothing (actually, on i2c.o, to keep the code small) smbus.o depends on nothing (actually, on i2c.o, to keep the code small) i2c.o depends on nothing. A non-i2c SMBus bus driver depends only on smbus.o A i2c bus driver depends only on i2c.o A sensor chip driver depends either on isa.o or smbus.o, or both. A SMBus chip driver depends only on smbus.o A I2C chip driver depends only on i2c.o We may need a sensor.o module, to act as a central point for sensor modules. At this moment, it seems not really necessary, but this may change. We will need an enhanced i2c-dev.o module, to add SMBus access to I2C /dev entries. isa.o ISA bus handling. Encapsulates ISA bus access within the i2c structures. Unites I2C adapters and the ISA bus. Defines variables isa_algorithm and isa_adapter. smbus.o Main SMBus handling. Encapsulates SMBus access within the smbus structures. Unites I2C adapters and SMBus hosts (like the PIIX4). Emulates SMBus access on pure I2C adapters. Defines variable smbus_algorithm. piix4.o SMBus adapter driver for the PIIX4 SMBus host. Defines variable piix4_adapter (based on smbus_algorithm). FOO.o Adapter driver for FOO SMBus host Defines variable FOO_adapter (based on smbus_algorithm). i2c-core.o (From Simon Vogl) Main i2c handling. ????.o I2C adapter driver Implementing a class of I2C busses A chip driver (typically defined in its own module) can be hooked on all these levels: * If it is a sensor chip, it should be hooked to isa.o or smbus.o * A pure ISA chip should be hooked to isa.o * A pure SMBus chip should be hooked to smbus.o * An I2C chip should be hooked to i2c.o It can be difficult to decide whether a specific chip should be hooked to smbus.o or i2c.o. A good deciding question is, 'could it be connected to a PIIX4?' Module i2c.o ============ This is Simon Vogl's i2c module (this one is different from the i2c module included in kernels around 2.1.120!). A driver -------- struct i2c_driver { char name[32]; int id; unsigned int flags; int (* attach_adapter) (struct i2c_adapter *); int (* detach_client) (struct i2c_client *); int (* command) (struct i2c_client *, unsigned int cmd, void *arg); void (* inc_use) (struct i2c_client *); void (* dec_use) (struct i2c_client *); } A driver tells us how we should handle a specific type of i2c chip. An instance of such a chip is called a 'client'. An example would be the LM75 module: it would contain only one driver, which tells us how to handle the LM75; but each detected LM75 would be a separate client (which would be dynamically allocated). A description of the above struct: name: The name of this driver id: A unique driver identifier flags: Flags to set certain kinds of behaviour. Most notably, DF_NOTIFY will notify the driver when a new i2c bus is detected, so it can try to detect chips on it. attach_adapter: A call-back function which is called if a new adapter (bus) is found. This allows us to do our detection stuff on the new adapter, and register new clients. detach_client: A call-back function which is called if a specific client which uses this driver should be disallocated. If a specific sensor module is removed, for instance, this function would be called for each registered client. command: A generic call-back function to communicate in a driver-dependent way with a specific client. This should only be seldom needed. inc_use: Can be called to add one to an internal 'use' counter. This can be used to control when it is safe to remove this module, for example. A client -------- struct i2c_client { char name[32]; int id; unsigned int flags; unsigned char addr; struct i2c_adapter *adapter; struct i2c_driver *driver; void *data; } A client is a specific sensor chip. Its operation is controlled by a driver (which describes a type of sensor chip), and it is connected to an adapter (a bus). A description of the above struct: name: The name of this client id: A unique client id flags: Flags to set certain kinds of behaviour (not very important) addr: The client address. 10-bit addresses are a bit of a kludge right now. adapter: A pointer to the adapter this client is on. driver: A pointer to the driver this client uses. data: Additional, client-dependent data An algorithm ------------ struct i2c_algorithm { char name[32]; unsigned int id; int (* master_xfer) (struct i2c_adapter *adap, struct i2c_msg msgs[], int num); int (* slave_send) (struct i2c_adapter *,char *, int); int (* slave_recv) (struct i2c_adapter *,char *, int); int (* algo_control) (struct i2c_adapter *, unsigned int, unsigned long); int (* client_register) (struct i2c_client *); int (* client_unregister) (struct i2c_client *); } An algorithm describes how a certain class of i2c busses can be accessed. A description of the above struct: name: The name of this algorithm id: A unique algorithm id master_xfer: Transfer a bunch of messages through a specific i2c bus. slave_send: Send a message as if we are a slave device slave_recv: Receive a message as if we are a slave device client_register: Register a new client client_unregister: Unregister a client An adapter ---------- struct i2c_adapter { char name[32]; unsigned int id; struct i2c_algorithm *algo; void *data; #ifdef SPINLOCK spinlock_t lock; unsigned long lockflags; #else struct semaphore lock; #endif unsigned int flags; struct i2c_client *clients[I2C_CLIENT_MAX]; int client_count; int timeout; int retries; } An adapter is a specific i2c bus. How to access it is defined in the algorithm associated with it. A description of the above struct: name: The name of this adapter id: A unique adapter id algo: The algorithm through which this bus must be accessed data: Adapter specific data lock, lockflags: To lock out simultaneous adapter access flags: Modifiers for adapter operation clients: All currently connected clients client_count: The number of currently connected clients timeout, retries: Internal variables (unimportant here). Access functions ---------------- All these functions are defined extern. int i2c_master_send(struct i2c_client *,const char *, int); int i2c_master_recv(struct i2c_client *,char *, int); int i2c_transfer(struct i2c_adapter *,struct i2c_msg [], int num); These function respectively send one message to a client, receive one message from a client, or transmit a bunch of messages. struct i2c_msg contains an i2c address to communicate with, and can both read from and write to this address. int i2c_slave_send(struct i2c_client *, char *, int); int i2c_slave_recv(struct i2c_client *, char *, int); Communicate with another master as if the normal master is a common slave device. Administration functions ------------------------ int i2c_add_algorithm(struct i2c_algorithm *); int i2c_del_algorithm(struct i2c_algorithm *); The i2c_{add,del}_algorithm functions must be called whenever a new module is inserted with this driver in it, by the module initialization function. int i2c_add_adapter(struct i2c_adapter *); int i2c_del_adapter(struct i2c_adapter *); The i2c_{add,del}_adapter functions must be called if you have detected a specific bus. It triggers driver->attach_adapter (add, for each driver present) or driver->detach_client (del, for each registered client on this adapter). int i2c_add_driver(struct i2c_driver *); int i2c_del_driver(struct i2c_driver *); The i2c_{add,del}_driver functions must be called whenever a new module is inserted with a chip driver in it, by the module initialization function. int i2c_attach_client(struct i2c_client *); int i2c_detach_client(struct i2c_client *); The i2c_{attach,detach}_client functions must be called if you have detected a single chip. Module smbus.o ============== This module offers support for SMBus operation. An SMBus adapter can either accept SMBus commands (like the PIIX4), or be in fact an I2C driver. In the last case, all SMBus commands will be expressed through I2C primitives. This means that any I2C adapter driver will automatically be a SMBus driver. At this point, it should be noted that there can only be one System Management Bus in a given system (is this really true, by the way?). This means there must be some way of selecting which of the many possible adapters is in fact *the* SMBus. For now, I will ignore this problem. Later on, we can add a hook somewhere in the i2c module to help us decide this. This module consists in fact of three separate parts: first of all, it extends all i2c structs to accomodate the new smbus fields. Second, it defines a new algorithm (smbus_algorithm), that will be used by all non-i2c adapters. Finally, it implements a new access function that sends or receives SMBus commands; these are either translated into I2C commands or sent to the SMBus driver. A driver, client and algorithm ------------------------------ We will not need to extend i2c_driver, i2c_client or i2c_adapter. This means that struct smbus_driver is exactly the same as struct i2c_driver, and struct smbus_client is the same as struct i2c_client, and smbus_adapter is the same as struct i2c_adapter. We *will* define the smbus_* variants, and use them within this module, so it should be easy to extend them after all. Note that at this moment, 10 bit SMBus addresses seem to be only partially supported by the i2c module. We will ignore this issue for now. An adapter ------------ struct smbus_adapter { char name[32]; unsigned int id; struct smbus_algorithm *algo; void *data; #ifdef SPINLOCK spinlock_t lock; unsigned long lockflags; #else struct semaphore lock; #endif unsigned int flags; struct smbus_client *clients[I2C_CLIENT_MAX]; int client_count; int timeout; int retries; /* Here ended i2c_adapter */ s32 (* smbus_access) (__u8 addr, char read_write, __u8 command, int size, union smbus_data * data); } A description of the above struct: smbus_access: A function to access the SMBus. It is only used for non-i2c smbus adapters. Access functions ---------------- All these functions are defined extern. The i2c access function should not be used within SMBus chip drivers, as they might not be defined (for the PIIX4, for example). Instead, use the following general access function, or one of the easier functions based on it: int smbus_access (struct i2c_adapter *, __u8 addr, char read_write, __u8 command, int size, union smbus_data * data); There are specific SMBus registration functions too, like the i2c ones. They are fully compatiable with each other; just substitute 'smbus' for 'i2c' everywhere in the i2c description. int i2c_is_smbus_client(struct i2c_client *); int i2c_is_smbus_adapter(struct i2c_adapter *); Decide whether this client, or adapter, is (on) a non-I2C SMBus. Usually not needed, but it is nice anyway to be able to decide this. Module isa.o ============ This module implements a new algorithm and a specific adapter for the (single) ISA bus in your computer. This makes writing drivers for chips that can be both on ISA and SMBus much easier. Note that this module does *not* in any way depend on smbus.o (previous versions of this document still assumed it would be build upon it; this is no longer true). A driver, adapter or algorithm ------------------------------ We will not need to extend i2c_driver, i2c_adapter or i2c_algorithm. This means that struct isa_driver is exactly the same as struct i2c_driver, struct isa_adapter is the same as struct i2c_adapter and struct isa_algorithm is the same as struct isa_driver. We *will* define the isa_* variants, and use them within this module, so it should be easy to extend them after all. A client -------- struct isa_client { char name[32]; int id; unsigned int flags; unsigned char addr; struct isa_adapter *adapter; struct isa_driver *driver; void *data; unsigned int isa_addr; } A client is a specific sensor chip. Its operation is controlled by a driver (which describes a type of sensor chip), and it is connected to an adapter (a bus, the (single) ISA bus here). A description of the above struct: isa_addr: ISA addresses do not fit in the i2c-compatible addr field, so we needed a new field. Access functions ---------------- All these functions are defined extern. In case of the ISA bus, the master_xfer, slave_send and slave_recv hooks will be NULL, because these functions make no sense. It is regrettably not easy to create an access abstraction in which both ISA bus access and SMBus access are united. See below for examples how you can solve this problem. The most imporant additional access function: int i2c_is_isa_client(struct i2c_client *); int i2c_is_isa_adapter(struct i2c_adapter *); Decide whether this client, or adapter, is (on) the ISA bus. This is important, because it determines whether we can use the SMBus access routines. As an example, I will here implement our old LM78 access function: /* The SMBus locks itself, but ISA access must be locked explicitely! We ignore the LM78 BUSY flag at this moment - it could lead to deadlocks, would slow down the LM78 access and should not be necessary. There are some ugly typecasts here, but the good new is - they should nowhere else be necessary! */ int lm78_read_value(struct i2c_client *client, u8 reg) { int res; if (i2c_is_isa_client(client)) { down((struct semaphore *) (client->data)); outb_p(reg,(((struct isa_client *) client)->isa_addr) + LM78_ADDR_REG_OFFSET); res = inb_p((((struct isa_client *) client)->isa_addr) + LM78_DATA_REG_OFFSET); up((struct semaphore *) (client->data)); return res; } else return smbus_read_byte_data(client->adapter,client->addr, reg); }