I2C Client Linux Device Driver – Linux Device Driver Tutorial Part 37

This article is a continuation of the  Series on Linux Device Drivers and carries the discussion on Linux device drivers and their implementation. The aim of this series is to provide easy and practical examples that anyone can understand. This is the I2C Linux Device Driver using Raspberry PI – Linux Device Driver Tutorial Part 37.

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

You can also read BMP280 Bosch Pressure Sensor I2C Device Driver, I2C dummy bus driver, I2C bus driver, SSD1306 driver, and GPIO Linux Driver.

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 involve while writing the I2C device driver are 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 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.

struct i2c_board_info {
  char type[I2C_NAME_SIZE];
  unsigned short flags;
  unsigned short addr;
  void * platform_data;
  struct dev_archdata * archdata;
  struct device_node * of_node;
  struct fwnode_handle * fwnode;
  int irq;
};

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 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.

Note: If you are using the newer kernel (5.2 =<), then you must use the i2c_new_client_device API instead of i2c_new_device.

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

#define I2C_BUS_AVAILABLE   (          1 )              // I2C Bus available in our Raspberry Pi
#define SLAVE_DEVICE_NAME   ( "ETX_OLED" )              // Device and Driver Name
#define SSD1306_SLAVE_ADDR  (       0x3C )              // SSD1306 OLED Slave Address

static struct i2c_adapter *etx_i2c_adapter     = NULL;  // I2C Adapter Structure
static struct i2c_client  *etx_i2c_client_oled = NULL;  // I2C Cient Structure (In our case it is OLED)

/*
** I2C Board Info strucutre
*/
static struct i2c_board_info oled_i2c_board_info = {
        I2C_BOARD_INFO(SLAVE_DEVICE_NAME, SSD1306_SLAVE_ADDR)
    };

/*
** Module Init function
*/
static int __init etx_driver_init(void)
{
    ....
    
    etx_i2c_adapter     = i2c_get_adapter(I2C_BUS_AVAILABLE);
    etx_i2c_client_oled = i2c_new_device(etx_i2c_adapter, &oled_i2c_board_info);
    
    ....
    return 0;
}

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.

struct i2c_device_id {
  char name[I2C_NAME_SIZE];
  kernel_ulong_t driver_data;
};

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

struct i2c_driver {
  unsigned int class;
  int (* attach_adapter) (struct i2c_adapter *);
  int (* probe) (struct i2c_client *, const struct i2c_device_id *);
  int (* remove) (struct i2c_client *);
  void (* shutdown) (struct i2c_client *);
  void (* alert) (struct i2c_client *, unsigned int data);
  int (* command) (struct i2c_client *client, unsigned int cmd, void *arg);
  struct device_driver driver;
  const struct i2c_device_id * id_table;
  int (* detect) (struct i2c_client *, struct i2c_board_info *);
  const unsigned short * address_list;
  struct list_head clients;
};

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 is shown below.

Example

/*
** This function getting called when the slave has been found
** Note : This will be called only once when we load the driver.
*/
static int etx_oled_probe(struct i2c_client *client,
                         const struct i2c_device_id *id)
{
    ...
    
    return 0;
}

/*
** This function getting called when the slave has been removed
** Note : This will be called only once when we unload the driver.
*/
static int etx_oled_remove(struct i2c_client *client)
{   
    ...
    return 0;
}

/*
** Structure that has slave device id
*/
static const struct i2c_device_id etx_oled_id[] = {
        { SLAVE_DEVICE_NAME, 0 },
        { }
};
MODULE_DEVICE_TABLE(i2c, etx_oled_id);

/*
** I2C driver Structure that has to be added to linux
*/
static struct i2c_driver etx_oled_driver = {
        .driver = {
            .name   = SLAVE_DEVICE_NAME,
            .owner  = THIS_MODULE,
        },
        .probe          = etx_oled_probe,
        .remove         = etx_oled_remove,
        .id_table       = etx_oled_id,
};

/*
** Module Init function
*/
static int __init etx_driver_init(void)
{
    ...
    i2c_add_driver(&etx_oled_driver);
    ...
    return 0;
}

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 length 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 returns 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 length 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), an 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 the whole transaction is 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 an 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 Technology OLED (Organic LED)
MCU Interface I2C / SPI
Screen Size 0.96 Inch Across
Resolution 128×64 pixels
Operating Voltage 3.3V – 5V
Operating Current 20mA max
Viewing Angle 160°
Characters Per Row 21
Number of Character Rows 7

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 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0
Co bit * D/C * 0 0 0 0 0 0

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 to 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]

/***************************************************************************//**
*  \file       driver.c
*
*  \details    Simple I2C driver explanation (SSD_1306 OLED Display Interface)
*
*  \author     EmbeTronicX
*
*  \Tested with Linux raspberrypi 5.4.51-v7l+
*
* *******************************************************************************/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/i2c.h>
#include <linux/delay.h>
#include <linux/kernel.h>

#define I2C_BUS_AVAILABLE   (          1 )              // I2C Bus available in our Raspberry Pi
#define SLAVE_DEVICE_NAME   ( "ETX_OLED" )              // Device and Driver Name
#define SSD1306_SLAVE_ADDR  (       0x3C )              // SSD1306 OLED Slave Address

static struct i2c_adapter *etx_i2c_adapter     = NULL;  // I2C Adapter Structure
static struct i2c_client  *etx_i2c_client_oled = NULL;  // I2C Cient Structure (In our case it is OLED)

/*
** This function writes the data into the I2C client
**
**  Arguments:
**      buff -> buffer to be sent
**      len  -> Length of the data
**   
*/
static int I2C_Write(unsigned char *buf, unsigned int len)
{
    /*
    ** Sending Start condition, Slave address with R/W bit, 
    ** ACK/NACK and Stop condtions will be handled internally.
    */ 
    int ret = i2c_master_send(etx_i2c_client_oled, buf, len);
    
    return ret;
}

/*
** This function reads one byte of the data from the I2C client
**
**  Arguments:
**      out_buff -> buffer wherer the data to be copied
**      len      -> Length of the data to be read
** 
*/
static int I2C_Read(unsigned char *out_buf, unsigned int len)
{
    /*
    ** Sending Start condition, Slave address with R/W bit, 
    ** ACK/NACK and Stop condtions will be handled internally.
    */ 
    int ret = i2c_master_recv(etx_i2c_client_oled, out_buf, len);
    
    return ret;
}

/*
** This function is specific to the SSD_1306 OLED.
** This function sends the command/data to the OLED.
**
**  Arguments:
**      is_cmd -> true = command, flase = data
**      data   -> data to be written
** 
*/
static void SSD1306_Write(bool is_cmd, unsigned char data)
{
    unsigned char buf[2] = {0};
    int ret;
    
    /*
    ** First byte is always control byte. Data is followed after that.
    **
    ** There are two types of data in SSD_1306 OLED.
    ** 1. Command
    ** 2. Data
    **
    ** Control byte decides that the next byte is, command or data.
    **
    ** -------------------------------------------------------                        
    ** |              Control byte's | 6th bit  |   7th bit  |
    ** |-----------------------------|----------|------------|    
    ** |   Command                   |   0      |     0      |
    ** |-----------------------------|----------|------------|
    ** |   data                      |   1      |     0      |
    ** |-----------------------------|----------|------------|
    ** 
    ** Please refer the datasheet for more information. 
    **    
    */ 
    if( is_cmd == true )
    {
        buf[0] = 0x00;
    }
    else
    {
        buf[0] = 0x40;
    }
    
    buf[1] = data;
    
    ret = I2C_Write(buf, 2);
}


/*
** This function sends the commands that need to used to Initialize the OLED.
**
**  Arguments:
**      none
** 
*/
static int SSD1306_DisplayInit(void)
{
    msleep(100);               // delay

    /*
    ** Commands to initialize the SSD_1306 OLED Display
    */
    SSD1306_Write(true, 0xAE); // Entire Display OFF
    SSD1306_Write(true, 0xD5); // Set Display Clock Divide Ratio and Oscillator Frequency
    SSD1306_Write(true, 0x80); // Default Setting for Display Clock Divide Ratio and Oscillator Frequency that is recommended
    SSD1306_Write(true, 0xA8); // Set Multiplex Ratio
    SSD1306_Write(true, 0x3F); // 64 COM lines
    SSD1306_Write(true, 0xD3); // Set display offset
    SSD1306_Write(true, 0x00); // 0 offset
    SSD1306_Write(true, 0x40); // Set first line as the start line of the display
    SSD1306_Write(true, 0x8D); // Charge pump
    SSD1306_Write(true, 0x14); // Enable charge dump during display on
    SSD1306_Write(true, 0x20); // Set memory addressing mode
    SSD1306_Write(true, 0x00); // Horizontal addressing mode
    SSD1306_Write(true, 0xA1); // Set segment remap with column address 127 mapped to segment 0
    SSD1306_Write(true, 0xC8); // Set com output scan direction, scan from com63 to com 0
    SSD1306_Write(true, 0xDA); // Set com pins hardware configuration
    SSD1306_Write(true, 0x12); // Alternative com pin configuration, disable com left/right remap
    SSD1306_Write(true, 0x81); // Set contrast control
    SSD1306_Write(true, 0x80); // Set Contrast to 128
    SSD1306_Write(true, 0xD9); // Set pre-charge period
    SSD1306_Write(true, 0xF1); // Phase 1 period of 15 DCLK, Phase 2 period of 1 DCLK
    SSD1306_Write(true, 0xDB); // Set Vcomh deselect level
    SSD1306_Write(true, 0x20); // Vcomh deselect level ~ 0.77 Vcc
    SSD1306_Write(true, 0xA4); // Entire display ON, resume to RAM content display
    SSD1306_Write(true, 0xA6); // Set Display in Normal Mode, 1 = ON, 0 = OFF
    SSD1306_Write(true, 0x2E); // Deactivate scroll
    SSD1306_Write(true, 0xAF); // Display ON in normal mode
    
    return 0;
}

/*
** This function Fills the complete OLED with this data byte.
**
**  Arguments:
**      data  -> Data to be filled in the OLED
** 
*/
static void SSD1306_Fill(unsigned char data)
{
    unsigned int total  = 128 * 8;  // 8 pages x 128 segments x 8 bits of data
    unsigned int i      = 0;
    
    //Fill the Display
    for(i = 0; i < total; i++)
    {
        SSD1306_Write(false, data);
    }
}

/*
** This function getting called when the slave has been found
** Note : This will be called only once when we load the driver.
*/
static int etx_oled_probe(struct i2c_client *client,
                         const struct i2c_device_id *id)
{
    SSD1306_DisplayInit();
    
    //fill the OLED with this data
    SSD1306_Fill(0xFF);

    pr_info("OLED Probed!!!\n");
    
    return 0;
}

/*
** This function getting called when the slave has been removed
** Note : This will be called only once when we unload the driver.
*/
static int etx_oled_remove(struct i2c_client *client)
{   
    //fill the OLED with this data
    SSD1306_Fill(0x00);
    
    pr_info("OLED Removed!!!\n");
    return 0;
}

/*
** Structure that has slave device id
*/
static const struct i2c_device_id etx_oled_id[] = {
        { SLAVE_DEVICE_NAME, 0 },
        { }
};
MODULE_DEVICE_TABLE(i2c, etx_oled_id);

/*
** I2C driver Structure that has to be added to linux
*/
static struct i2c_driver etx_oled_driver = {
        .driver = {
            .name   = SLAVE_DEVICE_NAME,
            .owner  = THIS_MODULE,
        },
        .probe          = etx_oled_probe,
        .remove         = etx_oled_remove,
        .id_table       = etx_oled_id,
};

/*
** I2C Board Info strucutre
*/
static struct i2c_board_info oled_i2c_board_info = {
        I2C_BOARD_INFO(SLAVE_DEVICE_NAME, SSD1306_SLAVE_ADDR)
    };

/*
** Module Init function
*/
static int __init etx_driver_init(void)
{
    int ret = -1;
    etx_i2c_adapter     = i2c_get_adapter(I2C_BUS_AVAILABLE);
    
    if( etx_i2c_adapter != NULL )
    {
        etx_i2c_client_oled = i2c_new_device(etx_i2c_adapter, &oled_i2c_board_info);
        
        if( etx_i2c_client_oled != NULL )
        {
            i2c_add_driver(&etx_oled_driver);
            ret = 0;
        }
        
        i2c_put_adapter(etx_i2c_adapter);
    }
    
    pr_info("Driver Added!!!\n");
    return ret;
}

/*
** Module Exit function
*/
static void __exit etx_driver_exit(void)
{
    i2c_unregister_device(etx_i2c_client_oled);
    i2c_del_driver(&etx_oled_driver);
    pr_info("Driver Removed!!!\n");
}

module_init(etx_driver_init);
module_exit(etx_driver_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("EmbeTronicX <[email protected]>");
MODULE_DESCRIPTION("Simple I2C driver explanation (SSD_1306 OLED Display Interface)");
MODULE_VERSION("1.34");

Makefile

obj-m += driver.o
 
KDIR = /lib/modules/$(shell uname -r)/build
 
 
all:
    make -C $(KDIR)  M=$(shell pwd) modules
 
clean:
    make -C $(KDIR)  M=$(shell pwd) clean

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.

Please find the other Linux device driver tutorials here.

You can also read the below tutorials.

Linux Device Driver TutorialsC Programming Tutorials
FreeRTOS TutorialsNuttX RTOS Tutorials
RTX RTOS TutorialsInterrupts Basics
I2C Protocol – Part 1 (Basics)I2C Protocol – Part 2 (Advanced Topics)
STM32 TutorialsLPC2148 (ARM7) Tutorials
PIC16F877A Tutorials8051 Tutorials
Unit Testing in C TutorialsESP32-IDF Tutorials
Raspberry Pi TutorialsEmbedded Interview Topics
Reset Sequence in ARM Cortex-M4BLE Basics
VIC and NVIC in ARMSPI – Serial Peripheral Interface Protocol
STM32F7 Bootloader TutorialsRaspberry PI Pico Tutorials
STM32F103 Bootloader TutorialsRT-Thread RTOS Tutorials
Zephyr RTOS Tutorials - STM32Zephyr RTOS Tutorials - ESP32
AUTOSAR TutorialsUDS Protocol Tutorials
Product ReviewsSTM32 MikroC Bootloader Tutorial
VHDL Tutorials
Subscribe
Notify of
guest

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

25 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Table of Contents