1 IIC硬件设备基本概念
1.1 IIC概述
I2C是飞利浦公司推出的两线式串行扩展总线,用于连接微控制器及其外围设备。
1.2 I2C使用两根双向信号线来传递数据
Serial clock Line(SCL)
Serial Data Address(SDA)
1.3 总线速度
标准速度100kbps,快速模式400kbps,高速模式3.4Mbps
1.4 I2C特点
半双工,仅需要两根线
2 IIC硬件设备时序(AT24C08为例)
2.1 起始信号
当时钟线SCL为高电平时 , 数据线SDA有高电平到低电平的下降沿
2.2 停止信号
当时钟线SCL为高电平时 , 数据线SDA由低电平到高电平的上升沿。
2.3 应答信号
应答信号表明IIC的数据传输结束 , IIC在传输一个字节的数据后著期间在第九个时钟周期释放总线SDA , 使其处于高电平 , 此时从器件输出低电平拉蒂数据SDA为应答信号。如果为高电平的话则时传送异常 , 结束发送。
2.4 非应答信号
当我们在进行读操作的时候 , 器件在读取8位数据之后 ,如果不在继续读取了就发送一个非应答信号 , 就是在第九个时钟周期时释放总线SDA , 将其拉高 持续整个时钟周期。
2.5 读写时序
i2c在在读写时 ,表示i2c总线忙 。要求在读或者写的时(SCL=1)数据必须稳定 。
2.6 读写操作分析
2.6.1写操作
- 1.装载AT24C08设备地址:0xA0
- 2.设置IIC开始条件
- 3.使能ACK
- 4.检测状态
- 5.装载存储地址
- 6.使能ACK
- 7.检测状态
- 8.写数据
- 9.使能ACK
- 10.设置IIC停止条件
2.6.2读操作
- 1.装载AT24C08设备地址:0xA0
- 2.设置IIC开始条件
- 3.使能ACK
- 4.检测状态
- 5.装载存储地址
- 6.使能ACK
- 7.检测状态
- 8.装载设备地址:0xA0 | 0x1
- 9.设置IIc开始条件
- 10.使能AcK信号
- 11.检测状态
- 12.读取数据
- 13.关闭ACK信号
- 14.设置停止条件
3 IIC硬件设备相关寄存器分析(S5PV210)
3.1 I2CCON
使能ACK,选择时钟源,使能Tx/Rx,检测状态
3.2 I2CSTAT
主机发送/接收,设置开始/停止条件,数据Tx/Rx使能,ACK能否被接收
3.3 I2CADD
i2c作为从机才使用
3.4 I2CDS
装载设备地址,发送/接收数据
4 IIC设备驱动框架
4.1 i2c设备驱动层组件(i2c-dev.c)
功能:
1.给用户提供调用接口
2.实现策略问题:它知道发什么数据,但不知道怎么发数据
4.2 i2c核心层组件(i2c-core.c)
功能:
1.注册一根i2c总线
2.给驱动编程人员提供编程接口
4.3 i2c总线驱动层组件(i2c-s3c2410.c)
功能:
1.初始化硬件(初始化i2c控制器), 根据i2c操作时序进行控制i2c控制器实现数据接收/发送
2.实现操作方法:它知道怎么去发数据,但不知道发什么数据
5 IIC设备驱动核心层分析
5.1 核心层代码流程分析
i2c_init:
//注册一根i2c总线
retval = bus_register(&i2c_bus_type);
struct bus_type i2c_bus_type = {
.name = "i2c",
.match = i2c_device_match, //匹配函数
.probe = i2c_device_probe,
.remove = i2c_device_remove,
.shutdown = i2c_device_shutdown,
};
static int i2c_device_match(struct device *dev, struct device_driver *drv)
{
struct i2c_client *client = i2c_verify_client(dev);
struct i2c_driver *driver;
//驱动和设备进行匹配
/* Attempt an OF style match */
if (i2c_of_match_device(drv->of_match_table, client))
return 1;
/* Then ACPI style match */
if (acpi_driver_match_device(dev, drv))
return 1;
driver = to_i2c_driver(drv);
/* Finally an I2C match */
if (i2c_match_id(driver->id_table, client))
return 1;
return 0;
}
//注册i2c驱动
retval = i2c_add_driver(&dummy_driver);
5.2 涉及重要结构体
struct i2c_driver { //表示一个i2c驱动
int (*probe)(struct i2c_client *client, const struct i2c_device_id *id);
int (*remove)(struct i2c_client *client);
struct device_driver driver;//表示驱动
const struct i2c_device_id *id_table;//记录i2c驱动能服务于那些设备
...
};
struct i2c_client { //表示i2c设备
unsigned short flags; //标号
unsigned short addr; //设备地址
char name[I2C_NAME_SIZE]; //设备名字
struct i2c_adapter *adapter; //所属适配器
struct device dev; //设备结构体
int init_irq; //初始化阶段中断
int irq; //中断号
struct list_head detected; //链表
};
struct i2c_adapter { //表示i2c适配器,控制器
//i2c操作方法(利用i2c操作协议数据)
const struct i2c_algorithm *algo;
int (*master_xfer)(struct i2c_adapter *adap, struct i2c_msg *msgs,
int num);
};
struct i2c_msg { //表示i2c数据包
__u16 addr; //设备地址
__u16 flags; //数据包: 读-1 写-0
__u16 len; //有效数据长度
__u8 *buf; //有效数据指针
};
struct i2c_board_info { //表示i2c办卡信息
char type[I2C_NAME_SIZE]; //i2c设备名字
unsigned short flags;
unsigned short addr; //i2c设备地址
const char *dev_name;
void *platform_data;
struct device_node *of_node;
struct fwnode_handle *fwnode;
const struct property_entry *properties;
const struct resource *resources;
unsigned int num_resources;
int irq;
};
#define I2C_BOARD_INFO(dev_type, dev_addr) \
.type = dev_type, .addr = (dev_addr)
6 IIC设备驱动驱动层分析
6.1 设备驱动层代码流程分析
static int __init i2c_dev_init(void)
{
//注册主设备号
res = register_chrdev_region(MKDEV(I2C_MAJOR, 0), I2C_MINORS, "i2c");
//创建设备类
i2c_dev_class = class_create(THIS_MODULE, "i2c-dev");
//绑定适配器,搜索i2c总线设备链表,每搜索到一个设备,i2cdev_attach_adapter
i2c_for_each_dev(NULL, i2cdev_attach_adapter);
}
//创建设备文件 /dev/i2c-0 /dev/i2c-1
static int i2cdev_attach_adapter(struct device *dev, void *dummy)
{
struct i2c_adapter *adap;
struct i2c_dev *i2c_dev;
int res;
if (dev->type != &i2c_adapter_type)
return 0;
adap = to_i2c_adapter(dev);
i2c_dev = get_free_i2c_dev(adap);
if (IS_ERR(i2c_dev))
return PTR_ERR(i2c_dev);
//注册硬件操作方法
cdev_init(&i2c_dev->cdev, &i2cdev_fops);
i2c_dev->cdev.owner = THIS_MODULE;
device_initialize(&i2c_dev->dev);
i2c_dev->dev.devt = MKDEV(I2C_MAJOR, adap->nr);
i2c_dev->dev.class = i2c_dev_class;
i2c_dev->dev.parent = &adap->dev;
i2c_dev->dev.release = i2cdev_dev_release;
dev_set_name(&i2c_dev->dev, "i2c-%d", adap->nr);
res = cdev_device_add(&i2c_dev->cdev, &i2c_dev->dev);
if (res) {
put_i2c_dev(i2c_dev, false);
return res;
}
pr_debug("i2c-dev: adapter [%s] registered as minor %d\n",
adap->name, adap->nr);
return 0;
}
//硬件操作方法
static const struct file_operations i2cdev_fops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.read = i2cdev_read,
.write = i2cdev_write,
.unlocked_ioctl = i2cdev_ioctl,
.compat_ioctl = compat_i2cdev_ioctl,
.open = i2cdev_open,
.release = i2cdev_release,
};
6.2 涉及重要操作函数
//发送i2c数据
static inline int i2c_master_send(const struct i2c_client *client,const char *buf, int count);
//接收i2c数据
static inline int i2c_master_recv(const struct i2c_client *client,char *buf, int count)
//传输一个i2c数据包
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);
//注册板块信息
int i2c_register_board_info(int busnum, struct i2c_board_info const *info,unsigned n);
//注册、注销适配器
int i2c_add_adapter(struct i2c_adapter *adap);
void i2c_del_adapter(struct i2c_adapter *adap);
int i2c_add_numbered_adapter(struct i2c_adapter *adap);
//注册、注销驱动
int i2c_register_driver(struct module *owner, struct i2c_driver *driver);
void i2c_del_driver(struct i2c_driver *driver);
7 IIC应用开发
7.1 查看驱动是否编入
cat /proc/devices 看是否有89
7.2 驱动编入
make menuconfig 开启相关选项
7.3 应用源码
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#define I2C_SLAVE 0x0703
int main(int argc, char **argv)
{
int fd;
int device_addr = 0x50; /* The I2C address AT24C08*/
char register_addr = 0x3;
char wbuf[10];
char rbuf[10];
fd = open("/dev/i2c-0", O_RDWR);
if (fd < 0) {
perror("open failed!\n");
exit(1);
}
if (ioctl(fd, I2C_SLAVE, device_addr) < 0) {
perror("ioctl failed!\n");
exit(1);
}
//printf("argv[1] = %s\n", argv[1]);
if(strcmp(argv[1], "w")==0)
{
wbuf[0] = register_addr;
wbuf[1] = argv[2];
if (write(fd, wbuf, 2) != 2) {
perror("write failed!\n");
exit(1);
}
}
else
{
if (write(fd, ®ister_addr, 1) != 1) {
perror("write failed!\n");
exit(1);
}
if (read(fd, &rbuf[0], 1) != 1) {
perror("read failed!\n");
exit(1);
} else {
printf("rbuf[0] = %d\n", rbuf[0]);
}
}
}
8 IIC设备驱动系统流程分析
8.1 open
APP: fd = open("/dev/i2c-0", O_RDWR);
=====================================
VFS: sys_open
i2c-dev.c
i2c_dev_init
//硬件操作方法
static const struct file_operations i2cdev_fops = {
.open = i2cdev_open,
//获取次设备号
unsigned int minor = iminor(inode);
struct i2c_client *client;
struct i2c_adapter *adap;
//获取适配器
adap = i2c_get_adapter(minor);
client->adapter = adap; //制定client属于那个设备器控制
file->private_data = client;
return 0;
};
8.2 ioctl
APP: ioctl(fd, I2C_SLAVE, device_addr)
=====================================
VFS: sys_ioctl
i2c-dev.c
i2c_dev_init
//硬件操作方法
static const struct file_operations i2cdev_fops = {
.unlocked_ioctl = i2cdev_ioctl,
//获取client属性
struct i2c_client *client = file->private_data;
switch (cmd) {
case I2C_SLAVE:
client->addr = arg;
return 0;
};
8.3 write
APP: write(fd, wbuf, 2) != 2
=====================================
VFS: sys_ioctl
i2c-dev.c
i2c_dev_init
//硬件操作方法
static const struct file_operations i2cdev_fops = {
.write = i2cdev_write,
//获取client属性
struct i2c_client *client = file->private_data;
//获取用户空间数据
tmp = memdup_user(buf, count);
copy_from_user(p, src, len)
//发送i2c数据
ret = i2c_master_send(client, tmp, count);
i2c-core.c
//发送i2c数据
ret = i2c_master_send(client, tmp, count);
//构建i2c数据包
struct i2c_msg msg = {
.addr = client->addr,
.flags = flags | (client->flags & I2C_M_TEN),
.len = count,
.buf = buf,
};
//发送i2c数据包
ret = i2c_transfer(client->adapter, &msg, 1);
adap->algo->master_xfer(adap, msgs, num);
i2c-s3c2410.c
//调用i2c总线驱动操作方法
adap->algo->master_xfer(adap, msgs, num);
static const struct i2c_algorithm s3c24xx_i2c_algorithm = {
.master_xfer = s3c24xx_i2c_xfer,
.functionality = s3c24xx_i2c_func,
};
9 IIC设备驱动资源层分析
arch/arm/mach-s3c/devs.c
/* I2C */
//构建平台资源
static struct resource s3c_i2c0_resource[] = {
[0] = DEFINE_RES_MEM(S3C_PA_IIC, SZ_4K),
[1] = DEFINE_RES_IRQ(IRQ_IIC),
};
//构建i2c结构体
struct platform_device s3c_device_i2c0 = {
.name = "s3c2410-i2c",
.id = 0,
.num_resources = ARRAY_SIZE(s3c_i2c0_resource),
.resource = s3c_i2c0_resource,
};
//构建i2c平台数据
struct s3c2410_platform_i2c default_i2c_data __initdata = {
.flags = 0,
.slave_addr = 0x10,
.frequency = 100*1000,
.sda_delay = 100, //间隔时间
};
//设置i2c平台数据
void __init s3c_i2c0_set_platdata(struct s3c2410_platform_i2c *pd)
{
struct s3c2410_platform_i2c *npd;
if (!pd) {
pd = &default_i2c_data;
pd->bus_num = 0;
}
npd = s3c_set_platdata(pd, sizeof(*npd), &s3c_device_i2c0);
if (!npd->cfg_gpio)
npd->cfg_gpio = s3c_i2c0_cfg_gpio; //配置i2c GPIO 功能
}
//注册平台设备
arch/arm/mach-s3c/smdk6400.c
static struct platform_device *smdk6400_devices[] __initdata = {
&s3c_device_i2c0,
};
static struct i2c_board_info i2c_devs[] __initdata = {
{ I2C_BOARD_INFO("wm8753", 0x1A), },
{ I2C_BOARD_INFO("24c08", 0x50), },
};
static void __init smdk6400_machine_init(void)
{
//注册i2c板块信息
i2c_register_board_info(0, i2c_devs, ARRAY_SIZE(i2c_devs));
//注册平台设备
platform_add_devices(smdk6400_devices, ARRAY_SIZE(smdk6400_devices));
}
10 IIC设备总线驱动层分析
//构建i2c平台设备驱动结构体
//通过id_table匹配对应驱动,如果匹配会调用probe函数
static struct platform_driver s3c24xx_i2c_driver = {
.probe = s3c24xx_i2c_probe, //通过name匹配
.remove = s3c24xx_i2c_remove,
.id_table = s3c24xx_driver_ids,
.driver = {
.name = "s3c-i2c",
.pm = S3C24XX_DEV_PM_OPS,
.of_match_table = of_match_ptr(s3c24xx_i2c_match),
},
};
//注册平台设备驱动
//1.将平台驱动结构体加入驱动链表
//2.搜索平台设备链表,每搜索一个会调用平台总线match,按照平台设备名字与平台驱动id_table名字进行匹配,匹配成功,调用probe函数
static int __init i2c_adap_s3c_init(void)
{
return platform_driver_register(&s3c24xx_i2c_driver);
}
subsys_initcall(i2c_adap_s3c_init);
//注销平台设备驱动
static void __exit i2c_adap_s3c_exit(void)
{
platform_driver_unregister(&s3c24xx_i2c_driver);
}
module_exit(i2c_adap_s3c_exit);
s3c24xx_i2c_probe:
//获取平台数据资源
pdata = dev_get_platdata(&pdev->dev);
//配置协议
strlcpy(i2c->adap.name, "s3c2410-i2c", sizeof(i2c->adap.name));
i2c->adap.owner = THIS_MODULE;
//设置adapter,发送数据接口,里面配置了时序
i2c->adap.algo = &s3c24xx_i2c_algorithm;//i2c时序操作函数
i2c->adap.retries = 2;
i2c->adap.class = I2C_CLASS_DEPRECATED;
i2c->tx_setup = 50;
//获取平台资源,IO内存地址资源
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
//物理地址映射为虚拟地址
i2c->regs = devm_ioremap_resource(&pdev->dev, res);
//初始化i2c控制器,硬件相关操作
ret = s3c24xx_i2c_init(i2c);
//获取IRQ资源
i2c->irq = ret = platform_get_irq(pdev, 0);
//申请IRQ
ret = devm_request_irq(&pdev->dev, i2c->irq, s3c24xx_i2c_irq,
0, dev_name(&pdev->dev), i2c);
//利用i2c中断方式发送/接受数据
s3c24xx_i2c_irq --> i2c_s3c_irq_nextbyte
//设置平台驱动数据
platform_set_drvdata(pdev, i2c);
//注册i2c适配器
ret = i2c_add_numbered_adapter(&i2c->adap);
//指定适配器名字
dev_set_name(&adap->dev, "i2c-%d", adap->nr);
//指定适配器所属总线
adap->dev.bus = &i2c_bus_type;
//指定适配器类型
adap->dev.type = &i2c_adapter_type;
//注册设备
res = device_register(&adap->dev);