Linux Device Driver Tutorial Part 37 – I2C Client Linux Device Driver using Raspberry PI

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 37 – I2C Linux Device Driver using Raspberry PI.

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

I2C Linux Device Driver

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.

I2C

I assume that you know about the I2C and how the master and slave are communicating normally. If you don’t know about I2C, please go through the I2C’s part 1 and part 2 tutorials before beginning. In this tutorial, we will focus on how the Linux kernel is handling the I2C.

I2C Subsystem

The kernel divided the I2C subsystem by Buses and Devices. The Buses are again divided into Algorithms and Adapters. The devices are again divided into Drivers and Clients. The below image will give you some understandings.

I2C SUBSYSTEM I2C Linux Device Driver

Algorithm

An Algorithm driver contains a general code that can be used for a whole class of I2C adapters.

Adapters

An Adapter effectively represents a bus – it is used to tie up a particular I2C with an algorithm and bus number. Each specific adapter driver either depends on one algorithm driver or includes its own implementation.

Clients

A Client represents a chip (slave) on the I2C.

Drivers

This is the driver that we are writing for the client.

Usually, Driver and Client are more closely integrated than Algorithm and Adapter.

So, you will need a driver for your I2C bus, and drivers for your I2C devices (usually one driver for each device).

I2C Driver in Linux Kernel

Steps that involves while writing the I2C device driver is given below.

  1. Get the I2C adapter.
  2. Create the oled_i2c_board_info structure and create a device using that.
  3. Create the i2c_device_id for your slave device and register that.
  4. Create the i2c_driver structure and add that to the I2C subsystem.
  5. Now the driver is ready. So you can transfer the data between master and slave.
  6. Once you are done, then remove the device.

Get the I2C adapter

In raspberry Pi 4, the i2c-1 bus is available already. You can check that using the command ls -al /sys/bus/i2c/devices/. So we will use the below API to get the adapter structure of this I2C bus.

struct i2c_adapter *i2c_get_adapter(int nr);

Where,

nr – I2C bus number. In our case (Raspberry Pi 4), it should be 1.

It returns the struct i2c_adapter.

Create the board info

Once after you get the adapter structure, then create the board info and using that board info, create the device.

Create Board info

Just create the i2c_board_info  structure and assign required members of that.

where,

type[I2C_NAME_SIZE] – chip type, to initialize i2c_client.name
flags                             – to initialize i2c_client.flags
addr                               – stored in i2c_client.addr
platform_data             – stored in i2c_client.dev.platform_data
archdata                       – copied into i2c_client.dev.archdata
of_node                         – pointer to OpenFirmware device node
fwnode                           – device node supplied by the platform firmware
irq                                 – stored in i2c_client.irq

You can use I2C_BOARD_INFO macro to initialize the essential fields of struct i2c_board_info.

I2C_BOARD_INFO ( dev_type, dev_addr);

where,

dev_type – identifies the device type

dev_addr – the device’s address on the bus

Create Device

Now board info structure is ready. Let’s instantiate the device from that I2C bus.

struct i2c_client * i2c_new_device ( struct i2c_adapter * adap, struct i2c_board_info const * info);

where,

*adap – Adapter structure that we got it from i2c_get_adapter()

*info – Board info structure that we have created

This will return the i2c_client structure. We can use this client for our future transfers.

Now we will see the example for this section. So that you will get some idea that how we are using this in our code.

Example

Create the device id and register

Now we have to create the i2c driver for our slave. In order to do that, you have to create the device id and i2c_driver. Then add that driver to the I2C subsystem.

Create the device id

Just create the structure i2c_device_id and initialize the necessary members.

where,

name               –  Slave name
driver_data – Data private to the driver (This data will be passed to the respective driver)

After this, call MODULE_DEVICE_TABLE(i2c, my_id_table) in order to expose the driver along with its I2C device table IDs to userspace.

Create the i2c_driver

Where,

class                   – What kind of i2c device we instantiate (for detect)
attach_adapter – Callback for bus addition (deprecated)
probe                   – Callback for device binding
remove                 – Callback for device unbinding
shutdown             – Callback for device shutdown
alert                   – Alert callback, for example for the SMBus alert protocol
command               – Callback for bus-wide signaling (optional)
driver                 – Device driver model driver
id_table             – List of I2C devices supported by this driver
detect                 – Callback for device detection
address_list     – The I2C addresses to probe (for detect)
clients               – List of detected clients we created (for i2c-core use only)

Add the I2C driver to the I2C subsystem

Now we have the i2c_driver structure. So we can add this structure to the I2C subsystem using the below API.

i2c_add_driver(struct i2c_driver *i2c_drive);

Where,

i2c_drive – The i2c_driver structure that we have created.

During the call to i2c_add_driver to register the I2C driver, all the I2C devices will be traversed. Once matched, the probe function of the driver will be executed.

You can remove the driver using i2c_del_driver(struct i2c_driver *i2c_drive).

Let’s put this together and the code snippet shown below.

Example

Transfer data

Till this point, everything is on our plate. So, we can start the communication between master and slave. I meant data transfer.

Send data

i2c_master_send

This API issue a single I2C message in the master transmit mode.

int i2c_master_send ( const struct i2c_client * client, const char * buf, int count);

Where,

client – Handle to the slave device
buf       – Data that will be written to the slave
count   – How many bytes to write, must be less than 64k since msg.len is u16

It returns negative errno, or else the number of bytes written.

i2c_smbus_write_byte

This API is used to send one byte to the slave.

s32 i2c_smbus_write_byte ( const struct i2c_client * client, u8 value);

Where,

client – Handle to the slave device
value   – Byte to be sent

It returning negative errno else zero on success.

i2c_smbus_write_byte_data

s32 i2c_smbus_write_byte_data ( const struct i2c_client * client, u8 command, u8 value);

Where,

client   – Handle to the slave device
command – Byte interpreted by slave
value     – Byte being written

It returning negative errno else zero on success.

i2c_smbus_write_word_data

s32 i2c_smbus_write_word_data ( const struct i2c_client * client, u8 command, u16 value);

Where,

client   – Handle to the slave device
command – Byte interpreted by slave
value     – 16-bit “word” being written

It returning negative errno else zero on success.

i2c_smbus_write_block_data

s32 i2c_smbus_write_block_data ( const struct i2c_client * client, u8 command, u8 length, const u8 * values);

Where,

client   – Handle to the slave device
command – Byte interpreted by slave
length   – Size of the data block; SMBus allows at most 32 bytes
values   – Byte array which will be written

It returning negative errno else zero on success.

Read data

i2c_master_recv

This API issue a single I2C message in master receive mode.

int i2c_master_recv ( const struct i2c_client * client, const char * buf, int count);

Where,

client – Handle to the slave device
buf       – Data that will be read from the slave
count   – How many bytes to read, must be less than 64k since msg.len is u16

It returns negative errno, or else the number of bytes reads.

i2c_smbus_read_byte

s32 i2c_smbus_read_byte ( const struct i2c_client * client);

Where,
client  – Handle to the slave device

It is returning negative errno else the byte received from the device.

i2c_smbus_read_byte_data

s32 i2c_smbus_read_byte_data ( const struct i2c_client * client, u8 command);

Where,

client   – Handle to the slave device
command – Byte interpreted by slave

It is returning negative errno else a data byte received from the device.

i2c_smbus_read_word_data

s32 i2c_smbus_read_word_data ( const struct i2c_client * client, u8 command);

Where,

client   – Handle to the slave device
command – Byte interpreted by slave

This returns negative errno else a 16-bit unsigned “word” received from the device.

i2c_smbus_read_block_data

s32 i2c_smbus_read_block_data ( const struct i2c_client * client, u8 command, u8 * values);

Where,

client   – Handle to the slave device
command – Byte interpreted by slave
values   – Byte array into which data will be read; big enough to hold the data returned by the slave. SMBus allows at most 32 bytes.

This returns negative errno else the number of data bytes in the slave’s response.

Note that using this function requires that the client’s adapter support the I2C_FUNC_SMBUS_READ_BLOCK_DATA functionality. Not all adapter drivers support this; its emulation through I2C messaging relies on a specific mechanism (I2C_M_RECV_LEN) which may not be implemented.

i2c_transfer

If you want to send multiple I2C messages then you can use the below-given API.

int i2c_transfer ( struct i2c_adapter * adap, struct i2c_msg * msgs, int num);

Where,

adap – Handle to the I2C bus
msgs – One or more messages to execute before STOP is issued to terminate the operation; each message begins with a START.
num   – Number of messages to be executed.

It returns negative errno, else the number of messages executed.

How I2C bus driver works

  1. I2C client driver initiates transfer using a function like i2c_transfer, i2c_master_send etc.
  2. It comes to the master_xfer function in the bus driver (drivers/i2c/busses/*).
  3. The bus driver splits the entire transaction into START, STOP, ADDRESS, READ with ACK, READ with NACK, etc. These conditions have to be created on the real i2c bus. The bus driver writes to the I2C hardware adaptor to generate these conditions on the I2C bus one by one, sleeping on a wait queue in between (basically giving the CPU to some other task to do some useful job rather than polling until hardware finishes).
  4. Once the hardware has finished a transaction on the bus (for eg a START condition), interrupt will be generated and the ISR will wake up the sleeping master_xfer.
  5. Once master_xfer wakes up, he will go and advise the hardware adaptor to send the second condition (for eg ADDRESS of the chip).
  6. This continues till whole transactions are over and return back to the client driver.

The point to note here is sleep done by the thread in between each condition. This is why I2C transactions cannot be used in ISRs. For client driver, it is just a simple function like i2c_transfer,i2c_master_send. But it is implemented in the bus driver as explained above.

SSD1306 OLED

SSD1306 is a single-chip CMOS OLED/PLED driver with a controller for organic / polymer light-emitting diode dot-matrix graphic display system. It consists of 128 segments and 64commons.

The SSD1306 embeds with contrast control, display RAM, and oscillator, which reduces the number of external components and power consumption. It has 256-step brightness control. Data/Commands are sent from general MCU through the hardware selectable 6800/8000 series compatible Parallel Interface, I2C interface, or Serial Peripheral Interface. It is suitable for many compact portable applications, such as mobile phone sub-display, MP3 player and calculator, etc.

The operating voltage of the SSD1306 controller is from 1.65V to 3.3V while the OLED panel requires 7V to 15V supply voltage. All these different power requirements are sufficed using internal charge pump circuitry. This makes it possible to connect it to any 5V logic microcontroller easily without using any logic level converter.

SSD1306 OLED Memory Map

Regardless of the size of the OLED module, the SSD1306 driver has a built-in 1KB Graphic Display Data RAM (GDDRAM) for the screen which holds the bit pattern to be displayed. This 1K memory area is organized in 8 pages (from 0 to 7). Each page contains 128 columns/segments (block 0 to 127). And each column can store 8 bits of data (from 0 to 7). That surely tells us we have

8 pages x 128 segments x 8 bits of data = 8192 bits = 1024 bytes = 1KB memory

Here are the complete specifications:

Display TechnologyOLED (Organic LED)
MCU InterfaceI2C / SPI
Screen Size0.96 Inch Across
Resolution128×64 pixels
Operating Voltage3.3V – 5V
Operating Current20mA max
Viewing Angle160°
Characters Per Row21
Number of Character Rows7

Data in SSD1306 OLED

There are two types of data that we can send to the SSD1306 OLED.

  1. Command
  2. Data to be written into the GDDRAM

Whenever you send some data, you have to send the control byte first, then send the data byte after that. That control byte used to tell the data that you are sending is command or data.

Control Byte

Bit 7Bit 6Bit 5Bit 4Bit 3Bit 2Bit 1Bit 0
Co bit *D/C *000000

The first 6 bits should be 0.

D/C – If this bit is 1, then the next byte will be a command. If this bit is 0, then the next byte will be data.

Co –  If this bit is 0, then the following bytes contain data bytes only.

Example

If you want to send a command, make the control byte as 0x00 and attach the command in the next byte. Let’s say I want to send the command 0xAE (Turn OFF the display), Follow the below steps.

  • Send the Start condition
  • Send the Slave address with R/W bit
  • Send the Control Byte (0x00)
  • Send the Command byte (0xAE)
  • Send the Stop condition

If you want to write some 0xFF to the display, then follow the below steps.

  • Send the Start condition
  • Send the Slave address with R/W bit
  • Send the Control Byte (0x40)
  • Send the Command byte (0xFF)
  • Send the Stop condition

For more information, please refer to the datasheet of the SSED1306.

Example Programming

In this example, we are going to use the SSD1306 OLED display as the slave device. This is not the tutorial of SSD1306 whereas this is the tutorial of I2C in the Linux device driver. So we don’t show any circus in the SSD1306 OLED display. We will just Fill something in the display and clear that. That’s all.

Note: Don’t care about the commands that we send to initialize the OLED display. We will explain that in separate tutorials.

The concept of this example is, we will fill 0xFF in the full display when we load the driver and clears it while unloading. This makes the process simple right. Let’s do it.

Please enable the I2C in the Raspberry Pi.

If you see the Pinout of the Raspberry Pi 4,

  • GPIO 2 – SDA
  • GPIO 3 – SCL

Connection Diagram

ssd1306-interface-with-raspberry-pi

Driver Source Code

If you want to interface any other I2C slave, then you don’t care about the functions that start with SSD1306_. You implement your own functions for that slave.

[Get the source code from the GitHub]

Makefile

Testing the Device Driver

  • Build the driver by using Makefile (sudo make)
  • Load the driver using sudo insmod driver.ko
  • See the Display is filled.
  • Unload the driver using sudo rmmod driver
  • See the Display has been cleared

Output Video

Click here if you don’t see the output gif

I2C Linux Device DriverIn our next tutorial, we will discuss the dummy 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.

4 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Night Hawk
Night Hawk
October 9, 2020 3:11 PM

what is use of i2c_get_adapter and i2c_put_adapter?

Night Hawk
Night Hawk
Reply to  owl
October 12, 2020 12:13 PM

In the next session part 38, you used i2c_add_adapter() api to register that adapter in subsystem.but in part 37 where did you registered the adapter to subsystem?Does it get registered when creating a new_device()?
Here my question is when will this add_adapter() function called in part 37?

4
0
Would love your thoughts, please comment.x
()
x
%d bloggers like this: