I2C in the 2.6.32 Linux Kernel

Homepage I2C in the 2.6.32 Linux Kernel

The other day I forward-ported an old and abandoned touch-screen driver to the shiny new 2.6.32 Linux kernel. A small portion of the work was to bring the driver in-line with the latest I2C framework (notably due the removal of detach_client from struct i2c_driver since 2.6.31 in favour of the new device driver binding model). This post shares my new-found understanding of the I2C framework and provides a very brief guide to writing an I2C slave device driver.

In a nutshell, I2C is a simple serial bus that is often used to communicate with devices such as EEPROMs and peripherals such as touchscreens – there is also SMBus which can sometimes be considered a subset of I2C. The Linux kernel breaks down I2C into ‘Buses’ and ‘Devices’, and then further breaks down buses into ‘Algorithms‘ and ‘Adapters‘, and devices into ‘Drivers‘ and ‘Clients‘. We’ll take a look at these in a little more detail.

Let’s dive into the Linux kernel I2C buses and devices

Algorithms

An Algorithm performs the actual reading and writing of I2C messages to the hardware – this may involve bit banging GPIO lines or writing to an I2C controller chip. An Algorithm is represented by the very straight-foward ‘struct i2c_algorithm‘ structure and allows you to define function pointers to functions that can write I2C messages (master_xfer) or SMBus messages (smbus_xfer).

Adapters

An Adapter effectively represents a bus – it is used to tie up a particular I2C/SMBus with an algorithm and bus number. It is represented by the ‘struct i2c_adapter‘ structure. If you imagine a system where there are many I2C buses – perhaps two controlled by a controller chip and one bit-banged – then you would expect to see 3 instances of an i2c_adapter and 2 instances of an i2c_algorithm.

Clients

A Client represents a chip (slave) on the I2C/SMBus such as a Touchscreen peripheral and is represented by a ‘struct i2c_client‘ structure. This includes various members such as chip address, name and pointers to the adapter and driver.

Drivers

Finally, a driver, represented by a ‘struct i2c_driver‘ structure represents the device driver for a particular class of I2C/SMBus slave devices, e.g. a touchscreen driver. The structure contains a bunch of function pointers – the ones of interest to us are the ‘probe’ and ‘remove’ pointers – which we’ll shortly come onto.

6 simple steps to write an I2C slave device driver in the 2.6.32 Linux kernel

So where do we start with writing an I2C slave device driver?

We start by populating a ‘struct i2c_driver’ structure. In this tutorial we’ll populate just the ‘driver’, ‘id_table’, ‘probe’, and ‘remove’ members. We’ll also use the ‘migor_ts.c‘ touchscreen driver as reference code.

1 static struct i2c_driver migor_ts_driver = {
2       .driver = {
3             .name = “migor_ts” ,
4        },
5        .probe = migor_ts_probe,
6        .remove = migor_ts_remove,
7        .id_table = migor_ts_id,
8 };

We tell the Linux kernel about our I2C slave driver by registering the ‘struct i2c_driver’ structure with the I2C core – we do this with a call to ‘i2c_add_driver’ in our __init function as follows:

1 static int _ init migor_ts_init (void)
2 {
3              return i2c_add_driver(&migor_ts_driver);
4 }

Back to our ‘struct i2c_driver’ structure – the id_table member allows us to tell the framework which I2C slaves chips we support – id_table is simply an array of ‘struct i2c_device_id’ – here’s the migors:

1 static struct i2c_device_id migor_ts_id[] = {
2 { “migor_ts“, 0 } ,
3 { }
4 };

So in this case we’re saying that we only support a slave chip named ‘migor_ts’. This makes us wonder – how does the Linux kernel know which slave chips are present and what they’re called? – we know the chip doesn’t contain such friendly naming strings and I2C doesn’t support enumeration. There is no magic – there are a number of mechanisms to discover and name an I2C slave – the most common in embedded devices is to define in your board set up file what slave devices you expect to see, their address and name. For example in the arch/sh/boards/mach-migor/setup.c file we see the following:

1 static struct i2c_board_info migor_i2c_devices[] = {
2 {
3 I2C_BOARD_INFO(“migor_ts” , 0x51),
4 .irq = 38, /* IRQ6 */
5 },
6 };
7
8 i2c_register_board_info(0, migor_i2c_devices, ARRAY_SIZE(migor_i2c_devices));

Without going into too much detail here – we populate a ‘struct i2c_board_info’ structure with the help of a I2C_BOARD_INFO macro – crucially we include a made up name for the slave chip and it’s I2C bus address.

So back to the migor driver. During boot, thanks to the ‘i2c_register_board_info’ call, the kernel will try to find any I2C driver which supports the ‘migor_ts’ name we provided. Upon finding such a driver, the Linux kernel will call it’s ‘probe’ function. The idea being that the Linux kernel believes it has found an I2C slave and would like us to probe to make sure that we are a suitable driver (It’s worth noting that the kernel won’t attempt to communicate with this device in any way).

So if all is well – upon start up the Linux kernel should call our probe function – let’s take a look.

1 static int migor_ts_probe(struct i2c_client * client, construct i2c_device_id*idp)
2 {
3
4 }

Passed along as a parameter to the probe function is a ‘struct i2c_client’ structure – this represents our I2C slave chip. Typically a probe function where possible will communicate with the device to ensure it really is the device we think it is – this can be done by reading values from a revision ID register or the like. We can communicate with the slave by calling the ‘i2c_master_send’ function with a pointer to our ‘struct i2c_client’ – e.g:

1 i2c_master_send(client, buf, 1);

With regards to driver initialisation this is the last we’ll here from the Linux kernel – so the probe function should also register with the input layer (assuming an input device) and perhaps set up an interrupt/timer. It’s also a good idea to make a note of the i2c_client for use later on. Now you can talk to your I2C slave device – what else goes on really depends on the type of device which is being supported.

First Publication date: 12/2009 by Embedded Bits

You might also like...
cat /proc/meminfo : MemTotal

cat /proc/meminfo : MemTotal Linux manages its physical memory in clever and often efficient ways – as a result, it’s not uncommon to only think about how the memory in your system is being used when we run into performance issues. And this is where the frustration can begin – without fully understanding how memory is managed, it can be very difficult to answer some seemingly straight-forward questions like ‘How much free memory do I have?‘ or ‘How much memory is this process taking?‘. There are a lot of complications and as a result, performance monitoring can be a challenge. Read my article to learn more

Read more
Andrew Murray - UK Managing Director
12 May 2017