Linux操作系统的I2C驱动
一、Linux的I2C体系结构
Linux I2C体系结构分为3个组成部分
(1)Linux核心
I2C核心提供了I2C总线驱动和设备驱动的注册、注销方法,I2C通信方法(algorithm)上层的,与具体适配器无关的代码以及探测设备、检测设备地址的上层代码等。
(2)I2C总线驱动
I2C总线驱动是对I2C硬件体系结构中适配器端的实现,适配器可由CPU控制,甚至可以直接集成在CPU内部。I2C总线驱动主要包含了I2C适配器数据结构i2c_adapter、I2C适配器的algorithm数据结构i2c_algorithm和控制I2C适配器产生通信信号的函数。经由I2C总线驱动的代码,我们可以控制I2C适配器以主控方式产生开始位、停止位、读写周期,以及以从设备方式被读写、产生ACK等。
(3)I2C设备驱动
I2C设备驱动是对I2C硬件体系结构中设备端的实现,设备一般挂接在首CPU控制的I2C适配器上,通过I2C适配器与CPU交换数据。I2C设备驱动主要包含了数据结构i2c_driver和i2c_client,我们需要根据具体设备实现其中的成员函数。
在Linux内核源代码中的drivers目录下包含一个i2c目录,而在i2c目录下又包含如下文件和文件夹:
- i2c_core.c
这个文件实现了I2C核心的功能以及/proc/bus/i2c*接口
- i2c_dev.c
实现了I2C适配器设备文件的功能,每一个I2C适配器都被分配为一个设备。通过适配器访问设备是的主设备号都为89,次设备号为0~255.应用程序通过“i2c-%d”(i2c-0,i2c-1,i2c-2,...i2c-10,...)文件名并使用文件操作结构open()/write()/read()/ioctl()和close()等来访问这个设备。
i2c-dev.c并没有针对特定的设备而设计,只是提供了通用read()等接口,应用程序可以借用这些接口访问适配器上的I2C设别的存储空间或寄存器,并控制I2C设别的工作方式。
- chips文件夹
这个目录中包含了一些特定的I2C设备驱动。在具体的I2C设别驱动中,调用的都是I2C核心提供的API,因此,这使得具体的I2C设备驱动不依赖于CPU类型和I2C适配器的硬件特性。
- buses文件夹
这个文件中包含了一些I2C总线的驱动。如针对S3C2410/S3C2440等处理器的I2C控制器驱动。
- algos文件夹
实现了一些I2C总线适配器的algorithm。
此外,内核中的i2c.h这个头文件对i2c_driver/i2c_client/i2c_adapter和i2c_algotithm这4个数据结构进行了定义。理解这4个数据结构作用有利于后续的理解。
i2c_adapter结构体
struct i2c_adapter {
struct module *owner; /* 所属模块 */
unsigned int id; /* algorithm的类型,定义于i2c-id.h,以I2C_ALGO_开始 */
unsinged int class;
struct i2c_algorithm *algo; /* 总线通信方法结构体指针 */
void *algo_data; /* algorithm 数据 */
int (*client_register) (struct i2c_client *); /* client注册时调用 */
int (*client_unregister) (struct i2c_client *); /* client注销时调用 */
u8 level;
struct semphore bus_lock;
struct semphore clist_lock;
int timeout;
int retries; /* 重试次数 */
struct device dev; /* 适配器设备 */
struct class_device class_dev; /* 类设备 */
int nr;
struct list_head client;
struct list_head list;
char name[48]; /* 适配器名称 */
struct completion dev_released; /* 用于同步 */
};
i2c_algoritm结构体
struct i2c_algorithm {
int (*master_xfer) (struct i2c_adapter, struct i2c_msg *msgs, int num); /* i2c 传输函数指针 */
int (*subus_xfer) (struct i2c_adapter *adap, u16 addr, unsigned short flags, char read_write,
u8 command, int size, union i2c_smbus_data *data);
u32 (*functionality) (struct i2c_adapter *adap);
};
i2c_driver结构体
struct i2c_driver {
int id;
unsigned int class;
int (*attach_adapter) (struct i2c_adapter *); /* 依附i2c_adapter 函数指针 */
int (*detach_adapter) (struct i2c_adapter *); /* 脱离i2c_adapter 函数指针 */
int (*detach_client) (struct i2c_client *); /* 脱离i2c_client 函数指针 */
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 *);
int (*command) (struct i2c_client *,unsigned int cmd, void *arg);
struct device_driver driver;
const struct i2c_device_id *id_table;
int (*detect) (struct i2c_client *,int kind, struct i2c_board_info *);
const struct i2c_client_address_data *address_data;
struct list_head clients;
};
i2c_client结构体
struct i2c_client {
unsigned int flags;
unsigned short addr; /* 低7位芯片地址 */
char name[I2C_NAMD_SIZE]; /* 设备名称 */
struct i2c_adapter *adapter;
struct i2c_driver *driver;
struct device dev;
int irq;
struct list_head list;
struct completion released;
};
(1)i2c_adapter和i2c_algorithm
i2c_adapter对应于物理上的一个适配器,而i2c_algorithm对应一套通信方法。一个I2C适配器需要i2c_algorithm中提供的通信函数来控制适配器上产生特定的访问周期。缺少i2c_algorithm的i2c_adapter什么也做不了,因此i2c_adapter中包含其使用的i2c_algorithm的指针。
i2c_algorithm中的关键函数master_xfer()用于产生I2C访问周期需要的信号,以i2c_msg为单位,i2c_msg结构体也非常关键。
i2c_msg结构体
struct i2c_msg {
__u16 addr; /* 设备地址 */
__u16 flags; /* 标志 */
__u16 len; /* 消息长度 */
__u8 *buf; /* 消息数据 */
};
(2)i2c_driver和i2c_client
i2c_driver对应一套驱动方法,其主要成员函数probe()、remove()等,另外id_table是该驱动所支持的I2C设备的ID表。i2c_client对应于真实的物理设备,每个I2C设备都需要一个i2c_client来描述。i2c_driver与i2c_client的关系是一对多,一个i2c_dirver上可以支持多个同等类型的client。
i2c_client信息通常在BSP的板文件中通过i2c_board_info填充,如下面代码就定义了一个i2c设备ID为“ad7142_joystick”、地址为0x2C、中断号为IRQ_PF5的i2c_client:
在I2C总线驱动i2c_bus_type的match()函数i2c_device_match()中,会调用i2c_match_id()函数匹配板文件的ID和i2c_driver所支持的ID表。
(3)i2c_adapter与i2c_client
虽然I2C硬件体系结构比较简单,但是I2C体系结构在Linux中的实现却相当复杂。当工程师拿到实际的电路板,面对复杂的Linux I2C子系统,应该如何下手写驱动呢?究竟有哪些是需要亲自做的,哪些是内核已经提供的呢?理解这个问题非常有意义,可以使我们面对具体问题时迅速地抓住重点。
一方面,适配器驱动可能是Linux内核本身还不包含的;另一方面,挂接在适配器上的具体设备驱动可能也是Linux内核还不包含的,因此,功能是要实现的主要工作如下:
- 提供I2C适配器的硬件驱动,探测、初始化I2C适配器(如申请I2C的I/O地址和中断号)、驱动CPU控制I2C适配器从硬件上产生各种信号以及处理I2C中断等。
- 驱动I2C适配器的algorithm,用具体适配器的xxx)xfer()函数填充i2c_algorithm的master_xfer指针,并把i2c_algorithm指针复制给i2c_adapter的algo指针。
- 实现I2C设备驱动中的i2c_driver接口,用具体设备的yyy_probe()、yyy_remove()、yyy_suspend()、yyy_resume()函数指针和i2c_device_id设备的ID表复制给i2c_driver的probe/remove/suspend/resume.
- 实现I2C设备所对应类型的具体驱动,i2c_driver只是实现设备与总线的连接,而挂机啊在总线上的设备则是千差万别的,例如,如果是字符设备,就实现文件操作接口,即实现具体设备yyy的yyy_read()、yyy_write和yyy_ioctl函数等;如果是声卡,就实现ALSA驱动。
上述工作中前两个属于I2C总线驱动,后两个属于I2C设备驱动,做完这些工作,系统会增加两个内核模块。
二、Linux I2C核心
I2C核心(drivers/i2c/i2c-core.c)提供了一组不依赖于平台的接口函数。这个文件一般不需要被工程师修改,但是理解其中的主要函数非常关键,因为I2C总线驱动和设备驱动之间依赖于I2C核心作为组带,I2C和心中的主要函数如下:
(1)增加/删除i2c_adapter:
int i2c_add_adapter(struct i2c_adapter *adapter);
int i2c_del_adapter(struct i2c_adapter *adapter);
(2)增加/删除i2c_driver
int i2c_register_driver(struct module *owner,struct i2c_driver *driver);
int i2c_del_driver(struct i2c_driver *driver);
inline int i2c_add_driver(struct i2c_driver *driver);
(3)i2c_client依附/脱离
int i2c_attach_client(struct i2c_client *client);
int i2c_detach_client(struct i2c_client *client);
当一个具体的client被侦测到并被关联的时候,设备和sysfs文件将被注册。相反地,在client被取消关联的时候,sysfs文件盒设备也被注销。
(4)I2C传输、发送和接收
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *mags, int num);
int i2c_master_send(struct i2c_client *client, const char *buf, int count);
int i2c_master_recv(struct i2c_client *client, char *buf, int count);
i2c_transfer()函数用于进行I2C适配器和I2C设备之间的一组消息交互,i2c_master_send()函数和i2c_master_recv()函数内部会调用i2c_transfer函数分别完成一条写消息和一条读消息。
代码如下:
int i2c_master_send(struct i2c_client *client, const char *buf, int count)
{
int ret;
struct i2c_adapter *adap = client->adapter;
}
i2c_transfer函数本身不具备驱动适配器物理硬件完成消息交互的能力,他只是寻找到i2c_adapter对应的i2c_algorithm,并使用i2c_algorithm的master_xfer()函数真正驱动硬件流程。
三、Linux I2C总线驱动
1 I2C适配器驱动加载和卸载
I2C总线驱动模块的加载函数要完成两个工作:
- 初始化I2C适配器所有的硬件资源,如申请I/O地址、中断号。
- 通过i2c_add_adapter()添加i2c_adapter的数据结构,当然这个数据结构的成员已经被xxx适配器的响应函数指针所初始化。
I2C总线驱动模块的卸载函数要完成的工作与加载函数相反。
- 释放I2C适配器所使用的硬件资源,如释放I/O地址,中断号等。
- 通过i2c_del_adapter删除i2c_adapter的数据结构。
I2C总线驱动的模块加载和卸载函数:
static int __init i2c_adapter_xxx_init(void) {
xxx_adapter_hw_init();
i2c_add_adapter(&xxx_adapter);
}
static int __exit i2c_adapter_xxx_exit(void)
{
xxx_adapter_hw_freee();
i2c_del_adapter(&xxx_adapter);
}
上述代码中xxx_adapter_hw_init和xxx_adapter_hw_free函数的实现都与具体的CPU和I2C适配器硬件相关。
2 I2C总线通信方法
我们需要为特定的I2C适配器实现其通信方法,主要实现i2c_algorithm的master_xfer()函数和functionality()函数。functionality()函数非常简单,用于返回algorithm所支持的通信协议,如I2C_FUNC_I2C、I2C_FUNC_10BIT_ADDR、I2C_FUNC_SMBUS_BYTE、I2C_FUNC_SMBUS_WRITE_BYTE等。master_xfer()函数在I2C适配器上完成传递给他的i2c_msg数组中的每个I2C消息,代码清单如下:
static
上述代码实际上给出了一个master_xfer()函数处理I2C消息数组的流程,对于数组中的每个消息,判断消息类型,若为都消息,则赋从设备地址为(msg->addr<<1)|1,否则为msg->addr<<1,对每个消息产生一个开始位,紧接着传送从设备地址,然后开始数据的发送或接收,对最后的消息还需产生一个停止位。如图所示
master_xfer函数模板中的i2c_adapter_xxx_start()、i2c_adapter_xxx_setaddr()、i2c_adapter_xxx_wait_ack()、i2c_adapter_xxx_readbytes()、i2c_adapter_xxx_writebytes()和i2c_adapter_xxx_stop()函数用于完成适配器的底层硬件操作,与I2C适配器和CPU具体硬件直接相关,需要由驱动工程师根据芯片的数据手册来实现。
四、Linux I2C设备驱动
I2C设备驱动要使用i2c_driver和i2c_client数据结构并填充i2c_driver中的成员函数,i2c_client一般被包含在设备的私有信息结构体yyy_data中,而i2c_driver则适合被定义为全局变量并初始化,代码如下:
static struct i2c_driver yyy_driver = {
.driver = {
.name = "yyy",
},
.probe = yyy_probe,
.remove = yyy_remove,
.id_table = yyy_id,
};
1 Linux I2C设备驱动的模块加载与卸载
I2C设备驱动的模块加载函数通用方法是在I2C设备驱动的模块加载函数进行,通过I2C核心的i2c_add_driver()函数添加i2c_driver的工作,而在模块卸载函数中需要做相反的工作;通过I2C狠心的i2c_del_driver()函数删除i2c_drivdr,I2C设备驱动的加载工作与写在功函数模板如下:
static int __init yyy_init(void)
{
return i2c_add_adapter(&yyy_driver);
}
static void __exit yyy_exit(void)
{
return i2c_del_adapter(&yyy_driver);
}
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);
3 Linux的i2c_dev.c文件分析
i2c-dev.c文件完全可以被看作一个I2C设备驱动。不过,它实现的一个i2c_client是虚拟的、临时的。锁着设备文件的打开而产生,并随设备文件的关闭而撤销,并没有被添加到i2c_adapter的client链表中。i2c-dev.c针对每个I2C适配器生成一个主设备号为89的设备文件,实现了i2c_driver的成员函数以及文件操作接口,所以i2c_dev.c的主题是“i2c_driver成员函数 + 字符设备驱动”。
i2c-dev.c中提供i2ccdev_read()/i2cdev_write()函数来对应用户空间要使用的read()/write()文件操作接口,这两个函数分别调用i2c_master_recv()和i2c_master_send()函数来构造一条I2C消息并印发适配器algorithm通信函数的调用。但是,很遗憾,大多数稍微复杂一点I2C设备的读写流程并不对应于一条消息,往往需要两条甚至更多的消息来进行一次读写周期,这种情况下,在应用层仍然调用read()、write()文件的API来读写I2C设备,将不能正确地读写。许多工程师碰到类似的问题,往往经过相当长时间的调试都没法解决I2C设备的读写,连错误的原因也无法找到,显然是对i2cdev_read()和i2cdec_write()函数的作用有所误解。
鉴于上述原因,i2c-dev.c中i2cdev_read()和i2cdev_write()函数不具备太强的通用性,没有踢啊大的使用价值。只能适用于非RepStart模式的情况。对于两条以上的消息组成的读写,在用户空间需要重新组织i2c-msg消息并调用I2C_RDWR_IOCTL命令。
五、S3C2410 I2C总线驱动实例
1 S3C2410 I2C控制器硬件描述
S3C2410处理器内部集成了一个I2C控制器,通常4个寄存器就可方便的对其进行控制,这4个寄存器如下:
- IICCON:I2C控制寄存器
- IICSTAT:I2C状态寄存器
- IICDS:I2C收发数据移位寄存器
- IICADD:I2C地址寄存器
S3C2410处理器内部集成的I2C控制器可支持主、从两种模式,我们主要使用其主模式。通过对IICCON、IICDS和IICADD寄存器的操作,可在I2C总线上产生开始位、停止位、数据和地址位,而传输的状态则通过IICSTAT寄存器获取。
2 S3C2410 I2C总线驱动总体分析
S3C2410的I2C总线驱动drivers/i2c/busses/i2c-s3c2410.c支持S3C24xx、S3C64xx、S5PC1xx和S5P6xx处理器。它主要完成的工作有:
- 设计对应于i2c_adapter_xxx_init()模板的S3C2410的模块加载函数和对应于i2c_adapter_xxx_exit()函数模板的模块卸载函数。
设计对应于i2c_adapter_xxx_xfer()模板的S3C2410适配器的通信方法函数。
针对S3C24xx、S3C64xx、S5PC1xx和S5P64xx处理器、functionality()函数s3c24xx_i2c_func()只需简单地返回I2C_FUNC_I2C|I2C_FUNC_SMBUS_EMUL|I2C_FUNC_PROTOCOL_MANGLING表明其支持的功能。
3 S3C2410 I2C适配器驱动的模块加载和卸载
I2C适配器驱动被作为一个单独的模块加载进内核,在模块的加载和卸载函数中,只需注册和注销一个platform_driver结构体,如下所示:
static int __init i2c_adap_s3c_init(void)
{
int ret;
ret = platform_driver_register(&s3c24xx_i2c_driver);
return ret;
}
static void __exit i2c_adap_s3c_exit(void)
{
platform_driver_unregister(&s3c24xx_i2c_driver);
}
platform_driver结构体包含了具体适配器的probe()函数、romove()函数、resume()函数指针等信息,他需要被定义和赋值。代码如下:
static struct platform_driver s3c24xx_i2c_driver = {
.probe = s3c24xx_i2c_probe,
.remove = s3c24xx_i2c_remove,
.resume = s3c24xx_i2c_resume,
.driver = {
.name = THIS_MODULE,
.name = "s3c24xx-i2c",
},
};
当通过Linux内核源码/drivers/base/platform.c文件中定义platform_driver_unregister()函数注册platform_driver结构体时,其中probe指针指向的s3c24xx_i2c_probe()函数将被调用,已初始化适配器硬件。代码如下
static int s3c24xx_i2c_probe(struct platform_device *pde)
{
struct s3c24xx_i2c *i2c;struct s3c2410_platform_i2c *npd;
struct resource *resource;
int retval;
npd = pdev->dev.platform_data;
if(!npd)
{
dev_err(&pdev->dev,"no platform data\n");
return -EINVAL;
}
i2c = kmalloc(sizeof(struct s3c24xx_i2c),GFP_KERNEL);
memset(i2c,0,sizeof(struct s3c24xx_i2c));
if(!i2c)
{
dev_err(&pdev->dev,"no memory for state\n");
return -ENOMEM;
}
strlcpy(i2c->adap.name,"s3c24xx-i2c",sizeof(i2c->adap.name));
i2c->adap.owner = THIS_MODULE;
i2c->adap.algo = &s3c24xx_i2c_algorithm;
i2c->adap.retries = 2;
i2c->adap.class = I2C_CLASS_HWMON|I2C_CLASS_SPD;
i2c->tx_setup = 50;
spin_lock_init(&i2c->lock);
init_waitqueue_head(&i2c->wait);
i2c->dev = &pdev->dev;
i2c->clk = clk_get(&pdev->dev,"i2c");
if (IS_ERR(i2c->clk))
{
dev_err(&pdev->dev, "cannot get clock\n");
ret = -ENOENT;
goto err_noclk;
}
dev_dbg(&pdev->dev, "clock source %p\n", i2c->clk);
clk_enable(i2c->clk);
resource = platform_get_resource(pdev,IORESOURCE_MEM,0);
i2c->ioarea = request_mem_region(resource->start,resource_size(resource),pdev->name);
i2c->regs = ioremap(res->start,resource_size(resource));
i2c->adap.algo_data = i2c;
i2c->adap.dev.parent = &pdev->dev;
ret = s3c24xx_i2c_init(i2c);
i2c->irq = platform_get_irq(pdev,0);
ret = request_irq(i2c->irq,s3c24xx_i2c_irq,IRQF_DISABLED,dev_name(&pdev->dev),i2c);
ret = s3c24xx_i2c_register_cpufreq(i2c);
i2c->adap.nr = pdata->bus_num;
ret = i2c_add_numbered_adapter(&i2c->adap);
platform_set_drvdata(pdev,i2c);
}
上述代码中的主体工作使能硬件并申请I2C适配器使用的I/O地址、中断号等,在这些工作度完成无误后,通过I2C核心提供的i2c_add_adapter()函数添加这个适配器。当处理器包含多个I2C控制器时,我们通过板文件定义的platform数据中的bus_num进行区分。
与s3c24xx_i2c_probe()函数完成相反功能的函数是s3c24xx_i2c_remove()函数,他在适配器模块卸载函数调用platform_driver_unregister()函数是通过platform_driver的remove指针方式被调用。xxx_i2c_remove()的设计模块如下:
static int s3c24xx_i2c_remove(struct platform_device *pdev)
{
struct s3c24xx_i2c *i2c = platform_get_drvdata(pdev);
s3c24xx_i2c_derefister_cpufreq(i2c);
i2c_del_adapter(&i2c_adap);
free_irq(i2c->irq,i2c);
clk_disable(i2c->clk);
clk_put(i2c->clk);
iounmap(i2c->regs);
release_resource(i2c->ioarea);
kfree(i2c->ioarea);
kfree(i2c);
return 0;
}
代码清单的s3c24xx_i2c结构体进行适配器所有信息的封装。其结构体定义如下:
struct s3c24xx_i2c { spinlock_t lock; wait_queue_head_t wait; unsigned int suspended:1; struct i2c_msg *msg; unsigned int msg_num; unsigned int msg_idx; unsigned int msg_ptr; unsigned int tx_setup; unsigned int irq; enum s3c24xx_i2c_state state; unsigned long clkrate; void __iomem *regs; struct clk *clk; struct device *dev; struct resource *ioarea; struct i2c_adapter adap; #ifdef CONFIG_CPU_FREQ struct notifier_block freq_transition; #endif };
4 S3C2410 I2C总线通信方法
由代码清单可以看出,I2C配置适配器对应的i2c_algorithm结构体实例为s3c24xx_i2c_algorithm。
static struct i2c_algorithm s3c24xx_i2c_algorithm = {
.master_xfer = s3c24xx_i2c_xfer,
.functionality = s3c24xx_i2c_func,
};
上述代码第一行指定了S3C2410 I2C总线通信传输函数s3c24xx_i2c_xfer(),这个函数非常关键,所有的I2C总线上对设备的访问最周应该由他来完成,代码清单所示这个重要函数以及其依赖的s3c24xx_i2c_doxfer()函数和s3c24xx_i2c_message_start()函数的源代码。
/* s3c24xx_i2c_xfer * * first port of call from the i2c bus code when an message needs * transferring across the i2c bus. */ 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; struct s3c2410_platform_i2c *pdata = i2c->dev->platform_data; if (pdata->cfg_gpio) pdata->cfg_gpio(to_platform_device(i2c->dev)); 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; }
/* s3c24xx_i2c_doxfer * * this starts an i2c transfer */ static int s3c24xx_i2c_doxfer(struct s3c24xx_i2c *i2c,struct i2c_msg *msgs, int num) { unsigned long timeout; int ret; if (i2c->suspended) return -EIO; ret = s3c24xx_i2c_set_master(i2c); if (ret != 0)
{ dev_err(i2c->dev, "cannot get bus (error %d)\n", ret); ret = -EAGAIN; goto out; } spin_lock_irq(&i2c->lock); i2c->msg = msgs; i2c->msg_num = num; i2c->msg_ptr = 0; i2c->msg_idx = 0; i2c->state = STATE_START; s3c24xx_i2c_enable_irq(i2c); s3c24xx_i2c_message_start(i2c, msgs); spin_unlock_irq(&i2c->lock); timeout = wait_event_timeout(i2c->wait, i2c->msg_num == 0, HZ * 5); ret = i2c->msg_idx; /* having these next two as dev_err() makes life very * noisy when doing an i2cdetect */ if (timeout == 0) dev_dbg(i2c->dev, "timeout\n"); else if (ret != num) dev_dbg(i2c->dev, "incomplete xfer (%d)\n", ret); /* ensure the stop has been through the bus */ msleep(1); out: return ret; }
/* s3c24xx_i2c_message_start * * put the start of a message onto the bus */ static void s3c24xx_i2c_message_start(struct s3c24xx_i2c *i2c, struct i2c_msg *msg) { unsigned int addr = (msg->addr & 0x7f) << 1; unsigned long stat; unsigned long iiccon; stat = 0; stat |= S3C2410_IICSTAT_TXRXEN; if (msg->flags & I2C_M_RD)
{ stat |= S3C2410_IICSTAT_MASTER_RX; addr |= 1; }
else
{ stat |= S3C2410_IICSTAT_MASTER_TX;
} if (msg->flags & I2C_M_REV_DIR_ADDR)
{ addr ^= 1;
} /* todo - check for wether ack wanted or not */ s3c24xx_i2c_enable_ack(i2c); iiccon = readl(i2c->regs + S3C2410_IICCON); writel(stat, i2c->regs + S3C2410_IICSTAT); dev_dbg(i2c->dev, "START: %08lx to IICSTAT, %02x to DS\n", stat, addr); writeb(addr, i2c->regs + S3C2410_IICDS); /* delay here to ensure the data byte has gotten onto the bus * before the transaction is started */ ndelay(i2c->tx_setup); dev_dbg(i2c->dev, "iiccon, %08lx\n", iiccon); writel(iiccon, i2c->regs + S3C2410_IICCON); stat |= S3C2410_IICSTAT_START; writel(stat, i2c->regs + S3C2410_IICSTAT); }
s3c24xx_i2c_xfer()函数调用s3c24xx_i2c_doxfer函数传输I2C消息。s3c24xx_i2c_doxfer()首先将s3c2410的I2C适配器设置为I2C主设备,气候初始化s3c24xx_i2c结构体,使能I2C中断,并调用s3c24xx_i2c_message_start()函数启动I2C消息的传输。s3c24xx_i2c_message_start()函数写s3c2410适配器对应的控制寄存器,向I2C从设备传递开始位和从设备地址。
上述代码只是启动了I2C消息数组的传输周期,并没有完整实现图给出的algorithm master_xfer流程。这个流程的完整实现需要借助I2C适配器上的中断来步步推进。下述代码所示为S3C2410 I2C适配器中断处理函数以及其依赖的i2c_s3c_irq_nextbyte()函数的源代码。
/* s3c24xx_i2c_irq * * top level IRQ servicing routine */ static irqreturn_t s3c24xx_i2c_irq(int irqno, void *dev_id) { struct s3c24xx_i2c *i2c = dev_id; unsigned long status; unsigned long tmp; status = readl(i2c->regs + S3C2410_IICSTAT); if (status & S3C2410_IICSTAT_ARBITR)
{ /* deal with arbitration loss */ dev_err(i2c->dev, "deal with arbitration loss\n"); } if (i2c->state == STATE_IDLE)
{ dev_dbg(i2c->dev, "IRQ: error i2c->state == IDLE\n"); tmp = readl(i2c->regs + S3C2410_IICCON); tmp &= ~S3C2410_IICCON_IRQPEND; writel(tmp, i2c->regs + S3C2410_IICCON); goto out; } /* pretty much this leaves us with the fact that we've * transmitted or received whatever byte we last sent */ i2s_s3c_irq_nextbyte(i2c, status); out: return IRQ_HANDLED; }
/* i2s_s3c_irq_nextbyte * * process an interrupt and work out what to do */ static int i2s_s3c_irq_nextbyte(struct s3c24xx_i2c *i2c, unsigned long iicstat) { unsigned long tmp; unsigned char byte; int ret = 0; switch (i2c->state) { case STATE_IDLE: dev_err(i2c->dev, "%s: called in STATE_IDLE\n", __func__); goto out; break; case STATE_STOP: dev_err(i2c->dev, "%s: called in STATE_STOP\n", __func__); s3c24xx_i2c_disable_irq(i2c); goto out_ack; case STATE_START: /* last thing we did was send a start condition on the * bus, or started a new i2c message */ if (iicstat & S3C2410_IICSTAT_LASTBIT &&!(i2c->msg->flags & I2C_M_IGNORE_NAK))
{ /* ack was not received... */ dev_dbg(i2c->dev, "ack was not received\n"); s3c24xx_i2c_stop(i2c, -ENXIO); goto out_ack; } if (i2c->msg->flags & I2C_M_RD) i2c->state = STATE_READ; else i2c->state = STATE_WRITE; /* terminate the transfer if there is nothing to do * as this is used by the i2c probe to find devices. */ if (is_lastmsg(i2c) && i2c->msg->len == 0)
{ s3c24xx_i2c_stop(i2c, 0); goto out_ack; } if (i2c->state == STATE_READ) goto prepare_read; /* fall through to the write state, as we will need to * send a byte as well */ case STATE_WRITE: /* we are writing data to the device... check for the * end of the message, and if so, work out what to do */ if (!(i2c->msg->flags & I2C_M_IGNORE_NAK))
{ if (iicstat & S3C2410_IICSTAT_LASTBIT)
{ dev_dbg(i2c->dev, "WRITE: No Ack\n"); s3c24xx_i2c_stop(i2c, -ECONNREFUSED); goto out_ack; } }
retry_write: if (!is_msgend(i2c))
{ byte = i2c->msg->buf[i2c->msg_ptr++]; writeb(byte, i2c->regs + S3C2410_IICDS); /* delay after writing the byte to allow the * data setup time on the bus, as writing the * data to the register causes the first bit * to appear on SDA, and SCL will change as * soon as the interrupt is acknowledged */ ndelay(i2c->tx_setup); }
else if (!is_lastmsg(i2c))
{ /* we need to go to the next i2c message */ dev_dbg(i2c->dev, "WRITE: Next Message\n"); i2c->msg_ptr = 0; i2c->msg_idx++; i2c->msg++; /* check to see if we need to do another message */ if (i2c->msg->flags & I2C_M_NOSTART)
{ if (i2c->msg->flags & I2C_M_RD)
{ /* cannot do this, the controller * forces us to send a new START * when we change direction */ s3c24xx_i2c_stop(i2c, -EINVAL); } goto retry_write; }
else
{ /* send the new start */ s3c24xx_i2c_message_start(i2c, i2c->msg); i2c->state = STATE_START; } }
else
{ /* send stop */ s3c24xx_i2c_stop(i2c, 0); } break; case STATE_READ: /* we have a byte of data in the data register, do * something with it, and then work out wether we are * going to do any more read/write */ byte = readb(i2c->regs + S3C2410_IICDS); i2c->msg->buf[i2c->msg_ptr++] = byte; prepare_read: if (is_msglast(i2c))
{ /* last byte of buffer */ if (is_lastmsg(i2c)) s3c24xx_i2c_disable_ack(i2c); }
else if (is_msgend(i2c))
{ /* ok, we've read the entire buffer, see if there * is anything else we need to do */ if (is_lastmsg(i2c))
{ /* last message, send stop and complete */ dev_dbg(i2c->dev, "READ: Send Stop\n"); s3c24xx_i2c_stop(i2c, 0); }
else
{ /* go to the next transfer */ dev_dbg(i2c->dev, "READ: Next Transfer\n"); i2c->msg_ptr = 0; i2c->msg_idx++; i2c->msg++; } } break; } /* acknowlegde the IRQ and get back on with the work */ out_ack: tmp = readl(i2c->regs + S3C2410_IICCON); tmp &= ~S3C2410_IICCON_IRQPEND; writel(tmp, i2c->regs + S3C2410_IICCON); out: return ret; }
中断处理函数s3c24xx_i2c_irq()主要通过调用i2c_s3c_irq_nextbyte()函数进行传输工作的进一步推进。i2s_s3c)irq_nexbyte函数通过switch(i2c->state)的不同状态进行处理,在每种状态下,先检查i2c_state的状态和硬件寄存器应该处于的状态是否一致,如果不一致,则证明有误,直接返回。当I2C处于读状态STATE_READ或写状态STATE_WRITE时,通过is_lastmsg()函数判断是否传输的是最后一条I2C消息。如果是,则产生停止位,否则通过i2c_msg_idx++、i2c->msg++推进到下一条消息。
六、总结
Linux I2C驱动体系结构有相当的复杂度,它主要由3部分组成,即I2C核心,I2C总线驱动和I2C设备驱动。I2C核心是I2C总线驱动和I2C设备驱动的中间枢纽,它以通用的、与平台无关的接口实现了I2C中设备与适配器的沟通。I2C总线驱动填充了i2c_adapter和i2c_algorithm结构体,I2C设备驱动填充i2c_driver结构体并实现其本身所对应的设备类型的驱动。
另外,系统中的i2c-dev.c文件定义的主设备号为89的设备可以方便的给应用程序提供读写I2C设备寄存器的能力,是的工程师大多数时候并不需要为具体的I2C设备驱动定义文件操作接口。