[IMX6ULL driver development learning] 09.Linux I2C driver framework introduction and driver template

 Reference: Linux I2C driver_linux i2c driver_Fengjian Liuli·’s blog-CSDN blog

Table of contents

1. Introduction to I2C driver framework

1.1 I2C bus driver

1.2 I2C device driver

2. I2C bus-device-driver model

2.1 i2c_driver

2.2 i2c_client

2.3 I2C device data sending, receiving and processing

3. Linux I2C driver template


1. Introduction to I2C driver framework

The I2C architecture in the Linux kernel is divided into 3 parts:

  • I2C core : The I2C core provides registration and unregistration methods for I2C bus drivers and device drivers. 
  • I2C bus driver : The I2C bus driver is  the implementation of the adapter side of the I2C hardware architecture . The adapter can be controlled by the CPU or even directly integrated inside the CPU. The I2C bus driver is  the I2C controller driver of the SOC , also called  the I2C adapter driver .
  • I2C device driver : I2C device driver is  the implementation of the device side of the I2C hardware architecture . The device is generally mounted on the I2C adapter controlled by the CPU, and exchanges data with the CPU through the I2C adapter . 

1.1 I2C bus driver

The I2C bus is similar to the platform bus. The difference is that the platform bus is a virtual bus, while the I2C bus actually
exists
. For devices that use I2C communication, just use the I2C summary directly in the driver . The focus of the I2C bus driver is the I2C adapter driver, which mainly involves two structures: i2c_adapter and i2c_algorithm. The i2c_adapter structure is used in the Linux kernel to represent the I2C adapter. The i2c_adapter structure is defined in the include/linux/i2c.h file

struct i2c_adapter {
    struct module *owner;
    unsigned int class; /* classes to allow probing for */
    const struct i2c_algorithm *algo; /* 总线访问算法 */
    void *algo_data;
 
    /* data fields that are valid for all devices */
    struct rt_mutex bus_lock;
 
    int timeout; /* in jiffies */
    int retries;
    struct device dev; /* the adapter device */
 
    int nr;
    char name[48];
    struct completion dev_released;
 
    struct mutex userspace_clients_lock;
    struct list_head userspace_clients;
 
    struct i2c_bus_recovery_info *bus_recovery_info;
    const struct i2c_adapter_quirks *quirks;
};

The pointer variable algo of type i2c_algorithm. For an I2C adapter, read and write API functions must be provided externally. The device driver can use these API functions to complete read and write operations.  i2c_algorithm is the method by which I2C adapters communicate with IIC devices . The i2c_algorithm structure is defined in the include/linux/i2c.h file

 
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);
 
    /* To determine what the adapter supports */
    u32 (*functionality) (struct i2c_adapter *);
    ......
};

Generally, SOC's I2C bus drivers are written by semiconductor manufacturers, so most of them only need to focus on I2C device drivers.

1.2 I2C device driver

There are two main important structures in the I2C device driver:  i2c_client and i2c_driver. i2c_client describes device information, and i2c_driver describes driver content .

When the driver and device are successfully matched, each time an I2C device is detected, an i2c_client will be assigned to the I2C device . This ic_client stores all the information of the device, such as the chip address. The i2c_client structure is defined in the include/linux/i2c.h file

struct i2c_client {
    unsigned short flags; /* 标志 */
    unsigned short addr; /* 芯片地址, 7 位,存在低 7 位*/
    ......
    char name[I2C_NAME_SIZE]; /* 名字 */
    struct i2c_adapter *adapter; /* 对应的 I2C 适配器 */
    struct device dev; /* 设备结构体 */
    int irq; /* 中断 */
    struct list_head detected;
    ......
};

i2c_driver is similar to platform_driver, and is the focus of writing I2C device drivers. The i2c_driver structure is defined in the include/linux/i2c.h file.

struct i2c_driver {
    unsigned int class;
 
    /* Notifies the driver that a new bus has appeared. You should
    * avoid using this, it will be removed in a near future.
    */
    int (*attach_adapter)(struct i2c_adapter *) __deprecated;
 
    /* Standard driver model interfaces */
    int (*probe)(struct i2c_client *, const struct i2c_device_id *);
    int (*remove)(struct i2c_client *);
 
    /* driver model interfaces that don't relate to enumeration */
    void (*shutdown)(struct i2c_client *);
 
    /* Alert callback, for example for the SMBus alert protocol.
    * The format and meaning of the data value depends on the
    * protocol.For the SMBus alert protocol, there is a single bit
    * of data passed as the alert response's low bit ("eventflag"). */
    void (*alert)(struct i2c_client *, unsigned int data);
    /* a ioctl like command that can be used to perform specific
    * functions with the device.
    */
    int (*command)(struct i2c_client *client, unsigned int cmd,void *arg);
 
    struct device_driver driver;
    const struct i2c_device_id *id_table;
 
    /* Device detection callback for automatic device creation */
    int (*detect)(struct i2c_client *, struct i2c_board_info *);
    const unsigned short *address_list;
    struct list_head clients;
};

The probe function will be executed when the I2C device and driver are successfully matched. device_driver driver structure, if you use the device tree, you need to set the of_match_table member variable of device_driver, that is, the driver's compatibility. Devices that do not use the device tree need to set the id_table device matching ID table .

Construct an i2c_driver structure, which will indicate which devices are supported. Register the i2c_driver structure in the entry function. If i2c_driver finds an i2c_client that can be supported, the probe function will be called. In the probe function, the client information is recorded, the character device is registered, and file_operations are registered. Automatically create device nodes

There is no essential difference between the I2C driver and the ordinary character device driver. The only difference is: i2c_transfer is used when initiating data transmission. This function requires the use of an adapter (i2c controller). When the probe function is called, the kernel will pass in i2c_client. i2c_client Contains i2c controller.

2. I2C bus-device-driver model

2.1 i2c_driver

2c_driver indicates which devices can be supported:

  • Use of_match_table to determine

    • In the device tree, an I2C device node can be created under an I2C controller node.

      • If the compatible attribute of the I2C device node is compatible with an item of of_match_table, the match is successful.

    • If i2c_client.name is the same as a certain of_match_table[i].compatible value, the match is successful.

  • Use id_table to determine

    • If i2c_client.name has the same value as an id_table[i].name, the match is successful.

After i2c_driver successfully matches i2c_client, the i2c_driver.probe function is called.

/* 传统匹配方式 ID 列表 */
static const struct i2c_device_id xxx_id[] = {
    {"xxx", 0},
    {}
};
 
 /* 设备树匹配列表 */
static const struct of_device_id xxx_of_match[] = {
    { .compatible = "xxx" },
    { /* Sentinel */ }
};

/* i2c 驱动结构体 */
static struct i2c_driver xxx_driver = {
    .probe = xxx_probe,
    .remove = xxx_remove,
    .driver = {
        .owner = THIS_MODULE,
        .name = "xxx",
        .of_match_table = xxx_of_match,  //使用设备树
    },
    .id_table = xxx_id,   //未使用设备树
};

2.2 i2c_client

i2c_client represents an I2C device. There are four ways to create i2c_client:

(1) Method 1: Create through device tree (commonly used)

i2c1: i2c@400a0000 {
		/* ... master properties skipped ... */
		clock-frequency = <100000>;

		flash@50 {
			compatible = "atmel,24c256";
			reg = <0x50>;
		};

		pca9532: gpio@60 {
			compatible = "nxp,pca9532";
			gpio-controller;
			#gpio-cells = <2>;
			reg = <0x60>;
		};
	};

Add a flash sub-node to i2c1, flash@50 is the name of the sub-node, and the 50 after @ is the I2C device address . The compatible attribute value is atmel,24c256. The reg attribute also sets the I2C device address. The creation of I2C device nodes mainly involves the setting of compatible attributes and reg attributes. One is used to match the driver and the other is used to set the device address .

(2) Method 2:

Sometimes it is impossible to know which I2C bus the device is mounted on, or its corresponding I2C bus number. But you can know the corresponding i2c_adapter structure through other methods. You can use the following two functions to create i2c_client:

i2c_new_device

 static struct i2c_board_info sfe4001_hwmon_info = {
	I2C_BOARD_INFO("max6647", 0x4e),
  };

  int sfe4001_init(struct efx_nic *efx)
  {
	(...)
	efx->board_info.hwmon_client =
		i2c_new_device(&efx->i2c_adap, &sfe4001_hwmon_info);

	(...)
  }

i2c_new_probed_device

static const unsigned short normal_i2c[] = { 0x2c, 0x2d, I2C_CLIENT_END };

  static int usb_hcd_nxp_probe(struct platform_device *pdev)
  {
	(...)
	struct i2c_adapter *i2c_adap;
	struct i2c_board_info i2c_info;

	(...)
	i2c_adap = i2c_get_adapter(2);
	memset(&i2c_info, 0, sizeof(struct i2c_board_info));
	strscpy(i2c_info.type, "isp1301_nxp", sizeof(i2c_info.type));
	isp1301_i2c_client = i2c_new_probed_device(i2c_adap, &i2c_info,
						   normal_i2c, NULL);
	i2c_put_adapter(i2c_adap);
	(...)
  }

difference:

  • i2c_new_device: will create i2c_client even if the device does not exist

  • i2c_new_probed_device:

    • If it succeeds, it will create i2c_client and indicate that this device definitely exists.

    • The address of the I2C device may change. For example, when the level of pin A2A1A0 of AT24C02 is different, the device address will be different.

    • Possible addresses can be listed

    • i2c_new_probed_device uses these addresses to determine whether the device exists

(3) Method 3 (not recommended): Use the i2c_driver.detect function to determine whether there is a corresponding I2C device and generate i2c_client  

(4) Method 4: When debugging is generated through user-space, or when it is inconvenient to explicitly generate i2c_client through code, it can be generated through user space.

// 创建一个i2c_client, .name = "eeprom", .addr=0x50, .adapter是i2c-3
  # echo eeprom 0x50 > /sys/bus/i2c/devices/i2c-3/new_device
  
// 删除一个i2c_client
  # echo 0x50 > /sys/bus/i2c/devices/i2c-3/delete_device

2.3 I2C device data sending, receiving and processing

In the I2C device driver, the creation, initialization and registration of the i2c_driver structure must first be completed. When the device and driver are successfully matched, the probe function will be executed. The probe function is a set of processes for executing the character device driver.

Generally, you need to initialize the I2C device in the probe function. To initialize the I2C device, you need to use the i2c_transfer function to read and write the I2C device register. The i2c_transfer function will call the master_xfer function in the i2c_algorithm in the I2C adapter. For I.MX6U, it is the i2c_imx_xfer function.
 

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

adap: The I2C adapter used,  i2c_client will save its corresponding i2c_adapter
msgs: One or more messages to be sent by I2C
num: The number of messages, the number of msgs
Return value: Negative value, failure, other non-negative values, msgs sent quantity

The msgs parameter is a pointer parameter of type i2c_msg. The Linux kernel uses the i2c_msg structure to describe a message.

struct i2c_msg {
    __u16 addr; /* 从机地址 */
    __u16 flags; /* 标志 */
    #define I2C_M_TEN 0x0010
    #define I2C_M_RD 0x0001
    #define I2C_M_STOP 0x8000
    #define I2C_M_NOSTART 0x4000
    #define I2C_M_REV_DIR_ADDR 0x2000
    #define I2C_M_IGNORE_NAK 0x1000
    #define I2C_M_NO_RD_ACK 0x0800
    #define I2C_M_RECV_LEN 0x0400
    __u16 len; /* 消息(本 msg)长度 */
    __u8 *buf; /* 消息数据 */
};

Before using the i2c_transfer function to send data, you must build i2c_msg and use i2c_transfer to perform I2C data sending and receiving templates:

/* 设备结构体 */
struct xxx_dev {
    ......
    void *private_data; /* 私有数据,一般会设置为 i2c_client */
};
 
/*
* @description : 读取 I2C 设备多个寄存器数据
* @param – dev : I2C 设备
* @param – reg : 要读取的寄存器首地址
* @param – val : 读取到的数据
* @param – len : 要读取的数据长度
* @return : 操作结果
*/
static int xxx_read_regs(struct xxx_dev *dev, u8 reg, void *val,
int len)
{
    int ret;
    struct i2c_msg msg[2];
    struct i2c_client *client = (struct i2c_client *)
    dev->private_data;
 
    /* msg[0],第一条写消息,发送要读取的寄存器首地址 */
    msg[0].addr = client->addr; /* I2C 器件地址 */
    msg[0].flags = 0; /* 标记为发送数据 */
    msg[0].buf = &reg; /* 读取的首地址 */
    msg[0].len = 1; /* reg 长度 */
 
    /* msg[1],第二条读消息,读取寄存器数据 */
    msg[1].addr = client->addr; /* I2C 器件地址 */
    msg[1].flags = I2C_M_RD; /* 标记为读取数据 */
    msg[1].buf = val; /* 读取数据缓冲区 */
    msg[1].len = len; /* 要读取的数据长度 */
    ret = i2c_transfer(client->adapter, msg, 2);
    if(ret == 2) 
    {
        ret = 0;
    } 
    else 
    {
        ret = -EREMOTEIO;
    }
    return ret;
}
 
/*
* @description : 向 I2C 设备多个寄存器写入数据
* @param – dev : 要写入的设备结构体
* @param – reg : 要写入的寄存器首地址
* @param – buf : 要写入的数据缓冲区
* @param – len : 要写入的数据长度
* @return : 操作结果
*/
static s32 xxx_write_regs(struct xxx_dev *dev, u8 reg, u8 *buf,u8 len)
{
    u8 b[256];
    struct i2c_msg msg;
    struct i2c_client *client = (struct i2c_client *)
    dev->private_data;
 
    b[0] = reg; /* 寄存器首地址 */
    memcpy(&b[1],buf,len); /* 将要发送的数据拷贝到数组 b 里面 */
 
    msg.addr = client->addr; /* I2C 器件地址 */
    msg.flags = 0; /* 标记为写数据 */
 
    msg.buf = b; /* 要发送的数据缓冲区 */
    msg.len = len + 1; /* 要发送的数据长度 */
 
    return i2c_transfer(client->adapter, &msg, 1);
}

Add a pointer member variable private_data that executes void in the device structure . This member variable is used to save the private data of the device. In the I2C device driver, it is generally pointed to the i2c_client corresponding to the I2C device .

The xxx_read_regs function is used to read multiple register data of the I2C device, and then defines an i2c_msg array with 2 array elements. As shown in the figure, I2C write timing diagram, because when I2C reads data, it must first send the register address to be read, and then read the data, so two i2c_msg need to be prepared. One is used to send the register address and one is used to read the register value.

For msg[0], set flags to 0, which means writing data . The addr of msg[0] is the device address of the I2C device, and the buf member variable of msg[0] is the address of the register to be read. For msg[1], set flags to I2C_M_RD, which means read data . The buf member variable of msg[1] is used to save the read data, and the len member variable is the length of the data to be read. Call the i2c_transfer function to complete the I2C data read operation.


 

The xxx_write_regs function is used to write data to multiple registers of the I2C device. Array b is used to store the first address of the register and the data to be sent, and the addr of msg is set to the address of the I2C device. Then set the flags of msg to 0, that is, write data. Set the data to be sent, which is array b. Set the len of msg to len+1, because a byte register address is added. Finally, complete the write operation to the I2C device through the i2c_transfer function.

There are also two API functions for I2C data sending and receiving operations, both of which call i2c_transfer.

I2C data sending function i2c_master_send:

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

client: i2c_client corresponding to the I2C device
buf: data to be sent
count: the number of data bytes to be sent, which must be less than 64KB. It is assumed that the len member variable of i2c_msg is a u16 (unsigned
16-bit) type data.
Return value: negative value, failure, other non-negative value, number of bytes sent

The I2C data receiving function is i2c_master_recv:

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

client: i2c_client buf corresponding to the I2C device
: data to be received

count: The number of data bytes to be received must be less than 64KB. It is assumed that the len member variable of i2c_msg is a u16 (unsigned
16-bit) type of data.
Return value: Negative value, failure, other non-negative values, the number of bytes sent.

3. Linux I2C driver template

i2c_drv.c

#include "linux/i2c.h"
#include <linux/module.h>
#include <linux/poll.h>

#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/gpio/consumer.h>
#include <linux/platform_device.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/slab.h>
#include <linux/fcntl.h>
#include <linux/timer.h>

/* 主设备号                                                                 */
static int major = 0;
static struct class *my_i2c_class;

struct i2c_client *g_client;

static DECLARE_WAIT_QUEUE_HEAD(gpio_wait);
struct fasync_struct *i2c_fasync;


/* 实现对应的open/read/write等函数,填入file_operations结构体                   */
static ssize_t i2c_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{
	int err;

	struct i2c_msg msgs[2];

	/* 初始化i2c_msg */

	err = i2c_transfer(g_client->adapter, msgs, 2);

	/* copy_to_user  */
	
	return 0;
}

static ssize_t i2c_drv_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
	int err;

	/* copy_from_user  */


	struct i2c_msg msgs[2];

	/* 初始化i2c_msg */

	err = i2c_transfer(g_client->adapter, msgs, 2);

	
	return 0;    
}


static unsigned int i2c_drv_poll(struct file *fp, poll_table * wait)
{
	//printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	poll_wait(fp, &gpio_wait, wait);
	//return is_key_buf_empty() ? 0 : POLLIN | POLLRDNORM;
	return 0;
}

static int i2c_drv_fasync(int fd, struct file *file, int on)
{
	if (fasync_helper(fd, file, on, &i2c_fasync) >= 0)
		return 0;
	else
		return -EIO;
}


/* 定义自己的file_operations结构体                                              */
static struct file_operations i2c_drv_fops = {
	.owner	 = THIS_MODULE,
	.read    = i2c_drv_read,
	.write   = i2c_drv_write,
	.poll    = i2c_drv_poll,
	.fasync  = i2c_drv_fasync,
};


static int i2c_drv_probe(struct i2c_client *client,
			const struct i2c_device_id *id)
{
	// struct device_node *np = client->dev.of_node;   从client获取设备节点
	// struct i2c_adapter *adapter = client->adapter;  从client获取控制器

	/* 记录client */
	g_client = client;

	/* 注册字符设备 */
	/* 注册file_operations 	*/
	major = register_chrdev(0, "100ask_i2c", &i2c_drv_fops);  /* /dev/gpio_desc */

	my_i2c_class = class_create(THIS_MODULE, "100ask_i2c_class");
	if (IS_ERR(my_i2c_class)) {
		printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
		unregister_chrdev(major, "100ask_i2c");
		return PTR_ERR(my_i2c_class);
	}

	device_create(my_i2c_class, NULL, MKDEV(major, 0), NULL, "myi2c"); /* /dev/myi2c */
	
	return 0;
}

static int i2c_drv_remove(struct i2c_client *client)
{
	/* 反注册字符设备 */
	device_destroy(my_i2c_class, MKDEV(major, 0));
	class_destroy(my_i2c_class);
	unregister_chrdev(major, "100ask_i2c");

	return 0;
}

static const struct of_device_id myi2c_dt_match[] = {
	{ .compatible = "100ask,i2cdev" },
	{},
};
static struct i2c_driver my_i2c_driver = {
	.driver = {
		   .name = "100ask_i2c_drv",
		   .owner = THIS_MODULE,
		   .of_match_table = myi2c_dt_match,
	},
	.probe = i2c_drv_probe,
	.remove = i2c_drv_remove,
};


static int __init i2c_drv_init(void)
{
	/* 注册i2c_driver */
	return i2c_add_driver(&my_i2c_driver);
}

static void __exit i2c_drv_exit(void)
{
	/* 反注册i2c_driver */
	i2c_del_driver(&my_i2c_driver);
}

/* 7. 其他完善:提供设备信息,自动创建设备节点                                     */

module_init(i2c_drv_init);
module_exit(i2c_drv_exit);

MODULE_LICENSE("GPL");


Guess you like

Origin blog.csdn.net/qq_43460230/article/details/132477473