I2C Drivers, Part I

The I2C bus helps you monitor the health of your system. Here's how to develop a driver that will get you all the hardware info you need to know.
I2C Algorithm Drivers

An I2C algorithm is used by the I2C bus driver to talk to the I2C bus. Most I2C bus drivers define their own I2C algorithms and use them, as they are tied closely to how the bus driver talks to that specific type of hardware. For some classes of I2C bus drivers, a number of I2C algorithm drivers already have been written. Examples of these are ITE adapters found in drivers/i2c/i2c-algo-ite.c, IBM PPC 405 adapters found in drivers/i2c/i2c-algo-ibm_ocp.c and a generic I2C bit shift algorithm found in drivers/i2c/i2c-algo-bit.c. All of these already written algorithms have their own functions with which an I2C bus driver needs to register to use. For more information on these, please see all of the drivers/i2c/i2c-algo-*.c files in the kernel tree.

For our example driver, we are going to create our own I2C algorithm driver. An algorithm driver is defined by a struct i2c_algorithm structure and is defined in the include/linux/i2c.h file. Here is a description of some of the commonly used fields:

  • char name[32];: the name of the algorithm.

  • unsigned int id;: description of the type of algorithm this structure defines. These different types are defined in the include/linux/i2c-id.h file and start with the characters I2C_ALGO_.

  • int (*master_xfer)(struct i2c_adapter *adap,struct i2c_msg msgs[], int num);: a function pointer to be set if this algorithm driver can do I2C direct-level accesses. If it is set, this function is called whenever an I2C chip driver wants to communicate with the chip device. If it is set to NULL, the smbus_xfer function is used instead.

  • int (*smbus_xfer) (struct i2c_adapter *adap, u16 addr, unsigned short flags, char read_write, u8 command, int size, union i2c_smbus_data *data);: a function pointer to be set if this algorithm driver can do SMB bus accesses. Most PCI-based I2C bus drivers are able to do this, and they should set this function pointer. If it is set, this function is called whenever an I2C chip driver wants to communicate with the chip device. If it is set to NULL, the master_xfer function is used instead.

  • u32 (*functionality) (struct i2c_adapter *);: a function pointer called by the I2C core to determine what kind of reads and writes the I2C adapter driver can do.

In our example I2C adapter driver, the i2c_adapter structure referenced the tiny_algorithm variable. That structure is defined as the following:


static struct i2c_algorithm tiny_algorithm = {
    .name           = "tiny algorithm",
    .id             = I2C_ALGO_SMBUS,
    .smbus_xfer     = tiny_access,
    .functionality  = tiny_func,
};

The tiny_func function is small and tells the I2C core what types of I2C messages this algorithm can support. For this driver, we want to be able to support a few different I2C message types:


static u32 tiny_func(struct i2c_adapter *adapter)
{
    return I2C_FUNC_SMBUS_QUICK |
           I2C_FUNC_SMBUS_BYTE |
           I2C_FUNC_SMBUS_BYTE_DATA |
           I2C_FUNC_SMBUS_WORD_DATA |
           I2C_FUNC_SMBUS_BLOCK_DATA;
}

All of the different I2C message types are defined in include/linux/i2c.h and start with the characters I2C_FUNC_.

The tiny_access function is called when an I2C client driver wants to talk to the I2C bus. Our example function is quite simple; it merely logs all of the requests the I2C chip driver makes to the syslog and reports success back to the caller. This log allows you to see all of the different addresses and data types that an I2C chip driver may request. The implementation looks like:


static s32 tiny_access(struct i2c_adapter *adap,
                       u16 addr,
                       unsigned short flags,
                       char read_write,
                       u8 command,
                       int size,
                       union i2c_smbus_data *data)
{
    int i, len;

    dev_info(&adap->dev, "%s was called with the "
             "following parameters:\n",
             __FUNCTION__);
    dev_info(&adap->dev, "addr = %.4x\n", addr);
    dev_info(&adap->dev, "flags = %.4x\n", flags);
    dev_info(&adap->dev, "read_write = %s\n",
             read_write == I2C_SMBUS_WRITE ?
             "write" : "read");
    dev_info(&adap->dev, "command = %d\n",
             command);

    switch (size) {
    case I2C_SMBUS_PROC_CALL:
        dev_info(&adap->dev,
                 "size = I2C_SMBUS_PROC_CALL\n");
        break;
    case I2C_SMBUS_QUICK:
        dev_info(&adap->dev,
                 "size = I2C_SMBUS_QUICK\n");
        break;
    case I2C_SMBUS_BYTE:
        dev_info(&adap->dev,
                 "size = I2C_SMBUS_BYTE\n");
        break;
    case I2C_SMBUS_BYTE_DATA:
        dev_info(&adap->dev,
                 "size = I2C_SMBUS_BYTE_DATA\n");
        if (read_write == I2C_SMBUS_WRITE)
            dev_info(&adap->dev,
                     "data = %.2x\n", data->byte);
        break;
    case I2C_SMBUS_WORD_DATA:
        dev_info(&adap->dev,
                 "size = I2C_SMBUS_WORD_DATA\n");
        if (read_write == I2C_SMBUS_WRITE)
            dev_info(&adap->dev,
                     "data = %.4x\n", data->word);
        break;
    case I2C_SMBUS_BLOCK_DATA:
        dev_info(&adap->dev,
                 "size = I2C_SMBUS_BLOCK_DATA\n");
        if (read_write == I2C_SMBUS_WRITE) {
            dev_info(&adap->dev, "data = %.4x\n",
                     data->word);
            len = data->block[0];
            if (len < 0)
                len = 0;
            if (len > 32)
                len = 32;
            for (i = 1; i <= len; i++)
                dev_info(&adap->dev,
                         "data->block[%d] = %x\n",
                         i, data->block[i]);
        }
        break;
    }
    return 0;
}

Now that the tiny_i2c_adap driver is built and loaded, what can it do? On its own, it cannot do anything. An I2C bus driver needs an I2C client driver in order to do anything besides sit in the sysfs tree. So, if the lm75 I2C client driver is loaded, it tries to use the tiny_i2c_adap driver to find the chip for which it was written:

$ modprobe lm75
$ tree /sys/bus/i2c/
/sys/bus/i2c/
|-- devices
|   |-- 0-0048 -> ../../../devices/legacy/i2c-0/0-0048
|   |-- 0-0049 -> ../../../devices/legacy/i2c-0/0-0049
|   |-- 0-004a -> ../../../devices/legacy/i2c-0/0-004a
|   |-- 0-004b -> ../../../devices/legacy/i2c-0/0-004b
|   |-- 0-004c -> ../../../devices/legacy/i2c-0/0-004c
|   |-- 0-004d -> ../../../devices/legacy/i2c-0/0-004d
|   |-- 0-004e -> ../../../devices/legacy/i2c-0/0-004e
|   `-- 0-004f -> ../../../devices/legacy/i2c-0/0-004f
`-- drivers
    |-- i2c_adapter
    `-- lm75
        |-- 0-0048 -> ../../../../devices/legacy/i2c-0/0-0048
        |-- 0-0049 -> ../../../../devices/legacy/i2c-0/0-0049
        |-- 0-004a -> ../../../../devices/legacy/i2c-0/0-004a
        |-- 0-004b -> ../../../../devices/legacy/i2c-0/0-004b
        |-- 0-004c -> ../../../../devices/legacy/i2c-0/0-004c
        |-- 0-004d -> ../../../../devices/legacy/i2c-0/0-004d
        |-- 0-004e -> ../../../../devices/legacy/i2c-0/0-004e
        `-- 0-004f -> ../../../../devices/legacy/i2c-0/0-004f

Because the tiny_i2c_adap driver responds with a success to every read and write request it is asked to accomplish, the lm75 I2C chip driver thinks it has found an lm75 chip at every known possible I2C address for this chip. This abundance of addresses is why I2C devices 0-0048 through 0-004f have been created. If we look at the directory for one of these devices, the sensor files for this chip driver are shown:

$ tree /sys/devices/legacy/i2c-0/0-0048/
/sys/devices/legacy/i2c-0/0-0048/
|-- detach_state
|-- name
|-- power
|   `-- state
|-- temp_input
|-- temp_max
`-- temp_min

______________________

Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

2.6

Anonymous's picture

Hello,
I am tried building the examples from parts 1 and 2, and got errors. I am running version 2.6.21. The example from part 1 tried to include linux/config.h, which does not seem to exist in 2.6. I removed the include, but I still get errors. They start with the spinlock header

include/linux/spinlock.h: At top level:
include/linux/spinlock.h:290: error: expected declaration specifiers or '...' before 'bool'
include/linux/spinlock.h: In function 'double_spin_lock':
include/linux/spinlock.h:294: error: 'l1_first' undeclared (first use in this function)
include/linux/spinlock.h:294: error: (Each undeclared identifier is reported only once
include/linux/spinlock.h:294: error: for each function it appears in.)
include/linux/spinlock.h:295: warning: implicit declaration of function 'current_thread_info'
include/linux/spinlock.h:295: error: invalid type argument of '->' (have 'int')
include/linux/spinlock.h:295: warning: implicit declaration of function 'barrier'
include/linux/spinlock.h:296: error: invalid type argument of '->' (have 'int')
include/linux/spinlock.h:298: error: invalid type argument of '->' (have 'int')
include/linux/spinlock.h:299: error: invalid type argument of '->' (have 'int')
include/linux/spinlock.h: At top level:
include/linux/spinlock.h:309: error: expected declaration specifiers or '...' before 'bool'
include/linux/spinlock.h: In function 'double_spin_unlock':
include/linux/spinlock.h:313: error: 'l1_taken_first' undeclared (first use in this function)
include/linux/spinlock.h:314: error: invalid type argument of '->' (have 'int')
include/linux/spinlock.h:314: warning: implicit declaration of function 'unlikely'
include/linux/spinlock.h:314: warning: implicit declaration of function 'test_thread_flag'
include/linux/spinlock.h:314: error: 'TIF_NEED_RESCHED' undeclared (first use in this function

Is there something I need to change to make this example compatible with 2.6?
Thanks,
Chris

trying to write a driver for an audio chip

kamou's picture

hello !

I'm trying to write a driver for an audio chip, this chip can be controlled via i2c bus (i2c-0)

the problem is that I don't understand how I can "open" the i2c-0 in kernel space...
I tried to do as le lm75 sensor driver...but it's not better..
I know that I have to register my driver as an i2c driver with the i2c_add_driver function

does this:
static int
chip_attach_adapter(struct i2c_adapter *adapter)
{
return i2c_detect(adapter, &addr_data,
chip_detect);
}

probes every i2c bus on my board ?
and I don't understand what addr_data is or what it does,
I know it is in the SENSORS_INSMOD_1 macro
but I'm not writing a driver for a sensor, but for an audio chip...

can anyone help me understand better all this ?

White Paper
Linux Management with Red Hat Satellite: Measuring Business Impact and ROI

Linux has become a key foundation for supporting today's rapidly growing IT environments. Linux is being used to deploy business applications and databases, trading on its reputation as a low-cost operating environment. For many IT organizations, Linux is a mainstay for deploying Web servers and has evolved from handling basic file, print, and utility workloads to running mission-critical applications and databases, physically, virtually, and in the cloud. As Linux grows in importance in terms of value to the business, managing Linux environments to high standards of service quality — availability, security, and performance — becomes an essential requirement for business success.

Learn More

Sponsored by Red Hat

White Paper
Private PaaS for the Agile Enterprise

If you already use virtualized infrastructure, you are well on your way to leveraging the power of the cloud. Virtualization offers the promise of limitless resources, but how do you manage that scalability when your DevOps team doesn’t scale? In today’s hypercompetitive markets, fast results can make a difference between leading the pack vs. obsolescence. Organizations need more benefits from cloud computing than just raw resources. They need agility, flexibility, convenience, ROI, and control.

Stackato private Platform-as-a-Service technology from ActiveState extends your private cloud infrastructure by creating a private PaaS to provide on-demand availability, flexibility, control, and ultimately, faster time-to-market for your enterprise.

Learn More

Sponsored by ActiveState