零. Linux I2C驱动框架
一. 驱动开发步骤
- Get the adapter that is available using
i2c_get_adapter() //获取i2c控制器
- Add the device using
i2c_new_device() //创建i2c device
- Add the driver to the subsystem using
i2c_add_driver() //创建device的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 usingi2c_del_driver()
.
二. Structures used for the I2C bus driver
But have you ever wondered that who is sending the START, STOP, ADDRESS, READ with ACK, READ with NACK, etc? —— The I2C bus driver will do such operations.
There are two structures that you need to use in order to write the i2c bus driver in the Linux kernel.
- Algorithm Structure
- Adapter Structure
Algorithm Structure
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
};
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 adapterunreg_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;
};
*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 structurebus_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.
三. API used for the I2C bus driver
1.Add the adapter to the subsystem
int i2c_add_adapter (struct i2c_adapter * adapter); ——
This API is used to register the adapter to the subsystem. But this will assign the dynamic bus number.
int i2c_add_numbered_adapter ( struct i2c_adapter * adap); ——
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.
2.Delete the adapter from the subsystem
void i2c_del_adapter ( struct i2c_adapter * adap);
四.程序示例
说明:
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.
/***************************************************************************//**
* \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 <embetronicx@gmail.com>");
MODULE_DESCRIPTION("Simple I2C Bus driver explanation");
MODULE_VERSION("1.35");
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
Device driver —— 用于验证上面Bus Driver
I2C device driver的说明详见《Linux I2C bus驱动》
/***************************************************************************//**
* \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 <embetronicx@gmail.com>");
MODULE_DESCRIPTION("Simple I2C driver explanation (SSD_1306 OLED Display Interface)");
MODULE_VERSION("1.36");