I’m working on a project that uses the Raspberry Pi and various sensors. One sensor is a MLX90614 IR thermometer; this communicates using SMBus. One of my goals is to avoid making the project specific to the target hardware, the hardware that actually runs my code. I want the code that communicates with the sensors to be Linux specific but not Raspberry Pi specific.
One of the problems a lot of people have had with the Raspberry Pi is with getting SMBus communication to work. SMBus is built on I2C, a way of managing serial communication between many devices with only two wires: clock and data. I2C is more flexible, while SMBus is made to be more reliable. As part of guaranteeing reliability, mostly in a multi-master setup, SMBus requires the use of I2C’s repeated starts for read operations. The I2C master driver for Linux on the Raspberry Pi does not support this, and I have been unable to find a patch or custom version anywhere that adds support. Most people have solved the problem by using code that directly interfaces with the Raspberry Pi’s hardware, ignoring the Linux kernel support altogether. This solution not only fails to meet my goal of avoiding code specific to the target hardware, it also won’t work well if more than one process on the same host attempts to use the I2c master hardware.
I decided to solve the problem by modifying the Linux kernel’s support for the I2C master of the Raspberry Pi. I succeeded. My implementation may not be the best or bug free (works for me is a weak guarantee), but it does allow for communication with a MLX90614 using the user-space (unprivileged code outside the kernel) SMBus interface provided by Linux. Since examples with SMBus are more difficult to find than I2C, here is my test code. I also tested with a BMP085 sensor using the support for it already in Linux. This sensor uses I2C rather than SMBus, and it also functions correctly. It doesn’t need a repeated start, but it gets one anyway.
To avoid causing extra trouble, I implemented repeated starts for a subset of conditions under which they could be valid. I focused on SMBus communication. With I2C, a slew of messages going back and forth can be requested, all with repeated starts, but this isn’t normally done or required. Also, the hardware requires that once the first message has begun transmission, the second message be setup in the registers ahead of the stop condition. To make matters worse, there is no interrupt condition for this, so polling in a busy wait is required. I stuck all this inside the bcm2708_i2c_master_xfer() function of i2c-bcm2708.c so it isn’t in the interrupt handler. After the second message is configured, the interrupt handler is used to receive the data. That avoids additional polling at the cost of not allowing additional repeated starts.
If you’d rather see the kernel change in source control, I stuck it on Github. It is a 3.6.11 kernel, but not the latest revision out there. I’m still using it for now. There is no kernel or kernel module for download here because I’m running Gentoo on my Raspberry Pi and I build my own kernels. If I gave you my kernel, you may well find that something doesn’t work anymore.