嵌入式内核及驱动开发高级

目录

第一部分

一. 设备模型

# 一、起源

# 二、新方案

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

## 2.2 uevent

# 三、代码自动mknod

二. 知识补充

第二部分

一. 平台总线框架之名臣匹配

# 一、总线、设备、驱动

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

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

# 二、基本数据类型

2.1  struct device

2.2 struct device_driver

# 三、platform总线驱动

## 3.1 核心数据类型之platform_device

## 3.2 核心数据类型之platform_driver

# 四、platform的三种匹配方式

# 五、名称匹配之基础框架

# 六、名称匹配之led实例

二. 补充

第三部分

一. 平台总线、 ID匹配、设备树匹配

# 一、ID匹配之框架代码

# 二、ID匹配之led驱动

# 三、设备树匹配

# 四、设备树匹配之led驱动

# 五、一个编写驱动用的宏

二. 补充

第四部分

一. IIC背景知识

# 一、I2C总线背景知识

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

## 2.1 发送

## 2.2 接收

二. linux内核对i2c的支持

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

# 四、MPU6050

三. 补充

第五部分

一. 应用层直接使用I2C通道

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

## 5.1 预备工作:

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

### 5.1.2 i2c总线驱动层提供了一个字符设备驱动,以便于应用层可以直接通过它去使用i2c总线通讯去操作二级外设,但需要

### 5.2 应用层直接使用i2c总线的代码实现

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

5.2.2 调用ioctl实现接收、发送

二. 补充

第六部分

一. I2C二级外设驱动开发方法driver模块的编写

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

#二. i2c二级外设驱动框架:

二. I2C二级外设驱动开发方法client模块的编写

# 七、I2C总线二级外设驱动开发之名称匹配

1. i2c_register_board_info

2. i2c_new_device:明确二级外设地址的情况下可用

3. i2c_new_probed_device

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

第七部分

一. input子系统

# 一、input子系统基本框架

# 二、驱动开发步骤

# 三、key2-input版代码解析

# 四、mpu6050-input版代码解析

二. 补充


第一部分



一. 设备模型

# 一、起源

仅devfs,导致开发不方便以及一些功能难以支持:比如获取设备信息, 需要大量的ioctl操作

  1. 热插拔:usb插入之后内核自动识别该设备, 并找到该设备相应的驱动代码支持该设备

  2. 不支持一些针对所有设备的统一操作(如电源管理), 按下电源键之后手机进入睡眠模式, 所有设备都参与响应

  3. 不能自动mknod

  4. 用户查看不了设备信息

  5. 设备信息硬编码,导致驱动代码通用性差,即没有分离设备和驱动

硬编:

# 二、新方案

uevent机制:sysfs + uevent + udevd(上层app)

uevent机制是内核和用户空间的一种通信机制

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

sysfs用途:(类似于windows的设备管理器)

1. 建立系统中总线、驱动、设备三者之间的桥梁

2. 向用户空间展示内核中各种设备的拓扑图, 方便用户空间查看相应硬件的驱动信息

3. 提供给用户空间对设备获取信息和操作的接口,部分取代ioctl功能

sysfs在内核中的组成要素

在用户空间/sys下的显示

内核对象(kobject)

目录

对象属性(attribute)

文件

对象关系(relationship)

链接(Symbolic Link)

对应关系:都用一些软连接将设备和一些文件链接关联起来

四个基本结构:

类型

所包含的内容

内核数据结构

对应/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

嵌入式内核及驱动开发高级_第1张图片

# 三、代码自动mknod

```c

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);来获得失败的返回错误码;

*/

```

```c

void class_destroy(struct class *cls)

/*

* 功能:删除class_create生成目录

* 参数:

         struct class *cls - class指针

* 返回值

*/

```

```c

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

 */

```

```c

void device_destroy(struct class *class, dev_t devt)

/*

 * 功能:删除device_create生成目录

 * 参数:

         struct class *class - class指针

         dev_t devt - 设备号

 * 返回值

*/

```

二. 知识补充

1.free, 为什么不需要指定释放空间的大小

嵌入式内核及驱动开发高级_第2张图片

malloc申请空间的时候会在所申请空间的头部多申请一个包头, 这个头部里面存储着申请空间的大小, 所以free释放的时候才不需要填写释放空间的大小。

第二部分



一. 平台总线框架之名臣匹配

# 一、总线、设备、驱动

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

1. 垃圾代码太多:驱动代码和硬件平台不匹配

2. 结构不清晰

3. 一些统一设备功能难以支持: 如电源管理

4. 开发效率低下

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

嵌入式内核及驱动开发高级_第3张图片

​struct device来表示一个具体设备,主要提供具体设备相关的资源(如寄存器地址、GPIO管脚、中断等等)

​struct device_driver来表示一个设备驱动,一个驱动可以支持多个操作逻辑相同的设备

​带来的问题-------怎样将二者进行关联(匹配)?

​硬件上同一总线上的设备遵循一致的时序通信,在其基础上增加管理设备和驱动的软件功能于是引入总线(bus),各种总线的核心框架由内核来实现,通信时序一般由SOC供应商支持内核中用struct bus_type来表示一种总线,总线可以是实际存在的总线,也可以是虚拟总线:

1. 实际总线:提供时序通信方式 + 管理设备和驱动

2. 虚拟总线:仅用来管理设备和驱动(最核心的作用之一就是完成设备和驱动的匹配)

理解方式:

设备:提供硬件资源——男方

驱动:提供驱动代码——女方

总线:匹配设备和驱动——婚介所:提供沟通机制,完成拉郎配

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

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

就是名称匹配和ID匹配都要编写一个设备模块然后注册进内核里面

# 二、基本数据类型

2.1  struct device

```c

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

```c

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对象地址) 重要

    //......

};

```

```c

struct of_device_id

{

char name[32];//设备名

char type[32];//设备类型

char compatible[128]; //用于device和driver的match,重点

};

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

```

# 三、platform总线驱动

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

基本结构图:

嵌入式内核及驱动开发高级_第4张图片

作用:将底层设备和驱动程序管理起来, 并完成驱动和设备的匹配工作

## 3.1 核心数据类型之platform_device

```c

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;//设备八字

};

```

```c

struct platform_device_id

{

char name[20];//匹配用名称

kernel_ulong_t driver_data;//需要向驱动传输的其它数据

};

```

```c

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使用的中断号的开始地址和结束值

*/

```

```c

/**

 *注册:把指定设备添加到内核中平台总线的设备列表申请一个设备节点,等待匹配,匹配成功则回调驱动中probe;

 */

int platform_device_register(struct platform_device *);

/**

 *注销:把指定设备从设备列表中删除,如果驱动已匹配则回调驱动方法和设备信息中的release;

 */

void platform_device_unregister(struct platform_device *);

```

```c

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

```c

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);      //suspend 唤醒

    int (*resume)(struct platform_device *);

    struct device_driver driver;//内核里所有的驱动必须包含该结构体

    const struct platform_device_id *id_table;  //能够支持的设备八字数组,用到结构体数组,一般不指定大小,初始化时最后加{}表示数组结束

};

```

```c

int platform_driver_register(struct platform_driver*pdrv);

/*

功能:在总线上注册平台设备驱动

参数:pdrv:平台设备驱动结构体

返回值:成功:0

失败:错误码

*/

void platform_driver_unregister(struct platform_driver*pdrv);

```

# 四、platform的三种匹配方式

嵌入式内核及驱动开发高级_第5张图片

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成员必须设置

# 五、名称匹配之基础框架

```c

/*platform device框架*/

#include

#include

#include

#include

//定义资源数组

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

```

```c

/*platform driver框架*/

#include

#include

#include

#include

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实例

1.改写设备模块的资源信息

嵌入式内核及驱动开发高级_第6张图片

2.led驱动从设备模块中获取资源信息

嵌入式内核及驱动开发高级_第7张图片

3.驱动里面的变化

嵌入式内核及驱动开发高级_第8张图片

原来init和exit函数的操作放到probe和remove函数里面去做, 真正的init和exit函数就向内核的总线上去申请设备对象

和驱动对象。

二. 补充

1.理解总线如何管理设备和驱动的

嵌入式内核及驱动开发高级_第9张图片

随便哪一个register函数被调用都会去对象的链表中找有没有和我名称一样的

2.查看获取到的信息

嵌入式内核及驱动开发高级_第10张图片

3.资源数排序的理解

嵌入式内核及驱动开发高级_第11张图片

4.设备和驱动代码结合和分离的时候就会被调用

嵌入式内核及驱动开发高级_第12张图片

5.这种错误的总结

原因是自己定义的结构体名字写错了, 解除了指针的引用

第三部分



一. 平台总线、 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可以不相同

一份驱动里面可以有多分设备id

```c

/*platform device框架*/

#include

#include

#include

#include

//定义资源数组

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

```

```c

/*platform driver框架*/

#include

#include

#include

#include

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驱动

# 三、设备树匹配

设备树匹配:内核启动时根据设备树自动产生的设备 , 所以这种办法就不用我们编写设备模块------ 优先级最高

注意事项:

1. 无需编写device模块,只需编写driver模块

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

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

```c

/*platform driver框架*/

#include

#include

#include

#include

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驱动

# 五、一个编写驱动用的宏

```c

struct platform_driver xxx = { 

    ...

};

module_platform_driver(xxx);

//最终展开后就是如下形式:3

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)

```

;

二. 补充

1.匹配优先级, 从高到低-----设备树、 ID、 名称

2.启动NFS和TFTP服务端

嵌入式内核及驱动开发高级_第13张图片

3.设备端这两个的名字必须一样

嵌入式内核及驱动开发高级_第14张图片

4 . 设备树匹配

嵌入式内核及驱动开发高级_第15张图片

5 . ID匹配

嵌入式内核及驱动开发高级_第16张图片

6 . 名称匹配

嵌入式内核及驱动开发高级_第17张图片

嵌入式内核及驱动开发高级_第18张图片

第四部分



一. IIC背景知识

# 一、I2C总线背景知识

SOC芯片平台的外设分为:

1. 一级外设:外设控制器集成在SOC芯片内部

2. 二级外设:外设控制器由另一块芯片负责,通过一些通讯总线与SOC芯片相连

嵌入式内核及驱动开发高级_第19张图片

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

嵌入式内核及驱动开发高级_第20张图片

嵌入式内核及驱动开发高级_第21张图片

嵌入式内核及驱动开发高级_第22张图片

嵌入式内核及驱动开发高级_第23张图片

i2c传输的要点就是: 传输一个字节 后面必然紧跟一个"响应"信号----应答信号.这个响应信号可能来自主机,或者是从机,具体是谁,就要看传输方向。

传输方向分两种情况(每种情况又有两种可能: A无应答和 B有应答):

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

         A) 主机读SDA为高电平,说明从机无应答(意味着从机接收完毕,主机发送停止信号)

          B) 主机读SDA为低电平,说明从机有应答。(可继续发送下一个字节)

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

​         A) 主机写SDA为高电平,从机收到主机的无应答信号之后,从机停止传输,等待主机的停止信号。

​         B) 主机写SDA为低电平,从机收到主机的应答信号之后,从机继续输出下一字节

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

I2CCON寄存器:控制寄存器

嵌入式内核及驱动开发高级_第24张图片

第7位:决定是否允许产生应答信号,无论发送还是接收前,需置1

第6位:传输时时钟线分频,一般选置1

第5位:决定是否开启发送或接收结束时发通知,无论发送还是接收前,需置1

第4位:接收或发送是否完毕可以通过检查此位是否为1,接收或发送完毕后需置0

I2CSTAT寄存器:状态寄存器

嵌入式内核及驱动开发高级_第25张图片

第6、7位:每次传输前需选择传输模式

第5位:置0产生将产生终止信号,传输前置1产生起始信号

第4位:使能数据输出,传输前需置1

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

## 2.1 发送

嵌入式内核及驱动开发高级_第26张图片

嵌入式内核及驱动开发高级_第27张图片

```c

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 接收

嵌入式内核及驱动开发高级_第28张图片

嵌入式内核及驱动开发高级_第29张图片

```c

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的支持

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

  1. linux内核与I2C架构图

嵌入式内核及驱动开发高级_第30张图片

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

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

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

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

嵌入式内核及驱动开发高级_第31张图片

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

```c

struct i2c_board_info {

    char        type[I2C_NAME_SIZE];

    unsigned short  flags;

    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结构信息,让子系统动态创建,并且注册。

*/

```

```c

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;

};

/*重要成员:

flags:地址长度,如是10位还是7位地址,默认是7位地址。如果是10位地址器件,则设置为I2C_CLIENT_TEN

addr:具体I2C器件如(at24c02),设备地址,低7位

name:设备名,用于和i2c_driver层匹配使用的,可以和平台模型中的平台设备层platform_driver中的name作用是一样的。

adapter:本设备所绑定的适配器结构(CPU有很多I2C适配器,类似单片机有串口1、串口2等等,linux中每个适配器都用一个结构描述), 哪一个I2C控制器。

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_get_adapter·后,需要使用该函数减少引用计数。(如果你的适配器驱动不需要卸载,可以不使用)*/

void i2c_put_adapter(struct i2c_adapter *adap);

/*

功能:根据参数adap,info,addr,addr_list动态创建i2c_client并且进行注册

参数:

adap:i2c_client所依附的适配器结构地址, 该外设依附在哪一个I2C总线控制器上。

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

*/

```

```c

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

```

```c

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

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

嵌入式内核及驱动开发高级_第32张图片

```c

#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(正常启用)

```

三. 补充

1.SDA线上的数据是非常广义的: 从机地址, 从机里面的寄存器编号, 数据

2.将IIC通道抽象为一个设备器。

3.probe  探测

4.i2c驱动框架

嵌入式内核及驱动开发高级_第33张图片

3 .结构体不能整体赋值

嵌入式内核及驱动开发高级_第34张图片

嵌入式内核及驱动开发高级_第35张图片

定义的时候直接初始化

4 .验证了联合体公用了一段空间(地址都是一样的)

嵌入式内核及驱动开发高级_第36张图片

5 .结构体对齐问题, 以空间换取时间效率, 计算机在为变量开空间的时候依次开取一样的空间效率更快

嵌入式内核及驱动开发高级_第37张图片

60个字节

在C语言中,结构体的对齐问题指的是结构体成员如何在内存中排列。由于硬件和操作系统的不同,结构体的对齐规则可能会有所差异。默认情况下,编译器会使用最大成员对齐规则,也就是说,结构体成员在内存中的起始地址必须是成员类型大小的整数倍。

6 . 哈佛结构冯诺依曼结构知识回顾

冯诺伊曼结构是一种基于存储程序的计算机架构,其中程序和数据存储在同一个内存空间中,并使用相同的总线进行访问。冯诺伊曼结构的特点是指令和数据具有相同的编址空间,需要依次从内存中获取指令和数据进行处理。冯诺伊曼结构常见的计算机体系结构如x86、ARM等。

而哈佛结构是一种基于分离存储的计算机架构,其中指令和数据存储在不同的内存空间中,并且使用独立的总线进行访问。哈佛结构的特点是指令和数据分开存储,可以同时读取指令和数据,提高了并行度。哈佛结构在一些嵌入式系统和特定领域的计算机中被广泛使用,如DSP(数字信号处理器)和FPGA(现场可编程门阵列)。

总体而言,冯诺伊曼结构更适用于通用计算机,而哈佛结构更适用于特定领域的计算机。选择何种结构要考虑实际需求,包括性能要求、成本限制以及具体应用场景等因素。

7 . ctrl + alt + space唤醒keil的自动补齐功能

第五部分



一. 应用层直接使用I2C通道

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

## 5.1 预备工作:

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

嵌入式内核及驱动开发高级_第38张图片

不要忘记:

1. 回内核源码顶层目录执行:make  dtbs

2. 将新生成的dtb拷贝到/tftpboot

### 5.1.2 i2c总线驱动层提供了一个字符设备驱动,以便于应用层可以直接通过它去使用i2c总线通讯去操作二级外设,但需要

内核编译时添加此字符设备驱动代码(i2c-dev.c),因此需要修改make menuconfig的配置:

嵌入式内核及驱动开发高级_第39张图片

不要忘记:

1. 回内核源码顶层目录执行:make  uImage

2. 将新生成的uImage拷贝到/tftpboot

### 5.2 应用层直接使用i2c总线的代码实现

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

见实例代码

5.2.2 调用ioctl实现接收、发送

见实例代码

缺点:

1. 需要应用程序开发人员查阅原理图和芯片手册,增加了他们的开发负担

2. 开发出的应用程序缺乏可移植性

二. 补充

1 .在设备树文件中添加这样一个节点, 内核在启动之后会根据这个节点的信息创建一个I2C通道, 可以参照内核文档中的设备树中的节点设置。

嵌入式内核及驱动开发高级_第40张图片

2 .这个命令的意思, 回到上一次工作的目录

3 .先编写主函数的执行逻辑, 设计好需要那些函数再去实现相应的函数。

4 . 学习一下这种出错处理(统一处理)

嵌入式内核及驱动开发高级_第41张图片

5  .设置ioctl的命令参数的宏在内核源码的头文件里面

6  .设置自己网卡的IP地址

嵌入式内核及驱动开发高级_第42张图片

7  .查看自己的I2C设备文件

嵌入式内核及驱动开发高级_第43张图片

I2C通道5对应的设备文件

1 .ioctl控制收发的数据结构所在位置

嵌入式内核及驱动开发高级_第44张图片

2 .直接操作根目录下的内容

3 .flag为0 , 代表从机地址为7位的

嵌入式内核及驱动开发高级_第45张图片

1  .命令积累

嵌入式内核及驱动开发高级_第46张图片

回到上一次目录

2 .设置为没有行号, 方便按住shift键复制

3 .开发板上的应用程序使用交叉编译器去编译

4 .在自己练习code的目录下写上readme, 方便今后复习

第六部分



一. I2C二级外设驱动开发方法driver模块的编写

# 一、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. 名称匹配

   2. 设备树匹配

   3. ACPI匹配

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

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

#二. 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");

```

1 . linux内核驱动开发中结构体赋值的几种行驶

嵌入式内核及驱动开发高级_第47张图片

嵌入式内核及驱动开发高级_第48张图片

嵌入式内核及驱动开发高级_第49张图片

2 . 驱动和设备模块通过总线匹配成功之后, probe函数会自动拿到代表设备模块对象的地址, 并赋值给probe函数的第一个参数, 在这个函数中就可以给自己定义的设备对象里面的struct clinet *类型的对象赋值.

二. I2C二级外设驱动开发方法client模块的编写

# 七、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

   #include

   #include

      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

   #include

   #include

  

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

   ```

  

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

嵌入式内核及驱动开发高级_第50张图片

1 .在开发板上面验证

嵌入式内核及驱动开发高级_第51张图片

2 .I2C匹配的必须要一个id匹配的

嵌入式内核及驱动开发高级_第52张图片

3 .内核维护了许多的数据结构

4.初始化结构体

嵌入式内核及驱动开发高级_第53张图片

5 .设备树匹配不需要编写client模块

第七部分



一. input子系统

# 一、input子系统基本框架

Linux内核为了两个目的:

1. 简化纯输入类外设(如:键盘、鼠标、游戏杆、轨迹球、触摸屏。。。等等)的驱动开发

2. 统一输入类外设产生的数据格式(你们struct input_event),更加方便应用层编程

设计了输入子系统

嵌入式内核及驱动开发高级_第54张图片

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

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

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

# 二、驱动开发步骤

```c

/*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), 没有数据变化的时候不上报数据。这种work_queue具有定时的效应.

上报数据的主要函数:

input_event

input_report_abs(上报绝对值坐标事件)

input_sync

*/

```

相关接口:

```c

/*_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;             // 哪个分值

    __s32 value;            // 具体值     

};

```

# 三、key2-input版代码解析

# 四、mpu6050-input版代码解析

二. 补充

1  .platform_device ---platform_driver

      i2c_client ------i2c_driver

      i2c_adapter  ----- 传输算法

    input_handle ----- input_dev, 以上都是linux内核惯用的套路

2 .字符设备  --- 应用层

    块设备 ----- 文件系统

    网络设备 ---- 网络协议栈

3 .重复定义

4 . 值的类型

嵌入式内核及驱动开发高级_第55张图片

5 . input_sync函数在数据没有发生变化得到时候不会上报数据

6 .ABS, 绝对坐标值事件

7 . 二分查找

嵌入式内核及驱动开发高级_第56张图片

8 . ascii

你可能感兴趣的:(linux内核和驱动开发,驱动开发)