Sophie

Sophie

distrib > Mandriva > 9.1 > ppc > by-pkgid > bd9dadf154bafc1c7d85b7c850c34200 > files > 81

lm_sensors-2.7.0-2mdk.ppc.rpm

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);
}