Custom I2C Kernel Driver Overview - cu-ecen-aeld/buildroot-assignments-base GitHub Wiki

Objective

Welcome to the Wiki page that aims to guide you through the process of interfacing an I2C-based sensor with a Raspberry Pi 3B+/4B using the Linux I2C driver. By leveraging the capabilities of the I2C protocol and the existing Linux I2C driver, you can easily integrate a wide range of sensors into your Raspberry Pi projects. Learn how to establish communication between Raspberry Pi and I2C sensors as the host/master. Please note that this tutorial specifically covers writing drivers and does not address I2C slave functionality.

The example code linked to this page is for BME280, but can easily be modified for any I2C-based sensor.

Driver Development

Necessary Data Structures

  • struct i2c_adapter *sensor_i2c_adapter - i2c_adapter is the structure used to identify a physical i2c bus along with the access algorithms necessary to access it. The struct i2c_adapter is typically initialized and registered by I2C driver code during driver initialization. It provides a standardized interface for I2C drivers to interact with the underlying I2C bus hardware. I2C drivers can use the functions and methods provided by the struct i2c_adapter to perform operations such as scanning for connected devices, initiating I2C transfers, and handling bus-level communication.

  • struct i2c_client *sensor_i2c_client - Represents an I2C Slave Device. The struct i2c_client is typically instantiated and populated by the I2C driver code when a matching I2C device is detected on the bus.

  • struct i2c_driver sensor_i2c_driver - Represents the I2C Device Driver that we will instantiate and use in our driver. This structure is used to define and register an I2C driver with the system.

  • struct i2c_board_info sensor_i2c_board - i2c_board_info is used to build tables of information listing I2C devices that are present. This board info is used when instantiating a new client (slave) device using i2c_new_client_device(). For a main board, this information is statically registered using i2c_register_board_info(). For add-on boards such as the sensor in our case, the adapter is already known and thus i2c_new_client_device() can be used.

Implementation

It can get a little confusing as to why so many data structures are required, so here's a simpler explanation:

  1. Get the I2C adapter to manage hardware. It serves as a bridge between the I2C controller hardware and the higher-level I2C bus operations. This is done during module init using i2c_get_adapter().

  2. Register the sensor as a client (slave) using i2c_new_client_device(). This is where board_info and the adapter handle from the previous step are passed as arguments.

  3. Add the I2C Driver - To use the Linux I2C driver, instantiate the i2c_driver in the driver code. This driver is then added in our init function using i2c_add_driver() and is removed during cleanup using i2c_del_driver().

Once you have added the driver, all communication can be performed by the functions provided as a part of this API. To perform I2C reads/writes, the following API calls can be used:

Some Useful Things to Remember

  • Unregister the client device after using i2c_unregister_device().
  • The i2c_put_adapter() function is used to release a reference to an I2C bus adapter that was obtained using i2c_get_adapter() or i2c_add_adapter(). It decrements the reference count of the adapter, and if the count reaches zero, it releases the adapter and associated resources. So, if you have obtained a reference to an I2C adapter, it is necessary to balance the reference count by calling i2c_put_adapter() when you no longer need the adapter.
  • i2c_del_driver() should be using while exiting the module.
  • To figure out the I2C address for a particular device, you should refer to the documentation or datasheet provided by the manufacturer of that device. The datasheet typically contains information about the device's specifications, including the I2C address. (For bme280, the board address is 0x76 or 0x77 depending on whether I2C0 or I2C1 is used.) This information is stored as a part of the structure called board_info mentioned above. (Reference: Page 31 of the BME280 datasheet)

Buildroot Settings

To add I2C to the kernel image, refer to this page.

References