The Inter-Integrated Circuit Protocol

Inter-Integrated Circuit, or “i2c” (pronounced “I squared C”), is a serial bus/protocol that’s widely used to interface hosts with devices such as EEPROM memory, audio codecs, and specialized chips that monitor parameters like temperature and power supply voltages. Take a look at how the kernel supports i2c.

Inter-Integrated Circuit, or “i2c” (pronounced “I squared C”), is a serial bus/protocol that’s widely used to interface hosts with devices such as EEPROM memory, audio codecs, and specialized chips that monitor parameters like temperature and power supply voltages. Additionally, i2c is widely used in embedded devices to communicate with real time clocks, with transmitters to link LCD monitors to a host, and so on. The System Management Bus (SMBus) is a subset of i2c.

i2c and SMBus are master-slave protocols, where communication takes place between a host adapter and client devices. The host adapter is usually part of the south bridge chipset on desktops and part of the microcontroller on embedded devices.

This month, let’s discuss how the Linux kernel supports i2c/SMBus host adapters and client devices, and let’s implement a very simple client device driver to access an SMBus memory device. To maximize the driver’s usefulness, we’ll use only the SMBus commands supported by the i2c core, yielding a driver that works with both SMBus and i2c adapters.

The Kernel i2c/SMBus Layer

Figure One illustrates the Linux i2c subsystem. The kernel i2c infrastructure consists of:

* The i2c-core, a code-base consisting of routines and data structures available to host adapter drivers and client drivers. The presence of common code in the core makes driver implementations easier. Additionally, the core provides a level of indirection that makes client drivers independent of the host adapter, allowing the client driver to work unchanged even if the device is used on a board that has a different host adapter. (This core layer” philosophy” is also present in various other kernel subsystems, such as the USB layer.)

* Device drivers for i2c host adapters. These device drivers usually consist of an adapter driver and an algorithm driver.

* Device drivers for i2c client devices.

* The i2c-dev module, which allows the implementation of user mode i2c client device drivers, and the i2c-proc module, which collects device information into /proc/sys/dev/sensors (which isn’t relevant for the example driver developed here).

Figure One also shows these kernel modules talking to a host adapter and a client device on the i2c bus.

FIGURE ONE: The Linux Inter-Integrated Circuit subsystem

Listing One shows a code snippet and the corresponding transactions that occur on the bus. The transactions are captured by connecting an i2c/SMBus bus analyzer while running the code snippet. (If the code seems a little obfuscated at first, don’t worry. It gets clearer shortly.)

LISTING ONE: Bus transactions

* Connect to the client device.
* 0×50 is the device address. i2c and SMBus addresses
* are 7-bit addresses. The protocol supports 10-bit addresses
* also, but many devices respond only to 7-bit addresses.

/* smbus_fp is a file pointer to the SMBus device */
ioctl(smbus_fp, 0×50, slave);

/* Write a byte (0xAB) at offset 0 in the device */
i2c_smbus_write_byte_data(smbus_fp, 0, 0xAB);

* This is the corresponding bus transaction:
* S 0×50 Wr [A] 0 [A] 0xAB [A] P
* S is the start bit, 0×50 is the slave device address,
* Wr is the write command, A is the Accept bit received
* from the client device, 0 is the address offset on the
* client device where the byte is to be written, 0xAB is
* the data that is to be written, and P is the stop bit.

/* Read a byte from offset 0 on the device */
res = i2c_smbus_read_byte_data(smbus_fp, 0);

* This is the corresponding bus transaction:
* S 0×50 Wr [A] 0 [A] S 0×50 Rd [A] [0xAB] NA P
* The explanation of the bits is the same as
* above, except that Rd stands for the Read command,
* 0xAB is the data received from the device and
* NA is the Reverse Accept bit.

Library Routines Provided by the i2c Core

Since the example client device driver needs to use the routines provided by the i2c core to talk to the host adapter, let’s look at those first. Table One contains the main data access routines provided by the core. (Those i2c data transfer routines that aren’t SMBus compatible have been omitted.) Table Two lists the register and unregister functions that are available to host adapter and client device drivers.

TABLE ONE: i2c/SMBus data access functions provided by the i2c core

i2c_smbus_write_block_data() Send a block of data(& lt;= 32 bytes) to the specified offset.
i2c_smbus_read_block_data() Read a block of data(& lt;= 32 bytes) from the specified offset.
i2c_smbus_process_call() Send two bytes to the specified offset and read back two bytes.
i2c_smbus_write_word_data() Send two bytes to the specified offset.
i2c_smbus_read_word_data() Read two bytes from the specified offset.
i2c_smbus_write_byte_data() Send a single byte to the device at a specified offset.
i2c_smbus_read_byte_data() Read a single byte from the device at a specified offset.
i2c_smbus_write_quick() Send a single bit to the device (at the place of the Rd/Wr bit shown in Listing One).
i2c_smbus_write_byte() Send a single byte to the device at the same memory offset as the previous command.
i2c_smbus_read_byte() Read a single byte from the device without specifying a location offset. Uses the same offset as the previous command.
TABLE TWO: Functions that register/unregister with the i2c core

i2c_detach_client() Called when the relevant host adapter or client driver unregisters.
i2c_attach_client() Called when a client device is detected.
i2c_del_driver() Called when a client device driver unregisters.
i2c_add_driver() Called when a client device driver registers.
i2c_del_adapter() Called when a host adapter driver unregisters.
i2c_add_adapter() Called when a host adapter driver registers.

Probing the Device

The client device driver needs to be informed by the i2c core when a bus address that belongs to the device is detected. For this, a” probe” entry point needs to be registered with the i2c core during driver initialization. That’s the purpose of Listing Two.

LISTING TWO: Driver initialization

static struct I2C_driver our_driver =
name: “Test SMBus client”,
flags: I2C_DF_NOTIFY,
attach_adapter: device_probe,
detach_client: device_detach

i2c_add_driver (&our_driver);

The driver id should be unique for the device and should be defined in linux/i2c-id.h. The flag I2C_DF_NOTIFY indicates that the client driver should be notified when a new host adapter is found by the i2c core. The device_probe routine is invoked by the i2c core whenever a new adapter registers with the core.

The example device_probe() function, shown in Listing Three, uses the i2c_probe() routine to see whether the device’s addresses are accessible by the adapter. In Listing Three, normal_i2c specifies the device bank addresses. (Optionally, you can use the normal_i2c_range field to list a set of address pairs that specify a range of valid device addresses.)

LISTING THREE: Probing the presence of device banks

/* Our device has two memory banks with addresses SLAVE_ADDR1
static unsigned short normal_addr[] = {

static struct i2c_client_address_data addr_data = {
normal_i2c: normal_addr,
normal_i2c_range: ignore,

static int device_probe (struct i2c_adapter *adapter)
/* The callback function, device_detect, is explained in Listing Four */
return i2c_probe (adapter, &addr_data, device_detect);

In this example, the device driver knows its bank addresses. However in some cases, the driver might be more generic and might have to scan multiple addresses and use some detection logic to determine if an address belongs to it. Additional fields can be used to request additional features, such as asking the core to ignore a range of addresses.

If the i2c_probe routine detects the specified address range (specified in the addr_data structure), it calls the device_detect() function that was registered. device_detect() then registers a new client data structure, as shown in Listing Four.

LISTING FOUR: Registering a new client

/* Private client Data structure for each memory bank that is
* supported by our driver
struct mem_bank {
struct i2c_client * client; /* i2c client for this bank */
unsigned int addr; /* Slave address of this bank */
unsigned short current_pointer; /* File pointer */
int bank_number; /* Actual memory bank number */

/* spinlocks, data cache for slow devices, .. */

struct mem_bank mem_bank_list [NUM_BANKS]; /* List of client memory banks */

int device_detect (struct i2c_adapter *adapter, int address, int kind)
static struct i2c_client * new_client;

new_client = kmalloc (sizeof(*new_client), GFP_KERNEL);
new_client->driver = &our_driver; /* See Listing 4 */
new_client->addr = address;
new_client->adapter = adapter; /* Host Adapter */
new_client->data = NULL; /* Private data */
new_client->flags = 0;

/* Fill up fields in the appropriate private client data structure */

/* Attach */
i2c_attach_client (new_client);

Each instance of the driver stores its state information in this data structure so that multiple driver threads accessing different memory banks can co-exist.

Checking Adapter Features

Each adapter has a set of constraints. For example, an adapter might support the SMBus read_word command, but not the read_block command. A client driver has to check whether a function is supported by the adapter before using it.

The i2c core provides two functions for this: i2c_check_functionality() checks whether a particular function is supported, and i2c_get_functionality() returns a mask containing all of adapter’s supported functions. (See linux/i2c.h for a complete set of possible features.)

Reading/Writing Data from the Device

The 2.6 kernel supports an in-memory filesystem called sysfs that interfaces with kernel objects and devices. i2c client drivers in 2.6 kernels use sysfs. However, if you want the the example driver to also work with 2.4 kernels, you have to use an alternative, devfs.

LISTING FIVE: Initializing

static struct file_operations our_fops = {
llseek: our_llseek,
read: our_read,
ioctl: our_ioctl,
open: our_open,
release: our_release,
write: our_write,

our_handle = devfs_mk_dir (NULL, “our_device”, NULL);
devfs_register_series (our_handle, “%u”, num_banks,
&our_fops, NULL);

Listing Five registers a devfs entry, /dev/our_device/. If the device has n banks, /dev/our_device/ will contain n files. A user mode program that needs to read from the n th memory bank can then operate on /dev/our_device/n. The our_fops structure contains the driver entry point addresses.

Listing Six shows how the private client data structures are stored, so that they are accessible from the rest of the entry points.

LISTING SIX: Opening the device

int our_open(struct inode *inode, struct file *file)
/* The opened bank */

file->private_data =
(struct mem_bank *) mem_bank_list [n];

To read data from the device, first glean information about its invocation thread from the private client data structure. Next, use the SMBus operations provided by the i2c core to actually read the data. Finally, send the data to user space and increment the internal file pointer so that the next read/write starts from where the last operation ended. These steps are shown in Listing Seven.

LISTING SEVEN: Reading from the device

ssize_t our_read(struct file *file, char *buf, size_t count, loff_t *ppos)
/* Get the private client data structure for this bank */
struct mem_bank * our_bank = (struct mem_bank *)file->private_data;

/* ..Check whether the smbus_read_word functionality is supported.. */

/* Serialize access to multiple memory banks using a spinlock */

while (transferred < count) {
ret = i2c_smbus_read_word_data (our_bank->client,
our_buf [i++] = (u8) (ret & 0xFF);
our_buf [i++] = (u8) (ret >> 8);

/* Copy data to user space and increment the internal file pointer */
copy_to_user (buffer, (void *)our_buf, transferred) ;
our_bank->current_pointer += transferred;

/* .. */

Writing to the device is done similarly, except that the corresponding smbus_write() function is used to transfer data to the device.

Some i2c EEPROM chips implement access protection via an access protection bank that controls read and write accesses to the other banks. In such cases, the driver has to “wiggle” corresponding bits in the access protection bank before it can operate on the other banks.

Other Entry Points

Another entry point to support in our driver is llseek(), which is used to assign a new value to the internal file pointer that’s used in Listing Seven. Since users might want to verify the integrity of the data, the driver must also support an ioctl() entry point to adjust and verify memory checksums.

The poll() and fsync() entry points won’t be supported for this device.

If you choose to compile the driver as a module, you need to supply an entry point to unregister the device and clean up client specific data structures. Unregistering the driver with the i2c core is a one-liner:

i2c_del_driver (&our_driver);

Implementing a user mode i2c/SMBus Driver

The i2c-dev module enables the development of user mode i2c/SMBus device drivers. User space code can access one device node per host adapter (/dev/i2c-0 or /dev/i2c/0 for the first adapter, depending on whether you have DEVFS enabled or not). The file linux/i2c-dev.h contains a list of inline functions that can be used to operate on the device nodes.

Listing Eight is a simple user mode driver that accesses a SMBus memory device.

Listing Eight: A sample user space i2c/SMBus device driver

#include <linux/i2c.h>
#include <linux/i2c-dev.h>

/* Bus addresses of our memory banks */
#define SLAVE_ADDR1 0×60
#define SLAVE_ADDR2 0×61

int main (int argc, char **argv)

/* Open the host Adapter */
if ((smbus_fp = open (“/dev/i2c/0″, O_RDWR)) < 0) {
exit (1);

/* Connect to the first bank */
if (ioctl(smbus_fp, I2C_SLAVE, SLAVE_ADDR1) < 0) {
exit (1);

/* .. */

/* Dump data from the device */
for (reg=0; reg < length; reg++) {
/* See linux/i2c-dev.h for the implementation of this inline
* function
res = i2c_smbus_read_byte_data(smbus_fp, (unsigned char) reg);
if (res < 0) {
exit (1);
/* Dump data */

/* .. */

/* Clear the second bank */

/* Switch banks */
if (ioctl(smbus_fp, I2C_SLAVE, SLAVE_ADDR2) < 0) {
exit (1);

for (reg=0; reg < length; reg+=2){
i2c_smbus_write_word_data (smbus_fp, (unsigned char) reg, 0×0);

/* .. */

close (smbus_fp);

There are pros and cones for writing a user mode driver instead of kernel mode driver. A user mode driver is easy to debug — you won’t have to reboot the system every time you reference a dangling pointer. But if you have multiple applications that need to access different memory banks, you’ll have to replicate a lot of code across your applications. In contrast, if you implement a kernel mode driver, the code needed to talk to the driver will be much simpler.

Looking at the Sources

In the 2.4 kenel source tree, the drivers/i2c/ directory contains the i2c/SMBus driver sources. The i2c code in 2.6 kernels is organized hierarchically: the drivers/i2c/busses/ directory contains the adapter drivers; the drivers/i2c/algos/ directory has the algorithm drivers; and the drivers/i2c/chips/ directory contains the client device driver implementations.

Additionally, the sound directory includes drivers for audio chipsets that use an i2c command interface. Finally, have a look at the Documentation/i2c directory for tips to port i2c/SMBus drivers from 2.4 to 2.6.

Sreekrishnan Venkateswaran has been working for IBM India since 1996. His recent Linux projects include putting Linux onto a wristwatch, a cell phone, and a pacemaker programmer. You can reach Krishnan at class="emailaddress">krishhna@gmail.com.

Comments are closed.