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.

struct i2c_algorithm {
  int (* master_xfer) (struct i2c_adapter *adap, struct i2c_msg *msgs,int num);
  int (* smbus_xfer) (struct i2c_adapter *adap, u16 addr,unsigned short flags, char read_write,u8 command, int size, union i2c_smbus_data *data);
  u32 (* functionality) (struct i2c_adapter *);
#if IS_ENABLED(CONFIG_I2C_SLAVE)
  int (* reg_slave) (struct i2c_client *client);
  int (* unreg_slave) (struct i2c_client *client);
#endif
};

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.
struct i2c_adapter {
    struct module *owner;
    unsigned int class;
    const struct i2c_algorithm *algo;
    void *algo_data;
    struct rt_mutex bus_lock;
    int timeout;
    int retries;
    struct device dev;
    int nr;
    char name[48];
    struct completion dev_released;
    struct mutex userspace_clients_lock;
    struct list_head userspace_clients;
};

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

/*
** I2C adapter Structure
*/
static struct i2c_adapter etx_i2c_adapter = {
    .owner  = THIS_MODULE,
    .class  = I2C_CLASS_HWMON,//| I2C_CLASS_SPD,
    .algo   = &etx_i2c_algorithm,
    .name   = ADAPTER_NAME,
    .nr     = 5,
};


/*
** Module Init function
*/
static int __init etx_driver_init(void)
{
    int ret = -1;
    
    //create bus driver as i2c-5 if it is available
    ret = i2c_add_numbered_adapter(&etx_i2c_adapter);
    
    pr_info("Bus Driver Added!!!\n");
    return ret;
}

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]

/***************************************************************************//**
*  \file       driver.c
*
*  \details    Simple I2C Bus driver explanation
*
*  \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 ADAPTER_NAME     "ETX_I2C_ADAPTER"

/*
** This function used to get the functionalities that are supported 
** by this bus driver.
*/
static u32 etx_func(struct i2c_adapter *adapter)
{
    return (I2C_FUNC_I2C             |
            I2C_FUNC_SMBUS_QUICK     |
            I2C_FUNC_SMBUS_BYTE      |
            I2C_FUNC_SMBUS_BYTE_DATA |
            I2C_FUNC_SMBUS_WORD_DATA |
            I2C_FUNC_SMBUS_BLOCK_DATA);
}

/*
** This function will be called whenever you call I2C read, wirte APIs like
** i2c_master_send(), i2c_master_recv() etc.
*/
static s32 etx_i2c_xfer( struct i2c_adapter *adap, struct i2c_msg *msgs,int num )
{
    int i;
    
    for(i = 0; i < num; i++)
    {
        int j;
        struct i2c_msg *msg_temp = &msgs[i];
        
        pr_info("[Count: %d] [%s]: [Addr = 0x%x] [Len = %d] [Data] = ", i, __func__, msg_temp->addr, msg_temp->len);
        
        for( j = 0; j < msg_temp->len; j++ )
        {
            pr_cont("[0x%02x] ", msg_temp->buf[j]);
        }
    }
    return 0;
}

/*
** This function will be called whenever you call SMBUS read, wirte APIs
*/
static s32 etx_smbus_xfer(  struct i2c_adapter *adap, 
                            u16 addr,
                            unsigned short flags, 
                            char read_write,
                            u8 command, 
                            int size, 
                            union i2c_smbus_data *data
                         )
{
    pr_info("In %s\n", __func__);
    return 0;
}

/*
** I2C algorithm Structure
*/
static struct i2c_algorithm etx_i2c_algorithm = {
    .smbus_xfer     = etx_smbus_xfer,
    .master_xfer    = etx_i2c_xfer,
    .functionality  = etx_func,
};

/*
** I2C adapter Structure
*/
static struct i2c_adapter etx_i2c_adapter = {
    .owner  = THIS_MODULE,
    .class  = I2C_CLASS_HWMON,//| I2C_CLASS_SPD,
    .algo   = &etx_i2c_algorithm,
    .name   = ADAPTER_NAME,
};


/*
** Module Init function
*/
static int __init etx_driver_init(void)
{
    int ret = -1;
    
    ret = i2c_add_adapter(&etx_i2c_adapter);
    
    pr_info("Bus Driver Added!!!\n");
    return ret;
}

/*
** Module Exit function
*/
static void __exit etx_driver_exit(void)
{
    i2c_del_adapter(&etx_i2c_adapter);
    pr_info("Bus 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 Bus driver explanation");
MODULE_VERSION("1.35");

Makefile

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

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.

[email protected]:~/Desktop/workspace $ tree /sys/bus/i2c/
/sys/bus/i2c/
├── devices
│   ├── i2c-1 -> ../../../devices/platform/soc/fe804000.i2c/i2c-1
│   └── i2c-11 -> ../../../devices/i2c-11
├── drivers
│   ├── dummy
│   │   ├── bind
│   │   ├── uevent
│   │   └── unbind
│   └── stmpe-i2c
│       ├── bind
│       ├── uevent
│       └── unbind
├── drivers_autoprobe
├── drivers_probe
└── uevent

6 directories, 9 files

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]

/***************************************************************************//**
*  \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   (         11 )              // I2C Bus that we have created
#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);           //commenting this line as this is not required for this tutorial

    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(0xFF);           //commenting this line as this is not required for this tutorial
    
    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)
{
    
    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);
        }
        
        i2c_put_adapter(etx_i2c_adapter);
    }
    
    pr_info("Client Driver Added!!!\n");
    return 0;
}

/*
** 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("Client 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.36");

Makefile

obj-m += driver_client.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

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.

[email protected]:~/Desktop/workspace $ dmesg 
[10645.945371] Bus Driver Added!!!
[10652.873687] [Count: 0] [etx_i2c_xfer]: [Addr = 0x3c] [Len = 2] [Data] = [0x00] [0xae] 
[10652.873694] [Count: 0] [etx_i2c_xfer]: [Addr = 0x3c] [Len = 2] [Data] = [0x00] [0xd5] 
[10652.873705] [Count: 0] [etx_i2c_xfer]: [Addr = 0x3c] [Len = 2] [Data] = [0x00] [0x80] 
[10652.873713] [Count: 0] [etx_i2c_xfer]: [Addr = 0x3c] [Len = 2] [Data] = [0x00] [0xa8] 
[10652.873721] [Count: 0] [etx_i2c_xfer]: [Addr = 0x3c] [Len = 2] [Data] = [0x00] [0x3f] 
[10652.873729] [Count: 0] [etx_i2c_xfer]: [Addr = 0x3c] [Len = 2] [Data] = [0x00] [0xd3] 
[10652.873737] [Count: 0] [etx_i2c_xfer]: [Addr = 0x3c] [Len = 2] [Data] = [0x00] [0x00] 
[10652.873745] [Count: 0] [etx_i2c_xfer]: [Addr = 0x3c] [Len = 2] [Data] = [0x00] [0x40] 
[10652.873753] [Count: 0] [etx_i2c_xfer]: [Addr = 0x3c] [Len = 2] [Data] = [0x00] [0x8d] 
[10652.873761] [Count: 0] [etx_i2c_xfer]: [Addr = 0x3c] [Len = 2] [Data] = [0x00] [0x14] 
[10652.873768] [Count: 0] [etx_i2c_xfer]: [Addr = 0x3c] [Len = 2] [Data] = [0x00] [0x20] 
[10652.873776] [Count: 0] [etx_i2c_xfer]: [Addr = 0x3c] [Len = 2] [Data] = [0x00] [0x00] 
[10652.873784] [Count: 0] [etx_i2c_xfer]: [Addr = 0x3c] [Len = 2] [Data] = [0x00] [0xa1] 
[10652.873792] [Count: 0] [etx_i2c_xfer]: [Addr = 0x3c] [Len = 2] [Data] = [0x00] [0xc8] 
[10652.873800] [Count: 0] [etx_i2c_xfer]: [Addr = 0x3c] [Len = 2] [Data] = [0x00] [0xda] 
[10652.873808] [Count: 0] [etx_i2c_xfer]: [Addr = 0x3c] [Len = 2] [Data] = [0x00] [0x12] 
[10652.873816] [Count: 0] [etx_i2c_xfer]: [Addr = 0x3c] [Len = 2] [Data] = [0x00] [0x81] 
[10652.873823] [Count: 0] [etx_i2c_xfer]: [Addr = 0x3c] [Len = 2] [Data] = [0x00] [0x80] 
[10652.873831] [Count: 0] [etx_i2c_xfer]: [Addr = 0x3c] [Len = 2] [Data] = [0x00] [0xd9] 
[10652.873839] [Count: 0] [etx_i2c_xfer]: [Addr = 0x3c] [Len = 2] [Data] = [0x00] [0xf1] 
[10652.873847] [Count: 0] [etx_i2c_xfer]: [Addr = 0x3c] [Len = 2] [Data] = [0x00] [0xdb] 
[10652.873854] [Count: 0] [etx_i2c_xfer]: [Addr = 0x3c] [Len = 2] [Data] = [0x00] [0x20] 
[10652.873862] [Count: 0] [etx_i2c_xfer]: [Addr = 0x3c] [Len = 2] [Data] = [0x00] [0xa4] 
[10652.873870] [Count: 0] [etx_i2c_xfer]: [Addr = 0x3c] [Len = 2] [Data] = [0x00] [0xa6] 
[10652.873878] [Count: 0] [etx_i2c_xfer]: [Addr = 0x3c] [Len = 2] [Data] = [0x00] [0x2e] 
[10652.873885] [Count: 0] [etx_i2c_xfer]: [Addr = 0x3c] [Len = 2] [Data] = [0x00] [0xaf] 
[10652.873893] OLED Probed!!!
[10652.873988] Client Driver Added!!!

  • 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 4 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
4
0
Would love your thoughts, please comment.x