您的位置:首页 > 其它

i2c 驱动二:devfs文件系统

2016-10-26 19:51 281 查看
有关linux的i2c相关文章有一下几篇,他们互相关联,应该一同看:

    - i2c 驱动一:简介

    - i2c 驱动二:devfs文件系统

    - i2c 驱动三:自己实现设备和驱动分离

    - i2c 驱动四:sysfs文件系统

    - i2c 驱动五:gpio模拟i2c

一、说明

    linux3.4.112 中提供了两种访问 i2c 的方法,一种是基于 devfs文件系统的,另外一种是基于 sysfs文件系统的。

二、基于 devfs文件系统的分析

1. i2c 平台设备和资源

由于 Samsung 平台有关的芯片具有很多延伸性,对 Samsung 平台的 平台设备和资源放在了 arch/arm/plat-samsung/devs.c 文件中。

【1】定义 i2c 的设备资源

/* I2C */

static struct resource s3c_i2c0_resource[] = {
[0] = DEFINE_RES_MEM(S3C_PA_IIC, SZ_4K),
[1] = DEFINE_RES_IRQ(IRQ_IIC),
};


我们将它展开

static struct resource s3c_i2c0_resource[] = {
[0] = {
.start = S3C_PA_IIC,
.end = S3C_PA_IIC + SZ_4K,
.name = NULL,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = IRQ_IIC,
.end = IRQ_IIC + 1,
.name = NULL,
.flags = IORESOURCE_IRQ,
},
};


【2】定义 platform_device 变量

struct platform_device s3c_device_i2c0 = {
.name		= "s3c2410-i2c",
#ifdef CONFIG_S3C_DEV_I2C1 /* 2440 芯片只有一个i2c控制器,这个宏是有关一个处理器中有多个i2c控制器的选型,这里没有定义 */
.id		= 0,
#else
.id		= -1,
#endif
.num_resources	= ARRAY_SIZE(s3c_i2c0_resource),
.resource	= s3c_i2c0_resource,
};


【3】定义 s3c2410_platform_i2c 变量

struct s3c2410_platform_i2c default_i2c_data __initdata = {
.flags		= 0, /* 总线中的一些标志 */
.slave_addr	= 0x10, /* 如果作为从设备的从地址 */
.frequency	= 100*1000, /* 期待的总线的频率,单位是HZ,如果值是0,则总线会选择一个能用的默认的值 */
.sda_delay	= 100, /* SDA 边沿持续时间,单位是 ns */
};

【4】将 platform_device 和 s3c2410_platform_i2c 结合起来

    这个功能是在 arch/arm/mach-s3c24xx/mini2440.c(因为我配置内核时使用的默认的mini2440开发板的配置文件)的 mini2440_init(void) 函数中这句实现的:s3c_i2c0_set_platdata(NULL);(这里需要说明一定的是,NULL 表示使用系统默认的配置,如果想要用户自己配置,你需要自己定义一个s3c2410_platform_i2c 的结构体变量,然后把他的指针传进去)下面,我们就来分析一下s3c_i2c0_set_platdata

这个函数的路径:arch/arm/plat-samsung/devs.c

void __init s3c_i2c0_set_platdata(struct s3c2410_platform_i2c *pd)
{
struct s3c2410_platform_i2c *npd;

if (!pd) { /* pd传进来的是NULL */
pd = &default_i2c_data;
pd->bus_num = 0; /* 填充上s3c2410_platform_i2c变量里边的总线号,总线号是0 */
}

npd = s3c_set_platdata(pd, sizeof(struct s3c2410_platform_i2c),
&s3c_device_i2c0);

if (!npd->cfg_gpio)
npd->cfg_gpio = s3c_i2c0_cfg_gpio;/* 关联上s3c2410_platform_i2c变量里边的有关配置i2c引脚的回调函数 */
}


    s3c_set_platdata 函数我们单独分析一下

void __init *s3c_set_platdata(void *pd, size_t pdsize,
struct platform_device *pdev)
{
void *npd;

npd = kmemdup(pd, pdsize, GFP_KERNEL); /* 复制pd指向的s3c2410_platform_i2c结构体,npd指向新的结构体的 */

pdev->dev.platform_data = npd; /* 将platform_device结构体中的.dev.platform_data指针指向新的复制的结构体 */
return npd; /* 返回复制后的新的结构体的地址 */
}


    配置i2c引脚的函数

void s3c_i2c0_cfg_gpio(struct platform_device *dev)
{
s3c_gpio_cfgpin(S3C2410_GPE(15), S3C2410_GPE15_IICSDA); /* 将 GPE15,GPE14分别设置成sda和scl */
s3c_gpio_cfgpin(S3C2410_GPE(14), S3C2410_GPE14_IICSCL);
}


在 devs.c 文件的下边还有很多相似的比如是s3c_i2c1_set_platdata、s3c_i2c2_set_platdata... 他们的设置是为了有多个 i2c 控制器的芯片(因为这个文件对应的不止 2440 芯片)。

2. i2c 平台驱动

    驱动文件是 driver/i2c/busses/i2c-s3c2410.c

    入口函数

static int __init i2c_adap_s3c_init(void)
{
return platform_driver_register(&s3c24xx_i2c_driver);
}
subsys_initcall(i2c_adap_s3c_init);


功能是将 platform_driver 结构体 s3c24xx_i2c_driver 注册到子系统中去

static struct platform_driver s3c24xx_i2c_driver = {
.probe		= s3c24xx_i2c_probe,
.remove		= s3c24xx_i2c_remove,
.id_table	= s3c24xx_driver_ids,
.driver		= {
.owner	= THIS_MODULE,
.name	= "s3c-i2c",
.pm	= S3C24XX_DEV_PM_OPS,
.of_match_table = s3c24xx_i2c_match,
},
};


其中我们先来看看设备和驱动是如何匹配上的,先来看一下platform的匹配函数:plarform_match ()

/**
* platform_match - bind platform device to platform driver.
* @dev: device.
* @drv: driver.
*
* Platform device IDs are assumed to be encoded like this:
* "<name><instance>", where <name> is a short description of the type of
* device, like "pci" or "floppy", and <instance> is the enumerated
* instance of the device, like '0' or '42'.  Driver IDs are simply
* "<name>".  So, extract the <name> from the platform_device structure,
* and compare it against the name of the driver. Return whether they match
* or not.
*/
static int platform_match(struct device *dev, struct device_driver *drv)
{
struct platform_device *pdev = to_platform_device(dev);
struct platform_driver *pdrv = to_platform_driver(drv);

/* Attempt an OF style match first */
if (of_driver_match_device(dev, drv))
return 1;

/* Then try to match against the id table */
if (pdrv->id_table)
return platform_match_id(pdrv->id_table, pdev) != NULL;

/* fall-back to driver name match */
return (strcmp(pdev->name, drv->name) == 0);
}

【1】先来看看of_driver_match_device,此函数表示在定义了 CONFIG_OF_DEVICE 宏的时候,会利用驱动结构体中的.drver.of_match_table成员进行匹配,当然如果没有定义前边的宏,则返回值是0

当定义了宏

static inline int of_driver_match_device(struct device *dev,  /* 功能是检查设备是不是在结构体类型是of_device_id类型的 of_match_table 中 */
const struct device_driver *drv)
{
return of_match_device(drv->of_match_table, dev) != NULL;
}
const struct of_device_id *of_match_device(const struct of_device_id *matches, /* 把of_device_id结构体数组的首直接传递过来 */
const struct device *dev)
{
if ((!matches) || (!dev->of_node))
return NULL;
return of_match_node(matches, dev->of_node);
}
const struct of_device_id *of_match_node(const struct of_device_id *matches, /* matches是一个结构体数组 */
const struct device_node *node)
{
if (!matches)
return NULL;

while (matches->name[0] || matches->type[0] || matches->compatible[0]) {
int match = 1;
if (matches->name[0])
match &= node->name && !strcmp(matches->name, node->name); /* 比较of_device_id 结构体的名字和设备在设备树上的name */
if (matches->type[0])
match &= node->type && !strcmp(matches->type, node->type); /* 比较of_device_id 结构体的第二项和device.of_node.type */
if (matches->compatible[0])
match &= of_device_is_compatible(node,
matches->compatible); /* 这个在这里并不执行,以上的逻辑都是如果of_device_of中有,就要和device.of_node中相应项目就行比较,如果没有,就认为是匹配的 */
if (match)
return matches; /* 如果匹配,返回的是匹配的 of_device_id 结构体的首地址 */
matches++; /* 指向下一个of_device_id结构体 */
}
return NULL;
}


可以看到,最终的目的是看看设备是不是在驱动中的.driver.of_match_table 中,如果在,则直接返回 1,如果不在,继续判断有没有定义 驱动中的 id_table ,如果有,则要用 platform_match_id函数来判断

有必要将i2c-s3c2410.c 文件中的 s3cs3c24xx_i2c_match[] 看一下

驱动.driver = {.of_match_table = s3c24xx_i2c_match}
#ifdef CONFIG_OF
static const struct of_device_id s3c24xx_i2c_match[] = {
{ .compatible = "samsung,s3c2410-i2c" },
{ .compatible = "samsung,s3c2440-i2c" },
{},
};
MODULE_DEVICE_TABLE(of, s3c24xx_i2c_match);
#else
#define s3c24xx_i2c_match NULL
#endif


【2】再来看看 platform_match_id

static const struct platform_device_id *platform_match_id( /* 传进来的第一个参数是 驱动的 id_table,第二个参数是设备的地址 */
const struct platform_device_id *id,
struct platform_device *pdev)
{
while (id->name[0]) {
if (strcmp(pdev->name, id->name) == 0) { /* 如果 platform_device_id 结构体中的名字 和 设备中的名字相同 */
pdev->id_entry = id; /* id 的入口指针指向这个匹配的 platform_device_id 结构体实例 */
return id; /* 返回匹配的 platform_device_id 结构体的首地址 */
}
id++; /* 切换到数组的下一个成员 */
}
return NULL;
}

i2c-s3c2410.c 文件中的驱动的 id_table

驱动.id_table	= s3c24xx_driver_ids,
static struct platform_device_id s3c24xx_driver_ids[] = {
{
.name		= "s3c2410-i2c",
.driver_data	= TYPE_S3C2410,
}, {
.name		= "s3c2440-i2c",
.driver_data	= TYPE_S3C2440,
}, { },
};

当然如果有匹配的,platform_match返回的是逻辑 true,如果,id_table 成员向上边那个还是没有,那接下来检查设备的名字和驱动的名字,如果相同,也算是匹配上了

匹配过程的总结:

匹配过程是灵活的,匹配的依据有3个

(1)匹配 platform_driver 结构体的 .driver.of_match_table 成员和设备结构体中的相应部分

(2)匹配 platform_driver 结构体的 .id_table 成员 和设备结构体中的相应部分

(3)匹配 plarform_driver 结构体的 名字 和 设备 的名字

匹配的逻辑是如果 plarform_driver 中有这个成员则匹配,如果没有,不影响,在比较下边的,是依次比较的

至此,匹配过程说完了

匹配上之后就是 probe 指针指向的函数了,probe函数不具体分析了,只是知道一点,在probe中申请了中断,后续一个字节一个字节的传输都是由中断一步一步触发发起的(此部分请参照之前章节)。

现在,我们可以用提供给用户的 文件操作接口 来操作i2c驱动了,当然,既然是devfs文件系统,那么自动创建的设备节点是 /dev/i2c-0。下边是使用devfs文件系统操作i2c的例子。

3. 用户层实例

由于手头上有个 mpu6050,所以,就以mpu6050为例,采用的传感器的小板子是 GY-521

mpu6050_devfs.c

#include <stdio.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>

#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))

#define SMPLRT_DIV      0x19
#define CONFIG          0x1A
#define GYRO_CONFIG     0x1B
#define ACCEL_CONFIG    0x1C
#define ACCEL_XOUT_H    0x3B
#define ACCEL_XOUT_L    0x3C
#define ACCEL_YOUT_H    0x3D
#define ACCEL_YOUT_L    0x3E
#define ACCEL_ZOUT_H    0x3F
#define ACCEL_ZOUT_L    0x40
#define TEMP_OUT_H      0x41
#define TEMP_OUT_L      0x42
#define GYRO_XOUT_H     0x43
#define GYRO_XOUT_L     0x44
#define GYRO_YOUT_H     0x45
#define GYRO_YOUT_L     0x46
#define GYRO_ZOUT_H     0x47
#define GYRO_ZOUT_L     0x48
#define PWR_MGMT_1      0x6B

#define ADDR_MPU6050    0x68

static int mpu6050_read_byte(int fd, unsigned char reg)
{
int ret = 0;
unsigned char txbuf[1] = {reg};
unsigned char rxbuf[1];
struct i2c_rdwr_ioctl_data mpu_data;

ioctl(fd, I2C_TIMEOUT, 1);
ioctl(fd, I2C_RETRIES, 2);

struct i2c_msg msg[] = {
{
.addr = ADDR_MPU6050, /* 设备的地址 */
.flags= 0, /* 0 是写,I2C_RDWR 是读 */
.len = ARRAY_SIZE(txbuf),  /* msg 的长度 */
.buf = txbuf
},
{ADDR_MPU6050, I2C_M_RD, ARRAY_SIZE(rxbuf), rxbuf},
};
mpu_data.msgs = msg;
mpu_data.nmsgs = ARRAY_SIZE(msg);

ret = ioctl(fd, I2C_RDWR, &mpu_data);
if (ret < 0) {
printf("ret = %d\n", ret);
return ret;
}

return rxbuf[0];
}

static int mpu6050_write_byte(int fd, unsigned char reg, unsigned char val)
{
unsigned char txbuf[2] = {reg, val};
struct i2c_rdwr_ioctl_data mpu_data;

ioctl(fd, I2C_TIMEOUT, 1);
ioctl(fd, I2C_RETRIES, 2);

struct i2c_msg msg[] = {
{ADDR_MPU6050, 0, ARRAY_SIZE(txbuf), txbuf},
};
mpu_data.msgs = msg;
mpu_data.nmsgs = ARRAY_SIZE(msg);

ioctl(fd, I2C_RDWR, &mpu_data);

return 0;
}

static void read_mpu6050(int fd)
{
unsigned short accel_x = 0, accel_y = 0, accel_z = 0;
unsigned short gyro_x = 0, gyro_y = 0, gyro_z = 0;
unsigned short temp = 0;

mpu6050_write_byte(fd, PWR_MGMT_1, 0x00);
mpu6050_write_byte(fd, SMPLRT_DIV, 0x07);
mpu6050_write_byte(fd, CONFIG, 0x06);
mpu6050_write_byte(fd, GYRO_CONFIG, 0x18);
mpu6050_write_byte(fd, ACCEL_CONFIG, 0x01);

while(1) {
accel_x = mpu6050_read_byte(fd, ACCEL_XOUT_L);
accel_x |= mpu6050_read_byte(fd, ACCEL_XOUT_H) << 8;

accel_y =  mpu6050_read_byte(fd, ACCEL_YOUT_L);
accel_y |= mpu6050_read_byte(fd, ACCEL_YOUT_H) << 8;

accel_z = mpu6050_read_byte(fd, ACCEL_ZOUT_L);
accel_z |= mpu6050_read_byte(fd, ACCEL_ZOUT_H) << 8;

printf("acceleration data: x = %04x, y = %04x, z = %04x\n", accel_x, accel_y, accel_z);

gyro_x = mpu6050_read_byte(fd, GYRO_XOUT_L);
gyro_x |= mpu6050_read_byte(fd, GYRO_XOUT_H) << 8;

gyro_y = mpu6050_read_byte(fd, GYRO_YOUT_L);
gyro_y |= mpu6050_read_byte(fd, GYRO_YOUT_H) << 8;

gyro_z = mpu6050_read_byte(fd, GYRO_ZOUT_L);
gyro_z |= mpu6050_read_byte(fd, GYRO_ZOUT_H) << 8;

printf("gyroscope data: x = %04x, y = %04x, z = %04x\n", gyro_x, gyro_y, gyro_z);

temp = mpu6050_read_byte(fd, TEMP_OUT_L);
temp |= mpu6050_read_byte(fd, TEMP_OUT_H) << 8;

printf("temperature data: %x\n", temp);

usleep(1000*1000);
}
}

int main(int argc, const char *argv[])
{
int fd;

fd = open("/dev/i2c-0", O_RDWR);
if (fd < 0)
perror("open error");

read_mpu6050(fd);

close(fd);
return 0;
}


此程序中需要注意的

【1】驱动中能用的 ARRAY_SIZE() 函数在用户层不能在使用,需要自己去实现

【2】用户层中的延时有秒延时sleep和微秒延时usleep两个,需要包含头文件<unistd.h>,驱动中的延时有睡眠毫秒延时msleep、睡眠秒延时ssleep、忙等待毫秒延时mdelay、忙等待纳秒延时ndelay,需要包含头文件<linux/delay.h>

【3】编译程序的程序的命令是 arm-none-linux-gnueabi-gcc mpu6050_devfs.c -o mpu6050_devfs -march=armv4t(最后的这个选项是跟编译器有关,我的编译器用的是通用编译器)

【4】最后运行结果如下:



【5】此用户层程序是唯一需要我们写的,至于上边的分析代码是linux内核中自己实现了的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: