"Linux driver: I2C driver is enough to read this article"

I. Introduction

The I2C (also written as IIC) bus supports short-distance communication between devices, and is used for data transmission between the processor and some peripheral devices. It only needs two signal lines to complete data transmission, which greatly simplifies the hardware. Resources and PCB board wiring space are occupied, so it is used in the interface between EEPROM, clock and other devices and the main control. The previous article briefly analyzed the I2C protocol, and simulated the I2C data transmission through the IO port. This article analyzes the use of the I2C protocol in the Linux system, and analyzes the architecture and working methods of the I2C driver in the Linux system. Finally, build an I2C device driver to understand the specific process of I2C device driver development.

Second, the architecture driven by IIC

insert image description here

2.1 IIC core

The I2C core registers the I2C bus with the kernel, and creates an adapter class (/sys/class/i2c-adapter) at the same time, so as to create an adapter device under the adapter class when registering the adapter for the I2C bus later. In the I2C core, the registration and cancellation methods of I2C adapter and I2C device drivers are provided.

Register the I2C adapter to the I2C bus through the i2c_add_adapter interface.
Register the I2C device driver to the I2C bus through the i2c_add_driver interface.

// linux-2.6.22.6/drivers/i2c/i2c-core.c
struct bus_type i2c_bus_type = {
    
    
	.name		= "i2c",
	.dev_attrs	= i2c_dev_attrs,
	.match		= i2c_device_match,
	.uevent		= i2c_device_uevent,
	.probe		= i2c_device_probe,
	.remove		= i2c_device_remove,
	.shutdown	= i2c_device_shutdown,
	.suspend	= i2c_device_suspend,
	.resume		= i2c_device_resume,
};

struct class i2c_adapter_class = {
    
    
    .owner			= THIS_MODULE,
    .name			= "i2c-adapter",
    .dev_attrs		= i2c_adapter_attrs,
};

static int __init i2c_init(void)
{
    
    
    int retval;

    // 注册i2c总线
    retval = bus_register(&i2c_bus_type);
    if (retval)
        return retval;
    // 在/sys/class/下创建一个适配器类 /sys/class/i2c-adapter
    return class_register(&i2c_adapter_class);
}

subsys_initcall(i2c_init);

2.2 IIC Adapter

2.2.1 Initialization and registration of adapter driver resources

Since the IIC bus controller is usually on the memory, it is also connected to the platform bus, and it must be executed through the matching of paltform_driver and paltform_device. In the probe function of the paltform_driver, two tasks are usually done.

  • Initialize the hardware resources used by the I2C adapter, such as applying for i/o address, interrupt number, clock, etc.
  • An adapter is registered with the I2C bus through the i2c_add_adapter interface.
static const struct i2c_algorithm s3c24xx_i2c_algorithm = {
    
    
	.master_xfer		= s3c24xx_i2c_xfer,
	.functionality		= s3c24xx_i2c_func,
};

static struct s3c24xx_i2c s3c24xx_i2c = {
    
    
	.lock		= __SPIN_LOCK_UNLOCKED(s3c24xx_i2c.lock),
	.wait		= __WAIT_QUEUE_HEAD_INITIALIZER(s3c24xx_i2c.wait),
	.tx_setup	= 50,
	.adap		= {
    
    
		.name			= "s3c2410-i2c",
		.owner			= THIS_MODULE,
		.algo			= &s3c24xx_i2c_algorithm,
		.retries		= 2,
		.class			= I2C_CLASS_HWMON,
	},
};

s3c24xx_i2c_probe ->
    ......
    // 时钟
    i2c->clk = clk_get(&pdev->dev, "i2c");
	clk_enable(i2c->clk);
	......
	// i/o资源
    i2c->ioarea = request_mem_region(res->start, (res->end-res->start)+1,
					 pdev->name);
	i2c->regs = ioremap(res->start, (res->end-res->start)+1);
    ......
    //中断
    ret = request_irq(res->start, s3c24xx_i2c_irq, IRQF_DISABLED,
			  pdev->name, i2c);
	......
        
    struct s3c24xx_i2c *i2c = &s3c24xx_i2c;
	// 注册适配器
	i2c_add_adapter(&i2c->adap)
        
	......
	

2.2.2 Communication method in IIC adapter

The I2C adapter provides a communication method for the device driver that matches it, that is, an interface for data transmission. It mainly implements the master_xfer function and functionality function of the i2c_algorithm structure. The functionality function is used to return the communication protocol supported by the algorithm. The master_xfer function completes on the I2C adapter each IIC message in the i2c_msg array passed to it.

static u32 s3c24xx_i2c_func(struct i2c_adapter *adap)
{
    
    
	return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | I2C_FUNC_PROTOCOL_MANGLING;
}

static int s3c24xx_i2c_xfer(struct i2c_adapter *adap,
			struct i2c_msg *msgs, int num)
{
    
    
	struct s3c24xx_i2c *i2c = (struct s3c24xx_i2c *)adap->algo_data;
	int retry;
	int ret;

	for (retry = 0; retry < adap->retries; retry++) {
    
    

		ret = s3c24xx_i2c_doxfer(i2c, msgs, num);

		if (ret != -EAGAIN)
			return ret;

		dev_dbg(i2c->dev, "Retrying transmission (%d)\n", retry);

		udelay(100);
	}

	return -EREMOTEIO;
}

2.2.3 Matching of IIC adapter and IIC device driver

s3c24xx_i2c_probe ->
    struct s3c24xx_i2c *i2c = &s3c24xx_i2c;
	// 注册适配器
	i2c_add_adapter(&i2c->adap) ->
		i2c_register_adapter(adapter) ->
            ......
            // 将该注册的适配器加入到适配器链表中
        	list_add_tail(&adap->list, &adapters);

        	// 在 /sys/class/i2c-adapter设备类下创建一个设备
        	// /sys/class/i2c-adapter/i2c-0
        	sprintf(adap->dev.bus_id, "i2c-%d", adap->nr);
        	adap->dev.release = &i2c_adapter_dev_release;
        	adap->dev.class = &i2c_adapter_class;
        	res = device_register(&adap->dev);

        	// 从I2C设备驱动链表中,取出每一项驱动,执行驱动的attach_adapter接口,以匹配适配器和设备驱动
        	// IIC设备驱动链表由IIC设备驱动注册时设置。
			list_for_each(item,&drivers) {
    
    
    		driver = list_entry(item, struct i2c_driver, list);
    		if (driver->attach_adapter)
    			/* We ignore the return code; if it fails, too bad */
                // 调用IIC设备驱动的attach_adapter接口                
    			driver->attach_adapter(adap); 
    		}
			......

2.3 IIC device driver

2.3.1 IIC general device driver

The function of the I2C adapter device file is realized, and each IIC adapter is assigned a device. When accessing the device through the adapter, the major device number is 89, and the minor device number is 0~255. The application program can use the file operation interface open(), write(), read(), ioctl(), etc. through the "/dev/i2c-%d" device node to use the corresponding IIC adapter to access an I2C device.


static const struct file_operations i2cdev_fops = {
    
    
	.owner		= THIS_MODULE,
	.llseek		= no_llseek,
	.read		= i2cdev_read,
	.write		= i2cdev_write,
	.ioctl		= i2cdev_ioctl,
	.open		= i2cdev_open,
	.release	= i2cdev_release,
};

static struct i2c_driver i2cdev_driver = {
    
    
	.driver = {
    
    
		.name	= "dev_driver",
	},
	.id		= I2C_DRIVERID_I2CDEV,
	.attach_adapter	= i2cdev_attach_adapter,
	.detach_adapter	= i2cdev_detach_adapter,
	.detach_client	= i2cdev_detach_client,
};

#define I2C_MAJOR	89		/* Device major number		*/

static int __init i2c_dev_init(void)
{
    
    
	int res;

	printk(KERN_INFO "i2c /dev entries driver\n");

	res = register_chrdev(I2C_MAJOR, "i2c", &i2cdev_fops);
	if (res)
		goto out;
	// 创建/sys/class/i2c-dev
	i2c_dev_class = class_create(THIS_MODULE, "i2c-dev");
	if (IS_ERR(i2c_dev_class))
		goto out_unreg_chrdev;

	res = i2c_add_driver(&i2cdev_driver);
	if (res)
		goto out_unreg_class;

	return 0;

out_unreg_class:
	class_destroy(i2c_dev_class);
out_unreg_chrdev:
	unregister_chrdev(I2C_MAJOR, "i2c");
out:
	printk(KERN_ERR "%s: Driver Initialisation failed\n", __FILE__);
	return res;
}

2.3.2 Matching of IIC general device driver and IIC adapter

static struct i2c_driver i2cdev_driver = {
    
    
	.driver = {
    
    
		.name	= "dev_driver",
	},
	.id		= I2C_DRIVERID_I2CDEV,
	.attach_adapter	= i2cdev_attach_adapter,
	.detach_adapter	= i2cdev_detach_adapter,
	.detach_client	= i2cdev_detach_client,
};

i2c_dev_init ->
    // 注册IIC设备驱动,匹配每个适配器进而为每个匹配到的适配器在 /sys/class/i2c-dev设备类下创建适配器设备
    i2c_add_driver(&i2cdev_driver) -> 
        i2c_register_driver -> 
        	
    		// 将IIC设备驱动添加到驱动链表中
    		list_add_tail(&driver->list,&drivers);

        	// 从IIC适配器链表中,取出每一个适配器,调用IIC设备驱动提供的attach_adapter接口
            list_for_each_entry(adapter, &adapters, list) {
    
    
    			driver->attach_adapter(adapter); // 即 调用i2cdev_attach_adapter
    		}

    // 调用i2cdev_attach_adapter
    i2cdev_attach_adapter
    	// 在 /sys/class/i2c-dev设备类下创建适配器设备 /sys/class/i2c-dev/i2c-%d
    	// 同时会以I2C_MAJOR为主设备号,次设备号0~255,在/dev/下生成 /dev/i2c-%d设备节点
    	i2c_dev->dev = device_create(i2c_dev_class, &adap->dev,
    				     MKDEV(I2C_MAJOR, adap->nr),
    				     "i2c-%d", adap->nr);
    	res = device_create_file(i2c_dev->dev, &dev_attr_name);

2.3.3 IIC device driver of at24cxx

The IIC device driver built for a specific IIC device usually calls the i2c_probe interface provided by the I2C core in the provided attach_adapter interface, and at the same time provides a function "at24cxx_detect" called after the successful detection of the device to i2c_probe, which will go to i2c_probe Detect whether a device with a device address exists, and call at24cxx_detect if it exists. Generally speaking, building an IIC device driver for a specific IIC device and building an IIC adapter for platform hardware resources is the work required for IIC driver-related development. The parts related to the IIC core and IIC bus driver are generally already in the system. exist.


static struct i2c_driver at24cxx_driver = {
    
    
	.driver = {
    
    
		.name	= "at24cxx",
	},
	.attach_adapter = at24cxx_attach,
	.detach_client  = at24cxx_detach,
};

static int at24cxx_attach(struct i2c_adapter *adapter)
{
    
    
	return i2c_probe(adapter, &addr_data, at24cxx_detect);
}

static int at24cxx_detect(struct i2c_adapter *adapter, int address, int kind)
{
    
    	
	printk("at24cxx_detect\n");

	/* 构构一个i2c_client结构体: 以后收改数据时会用到它 */
	at24cxx_client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL);
	at24cxx_client->addr    = address;
	at24cxx_client->adapter = adapter;
	at24cxx_client->driver  = &at24cxx_driver;
	strcpy(at24cxx_client->name, "at24cxx");
	i2c_attach_client(at24cxx_client);

    // 注册一个IIC设备的驱动,提供file_operations接口
	major = register_chrdev(0, "at24cxx", &at24cxx_fops);

    // 创建一个设备类/sys/class/at24cxx,并在该设备类下创建一个设备/sys/class/at24cxx/at24cxx
	// 以 major为主设备号,次设备号0~255,生成/dev/at24cxx设备节点,供应用程序使用
    cls = class_create(THIS_MODULE, "at24cxx");
	class_device_create(cls, NULL, MKDEV(major, 0), NULL, "at24cxx"); /* /dev/at24cxx */
	
	return 0;
}

static int at24cxx_init(void)
{
    
    
	i2c_add_driver(&at24cxx_driver);
	return 0;
}

static unsigned short ignore[]      = {
    
     I2C_CLIENT_END };
static unsigned short normal_addr[] = {
    
     0x50, I2C_CLIENT_END }; /* 地址值是7位 */                                       
										
static struct i2c_client_address_data addr_data = {
    
    
	.normal_i2c	= normal_addr,  /* 要发出S信号和设备地址并得到ACK信号,才能确定存在这个设备 */
	.probe		= ignore,
	.ignore		= ignore,
	//.forces     = forces, /* 强制认为存在这个设备 */
};

2.3.4 Matching of at24cxx IIC device driver and IIC adapter

at24cxx_init ->
    i2c_add_driver -> 
        i2c_register_driver -> 
            // 将IIC设备驱动添加到驱动链表中
    		list_add_tail(&driver->list,&drivers);

        	// 从IIC适配器链表中,取出每一个适配器,调用IIC设备驱动提供的attach_adapter接口
            list_for_each_entry(adapter, &adapters, list) {
    
    
    			driver->attach_adapter(adapter); // 即 调用at24cxx_attach
    		}

at24cxx_attach -> 
    i2c_probe(adapter, &addr_data, at24cxx_detect) -> 
        // 对每个设备地址执行
        i2c_probe_address(adapter,address_data->probe[i + 1],-1, found_proc) ->
            // 判断之前适配器中是否已经有该I2C设备连接了
    		i2c_check_addr(adapter, addr)
            // 去探测这个设备地址的设备
    		i2c_smbus_xfer(adapter, addr, 0, 0, 0,I2C_SMBUS_QUICK, NULL) ->
                i2c_smbus_xfer_emulated(adapter,addr,flags,read_write,command,size,data) ->
                    i2c_transfer(adapter, msg, num)
                        adap->algo->master_xfer(adap,msgs,num) ->
                            // 以i2c-s3c2410.c的适配器为例
                            s3c24xx_i2c_doxfer(i2c, msgs, num)->
                                // 向某设备地址传输一个msg
                                s3c24xx_i2c_message_start(i2c, msgs);
                            	// 等待设备的ACK回应,在s3c24xx_i2c_master_complete会唤醒该wait,并赋值i2c->msg_idx
                                timeout = wait_event_timeout(i2c->wait, i2c->msg_num == 0, HZ * 5);
								ret = i2c->msg_idx;  // 返回ACK的值
        	// 设备有ACK回应则调用found_proc,即at24cxx_detect函数
			found_proc(adapter, addr, kind);

at24cxx_detect中
	设置了i2c_client结构体,记录了I2C设备的设备地址、匹配的适配器等信息
	注册了一个字符设备,提供了file_operations接口,以供应用程序使用
	创建设备类、设备、生成设备节点,以供应用程序使用

3. The data transmission method between the application program and the IIC device

Applications generally transmit data with i2c devices in two ways

  1. The application transmits data through the device node "/dev/i2c-0" of the general device driver (i2c-dev) and an i2c device.

The application opens the "/dev/i2c-0" node, uses its file_operations interface (open, read, write, ioctl, close) and an IIC device for data transmission.

  1. Build a device driver for an i2c device, create a device node ("/dev/at24cxx") of the i2c device, and then the application performs data transmission through the device node of the i2c device and the i2c device.

The application opens the "/dev/at24cxx" node, uses its file_operations interface (open, read, write, ioctl, close) and an IIC device for data transmission.

Fourth, the process of data transmission with IIC equipment

Perform a data transmission operation, and the IIC device address is 0x50.

4.1 Data transmission through IIC general device driver

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/i2c-dev.h>


/* i2c_test r addr
 * i2c_test w addr val
 */

void print_usage(char *file)
{
    
    
	printf("%s r addr\n", file);
	printf("%s w addr val\n", file);
}

int main(int argc, char **argv)
{
    
    
	int fd;
	unsigned char buf[2];
	
	if ((argc != 3) && (argc != 4))
	{
    
    
		print_usage(argv[0]);
		return -1;
	}

	// 打开通用设备驱动的设备节点
	fd = open("/dev/i2c-0", O_RDWR);
	if (fd < 0)
	{
    
    
		printf("can't open /dev/i2c-0\n");
		return -1;
	}

	ioctl(fd,I2C_SLAVE,0x50); // 设置从设备地址
	ioctl(fd,I2C_TIMEOUT,1);  // 设置超时
	ioctl(fd,I2C_RETRIES,1);  // 设置重试次数

	if (strcmp(argv[1], "r") == 0)
	{
    
    
		buf[0] = strtoul(argv[2], NULL, 0);
		write(fd, buf, 1);  // 先写入要读取的设备内部地址
		read(fd, buf, 1);   // 再读取一个字节
		printf("data: %c, %d, 0x%2x\n", buf[0], buf[0], buf[0]);
	}
	else if (strcmp(argv[1], "w") == 0)
	{
    
    
		buf[0] = strtoul(argv[2], NULL, 0);
		buf[1] = strtoul(argv[3], NULL, 0);
		write(fd, buf, 2);
	}
	else
	{
    
    
		print_usage(argv[0]);
		return -1;
	}
	
	return 0;
}


4.1.1 The function call process of data transmission using IIC general device driver

The IIC general device driver registers a character device, provides the file_operations interface (open\read\write\ioctl, etc.), and creates a device node "/dev/i2c-0".

open("/dev/i2c-0", O_RDWR) 	  // 即 i2cdev_open
    adap = i2c_get_adapter(i2c_dev->adap->nr); // 获取驱动对应的适配器
	client = kzalloc(sizeof(*client), GFP_KERNEL); // 申请并设置struct i2c_client 结构体
	client->driver = &i2cdev_driver;
	/* registered with adapter, passed as client to user */
	client->adapter = adap;
	file->private_data = client;

ioctl(fd,I2C_SLAVE,0x50) ->   // 即 i2cdev_ioctl
    case I2C_SLAVE:
    	client->addr = arg;   // 设置从设备地址

read(fd, buf, 1) -> // 即 i2cdev_read
    i2c_master_recv -> 
        i2c_transfer
        	adap->algo->master_xfer // 以下和上面分析的探测某设备地址的设备一致

write(fd, buf, 1) -> // 即 i2cdev_write
	i2c_master_send -> 
       i2c_transfer
        	adap->algo->master_xfer // 以下和上面分析的探测某设备地址的设备一致 

4.2 Data transmission through a specific IIC device driver

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>


/* i2c_test r addr
 * i2c_test w addr val
 */

void print_usage(char *file)
{
    
    
	printf("%s r addr\n", file);
	printf("%s w addr val\n", file);
}

int main(int argc, char **argv)
{
    
    
	int fd;
	unsigned char buf[2];
	
	if ((argc != 3) && (argc != 4))
	{
    
    
		print_usage(argv[0]);
		return -1;
	}

    // 打开at24cxxIIC设备的设备节点
	fd = open("/dev/at24cxx", O_RDWR);
	if (fd < 0)
	{
    
    
		printf("can't open /dev/at24cxx\n");
		return -1;
	}

	if (strcmp(argv[1], "r") == 0)
	{
    
    
		buf[0] = strtoul(argv[2], NULL, 0);
		read(fd, buf, 1);
		printf("data: %c, %d, 0x%2x\n", buf[0], buf[0], buf[0]);
	}
	else if (strcmp(argv[1], "w") == 0)
	{
    
    
		buf[0] = strtoul(argv[2], NULL, 0);
		buf[1] = strtoul(argv[3], NULL, 0);
		write(fd, buf, 2);
	}
	else
	{
    
    
		print_usage(argv[0]);
		return -1;
	}
	
	return 0;
}


Five, summary

Generally speaking, building an IIC device driver for a specific IIC device and building an IIC adapter for platform hardware resources is the work required for IIC driver-related development. The parts related to the IIC core and IIC bus driver are generally already in the system. exist.
The general work required to build an IIC device driver

  • Set the struct i2c_driver structure and provide attach_adapter and detach_client interfaces.
  • Call the i2c_probe interface in the attach_adapter to detect the device at a certain device address, and provide the function at24cxx_detect called after the device detects the existence, and set the struct i2c_client in this function.
  • It is also necessary to implement the specific driver corresponding to the type of IIC device in the at24cxx_detect function (that is, after the device detection is successful). For example, in this example, it is a character device, so the character device driver is registered and the character device node is created.

The general work required to build an IIC adapter

  • Provide hardware drivers for IIC adapters, detect and initialize IIC adapters (such as applying for IIC I/O addresses and interrupt numbers), drive CPU-controlled IIC controllers to generate various signals from hardware, and handle IIC interrupts.
  • Provide the algorithm of the IIC adapter, that is, provide the master_xfer interface and the functionality interface, and the IIC device driver will call the corresponding adapter's master_xfer interface for data transmission.

Guess you like

Origin blog.csdn.net/qq_40709487/article/details/127418232