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
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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(" 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(" 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