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 Real I2C Bus Linux Device Driver example using Raspberry PI – Linux Device Driver Tutorial Part 39.
You can also read BMP280 Bosch Pressure Sensor I2C Device Driver, I2C dummy bus driver, I2C client driver, Sysfs, Procfs, Workqueue, Completion, Softirq, and threaded IRQ in the Linux device driver.
We are using the Raspberry PI 4 Model B for this demonstration.
Table of Contents
Real I2C Bus Linux Device Driver
Prerequisites
- I2C Introduction – Part 1 (Basics)
- I2C Introduction – Part 2 (Advanced Topics)
- I2C Client Driver in Linux kernel
- I2C Dummy Driver in Linux kernel
- GPIO Linux Driver
Hardware Required
- Raspberry Pi
- SSD1306 OLED I2C Display
Bring up Raspberry PI
- Install Raspberry Pi OS (32-bit) with desktop on the SD card.
- 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 dummy I2C bus driver in the Linux kernel. It doesn’t send any data to the Salve device. So in this tutorial, we have come up with the real I2C bus Linux device driver. Using this I2C bus driver, we can send data to the slave device. Let’s start.
We have discussed the APIs too in the last tutorials. In this tutorial, we just used a new API called i2c_add_numbered_adapter().
This API will allocate the bus number that we are asking if that is available. In the last tutorial, we have not used this. Instead, we used i2c_add_adapter().
This API will allocate the bus number dynamically.
We can move to the example straightaway since we don’t have any other thing to discuss.
Example Programming
In this I2C client Driver tutorial, we have just written the I2C Client driver which communicates to the slave device called SSD1306 OLED I2C Display by using its internal I2C bus driver. That example demonstrates it will just fill something into the display while loading and it will clear the display while unloading. The same thing only we are going to try with our own I2C bus driver in this tutorial.
Connection Diagram
- SCL – GPIO 20
- SDA – GPIO 21
I2C Bus Driver Source Code
[Get the source code from GitHub]
Note: In the below example I have implemented the I2C communication by using the bit-banging method (Linux Kernel’s GPIO API has been used). I have not implemented the I2C read part. And also we have not handled arbitration, clock stretching, etc in this tutorial as this is just an example program. This is only for demonstration purposes.
/***************************************************************************//** * \file driver_bus.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> #include <linux/gpio.h> #define ADAPTER_NAME "ETX_I2C_ADAPTER" /*------------ I2C related functions - Bit banging method - START --------------*/ #define SCL_GPIO 20 //GPIO act as SCL #define SDA_GPIO 21 //GPIO act as SDA #define I2C_DELAY usleep_range(5, 10) /* ** Function to read the SCL GPIO */ static bool ETX_I2C_Read_SCL(void) { gpio_direction_input(SCL_GPIO); return gpio_get_value(SCL_GPIO); } /* ** Function to read the SDA GPIO */ static bool ETX_I2C_Read_SDA(void) { gpio_direction_input(SDA_GPIO); return gpio_get_value(SDA_GPIO); } /* ** Function to clear the SCL GPIO */ static void ETX_I2C_Clear_SCL(void) { gpio_direction_output(SCL_GPIO, 0); gpio_set_value(SCL_GPIO, 0); } /* ** Function to clear the SDA GPIO */ static void ETX_I2C_Clear_SDA(void) { gpio_direction_output(SDA_GPIO, 0); gpio_set_value(SDA_GPIO, 0); } /* ** Function to set the SCL GPIO */ static void ETX_I2C_Set_SCL(void) { gpio_direction_output(SCL_GPIO, 1); gpio_set_value(SCL_GPIO, 1); } /* ** Function to set the SDA GPIO */ static void ETX_I2C_Set_SDA(void) { gpio_direction_output(SDA_GPIO, 1); gpio_set_value(SDA_GPIO, 1); } /* ** Function to Initialize the GPIOs */ static int ETX_I2C_Init( void ) { int ret = 0; do //Break if any error { //Checking the SCL GPIO is valid or not if(gpio_is_valid(SCL_GPIO) == false){ pr_err("SCL GPIO %d is not valid\n", SCL_GPIO); ret = -1; break; } //Checking the SDA GPIO is valid or not if(gpio_is_valid(SDA_GPIO) == false){ pr_err("SDA GPIO %d is not valid\n", SDA_GPIO); ret = -1; break; } //Requesting the SCL GPIO if(gpio_request(SCL_GPIO,"SCL_GPIO") < 0){ pr_err("ERROR: SCL GPIO %d request\n", SCL_GPIO); ret = -1; break; } //Requesting the SDA GPIO if(gpio_request(SDA_GPIO,"SDA_GPIO") < 0){ pr_err("ERROR: SDA GPIO %d request\n", SDA_GPIO); //free already requested SCL GPIO gpio_free(SCL_GPIO); ret = -1; break; } /* ** configure the SCL GPIO as output, We will change the ** direction later as per our need. */ gpio_direction_output(SCL_GPIO, 1); /* ** configure the SDA GPIO as output, We will change the ** direction later as per our need. */ gpio_direction_output(SDA_GPIO, 1); } while(false); return ret; } /* ** Function to Deinitialize the GPIOs */ static void ETX_I2C_DeInit( void ) { //free both the GPIOs gpio_free(SCL_GPIO); gpio_free(SDA_GPIO); } /* ** Function to send the START condition ** ______ ** \ ** SDA \_____ ** : ** : ** ___:____ ** SCL / ** ___/ */ static void ETX_I2C_Sart( void ) { ETX_I2C_Set_SDA(); ETX_I2C_Set_SCL(); I2C_DELAY; ETX_I2C_Clear_SDA(); I2C_DELAY; ETX_I2C_Clear_SCL(); I2C_DELAY; } /* ** Function to send the STOP condition ** _____ ** / ** SDA _______/ ** : ** : ** __:______ ** SCL / ** ___/ */ static void ETX_I2C_Stop( void ) { ETX_I2C_Clear_SDA(); I2C_DELAY; ETX_I2C_Set_SCL(); I2C_DELAY; ETX_I2C_Set_SDA(); I2C_DELAY; ETX_I2C_Clear_SCL(); } /* ** Function to reads the SDA to get the status ** ** Retutns 0 for NACK, returns 1 for ACK */ static int ETX_I2C_Read_NACK_ACK( void ) { int ret = 1; //reading ACK/NACK I2C_DELAY; ETX_I2C_Set_SCL(); I2C_DELAY; if( ETX_I2C_Read_SDA() ) //check for ACK/NACK { ret = 0; } ETX_I2C_Clear_SCL(); return ret; } /* ** Function to send the 7-bit address to the slave ** ** Retutns 0 if success otherwise negative number */ static int ETX_I2C_Send_Addr( u8 byte, bool is_read ) { int ret = -1; u8 bit; u8 i = 0; u8 size = 7; //Writing 7bit slave address for(i = 0; i < size ; i++) { bit = ( ( byte >> ( size - ( i + 1 ) ) ) & 0x01 ); //store MSB value (bit) ? ETX_I2C_Set_SDA() : ETX_I2C_Clear_SDA(); //write MSB value I2C_DELAY; ETX_I2C_Set_SCL(); I2C_DELAY; ETX_I2C_Clear_SCL(); } // Writing Read/Write bit (8th bit) (is_read) ? ETX_I2C_Set_SDA() : ETX_I2C_Clear_SDA(); //read = 1, write = 0 I2C_DELAY; ETX_I2C_Set_SCL(); I2C_DELAY; ETX_I2C_Clear_SCL(); I2C_DELAY; if( ETX_I2C_Read_NACK_ACK() ) { //got ACK ret = 0; } return ret; } /* ** Function to send the one byte to the slave ** ** Retutns 0 if success otherwise negative number */ static int ETX_I2C_Send_Byte( u8 byte ) { int ret = -1; u8 bit; u8 i = 0; u8 size = 7; for(i = 0; i <= size ; i++) { bit = ( ( byte >> ( size - i ) ) & 0x01 ); //store MSB value (bit) ? ETX_I2C_Set_SDA() : ETX_I2C_Clear_SDA(); //write MSB value I2C_DELAY; ETX_I2C_Set_SCL(); I2C_DELAY; ETX_I2C_Clear_SCL(); } if( ETX_I2C_Read_NACK_ACK() ) { //got ACK ret = 0; } return ret; } /* ** Function to read the one byte from the slave ** ** Retutns 0 if success otherwise negative number */ static int ETX_I2C_Read_Byte( u8 *byte ) { int ret = 0; //TODO: Implement this return ret; } /* ** Function to send the number of bytes to the slave ** ** Retutns 0 if success otherwise negative number */ static int ETX_I2C_Send( u8 slave_addr, u8 *buf, u16 len ) { int ret = 0; u16 num = 0; do //Break if any error { if( ETX_I2C_Send_Addr(slave_addr, false) < 0 ) // Send the slave address { pr_err("ERROR: ETX_I2C_Send_Byte - Slave Addr\n"); ret = -1; break; } for( num = 0; num < len; num++) { if( ETX_I2C_Send_Byte(buf[num]) < 0 ) // Send the data bytes { pr_err("ERROR: ETX_I2C_Send_Byte - [Data = 0x%02x]\n", buf[num]); ret = -1; break; } } } while(false); return ret; } /* ** Function to read the number of bytes from the slave ** ** Retutns 0 if success otherwise negative number */ static int ETX_I2C_Read( u8 slave_addr, u8 *buf, u16 len ) { int ret = 0; //TODO: Implement this return ret; } /*------------ I2C related functions - Bit banging method - END ----------------*/ /* ** 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; s32 ret = 0; do //Break if any error { if( ETX_I2C_Init() < 0 ) // Init the GPIOs { pr_err("ERROR: ETX_I2C_Init\n"); break; } ETX_I2C_Sart(); // Send START conditon for(i = 0; i < num; i++) { //int j; struct i2c_msg *msg_temp = &msgs[i]; if( ETX_I2C_Send(msg_temp->addr, msg_temp->buf, msg_temp->len) < 0 ) { ret = 0; break; } ret++; #if 0 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]); } #endif } } while(false); ETX_I2C_Stop(); // Send STOP condtion ETX_I2C_DeInit(); // Deinit the GPIOs return ret; } /* ** 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__); // TODO: Implement this 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, .nr = 5, }; /* ** Module Init function */ static int __init etx_driver_init(void) { int ret = -1; ret = i2c_add_numbered_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.37");
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
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 5 as we have asked our i2c bus number as 5.
[Get the source code from GitHub]
/***************************************************************************//** * \file driver_client.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 ( 5 ) // 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.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
- Build the driver by using Makefile (
sudo make
) in both bus and client driver directories. - Load the bus driver first using
sudo insmod driver_bus.ko
- Load the client driver using
sudo insmod driver_client.ko
- See the Display is filled.
- For your understanding, I have given the I2C’s capture for the first command in
SSD1306_DisplayInit()
which is0xAE
.
Click here if you don’t see the timing diagram
- Unload the driver using
sudo rmmod driver_client
- See the Display has been cleared.
- Unload the driver using
sudo rmmod driver_bus
Output Video
Click here if you don’t see the output gif
In our next tutorial, we will see how to write a complete I2C bus driver using I2C-GPIO.
Please find the other Linux device driver tutorials here.
You can also read the below tutorials.
Embedded Software | Firmware | Linux Devic Deriver | RTOS
Hi, I am a tech blogger and an Embedded Engineer. I am always eager to learn and explore tech-related concepts. And also, I wanted to share my knowledge with everyone in a more straightforward way with easy practical examples. I strongly believe that learning by doing is more powerful than just learning by reading. I love to do experiments. If you want to help or support me on my journey, consider sharing my articles, or Buy me a Coffee! Thank you for reading my blog! Happy learning!