I2C总线(I2C bus,Inter-IC bus)是一个双向的两线连续总线,提供集成电路(ICs)之间的通信线路。I2C总线是一种串行扩展技术,最早由Philips公司推出。Philips公司推出的I2C总线采用一条数据线(SDA),加一条时钟线(SCL)来完成数据的传输及外围器件的扩展;对各个节点的寻址是软寻址方式,节省了片选线,标准的寻址字节SLAM为7位,可以寻址127个单元。
I2C总线有三种数据传输速度:标准,快速模式和高速模式。标准的是100Kbps,快速模式为400Kbps,高速模式支持快至3.4Mbps的速度。
I2C位传输
数据传输:SCL为高电平时,SDA线若保持稳定,那么SDA上是在传输数据bit;若SDA发生跳变,则用来表示一个会话的开始或结束(后面讲)
数据改变:SCL为低电平时,SDA线才能改变传输的bit
I2C开始和结束信号
开始信号:SCL为高电平时,SDA由高电平向低电平跳变,开始传送数据。
结束信号:SCL为高电平时,SDA由低电平向高电平跳变,结束传送数据。
I2C应答信号
Master每发送完8bit数据后等待Slave的ACK。
即在第9个clock,若从IC发ACK,SDA会被拉低。
若没有ACK,SDA会被置高,这会引起Master发生RESTART或STOP流程,如下所示:
SCL is a clock signal that is driven by the master. SDA is a bi-directional data signal that can be driven by either the master or the slave.
I2C架构图:
代码分析:
kernel-3.10\drivers\misc\mediatek\i2c\mt6735\I2c.c
(注:所有的i2c驱动,在该模块加载前就已经加载,调用i2c_register_board_info函数加载的board_info信息,已经加入__i2c_board_list链表)。
设备驱动以qn8027为例。
第1607~1623行,在device tree上找到AP_DMA结点,见kernel-3.10/arch/arm64/boot/dts/mt6735m.dtsi。
DT的相关知识请参考:http://blog.csdn.net/21cnbao/article/details/8457546
第1624行执行platform_driver_register(&mt_i2c_driver);
第1508行,初始化i2c->wait等待对列。
第1510行,注册下降沿中断,中断处理函数为mt_i2c_irq。该函数读取I2C中断状态,唤醒等待对列i2c->wait队列。
第1521行,调用i2c_add_numbered_adapter(&i2c->adap);该函数会调用到__i2c_add_numbered_adapter,其代码如下:
第1090行,分配ID号,IDR机制具体描述如下:
IDR机制在Linux内核中指的是整数ID管理机制。实质上来讲,这就是一种将一个整数ID号和一个指针关联在一起的机制。这个机制最早在03年2月加入内核,当时作为POSIX定时器的一个补丁。现在,内核中很多地方都可以找到它的身影。
IDR机制适用在那些需要把某个整数和特定指针关联在一起的地方。例如,在IIC总线中,每个设备都有自己的地址,要想在总线上找到特定的设备,就必须要先发送设备的地址。当适配器要访问总线上的IIC设备时,首先要知道它们的ID号,同时要在内核中建立一个用于描述该设备的结构体,和驱动程序。将ID号和设备结构体结合起来,如果使用数组进行索引,一旦ID号很大,则用数组索引会占据大量内存空间。这显然不可能。或者用链表,但是,如果总线中实际存在的设备很多,则链表的查询效率会很低。此时,IDR机制应运而生。该机制内部采用红黑树实现,可以很方便的将整数和指针关联起来,并且具有很高的搜索效率。
第1096行调用i2c_register_adapter注册adapter,主要代码如下:
第1013行,注册&adap->dev.
第1020行,调用class_compat_create_link,代码如下:
即创建i2c_adapter_compat_class->kobj目录下指向&adap->dev,&adap->dev->kobj目录下指向adap->dev.parent目录的软链接。
第1062行,因其他驱动模块已经加载,即所有其他模块用到的i2c总线,例如如果用到i2c4,则根据i2c_register_board_info函数,
__i2c_first_dynamic_bus_num会为5. 故会调用到i2c_scan_static_board_info函数,该函数代码如下:
第948行,调用i2c_new_device(adap, &info);根据info数据,注册新的client设备。
回到i2c_register_adapter函数,第1066行,调用bus_for_each_drv对i2c bus上的驱动进行遍历,对找到驱动,执行__process_new_adapter(drv,adap),该函数调用
i2c_do_add_adapter(to_i2c_driver(drv), adap); 再调用 i2c_detect(adap, driver);因driver->detect || address_list为空,会直接退出。
driver->attach_adapter即qn8027_driver中没有赋值,故不会调用到。
回到mt_i2c_probe函数,第1529行,调用of_i2c_register_devices,
注:系统集成的模块会走该流程,如ncp1854。
node->full_name = /bus/I2C3@0x1100F000/ncp1854@36
request_module("%s%s", I2C_MODULE_PREFIX, info.type);即请求加载 i2c:ncp1854。
而外部加的模块则在之前的i2c_scan_static_board_info函数已作了处理。
第67行,让linux系统的用户空间调用/sbin/modprobe函数加载名为i2c:I2Cx模块。
kernel-3.10/include/linux/mod_devicetable.h:
#define I2C_MODULE_PREFIX "i2c:"
第69行,调用i2c_new_device(adap, &info);根据info数据,注册新的client设备。
kernel-3.10\drivers\i2c\I2c-core.c
postcore_initcall(i2c_init);
第1433行,注册i2c_bus_type总线。
第1437行, register a compatibility class。
第1443,调用i2c_add_driver添加一个dummy_driver驱动。该函数调用i2c_register_driver,其代码如下:
第1327行调用driver_register(&driver->driver);向i2c bus注册i2c驱动。
第1343行调用i2c_for_each_dev,
第1811行对driver->detect || !address_list进行判断,如I2C驱动这两个成员变量没有赋值,则直接退出。
i2c_register_board_info函数解析:
生成i2c_devinfo结构体,赋值为busnum,*info,将其加入__i2c_board_list链表。
小结:设备驱动程序通过i2c_register_board_info函数注册i2c_board_info数据,i2c_scan_static_board_info函数会根据这些数据生成对应的client设备,设备驱动提供接口给应用层,并且在加载时调用i2c_add_driver完成与对应的client配对,而Client与i2c_adapter关联在一起。数据最终由mt_i2c处理。I2c_adapter与mt_i2c、platform_device关系见下图:
I2c数据发送代码流程:
kernel-3.10\drivers\i2c\I2c-core.c
该函数定义并初始化i2c_msg消息结构体后,第1593行调用i2c_transfer进行数据传送。i2c_transfer进行相应的判断及互斥操作后调用__i2c_transfer函数,该函数最终会调用
adap->algo->master_xfer(adap, msgs, num);
第1159行通过i2c_get_adapdata找到关联的mt_i2c。
第1163行调用mt_i2c_do_transfer函数,该函数再会调用到mt_i2c_start_xfer函数。
第947行,调用_i2c_translate_msg,将msg转换为mt_i2c。
第960行,调用_i2c_transfer_interface进行传输。该函数最后会调用_i2c_deal_result函数来处理传输结果。
tmo = wait_event_timeout(i2c->wait,atomic_read(&i2c->trans_stop), tmo);即中断模式下,当前进程加入i2c->wait等待对列进行休眠。
唤醒时机:a. 发生中断,正常唤醒,
b. 超时唤醒。
QN8027.C驱动代码示例:
/***************************************************************************** Copyright(c) 2012 xxxx Inc. All Rights Reserved File name : xxxx-qn8027.c Description : NM326 host interface History : ---------------------------------------------------------------------- 2015/02/28 dlj initial *******************************************************************************/ #include <linux/init.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/i2c.h> #include <linux/miscdevice.h> #include <asm/uaccess.h> #include <linux/delay.h> #include <linux/input.h> #include <linux/kobject.h> #include <linux/earlysuspend.h> #include <linux/platform_device.h> #include <linux/cdev.h> #include <linux/gpio.h> #include <linux/fs.h> #include <linux/err.h> #include "../inc/fm_ioctl.h" #include "xxxx-qn8027.h" static QN8027_CONF qn8027_conf_setting; static unsigned char load_setting_flag = 0; static unsigned char power_up_flag = 0; static int qn8027_reg = -1; static struct class *qn8027_class; static struct i2c_client *qn8027_client; static struct mutex qn8027_i2c_lock; static struct i2c_board_info __initdata i2c_qn8027={ I2C_BOARD_INFO(QN8027_DEV_NAME, QN8027_I2C_SLAVE_ADDR)}; static int qn8027_save_conf(void) { struct file *fp; mm_segment_t fs; loff_t pos = 0; int ret = 0; fp = filp_open(QN8027_CONF_FILE, O_RDWR|O_CREAT, 0644); if(IS_ERR(fp)) { QN8027_ERR("create %s file failed!\n", QN8027_CONF_FILE); ret = -1; } else { fs = get_fs(); set_fs(KERNEL_DS); vfs_write(fp, (char *)&qn8027_conf_setting, sizeof(qn8027_conf_setting), &pos); set_fs(fs); filp_close(fp, NULL); } return ret; } static int qn8027_load_conf(void) { struct file *fp; mm_segment_t fs; loff_t pos = 0; int ret = 0; fp = filp_open(QN8027_CONF_FILE, O_RDONLY, 0644); if(IS_ERR(fp)) { QN8027_ERR("read %s file failed!\n", QN8027_CONF_FILE); ret = -1; } else { fs = get_fs(); set_fs(KERNEL_DS); vfs_read(fp, (char *)&qn8027_conf_setting, sizeof(qn8027_conf_setting), &pos); set_fs(fs); filp_close(fp, NULL); } return ret; } static int qn8027_i2c_write(u8 reg, u8 writedata) { u8 databuf[2] = {0}; int ret = 0; databuf[0] = reg; databuf[1] = writedata; mutex_lock(&qn8027_i2c_lock); QN8027_LOG("%s: write value(0x%x) to reg(0x%x) \n", __FUNCTION__, (int)writedata, (int)reg); ret = i2c_master_send(qn8027_client, databuf, 0x2); if(ret < 0) { QN8027_LOG("qn8027_i2c_write send data failed!!!\n"); } mutex_unlock(&qn8027_i2c_lock); return ret; } static int qn8027_i2c_read(u8 reg, u8 *readdata) { u8 databuf = 0; int ret = 0; databuf = reg; mutex_lock(&qn8027_i2c_lock); qn8027_client->ext_flag = I2C_WR_FLAG; ret = i2c_master_send(qn8027_client, &databuf, (1<<8 | 1)); if(ret < 0) { QN8027_LOG("qn8027_i2c_write send data failed!!!\n"); } qn8027_client->ext_flag = 0; mutex_unlock(&qn8027_i2c_lock); QN8027_LOG("qn8027_i2c_read: databuf = 0x%x\n", databuf); *readdata = databuf; return ret; } void QNF_SetRegBit(u8 reg, u8 bitMask, u8 data_val) { u8 temp; qn8027_i2c_read(reg, &temp); temp &= (u8)(~bitMask); temp |= (data_val & bitMask); qn8027_i2c_write(reg, temp); } static int qn8027_open(struct inode *inode, struct file *file) { QN8027_FUN(); return 0; } static int qn8027_release(struct inode *inode, struct file *file) { QN8027_FUN(); return 0; } static long qn8027_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { int ret = 0; void __user *data; char strbuf[QN8027_BUFSIZE] = {0}; QN8027_FUN(); QN8027_LOG("qn8027_ioctl: cmd = %u\n", cmd); QN8027_LOG("qn8027_ioctl: QN8027_IOCTL_POWERUP = %lu\n", QN8027_IOCTL_POWERUP); QN8027_LOG("qn8027_ioctl: QN8027_IOCTL_SETCHENNEL = %lu\n", QN8027_IOCTL_SETCHENNEL); QN8027_LOG("qn8027_ioctl: QN8027_IOCTL_GETINFO = %lu\n", QN8027_IOCTL_GETINFO); switch (cmd) { case QN8027_IOCTL_POWERUP: QN8027_LOG("qn8027_ioctl: arg = %ld\n", arg); if(arg == QN8027_POWERON) { if(power_up_flag == 0) { gpio_set_value(QN8027_POWER_ENABLE_PIN, 1); QN_ChipInitialization(); QND_TuneToCH(qn8027_conf_setting.channel); power_up_flag = 1; } } else if(arg == QN8027_POWEROFF) { if(power_up_flag == 1) { QNF_SetRegBit(SYSTEM, R_TXRX_MASK, 0); gpio_set_value(QN8027_POWER_ENABLE_PIN, 0); power_up_flag = 0; } } break; case QN8027_IOCTL_SETCHENNEL: if(arg<7600) arg=7600; if(arg>10800) arg= 10800; qn8027_conf_setting.channel = arg; QN8027_LOG("qn8027_ioctl: arg = %ld\n", arg); QND_TuneToCH(arg); if(qn8027_save_conf()) { QN8027_ERR("qn8027_save_conf failed\n"); } break; case QN8027_IOCTL_GETINFO: data = (void __user *) arg; if(data == NULL) { ret = -EINVAL; break; } if(load_setting_flag == 0 ) { if(qn8027_load_conf()) { QN8027_ERR("qn8027_load_conf failed\n"); } } load_setting_flag = 1; snprintf(strbuf, QN8027_BUFSIZE, "CH=%ld\n", qn8027_conf_setting.channel); if(copy_to_user(data, strbuf, sizeof(strbuf))) { ret = -EFAULT; } break; case QN8027_IOCTL_GETPOWERUP_STATUS: data = (void __user *) arg; if(data == NULL) { ret = -EINVAL; break; } snprintf(strbuf, QN8027_BUFSIZE, "%d\n", power_up_flag); if(copy_to_user(data, strbuf, sizeof(strbuf))) { ret = -EFAULT; } break; default: QN8027_ERR("cmd not support!\n"); ret = -EPERM; } return ret; } void QND_TuneToCH(int freq) { u8 tStep; u8 tCh; int f; if(freq<7600) freq=7600; if(freq>10800) freq= 10800; QN8027_LOG("QND_TuneToCH: ch = %ld\n", qn8027_conf_setting.channel); f = FREQ2CHREG(freq); // set to reg: CH tCh = (u8) f; qn8027_i2c_write(0x01, tCh); // set to reg: CH_STEP qn8027_i2c_read(0x00, &tStep); tStep &= ~0x03; tStep |= ((u8) (f >> 8) & 0x03); qn8027_i2c_write(0x00, tStep); } void QN_ChipInitialization(void) { QN8027_FUN(); qn8027_i2c_write(0x00,0x80);// reset all registers to the default value mdelay(200); qn8027_i2c_write(0x03,0x3F); qn8027_i2c_write(0x04,0x33);//set 12Mhz clock qn8027_i2c_write(0x00,0x41); qn8027_i2c_write(0x00,0x01); mdelay(200); qn8027_i2c_write(0x18,0xE4); qn8027_i2c_write(0x1B,0xF0); qn8027_i2c_write(0x01,0x00); qn8027_i2c_write(0x02,0xB9); qn8027_i2c_write(0x00,0x22);//send } /******************************************************************************/ static ssize_t qn8027_store_reg_addr(struct device* dev, struct device_attribute *attr, const char *buf, size_t count) { int write_data; if (!dev) { QN8027_ERR("dev is null!!\n"); return 0; } if (!strncmp(buf, "reg=",4) && (1 == sscanf(buf+4, "%d", &qn8027_reg))) { QN8027_LOG("qn8027_reg = 0x%x\n", qn8027_reg); } if (!strncmp(buf, "val=", 4)&&(1==sscanf(buf+4, "%d", &write_data))) { QN8027_LOG("write_data = 0x%x\n", write_data); qn8027_i2c_write((u8)qn8027_reg, (u8)write_data); } return count; } /******************************************************************************/ static ssize_t qn8027_show_reg_value(struct device* dev, struct device_attribute *attr, char *buf) { ssize_t res = 0; u8 value = 0; QN8027_LOG("reg is: 0x%x\n", qn8027_reg); qn8027_i2c_read((u8)qn8027_reg, &value); res = snprintf(buf, PAGE_SIZE, "0x%x\n", (int)value); return res; } /******************************************************************************/ static ssize_t qn8027_store_gpio_value(struct device* dev, struct device_attribute *attr, const char *buf, size_t count) { int value; if (!dev) { QN8027_ERR("dev is null!!\n"); return 0; } QN8027_LOG("qn8027_init_test now!!!\n"); if (!strncmp(buf, "val=",4) && (1 == sscanf(buf+4, "%d", &value))) { QN8027_LOG("val = %d\n", value); } if(value == 0 || value == 1) { gpio_set_value(QN8027_POWER_ENABLE_PIN, value); } return count; } /******************************************************************************/ static ssize_t qn8027_show_gpio_value(struct device* dev, struct device_attribute *attr, char *buf) { ssize_t res = 0; int value = 0; value = gpio_get_value(QN8027_POWER_ENABLE_PIN); QN8027_LOG("val = %d\n", value); res = snprintf(buf, PAGE_SIZE, "%d\n", value); return res; } DEVICE_ATTR(reg, S_IWUSR | S_IWGRP | S_IRUGO, qn8027_show_reg_value, qn8027_store_reg_addr); DEVICE_ATTR(gpio_val, S_IWUSR | S_IWGRP | S_IRUGO, qn8027_show_gpio_value, qn8027_store_gpio_value); static struct device_attribute *qn8027_attr_list[] = { &dev_attr_reg, &dev_attr_gpio_val, }; static int xxxx_create_attr(struct device *dev) { int idx, err = 0; int num = (int)(sizeof(qn8027_attr_list)/sizeof(qn8027_attr_list[0])); if (!dev) { return -EINVAL; } for (idx = 0; idx < num; idx++) { if ((err = device_create_file(dev, qn8027_attr_list[idx]))) { QN8027_ERR("device_create_file\n"); break; } } return err; } static struct file_operations qn8027_fops = { .owner = THIS_MODULE, .open = qn8027_open, .release = qn8027_release, .unlocked_ioctl = qn8027_ioctl, }; static int qn8027_probe(struct i2c_client *client, const struct i2c_device_id *id) { int ret = 0; struct device *qn8027_dev; QN8027_FUN(); qn8027_client = client; // 1. register character device ret = register_chrdev(QN8027_DEV_MAJOR, QN8027_DEV_NAME, &qn8027_fops); if(ret) { QN8027_ERR("<xxxx> register_chrdev(QN8027_DEV) failed\n"); goto error; } // 2. class create qn8027_class = class_create(THIS_MODULE, QN8027_DEV_NAME); if(IS_ERR(qn8027_class)) { QN8027_ERR("<xxxx> class create failed\n"); goto error; } // 3. device create qn8027_dev = device_create(qn8027_class, NULL, MKDEV(QN8027_DEV_MAJOR, QN8027_DEV_MINOR), NULL, QN8027_DEV_NAME); if (xxxx_create_attr(qn8027_dev)) { class_destroy(qn8027_class); goto error; } mutex_init(&qn8027_i2c_lock); QNF_SetRegBit(SYSTEM, R_TXRX_MASK, 0); gpio_set_value(QN8027_POWER_ENABLE_PIN, 0); qn8027_conf_setting.channel = QN8027_DEFCH; gpio_set_value(QN8027_POWER_ENABLE_PIN, 0); return 0; error: if (ret == 0) { unregister_chrdev(QN8027_DEV_MAJOR, QN8027_DEV_NAME); } return -1; } static int qn8027_remove(struct i2c_client *client) { unregister_chrdev(QN8027_DEV_MAJOR, QN8027_DEV_NAME); device_destroy(qn8027_class, MKDEV(QN8027_DEV_MAJOR, QN8027_DEV_MINOR)); class_destroy(qn8027_class); return 0; } static const struct i2c_device_id qn8027_ids[] = { { QN8027_DEV_NAME, 0, }, { /* LIST END */ } }; static struct i2c_driver qn8027_driver = { .driver = { .name = QN8027_DEV_NAME, }, .probe = qn8027_probe, .remove = qn8027_remove, .id_table = qn8027_ids, }; /*----------------------------------------------------------------------------*/ static int __init qn8027_init(void) { QN8027_FUN(); i2c_register_board_info(1, &i2c_qn8027, 1); if(i2c_add_driver(&qn8027_driver)!=0) { QN8027_ERR("qn8027_driver initialization failed!!\n"); return -1; } else { QN8027_LOG("qn8027_driver initialization succeed!!\n"); } return 0; } /*----------------------------------------------------------------------------*/ static void __exit qn8027_exit(void) { QN8027_FUN(); } /*----------------------------------------------------------------------------*/ module_init(qn8027_init); module_exit(qn8027_exit); /*----------------------------------------------------------------------------*/ MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("QN8027 driver"); MODULE_AUTHOR("[email protected]"); xxxx-qn8027.h /***************************************************************************** Copyright(c) 2012 XXXX Inc. All Rights Reserved File name : xxxx-qn8027.h Description : NM326 host interface History : ---------------------------------------------------------------------- 2015/02/28 dlj initial *******************************************************************************/ #ifndef XXXX_QN8027_H #define XXXX_QN8027_H #define QN8027_BUFSIZE 32 #define QN8027_CONF_FILE "/data/qn8027.setting" #define QN8027_DEV_MAJOR 228 #define QN8027_DEV_MINOR 0 #define QN8027_DEV_NAME "QN8027" #define QN8027_I2C_SLAVE_ADDR 0x2C #define QN8027_POWER_ENABLE_PIN 5 #define R_TXRX_MASK 0x20 #define SYSTEM 0x00 #define CH 0x01 #define CH_STEP 0x00 #define RDSD0 0x08 #define PAG_CAL 0x1f #define CID2 0x06 #define RDSEN 0x80 #define TXREQ 0x20 #define CH_CH 0x03 #define RDSTXRDY 0x04 #define TX_FDEV 0x11 // FDEV on datasheet typedef struct { unsigned long channel; }QN8027_CONF; /*----------------------------------------------------------------------------*/ #define QN8027_TAG "[QN8027] " #define QN8027_FUN(f) printk(KERN_ERR QN8027_TAG"%s\n", __FUNCTION__) #define QN8027_ERR(fmt, args...) printk(KERN_ERR QN8027_TAG"%s %d : "fmt, __FUNCTION__, __LINE__, ##args) #define QN8027_LOG(fmt, args...) printk(KERN_ERR QN8027_TAG fmt, ##args) #define QN8027_DEFCH 8800 #define FREQ2CHREG(freq) ((freq-7600)/5) typedef enum { QN8027_POWEROFF = 0, QN8027_POWERON = 1, }QN8027_POWER_TYPE; #define QN8027_IOC_MAGIC 0xf6 #define QN8027_IOCTL_POWERUP _IOWR(QN8027_IOC_MAGIC, 0, int32_t) #define QN8027_IOCTL_SETCHENNEL _IOWR(QN8027_IOC_MAGIC, 1, int32_t) #define QN8027_IOCTL_GETINFO _IOWR(QN8027_IOC_MAGIC, 2, int32_t) #define QN8027_IOCTL_GETPOWERUP_STATUS _IOWR(QN8027_IOC_MAGIC, 3, int32_t) //#define QN8027_IOCTL_POWERUP 1 //#define QN8027_IOCTL_SETCHENNEL 2 //#define QN8027_IOCTL_GETINFO 3 void QND_TuneToCH(int freq); void QN_ChipInitialization(void); #endif //XXXX_QN8027_H
参考文件:
http://blog.chinaunix.net/uid-24148050-id-120532.html