Linux Device Driver Tutorial Part 38 – I2C Bus Driver Dummy Linux Device Driver

This is the Series on Linux Device Driver. The aim of this series is to provide easy and practical examples that anyone can understand. This is the Linux Device Driver Tutorial Part 38 – I2C Bus Driver Dummy Linux Device Driver.

We are using the Raspberry PI 4 Model B for this demonstration.

I2C Bus Driver in Linux

Prerequisites

Hardware Required

Bring up Raspberry PI

  1. Install Raspberry Pi OS (32-bit) with desktop in the SD card.
  2. Then install the kernel header using sudo apt install raspberrypi-kernel-headers

For your information, In our Raspberry PI 4 board, kernel 5.4.51-v7l+ is installed.

Please enable the I2C in the Raspberry Pi.

Introduction

In our last tutorial, we have seen how to write the I2C device driver in the Linux kernel for the Slave client. Let’s recap what we have done there.

  • Get the adapter that is available using i2c_get_adapter()
  • Add the device using i2c_new_device()
  • Add the driver to the subsystem using i2c_add_driver()
  • Once you have done these steps, then just transfer the data using any transfer API.
  • Finally, unregister the device using i2c_unregister_device() and delete the driver from the subsystem using i2c_del_driver().

Have you ever thought that it is very simple, unlike the I2C Bare-metal coding? Yes, you are correct. It is simple. But have you ever wondered that who is sending the START, STOP, ADDRESS, READ with ACK, READ with NACK, etc? I might give you the hint in the previous tutorial. Yes, you are correct. The I2C bus driver will do such operations.

So in this tutorial, we will discuss the I2C bus drivers in the Linux kernel.

APIs used for the I2C bus driver

There are two structures that you need to use in order to write the i2c bus driver in the Linux kernel.

  1. Algorithm Structure
  2. Adapter Structure

Algorithm Structure

This structure represents the I2C transfer method.

Where,

master_xfer    — Issue a set of i2c transactions to the given I2C adapter defined by the msgs array, with num messages available to transfer via the adapter specified by adap. This function will be called whenever we call I2C read-write APIs from the client driver.
smbus_xfer      — Issue SMBus transactions to the given I2C adapter. If this is not present, then the bus layer will try and convert the SMBus calls into I2C transfers instead. This function will be called whenever we call SMBus read-write APIs from the client driver.
functionality  — Return the flags that this algorithm/adapter pair supports from the I2C_FUNC_* flags.
reg_slave          — Register given client to I2C slave mode of this adapter
unreg_slave      — Unregister given client from I2C slave mode of this adapter

The return codes from the master_xfer field should indicate the type of error code that occurred during the transfer, as documented in the kernel Documentation file Documentation/i2c/fault-codes.

Adapter Structure

This structure is used to identify a physical i2c bus along with the access algorithms necessary to access it.

Where,

*owner       — Owner of the module(usually set this to THIS_MODULE).
class        — the type of I2C class devices that this driver supports. Usually, this is set to any one of the I2C_CLASS_* based on our need.
*algo         — a pointer to the struct i2c_algorithm structure
bus_lock   — Mutex lock.
timeout    — Timeout in jiffies.
retries     — Number of retries.
dev             — the adapter device.
nr               — bus number which you want to create. This will be applicable only for i2c_add_numbered_adapter().
char name[I2C_NAME_SIZE] — I2C bus driver name. This value shows up in the sysfs filename associated with this I2C adapter.

After you create the two structures, then we have to add the adapter to the i2c subsystem.

Add the adapter to the subsystem

i2c_add_adapter

This API is used to register the adapter to the subsystem. But this will assign the dynamic bus number.

int i2c_add_adapter (struct i2c_adapter * adapter);

where,
adapter – the adapter to add

It returns zero when a new bus number was allocated and stored in adap->nr, and the specified adapter became available for clients. Otherwise, a negative errno value is returned

i2c_add_numbered_adapter

This API is used to register the adapter to the subsystem. But it assigns the number that we asked for if only it is available. We have to initialize the member called nr in the i2c_adapter structure before calling this.

int i2c_add_numbered_adapter ( struct i2c_adapter * adap);

Where,
adap – the adapter to register (with adap->nr initialized)

This returns zero when the specified adapter is available for clients using the bus number provided in adap->nr. Otherwise, negative errno value is returned.

Example

If the bus number 5 is not used by anyone, then it will assign that bus number to the client. Otherwise, it will return a negative number.

Delete the adapter from the subsystem

This API is used to unregister the adapter from the subsystem.

void i2c_del_adapter ( struct i2c_adapter * adap);

Where,
adap – the adapter being unregistered

Example Programming

We are going to write the I2C bus driver which does nothing. I meant it won’t send any START, STOP, ADDRESS, READ with ACK, READ with NACK, etc. It won’t communicate with the slave device. The i2c bus driver just prints the message that it gets instead. So actually it is a dummy I2C bus driver. What is the use of this dummy I2C bus driver? This will give you some basic ideas about how it is working. What if you call i2c_master_send(), i2c_master_recv() and any SMBUS API like i2c_smbus_read_byte() from the I2C Client Device driver? So this example will give you an overview.

I2C Bus Driver Source Code

[Get the source code form the GitHub]

Makefile

Since we have used i2c_add_adapter(), it will assign the dynamic bus number. So we have to check the bus number before using that bus by the i2c client driver.

Once you have written the I2C bus driver build that using sudo make, then load the I2C bus driver using sudo insmod driver_bus.ko. Then you just read the busses available using  tree /sys/bus/i2c/. You must get something like below.

Here in my case, I got a new I2C bus called i2c-11. So this I2C bus driver gave me the bus number as 11. You just get yours. That number will be used in the I2C client device driver.

I2C Client Device Driver Source code

We just took the last tutorial’s I2C Client driver example code. We made the below changes on top of that.

  • Changed the macro I2C_BUS_AVAILABLE value from 1 to 11 as our i2c bus driver is 11 now.
  • I just commented (removed) the function call SSD1306_Fill() wherever it was being called. Because if we add that we will see more prints in dmesg. In this code, we will just see the prints of init commands from SSD1306_DisplayInit().

[Get the source code from the GitHub]

Makefile

Testing the Device Driver

Since we have loaded the i2c bus driver already, now we will load the i2c client driver.

  • Build the driver by using Makefile (sudo make)
  • Load the driver using sudo insmod driver_client.ko
  • Once after we have loaded the i2c client driver, it should have called the probe function.
  • So in that probe function, we were using some I2C write functions. That must get us to the i2c bus drivers function. Let’s check the dmesg.

  • yeah hoooo. We got it. We can able to see all the bytes that we are transmitting for initializing the SSD1306 display.
  • Now you can unload the i2c client driver using sudo rmmod driver_client
  • After that unload the i2c bus driver using sudo rmmod driver_bus

In our next tutorial, we will see how to communicate with the slave device from the I2C bus driver.

5 2 votes
Article Rating
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x
%d bloggers like this: