LV.12 嵌入式系统驱动高级

D1 设备模型

硬编驱动:没有设备树时,硬件资源资源信息和源代码混合在一起

Linux 2.6内核之前是借助devfs 1硬编
Linux 2.6内核之后是总线2驱动式开发
Linux 3.0内核开始支持设备树。把很多外设信息记录到设备树,相同驱动代码可以复用,硬编仍是基础。

一、起源

仅devfs,导致开发不方便以及一些功能难以支持:

  1. 热插拔

    1. 不支持一些针对所有设备的统一操作(如电源管理)
    2. 不能自动mknod
    3. 用户查看不了设备信息
    4. 设备信息硬编码,导致驱动代码通用性差,即没有分离设备和驱动

二、新方案

内核 2.6 之后
devices + driver
uevent机制:sysfs + uevent + udevd(通用计算机上常叫udevd 其他也叫 mdevd上层app)

检测到设备接入后,通过uevent 向udevd ,udevd 找对应驱动,然后载入

总结:
外存中,文件系统部分提供 /sys 目录 维护众多的 devices + driver
内核中,把驱动分成 devices driver 用相应总线管理

2.1 sysfs: 一种用内存模拟的文件系统,系统启动时mount到/sys目录

sysfs用途:(类似于windows的设备管理器,把系统用到的设备进行多种方式的分类组织)

  1. 建立系统中总线(硬件上进行通信,软件上devices 和 driver 的管理者)、驱动、设备三者之间的桥梁
  2. 向用户空间展示内核中各种设备的拓扑图
  3. 提供给用户空间对设备获取信息和操作的接口,部分取代ioctl功能
sysfs在内核中的组成要素在用户空间/sys下的显示
内核对象(kobject)目录
对象属性(attribute)文件
对象关系(relationship)链接(Symbolic Link)

devise 和 driver 都是内核对象
不同分类方式用软链接链接
四个基本结构

类型所包含的内容内核数据结构对应/sys项
设备(Devices)设备是此模型中最基本的类型,以设备本身的连接按层次组织struct device/sys/devices/?/?/…/
驱动(Drivers)在一个系统中安装多个相同设备,只需要一份驱动程序的支持struct device_driver/sys/bus/pci/drivers/?/
总线(Bus)在整个总线级别对此总线上连接的所有设备进行管理struct bus_type/sys/bus/?/
类别(Classes)这是按照功能进行分类组织的设备层次树;如 USB 接口和 PS/2 接口的鼠标都是输入设备,都会出现在/sys/class/input/下struct class/sys/class/?/

目录组织结构:

/sys下的子目录所包含的内容
/sys/devices这是内核对系统中所有设备的分层次表达模型,也是/sys文件系统管理设备的最重要的目录结构;
/sys/dev这个目录下维护一个按字符设备和块设备的主次号码(major:minor)链接到真实的设备(/sys/devices下)的符号链接文件;
/sys/bus这是内核设备按总线类型分层放置的目录结构, devices 中的所有设备都是连接于某种总线之下,在这里的每一种具体总线之下可以找到每一个具体设备的符号链接,它也是构成 Linux 统一设备模型的一部分;
/sys/class这是按照设备功能分类的设备模型,如系统所有输入设备都会出现在/sys/class/input 之下,而不论它们是以何种总线连接到系统。它也是构成 Linux 统一设备模型的一部分;
/sys/kernel这里是内核所有可调整参数的位置,目前只有 uevent_helper, kexec_loaded, mm, 和新式的slab 分配器等几项较新的设计在使用它,其它内核可调整参数仍然位于sysctl(/proc/sys/kernel) 接口中;
/sys/module这里有系统中所有模块的信息,不论这些模块是以内联(inlined)方式编译到内核映像文件(vmlinuz)中还是编译为外部模块(ko文件),都可能会出现在/sys/module 中
/sys/power这里是系统中电源选项,这个目录下有几个属性文件可以用于控制整个机器的电源状态,如可以向其中写入控制命令让机器关机、重启等。

2.2 uevent

解决热插拔,内核跟应用层的一种通信机制
SOC架构图

三、代码中自动mknod

struct class *class_create(struct module *owner, const char *name);
/*
 * 功能:在/sys/class生成一个目录,目录名由name指定
 * 参数:
	struct module *owner - THIS_MODULE
	const char *name - 目录名
 * 返回值  成功:class指针   失败:NULL
*/
/*
辅助接口:可以定义一个struct class 的指针变量cls来接受返回值,然后通过IS_ERR(cls)判断是否失败;
IS_ERR(cls);成功----------------->0
IS_ERR(cls);失败----------------->非0
PTR_ERR(cls);来获得失败的返回错误码;
*/
void class_destroy(struct class *cls)
/*
* 功能:删除class_create生成目录
* 参数:
 	struct class *cls - class指针
* 返回值
*/
struct device *device_create(struct class *class, struct device *parent,
			     dev_t devt, void *drvdata, const char *fmt, ...)
/*
 * 功能:在/sys/class目录下class_create生成目录再生成一个子目录与该设备相对应,发uevent让应用程序udevd创建设备文件
 * 参数:
 	struct class *class - class指针
 	struct device *parent - 父对象,一般NULL
 	dev_t devt - 设备号
 	void *drvdata - 驱动私有数据,一般NULL
 	const char *fmt - 字符串的格式
 	 ... - 不定参数
 * 返回值
 	成功:device指针
 	失败:NULL
 */
void device_destroy(struct class *class, dev_t devt)
/*
 * 功能:删除device_create生成目录
 * 参数:
 	struct class *class - class指针
 	dev_t devt - 设备号
 * 返回值
*/

D2 平台总线式驱动开发上_基础框架

一、总线、设备、驱动

硬编码式的驱动开发带来的问题:

  1. 垃圾代码太多
  2. 结构不清晰
  3. 一些统一设备功能难以支持
  4. 开发效率低下

1.1 初期解决思路:设备和驱动分离

在这里插入图片描述
把系统总线假想成平台
​ struct device来表示一个具体设备,主要提供具体设备相关的资源(如寄存器地址、GPIO、中断等等)
​ struct device_driver来表示一个设备驱动,一个驱动可以支持多个操作逻辑相同的设备
​ 带来的问题-------怎样将二者进行关联(匹配)?
​ 硬件上同一总线上的设备遵循一致的时序通信,在其基础上增加管理设备和驱动的软件功能
​ 于是引入总线(bus),各种总线的核心框架由内核来实现,通信时序一般由SOC供应商支持
​ 内核中用struct bus_type来表示一种总线,总线可以是实际存在的总线,也可以是虚拟总线:

  1. 实际总线:提供时序通信方式 + 管理设备和驱动
  2. 虚拟总线:仅用来管理设备和驱动(最核心的作用之一就是完成设备和驱动的匹配)

理解方式:

设备:提供硬件资源——男方
驱动:提供驱动代码——女方
总线:匹配设备和驱动——婚介所:提供沟通机制,完成拉郎配

1.2 升级思路:根据设备树,在系统启动时自动产生每个节点对应的设备

初期方案,各种device需要编码方式注册进内核中的设备管理结构中,为了进一步减少这样的编码,引进设备树

二、基本数据类型

2.1 struct device

struct device 
{
	struct bus_type	*bus;	//总线类型
	dev_t			devt;	//设备号
	struct device_driver *driver;	//设备驱动
    struct device_node  *of_node;//设备树中的节点,重要
	void	(*release)(struct device *dev);//删除设备,重要
    //.......
}

2.2 struct device_driver

struct device_driver 
{
	const char		*name;	//驱动名称,匹配device用,重要
	struct bus_type	*bus;    //总线类型
	struct module		*owner;	//模块THIS_MODULE 
	const struct of_device_id	*of_match_table;//用于设备树匹配 of_match_ptr(某struct of_device_id对象地址) 重要
    //......
};
struct of_device_id
{
	char name[32];//设备名
	char type[32];//设备类型
	char compatible[128]; //用于device和driver的match,重点
};
//用到结构体数组,一般不指定大小,初始化时最后加{}表示数组结束

三、platform总线驱动

platform是一种虚拟总线,主要用来管理那些不需要时序通信的设备

基本结构图:
platform基本结构

3.1 核心数据类型之platform_device

struct platform_device 
{
    const char    *name;    //匹配用的名字
    int        id;//设备id,用于在该总线上同名的设备进行编号,如果只有一个设备,则为-1
    struct device    dev;   //设备模块必须包含该结构体
    struct resource    *resource;//资源结构体 指向资源数组
    u32        num_resources;//资源的数量 资源数组的元素个数
    const struct platform_device_id    *id_entry;//设备八字
};
struct platform_device_id
{
	char name[20];//匹配用名称
	kernel_ulong_t driver_data;//需要向驱动传输的其它数据
};
struct resource 
{
	resource_size_t start;  //资源起始位置   
	resource_size_t end;   //资源结束位置
	const char *name;      
	unsigned long flags;   //区分资源是什么类型的
};
 
#define IORESOURCE_MEM        0x00000200
#define IORESOURCE_IRQ        0x00000400 
/*
flags 指资源类型,我们常用的是 IORESOURCE_MEM、IORESOURCE_IRQ  这两种。start 和 end 的含义会随着 flags而变更,如

a -- flags为IORESOURCE_MEM 时,start 、end 分别表示该platform_device占据的内存的开始地址和结束值;注意不同MEM的地址值不能重叠

b -- flags为 IORESOURCE_IRQ   时,start 、end 分别表示该platform_device使用的中断号的开始地址和结束值
*/
/**
 *注册:把指定设备添加到内核中平台总线的设备列表,等待匹配,匹配成功则回调驱动中probe;
 */
int platform_device_register(struct platform_device *);
/**
 *注销:把指定设备从设备列表中删除,如果驱动已匹配则回调驱动方法和设备信息中的release;
 */
void platform_device_unregister(struct platform_device *);
struct resource *platform_get_resource(struct platform_device *dev,unsigned int type, unsigned int num);
/*
	功能:获取设备资源
	参数:dev:平台驱动
		type:获取的资源类型
		num:对应类型资源的序号(如第0个MEM、第2个IRQ等,不是数组下标)
	返回值:成功:资源结构体首地址,失败:NULL
*/

3.2 核心数据类型之platform_driver

struct platform_driver 
{
    int (*probe)(struct platform_device *);//设备和驱动匹配成功之后调用该函数
    int (*remove)(struct platform_device *);//设备卸载了调用该函数
    
    void (*shutdown)(struct platform_device *);//关机时调用
    int (*suspend)(struct platform_device *, pm_message_t state);//休眠时调用
    int (*resume)(struct platform_device *);//唤醒时调用
    struct device_driver driver;//内核里所有的驱动必须包含该结构体,跟devices 中的name进行匹配
    const struct platform_device_id *id_table;  //能够支持的设备八字数组,用到结构体数组,一般不指定大小,初始化时最后加{}表示数组结束。有 成员,有跟devices 中的idn成员匹配
};
int platform_driver_register(struct platform_driver*pdrv);
/*
	功能:注册平台设备驱动
	参数:pdrv:平台设备驱动结构体
	返回值:成功:0
	失败:错误码
*/
void platform_driver_unregister(struct platform_driver*pdrv);

四、platform的三种匹配方式

driver和device

2.1 名称匹配:一个驱动只对应一个设备 ----- 优先级最低

2.2 id匹配(可想象成八字匹配):一个驱动可以对应多个设备 ------优先级次低

​ device模块中,id的name成员必须与struct platform_device中的name成员内容一致

​ 因此device模块中,struct platform_device中的name成员必须指定

​ driver模块中,struct platform_driver成员driver的name成员必须指定,但与device模块中name可以不相同

2.3 设备树匹配:内核启动时根据设备树自动产生的设备 ------ 优先级最高

使用compatible属性进行匹配,注意设备树中compatible属性值不要包含空白字符

​ id_table可不设置,但struct platform_driver成员driver的name成员必须设置

五、名称匹配之基础框架

/*platform device框架*/
#include <linux/module.h> 
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/platform_device.h>

//定义资源数组

static void device_release(struct device *dev)
{
	printk("platform: device release\n");
}

struct platform_device test_device = {
	.id = -1,
	.name = "test_device",//必须初始化
	.dev.release = device_release, 
};

static int __init platform_device_init(void)
{
	platform_device_register(&test_device);
	return 0;
}

static void __exit platform_device_exit(void)
{
	platform_device_unregister(&test_device);
}

module_init(platform_device_init);
module_exit(platform_device_exit);
MODULE_LICENSE("Dual BSD/GPL");
/*platform driver框架*/
#include <linux/module.h> 
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/platform_device.h>

static int driver_probe(struct platform_device *dev)
{
	printk("platform: match ok!\n");
	return 0;
}

static int driver_remove(struct platform_device *dev)
{
	printk("platform: driver remove\n");
	return 0;
}

struct platform_driver test_driver = {
	.probe = driver_probe,
	.remove = driver_remove,
	.driver = {
		.name = "test_device", //必须初始化
	},
};

static int __init platform_driver_init(void)
{
	platform_driver_register(&test_driver);
	return 0;
}

static void __exit platform_driver_exit(void)
{
	platform_driver_unregister(&test_driver);
}

module_init(platform_driver_init);
module_exit(platform_driver_exit);
MODULE_LICENSE("Dual BSD/GPL");

设备中增加资源,驱动中访问资源

六、名称匹配之led实例

platform_device.h

#ifndef LED_DRIVER_H
#define LED_DRIVER_H
#define LED_DEV_MAGIC 'g'


#define MY_LED_OFF _IO(LED_DEV_MAGIC,0)
#define MY_LED_ON _IO(LED_DEV_MAGIC,1)

#endif

s4412leds_device.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>

#define GPX1CON 0x11000C20
#define GPX1DAT 0x11000C24

#define GPX2CON 0x11000C40
#define GPX2DAT 0x11000C44
#define GPX3CON 0x114001E0
#define GPX3DAT 0x114001E4

void fs4412leds_dev_release(struct device *pdev)
{
	printk("fs4412leds_dev_release is called\n");
}

struct resource fs4412leds_dev_res[] = {
	[0] = {.start = GPX1CON,.end = GPX1CON+3,.name = "GPX1CON",.flags = IORESOURCE_MEM},
	[1] = {.start = GPX1DAT,.end = GPX1DAT+3,.name = "GPX1DAT",.flags = IORESOURCE_MEM},
	[2] = {.start = GPX2CON,.end = GPX2CON+3,.name = "GPX2CON",.flags = IORESOURCE_MEM},
	[3] = {.start = GPX2DAT,.end = GPX2DAT+3,.name = "GPX2DAT",.flags = IORESOURCE_MEM},
	[4] = {.start = GPX3CON,.end = GPX3CON+3,.name = "GPX3CON",.flags = IORESOURCE_MEM},
	[5] = {.start = GPX3DAT,.end = GPX3DAT+3,.name = "GPX3DAT",.flags = IORESOURCE_MEM},
};
struct platform_device fs4412leds_device = {
	.name = "fs4412leds",
	.dev.release = fs4412leds_dev_release,
	.resource = fs4412leds_dev_res,
	.num_resources = ARRAY_SIZE(fs4412leds_dev_res),

};


int __init fs4412leds_device_init(void)
{
	platform_device_register(&fs4412leds_device);
	return 0;
}

void __exit fs4412leds_device_exit(void)
{
	platform_device_unregister(&fs4412leds_device);
}


MODULE_LICENSE("GPL");
module_init(fs4412leds_device_init);
module_exit(fs4412leds_device_exit);


fs4412leds_driver.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>                                                                 
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/poll.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include "fs4412leds.h"
#define BUF_LEN 100

int major = 11;
int minor = 0 ;
int fs4412leds_num = 1;

typedef struct{
	struct cdev mydev;

	volatile unsigned long *pled2_con;
	volatile unsigned long *pled2_dat;

	volatile unsigned long *pled3_con;
	volatile unsigned long *pled3_dat;

	volatile unsigned long *pled4_con;
	volatile unsigned long *pled4_dat;

	volatile unsigned long *pled5_con;
	volatile unsigned long *pled5_dat;

}MYDEV;
MYDEV *pgmydev = NULL;

int fs4412leds_open(struct inode *pnode,struct file *pfile){
	pfile->private_data = (void*)container_of(pnode->i_cdev,MYDEV,mydev);
	printk("fs4412leds_open is called\n");
	return 0 ;
}


int fs4412leds_close(struct inode *pnode,struct file *pfile){
	
	printk("fs4412leds_open is close\n");
	return 0 ;
}


void led_on(MYDEV *pmydev, int ledno){
	switch(ledno)
	{
		case 2:
	writel(readl(pmydev->pled2_dat) | ((0x1 << 7)),pmydev->pled2_dat);
			break;
		case 3:
	writel(readl(pmydev->pled3_dat) | ((0x1 )),pmydev->pled3_dat);
			break;
		case 4:
	writel(readl(pmydev->pled4_dat) | ((0x1 << 4)),pmydev->pled4_dat);
			break;
		case 5:
	writel(readl(pmydev->pled5_dat) | ((0x1 << 5)),pmydev->pled5_dat);
			break;
	}
}
		
void led_off(MYDEV *pmydev, int ledno){
	switch(ledno)
	{
		case 2:
	writel(readl(pmydev->pled2_dat) & (~(0x1 << 7)),pmydev->pled2_dat);
			break;
		case 3:
	writel(readl(pmydev->pled3_dat) & (~(0x1 )),pmydev->pled3_dat);
			break;
		case 4:
	writel(readl(pmydev->pled4_dat) & (~(0x1 << 4)),pmydev->pled4_dat);
			break;
		case 5:
	writel(readl(pmydev->pled5_dat) & (~(0x1 << 5)),pmydev->pled5_dat);
			break;
	}
}


long fs4412leds_ioctl(struct file *pfile,unsigned int cmd ,unsigned long arg){
	MYDEV *pmydev = (MYDEV*)pfile->private_data;
	if(arg < 2 || arg >5){
		return -1;
	}


	switch(cmd){
	case MY_LED_ON:
		led_on(pmydev,arg);
		break;
	case MY_LED_OFF:
		led_off(pmydev,arg);
		break;
	default:
		printk("The CMD is uknow\n");
		return -1;
	}
	return 0;
}


struct file_operations myops = {
	//.owner = THIS_MODULE,//内核在插入模块的时候,在内核中生成的struct module 对象的地址
	.open = fs4412leds_open,
	.release = fs4412leds_close,
	.unlocked_ioctl = fs4412leds_ioctl,
};

void ioremap_ledreg(MYDEV *pmydev,struct platform_device *p_pltdev){

	struct resource *pres = NULL;

	pres = platform_get_resource(p_pltdev,IORESOURCE_MEM,2);
	pmydev->pled2_con = ioremap(pres->start,4);
	pres = platform_get_resource(p_pltdev,IORESOURCE_MEM,3);
	pmydev->pled2_dat = ioremap(pres->start,4);

	pres = platform_get_resource(p_pltdev,IORESOURCE_MEM,0);
	pmydev->pled3_con = ioremap(pres->start,4);
	pres = platform_get_resource(p_pltdev,IORESOURCE_MEM,1);
	pmydev->pled3_dat = ioremap(pres->start,4);

	pres = platform_get_resource(p_pltdev,IORESOURCE_MEM,4);
	pmydev->pled4_con = ioremap(pres->start,4);
	pres = platform_get_resource(p_pltdev,IORESOURCE_MEM,5);
	pmydev->pled4_dat = ioremap(pres->start,4);

	pmydev->pled5_con = pmydev->pled4_con;
	pmydev->pled5_dat = pmydev->pled4_dat;
	

}
void set_output_ledconreg(MYDEV *pmydev){
	writel((readl(pmydev->pled2_con) & (~(0xF << 28))) | (0x1 << 28),pmydev->pled2_con);
	writel((readl(pmydev->pled3_con) & (~(0xF ))) | (0x1 ),pmydev->pled3_con);
	writel((readl(pmydev->pled4_con) & (~(0xF << 16))) | (0x1 << 16),pmydev->pled4_con);
	writel((readl(pmydev->pled5_con) & (~(0xF << 20))) | (0x1 << 20),pmydev->pled5_con);

	writel(readl(pmydev->pled2_dat) & (~(0x1 << 7)),pmydev->pled2_dat);
	writel(readl(pmydev->pled3_dat) & (~(0x1 )),pmydev->pled3_dat);
	writel(readl(pmydev->pled4_dat) & (~(0x1 << 4)),pmydev->pled4_dat);
	writel(readl(pmydev->pled5_dat) & (~(0x1 << 5)),pmydev->pled5_dat);

}
void iounmap_ledreg(MYDEV *pmydev){
	iounmap(pmydev->pled2_con);
	pmydev->pled2_con = NULL;
	iounmap(pmydev->pled2_dat);
	pmydev->pled2_dat = NULL;

	iounmap(pmydev->pled3_con);
	pmydev->pled3_con = NULL;
	iounmap(pmydev->pled3_dat);
	pmydev->pled3_dat = NULL;

	iounmap(pmydev->pled4_con);
	pmydev->pled4_con = NULL;
	iounmap(pmydev->pled4_dat);
	pmydev->pled4_dat = NULL;

	pmydev->pled5_con = NULL;
	pmydev->pled5_dat = NULL;

}

int fs4412leds_driver_probe(struct platform_device *p_pltdev)
{

	int ret = 0;
	dev_t devno = MKDEV(major,minor);

	ret = register_chrdev_region(devno,fs4412leds_num,"fs4412leds1");

	if(ret){
		ret = alloc_chrdev_region(&devno,minor,fs4412leds_num,"fs4412leds1");
		if(ret){
			printk("get devno failed\n");
			return -1;
		}
		major = MAJOR(devno);//分离出自动分配的主设备号
	}

	//使用kmalloc方式动态分配内存
	pgmydev = (MYDEV*)kmalloc(sizeof(MYDEV),GFP_KERNEL);
	if(NULL == pgmydev){
		unregister_chrdev_region(devno,fs4412leds_num);
		printk("kmalloc failed");
		return -1;
	}


	//初始设备操作函数集
	cdev_init(&pgmydev->mydev,&myops);
	//将struct cdev 对象添加到内核对应的数据结构里
	pgmydev->mydev.owner = THIS_MODULE;//推测不必要
	cdev_add(&pgmydev->mydev,devno,fs4412leds_num);

	//ioremap
	ioremap_ledreg(pgmydev,p_pltdev);	
	//con-register set output
	set_output_ledconreg(pgmydev);
	printk("fs4412leds will init\n");

	return 0;
}

int fs4412leds_driver_remove(struct platform_device *p_pltdev)
{
	dev_t devno = MKDEV(major,minor);

	//iounmap
	iounmap_ledreg(pgmydev);

	cdev_del(&pgmydev->mydev);
	unregister_chrdev_region(devno,fs4412leds_num);

	kfree(pgmydev);

	printk("fs4412leds will exit\n");
	return 0;
}

struct platform_driver my_driver = 
{
     .driver.name = "fs4412leds",                                                                        
     .probe  = fs4412leds_driver_probe,
     .remove = fs4412leds_driver_remove,

};

int __init fs4412leds_init(void)
{
     platform_driver_register(&my_driver);
     return 0;

}
void __exit fs4412leds_exit(void)
{
	  platform_driver_unregister(&my_driver);

}


MODULE_LICENSE("GPL");

module_init(fs4412leds_init);
module_exit(fs4412leds_exit);

D3 平台总线式驱动开发下_ID匹配和设备树匹配

一、ID匹配之框架代码

id匹配(可想象成八字匹配):一个驱动可以对应多个设备 ------优先级次低

注意事项:

  1. device模块中,id的name成员必须与struct platform_device中的name成员内容一致,因此device模块中,struct platform_device中的name成员必须指定
  2. driver模块中,struct platform_driver成员driver的name成员必须指定,但与device模块中name可以不相同
/*platform device框架*/
#include <linux/module.h> 
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/platform_device.h>

//定义资源数组

static void device_release(struct device *dev)
{
	printk("platform: device release\n");
}

struct platform_device_id test_id = {
    .name = "test_device",
};

struct platform_device test_device = {
	.name = "test_device",//必须初始化
	.dev.release = device_release, 
    .id_entry = &test_id,
};

static int __init platform_device_init(void)
{
	platform_device_register(&test_device);
	return 0;
}

static void __exit platform_device_exit(void)
{
	platform_device_unregister(&test_device);
}

module_init(platform_device_init);
module_exit(platform_device_exit);
MODULE_LICENSE("Dual BSD/GPL");
/*platform driver框架*/
#include <linux/module.h> 
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/platform_device.h>

static int driver_probe(struct platform_device *dev)
{
	printk("platform: match ok!\n");
	return 0;
}

static int driver_remove(struct platform_device *dev)
{
	printk("platform: driver remove\n");
	return 0;
}

struct platform_device_id testdrv_ids[] = 
{
	[0] = {.name = "test_device"},
    [1] = {.name = "abcxyz"},
    [2] = {}, //means ending
};

struct platform_driver test_driver = {
	.probe = driver_probe,
	.remove = driver_remove,
	.driver = {
		.name = "xxxxx", //必须初始化
	},
    .id_table = testdrv_ids,
};

static int __init platform_driver_init(void)
{
	platform_driver_register(&test_driver);
	return 0;
}

static void __exit platform_driver_exit(void)
{
	platform_driver_unregister(&test_driver);
}

module_init(platform_driver_init);
module_exit(platform_driver_exit);
MODULE_LICENSE("Dual BSD/GPL");

用到结构体数组,一般不指定大小,初始化时最后加{}表示数组结束

设备中增加资源,驱动中访问资源

二、ID匹配之led驱动

fs4412leds.h

#ifndef LED_DRIVER_H
#define LED_DRIVER_H
#define LED_DEV_MAGIC 'g'


#define MY_LED_OFF _IO(LED_DEV_MAGIC,0)
#define MY_LED_ON _IO(LED_DEV_MAGIC,1)

#endif

s4412leds_device.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>

#define GPX1CON 0x11000C20
#define GPX1DAT 0x11000C24

#define GPX2CON 0x11000C40
#define GPX2DAT 0x11000C44
#define GPX3CON 0x114001E0
#define GPX3DAT 0x114001E4

void fs4412leds_dev_release(struct device *pdev)
{
	printk("fs4412leds_dev_release is called\n");
}

struct resource fs4412leds_dev_res[] = {
	[0] = {.start = GPX1CON,.end = GPX1CON+3,.name = "GPX1CON",.flags = IORESOURCE_MEM},
	[1] = {.start = GPX1DAT,.end = GPX1DAT+3,.name = "GPX1DAT",.flags = IORESOURCE_MEM},
	[2] = {.start = GPX2CON,.end = GPX2CON+3,.name = "GPX2CON",.flags = IORESOURCE_MEM},
	[3] = {.start = GPX2DAT,.end = GPX2DAT+3,.name = "GPX2DAT",.flags = IORESOURCE_MEM},
	[4] = {.start = GPX3CON,.end = GPX3CON+3,.name = "GPX3CON",.flags = IORESOURCE_MEM},
	[5] = {.start = GPX3DAT,.end = GPX3DAT+3,.name = "GPX3DAT",.flags = IORESOURCE_MEM},
};


struct platform_device_id fs4412leds_id = {

	.name = "fs4412leds",

};

struct platform_device fs4412leds_device = {
	.name = "fs4412leds",
	.dev.release = fs4412leds_dev_release,
	.resource = fs4412leds_dev_res,
	.num_resources = ARRAY_SIZE(fs4412leds_dev_res),

	.id_entry = &fs4412leds_id,

};


int __init fs4412leds_device_init(void)
{
	platform_device_register(&fs4412leds_device);
	return 0;
}

void __exit fs4412leds_device_exit(void)
{
	platform_device_unregister(&fs4412leds_device);
}


MODULE_LICENSE("GPL");
module_init(fs4412leds_device_init);
module_exit(fs4412leds_device_exit);


fs4412leds_driver.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>                                                                 
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/poll.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include "fs4412leds.h"
#define BUF_LEN 100

int major = 11;
int minor = 0 ;
int fs4412leds_num = 1;

typedef struct{
	struct cdev mydev;

	volatile unsigned long *pled2_con;
	volatile unsigned long *pled2_dat;

	volatile unsigned long *pled3_con;
	volatile unsigned long *pled3_dat;

	volatile unsigned long *pled4_con;
	volatile unsigned long *pled4_dat;

	volatile unsigned long *pled5_con;
	volatile unsigned long *pled5_dat;

}MYDEV;
MYDEV *pgmydev = NULL;

int fs4412leds_open(struct inode *pnode,struct file *pfile){
	pfile->private_data = (void*)container_of(pnode->i_cdev,MYDEV,mydev);
	printk("fs4412leds_open is called\n");
	return 0 ;
}


int fs4412leds_close(struct inode *pnode,struct file *pfile){
	
	printk("fs4412leds_open is close\n");
	return 0 ;
}


void led_on(MYDEV *pmydev, int ledno){
	switch(ledno)
	{
		case 2:
	writel(readl(pmydev->pled2_dat) | ((0x1 << 7)),pmydev->pled2_dat);
			break;
		case 3:
	writel(readl(pmydev->pled3_dat) | ((0x1 )),pmydev->pled3_dat);
			break;
		case 4:
	writel(readl(pmydev->pled4_dat) | ((0x1 << 4)),pmydev->pled4_dat);
			break;
		case 5:
	writel(readl(pmydev->pled5_dat) | ((0x1 << 5)),pmydev->pled5_dat);
			break;
	}
}
		
void led_off(MYDEV *pmydev, int ledno){
	switch(ledno)
	{
		case 2:
	writel(readl(pmydev->pled2_dat) & (~(0x1 << 7)),pmydev->pled2_dat);
			break;
		case 3:
	writel(readl(pmydev->pled3_dat) & (~(0x1 )),pmydev->pled3_dat);
			break;
		case 4:
	writel(readl(pmydev->pled4_dat) & (~(0x1 << 4)),pmydev->pled4_dat);
			break;
		case 5:
	writel(readl(pmydev->pled5_dat) & (~(0x1 << 5)),pmydev->pled5_dat);
			break;
	}
}


long fs4412leds_ioctl(struct file *pfile,unsigned int cmd ,unsigned long arg){
	MYDEV *pmydev = (MYDEV*)pfile->private_data;
	if(arg < 2 || arg >5){
		return -1;
	}


	switch(cmd){
	case MY_LED_ON:
		led_on(pmydev,arg);
		break;
	case MY_LED_OFF:
		led_off(pmydev,arg);
		break;
	default:
		printk("The CMD is uknow\n");
		return -1;
	}
	return 0;
}


struct file_operations myops = {
	//.owner = THIS_MODULE,//内核在插入模块的时候,在内核中生成的struct module 对象的地址
	.open = fs4412leds_open,
	.release = fs4412leds_close,
	.unlocked_ioctl = fs4412leds_ioctl,
};

void ioremap_ledreg(MYDEV *pmydev,struct platform_device *p_pltdev){

	struct resource *pres = NULL;

	pres = platform_get_resource(p_pltdev,IORESOURCE_MEM,2);
	pmydev->pled2_con = ioremap(pres->start,4);
	pres = platform_get_resource(p_pltdev,IORESOURCE_MEM,3);
	pmydev->pled2_dat = ioremap(pres->start,4);

	pres = platform_get_resource(p_pltdev,IORESOURCE_MEM,0);
	pmydev->pled3_con = ioremap(pres->start,4);
	pres = platform_get_resource(p_pltdev,IORESOURCE_MEM,1);
	pmydev->pled3_dat = ioremap(pres->start,4);

	pres = platform_get_resource(p_pltdev,IORESOURCE_MEM,4);
	pmydev->pled4_con = ioremap(pres->start,4);
	pres = platform_get_resource(p_pltdev,IORESOURCE_MEM,5);
	pmydev->pled4_dat = ioremap(pres->start,4);

	pmydev->pled5_con = pmydev->pled4_con;
	pmydev->pled5_dat = pmydev->pled4_dat;
	

}
void set_output_ledconreg(MYDEV *pmydev){
	writel((readl(pmydev->pled2_con) & (~(0xF << 28))) | (0x1 << 28),pmydev->pled2_con);
	writel((readl(pmydev->pled3_con) & (~(0xF ))) | (0x1 ),pmydev->pled3_con);
	writel((readl(pmydev->pled4_con) & (~(0xF << 16))) | (0x1 << 16),pmydev->pled4_con);
	writel((readl(pmydev->pled5_con) & (~(0xF << 20))) | (0x1 << 20),pmydev->pled5_con);

	writel(readl(pmydev->pled2_dat) & (~(0x1 << 7)),pmydev->pled2_dat);
	writel(readl(pmydev->pled3_dat) & (~(0x1 )),pmydev->pled3_dat);
	writel(readl(pmydev->pled4_dat) & (~(0x1 << 4)),pmydev->pled4_dat);
	writel(readl(pmydev->pled5_dat) & (~(0x1 << 5)),pmydev->pled5_dat);

}
void iounmap_ledreg(MYDEV *pmydev){
	iounmap(pmydev->pled2_con);
	pmydev->pled2_con = NULL;
	iounmap(pmydev->pled2_dat);
	pmydev->pled2_dat = NULL;

	iounmap(pmydev->pled3_con);
	pmydev->pled3_con = NULL;
	iounmap(pmydev->pled3_dat);
	pmydev->pled3_dat = NULL;

	iounmap(pmydev->pled4_con);
	pmydev->pled4_con = NULL;
	iounmap(pmydev->pled4_dat);
	pmydev->pled4_dat = NULL;

	pmydev->pled5_con = NULL;
	pmydev->pled5_dat = NULL;

}

int fs4412leds_driver_probe(struct platform_device *p_pltdev)
{

	int ret = 0;
	dev_t devno = MKDEV(major,minor);

	ret = register_chrdev_region(devno,fs4412leds_num,"fs4412leds");

	if(ret){
		ret = alloc_chrdev_region(&devno,minor,fs4412leds_num,"fs4412leds");
		if(ret){
			printk("get devno failed\n");
			return -1;
		}
		major = MAJOR(devno);//分离出自动分配的主设备号
	}

	//使用kmalloc方式动态分配内存
	pgmydev = (MYDEV*)kmalloc(sizeof(MYDEV),GFP_KERNEL);
	if(NULL == pgmydev){
		unregister_chrdev_region(devno,fs4412leds_num);
		printk("kmalloc failed");
		return -1;
	}


	//初始设备操作函数集
	cdev_init(&pgmydev->mydev,&myops);
	//将struct cdev 对象添加到内核对应的数据结构里
	pgmydev->mydev.owner = THIS_MODULE;//推测不必要
	cdev_add(&pgmydev->mydev,devno,fs4412leds_num);

	//ioremap
	ioremap_ledreg(pgmydev,p_pltdev);	
	//con-register set output
	set_output_ledconreg(pgmydev);
	printk("fs4412leds will init\n");

	return 0;
}

int fs4412leds_driver_remove(struct platform_device *p_pltdev)
{
	dev_t devno = MKDEV(major,minor);

	//iounmap
	iounmap_ledreg(pgmydev);

	cdev_del(&pgmydev->mydev);
	unregister_chrdev_region(devno,fs4412leds_num);

	kfree(pgmydev);

	printk("fs4412leds will exit\n");
	return 0;
}

struct platform_device_id fs4412leds_ids[]=
{
	[0] = {.name = "fs4412leds"},
	[1] = {.name = "hello"},
	[2] = {},
};

struct platform_driver fs4412leds_driver = 
{
     .driver.name = "hello",                                                                        
     .probe  = fs4412leds_driver_probe ,
     .remove = fs4412leds_driver_remove ,

	 .id_table = fs4412leds_ids,

};

int __init fs4412leds_init(void)
{
     platform_driver_register(&fs4412leds_driver);
     return 0;

}
void __exit fs4412leds_exit(void)
{
	  platform_driver_unregister(&fs4412leds_driver);

}


MODULE_LICENSE("GPL");

module_init(fs4412leds_init);
module_exit(fs4412leds_exit);

三、设备树匹配

设备树匹配:内核启动时根据设备树自动产生的设备 ------ 优先级最高

注意事项:

  1. 无需编写device模块,只需编写driver模块
  2. 使用compatible属性进行匹配,注意设备树中compatible属性值不要包含空白字符
  3. id_table可不设置,但struct platform_driver成员driver的name成员必须设置
/*platform driver框架*/
#include <linux/module.h> 
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/platform_device.h>

static int driver_probe(struct platform_device *dev)
{
	printk("platform: match ok!\n");
	return 0;
}

static int driver_remove(struct platform_device *dev)
{
	printk("platform: driver remove\n");
	return 0;
}

struct platform_device_id testdrv_ids[] = 
{
	[0] = {.name = "test_device"},
    [1] = {.name = "abcxyz"},
    [2] = {}, //means ending
};

struct of_device_id test_of_ids[] = 
{
	[0] = {.compatible = "xyz,abc"},
    [1] = {.compatible = "qwe,opq"},
    [2] = {},
};

struct platform_driver test_driver = {
	.probe = driver_probe,
	.remove = driver_remove,
	.driver = {
		.name = "xxxxx", //必须初始化
        .of_match_table = test_of_ids,
	},
};

static int __init platform_driver_init(void)
{
	platform_driver_register(&test_driver);
	return 0;
}

static void __exit platform_driver_exit(void)
{
	platform_driver_unregister(&test_driver);
}

module_init(platform_driver_init);
module_exit(platform_driver_exit);
MODULE_LICENSE("Dual BSD/GPL");

四、设备树匹配之led驱动

myled.h

#ifndef LED_DRIVER_H
#define LED_DRIVER_H
#define LED_DEV_MAGIC 'g'
#define MY_LED_OFF _IO(LED_DEV_MAGIC,0)
#define MY_LED_ON _IO(LED_DEV_MAGIC,1)

#endif

设备树文件地址:~/fs4412/linux-3.14/arch/arm/boot/dts/exynos4412-fs4412.dts

fs4412leds_driver.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <linux/fs.h>
#include <linux/gpio.h>
#include <linux/of.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/of_gpio.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include "myled.h"
#define BUF_LEN 100

int major = 11;
int minor = 0 ;
int myleddts_num = 1;

typedef struct{
	struct cdev mydev;

	unsigned int led2gpio;
	unsigned int led3gpio;
	unsigned int led4gpio;
	unsigned int led5gpio;

}MYDEV;
MYDEV *pgmydev = NULL;

int myleddts_open(struct inode *pnode,struct file *pfile){
	pfile->private_data = (void*)container_of(pnode->i_cdev,MYDEV,mydev);
	printk("myleddts_open is called\n");
	return 0 ;
}


int myleddts_close(struct inode *pnode,struct file *pfile){

	printk("myleddts_open is close\n");
	return 0 ;
}
void led_on(MYDEV *pmydev, int ledno){
	switch(ledno)
	{
	case 2:
		gpio_set_value(pmydev->led2gpio,1);
		break;
	case 3:
		gpio_set_value(pmydev->led3gpio,1);
		break;
	case 4:
		gpio_set_value(pmydev->led4gpio,1);
		break;
	case 5:
		gpio_set_value(pmydev->led5gpio,1);
		break;
	}
}

void led_off(MYDEV *pmydev, int ledno){
	switch(ledno)
	{
	case 2:
		gpio_set_value(pmydev->led2gpio,0);
		break;
	case 3:
		gpio_set_value(pmydev->led3gpio,0);
		break;
	case 4:
		gpio_set_value(pmydev->led4gpio,0);
		break;
	case 5:
		gpio_set_value(pmydev->led5gpio,0);
		break;
	}
}
long myleddts_ioctl(struct file *pfile,unsigned int cmd ,unsigned long arg){
	MYDEV *pmydev = (MYDEV*)pfile->private_data;
	if(arg < 2 || arg >5){
		return -1;
	}

	switch(cmd){
	case MY_LED_ON:
		led_on(pmydev,arg);
		break;
	case MY_LED_OFF:
		led_off(pmydev,arg);
		break;
	default:
		printk("The CMD is uknow\n");
		return -1;
	}
	return 0;
}
struct file_operations myops = {
	//.owner = THIS_MODULE,//内核在插入模块的时候,在内核中生成的struct module 对象的地址
	.open = myleddts_open,
	.release = myleddts_close,
	.unlocked_ioctl = myleddts_ioctl,
};

void request_led_gpio(MYDEV *pmydev,struct device_node *pnode){

	pmydev->led2gpio =	of_get_named_gpio(pnode,"led2-gpio",0);//从设备树中提取gpio口
	gpio_request(pmydev->led2gpio,"led2");//让内核检查一下该GPIO引脚是否被其它设备占用,如果没有占用则返回0并用label做一下标记,表示被本设备占用,否则返回负数
	pmydev->led3gpio =	of_get_named_gpio(pnode,"led3-gpio",0);
	gpio_request(pmydev->led3gpio,"led3");
	pmydev->led4gpio =	of_get_named_gpio(pnode,"led4-gpio",0);
	gpio_request(pmydev->led4gpio,"led4");
	pmydev->led5gpio =	of_get_named_gpio(pnode,"led5-gpio",0);
	gpio_request(pmydev->led5gpio,"led5");

}
void set_leds_gpio_output(MYDEV *pmydev){
	//将引脚设置成输出并置于底电平
	gpio_direction_output(pmydev->led2gpio,1);
	gpio_direction_output(pmydev->led3gpio,1);
	gpio_direction_output(pmydev->led4gpio,1);
	gpio_direction_output(pmydev->led5gpio,1);
}
void free_leds_gpio(MYDEV *pmydev){
	gpio_free(pmydev->led2gpio);
	gpio_free(pmydev->led3gpio);
	gpio_free(pmydev->led4gpio);
	gpio_free(pmydev->led5gpio);

}

int myleddts_probe(struct platform_device *p_platdev)
{
	int ret = 0;
	dev_t devno = MKDEV(major,minor);
	//定义设备树节点指针变量
	struct device_node *pnode1=NULL;


	//申请设备号
	ret = register_chrdev_region(devno,myleddts_num,"myleddts");

	if(ret){
		ret = alloc_chrdev_region(&devno,minor,myleddts_num,"myleddts");
		if(ret){
			printk("get devno failed\n");
			return -1;
		}
		major = MAJOR(devno);//分离出自动分配的主设备号
	}

	//使用kmalloc方式动态分配内存
	pgmydev = (MYDEV*)kmalloc(sizeof(MYDEV),GFP_KERNEL);
	if(NULL == pgmydev){
		unregister_chrdev_region(devno,myleddts_num);
		printk("kmalloc failed");
		return -1;
	}


	//初始设备操作函数集
	cdev_init(&pgmydev->mydev,&myops);
	//将struct cdev 对象添加到内核对应的数据结构里
	pgmydev->mydev.owner = THIS_MODULE;//推测不必要
	cdev_add(&pgmydev->mydev,devno,myleddts_num);

	pnode1 = p_platdev->dev.of_node;

	//ioremap 引入编程依据,初始化引脚功能
	request_led_gpio(pgmydev,pnode1);
	//con-register set output
	set_leds_gpio_output(pgmydev);

	printk("myleddts will init\n");

	return 0;
}

int myleddts_remove(struct platform_device *p_platdev)
{
	dev_t devno = MKDEV(major,minor);

	//从设备树中取消占用
	free_leds_gpio(pgmydev);

	cdev_del(&pgmydev->mydev);
	unregister_chrdev_region(devno,myleddts_num);

	kfree(pgmydev);

	printk("myleddts will exit\n");
	return 0;
}

struct of_device_id myleddrv_of_ids[] = 
{
	[0] = {.compatible= "fs4412,led2-5"},
	[1] = {.compatible= "mykey2,key2"},
	[2] = {},
};

struct platform_driver myled_driver = 
{
	.driver = {
		.name = "fs4412leds",
		.of_match_table = myleddrv_of_ids,
	},
	.probe = myleddts_probe ,
	.remove = myleddts_remove,
};


int __init myleddts_init(void)
{
	platform_driver_register(&myled_driver);
	return 0;
}

void __exit myleddts_exit(void)
{
	platform_driver_unregister(&myled_driver);
}

MODULE_LICENSE("GPL");

module_init(myleddts_init);
module_exit(myleddts_exit);

五、一个编写驱动用的宏

struct platform_driver xxx = {  
    ...
};
module_platform_driver(xxx);
//最终展开后就是如下形式:
static int __init xxx_init(void)
{
        return platform_driver_register(&xxx);
}
module_init(xxx_init);
static void __exit xxx_init(void)
{
        return platform_driver_unregister(&xxx);
}
module_exit(xxx_exit)

;

D4 I2C总线式驱动开发

一、I2C总线背景知识

SOC芯片平台的外设分为:

  1. 一级外设:外设控制器集成在SOC芯片内部
  2. 二级外设:外设控制器由另一块芯片负责,通过一些通讯总线与SOC芯片相连
    00_SOC架构图

Inter-Integrated Circuit: 字面意思是用于“集成电路之间”的通信总线,简写:IIC(或者I2C)
01_I2C概述
02_I2C连接

03_I2C时序
04_I2C协议01

05_I2C协议02

i2c传输的要点就是: 传输一个字节 后面必然紧跟一个"响应"信号----应答信号.这个响应信号可能来自主机,或者是从机,具体是谁,就要看传输方向。
传输方向分两种情况(每种情况又有两种可能: A无应答和 B有应答):

1.主机->从机,主机对从机发一个字节之后,主机要读取从机的响应信号(主机读SDA线)

A) 主机读SDA为高电平,说明从机无应答(意味着从机接收完毕,主机发送停止信号)
 B) 主机读SDA为低电平,说明从机有应答。(可继续发送下一个字节)

2.从机->主机, 主机读取从机一个字节之后,主机要向从机发送一个响应信号(主机写SDA线)

​ A) 主机写SDA为高电平,从机收到主机的无应答信号之后,从机停止传输,等待主机的停止信号。
​ B) 主机写SDA为低电平,从机收到主机的应答信号之后,从机继续输出下一字节

二、Exynos4412 I2C收发实现之裸机版

Exynos4412好像提供8个I2C通道,每个提供一组i2c总线。
每组总线由3个寄存器控制:
I2CCON寄存器:控制寄存器
I2CSTAT寄存器:状态寄存器
I2CDS寄存器:数据寄存器

I2CCON寄存器:控制寄存器
I2CCON.

第7位:决定是否允许产生应答信号,无论发送还是接收前,需置1
第6位:传输时时钟线分频,一般选置1
第5位:决定是否开启发送或接收结束时发通知,无论发送还是接收前,需置1
第4位:接收或发送是否完毕可以通过检查此位是否为1,接收或发送完毕后需置0

I2CSTAT寄存器:状态寄存器
I2CSTAT
第6、7位:每次传输前需选择传输模式
第5位:置0产生将产生终止信号,传输前置1产生起始信号
第4位:使能数据输出,传输前需置1

I2CDS寄存器:数据寄存器,发送前被发送的数据存放处,接收后结果也从此处读取

2.1 发送

06_write

07_发送流程

void iic_write (unsigned char slave_addr, unsigned char addr, unsigned char data)
{
    //从设备寻址
	I2C5.I2CDS = slave_addr;
	I2C5.I2CCON = 1<<7 | 1<<6 | 1<<5;/*ENABLE ACK BIT, PRESCALER:512, ,ENABLE RX/TX */
	
    I2C5.I2CSTAT = 0x3 << 6 | 1<<5 | 1<<4;/*Master Trans mode ,START ,ENABLE RX/TX ,*/
	while(!(I2C5.I2CCON & (1<<4)));

	I2C5.I2CDS = addr;
	I2C5.I2CCON &= ~(1<<4);	//Clear pending bit to resume.
	while(!(I2C5.I2CCON & (1<<4)));

    //发送数据
	I2C5.I2CDS = data;	// Data
	I2C5.I2CCON &= ~(1<<4);	//Clear pending bit to resume.
	while(!(I2C5.I2CCON & (1<<4)));

	I2C5.I2CSTAT = 0xD0; //stop

	I2C5.I2CCON &= ~(1<<4);//Clear pending bit to resume.

	mydelay_ms(10);
}

2.2 接收

09_read

10_接收流程

void iic_read(unsigned char slave_addr, unsigned char addr, unsigned char *data)
{
    //从设备寻址
	I2C5.I2CDS = slave_addr;

	I2C5.I2CCON = 1<<7 | 1<<6 | 1<<5;/*ENABLE ACK BIT, PRESCALER:512, ENABLE RX/TX Interrupt-enable */
	I2C5.I2CSTAT = 0x3 << 6 | 1<<5 | 1<<4;/*Master Trans mode ,START ,ENABLE RX/TX ,*/
	while(!(I2C5.I2CCON & (1<<4))); /*对应位为1表示slave_addr传输完成,线路处于挂起状态*/

	I2C5.I2CDS = addr;
	I2C5.I2CCON &= ~(1<<4);	//Clear pending bit to resume. 继续传输
	while(!(I2C5.I2CCON & (1<<4)));
    
	
    I2C5.I2CSTAT = 0xD0; //stop  第5位写0,表示要求产生stop信号

	//接收数据
	I2C5.I2CDS = slave_addr | 0x01;	// Read
	I2C5.I2CCON = 1<<7 | 1<<6 | 1<<5;/*ENABLE ACK BIT, PRESCALER:512, ENABLE RX/TX Interrupt-enable */

	I2C5.I2CSTAT = 2<<6 | 1<<5 | 1<<4;/*Master receive mode ,START ,ENABLE RX/TX , 0xB0*/
	while(!(I2C5.I2CCON & (1<<4)));

	I2C5.I2CCON &= ~((1<<7) | (1<<4));/* Resume the operation  & no ack*/
	while(!(I2C5.I2CCON & (1<<4)));

	I2C5.I2CSTAT = 0x90; //stop  第5位写0,表示要求产生stop信号
	I2C5.I2CCON &= ~(1<<4);		/*clean interrupt pending bit  */

	*data = I2C5.I2CDS;
	mydelay_ms(10);
}

三、Linux内核对I2C总线的支持

LinuxI2C框架图
**I2C设备驱动:**即挂接在I2C总线上的二级外设的驱动,也称客户(client)驱动,实现对二级外设的各种操作,二级外设的几乎所有操作全部依赖于对其自身内部寄存器的读写,对这些二级外设寄存器的读写又依赖于I2C总线的发送和接收

I2C总线驱动:即对I2C总线自身控制器的驱动,一般SOC芯片都会提供多个I2C总线控制器,每个I2C总线控制器提供一组I2C总线(SDA一根+SCL一根),每一组被称为一个I2C通道,Linux内核里将I2C总线控制器叫做适配器(adapter),适配器驱动主要工作就是提供通过本组I2C总线与二级外设进行数据传输的接口,每个二级外设驱动里必须能够获得其对应的adapter对象才能实现数据传输

**I2C核心:**承上启下,为I2C设备驱动和I2C总线驱动开发提供接口,为I2C设备驱动层提供管理多个i2c_driver、i2c_client对象的数据结构,为I2C总线驱动层提供多个i2c_algorithm、i2c_adapter对象的数据结构

四大核心对象之间的关系图
i2c四大核心对象之间的关联

i2c二级外设驱动开发涉及到核心结构体及其相关接口函数:

struct i2c_board_info {
    char        type[I2C_NAME_SIZE];//给从设备取名字
    unsigned short  flags;//从设备地址两种,7位或10位。一般置零,表示7位从设备地址。
    unsigned short  addr;
    void        *platform_data;
    struct dev_archdata *archdata;
    struct device_node *of_node;
    int     irq;
};
/*用来协助创建i2c_client对象
重要成员
type:用来初始化i2c_client结构中的name成员
flags:用来初始化i2c_client结构中的flags成员
addr:用来初始化i2c_client结构中的addr成员
platform_data:用来初始化i2c_client结构中的.dev.platform_data成员
archdata:用来初始化i2c_client结构中的.dev.archdata成员
irq:用来初始化i2c_client结构中的irq成员

关键就是记住该结构和i2c_client结构成员的对应关系。在i2c子系统不直接创建i2c_client结构,只是提供struct i2c_board_info结构信息,让子系统动态创建,并且注册。
*/
struct i2c_client {
    unsigned short flags;
    unsigned short addr;
    char name[I2C_NAME_SIZE];
    struct i2c_adapter *adapter;//重要
    struct i2c_driver *driver;
    struct device dev;
    int irq;
    struct list_head detected;//很多个client对象用链式结构管理
};
/*重要成员:
flags:地址长度,如是10位还是7位地址,默认是7位地址。如果是10位地址器件,则设置为I2C_CLIENT_TEN
addr:具体I2C器件如(at24c02),设备地址,低7位
name:设备名,用于和i2c_driver层匹配使用的,可以和平台模型中的平台设备层platform_driver中的name作用是一样的。
adapter:本设备所绑定的适配器结构(CPU有很多I2C适配器,类似单片机有串口1、串口2等等,在linux中每个适配器都用一个结构描述)
driver:指向匹配的i2c_driver结构,不需要自己填充,匹配上后内核会完成这个赋值操作
dev:内嵌的设备模型,可以使用其中的platform_data成员传递给任何数据给i2c_driver使用。
irq:设备需要使用到中断时,把中断编号传递给i2c_driver进行注册中断,如果没有就不需要填充。(有的I2C器件有中断引脚编号,与CPU相连)
*/

/* 获得/释放 i2c_adapter 路径:i2c-core.c linux-3.5\drivers\i2c */
/*功能:通过i2c总线编号获得内核中的i2c_adapter结构地址,然后用户可以使用这个结构地址就可以给i2c_client结构使用,从而实现i2c_client进行总线绑定,从而增加适配器引用计数。
返回值:
NULL:没有找到指定总线编号适配器结构
非NULL:指定nr的适配器结构内存地址*/
struct i2c_adapter *i2c_get_adapter(int nr);//调用得到I2C通道,0-7


/*减少引用计数:当使用·i2c_get_adapter·后,需要使用该函数减少引用计数。(如果你的适配器驱动不需要卸载,可以不使用)*/
void i2c_put_adapter(struct i2c_adapter *adap);

/*
功能:根据参数adap,info,addr,addr_list动态创建i2c_client并且进行注册
参数:
adap:i2c_client所依附的适配器结构地址
info:i2c_client基本信息
addt_list: i2c_client的地址(地址定义形式是固定的,一般是定义一个数组,数组必须以I2C_CLIENT_END结束,示例:unsigned short ft5x0x_i2c[]={0x38,I2C_CLIENT_END};
probe:回调函数指针,当创建好i2c_client后,会调用该函数,一般没有什么特殊需求传递NULL。
返回值:
非NULL:创建成功,返回创建好的i2c_client结构地址
NULL:创建失败
*/
struct i2c_client * i2c_new_probed_device
(
 struct i2c_adapter *adap,
 struct i2c_board_info *info,
 unsigned short const *addr_list,
 int (*probe)(struct i2c_adapter *, unsigned short addr)
);//该函数在,不是明确知道从设备地址,该结构体具备探测功能
/*示例:
struct i2c_adapter *ad;
struct i2c_board_info info={""};

unsigned short addr_list[]={0x38,0x39,I2C_CLIENT_END};

//假设设备挂在i2c-2总线上
ad=i2c_get_adapter(2);

//自己填充board_info 
strcpy(inf.type,"xxxxx");
info.flags=0;
//动态创建i2c_client并且注册
i2c_new_probed_device(ad,&info,addr_list,NULL); 

i2c_put_adapter(ad);
*/

/*注销*/
void i2c_unregister_device(struct i2c_client *pclt)


 struct i2c_client * i2c_new_device
 (
     struct i2c_adapter *padap,
     struct i2c_board_info const *pinfo
 );//该函数在,明确知道从设备地址条件下使用
/*示例:
struct i2c_adapter *ad;
struct i2c_board_info info={
	I2C_BOARD_INFO(name,二级外设地址)
};
//假设设备挂在i2c-2总线上
ad=i2c_get_adapter(2);

//动态创建i2c_client并且注册
i2c_new_device(ad,&info);

i2c_put_adapter(ad);
*/
struct i2c_driver {
    unsigned int class;

    /* Standard driver model interfaces */
    int (*probe)(struct i2c_client *, const struct i2c_device_id *);
    int (*remove)(struct i2c_client *);

    /* driver model interfaces that don't relate to enumeration  */
    void (*shutdown)(struct i2c_client *);
    int (*suspend)(struct i2c_client *, pm_message_t mesg);
    int (*resume)(struct i2c_client *);
	void (*alert)(struct i2c_client *, unsigned int data);

    /* a ioctl like command that can be used to perform specific functions
     * with the device.
     */
    int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);

    struct device_driver driver;
    const struct i2c_device_id *id_table;

    /* Device detection callback for automatic device creation */
    int (*detect)(struct i2c_client *, struct i2c_board_info *);
    const unsigned short *address_list;
    struct list_head clients;
};
/*重要成员:
probe:在i2c_client与i2c_driver匹配后执行该函数
remove:在取消i2c_client与i2c_driver匹配绑定后后执行该函数
driver:这个成员类型在平台设备驱动层中也有,而且使用其中的name成员来实现平台设备匹配,但是i2c子系统中不使用其中的name进行匹配,这也是i2c设备驱动模型和平台设备模型匹配方法的一点区别
id_table:用来实现i2c_client与i2c_driver匹配绑定,当i2c_client中的name成员和i2c_driver中id_table中name成员相同的时候,就匹配上了。

补充:i2c_client与i2c_driver匹配问题
- i2c_client中的name成员和i2c_driver中id_table中name成员相同的时候
- i2c_client指定的信息在物理上真实存放对应的硬件,并且工作是正常的才会绑定上,并执行其中的probe接口函数这第二点要求和平台模型匹配有区别,平台模型不要求设备层指定信息在物理上真实存在就能匹配
*/

/*功能:向内核注册一个i2c_driver对象
返回值:0成功,负数 失败*/
#define i2c_add_driver(driver)     i2c_register_driver(THIS_MODULE, driver)
int i2c_register_driver(struct module *owner, struct i2c_driver *driver);

/*功能:从内核注销一个i2c_driver对象
返回值:无 */
void i2c_del_driver(struct i2c_driver *driver);
struct i2c_msg {
    __u16 addr; /* slave address            */
    __u16 flags;
#define I2C_M_TEN       0x0010  /* this is a ten bit chip address */
#define I2C_M_RD        0x0001  /* read data, from slave to master */
    __u16 len;      /* msg length               */
    __u8 *buf;      /* pointer to msg data          */
};
/* 重要成员:
addr:要读写的二级外设地址
flags:表示地址的长度,读写功能。如果是10位地址必须设置I2C_M_TEN,如果是读操作必须设置有I2C_M_RD······,可以使用或运算合成。
buf:要读写的数据指针。写操作:数据源 读操作:指定存放数据的缓存区
len:读写数据的数据长度
*/

/*i2c收发一体化函数,收还是发由参数msgs的成员flags决定*/
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
/*
功能:根据msgs进行手法控制
参数:
adap:使用哪一个适配器发送信息,一般是取i2c_client结构中的adapter指针作为参数
msgs:具体发送消息指针,一般情况下是一个数组
num:表示前一个参数msgs数组有多少个消息要发送的
返回值:
负数:失败
> 0 表示成功发送i2c_msg数量
*/

/*I2C读取数据函数*/
int i2c_master_recv(const struct i2c_client *client, char *buf, int count)
/*功能:实现标准的I2C读时序,数据可以是N个数据,这个函数调用时候默认已经包含发送从机地址+读方向这一环节了
参数:
client:设备结构
buf:读取数据存放缓冲区
count:读取数据大小 不大于64k
返回值:
失败:负数
成功:成功读取的字节数
*/
    
/*I2C发送数据函数*/
int i2c_master_send(const struct i2c_client *client, const char *buf, int count)
/*功能:实现标准的I2C写时序,数据可以是N个数据,这个函数调用时候默认已经包含发送从机地址+写方向这一环节了
参数:
client:设备结构地址
buf:发送数据存放缓冲区
count:发送数据大小 不大于64k
返回值:
失败:负数
成功:成功发送的字节数
*/

四、MPU6050

三轴角速度+三轴加速度+温度传感器

i2c四大核心对象之间的关联

//MPU6050内部寄存器
#define SMPLRT_DIV  0x19 //陀螺仪采样率,典型值:0x07(125Hz)
#define CONFIG   0x1A //低通滤波频率,典型值:0x06(5Hz)
#define GYRO_CONFIG  0x1B //陀螺仪自检及测量范围,典型值:0xF8(不自检,+/-2000deg/s)
#define ACCEL_CONFIG 0x1C //加速计自检、测量范围,典型值:0x19(不自检,+/-G)
#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 //电源管理,典型值:0x00(正常启用)

五、应用层直接使用I2C通道

I2C总线上二级外设的驱动程序的开发方式大体分两种:
1. 按传统的方式,在内核里增加对二级外设的驱动程序,然后应用程序通过操作相应的设备的文件来操作相应的二级外设。
2. 直接在应用层编写对二级外设的驱动

5.1 预备工作:

不管是应用层还是底层,I2C总线的驱动都是soc芯片厂商开发好的,仍然需要做相关的一些配套工作,比如,exynos4412平台 上的I2C总线驱动写好了,但是每个I2C总线通道的信息是通过设备树提供的,因此需要在和开发板配套的dts中添加相应节点信息。

5.1.1 exynos4412-fs4412.dts增加节点

exynos4412平台每个i2c通道的信息是通过设备树提供的,而MPU6050挂在第五个通道上,因此需要首先在exynos4412-fs4412.dts中增加5通道的节点:i2c通道5设备树节点

i2c@138B0000 {
		#address-cells = <1>;
		#size-cells = <0>;
		samsung,i2c-sda-delay = <100>;
		samsung,i2c-max-bus-freq = <20000>;
		pinctrl-0 = <&i2c5_bus>;
		pinctrl-names = "default";
		status = "okay";
};

不要忘记:

  1. 回内核源码顶层目录执行:make dtbs
  2. 将新生成的dtb拷贝到/tftpboot
5.1.2 添加此字符设备驱动代码,修改make menuconfig的配置

i2c总线驱动层在/dev下提供了一个字符设备驱动文件,以便于应用层可以直接通过它去使用i2c总线通讯去操作二级外设,但需要内核编译时添加此字符设备驱动代码(i2c-dev.c),因此需要修改make menuconfig的配置:

menuconfig之i2c-dev支持
不要忘记:

  1. 回内核源码顶层目录执行:make uImage
  2. 将新生成的uImage拷贝到/tftpboot
5.2 应用层直接使用i2c总线的代码实现

应用层直接通过I2C总线去操控相应的二级外设即数据交互,有两种方法去实现接收和发送。

5.2.1 调用read、write实现接收、发送

详解static inline关键字

通过设备文件去做,内核中i2c_dev.c 针对设备的驱动程序, 寻址的过程已经写好了。read 函数中只需要告诉寄存器地址就好了。

见实例代码
cp ~/fs4412/linux-3.14/include/uapi/linux/i2c-dev.h .

main.c
#include "mpu6050.h"
int main(int argc, char *argv[])
{
	int fd = -1;
	if(argc < 2)
	{
		printf("Argumen is too few\n");
		return 1;
	}

	//open
	fd = open(argv[1],O_RDWR);
	if(fd < 0){
		printf("open %s failed\n",argv[1]);
		return 2;
	}
 

	//init mpu6050
	init_mpu6050(fd);


	while(1)
	{
		sleep(2);
		/*read and print data form 6050*/
		printf("Accel-X:0x%x\n",read_accelx(fd));
		printf("Accel-Y:0x%x\n",read_accely(fd));
		printf("Accel-Z:0x%x\n",read_accelz(fd));
		printf("Temp:0x%x\n",read_temp(fd));
		printf("Gyro-X:0x%x\n",read_gyrox(fd));
		printf("Gyro-Y:0x%x\n",read_gyroy(fd));
		printf("Gyro-Z:0x%x\n",read_gyroz(fd));

		printf("\n");
	}


	close(fd);
	fd = -1;
	
	return 0;
}
mpu6050.h
#ifndef MPU_6050_H
#define MPU_6050_H
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>


int init_mpu6050(int fd);
int read_accelx(int fd);
int read_accely(int fd);
int read_accelz(int fd);
int read_temp(int fd);
int read_gyrox(int fd);
int read_gyroy(int fd);
int read_gyroz(int fd);

#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

//拷贝自:~/fs4412/linux-3.14/include/uapi/linux/i2c-dev.h  
#define I2C_SLAVE   0x0703  /* Use this slave address */
#define I2C_SLAVE_FORCE 0x0706  /* Use this slave address, even if it is already in use by a driver! */
#define I2C_TENBIT  0x0704  /* 0 for 7 bit addrs, != 0 for 10 bit */

#endif
mpu6050_op_rw.c
#include "mpu6050.h"
static int read_data_from_mpu6050(int fd,unsigned char reg ,unsigned char *pdata)
{
	int ret = 0 ;
	unsigned char buf[1] = {reg};

	/*内置i2c设备文件相关c文件已经完成I2C通道寻址,只需要说明寄存器地址就行*/
	ret = write(fd,buf,1);
	if(ret != 1)
	{
		printf("write reg failed,in read_data_from_mpu6050\n");
		return -1;
	}
	/*read data*/
	buf[0] = 0;
	ret = read(fd,buf,1);
	if(ret != 1)
	{
		printf("read reg failed,in read_data_from_mpu6050\n");
		return -1;
	}
	
//	printf("************%c \n",buf[0]);

	*pdata = buf[0];
	return 0;

}

static int write_data_to_mpu6050(int fd,unsigned char reg ,unsigned char data)
{
//	int ret= -1;
	unsigned char buf[2] = {reg,data};

	int ret = 0;	
	ret = write(fd,buf,2);

	if(ret != 2)
	{
		printf("write reg failed,in write_data_to_mpu6050\n");
		return -1;
	}
	return 0;
}


int init_mpu6050(int fd)
{
	int ret = 0;

	ret =ioctl(fd,I2C_TENBIT,0);//从设备地址两种,7位或10位。一般置零,表示7位从设备地址。
	if(ret < 0){
		printf("ioctl I2C_TENBIT failed,in init_mpu6050\n");
		return -1;
	}

	ret =ioctl(fd,I2C_SLAVE,0x68);
	if(ret < 0){
		printf("ioctl I2C_SLAVE failed,in init_mpu6050\n");
		return -1;
	}

	ret = write_data_to_mpu6050(fd,PWR_MGMT_1,0x00);//电源管理,典型值:0x00(正常启用)
	ret += write_data_to_mpu6050(fd,SMPLRT_DIV,0x07);//陀螺仪采样率,典型值:0x07(125Hz)
	ret += write_data_to_mpu6050(fd,ACCEL_CONFIG,0x19);//加速计自检、测量范围,典型值:0x19(不自检,+/-G)
	ret += write_data_to_mpu6050(fd,GYRO_CONFIG,0xF8);//陀螺仪自检及测量范围,典型值:0xF8(不自检,+/-2000deg/s)
//	ret += write_data_to_mpu6050(fd,CONFIG,0x06);//低通滤波频率,典型值:0x06(5Hz)
	if(ret < 0){
		printf("write init data to mpu6050 failed ,in init_mpu6050\n");
		return -1;
	}
	return 0;
}

int read_accelx(int fd){
	unsigned short val = 1;
	unsigned char d = 0;
	int ret = 0;

	ret = read_data_from_mpu6050(fd,ACCEL_XOUT_L,&d);
	val = d ;

	ret = read_data_from_mpu6050(fd,ACCEL_XOUT_H,&d);
	val |= d << 8;

	if(ret < 0)
	{
		printf("read accel x value failed,in read_accelx\n");
		return -1;
	}
	else
	{
		return val;
	}
}

int read_accely(int fd){
	unsigned short val = 0;
	unsigned char d = 0;
	int ret = 0;

	ret = read_data_from_mpu6050(fd,ACCEL_YOUT_L,&d);
	val = d ;

	ret = read_data_from_mpu6050(fd,ACCEL_YOUT_H,&d);
	val |= d << 8;

	if(ret < 0)
	{
		printf("read accel y value failed,in read_accely\n");
		return -1;
	}
	else
	{
		return val;
	}
}
int read_accelz(int fd){
	unsigned short val = 0;
	unsigned char d = 0;
	int ret = 0;

	ret = read_data_from_mpu6050(fd,ACCEL_ZOUT_L,&d);
	val = d ;

	ret = read_data_from_mpu6050(fd,ACCEL_ZOUT_H,&d);
	val |= d << 8;

	if(ret < 0)
	{
		printf("read accel z value failed,in read_accelz\n");
		return -1;
	}
	else
	{
		return val;
	}
}

int read_temp(int fd){
	unsigned short val = 0;
	unsigned char d = 0;
	int ret = 0;

	ret = read_data_from_mpu6050(fd,TEMP_OUT_L,&d);
	val = d ;

	ret = read_data_from_mpu6050(fd,TEMP_OUT_H,&d);
	val |= d << 8;

	if(ret < 0)
	{
		printf("read accel 	temp value failed,in read_temp\n");
		return -1;
	}
	else
	{
		return val;
	}
}
int read_gyrox(int fd){
	unsigned short val = 0;
	unsigned char d = 0;
	int ret = 0;

	ret = read_data_from_mpu6050(fd,GYRO_XOUT_L,&d);
	val = d ;

	ret = read_data_from_mpu6050(fd,GYRO_XOUT_H,&d);
	val |= d << 8;

	if(ret < 0)
	{
		printf("read gyro x  value failed,in read_gyrox\n");
		return -1;
	}
	else
	{
		return val;
	}
}
int read_gyroy(int fd){
	unsigned short val = 0;
	unsigned char d = 0;
	int ret = 0;

	ret = read_data_from_mpu6050(fd,GYRO_YOUT_L,&d);
	val = d ;

	ret = read_data_from_mpu6050(fd,GYRO_YOUT_H,&d);
	val |= d << 8;

	if(ret < 0)
	{
		printf("read gyro y  value failed,in read_gyroy\n");
		return -1;
	}
	else
	{
		return val;
	}
}
int read_gyroz(int fd){
	unsigned short val = 0;
	unsigned char d = 0;
	int ret = 0;

	ret = read_data_from_mpu6050(fd,GYRO_ZOUT_L,&d);
	val = d ;

	ret = read_data_from_mpu6050(fd,GYRO_ZOUT_H,&d);
	val |= d << 8;

	if(ret < 0)
	{
		printf("read gyro Z  value failed,in read_gyroz\n");
		return -1;
	}
	else
	{
		return val;
	}
}

5.2.2 调用ioctl实现接收、发送

见实例代码

缺点:

  1. 需要应用程序开发人员查阅原理图和芯片手册,增加了他们的开发负担
  2. 开发出的应用程序缺乏可移植性
mpu6050.h
#ifndef MPU_6050_H
#define MPU_6050_H
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>


int init_mpu6050(int fd);
int read_accelx(int fd);
int read_accely(int fd);
int read_accelz(int fd);
int read_temp(int fd);
int read_gyrox(int fd);
int read_gyroy(int fd);
int read_gyroz(int fd);

#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

//拷贝自:~/fs4412/linux-3.14/include/uapi/linux/i2c-dev.h  
#define I2C_SLAVE   0x0703  /* Use this slave address */
#define I2C_SLAVE_FORCE 0x0706  /* Use this slave address, even if it is already in use by a driver! */
#define I2C_TENBIT  0x0704  /* 0 for 7 bit addrs, != 0 for 10 bit */
#define I2C_RDWR    0x0707  /* Combined R/W transfer (one STOP only) */                      


//拷贝自:~/fs4412/linux-3.14/include/uapi/linux/i2c.h  
 
struct i2c_msg {                                                                                          
  unsigned  short addr; /* slave address            */
  unsigned  short flags;/*利用下边两个宏的后两位,控制数据的位数(7或10) 和 读写方向*/
#define I2C_M_TEN       0x0010  /* this is a ten bit chip address */
#define I2C_M_RD        0x0001  /* read data, from slave to master */
  unsigned short len;      /* msg length               */
  unsigned   char *buf;      /* pointer to msg data          */
};

/* This is the structure as used in the I2C_RDWR ioctl call */
struct i2c_rdwr_ioctl_data {
	struct i2c_msg *msgs;    /* pointers to i2c_msgs */
	unsigned int nmsgs;            /* number of i2c_msgs */
};



#endif
main.c
#include "mpu6050.h"
int main(int argc, char *argv[])
{
	int fd = -1;
	if(argc < 2)
	{
		printf("Argumen is too few\n");
		return 1;
	}

	//open
	fd = open(argv[1],O_RDWR);
	if(fd < 0){
		printf("open %s failed\n",argv[1]);
		return 2;
	}
 

	//init mpu6050
	init_mpu6050(fd);


	while(1)
	{
		sleep(2);
		/*read and print data form 6050*/
		printf("Accel-X:%d\n",read_accelx(fd));
		printf("Accel-Y:%d\n",read_accely(fd));
		printf("Accel-Z:%d\n",read_accelz(fd));
		printf("Temp:%d\n",read_temp(fd));
		printf("Gyro-X:%d\n",read_gyrox(fd));
		printf("Gyro-Y:%d\n",read_gyroy(fd));
		printf("Gyro-Z:%d\n",read_gyroz(fd));

		printf("\n");
	}


	close(fd);
	fd = -1;
	
	return 0;
}
mpu6050_op_rw.c
#include "mpu6050.h"
static int read_data_from_mpu6050(int fd,unsigned char slave,unsigned char reg ,unsigned char *pdata)
{
	struct i2c_rdwr_ioctl_data work = {NULL};
	struct i2c_msg msgs[2] = {{0}};
	unsigned char buf1[1]= {reg};
	unsigned char buf2[1]= {0};
	int ret = 0;
	
	work.msgs = msgs;
	work.nmsgs = 2;

	msgs[0].addr = slave;
	msgs[0].flags = 0;
	msgs[0].buf = buf1;
	msgs[0].len = 1;

	msgs[1].addr = slave;
	msgs[1].flags = I2C_M_RD;
	msgs[1].buf = buf2;
	msgs[1].len = 1;

	ret = ioctl(fd,I2C_RDWR,&work);
	if(ret < 0)
	{
		printf("ioctl I2C_RDWR failed,in read_data_from_mpu6050\n");
		return -1;
	}
	else
	{
		*pdata = buf2[0];
		return 0;
	}

	return 0;
}

static int write_data_to_mpu6050(int fd,unsigned char slave,unsigned char reg ,unsigned char data)
{
	struct i2c_rdwr_ioctl_data work = {NULL};
	struct i2c_msg msg = {0};
	unsigned char buf[2]= {reg,data};
	int ret = 0;
	
	work.msgs= &msg;
	work.nmsgs = 1;

	msg.addr = slave;
	msg.flags = 0;
	msg.buf = buf;
	msg.len = 2;

	ret = ioctl(fd,I2C_RDWR,&work);
	if(ret < 0)
	{
		printf("ioctl I2C_RDWR failed,in write_data_from_mpu6050\n");
		return -1;
	}
	else
	{
		return 0;
	}

	return 0;
}


int init_mpu6050(int fd)
{
	int ret = 0;

	ret =ioctl(fd,I2C_TENBIT,0);//从设备地址两种,7位或10位。一般置零,表示7位从设备地址。
	if(ret < 0){
		printf("ioctl I2C_TENBIT failed,in init_mpu6050\n");
		return -1;
	}

	ret =ioctl(fd,I2C_SLAVE,0x68);
	if(ret < 0){
		printf("ioctl I2C_SLAVE failed,in init_mpu6050\n");
		return -1;
	}

	ret = write_data_to_mpu6050(fd,0x68,PWR_MGMT_1,0x00);//电源管理,典型值:0x00(正常启用)
	ret += write_data_to_mpu6050(fd,0x68,SMPLRT_DIV,0x07);//陀螺仪采样率,典型值:0x07(125Hz)
	ret += write_data_to_mpu6050(fd,0x68,ACCEL_CONFIG,0x19);//加速计自检、测量范围,典型值:0x19(不自检,+/-G)
	ret += write_data_to_mpu6050(fd,0x68,GYRO_CONFIG,0xF8);//陀螺仪自检及测量范围,典型值:0xF8(不自检,+/-2000deg/s)
//	ret += write_data_to_mpu6050(fd,CONFIG,0x06);//低通滤波频率,典型值:0x06(5Hz)
	if(ret < 0){
		printf("write init data to mpu6050 failed ,in init_mpu6050\n");
		return -1;
	}
	return 0;
}

int read_accelx(int fd){
	unsigned short val = 1;
	unsigned char d = 0;
	int ret = 0;

	ret = read_data_from_mpu6050(fd,0x68,ACCEL_XOUT_L,&d);
	val = d ;

	ret = read_data_from_mpu6050(fd,0x68,ACCEL_XOUT_H,&d);
	val |= d << 8;

	if(ret < 0)
	{
		printf("read accel x value failed,in read_accelx\n");
		return -1;
	}
	else
	{
		return val;
	}
}

int read_accely(int fd){
	unsigned short val = 0;
	unsigned char d = 0;
	int ret = 0;

	ret = read_data_from_mpu6050(fd,0x68,ACCEL_YOUT_L,&d);
	val = d ;

	ret = read_data_from_mpu6050(fd,0x68,ACCEL_YOUT_H,&d);
	val |= d << 8;

	if(ret < 0)
	{
		printf("read accel y value failed,in read_accely\n");
		return -1;
	}
	else
	{
		return val;
	}
}
int read_accelz(int fd){
	unsigned short val = 0;
	unsigned char d = 0;
	int ret = 0;

	ret = read_data_from_mpu6050(fd,0x68,ACCEL_ZOUT_L,&d);
	val = d ;

	ret = read_data_from_mpu6050(fd,0x68,ACCEL_ZOUT_H,&d);
	val |= d << 8;

	if(ret < 0)
	{
		printf("read accel z value failed,in read_accelz\n");
		return -1;
	}
	else
	{
		return val;
	}
}

int read_temp(int fd){
	unsigned short val = 0;
	unsigned char d = 0;
	int ret = 0;

	ret = read_data_from_mpu6050(fd,0x68,TEMP_OUT_L,&d);
	val = d ;

	ret = read_data_from_mpu6050(fd,0x68,TEMP_OUT_H,&d);
	val |= d << 8;

	if(ret < 0)
	{
		printf("read accel 	temp value failed,in read_temp\n");
		return -1;
	}
	else
	{
		return val;
	}
}
int read_gyrox(int fd){
	unsigned short val = 0;
	unsigned char d = 0;
	int ret = 0;

	ret = read_data_from_mpu6050(fd,0x68,GYRO_XOUT_L,&d);
	val = d ;

	ret = read_data_from_mpu6050(fd,0x68,GYRO_XOUT_H,&d);
	val |= d << 8;

	if(ret < 0)
	{
		printf("read gyro x  value failed,in read_gyrox\n");
		return -1;
	}
	else
	{
		return val;
	}
}
int read_gyroy(int fd){
	unsigned short val = 0;
	unsigned char d = 0;
	int ret = 0;

	ret = read_data_from_mpu6050(fd,0x68,GYRO_YOUT_L,&d);
	val = d ;

	ret = read_data_from_mpu6050(fd,0x68,GYRO_YOUT_H,&d);
	val |= d << 8;

	if(ret < 0)
	{
		printf("read gyro y  value failed,in read_gyroy\n");
		return -1;
	}
	else
	{
		return val;
	}
}
int read_gyroz(int fd){
	unsigned short val = 0;
	unsigned char d = 0;
	int ret = 0;

	ret = read_data_from_mpu6050(fd,0x68,GYRO_ZOUT_L,&d);
	val = d ;

	ret = read_data_from_mpu6050(fd,0x68,GYRO_ZOUT_H,&d);
	val |= d << 8;

	if(ret < 0)
	{
		printf("read gyro Z  value failed,in read_gyroz\n");
		return -1;
	}
	else
	{
		return val;
	}
}

六、I2C总线二级外设驱动开发方法

第6小节编写驱动的方法因为缺点太大,很少采用,很明显,还是更加习惯于在内核中编写二级外设的驱动程序,然后让应用程序直接去操作驱动程序对应的设备文件。如下:

  1. 查阅原理图以便得知二级外设挂在哪条I2C总线上、二级外设的身份标识(二级外设自身的地址)

  2. 参照platform样式搭建二级外设驱动框架

  3. 查询二级外设芯片手册以便得知驱动需要用到的寄存器地址

    注意:

    1. 此处寄存器是指二级外设内部的寄存器,每个寄存器在芯片手册里有个对应编号(也被称为地址),但不是内存地址,特别提醒此寄存器不是SOC芯片内部参与内存统一编址的寄存器,更不是ARM核-CPU的寄存器
    2. 通过调用i2c_tranfer函数完成与相应寄存器的数据交互
    3. 参照字符驱动完成其余代码编写
    4. 创建对应的i2c_client对象

    linux-3.14\Documentation\i2c\instantiating-devices

    匹配方式:

    1. 名称匹配

    2. 设备树匹配

    3. ACPI匹配

      Advanced Configuration and Power Management Interface 高级配置和电源管理接口

      PC机平台采用的一种硬件配置接口

i2c二级外设驱动框架:

//其它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总线二级外设驱动开发之名称匹配

7.1 理论

这种匹配方式需要自己创建i2c_client对象

创建i2c_client对象有三种方式:

  1. i2c_register_board_info //本fs4412教学用开发板不支持该方法

    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框架:

   #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);//引用计数减1
       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");
  1. i2c_new_probed_device

    i2c二级外设client框架:不明确二级外设地址,但是知道是可能几个值之一的情况下可用

   #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); // 最后一个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");

7.2 代码

7.2.1 精确匹配
mpu6050.h
#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
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),要跟驱动列表或者说id成员的中的name一样

};

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/mm.h>
#include <linux/slab.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include "mpu6050.h"
#define BUF_LEN 100

#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;

typedef struct{
	struct cdev mydev;
	struct i2c_client *pclt;


}MYDEV;
MYDEV *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;
	}
//	printk("***********rxbuf[0]: %x \n",rxbuf[0]);
	return rxbuf[0];

}

int mpu6050_write_byte(struct i2c_client *pclt,unsigned char reg,unsigned char var)
{
	int ret = 0;
	char txbuf[2] = {reg,var};
	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_read_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,MYDEV,mydev);
	printk("mpu6050_open is called\n");
	return 0 ;
}


int mpu6050_close(struct inode *pnode,struct file *pfile){
	
	printk("mpu6050_open is close\n");
	return 0 ;
}




long mpu6050_ioctl(struct file *pfile,unsigned int cmd ,unsigned long arg){
	MYDEV *pmydev = (MYDEV*)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)<<8;
		data.gyro.x = mpu6050_read_byte(pmydev->pclt,GYRO_XOUT_H)<<8;
		data.gyro.y = mpu6050_read_byte(pmydev->pclt,GYRO_YOUT_L)<<8;
		data.gyro.y = mpu6050_read_byte(pmydev->pclt,GYRO_YOUT_H)<<8;
		data.gyro.z = mpu6050_read_byte(pmydev->pclt,GYRO_ZOUT_L)<<8;
		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:
		printk("The CMD is uknow\n");
		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,0X01);
	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,//内核在插入模块的时候,在内核中生成的struct 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);//分离出自动分配的主设备号
	}

	//使用kmalloc方式动态分配内存
	pgmydev = (MYDEV*)kmalloc(sizeof(MYDEV),GFP_KERNEL);
	if(NULL == pgmydev){
		unregister_chrdev_region(devno,mpu6050_num);
		printk("kmalloc failed");
		return -1;
	}


	pgmydev->pclt = pclt;

	//初始设备操作函数集
	cdev_init(&pgmydev->mydev,&myops);
	//将struct cdev 对象添加到内核对应的数据结构里
	pgmydev->mydev.owner = THIS_MODULE;//推测不必要
	cdev_add(&pgmydev->mydev,devno,mpu6050_num);

	//ioremap
//	ioremap_ledreg(pgmydev);	 
	//con-register set output
//	set_output_ledconreg(pgmydev);
	printk("mpu6050 will init\n");

	init_mpu6050(pgmydev->pclt);

	return 0;
}

static int  mpu6050_remove(struct i2c_client *pclt)
{
	dev_t devno = MKDEV(major,minor);

	//iounmap
//	iounmap_ledreg(pgmydev);

	cdev_del(&pgmydev->mydev);
	unregister_chrdev_region(devno,mpu6050_num);

	kfree(pgmydev);

	printk("mpu6050 will exit\n");

	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_init);
module_exit(mpu6050_exit);
#else

module_i2c_driver(mpu6050_driver);
#endif
MODULE_LICENSE("GPL");
7.2.2 模糊匹配

只要client 做了修改

mpu6050_client.c
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/i2c.h>

static unsigned short mpu6050_addr_list[] = 
{
	0x68,
	0x69,
	0x70,
	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");//要跟驱动列表或者说id成员的中的name一样
 

	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;
	}


	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");

八、I2C总线二级外设驱动开发之设备树匹配

mpu6050设备树节点

interrupt-parent 属性可以不设置,/中断也可以不用,因为经常产生信号,使用中断不太好。/

8.1 代码
设备树
      i2c@138B0000 { 
          #address-cells = <1>;
          #size-cells = <0>;
          samsung,i2c-sda-delay = <100>;
          samsung,i2c-max-bus-freq = <20000>;
          pinctrl-0 = <&i2c5_bus>;
          pinctrl-names = "default";
          status = "okay";                                                                              
   
          mpu6050-3-asix@68{
              compatible = "invensense,mpu6050";
             reg = <0x68>;
              interrupt-parent = <&gpx3>;/*中断也可以不用,因为经常产生信号,使用中断不太好。*/
              interrupt = <3 2>;
          };
  
     };

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/mm.h>
#include <linux/slab.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include "mpu6050.h"
#define BUF_LEN 100

#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;

typedef struct{
	struct cdev mydev;
	struct i2c_client *pclt;


}MYDEV;
MYDEV *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;
	}
//	printk("***********rxbuf[0]: %x \n",rxbuf[0]);
	return rxbuf[0];

}

int mpu6050_write_byte(struct i2c_client *pclt,unsigned char reg,unsigned char var)
{
	int ret = 0;
	char txbuf[2] = {reg,var};
	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_read_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,MYDEV,mydev);
	printk("mpu6050_open is called\n");
	return 0 ;
}


int mpu6050_close(struct inode *pnode,struct file *pfile){
	
	printk("mpu6050_open is close\n");
	return 0 ;
}




long mpu6050_ioctl(struct file *pfile,unsigned int cmd ,unsigned long arg){
	MYDEV *pmydev = (MYDEV*)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)<<8;
		data.gyro.x = mpu6050_read_byte(pmydev->pclt,GYRO_XOUT_H)<<8;
		data.gyro.y = mpu6050_read_byte(pmydev->pclt,GYRO_YOUT_L)<<8;
		data.gyro.y = mpu6050_read_byte(pmydev->pclt,GYRO_YOUT_H)<<8;
		data.gyro.z = mpu6050_read_byte(pmydev->pclt,GYRO_ZOUT_L)<<8;
		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:
		printk("The CMD is uknow\n");
		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,0X01);
	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,//内核在插入模块的时候,在内核中生成的struct 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);//分离出自动分配的主设备号
	}

	//使用kmalloc方式动态分配内存
	pgmydev = (MYDEV*)kmalloc(sizeof(MYDEV),GFP_KERNEL);
	if(NULL == pgmydev){
		unregister_chrdev_region(devno,mpu6050_num);
		printk("kmalloc failed");
		return -1;
	}


	pgmydev->pclt = pclt;

	//初始设备操作函数集
	cdev_init(&pgmydev->mydev,&myops);
	//将struct cdev 对象添加到内核对应的数据结构里
	pgmydev->mydev.owner = THIS_MODULE;//推测不必要
	cdev_add(&pgmydev->mydev,devno,mpu6050_num);

	//ioremap
//	ioremap_ledreg(pgmydev);	 
	//con-register set output
//	set_output_ledconreg(pgmydev);
	printk("mpu6050 will init\n");

	init_mpu6050(pgmydev->pclt);

	return 0;
}

static int  mpu6050_remove(struct i2c_client *pclt)
{
	dev_t devno = MKDEV(major,minor);

	//iounmap
//	iounmap_ledreg(pgmydev);

	cdev_del(&pgmydev->mydev);
	unregister_chrdev_region(devno,mpu6050_num);

	kfree(pgmydev);

	printk("mpu6050 will exit\n");

	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_init);
module_exit(mpu6050_exit);
#else

module_i2c_driver(mpu6050_driver);
#endif
MODULE_LICENSE("GPL");

D5 Input子系统

一、input子系统基本框架

Linux内核为了两个目的:

  1. 简化纯输入类外设(如:键盘、鼠标、游戏杆、轨迹球、触摸屏。。。等等)的驱动开发
  2. 统一输入类外设产生的数据格式(struct input_event),更加方便应用层编程

设计了输入子系统
框架图

事件处理层:接收来自核心层上报的事件,并选择对应的handler(事件处理器 struct input_handler)去处理。内核维护着多个事件处理器对象,每个input_handler对象专门处理一类事件,所有产生同类事件的设备驱动共用同一个handler。

事件:
	按键:
	相对坐标值:鼠标产生
	绝对坐标值:MPU6050产生
	...

核心层:负责连接设备驱动层和事件处理层,为设备驱动层提供输入设备驱动的接口(struct input_dev)以及输入设备驱动的注册函数(input_register_device),为事件处理层提供输入事件驱动的接口;通知事件处理层对事件进行处理。

设备驱动层:主要实现获取硬件设备的数据信息(包括触摸屏被按下、按下位置、鼠标移动、键盘按下等等),并转换为核心层定义的规范事件后提交给核心层,该层每个设备对应一个struct input_dev对象,

二、驱动开发步骤

/*init或probe函数中:
1. 创建struct input_dev对象input_allocate_device
2. 设置事件类型以及相关参数set_bit
3. 注册struct input_dev对象input_register_device
*/

/*exit或remove函数中:
1. 注销struct input_dev对象input_unregister_device
2. 销毁struct input_dev对象input_free_device
*/

/*上报事件
	两种事件上报方式:
	1. 对有中断支持的输入设备:在其中断处理函数(上半部或下半部)中上报事件
	2. 对无中断支持的输入设备:使用workqueue循环定时上报(struct delayed_work)
	主要函数:
	input_event//上报分量,并没有真的上报
	input_report_abs//上报分量,上报绝对坐标,并没有真的上报
	input_sync//统一上报
*/

相关接口:

/*_init*/
struct input_dev *input_allocate_device(void)//创建对象

void set_bit(struct input_dev *dev,unsigned long whichbits)//设置事件类型

void input_set_abs_params(struct input_dev *dev,unsigned int axis,int min,int max,int fuzz,int flat)//设置绝对坐标相关参数

int input_register_device(struct input_dev *dev)//注册input设备到内核

/*_exit*/
void input_unregister_device(struct input_dev *dev)
void input_free_device(struct input_dev *dev)

/*上报事件*/
void input_event(struct input_dev *,unsigned int t,unsigned int c,int v)//统一上报

void input_report_key(struct input_dev *,unsigned int c,int v) //上报按键事件
void input_report_abs(struct input_dev *,unsigned int c,int v)//上报绝对坐标事件
    
void input_sync(struct input_dev *)//上报完成后需要调用这些函数来通知系统处理完整事件

/*应用层数据类型*/
struct input_event {
    struct timeval time;       // 时间戳  
    __u16 type;             // 事件类型  按键				 坐标
    __u16 code;             // 哪个分值	键值				 x
    __s32 value;            // 具体值    按键状态(上升或抬起) 具体值
};

三、key2-input版代码解析

设备树

/home/linux/fs4412/linux-3.14/arch/arm/boot/dts/exynos4412-fs4412.dts

      mykey2_node {
          compatible = "mykey2,key2";
          key2-gpio = <&gpx1 1 0>;
          interrupt-parent = <&gpx1>;
          interrupts = <1 3>;
     };

fs4412key2.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/poll.h>
#include <linux/mm.h>
#include <linux/input.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/slab.h>
#include <asm/uaccess.h>
#include <asm/gpio.h>
#include<linux/interrupt.h>
#include<linux/delay.h>
//定义自定义设备结构体



typedef struct {
	struct input_dev *pdev;
	
	int gpio;
	int irqno;

}MYDEV;

MYDEV *pgmydev= NULL;

//中断处理函数
irqreturn_t key2_irq_handle(int no,void *arg){
	MYDEV *pmydev = (MYDEV*)arg;
	int status1 = 0;
	int status2 = 0;

	status1 = gpio_get_value(pmydev->gpio);
	mdelay(1);
	status2 = gpio_get_value(pmydev->gpio);

	if(status1 != status2){ 
		return IRQ_NONE;
	}
	
	if(status1)
	{
		input_event(pmydev->pdev,EV_KEY,KEY_2,0);
		input_sync(pmydev->pdev);
	}
	else
	{
		input_event(pmydev->pdev,EV_KEY,KEY_2,1);
		input_sync(pmydev->pdev);

	}

	return IRQ_HANDLED;
}


//初始化
int __init fs4412key2_init(void){
	int ret = 0;

	struct device_node *pnode = NULL;
	pnode = of_find_node_by_path("/mykey2_node");
	if(NULL == pnode){
		printk("find node failed\n");
		return -1;
	}

	//使用kmalloc方式动态分配内存
	pgmydev = (MYDEV*)kmalloc(sizeof(MYDEV),GFP_KERNEL);

	//获取gpio编号
	pgmydev->gpio = of_get_named_gpio(pnode,"key2-gpio",0);

	/* 1. 分配一个input_dev结构体 */
	pgmydev->pdev = input_allocate_device();
    pgmydev->pdev->name = "keys";  //该行也可不要,只是会出现input: Unspecified device as /devices/virtual/input/input1,不影响程序使用
	set_bit(EV_KEY,pgmydev->pdev->evbit);//指定按键事件,EV_KEY 事件类型代码(宏定义在<linux/input.h);evbit是一个数组,evbit里面哪一位相对应的按键事件置1
	set_bit(KEY_2,pgmydev->pdev->keybit);//指定想要上报的按键,键值是按键2,KEY_2,需要和应用层保持一致 事件代码 (宏定义在<linux/input.h);

	ret = input_register_device(pgmydev->pdev);//把dev 注册到系统


	//获取中断号
	pgmydev->irqno = irq_of_parse_and_map(pnode,0);
	//申请中断号
	ret = request_irq(pgmydev->irqno,key2_irq_handle,IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,"FS4412key2",pgmydev);
	if(ret){
		printk("request_irq failed\n");
		input_unregister_device(pgmydev->pdev);//把dev 从系统注销
		input_free_device(pgmydev->pdev);//回收设备
		kfree(pgmydev);
		pgmydev = NULL;
		return -1;
	}

	printk("fs4412key2 will init\n");

	return 0;
}
//退出

void __exit fs4412key2_exit(void){


	free_irq(pgmydev->irqno,pgmydev);
	input_unregister_device(pgmydev->pdev);//把dev 从系统注销
	input_free_device(pgmydev->pdev);//回收设备

	kfree(pgmydev);
	pgmydev = NULL;
	printk("fs4412key2 will exit\n");
}

MODULE_LICENSE("GPL");

module_init(fs4412key2_init);
module_exit(fs4412key2_exit);

testapp.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/input.h>

int main(int argc, char *argv[])
{
	int fd = -1;
	struct input_event evt;

	if(argc < 2)
	{
		printf("Argumen is too few\n");
		return 1;
	}

	//open
	fd = open(argv[1],O_RDWR);
	if(fd < 0){
		printf("open %s failed\n",argv[1]);
		return 2;
	}
 


	while(1)
	{
		read(fd,&evt,sizeof(evt));
		if(evt.type == EV_KEY && evt.code == KEY_2)
		{
			if(evt.value)
			{
				printf("KEY2 DOWN\n");
			}
			else
			{
				printf("key2 UP\n");
			}

		}
	}


	close(fd);
	fd = -1;
	
	return 0;
}

四、mpu6050-input 版硬编码代码解析

设备树不变
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/mm.h>
#include <linux/input.h>
#include <linux/slab.h>
#include <asm/uaccess.h>
#include <linux/io.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
typedef struct{
	struct input_dev *pinput;
	struct i2c_client *pclient;
	
	struct delayed_work work;//类似于中断下半部中的work,但是具有定时功能

}MYDEV;
MYDEV *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;
	}
//	printk("***********rxbuf[0]: %x \n",rxbuf[0]);
	return rxbuf[0];

}

int mpu6050_write_byte(struct i2c_client *pclt,unsigned char reg,unsigned char var)
{
	int ret = 0;
	char txbuf[2] = {reg,var};
	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_read_byte\n",ret);
		return ret;
	}
	return 0;


}


void mpu6050_work_func(struct work_struct *pwk)
{
	MYDEV  *pmydev = container_of((struct delayed_work*)pwk,MYDEV,work);
	unsigned short ax =0;
	unsigned short ay =0;
	unsigned short az =0;
	unsigned short gx =0;
	unsigned short gy =0;
	unsigned short gz =0;
	unsigned short temp =0;


	ax = mpu6050_read_byte(pmydev->pclient,ACCEL_XOUT_L);
	ax = mpu6050_read_byte(pmydev->pclient,ACCEL_XOUT_H)<<8;
	input_report_abs(pmydev->pinput,ABS_X,ax);

	ay = mpu6050_read_byte(pmydev->pclient,ACCEL_YOUT_L);
	ay = mpu6050_read_byte(pmydev->pclient,ACCEL_YOUT_H)<<8;
	input_report_abs(pmydev->pinput,ABS_Y,ay);
	az = mpu6050_read_byte(pmydev->pclient,ACCEL_ZOUT_L);
	az = mpu6050_read_byte(pmydev->pclient,ACCEL_ZOUT_H)<<8;
	input_report_abs(pmydev->pinput,ABS_Z,az);

	gx = mpu6050_read_byte(pmydev->pclient,GYRO_XOUT_L)<<8;
	gx = mpu6050_read_byte(pmydev->pclient,GYRO_XOUT_H)<<8;
	input_report_abs(pmydev->pinput,ABS_RX,gx);
	gy = mpu6050_read_byte(pmydev->pclient,GYRO_YOUT_L)<<8;
	gy = mpu6050_read_byte(pmydev->pclient,GYRO_YOUT_H)<<8;
	input_report_abs(pmydev->pinput,ABS_RY,gx);
	gz = mpu6050_read_byte(pmydev->pclient,GYRO_ZOUT_L)<<8;
	gz = mpu6050_read_byte(pmydev->pclient,GYRO_ZOUT_H)<<8;
	input_report_abs(pmydev->pinput,ABS_RZ,gz);

	temp = mpu6050_read_byte(pmydev->pclient,TEMP_OUT_L);
	temp = mpu6050_read_byte(pmydev->pclient,TEMP_OUT_H)<<8;
	input_report_abs(pmydev->pinput,ABS_MISC,temp);

	input_sync(pmydev->pinput);
	schedule_delayed_work(&pgmydev->work,msecs_to_jiffies(10000));
}

void init_mpu6050(struct i2c_client *pclt)
{
	mpu6050_write_byte(pclt,PWR_MGMT_1,0X01);
	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);

}


static int mpu6050_probe(struct i2c_client *pclt,const struct i2c_device_id *pid)
{

	int ret = 0;

	//使用kmalloc方式动态分配内存
	pgmydev = (MYDEV*)kmalloc(sizeof(MYDEV),GFP_KERNEL);
	if(NULL == pgmydev){
		printk("kmalloc failed");
		return -1;
	}


	pgmydev->pclient = pclt;

	init_mpu6050(pgmydev->pclient);
	

	pgmydev->pinput = input_allocate_device();
	pgmydev->pinput->name = "wangzhimin";


	set_bit(EV_ABS,pgmydev->pinput->evbit);
	input_set_abs_params(pgmydev->pinput,ABS_X,-32768,32767,0,0);
	input_set_abs_params(pgmydev->pinput,ABS_Y,-32768,32767,0,0);
	input_set_abs_params(pgmydev->pinput,ABS_Z,-32768,32767,0,0);
	input_set_abs_params(pgmydev->pinput,ABS_RX,-32768,32767,0,0);
	input_set_abs_params(pgmydev->pinput,ABS_RY,-32768,32767,0,0);
	input_set_abs_params(pgmydev->pinput,ABS_RZ,-32768,32767,0,0);
	input_set_abs_params(pgmydev->pinput,ABS_MISC,-32768,32767,0,0);
	
	ret = input_register_device(pgmydev->pinput);
	if(ret)
	{
		printk("input_register_device failed\n");

		input_free_device(pgmydev->pinput);
		pgmydev->pinput = NULL;
		kfree(pgmydev);
		pgmydev = NULL;
		return -1;
	}

	INIT_DELAYED_WORK(&pgmydev->work,mpu6050_work_func);

	schedule_delayed_work(&pgmydev->work,msecs_to_jiffies(1000));//提交任务到工作队列
	printk("mpu6050 will init\n");
	return 0;
}

static int  mpu6050_remove(struct i2c_client *pclt)
{
	cancel_delayed_work(&pgmydev->work);	//删除提交到工作队列的任务
	input_unregister_device(pgmydev->pinput);

	input_free_device(pgmydev->pinput);
	pgmydev->pinput = NULL;

	kfree(pgmydev);
	pgmydev = NULL;

	printk("mpu6050 will exit\n");

	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_init);
module_exit(mpu6050_exit);
#else

module_i2c_driver(mpu6050_driver);
#endif
MODULE_LICENSE("GPL");
testmpu6050.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/input.h>

int main(int argc, char *argv[])
{
	int fd = -1;
	struct input_event evt;

	if(argc < 2)
	{
		printf("Argumen is too few\n");
		return 1;
	}

	//open
	fd = open(argv[1],O_RDWR);
	if(fd < 0){
		printf("open %s failed\n",argv[1]);
		return 2;
	}
 


	while(1)
	{
		read(fd,&evt,sizeof(evt));
		if(evt.type == EV_ABS)
		{
			switch(evt.code)
			{
			case ABS_X:
				printf("Accel-x:%d\n",evt.value);
				break;
			case ABS_Y:
				printf("Accel-y:%d\n",evt.value);
				break;
			case ABS_Z:
				printf("Accel-z:%d\n",evt.value);
				break;
			case ABS_RX:
				printf("Gyro-x:%d\n",evt.value);
				break;
			case ABS_RY:
				printf("Gyro-y:%d\n",evt.value);
				break;
			case ABS_RZ:
				printf("Gyro-z:%d\n",evt.value);
				break;
			case ABS_MISC:
				printf("Temp:%d\n",evt.value);
				break;
			}
		}
	}


	close(fd);
	fd = -1;
	
	return 0;
}

总结:
字符设备面向应用层。
网络设备面向协议栈。
块设备面向文件系统。

注脚


  1. linux下有专门的文件系统用来对设备进行管理,devfs和sysfs就是其中两种。
    在2.6内核以前一直使用的是devfs,devfs挂载于/dev目录下,提供了一种类似于文件的方法来管理位于/dev目录下的所有设备,我们知道 /dev目录下的每一个文件都对应的是一个设备,至于当前该设备存在与否先且不论,而且这些特殊文件是位于根文件系统上的,在制作文件系统的时候我们就已 经建立了这些设备文件,因此通过操作这些特殊文件,可以实现与内核进行交互。但是devfs文件系统有一些缺点,例如:不确定的设备映射,有时一个设备映 射的设备文件可能不同,例如我的U盘可能对应sda有可能对应sdb;没有足够的主/辅设备号,当设备过多的时候,显然这会成为一个问题;/dev目录下 文件太多而且不能表示当前系统上的实际设备;命名不够灵活,不能任意指定等等。 ↩︎

  2. Platform总线是一种面向硬件平台的总线,用于将设备驱动程序与底层硬件进行连接。通常,平台设备是嵌入式系统中的一些固定的硬件设备,例如各种传感器、LED、显示器等,这些设备会通过特定的接口与CPU连接。
    相比之下,I2C总线和SPI总线是两种串行总线,它们是通过I2C总线和SPI总线协议来进行通信的。I2C总线主要用于连接一些低速的外设,例如温度传感器、EEPROM等,而SPI总线则主要用于连接高速的外设,例如Flash存储器、LCD显示器等。
    因此,这些总线之间的关系并不是平行的,而是在Linux内核中各自独立地实现。同时,不同类型的设备通常会使用不同类型的总线进行连接和通信。参考链接 ↩︎

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值