首先说明下为什么写这篇文章,网上有许多博客也是介绍I2C驱动在linux上移植的实现,但是笔者认为他们相当一部分没有分清所写的驱动时的驱动模型,是基于device tree, 还是基于传统的Platform模型,有些文章只是把代码移植到平台上调试测试下,并没有理清内部逻辑调用关系,所以觉得有必要把两种驱动模型阐述剖析清楚,本文阅读者必须以在单片机上调试过IIC总线为前提,能够分析从芯片datasheet和其工作原理和总线的基本操作,虽然I2C硬件体系结构比较简单,但是I2C体系结构在Linux中的实现却相当复杂,作为驱动工程师,编写具体的I2C驱动时,主要工作如下:
1)、提供I2C适配器的硬件驱动,探测,初始化I2C适配器(如申请I2C的I/O地址和中断号),驱动CPU控制的I2C适配器从硬件上产生各种信号以及处理I2C中断(I2C总线驱动);
2)、提供I2C控制的algorithm, 用具体适配器的xxx_xfer()函数填充i2c_algorithm的master_xfer指针,并把i2c_algorithm指针赋给i2c_adapter的algo指针(I2C总线驱动),用于产生I2C访问从设备周期所需要的信号;
3)、实现I2C设备驱动中的i2c_driver接口,用具体yyy的yyy_probe(),yyy_remove(),yyy_suspend(),yyy_resume()函数指针和i2c_device_id设备ID表赋给i2c_driver的probe,remove,suspend,resume和id_table指针(I2C设备驱动);
4)、实现I2C设备所对应类型的具体驱动,i2c_driver只是实现设备与总线的挂接(I2C设备驱动)。
Step1,必须理清platform_device和platform_driver之间的匹配方式
对比linux2.6.29实现方式见图1,linux3.14.78的实现方式见图2,可以发现,传统的Platform驱动模型只是通过匹配platform_device设备名和驱动的名字来实现,而对于3.0以后的内核,通过一下四种方式实现,首先时基于设备树风格的匹配(设备树是一种描述硬件的数据结构),第二种是基于ACPI风格的匹配,第三种是匹配ID表(platform_device设备名是否出现在platform_driver的ID表内),第四种方式才采用传统的匹配设备与驱动的名字来实现,我们先通过匹配platform_device设备名和驱动的名字来实现IIC在2.6.29上移植,然后再分析通过设备树的方式实现IIC在3.14.78上移植,下面我们开始:
//linux2.6.29系统中为platform总线定义了一个bus_type的实例platform_bus_type,
struct bus_type platform_bus_type = {
.name = “platform”,
.dev_attrs = platform_dev_attrs,
.match = platform_match,
.uevent = platform_uevent,
.pm = PLATFORM_PM_OPS_PTR,
};
EXPORT_SYMBOL_GPL(platform_bus_type); //这里要重点关注其match()成员函数,正是此成员表明了platform_device和platform_driver之间如何匹配。
static int platform_match(struct device *dev, struct device_driver *drv)
{
struct platform_device *pdev; pdev = container_of(dev, struct platform_device, dev);
return (strncmp(pdev->name, drv->name, BUS_ID_SIZE) == );
}
//匹配platform_device和platform_driver主要看二者的name字段是否相同。
//对platform_device的定义通常在BSP的板文件中实现,在板文件中,将platform_device归纳为一个数组,最终通过platform_add_devices()函数统一注册。
//platform_add_devices()函数可以将平台设备添加到系统中,这个函数的 原型为:
int platform_add_devices(struct platform_device **devs, int num);
//该函数的第一个参数为平台设备数组的指针,第二个参数为平台设备的数量,它内部调用了platform_device_register()函 数用于注册单个的平台设备。
//linux3.14.78内核
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 ; /* Then try ACPI style match */
if (acpi_driver_match_device(dev, drv))
return ; /* 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) == );
}
Step2,添加设备对象硬件信息,对应i2c_client,i2c_client的信息通常在BSP的板文件中通过i2c_board_info填充,在系统启动之处静态地进行i2c设备注册(设备注册分为动态发现注册和静态注册)下面的代码定义了一个I2C设备的ID为“24c08”,地址为0x50的i2c_client,使用i2c_register_board_info将所有注册i2c_board_info对象添加到一个名为__i2c_board_list的链表上,具体实现在mach_s3c2440.c中调用i2c_register_board_info,详见下面代码;
static struct i2c_board_info i2c_devices[] __initdata = {
{I2C_BOARD_INFO("24c08", 0x50), },
};
static void __init My2440_machine_init(void)
{
s3c24xx_fb_set_platdata(&My2440_fb_info);
s3c_i2c0_set_platdata(NULL);
i2c_register_board_info(, i2c_devices, ARRAY_SIZE(i2c_devices)); s3c_device_spi0.dev.platform_data= &s3c2410_spi0_platdata;
spi_register_board_info(s3c2410_spi0_board, ARRAY_SIZE(s3c2410_spi0_board));
s3c_device_spi1.dev.platform_data= &s3c2410_spi1_platdata;
spi_register_board_info(s3c2410_spi1_board, ARRAY_SIZE(s3c2410_spi1_board));
s3c_device_nand.dev.platform_data = &My2440_nand_info;
s3c_device_sdi.dev.platform_data = &My2440_mmc_cfg; platform_add_devices(My2440_devices, ARRAY_SIZE(My2440_devices));
} MACHINE_START(MY2440, "MY2440")
/* Maintainer: Ben Dooks <ben@fluff.org> */
.phys_io = S3C2410_PA_UART,
.io_pg_offst = (((u32)S3C24XX_VA_UART) >> ) & 0xfffc,
.boot_params = S3C2410_SDRAM_PA + 0x100, .init_irq = s3c24xx_init_irq,
.map_io = My2440_map_io,
.init_machine = My2440_machine_init,
.timer = &s3c24xx_timer,
MACHINE_END
Step3,概念理清楚,i2c_adapter对应物理上的一个适配器,i2c_client对应真实的物理设备,每个i2c都需要一个i2c_client来描述,i2c_client依附于i2c_adapter,由于一个适配器可以连接多个I2C设备,所以一个i2c_adapter可以被多个i2c_client所依附;而i2c_driver对应与一套驱动方法,他们之间的数据结构之间的关系见下图。在系统启动或者模块加载时,i2c适配器设备驱动i2cdev_driver被添加到系统中,具体实现在drivers/i2c/i2c-dev.c中,现在分析它的实现,在i2c-dev.c中,定义了一个名为i2c_dev_init()的初始化函数,从module_init(i2c_dev_init)可以看出,该函数在系统启动或模块加载时执行,主要完成3种操作:
first. 调用register_chrdev(I2C_MAJOR, "i2c", &i2cdev_fops),为I2C适配器注册主设备号为I2C_MAJOR(89)、次设备号为0~255、文件操作集合为i2cdev_fops的字符设备,也就是针对每个I2C适配器生成一个主设备号为89的设备文件,实现了i2c_driver的成员函数以及文件操作接口;
second. 调用class_create(THIS_MODULE, "i2c-dev")注册名为“i2c-dev”的设备类,然后又注册了一个i2cdev_notifier;
Third. 调用i2c_for_each_dev(NULL, i2cdev_attach_adapter)遍历i2c_bus_type总线上的设备,对找到的设备执行i2cdev_attach_adapter()函数,它首先调用get_free_i2c_dev()分配并初始化一个struct i2c_dev结构,使i2c_dev->adap指向操作的adapter之后,该i2c_dev会被插入到连边i2c_dev_list中,再创建一个device,即绑定adapter并在/dev/目录下创建字符设备节点;
static int __init i2c_dev_init(void)
{
int res;
printk(KERN_INFO "i2c /dev entries driver\n");
res = register_chrdev(I2C_MAJOR, "i2c", &i2cdev_fops);
if (res)
goto out;
i2c_dev_class = class_create(THIS_MODULE, "i2c-dev");
if (IS_ERR(i2c_dev_class)) {
res = PTR_ERR(i2c_dev_class);
goto out_unreg_chrdev;
}
i2c_dev_class->dev_groups = i2c_groups;
/* Keep track of adapters which will be added or removed later */
res = bus_register_notifier(&i2c_bus_type, &i2cdev_notifier);
if (res)
goto out_unreg_class;
/* Bind to already existing adapters right away */
i2c_for_each_dev(NULL, i2cdev_attach_adapter);
return ;
out_unreg_class:
class_destroy(i2c_dev_class);
out_unreg_chrdev:
unregister_chrdev(I2C_MAJOR, "i2c");
out:
printk(KERN_ERR "%s: Driver Initialisation failed\n", __FILE__);
return res;
}
Step4,添加i2c总线驱动(I2C适配器驱动的注册),由于I2C总线控制器通常是在内存上的,所以它本身也连接在platform总线上,要通过platform_driver和platform_device的匹配来执行。尽管I2C适配器给别人提供了总线,它自己也是连接在platform总线上的一个客户。在文件drivers/i2c/busses/i2c-s3c2410.c中,platform_driver的注册通过调用初始化函数i2c_adapter_s3c_init函数来完成,与I2C适配器所对应的platform_driver的probe函数主要完成以下两个工作:
1、初始化I2C适配器所使用的硬件资源,如申请I/O地址、中断号、时钟等;2、通过i2c_add_numbered_adapter()添加i2c_adapter的数据结构,当然这个i2c_adapter数据结构的成员已经被对应的适配器的相应函数指针所初始化具体实现
i2c_add_numbered_adapter(&i2c->adap)-->i2c_register_adapter(adap)-->i2c_scan_static_board_info(adap)-->list_for_each_entry(devinfo, &__i2c_board_list, list)-->i2c_new_device(adapter,&devinfo->board_info)),以adapter结构体和找到的devinfo结构体中的i2c_board_info结构体为参数在i2c_bus_type总线上添加client设备,即添加i2c_client.
static int s3c24xx_i2c_probe(struct platform_device *pdev)
{
struct s3c24xx_i2c *i2c;
struct s3c2410_platform_i2c *pdata = NULL;
struct resource *res;
int ret;
if (!pdev->dev.of_node) {
pdata = dev_get_platdata(&pdev->dev);
if (!pdata) {
dev_err(&pdev->dev, "no platform data\n");
return -EINVAL;
}
}
i2c = devm_kzalloc(&pdev->dev, sizeof(struct s3c24xx_i2c), GFP_KERNEL);
if (!i2c) {
dev_err(&pdev->dev, "no memory for state\n");
return -ENOMEM;
}
i2c->pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
if (!i2c->pdata) {
dev_err(&pdev->dev, "no memory for platform data\n");
return -ENOMEM;
}
i2c->quirks = s3c24xx_get_device_quirks(pdev);
if (pdata)
memcpy(i2c->pdata, pdata, sizeof(*pdata));
else
s3c24xx_i2c_parse_dt(pdev->dev.of_node, i2c);
strlcpy(i2c->adap.name, "s3c2410-i2c", sizeof(i2c->adap.name));
i2c->adap.owner = THIS_MODULE;
i2c->adap.algo = &s3c24xx_i2c_algorithm; //设置适配器的通信方法 32
i2c->adap.retries = ;
i2c->adap.class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
i2c->tx_setup = ;
init_waitqueue_head(&i2c->wait);
/* find the clock and enable it */
i2c->dev = &pdev->dev;
i2c->clk = devm_clk_get(&pdev->dev, "i2c");
if (IS_ERR(i2c->clk)) {
dev_err(&pdev->dev, "cannot get clock\n");
return -ENOENT;
}
dev_dbg(&pdev->dev, "clock source %p\n", i2c->clk);
/* map the registers */
res = platform_get_resource(pdev, IORESOURCE_MEM, );
i2c->regs = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(i2c->regs))
return PTR_ERR(i2c->regs);
dev_dbg(&pdev->dev, "registers %p (%p)\n",
i2c->regs, res);
/* setup info block for the i2c core ,将s3c24xx_i2c类型的对象I2C作为通信方法的私有数据存放在algo_data中,
这样master_xfer等方法就能通过适配器的algo_data获得这个对象
*/
i2c->adap.algo_data = i2c;
i2c->adap.dev.parent = &pdev->dev;
i2c->pctrl = devm_pinctrl_get_select_default(i2c->dev);
/* inititalise the i2c gpio lines */
if (i2c->pdata->cfg_gpio) {
i2c->pdata->cfg_gpio(to_platform_device(i2c->dev));
} else if (IS_ERR(i2c->pctrl) && s3c24xx_i2c_parse_dt_gpio(i2c)) {
return -EINVAL;
}
/* initialise the i2c controller*/
clk_prepare_enable(i2c->clk);
ret = s3c24xx_i2c_init(i2c);
clk_disable(i2c->clk);
if (ret != ) {
dev_err(&pdev->dev, "I2C controller init failed\n");
return ret;
}
/* find the IRQ for this unit (note, this relies on the init call to
70 * ensure no current IRQs pending,获取IRQ资源并注册该中断,s3c24xx_i2c_irq是用来后续传输的中断处理函数
71 */
if (!(i2c->quirks & QUIRK_POLL)) {
i2c->irq = ret = platform_get_irq(pdev, );
if (ret <= ) {
dev_err(&pdev->dev, "cannot find IRQ\n");
clk_unprepare(i2c->clk);
return ret;
}
ret = devm_request_irq(&pdev->dev, i2c->irq, s3c24xx_i2c_irq, ,
dev_name(&pdev->dev), i2c);
if (ret != ) {
dev_err(&pdev->dev, "cannot claim IRQ %d\n", i2c->irq);
clk_unprepare(i2c->clk);
return ret;
}
}
ret = s3c24xx_i2c_register_cpufreq(i2c);
if (ret < ) {
dev_err(&pdev->dev, "failed to register cpufreq notifier\n");
clk_unprepare(i2c->clk);
return ret;
}
/* Note, previous versions of the driver used i2c_add_adapter()
94 * to add the bus at any number. We now pass the bus number via
95 * the platform data, so if unset it will now default to always
96 * being bus 0.
97 */
i2c->adap.nr = i2c->pdata->bus_num;
i2c->adap.dev.of_node = pdev->dev.of_node;
platform_set_drvdata(pdev, i2c);
pm_runtime_enable(&pdev->dev);
ret = i2c_add_numbered_adapter(&i2c->adap); //静态方式添加适配器
if (ret < ) {
dev_err(&pdev->dev, "failed to add bus to i2c core\n");
pm_runtime_disable(&pdev->dev);
s3c24xx_i2c_deregister_cpufreq(i2c);
clk_unprepare(i2c->clk);
return ret;
}
pm_runtime_enable(&i2c->adap.dev);
dev_info(&pdev->dev, "%s: S3C I2C adapter\n", dev_name(&i2c->adap.dev));
return ;
}
Step5,编写i2c设备驱动,这部分工作就完全由驱动工程师来完成了。之前所完成的工作:I2C适配器所在驱动的platform_driver与arch/arm/mach-s3c2440中的platform(或者设备树中的节点)通过platform总线的match()函数导致s3c24xx_i2c_probe的执行,从而完成I2C适配器控制器的注册;而挂载在I2C上面的设备,以陀螺仪MPU6050为对象,它所依附的i2c_driver与arch/arm/mach-s3c2440中的i2c_board_info指向的设备(或设备树中的节点)通过I2C总线的match()函数匹配导致i2c_driver.i2c_probe执行。
设备树信息
i2c@138B0000 {
#address-cells = <>;
#size-cells = <>;
samsung,i2c-sda-delay = <>;
samsung,i2c-max-bus-freq = <>;
pinctrl- = <&i2c5_bus>;
pinctrl-names = "default";
status = "okay"; mpu6050@ {
compatible = "fs4412,mpu6050";
reg = <0x68>;
};
};
//i2c_driver.c
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <asm/uaccess.h>
#include "mpu6050.h" #define DEVICE_MAJOR 665
#define DEVICE_MINOR 0
#define DEV_NUM 1
#define DEVICE_NAME "mpu6050" struct mpu6050_device{
struct cdev i2c_cdev;
struct i2c_client *client;
}; struct mpu6050_device* mpu6050_device;
struct class* cls; dev_t devno;
int ret; MODULE_LICENSE("GPL"); static const struct of_device_id i2c_dt_table[]={ {
.compatible="i2c,mpu6050",
},
{
}, };
static const struct i2c_device_id i2c_id_table[]={
{
"mpu6050",
},
{
}, }; static int mpu6050_read_byte(struct i2c_client* client,char reg)
{ char txbuf[]={reg};
char rxbuf[];
struct i2c_msg msg[]={
{
client->addr,,,txbuf },
{
client->addr,,,rxbuf
} };
ret=i2c_transfer(client->adapter,msg,ARRAY_SIZE(msg));
if(ret<)
{
return -EFAULT;
}
return rxbuf[]; } static int mpu6050_write_byte(struct i2c_client* client,char reg,char val)
{ char txbuf[]={reg,val};
struct i2c_msg msg[]={
{
client->addr,,,txbuf },
};
ret=i2c_transfer(client->adapter,msg,ARRAY_SIZE(msg));
if(ret<)
{
printk("i2c_transfer failed!!\n");
return -EFAULT;
}
return ;
} static int i2c_open(struct inode *inode,struct file* file)
{
return ;
}
static int i2c_release(struct inode *inode,struct file* file)
{
return ;
}
static long i2c_ioctl(struct file* file,unsigned int cmd,unsigned long arg)
{
union mpu6050_data data;
struct i2c_client *client=mpu6050_device->client;
switch (cmd)
{
case GET_ACCEL:
data.accel.x=mpu6050_read_byte(client,ACCEL_XOUT_L);
data.accel.x|=mpu6050_read_byte(client,ACCEL_XOUT_H)<<;
data.accel.y=mpu6050_read_byte(client,ACCEL_YOUT_L);
data.accel.y|=mpu6050_read_byte(client,ACCEL_YOUT_H)<<;
data.accel.z=mpu6050_read_byte(client,ACCEL_ZOUT_L);
data.accel.z|=mpu6050_read_byte(client,ACCEL_ZOUT_H)<<;
break;
case GET_GYRO:
data.gyro.x=mpu6050_read_byte(client,GYRO_XOUT_L);
data.gyro.x|=mpu6050_read_byte(client,GYRO_XOUT_H)<<;
data.gyro.y=mpu6050_read_byte(client,GYRO_YOUT_L);
data.gyro.y|=mpu6050_read_byte(client,GYRO_YOUT_H)<<;
data.gyro.z=mpu6050_read_byte(client,GYRO_ZOUT_L);
data.gyro.z|=mpu6050_read_byte(client,GYRO_ZOUT_H)<<;
break;
case GET_TEMP:
data.temp=mpu6050_read_byte(client,TEMP_OUT_L);
data.temp|=mpu6050_read_byte(client,TEMP_OUT_H)<<;
break;
default :
printk("invalid argument\n");
return -EINVAL; } if(copy_to_user((void*)arg,&data,sizeof(data)))
{
return -EFAULT;
}
return sizeof(data);
} const struct file_operations fops={
.owner=THIS_MODULE,
.open=i2c_open,
.release=i2c_release,
.unlocked_ioctl=i2c_ioctl,
}; int i2c_probe(struct i2c_client* client,const struct i2c_device_id* device_id)
{
printk("i2c_mpu6050 probe!!!\n");
devno=MKDEV(DEVICE_MAJOR,DEVICE_MINOR);
mpu6050_device=kzalloc(sizeof(mpu6050_device),GFP_KERNEL);
if(mpu6050_device==NULL)
{
return -ENOMEM;
}
mpu6050_device->client=client; ret=register_chrdev_region(devno,DEV_NUM,DEVICE_NAME);
if(ret<)
{
printk("register_chrdev_region failed!!!\n");
return -;
} cls=class_create(THIS_MODULE,"I2C");
if(IS_ERR(cls))
{
printk("class:I2C create failed!!!\n");
}
printk("class:I2C create succeed!!!\n");
device_create(cls,NULL,devno,NULL,"MPU6050");
cdev_init(&mpu6050_device->i2c_cdev,&fops);
mpu6050_device->i2c_cdev.owner=THIS_MODULE;
printk("i2c_cdev init succeed!!!\n");
cdev_add(&mpu6050_device->i2c_cdev,devno,DEV_NUM);
printk("i2c_cdev add succeed!!!\n"); mpu6050_write_byte(client,SMPLRT_DIV,0X07);
mpu6050_write_byte(client,CONFIG,0X06);
mpu6050_write_byte(client,GYRO_CONFIG,0XF8);
mpu6050_write_byte(client,ACCEL_CONFIG,0X19);
mpu6050_write_byte(client,PWR_MGMT_1,0X00);
mpu6050_write_byte(client,WHO_AM_I,0x68); return ;
}
int i2c_remove(struct i2c_client*client)
{ device_destroy(cls,devno);
class_destroy(cls);
printk("class_destroy succeed!!!\n");
cdev_del(&mpu6050_device->i2c_cdev);
unregister_chrdev_region(devno,DEV_NUM); kfree(mpu6050_device);
return ;
}
static struct i2c_driver i2c_dr ={ .driver={
.name="mpu6050",
.of_match_table=i2c_dt_table,
},
.id_table=i2c_id_table,
.probe=i2c_probe,
.remove=i2c_remove,
}; module_i2c_driver(i2c_dr);
test.c
#include <stdio.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h> #include "mpu6050.h"
int main(int argc, const char *argv[])
{
int fd;
float temperature;
// unsigned long data;
union mpu6050_data data;
int ret;
fd=open("/dev/MPU6050",O_RDWR);
if(fd<)
{
perror("fail to open MPU6050");
return -;
}
printf("open /dev/MPU6050 succeed!\n");
while()
{ printf("****************data from mpu6050*************************\n");
printf("\n"); ioctl(fd,GET_ACCEL,&data);
printf("accel:x=%-5d,y=%-5d,z=%-5d\n",data.accel.x,data.accel.y,data.accel.z);
printf("\n"); ioctl(fd,GET_GYRO,&data);
printf("gyro :x=%-5d,y=%-5d,z=%-5d\n",data.gyro.x,data.gyro.y,data.gyro.z);
printf("\n"); ioctl(fd,GET_TEMP,&data);
temperature=((float)data.temp)/+36.53;
printf("temperature=%f\n",temperature);
printf("\n"); sleep(); }
实际采集到的数据:
参考文献:1.《深入Linux内核架构》 2.《Linux设备驱动开发详解》