Table of Contents

I2C Subsystem Internals

This page provides the official documentation of the I2C internals of MINIX 3. It describes the overarching design themes used in the I2C subsystem as well as the I2C bus driver itself. The current version documents I2C in git commit fdbede5 and later. If you update this document because of changes to MINIX 3, please mention the commit ID of the change in the wiki comment.

General Overview

I2C (Inter-Integrated Circuit) is a bus type commonly used in embedded systems to transfer data between a central processor and external integrated circuits such as eeproms, sensors, real time clocks, power management chips, and other such integrated circuits. I2C controllers are built into the processors on the BeagleBone, BeagleBone Black, and BeagleBoard-xM. Since they provide access to useful devices (power management, HDMI framer, eeprom, expansion boards, etc), support was added to Minix.

Note: in an attempt to reduce the size of this document, the author tries not to repeat information. For example, the names and numbers section discuses how device files are named (/dev/i2c-1, /dev/i2c-2, etc). That information is not repeated in the NetBSD/OpenBSD /dev Interface section.

Terminology

Bus Drivers

I2C bus drivers are I2C bus managers, one instance per bus. They read and write to and from the I2C registers on the SoC to transfer data on the bus. For example, the I2C drivers directly manipulate the I2C controllers on the Cortex-A8 processor.

Device Drivers

I2C device drivers are I2C-bus-attached-device-specific drivers, one instance per I2C attached device. They communicate with the chips on the bus through the i2c bus drivers.

Service Labels

Labels are an RS concept used to uniquely identify driver instances, specified by the service command which is used to start and stop driver instances.

Names and Numbers

Bus Numbering Conventions

The am335x/dm37xx hardware I2C controllers are counted starting at 0. For example, on the BeagleBone Black the buses are named i2c0, i2c1, and i2c2. However, in software they are numbered starting from 1. For example, the hardware I2C controller on the am335x SoC named i2c0 corresponds to the device file /dev/i2c-1 which is backed by the driver labelled i2c.1.

Device File Naming

The device files for the bus drivers are named using the bus numbers used in software: /dev/i2c-1 for the first bus, /dev/i2c-2 for bus 2, etc.

The device files for specific devices are named using the device name, bus number, and slave address in the form /dev/${device}b${bus_number}s${slave_addr}. For example, the device file for the eeprom on bus 3 with slave address 0x54 would be /dev/eepromb3s54

Service Labels

Bus Driver Labels

I2C bus drivers are required to be labelled in the following form: i2c.N (where N is the bus number). For example, i2c.1 is for the first I2C bus. This is because device drivers look up the bus drivers' endpoint using the label. The endpoint is the 'address' that other processes use when sending a message. If the bus driver has a different label, device drivers won't be able to find them when they look up the endpoint using the i2c.N label format. Similarly, device drivers register with DS to receive updates about their bus driver restarting. When subscribing for updates, driver construct the regular expression using the label format described above.

Device Driver Labels

I2C device driver labels only have special meaning to humans. Their format can be completely arbitrary, but for consistency's sake, it makes sense to use the same scheme for all device drivers. That scheme is as follows: driver_name.N.A (where N is the bus number and A is the slave address in hex). For example, cat24c256.1.50 is for a CAT24C256 EEPROM on I2C bus 1 having slave address 0x50.

Caveat: when reserving a device on the bus, the bus driver makes note of the label of the device driver. This is so that if the device driver is restarted, the bus driver can update the reservation table with the new endpoint of the device driver. The catch is that if you restart a driver for the same device, same bus, same slave address, and use a different label, the reservation will fail because it was reserved using the original label. The “fix” is to reboot or restart the device driver with the original label.

Starting I2C Bus and Device Drivers

Within the /usr/etc/rc script, there is a block of shell script which brings up the I2C bus drivers. Then, the eepromread utility is invoked. It attempts to read an EEPROM on bus 1 with slave address 0x50. This is used to detect the board that Minix is running on. Based on the results, the proper device drivers for the board are started with the service command. Refer to that script for hints about the commands to use to start I2C drivers.

Each driver is single threaded and responsible for one and only one physical device or controller. Separate instances of each driver are started for each bus and each device. For example, on the BeagleBone Black there are 3 I2C buses. Thus, there should be 3 instances of the bus driver started. If 2 EEPROMs are attached, 2 instances of the EEPROM driver should be started.

Caveat: device drivers reserve their devices with the bus driver. Once a device driver claims a device, it can no longer be accessed via the /dev/i2c-N interface. All access to that device must happen through an interface provided by the device driver.

User Tools

There are a couple of user level tools which interact with I2C devices. Both use the /dev/i2c-N interface, so be aware that i2cscan won't detect devices claimed by drivers and eepromread won't be allowed to read the EEPROM if a driver instance for the device has been started.

i2cscan

This is a small utility imported from NetBSD. It probes the bus for devices. It uses several methods for detecting devices. However, not all methods are supported by all I2C controllers. For example, the default scan method uses a feature not supported by the I2C controllers on the BeagleBones and BeagleBoards. You should use the '-r' option on those systems to scan using only the 1 byte read method. Also note that i2cscan is not perfect and may not find all devices on the bus as some address ranges are skipped and the 1 byte read method may not find certain devices.

eepromread

This is a small Minix utility for reading the EEPROM. It's mainly used for board identification and testing. It can interpret the on board EEPROM contents as well as display the first 256 bytes of the EEPROM in HEX and ASCII.

External Interfaces

Minix IPC Interface

The I2C bus driver accepts messages sent by device drivers. The message types are documented on the I2C Protocol wiki page. The cat24c256 driver source in conjunction with the libi2cdriver source provide a nice example of how to use this interface.

NetBSD/OpenBSD

The /dev interface implemented in NetBSD and OpenBSD is very simple and atomic. All operations happen through the 1 ioctl() call (passing a ioctl_exec_t structure), and none depend on prior calls. Everything needed is in one struct that's passed via ioctl(). Since the generic i2c bus driver only handles 1 request at once, calls from different processes at the same time don't conflict.

The main structure, ioctl_exec_t, contains pointers. Since a structure containing pointers passed via ioctl() will not pass the data pointed to, a translation must be done to “flatten” the structure. A flat version named minix_ioctl_exec_t has fixed size buffers. Before forwarding the request to VFS, libc does a translation from ioctl_exec_t to minix_ioctl_exec_t. When VFS replies, the result is translated back into the original ioctl_exec_t.

The definition of the interface is available here.

Reservation System

As aluded to above, the bus drivers each maintain a table of reserved devices on their specific bus. A reservation is required before performing read/write operations on the bus using Minix IPC to the bus driver. Access through the /dev/i2c-N interface can only be to devices that haven't been reserved by a driver.

Adding support for new I2C Controllers

The i2c bus driver separates the generic code that will be the same for all buses (the /dev interface, the IPC interface, the reservation system, validation, etc) and the bus specific driver code. The bus specific code simply has to provide an initialization function and set a function pointer to a minix_i2c_ioctl_exec_t handler that looks like this:

int process(minix_i2c_ioctl_exec_t * ioctl_exec);

libi2cdriver

The i2cdriver library provides common I2C device driver functions that handle tasks which would be repeated in nearly every I2C device driver. A full tutorial on developing a driver using this library is currently being developed and will be posted to the wiki soon.