【Linux驱动设备开发详解】15.Linux I2C核心、总线和设备驱动

IC总线共有两根线,SCL和SDA,通过这两根信号线就实现了设备间的数据交互

1.Linux I2C体系架构的3个组成部分

1.1 I2C核心

I2C核心提供了I2C总线驱动和设备驱动的注册、注销方法、I2C通信方法(即Algorithm)上层的与具体适配器无关的代码以及探测设备、检测设备地址的上层代码等。

1.2 I2C总线驱动

I2C总线驱动是对I2C硬件体系结构中适配器端的实现,适配器可由CPU控制,甚至可以直接集成在CPU内部
I2C总线驱动主要包含:

  • I2C适配器数据结构i2c_adapter
  • I2C适配器的Algnorithm数据结构i2c_algorithm
  • I2C适配器产生通信信号的函数。

经由I2C总线驱动的代码,我们可以控制I2C适配器以主控方式产生开始位、停止位、读写周期,以及以从设备方式被读写、产生ACK等。
image.png

1.3 I2C设备驱动

I2C设备驱动是对I2C硬件体系结构中设备端的实现,设备一般挂接在受CPU控制I2C适配器上,通过I2C适配器和CPU交换数据。
I2C设备驱动主要包含数据结构i2c_driver和i2c_client,我们需要更具具体设备实现其中的成员函数。
在linux2.6内核中,所有I2C设备都在sysfs文件系统中显示,存在于/sys/bus/i2c目录下,以适配器和芯片地址的形式列出,以Ubuntu为例:
image.png

2.Linux I2C源码框架说明

在linux内核源代码中的drivers目录下有一个i2c目录,而在i2c目录下又包含了如下文件和文件夹。
image.png
1.i2c-core.c : 实现了I2C核心的功能以及/proc/bus/i2c*接口
2.i2c-dev.c : 实现了I2C适配器设备文件的功能,每一个I2C适配器都被分配一个设备。通过适配器访问设备时的主设备号都为89,次设备号为0-255。
3.busses文件夹:这个文件夹包含了一些I2C主机控制器的驱动,如i2c-tegra.c、i2c-omap.c、i2c-versatile.c、
4.alogos文件夹:实现了一些I2C总线适配器的通信方法

内核中的include/linux/i2c.h头文件对i2c_adapter、i2c_algorithm、i2c_driver和i2c_client这4个数据结构进行了定义。

2.1 i2c_adapter

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;

    struct i2c_bus_recovery_info *bus_recovery_info;
};

2.2 i2c_algorithm

struct i2c_algorithm
{
    // i2c传输函数指针
    int (*master_xfer)(struct i2c_adapter *adap,struct i2c_msg *msgs,int num);
    // SMBus传输函数指针
    int (*smbus_xfer)(struct i2c_adapter *adap,struct i2c_msg *msgs,int num);
    // 确定适配器支持的功能
    u32 (*functionality)(struct i2c_adapter *);
};

2.3 i2c_driver

struct i2c_driver
{
    unsigned int class;

    // 通知驱动有总线出现。应避免它将在不久的将来被移除。
    int (*attach_adapter)(struct i2c_adapter*) __deprecated;

    // 标准驱动程序模型接口
    int (*probe)(struct i2c_client *,const struct i2c_device_id *);
    int (*remove)(struct i2c_client *);

    // 与枚举无关的驱动程序模型接口
    void (*shutdown)(struct i2c_client *);
    int (*suspend)(struct i2c_client *,pm_message_t mesg);
    int (*resume)(struct i2c_client *);

    void (*alert)(struct i2c_client *,unsigned int data);

    // 类似于 ioctl 的命令,可用于执行特定功能。设备的特定功能。
    int (*command)(struct i2c_client *client,unsigned int cmd,void *arg);

    struct device_driver driver;
    const struct i2c_device_id *id_table;

    // 设备检测回调,用于自动创建设备
    int (*detect)(struct i2c_client *,struct i2c_board_info);
    const unsigned short *address_list;
    struct list_head clients;

};

2.4 i2c_client

struct i2c_client
{
    unsigned short flags;  
    unsigned short addr;

    char name[I2C_NAME_SIZE];
    struct i2c_adapter *adapter;
    struct device dev;    // 设备结构
    int irq;              // 设备发出的 irq
    struct list_head detected;
};

下面分析i2c_adapter、i2c_algorithm、i2c_driver和i2c_client这4个数据结构的作用及其盘根错节的关系。

2.5 i2c_adapter与i2c_algorithm

i2c_adapter对应于物理上的一个适配器,而i2c_algorithm对应一套通信方法。
一个I2C适配器需要i2c_algorithm提供的通信函数来控制适配器产生特定的访问周期。缺少i2c_algorith的i2c_adapter什么也做不了,因此i2c_algorithm中包含所使用的i2c_algorithm的指针。
i2c_algorithm中的关键函数master_xfer()用于产生I2C访问周期需要的信号,以i2c_msg(即I2C消息)为单位。i2c_msg定义于include/linux/i2c.h中,其中成员表明了I2C的传输地址、方向、缓冲区、缓冲区长度等信息。

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;
    __u8 *buf;
};

2.6 i2c_driver与i2c_client

i2c_driver对应于一套驱动方法,另外,struct i2c_device_id形式的id_table是该驱动所支持的I2C设备的ID表。i2c_client对应于真实的物理设备,每个I2C设备都需要一个i2c_client来描述。i2c_driver与i2c_client的关系是一对多, 一个i2c_driver可以支持多个同类型的i2c_client。
在I2C总线驱动i2c_bus_type和match()函数i2c_device_match()中,会调用i2c_match_id()函数匹配在板文件中定义的ID和i2c_driver所支持的ID表。

1.2.7 i2c_adpater与i2c_client

i2c_adpater和i2c_client的关系与I2C硬件体系中适配器和设备的关系一致,即i2c_client依附于i2c_adpater。由于一个适配器可以连接多个I2C设备,所以一个i2c_adpater也可以被多个i2c_client依附,i2c_adpater中包括依附于它的i2c_client链表
image.png

从上面这幅框图可以看出,I2C体系结构在Linux中的视线相当复杂,所以当我们进行适配器驱动适配时,主要进行以下工作:

1.提供I2C适配器的硬件驱动,探测,初始化I2C适配器(如申请I2C的I/O地址和中断号)、驱动CPU控制的I2C适配器从硬件上产生各种信号以及处理I2C中断等
2.提供I2C适配器的Algorithm,用具体适配器的xxx_xfer()函数填充i2c_algorithm的master_xfer指针,并把i2c_algorithm指针赋值给i2c_adapter的algo指针。
3.实现I2C设备驱动中的i2c_driver接口,用具体设备yyy的yyy_probe()、yyy_remove()、yyy_suspend()、yyy_resume()函数指针和i2c_device_id设备ID表赋值给i2c_driver的probe、remove、syspend、resume和id_table指针。
4.实现I2C设备所对应类型的具体驱动,i2c_driver知识实现设备与总线的挂接,而挂接在总线上的设备则千差万别。例如,如果是字符设备,就实现文件操作接口,即实现具体设备yyy的yyy_read()、yyy_write()和yyy_ioctl()函数等;
上述步骤中,前两个属于I2C总线驱动,后两个属于I2C设备驱动。

3.Linux框架中I2C核心

I2C Core(drivers/i2c/i2c-core.c)中提供了一组不依赖于硬件平台的接口函数,I2C总线驱动和设备驱动之间以I2C核心作为纽带。I2C核心中的主要函数如下:

3.1 I2C Core的主要接口

1.增加/删除i2c_adapter

int i2c_add_adapter(struct i2c_adapter *adap);
void i2c_del_adapter(struct i2c_adapter *adap);

2.增加/删除i2c_driver

int i2c_register_driver(struct module *owner,struct i2c_driver *driver);
void i2c_del_driver(struct i2c_driver *driver);

#define i2c_add_driver(driver)  \
    i2c_register_driver(THIS_MODULE,driver)

3.I2C传输、发送和接收

// 用于进行I2C适配器和I2C设备之间的一组消息交互,i2c_transfer()一次可以传输多个i2c_msg
int i2c_transfer(struct i2c_adapter *adap,struct i2c_msg *msgs,int num);
// 调用i2c_transfer()函数完成一条写消息
int i2c_master_send(struct i2c_client *client,const char *buf,int count);
// 调用i2c_transfer()函数完成一条读消息
int i2c_master_recv(struct i2c_client,char *buf,int count);

3.2 I2C Core的接口实现

1.i2c_master_send()函数

int i2c_master_send(const struct i2c_client *client,const char *buf,int count)
{
    int ret;
    struct i2c_adapter *adap = client->adapter;
    struct i2c_msg msg;

    msg.addr = client->addr;
    msg.flags = client->flags & I2C_M_TEN;
    msg.len = count;
    msg.buf = (char *)buf;

    ret = i2c_transfer(adap,&msg,1);

    return (ret == 1) count : ret;
}

2.i2c_master_recv()

int i2c_master_recv(const struct i2c_client *client,char *buf,int count)
{
    int ret;
    struct i2c_adapter *adap = client->adapter;
    struct i2c_msg msg;

    msg.addr = client->addr;
    msg.flags = client->flags & I2C_M_TEN;
    msg.flags |= I2C_M_RD;     // 读标志
    msg.len = count;
    msg.buf = (char *)buf;

    ret = i2c_transfer(adap,&msg,1);

    return (ret == 1) count : ret;
}

3.i2c_transfer()
i2c_transfer()函数本身不具备驱动适配器物理硬件以完成消息交互的能力,它只是寻找到与i2c_adapter对应的i2c_algorithm,并使用i2c_algorithm的mater_xfer()函数真正驱动硬件流程。

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

    if(adap->algo->master_xfer)
    {
        ...
        ret = adap->algo->master_xfer(adap,msgs,num);
        ...
        return ret;
    }
    else
    {
        dev_dbg(&adap->dev, "I2C level transfers not supported\n");
        return -ENOSYS;
    }
}

4.I2C适配器驱动(adapter)

4.1 I2C适配器驱动的注册与注销

I2C适配器所对应的platform_driver的probe()函数中完成两个工作:

  • 初始化I2C适配器所使用的硬件资源,如申请I/O地址、中断号、时钟等。
  • 通过i2c_add_adapter()添加i2c_adapter的数据结构,此时这个i2c_adapter数据结构的成员已经被xxx适配器的响应函数指针所初始化
  • 释放I2C适配器所使用的硬件资源,如释放I/O地址、中断号、时钟等
  • 通过i2c_del_adapter()删除i2c_adapter的数据结构

I2C适配器驱动的注册和注销模板:

static int xxx_i2c_probe(struct platform_device *pdev)
{
    struct i2c_adapter *adap;

    ...
    // 与具体的CPU和I2C适配器的硬件有关
    xxx_adapter_hw_init();
    adap->dev.parent = &pdev->dev;
    adap->dev.of_node = pdev->dev.of_node;
    rc = i2c_add_adapter(adap);

    ...
}

static int xxx_i2c_remove(struct platform_device *pdev)
{
    ...
    // 与具体的CPU和I2C适配器的硬件有关
    xxx_adapter_hw_free();
    i2c_del_adapter(&dev->adapter);

    return 0;
}

static const struct of_device_id xxx_i2c_of_match[] =
{
    {.compatible = "vendor,xxx-i2c",};
    {},
};
MODULE_DEVICE_TABLE(of,xxx_i2c_of_match);

static struct platform_driver xxx_i2c_driver =
{
    .driver = {
         .name = "xxx-i2c",
         .owner = THIS_MODULE,
         .of_match_table = xxx_i2c_of_match,
    },
    .probe = xxx_i2c_probe();
    .remove = xxx_i2c_remove();
};
module_platform_driver(xxx_i2c_driver);

4.2 I2C总线的通信方法

如果需要为特定的I2C适配器实现通信方法,主要是实现i2c_algorithm的functionality()函数和master_xfer()函数。

  • functionality()函数主要用于返回algorithm所支持的通信协议,如I2C_FUNC_I2C、I2C_FUNC_10BIT_ADDR、I2C_FUNC_SMBUS_READ_BYTE、I2C_FUNC_SMBUS_WRITE_BYTE等
  • master_xfer()函数在I2C适配器上完成传递给它的i2c_msg数组中的每个I2C消息,时序和代码模板如下:

master_xfer()的时序:
image.png
master_xfer的代码模板:

static int i2c_adapter_xxx_xfer(struct i2c_adapter *adap,struct i2c_msg *msgs,int num)
{
    ...
    for(int i = 0;i < num;i++)
    {
        i2c_adapter_xxx_start();         // 1.产生起始位

        if(msgs[i]->flags & I2C_M_RD)    // 2.读消息
        {
            i2c_adapter_xxx_setaddr((msg->addr << 1) | 1);      // 发送从设备读地址
            i2c_adapter_xxx_wait_ack();                         // 获取从设备的ack
            i2c_adapter_xxx_readbytes(msgs[i]->buf,msgs[i]->len);  // 读取len长度的数据到buf中
        }
        else                             // 2.写消息
        {
            i2c_adapter_xxx_setaddr((msg->addr << 1));      // 发送从设备写地址
            i2c_adapter_xxx_wait_ack();                     // 获取从设备的ack
            i2c_adapter_xxx_writebytes(msgs[i]->buf,msgs[i]->len);
        }
    }
    i2c_adapter_xxx_stop();         // 1.产生起始位
}

master_xfer()函数模板需要用适配器的底层硬件操作如下,这一部分需要参考芯片的数据手册来实现:

i2c_adapter_xxx_start();       // 产生起始位
i2c_adapter_xxx_setaddr();     // 设置设备地址
i2c_adapter_xxx_wait_ack();    // 等待从设备ack
i2c_adapter_xxx_readbytes();   // 从 从设备上接收一串数据
i2c_adapter_xxx_writebytes();  // 向 从设备写入一串数据

多数i2c总线驱动会定义一个xxx_i2c结构体,作为i2c_adapter的algo_data(类似"私有数据"),其中包含I2C消息数组指针、数组索引及I2C适配器Algorithm访问控制用的自旋锁、等待队列等。而master_xfer()函数在完成i2c_msg数组中消息的处理时,也经常需要访问xxx_i2c结构体的成员以获取寄存器基地址、锁等信息。一个典型的xxx_i2c结构体的定义如下:

struct xxx_i2c{
    spinlock_t lock;
    wait_queue_head_t wait;
    struct i2c_msg *msg;
    unsigned int msg_num;
    unsigned int msg_idx;
    unsigned int msg_ptr;
    ...
    struct i2c_adapter adap;
};

5.I2C设备驱动(device)

I2C设备驱动(device)要使用i2c_driver和i2c_client数据结构并填充i2c_driver中的成员函数。
I2C_client一般被包含在设备的私有信息结构体yyy_data中,而i2c_driver则适合被定义为全局变量并初始化。
i2c_driver模板如下:

static struct i2c_driver yyy_driver = {
    .driver = {
        .name = "yyy",
    },
    .probe = yyy_probe,
    .remove = yyy_remove,
    .id_table = yyy_id,
};

5.1 Linux I2C设备驱动的模块加载与卸载

I2C设备驱动的模块加载函数通过I2C核心的i2c_add_driver()函数添加i2c_driver的工作。而模块卸载函数需要做相反的工作:通过I2C核心的i2c_del_driver()函数删除i2c_driver。
I2C外设驱动的模块加载与卸载函数模板

static int __init yyy_init(void)
{
    return i2c_add_driver(&yyy_driver);
}
module_initcall(yyy_init);

static void __exit yyy_exit(void)
{
    i2c_del_driver(&yyy_driver);
}
module_exit(yyy_exit);

5.2 Linux I2C设备驱动的数据传输

在I2C设备上读写数据的时序且数据通过i2c_msg数组进行组织,最后通过i2c_transfer()函数完成,读取指定偏移offs的寄存器示例:

struct i2c_msg msg[2];
// 写消息
msg[0].addr = client->addr;
msg[0].flags = 0;
msg[0].len = 1;
msg[0].buf = &offs;
// 读消息
msg[1].addr = client->addr;
msg[1].flags = I2C_M_RD;
msg[1].len = sizeof(buf);
msg[1].buf = &buf[0];

i2c_transfer(client->adapter,msg,2);

5.3 Linux的i2c-dev.c文件分析

i2c-dev.c文件提供的i2cdev_read()、i2cdev_write()函数对应于用户空间要使用的read()和write()文件操作接口,这两个函数分别调用i2c-core的i2c_master_recv()和i2c_master_send()函数来构造一条I2C消息并引发适配器Algorithm通信函数的调用,以完成消息的传输,它们对应的时序如下:
image.png
但是大多数的I2C设备的读写流程并不对应于一条消息,往往需要两条甚至多条消息来进行一次读写周期(即如图所示的重复开始位的RepStart模式),这种情况下仍然调用read()和write()文件API来读写I2C设备,将不能正确地读写。
image.png
因为以上原因,i2cdev_read和i2c_write()函数不具备太强的通用性,没有太大的实用价值,只能适用于非Repstart模式的情况。对于两条以上的消息组成的读写,在用户空间需要组织i2c_msg消息数组并调用I2C__RDWR_IOCTL命令。_
i2cdev_ioctl实现:

static long i2cdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
	struct i2c_client *client = file->private_data;
	unsigned long funcs;

	dev_dbg(&client->adapter->dev, "ioctl, cmd=0x%02x, arg=0x%02lx\n",
		cmd, arg);

	switch (cmd) {
	case I2C_SLAVE:
	case I2C_SLAVE_FORCE:   /*设置从设备地址*/
		if ((arg > 0x3ff) ||
		    (((client->flags & I2C_M_TEN) == 0) && arg > 0x7f))
			return -EINVAL;
		if (cmd == I2C_SLAVE && i2cdev_check_addr(client->adapter, arg))
			return -EBUSY;
		/* REVISIT: address could become busy later */
		client->addr = arg;
		return 0;
	case I2C_TENBIT:
		if (arg)
			client->flags |= I2C_M_TEN;
		else
			client->flags &= ~I2C_M_TEN;
		return 0;
	case I2C_PEC:
		/*
		 * Setting the PEC flag here won't affect kernel drivers,
		 * which will be using the i2c_client node registered with
		 * the driver model core.  Likewise, when that client has
		 * the PEC flag already set, the i2c-dev driver won't see
		 * (or use) this setting.
		 */
		if (arg)
			client->flags |= I2C_CLIENT_PEC;
		else
			client->flags &= ~I2C_CLIENT_PEC;
		return 0;
	case I2C_FUNCS:
		funcs = i2c_get_functionality(client->adapter);
		return put_user(funcs, (unsigned long __user *)arg);

	case I2C_RDWR: {
		struct i2c_rdwr_ioctl_data rdwr_arg;
		struct i2c_msg *rdwr_pa;

		if (copy_from_user(&rdwr_arg,
				   (struct i2c_rdwr_ioctl_data __user *)arg,
				   sizeof(rdwr_arg)))
			return -EFAULT;

		/* Put an arbitrary limit on the number of messages that can
		 * be sent at once */
		if (rdwr_arg.nmsgs > I2C_RDWR_IOCTL_MAX_MSGS)
			return -EINVAL;

		rdwr_pa = memdup_user(rdwr_arg.msgs,
				      rdwr_arg.nmsgs * sizeof(struct i2c_msg));
		if (IS_ERR(rdwr_pa))
			return PTR_ERR(rdwr_pa);

		return i2cdev_ioctl_rdwr(client, rdwr_arg.nmsgs, rdwr_pa);
	}

	case I2C_SMBUS: {
		struct i2c_smbus_ioctl_data data_arg;
		if (copy_from_user(&data_arg,
				   (struct i2c_smbus_ioctl_data __user *) arg,
				   sizeof(struct i2c_smbus_ioctl_data)))
			return -EFAULT;
		return i2cdev_ioctl_smbus(client, data_arg.read_write,
					  data_arg.command,
					  data_arg.size,
					  data_arg.data);
	}
	case I2C_RETRIES:    /*没有收到设备ACK情况下的重试次数,默认为1*/
		if (arg > INT_MAX)
			return -EINVAL;

		client->adapter->retries = arg;
		break;
	case I2C_TIMEOUT:   /*超时*/
		if (arg > INT_MAX)
			return -EINVAL;

		/* For historical reasons, user-space sets the timeout
		 * value in units of 10 ms.
		 */
		client->adapter->timeout = msecs_to_jiffies(arg * 10);
		break;
	default:
		/* NOTE:  returning a fault code here could cause trouble
		 * in buggy userspace code.  Some old kernel bugs returned
		 * zero in this case, and userspace code might accidentally
		 * have depended on that bug.
		 */
		return -ENOTTY;
	}
	return 0;
}

以下为直接用read()、write()接口和O_RDWR_IOCTL读写I2C设备的例子:

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

#define BUFF_SIZE   32

int main(int argc,char **argv)
{
    unsigned int fd;
    unsigned short mem_addr;
    unsigned short size;
    unsigned short idx;
    char buff[BUFF_SIZE];
    char cswap;

    union{
        unsigned short addr;
        char bytes[2];
    }tmp;

    sscanf(argv[2],"%d",&mem_addr);
    sscanf(argv[3],"%d",&size);

    if(size > BUFF_SIZE)
    {
        size = BUFF_SIZE;
    }

    fd = open(argv[1],O_RDWR);
    if(!fd)
    {
        printf("xxxx");
        return 0;
    }

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

    for(idx = 0 ; idx < size; ++idx, ++mem_addr)
    {
        tmp.addr = mem_addr;
        cswap = tmp.bytes[1];
        tmp.bytes[0] = tmp.bytes[1];
        tmp.byte[1] = cswap;
        write(fd,&tmp.addr,2);
        read(fd),&buf[idx],1);
    }
    buf[size] = 0;
    close(fd);
    printf("Read %d char:%s\n",size,buf);
    return 0;
}

通过O_RDWR IOCTL读写I2C设备:

#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <assert.h>
#include <string.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>

int main(int argc, char **argv)
{
	struct i2c_rdwr_ioctl_data work_queue;
	unsigned int idx;
	unsigned int fd;
	unsigned int slave_address, reg_address;
	unsigned char val;
	int i;
	int ret;

	if (argc < 4) {
	 printf("Usage:\n%s /dev/i2c-x start_addr reg_addr\n", argv[0]);
	 return 0;
	}

	fd = open(argv[1], O_RDWR);

	if (!fd) {
	 printf("Error on opening the device file\n");
	 return 0;
	}
	sscanf(argv[2], "%x", &slave_address);
	sscanf(argv[3], "%x", &reg_address);

	work_queue.nmsgs = 2; /* 消息数量 */
	work_queue.msgs = (struct i2c_msg*)malloc(work_queue.nmsgs *sizeof(struct i2c_msg));
	if (!work_queue.msgs) {
	 printf("Memory alloc error\n");
	 close(fd);
	 return 0;
	}

	ioctl(fd, I2C_TIMEOUT, 2); /* 设置超时 */
	ioctl(fd, I2C_RETRIES, 1); /* 设置重试次数 */

	for (i = reg_address; i < reg_address + 16; i++) {
		 val = i;
		(work_queue.msgs[0]).len = 1;
		(work_queue.msgs[0]).addr = slave_address;
		(work_queue.msgs[0]).buf = &val;

		(work_queue.msgs[1]).len = 1;
		(work_queue.msgs[1]).flags = I2C_M_RD;
		(work_queue.msgs[1]).addr = slave_address;
		(work_queue.msgs[1]).buf = &val;

		ret = ioctl(fd, I2C_RDWR, (unsigned long) &work_queue);
		if (ret < 0)
			printf("Error during I2C_RDWR ioctl with error code: %d\n", ret);
		else
			printf("reg:%02x val:%02x\n", i, val);
	}
	close(fd);
	return ;
}

使用此代码可指定某I2C控制器上某I2C从设备的某寄存器,如读I2C控制器0上的地址为0x18的从设备,从寄存器0x20开始读:

# i2c-test /dev/i2c-0 0x18 0x20
reg:20 val:07
reg:21 val:00
reg:22 val:00
reg:23 val:00
reg:24 val:00
reg:25 val:00
reg:26 val:00
reg:27 val:00
reg:28 val:00
reg:29 val:00
reg:2a val:00
reg:2b val:00
reg:2c val:00
reg:2d val:00
reg:2e val:00
reg:2f val:00

6.I2C总线驱动实例

这里以NVIDIA Tegra I2C总线驱动为例(drivers/i2c/busses/i2c-tegra.c)

6.1 I2C总线驱动的加载与卸载

I2C总线驱动是一个单独的驱动,在模块的加载和卸载函数中,只需注册和注销一个platform_driver结构体

static const struct of_device_id tegra_i2c_of_match[] = {
    {.compatible = "nvidia,tegra114-i2c",.data = &tegra114_i2c_hw, },
    {.compatible = "nvidia,tegra30-i2c",.data = &tegra30_i2c_hw, },
    {.compatible = "nvidia,tegra20-i2c",.data = &tegra20_i2c_hw, },
    {.compatible = "nvidia,tegra20-i2c-dvc",.data = &tegra20_i2c_hw, },
    {},
};
MODULE_DEVICE_TABLE(of,tegra_i2c_of_match);

static struct platform_driver tegra_i2c_driver = {
    .probe = tegra_i2c_probe,
    .remove = tegra_i2c_remove,
    .driver = {
          .name = "tegra-i2c",
          .owner = THIS_MODULE,
          .of_match_table = tegra_i2c_of_match,
          .pm = TEGRA_I2C_PM,
    },
};

static int __init tegra_i2c_init_driver(void)
{
    return platform_driver_register(&tegra_i2c_driver);
}

static void __exit tegra_i2c_exit_driver(void)
{
    platform_driver_unregister(&tegra_i2c_driver);
}
subsys_initcall(tegra_i2c_init_driver);
module_exit(tegra_i2c_exit_driver);

当在tegra的设备树中添加了tegra_i2c_of_match匹配表兼容的节点后,上述platform_driver的tegra_i2c_probe函数会执行。

6.2 I2C总线驱动中的probe函数

probe指针指向的tegra_i2c_probe()函数将被调用,以初始化适配器硬件、申请适配器要的内存、时钟、中断等资源,最终注册适配器:

static int tegra_i2c_probe(struct platform_device *pdev)
{
	struct tegra_i2c_dev *i2c_dev;
	struct resource *res;
	struct clk *div_clk;
	struct clk *fast_clk;
	void __iomem *base;
	int irq;
	int ret = 0;
	int clk_multiplier = I2C_CLK_MULTIPLIER_STD_FAST_MODE;

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	base = devm_ioremap_resource(&pdev->dev, res);
	if (IS_ERR(base))
		return PTR_ERR(base);

	res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
	if (!res) {
		dev_err(&pdev->dev, "no irq resource\n");
		return -EINVAL;
	}
	irq = res->start;

	div_clk = devm_clk_get(&pdev->dev, "div-clk");
	if (IS_ERR(div_clk)) {
		dev_err(&pdev->dev, "missing controller clock\n");
		return PTR_ERR(div_clk);
	}

	i2c_dev = devm_kzalloc(&pdev->dev, sizeof(*i2c_dev), GFP_KERNEL);
	if (!i2c_dev)
		return -ENOMEM;

	i2c_dev->base = base;
	i2c_dev->div_clk = div_clk;
	i2c_dev->adapter.algo = &tegra_i2c_algo;
	i2c_dev->adapter.quirks = &tegra_i2c_quirks;
	i2c_dev->irq = irq;
	i2c_dev->cont_id = pdev->id;
	i2c_dev->dev = &pdev->dev;

	i2c_dev->rst = devm_reset_control_get_exclusive(&pdev->dev, "i2c");
	if (IS_ERR(i2c_dev->rst)) {
		dev_err(&pdev->dev, "missing controller reset\n");
		return PTR_ERR(i2c_dev->rst);
	}

	tegra_i2c_parse_dt(i2c_dev);

	i2c_dev->hw = of_device_get_match_data(&pdev->dev);
	i2c_dev->is_dvc = of_device_is_compatible(pdev->dev.of_node,
						  "nvidia,tegra20-i2c-dvc");
	init_completion(&i2c_dev->msg_complete);
	spin_lock_init(&i2c_dev->xfer_lock);

	if (!i2c_dev->hw->has_single_clk_source) {
		fast_clk = devm_clk_get(&pdev->dev, "fast-clk");
		if (IS_ERR(fast_clk)) {
			dev_err(&pdev->dev, "missing fast clock\n");
			return PTR_ERR(fast_clk);
		}
		i2c_dev->fast_clk = fast_clk;
	}

	platform_set_drvdata(pdev, i2c_dev);

	if (!i2c_dev->hw->has_single_clk_source) {
		ret = clk_prepare(i2c_dev->fast_clk);
		if (ret < 0) {
			dev_err(i2c_dev->dev, "Clock prepare failed %d\n", ret);
			return ret;
		}
	}

	i2c_dev->clk_divisor_non_hs_mode =
			i2c_dev->hw->clk_divisor_std_fast_mode;
	if (i2c_dev->hw->clk_divisor_fast_plus_mode &&
		(i2c_dev->bus_clk_rate == 1000000))
		i2c_dev->clk_divisor_non_hs_mode =
			i2c_dev->hw->clk_divisor_fast_plus_mode;

	clk_multiplier *= (i2c_dev->clk_divisor_non_hs_mode + 1);
	ret = clk_set_rate(i2c_dev->div_clk,
			   i2c_dev->bus_clk_rate * clk_multiplier);
	if (ret) {
		dev_err(i2c_dev->dev, "Clock rate change failed %d\n", ret);
		goto unprepare_fast_clk;
	}

	ret = clk_prepare(i2c_dev->div_clk);
	if (ret < 0) {
		dev_err(i2c_dev->dev, "Clock prepare failed %d\n", ret);
		goto unprepare_fast_clk;
	}

	pm_runtime_enable(&pdev->dev);
	if (!pm_runtime_enabled(&pdev->dev)) {
		ret = tegra_i2c_runtime_resume(&pdev->dev);
		if (ret < 0) {
			dev_err(&pdev->dev, "runtime resume failed\n");
			goto unprepare_div_clk;
		}
	}

	if (i2c_dev->is_multimaster_mode) {
		ret = clk_enable(i2c_dev->div_clk);
		if (ret < 0) {
			dev_err(i2c_dev->dev, "div_clk enable failed %d\n",
				ret);
			goto disable_rpm;
		}
	}

	ret = tegra_i2c_init(i2c_dev);
	if (ret) {
		dev_err(&pdev->dev, "Failed to initialize i2c controller\n");
		goto disable_div_clk;
	}

	ret = devm_request_irq(&pdev->dev, i2c_dev->irq,
			tegra_i2c_isr, 0, dev_name(&pdev->dev), i2c_dev);
	if (ret) {
		dev_err(&pdev->dev, "Failed to request irq %i\n", i2c_dev->irq);
		goto disable_div_clk;
	}

	i2c_set_adapdata(&i2c_dev->adapter, i2c_dev);
	i2c_dev->adapter.owner = THIS_MODULE;
	i2c_dev->adapter.class = I2C_CLASS_DEPRECATED;
	strlcpy(i2c_dev->adapter.name, dev_name(&pdev->dev),
		sizeof(i2c_dev->adapter.name));
	i2c_dev->adapter.dev.parent = &pdev->dev;
	i2c_dev->adapter.nr = pdev->id;
	i2c_dev->adapter.dev.of_node = pdev->dev.of_node;

	ret = i2c_add_numbered_adapter(&i2c_dev->adapter);
	if (ret)
		goto disable_div_clk;

	return 0;

disable_div_clk:
	if (i2c_dev->is_multimaster_mode)
		clk_disable(i2c_dev->div_clk);

disable_rpm:
	pm_runtime_disable(&pdev->dev);
	if (!pm_runtime_status_suspended(&pdev->dev))
		tegra_i2c_runtime_suspend(&pdev->dev);

unprepare_div_clk:
	clk_unprepare(i2c_dev->div_clk);

unprepare_fast_clk:
	if (!i2c_dev->hw->has_single_clk_source)
		clk_unprepare(i2c_dev->fast_clk);

	return ret;
}

与probe函数相反功能的是remove函数

6.3 I2C总线驱动中的remove函数

remove函数功能与probe函数相反,它在适配器模块卸载函数调用platform_driver_unregister()时通过platform_driver的remove指针方式被调用:

static int tegra_i2c_remove(struct platform_device *pdev)
{
	struct tegra_i2c_dev *i2c_dev = platform_get_drvdata(pdev);

	i2c_del_adapter(&i2c_dev->adapter);

	if (i2c_dev->is_multimaster_mode)
		clk_disable(i2c_dev->div_clk);

	pm_runtime_disable(&pdev->dev);
	if (!pm_runtime_status_suspended(&pdev->dev))
		tegra_i2c_runtime_suspend(&pdev->dev);

	clk_unprepare(i2c_dev->div_clk);
	if (!i2c_dev->hw->has_single_clk_source)
		clk_unprepare(i2c_dev->fast_clk);

	return 0;
}

tegra_i2c_dev结构体对适配器的所有信息进行了封装

6.4 tegra_i2c_dev结构体

/**
 * struct tegra_i2c_dev	- per device i2c context
 * @dev: device reference for power management
 * @hw: Tegra i2c hw feature.
 * @adapter: core i2c layer adapter information
 * @div_clk: clock reference for div clock of i2c controller.
 * @fast_clk: clock reference for fast clock of i2c controller.
 * @base: ioremapped registers cookie
 * @cont_id: i2c controller id, used for for packet header
 * @irq: irq number of transfer complete interrupt
 * @is_dvc: identifies the DVC i2c controller, has a different register layout
 * @msg_complete: transfer completion notifier
 * @msg_err: error code for completed message
 * @msg_buf: pointer to current message data
 * @msg_buf_remaining: size of unsent data in the message buffer
 * @msg_read: identifies read transfers
 * @bus_clk_rate: current i2c bus clock rate
 * @is_suspended: prevents i2c controller accesses after suspend is called
 */
struct tegra_i2c_dev {
	struct device *dev;
	const struct tegra_i2c_hw_feature *hw;
	struct i2c_adapter adapter;
	struct clk *div_clk;
	struct clk *fast_clk;
	struct reset_control *rst;
	void __iomem *base;
	int cont_id;
	int irq;
	bool irq_disabled;
	int is_dvc;
	struct completion msg_complete;
	int msg_err;
	u8 *msg_buf;
	size_t msg_buf_remaining;
	int msg_read;
	u32 bus_clk_rate;
	u16 clk_divisor_non_hs_mode;
	bool is_suspended;
	bool is_multimaster_mode;
	spinlock_t xfer_lock;
};

tegra_i2c_probe() 函数中的platform_set_drvdata(pdev, i2c_dev) 和i2c_set_adapdata(&i2c_dev->adapter, i2c_dev) 已经把这个结构体的实例依附到了platform_device和i2c_adapter的私有数据上了, 在其他地方只要用相应的方法就可以把这个结构体的实例取出来。

与I2C适配器对应的i2c_algorithm结构体实例为tegra_i2c_algo,定义代码如下:

static const struct i2c_algorithm tegra_i2c_algo = {
	.master_xfer	= tegra_i2c_xfer,     // 总线通信传输函数
	.functionality	= tegra_i2c_func,
};

上述第2行指定了Tegra I2C总线通信传输函数tegra_i2c_xfer(),这个函数实现了对I2C总线上设备的访问,它依赖于tegra_i2c_xfer_msg()函数的源代码。

6.5 tegra_i2c_xfer函数

static int tegra_i2c_xfer_msg(struct tegra_i2c_dev *i2c_dev,
	struct i2c_msg *msg, enum msg_end_type end_state)
{
	u32 packet_header;
	u32 int_mask;
	unsigned long time_left;
	unsigned long flags;

	tegra_i2c_flush_fifos(i2c_dev);

	if (msg->len == 0)
		return -EINVAL;

	i2c_dev->msg_buf = msg->buf;
	i2c_dev->msg_buf_remaining = msg->len;
	i2c_dev->msg_err = I2C_ERR_NONE;
	i2c_dev->msg_read = (msg->flags & I2C_M_RD);
	reinit_completion(&i2c_dev->msg_complete);

	spin_lock_irqsave(&i2c_dev->xfer_lock, flags);

	int_mask = I2C_INT_NO_ACK | I2C_INT_ARBITRATION_LOST;
	tegra_i2c_unmask_irq(i2c_dev, int_mask);

	packet_header = (0 << PACKET_HEADER0_HEADER_SIZE_SHIFT) |
			PACKET_HEADER0_PROTOCOL_I2C |
			(i2c_dev->cont_id << PACKET_HEADER0_CONT_ID_SHIFT) |
			(1 << PACKET_HEADER0_PACKET_ID_SHIFT);
	i2c_writel(i2c_dev, packet_header, I2C_TX_FIFO);

	packet_header = msg->len - 1;
	i2c_writel(i2c_dev, packet_header, I2C_TX_FIFO);

	packet_header = I2C_HEADER_IE_ENABLE;
	if (end_state == MSG_END_CONTINUE)
		packet_header |= I2C_HEADER_CONTINUE_XFER;
	else if (end_state == MSG_END_REPEAT_START)
		packet_header |= I2C_HEADER_REPEAT_START;
	if (msg->flags & I2C_M_TEN) {
		packet_header |= msg->addr;
		packet_header |= I2C_HEADER_10BIT_ADDR;
	} else {
		packet_header |= msg->addr << I2C_HEADER_SLAVE_ADDR_SHIFT;
	}
	if (msg->flags & I2C_M_IGNORE_NAK)
		packet_header |= I2C_HEADER_CONT_ON_NAK;
	if (msg->flags & I2C_M_RD)
		packet_header |= I2C_HEADER_READ;
	i2c_writel(i2c_dev, packet_header, I2C_TX_FIFO);

	if (!(msg->flags & I2C_M_RD))
		tegra_i2c_fill_tx_fifo(i2c_dev);

	if (i2c_dev->hw->has_per_pkt_xfer_complete_irq)
		int_mask |= I2C_INT_PACKET_XFER_COMPLETE;
	if (msg->flags & I2C_M_RD)
		int_mask |= I2C_INT_RX_FIFO_DATA_REQ;
	else if (i2c_dev->msg_buf_remaining)
		int_mask |= I2C_INT_TX_FIFO_DATA_REQ;

	tegra_i2c_unmask_irq(i2c_dev, int_mask);
	spin_unlock_irqrestore(&i2c_dev->xfer_lock, flags);
	dev_dbg(i2c_dev->dev, "unmasked irq: %02x\n",
		i2c_readl(i2c_dev, I2C_INT_MASK));

	time_left = wait_for_completion_timeout(&i2c_dev->msg_complete,
						TEGRA_I2C_TIMEOUT);
	tegra_i2c_mask_irq(i2c_dev, int_mask);

	if (time_left == 0) {
		dev_err(i2c_dev->dev, "i2c transfer timed out\n");

		tegra_i2c_init(i2c_dev);
		return -ETIMEDOUT;
	}

	dev_dbg(i2c_dev->dev, "transfer complete: %lu %d %d\n",
		time_left, completion_done(&i2c_dev->msg_complete),
		i2c_dev->msg_err);

	if (likely(i2c_dev->msg_err == I2C_ERR_NONE))
		return 0;

	tegra_i2c_init(i2c_dev);
	if (i2c_dev->msg_err == I2C_ERR_NO_ACK) {
		if (msg->flags & I2C_M_IGNORE_NAK)
			return 0;
		return -EREMOTEIO;
	}

	return -EIO;
}

static int tegra_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[],
	int num)
{
	struct tegra_i2c_dev *i2c_dev = i2c_get_adapdata(adap);
	int i;
	int ret = 0;

	if (i2c_dev->is_suspended)
		return -EBUSY;

	ret = pm_runtime_get_sync(i2c_dev->dev);
	if (ret < 0) {
		dev_err(i2c_dev->dev, "runtime resume failed %d\n", ret);
		return ret;
	}

	// 遍历所有的i2c_msg
	for (i = 0; i < num; i++) {
		enum msg_end_type end_type = MSG_END_STOP;

		if (i < (num - 1)) {
			if (msgs[i + 1].flags & I2C_M_NOSTART)
				end_type = MSG_END_CONTINUE;
			else
				end_type = MSG_END_REPEAT_START;
		}
		// 每个i2c_msg由tegra_i2c_xfer_msg处理
		ret = tegra_i2c_xfer_msg(i2c_dev, &msgs[i], end_type);
		if (ret)
			break;
	}

	pm_runtime_put(i2c_dev->dev);

	return ret ?: i;
}

从代码层面上看, 第111行的for循环遍历所有的i2c_msg, 而每个i2c_msg则由tegra_i2c_xfer_msg() 函数处理, 它每次发起硬件操作后, 实际上需要通过wait_for_completion_timeout() 等待传输的完成, 因此这里面就会有一个被调度出去的过程。 中断到来且I2C的包传输结束的时候, 就是唤醒这个睡眠进程的时候。Tegra I2C总线驱动的中断服务程序如下:

static irqreturn_t tegra_i2c_isr(int irq, void *dev_id)
{
	u32 status;
	const u32 status_err = I2C_INT_NO_ACK | I2C_INT_ARBITRATION_LOST;
	struct tegra_i2c_dev *i2c_dev = dev_id;
	unsigned long flags;

	status = i2c_readl(i2c_dev, I2C_INT_STATUS);

	spin_lock_irqsave(&i2c_dev->xfer_lock, flags);
	if (status == 0) {
		dev_warn(i2c_dev->dev, "irq status 0 %08x %08x %08x\n",
			 i2c_readl(i2c_dev, I2C_PACKET_TRANSFER_STATUS),
			 i2c_readl(i2c_dev, I2C_STATUS),
			 i2c_readl(i2c_dev, I2C_CNFG));
		i2c_dev->msg_err |= I2C_ERR_UNKNOWN_INTERRUPT;

		if (!i2c_dev->irq_disabled) {
			disable_irq_nosync(i2c_dev->irq);
			i2c_dev->irq_disabled = true;
		}
		goto err;
	}

	if (unlikely(status & status_err)) {
		tegra_i2c_disable_packet_mode(i2c_dev);
		if (status & I2C_INT_NO_ACK)
			i2c_dev->msg_err |= I2C_ERR_NO_ACK;
		if (status & I2C_INT_ARBITRATION_LOST)
			i2c_dev->msg_err |= I2C_ERR_ARBITRATION_LOST;
		goto err;
	}

	if (i2c_dev->msg_read && (status & I2C_INT_RX_FIFO_DATA_REQ)) {
		if (i2c_dev->msg_buf_remaining)
			tegra_i2c_empty_rx_fifo(i2c_dev);
		else
			BUG();
	}

	if (!i2c_dev->msg_read && (status & I2C_INT_TX_FIFO_DATA_REQ)) {
		if (i2c_dev->msg_buf_remaining)
			tegra_i2c_fill_tx_fifo(i2c_dev);
		else
			tegra_i2c_mask_irq(i2c_dev, I2C_INT_TX_FIFO_DATA_REQ);
	}

	i2c_writel(i2c_dev, status, I2C_INT_STATUS);
	if (i2c_dev->is_dvc)
		dvc_writel(i2c_dev, DVC_STATUS_I2C_DONE_INTR, DVC_STATUS);

	if (status & I2C_INT_PACKET_XFER_COMPLETE) {
		BUG_ON(i2c_dev->msg_buf_remaining);
		complete(&i2c_dev->msg_complete);
	}
	goto done;
err:
	/* An error occurred, mask all interrupts */
	tegra_i2c_mask_irq(i2c_dev, I2C_INT_NO_ACK | I2C_INT_ARBITRATION_LOST |
		I2C_INT_PACKET_XFER_COMPLETE | I2C_INT_TX_FIFO_DATA_REQ |
		I2C_INT_RX_FIFO_DATA_REQ);
	i2c_writel(i2c_dev, status, I2C_INT_STATUS);
	if (i2c_dev->is_dvc)
		dvc_writel(i2c_dev, DVC_STATUS_I2C_DONE_INTR, DVC_STATUS);

	complete(&i2c_dev->msg_complete);
done:
	spin_unlock_irqrestore(&i2c_dev->xfer_lock, flags);
	return IRQ_HANDLED;
}
posted @ 2024-06-26 17:29  Emma1111  阅读(261)  评论(0编辑  收藏  举报