i2c 驱动二:devfs文件系统
2016-10-26 19:51
281 查看
有关linux的i2c相关文章有一下几篇,他们互相关联,应该一同看:
- i2c 驱动一:简介
- i2c 驱动二:devfs文件系统
- i2c 驱动三:自己实现设备和驱动分离
- i2c 驱动四:sysfs文件系统
- i2c 驱动五:gpio模拟i2c
【1】定义 i2c 的设备资源
我们将它展开
【2】定义 platform_device 变量
【3】定义 s3c2410_platform_i2c 变量
【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
s3c_set_platdata 函数我们单独分析一下
配置i2c引脚的函数
在 devs.c 文件的下边还有很多相似的比如是s3c_i2c1_set_platdata、s3c_i2c2_set_platdata... 他们的设置是为了有多个 i2c 控制器的芯片(因为这个文件对应的不止 2440 芯片)。
入口函数
功能是将 platform_driver 结构体 s3c24xx_i2c_driver 注册到子系统中去
其中我们先来看看设备和驱动是如何匹配上的,先来看一下platform的匹配函数:plarform_match ()
【1】先来看看of_driver_match_device,此函数表示在定义了 CONFIG_OF_DEVICE 宏的时候,会利用驱动结构体中的.drver.of_match_table成员进行匹配,当然如果没有定义前边的宏,则返回值是0
当定义了宏
可以看到,最终的目的是看看设备是不是在驱动中的.driver.of_match_table 中,如果在,则直接返回 1,如果不在,继续判断有没有定义 驱动中的 id_table ,如果有,则要用 platform_match_id函数来判断
有必要将i2c-s3c2410.c 文件中的 s3cs3c24xx_i2c_match[] 看一下
【2】再来看看 platform_match_id
i2c-s3c2410.c 文件中的驱动的 id_table
当然如果有匹配的,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的例子。
mpu6050_devfs.c
此程序中需要注意的
【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内核中自己实现了的。
- 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-521mpu6050_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内核中自己实现了的。
相关文章推荐
- 第二章 驱动开发_devfs设备文件系统详解
- 嵌入式系统通用驱动程序接口及其实现-I2C主机设备驱动(视频教学时的同步输入文件)
- linux驱动学习--第十天:第五章 Linux 文件系统与设备文件系统(四) 之 设备文件系统 devfs 和 udev
- Linux 设备驱动模型,I2C驱动,sys文件系统(1)
- i2c 驱动四:sysfs文件系统
- i2c 驱动四:sysfs文件系统
- 文件过滤系统驱动开发Filemon学习笔记
- Windows文件系统过滤驱动开发教程(2)
- NandFlash驱动和Yaffs文件系统的移植
- Windows文件系统过滤驱动开发教程
- Windows文件系统过滤驱动开发教程(11)
- 高级文件系统 - devfs介绍
- Windows文件系统过滤驱动开发教程(0,1)
- 文件系统过滤驱动基础知识
- Windows文件系统过滤驱动开发教程(6)
- Windows文件系统过滤驱动开发教程(9)
- Windows文件系统过滤驱动开发教程(3)
- 文件过滤系统驱动开发Filemon学习笔记
- Windows文件系统过滤驱动开发教程
- VC文件过滤系统驱动开发Filemon学习笔记