【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等。
1.3 I2C设备驱动
I2C设备驱动是对I2C硬件体系结构中设备端的实现,设备一般挂接在受CPU控制I2C适配器上,通过I2C适配器和CPU交换数据。
I2C设备驱动主要包含数据结构i2c_driver和i2c_client,我们需要更具具体设备实现其中的成员函数。
在linux2.6内核中,所有I2C设备都在sysfs文件系统中显示,存在于/sys/bus/i2c目录下,以适配器和芯片地址的形式列出,以Ubuntu为例:
2.Linux I2C源码框架说明
在linux内核源代码中的drivers目录下有一个i2c目录,而在i2c目录下又包含了如下文件和文件夹。
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链表
从上面这幅框图可以看出,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()的时序:
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通信函数的调用,以完成消息的传输,它们对应的时序如下:
但是大多数的I2C设备的读写流程并不对应于一条消息,往往需要两条甚至多条消息来进行一次读写周期(即如图所示的重复开始位的RepStart模式),这种情况下仍然调用read()和write()文件API来读写I2C设备,将不能正确地读写。
因为以上原因,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", ®_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;
}