目录
六、I2C总线二级外设驱动开发方法
七、I2C总线二级外设驱动开发之名称匹配
1. i2c_register_board_info
2. i2c_new_device:明确二级外设地址的情况下可用
3. i2c_new_probed_device
八、I2C总线二级外设驱动开发之设备树匹配
六、I2C总线二级外设驱动开发方法
1. 查阅原理图以便得知二级外设挂在哪条I2C总线上、二级外设的身份标识(二级外设自身的地址)
2. 参照platform样式搭建二级外设驱动框架
3. 查询二级外设芯片手册以便得知驱动需要用到的寄存器地址
注意:
(1) 此处寄存器是指二级外设内部的寄存器,每个寄存器在芯片手册里有个对应编号(也被称为地址),但不是内存地址,特别提醒此寄存器不是SOC芯片内部参与内存统一编址的寄存器,更不是ARM核-CPU的寄存器
(2)通过调用i2c_tranfer函数完成与相应寄存器的数据交互
4. 参照字符驱动完成其余代码编写
5. 创建对应的i2c_client对象
linux-3.14\Documentation\i2c\instantiating-devices
匹配方式:
1. 名称匹配(id和name差不多)
2. 设备树匹配
3. ACPI匹配
Advanced Configuration and Power Management Interface 高级配置和电源管理接口
PC机平台采用的一种硬件配置接口
(咱们是arm平台这种方式用不了)
i2c二级外设驱动框架:
```c //其它struct file_operations函数实现原理同硬编驱动 static int mpu6050_probe(struct i2c_client *pclt,const struct i2c_device_id *pid) { //做硬编驱动模块入口函数的活 } static int mpu6050_remove(struct i2c_client *pclt) { //做硬编驱动模块出口函数的活 } /*名称匹配时定义struct i2c_device_id数组*/ static struct i2c_device_id mpu6050_ids = { {"mpu6050",0}, //..... {} }; /*设备树匹配时定义struct of_device_id数组*/ static struct of_device_id mpu6050_dts = { {.compatible = "invensense,mpu6050"}, //.... {} }; /*通过定义struct i2c_driver类型的全局变量来创建i2c_driver对象,同时对其主要成员进行初始化*/ struct i2c_driver mpu6050_driver = { .driver = { .name = "mpu6050", .owner = THIS_MODULE, .of_match_table = mpu6050_dts, }, .probe = mpu6050_probe, .remove = mpu6050_remove, .id_table = mpu6050_ids, }; /*以下其实是个宏,展开后相当于实现了模块入口函数和模块出口函数*/ module_i2c_driver(mpu6050_driver); MODULE_LICENSE("GPL"); ```
七、I2C总线二级外设驱动开发之名称匹配
这种匹配方式需要自己创建i2c_client对象
创建i2c_client对象有三种方式:
1. i2c_register_board_info
1.当开发板上电内核跑起来的时候,肯定是架构相关的程序首先运行,也就是mach-xxx.c
2. mach-xxx.c文件里首先会定义i2c_board_info的结构体数组,在mach-xxx.c的初始化函数里调用i2c_register_board_info函数把i2c_board_inifo链接进内核的i2c_board_list链表当中去
3.在驱动i2c目录下和开发板板对应的驱动文件i2c-xxx.c里,创建i2c_adapter对象
4.这种方式严重依赖平台,缺乏灵活性,基本会被遗弃
2. i2c_new_device:明确二级外设地址的情况下可用
i2c二级外设client框架:
```c #include <linux/kernel.h> #include <linux/module.h> #include <linux/i2c.h> static struct i2c_board_info mpu6050_info = { I2C_BOARD_INFO("mpu6050",二级外设地址) }; static struct i2c_client *mpu6050_client; static int __init mpu6050_dev_init(void) { struct i2c_adapter *padp = NULL; padp = i2c_get_adapter(i2c通道编号); mpu6050_client = i2c_new_device(padp,&mpu6050_info); i2c_put_adapter(padp); return 0; } module_init(mpu6050_dev_init); static void __exit mpu6050_dev_exit(void) { i2c_unregister_device(mpu6050_client); } module_exit(mpu6050_dev_exit); MODULE_LICENSE("GPL"); ```
3. i2c_new_probed_device
i2c二级外设client框架:不明确二级外设地址,但是知道是可能几个值之一的情况下可用
```c #include <linux/kernel.h> #include <linux/module.h> #include <linux/i2c.h> static const unsigned short addr_list[] = { 0x68, //..... I2C_CLIENT_END }; static struct i2c_client *mpu6050_client; static int __init mpu6050_dev_init(void) { struct i2c_adapter *padp = NULL; struct i2c_board_info mpu6050_info = {""}; strcpy(mpu6050_info.type,"mpu6050"); padp = i2c_get_adapter(i2c通道编号); mpu6050_client = i2c_new_probed_device(padp,&mpu6050_info,addr_list,NULL); i2c_put_adapter(padp); if(mpu6050_client != NULL) { return 0; } else { return -ENODEV; } } module_init(mpu6050_dev_init); static void __exit mpu6050_dev_exit(void) { i2c_unregister_device(mpu6050_client); } module_exit(mpu6050_dev_exit); MODULE_LICENSE("GPL"); ```
mpu6050_client.c
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/i2c.h>
static struct i2c_board_info mpu6050_info =
{
I2C_BOARD_INFO("mpu6050",0x68)
};
static struct i2c_client *gpmpu6050_client = NULL;
static int __init mpu6050_client_init(void)
{
struct i2c_adapter *padp = NULL;
padp = i2c_get_adapter(5);
gpmpu6050_client = i2c_new_device(padp,&mpu6050_info);
i2c_put_adapter(padp);
return 0;
}
static void mpu6050_client_exit(void)
{
i2c_unregister_device(gpmpu6050_client);
}
module_init(mpu6050_client_init);
module_exit(mpu6050_client_exit);
MODULE_LICENSE("GPL");
mpu6050_drv.c
#include <linux/module.h> #include <linux/kernel.h> #include <linux/fs.h> #include <linux/i2c.h> #include <linux/cdev.h> #include <linux/wait.h> #include <linux/sched.h> #include <linux/poll.h> #include <linux/slab.h> #include <linux/mm.h> #include <linux/io.h> #include <asm/uaccess.h> #include <asm/atomic.h> #include "mpu6050.h" #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 int major = 11; int minor = 0; int mpu6050_num = 1; struct mpu6050_dev { struct cdev mydev; struct i2c_client *pclt; }; struct mpu6050_dev *pgmydev = NULL; int mpu6050_read_byte(struct i2c_client *pclt,unsigned char reg) { int ret = 0; char txbuf[1] = {reg}; char rxbuf[1] = {0}; struct i2c_msg msg[2] = { {pclt->addr,0,1,txbuf}, {pclt->addr,I2C_M_RD,1,rxbuf} }; ret = i2c_transfer(pclt->adapter,msg,ARRAY_SIZE(msg)); if(ret < 0) { printk("ret = %d,in mpu6050_read_byte\n",ret); return ret; } return rxbuf[0]; } int mpu6050_write_byte(struct i2c_client *pclt,unsigned char reg,unsigned char val) { int ret = 0; char txbuf[2] = {reg,val}; struct i2c_msg msg[1] = { {pclt->addr,0,2,txbuf}, }; ret = i2c_transfer(pclt->adapter,msg,ARRAY_SIZE(msg)); if(ret < 0) { printk("ret = %d,in mpu6050_write_byte\n",ret); return ret; } return 0; } int mpu6050_open(struct inode *pnode,struct file *pfile) { pfile->private_data =(void *) (container_of(pnode->i_cdev,struct mpu6050_dev,mydev)); return 0; } int mpu6050_close(struct inode *pnode,struct file *pfile) { return 0; } long mpu6050_ioctl(struct file *pfile,unsigned int cmd,unsigned long arg) { struct mpu6050_dev *pmydev = (struct mpu6050_dev *)pfile->private_data; union mpu6050_data data; switch(cmd) { case GET_ACCEL: data.accel.x = mpu6050_read_byte(pmydev->pclt,ACCEL_XOUT_L); data.accel.x |= mpu6050_read_byte(pmydev->pclt,ACCEL_XOUT_H) << 8; data.accel.y = mpu6050_read_byte(pmydev->pclt,ACCEL_YOUT_L); data.accel.y |= mpu6050_read_byte(pmydev->pclt,ACCEL_YOUT_H) << 8; data.accel.z = mpu6050_read_byte(pmydev->pclt,ACCEL_ZOUT_L); data.accel.z |= mpu6050_read_byte(pmydev->pclt,ACCEL_ZOUT_H) << 8; break; case GET_GYRO: data.gyro.x = mpu6050_read_byte(pmydev->pclt,GYRO_XOUT_L); data.gyro.x |= mpu6050_read_byte(pmydev->pclt,GYRO_XOUT_H) << 8; data.gyro.y = mpu6050_read_byte(pmydev->pclt,GYRO_YOUT_L); data.gyro.y |= mpu6050_read_byte(pmydev->pclt,GYRO_YOUT_H) << 8; data.gyro.z = mpu6050_read_byte(pmydev->pclt,GYRO_ZOUT_L); data.gyro.z |= mpu6050_read_byte(pmydev->pclt,GYRO_ZOUT_H) << 8; break; case GET_TEMP: data.temp = mpu6050_read_byte(pmydev->pclt,TEMP_OUT_L); data.temp |= mpu6050_read_byte(pmydev->pclt,TEMP_OUT_H) << 8; break; default: return -EINVAL; } if(copy_to_user((void *)arg,&data,sizeof(data))) { return -EFAULT; } return sizeof(data); } void init_mpu6050(struct i2c_client *pclt) { mpu6050_write_byte(pclt,PWR_MGMT_1,0x00); mpu6050_write_byte(pclt,SMPLRT_DIV,0x07); mpu6050_write_byte(pclt,CONFIG,0x06); mpu6050_write_byte(pclt,GYRO_CONFIG,0xF8); mpu6050_write_byte(pclt,ACCEL_CONFIG,0x19); } struct file_operations myops = { .owner = THIS_MODULE, .open = mpu6050_open, .release = mpu6050_close, .unlocked_ioctl = mpu6050_ioctl, }; static int mpu6050_probe(struct i2c_client *pclt,const struct i2c_device_id *pid) { int ret = 0; dev_t devno = MKDEV(major,minor); /*申请设备号*/ ret = register_chrdev_region(devno,mpu6050_num,"mpu6050"); if(ret) { ret = alloc_chrdev_region(&devno,minor,mpu6050_num,"mpu6050"); if(ret) { printk("get devno failed\n"); return -1; } major = MAJOR(devno);//容易遗漏,注意 } pgmydev = (struct mpu6050_dev *)kmalloc(sizeof(struct mpu6050_dev),GFP_KERNEL); if(NULL == pgmydev) { unregister_chrdev_region(devno,mpu6050_num); printk("kmalloc failed\n"); return -1; } memset(pgmydev,0,sizeof(struct mpu6050_dev)); pgmydev->pclt = pclt; /*给struct cdev对象指定操作函数集*/ cdev_init(&pgmydev->mydev,&myops); /*将struct cdev对象添加到内核对应的数据结构里*/ pgmydev->mydev.owner = THIS_MODULE; cdev_add(&pgmydev->mydev,devno,mpu6050_num); init_mpu6050(pgmydev->pclt); return 0; } static int mpu6050_remove(struct i2c_client *pclt) { dev_t devno = MKDEV(major,minor); cdev_del(&pgmydev->mydev); unregister_chrdev_region(devno,mpu6050_num); kfree(pgmydev); pgmydev = NULL; return 0; } struct i2c_device_id mpu6050_ids[] = { {"mpu6050",0}, {} }; struct i2c_driver mpu6050_driver = { .driver = { .name = "mpu6050", .owner = THIS_MODULE, }, .probe = mpu6050_probe, .remove = mpu6050_remove, .id_table = mpu6050_ids, }; #if 0 int __init mpu6050_driver_init(void) { i2c_add_driver(&mpu6050_driver); } void __exit mpu6050_driver_exit(void) { i2c_del_driver(&mpu6050_driver); } module_init(mpu6050_driver_init); module_exit(mpu6050_driver_exit); #else module_i2c_driver(mpu6050_driver); #endif MODULE_LICENSE("GPL");
#ifndef MPU_6050_H
#define MPU_6050_H
struct accel_data
{
unsigned short x;
unsigned short y;
unsigned short z;
};
struct gyro_data
{
unsigned short x;
unsigned short y;
unsigned short z;
};
union mpu6050_data
{
struct accel_data accel;
struct gyro_data gyro;
unsigned short temp;
};
#define MPU6050_MAGIC 'K'
#define GET_ACCEL _IOR(MPU6050_MAGIC,0,union mpu6050_data)
#define GET_GYRO _IOR(MPU6050_MAGIC,1,union mpu6050_data)
#define GET_TEMP _IOR(MPU6050_MAGIC,2,union mpu6050_data)
#endif
使用probed也行
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/i2c.h>
static unsigned short mpu6050_addr_list[] =
{
0x68,
0x69,
I2C_CLIENT_END
};
static struct i2c_client *gpmpu6050_client = NULL;
static int __init mpu6050_client_init(void)
{
struct i2c_adapter *padp = NULL;
struct i2c_board_info mpu6050_info = {""};
strcpy(mpu6050_info.type,"mpu6050");
padp = i2c_get_adapter(5);
gpmpu6050_client = i2c_new_probed_device(padp,&mpu6050_info,mpu6050_addr_list,NULL);
i2c_put_adapter(padp);
if(gpmpu6050_client != NULL)
{
return 0;
}
else
{
return -ENODEV;
}
}
static void mpu6050_client_exit(void)
{
i2c_unregister_device(gpmpu6050_client);
}
module_init(mpu6050_client_init);
module_exit(mpu6050_client_exit);
MODULE_LICENSE("GPL");
测试用的APP
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include "mpu6050.h"
int main(int argc,char *argv[])
{
int fd = -1;
union mpu6050_data data;
if(argc < 2)
{
printf("The argument is too few\n");
return 1;
}
fd = open(argv[1],O_RDONLY);
if(fd < 0)
{
printf("open %s failed \n",argv[1]);
return 2;
}
while(1)
{
sleep(2);
ioctl(fd,GET_ACCEL,&data);
printf("Accel-x=0x%x\n",data.accel.x);
printf("Accel-y=0x%x\n",data.accel.y);
printf("Accel-z=0x%x\n",data.accel.z);
ioctl(fd,GET_GYRO,&data);
printf("Gyro-x=0x%x\n",data.gyro.x);
printf("Gyro-y=0x%x\n",data.gyro.y);
printf("Gyro-z=0x%x\n",data.gyro.z);
ioctl(fd,GET_TEMP,&data);
printf("Temp=0x%x\n",data.temp);
printf("\n");
}
close(fd);
fd = -1;
return 0;
}
八、I2C总线二级外设驱动开发之设备树匹配
0x68是从设备号,interrupt是中断
IIC的实现和前面LED不太一样那个使用设备树匹配不需要ip IIC需要有一个IP。
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/i2c.h>
#include <linux/cdev.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/poll.h>
#include <linux/slab.h>
#include <linux/mm.h>
#include <linux/io.h>
#include <asm/uaccess.h>
#include <asm/atomic.h>
#include "mpu6050.h"
#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
int major = 11;
int minor = 0;
int mpu6050_num = 1;
struct mpu6050_dev
{
struct cdev mydev;
struct i2c_client *pclt;
};
struct mpu6050_dev *pgmydev = NULL;
int mpu6050_read_byte(struct i2c_client *pclt,unsigned char reg)
{
int ret = 0;
char txbuf[1] = {reg};
char rxbuf[1] = {0};
struct i2c_msg msg[2] =
{
{pclt->addr,0,1,txbuf},
{pclt->addr,I2C_M_RD,1,rxbuf}
};
ret = i2c_transfer(pclt->adapter,msg,ARRAY_SIZE(msg));
if(ret < 0)
{
printk("ret = %d,in mpu6050_read_byte\n",ret);
return ret;
}
return rxbuf[0];
}
int mpu6050_write_byte(struct i2c_client *pclt,unsigned char reg,unsigned char val)
{
int ret = 0;
char txbuf[2] = {reg,val};
struct i2c_msg msg[1] =
{
{pclt->addr,0,2,txbuf},
};
ret = i2c_transfer(pclt->adapter,msg,ARRAY_SIZE(msg));
if(ret < 0)
{
printk("ret = %d,in mpu6050_write_byte\n",ret);
return ret;
}
return 0;
}
int mpu6050_open(struct inode *pnode,struct file *pfile)
{
pfile->private_data =(void *) (container_of(pnode->i_cdev,struct mpu6050_dev,mydev));
return 0;
}
int mpu6050_close(struct inode *pnode,struct file *pfile)
{
return 0;
}
long mpu6050_ioctl(struct file *pfile,unsigned int cmd,unsigned long arg)
{
struct mpu6050_dev *pmydev = (struct mpu6050_dev *)pfile->private_data;
union mpu6050_data data;
switch(cmd)
{
case GET_ACCEL:
data.accel.x = mpu6050_read_byte(pmydev->pclt,ACCEL_XOUT_L);
data.accel.x |= mpu6050_read_byte(pmydev->pclt,ACCEL_XOUT_H) << 8;
data.accel.y = mpu6050_read_byte(pmydev->pclt,ACCEL_YOUT_L);
data.accel.y |= mpu6050_read_byte(pmydev->pclt,ACCEL_YOUT_H) << 8;
data.accel.z = mpu6050_read_byte(pmydev->pclt,ACCEL_ZOUT_L);
data.accel.z |= mpu6050_read_byte(pmydev->pclt,ACCEL_ZOUT_H) << 8;
break;
case GET_GYRO:
data.gyro.x = mpu6050_read_byte(pmydev->pclt,GYRO_XOUT_L);
data.gyro.x |= mpu6050_read_byte(pmydev->pclt,GYRO_XOUT_H) << 8;
data.gyro.y = mpu6050_read_byte(pmydev->pclt,GYRO_YOUT_L);
data.gyro.y |= mpu6050_read_byte(pmydev->pclt,GYRO_YOUT_H) << 8;
data.gyro.z = mpu6050_read_byte(pmydev->pclt,GYRO_ZOUT_L);
data.gyro.z |= mpu6050_read_byte(pmydev->pclt,GYRO_ZOUT_H) << 8;
break;
case GET_TEMP:
data.temp = mpu6050_read_byte(pmydev->pclt,TEMP_OUT_L);
data.temp |= mpu6050_read_byte(pmydev->pclt,TEMP_OUT_H) << 8;
break;
default:
return -EINVAL;
}
if(copy_to_user((void *)arg,&data,sizeof(data)))
{
return -EFAULT;
}
return sizeof(data);
}
void init_mpu6050(struct i2c_client *pclt)
{
mpu6050_write_byte(pclt,PWR_MGMT_1,0x00);
mpu6050_write_byte(pclt,SMPLRT_DIV,0x07);
mpu6050_write_byte(pclt,CONFIG,0x06);
mpu6050_write_byte(pclt,GYRO_CONFIG,0xF8);
mpu6050_write_byte(pclt,ACCEL_CONFIG,0x19);
}
struct file_operations myops = {
.owner = THIS_MODULE,
.open = mpu6050_open,
.release = mpu6050_close,
.unlocked_ioctl = mpu6050_ioctl,
};
static int mpu6050_probe(struct i2c_client *pclt,const struct i2c_device_id *pid)
{
int ret = 0;
dev_t devno = MKDEV(major,minor);
/*申请设备号*/
ret = register_chrdev_region(devno,mpu6050_num,"mpu6050");
if(ret)
{
ret = alloc_chrdev_region(&devno,minor,mpu6050_num,"mpu6050");
if(ret)
{
printk("get devno failed\n");
return -1;
}
major = MAJOR(devno);//容易遗漏,注意
}
pgmydev = (struct mpu6050_dev *)kmalloc(sizeof(struct mpu6050_dev),GFP_KERNEL);
if(NULL == pgmydev)
{
unregister_chrdev_region(devno,mpu6050_num);
printk("kmalloc failed\n");
return -1;
}
memset(pgmydev,0,sizeof(struct mpu6050_dev));
pgmydev->pclt = pclt;
/*给struct cdev对象指定操作函数集*/
cdev_init(&pgmydev->mydev,&myops);
/*将struct cdev对象添加到内核对应的数据结构里*/
pgmydev->mydev.owner = THIS_MODULE;
cdev_add(&pgmydev->mydev,devno,mpu6050_num);
init_mpu6050(pgmydev->pclt);
return 0;
}
static int mpu6050_remove(struct i2c_client *pclt)
{
dev_t devno = MKDEV(major,minor);
cdev_del(&pgmydev->mydev);
unregister_chrdev_region(devno,mpu6050_num);
kfree(pgmydev);
pgmydev = NULL;
return 0;
}
struct of_device_id mpu6050_dt[] =
{
{.compatible = "invensense,mpu6050"},
{}
};
struct i2c_device_id mpu6050_ids[] =
{
{"mpu6050",0},
{}
};
struct i2c_driver mpu6050_driver =
{
.driver = {
.name = "mpu6050",
.owner = THIS_MODULE,
.of_match_table = mpu6050_dt,
},
.probe = mpu6050_probe,
.remove = mpu6050_remove,
.id_table = mpu6050_ids,
};
#if 0
int __init mpu6050_driver_init(void)
{
i2c_add_driver(&mpu6050_driver);
}
void __exit mpu6050_driver_exit(void)
{
i2c_del_driver(&mpu6050_driver);
}
module_init(mpu6050_driver_init);
module_exit(mpu6050_driver_exit);
#else
module_i2c_driver(mpu6050_driver);
#endif
MODULE_LICENSE("GPL");
测试程序和.h程序和上面一样。