platform框架--Linux MISC杂项框架--Linux INPUT子系统框架--串行集成电路总线I2C设备驱动框架--串行外设接口SPI 设备驱动框架---通用异步收发器UART驱动框架

platform框架

input、 pinctrl、 gpio 子系统都是 Linux 内核针对某一类设备而创建的框架,
input子系统是管理输入的子系统
pinctrl 子系统重点是设置 PIN(有的 SOC 叫做 PAD)的复用和电气属性
gpio 子系统用于初始化 GPIO 并且提供相应的 API 函数,比如设置 GPIO为输入输出,读取 GPIO 的值等
platform是 Linux 内核针对不同的系统级芯片对鼠标,打印机等设备的控制器使用方法一样而创建的框架

平台(platform)框架简述

人用指令控制系统级芯片的若干控制器,系统级芯片的若干控制器控制鼠标,打印机等设备的控制器使用方法一样
不同架构有不同指令集
不同的系统级芯片的若干控制器的使用方法不同
鼠标,打印机等设备的控制器使用方法一样
platform框架--Linux MISC杂项框架--Linux INPUT子系统框架--串行集成电路总线I2C设备驱动框架--串行外设接口SPI 设备驱动框架---通用异步收发器UART驱动框架_第1张图片

平台(platform)框架-总线- 匹配

platform总线就是使用 platform_match 函数来根据注册的设备来查找对应的驱动,或者根据注册的驱动来查找相应的设备
platform_match(设备,驱动) //platform总线匹配设备和驱动
驱动和设备的匹配有四种方法,我们依次来看一下:
第一种匹配方式, OF 类型的匹配,也就是设备树采用的匹配方式,of_driver_match_device 函数定义在文件 include/linux/of_device.h 中。 device_driver 结构体(表示设备驱动)中有个名为of_match_table的成员变量,此成员变量保存着驱动的compatible匹配表,设备树中的每个设备节点的 compatible 属性会和 of_match_table 表中的所有成员比较,查看是否有相同的条目,如果有的话就表示设备和此驱动匹配,设备和驱动匹配成功以后 probe 函数就会执行。
第二种匹配方式, ACPI 匹配方式。
第三种匹配方式, id_table 匹配,每个 platform_driver 结构体有一个 id_table成员变量,顾名思义,保存了很多 id 信息。这些 id 信息存放着这个 platformd 驱动所支持的驱动类型。
第四种匹配方式,如果第三种匹配方式的 id_table 不存在的话就直接比较驱动和设备的 name 字段,看看是不是相等,如果相等的话就匹配成功。
对于支持设备树的 Linux 版本号,一般设备驱动为了兼容性都支持设备树和无设备树两种匹配方式。也就是第一种匹配方式一般都会存在,第三种和第四种只要存在一种就可以,一般用的最多的还是第四种,也就是直接比较驱动和设备的 name 字段,毕竟这种方式最简单了。

平台(platform)框架-驱动- 驱动的分隔与分离

platform_driver 结构体描画platform驱动(Driver 驱动程序,司机,车手,驾驶员)

 struct platform_driver {
 int (*probe)(struct platform_device *);            //当驱动与设备匹配成功以后 probe 函数就会执行
 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;                      //device_driver 相当于基类,提供了最基础的驱动框架。plaform_driver 继承了这个基类,然后在此基础上又添加了一些特有的成员变量
 const struct platform_device_id *id_table;        //id_table 是个表(也就是数组)
 bool prevent_deferred_probe;
 };

平台(platform)框架-设备- 传感器,打印机,鼠标等等

platform_device 结构体中的resource 结构体resource 表示资源,也就是设备信息,比如外设寄存器等。

struct resource {
 resource_size_t start;  //资源的起始信息
 resource_size_t end;    //资源的终止信息
 const char *name;       //资源名字
 unsigned long flags;    //资源类型
 struct resource *parent, *sibling, *child;
 };

platform_device 结构体来段描述设备,现在早都改用设备树去描述了(Device 设备,装置)

 struct platform_device {
 const char *name;
 int id;
 bool id_auto;
 struct device dev;
 u32 num_resources;
 struct resource *resource;

 const struct platform_device_id *id_entry;
 char *driver_override; /* 强制匹配的驱动程序名称 */

 /*维护功能配置单元的指针 */
 struct mfd_cell *mfd_cell;

 struct pdev_archdata archdata;
 };

实现平台(platform)框架的Linux内核API函数

int platform_driver_register (要注册的platform驱动)     //向Linux内核注册一个 platform 驱动
void platform_driver_unregister(要拆卸的platform驱动)   //向Linux内核拆卸一个 platform 驱动
int platform_match(设备,驱动)                     //platform总线匹配设备和驱动
int platform_device_register(要注册的platform设备)      //向Linux内核注册一个platform设备
void platform_device_unregister(要拆卸的platform设备)   //向Linux内核拆卸一个platform设备

传统过时的拆卸设备的过程如下:
 cdev_del(); /* 删除 cdev */
 unregister_chrdev_region(); /* 注销设备号 */
 device_destroy(); /* 删除设备 */
 class_destroy(); /* 删除类 */

平台驱动编写流程=设备导入Linux内核+驱动导入Linux内核

平台(platform)框架之设备导入Linux内核(device设备,装置)

 /* 寄存器地址定义*/
 #define PERIPH1_REGISTER_BASE (0X20000000) /* 外设 1 寄存器首地址 */
 #define PERIPH2_REGISTER_BASE (0X020E0068) /* 外设 2 寄存器首地址 */
 #define REGISTER_LENGTH 4                  //寄存器长度
 
/* platform 设备资源 */
 static struct resource xxx_resources[] = {  //一共有两个资源
 [0] = {
 .start = PERIPH1_REGISTER_BASE,             //资源的起始信息为外设1寄存器首地址
 .end = (PERIPH1_REGISTER_BASE + REGISTER_LENGTH - 1),//资源的终止信息为外设1寄存器首地址+寄存器长度-1
 .flags = IORESOURCE_MEM,                    //资源为内存类型的
 },
 [1] = {
 .start = PERIPH2_REGISTER_BASE,             //资源的起始信息为外设2寄存器首地址
 .end = (PERIPH2_REGISTER_BASE + REGISTER_LENGTH - 1),//资源的终止信息为外设2寄存器首地址+寄存器长度-1
 .flags = IORESOURCE_MEM,                    //资源为内存类型的
 },
 
 };
 /* platform 设备结构体, platform设备长相 */
 static struct platform_device xxxdevice = {
 .name = "xxx-gpio",                      //注意 name 字段要和所使用的驱动中的 name 字段一致,否则驱动和设备无法匹配成功
 .id = -1,
 .num_resources = ARRAY_SIZE(xxx_resources),//num_resources 表示资源大小,其实就是数组 xxx_resources的元素数量
 .resource = xxx_resources,
 };

 /* 设备模块加载, platform总线就是使用 platform_match 函数来根据注册的设备来查找对应的驱动,或者根据注册的驱动来查找相应的设备*/
 static int __init xxxdevice_init(void)
 {
     return platform_device_register(&xxxdevice);  //向 Linux 内核注册一个 platform 驱动,Register 注册帐户,寄存器,登记
 }

 /* 设备模块注销 */
 static void __exit xxx_resourcesdevice_exit(void)
 {
    platform_device_unregister(&xxxdevice);      //向 Linux 内核拆卸一个 platform 驱动,Register 注册帐户,寄存器,登记
 }

 module_init(xxxdevice_init);
 module_exit(xxxdevice_exit);
 MODULE_LICENSE("GPL");
 MODULE_AUTHOR("zuozhongkai");

平台(platform)框架之驱动导入Linux内核(driver 驱动程序,司机)

/* 设备结构体 */
 struct xxx_dev{
 struct cdev cdev;
 /* 设备结构体其他具体内容 */
 };

struct xxx_dev xxxdev; /* 定义个设备结构体变量 */

static int xxx_open(struct inode *inode, struct file *filp)  //字符设备驱动open操作函数
 {
 /* 函数具体内容 */
 return 0;
 }

 static ssize_t xxx_write(struct file *filp, const char __user *buf,size_t cnt, loff_t *offt)//字符设备驱动write操作函数
 {
 /* 函数具体内容 */
 return 0;
 }

 static struct file_operations xxx_fops = {       //字符设备驱动操作集,注册字符设备驱动要指针xxx_fops
 .owner = THIS_MODULE,
 .open = xxx_open,
 .write = xxx_write,
 };

 static int xxx_probe(struct platform_device *dev) // platform驱动的probe函数,驱动与设备匹配成功以后此函数就会执行
 {
  ......
 cdev_init(&xxxdev.cdev, &xxx_fops); /* 注册字符设备驱动 */
/* 函数具体内容 */
 return 0;
 }

 static int xxx_remove(struct platform_device *dev)  //当关闭 platform备驱动的时候此函数就会执行,使用 iounmap 释放内存、删除 cdev,注销设备号等等
 {
......
 cdev_del(&xxxdev.cdev);/* 删除 cdev */
 /* 函数具体内容 */
 return 0;
 }

 /* 匹配列表.如果使用设备树的话将通过此匹配表进行驱动和设备的匹配,不使用设备树就自己定义初始化platform_device并加载此设备模块到linux (Device 设备,装置)*/
 // lcdif 节点的 compatible 属性值为“fsl,imx6ul-lcdif”和“fsl,imx28-lcdif”,因此在 Linux 源码中搜索这两个字符串即可找到 I.MX6ULL 的 LCD 驱动文件,这个文件为 drivers/video/fbdev/mxsfb.c, mxsfb.c就是 I.MX6ULL 的 LCD 驱动文件
 static const struct of_device_id xxx_of_match[] = {
 { .compatible = "xxx-gpio" },   //匹配项的 compatible 值为“xxx-gpio”,因此当设备树中设备节点的 compatible 属性值为“xxx-gpio”的时候此设备就会与此驱动匹配
 { /* Sentinel */ }              //of_device_id 表最后一个匹配项必须是空的
 };

 /* platform 平台驱动结构体*/
static struct platform_driver xxx_driver = {
 .driver = {
 .name = "xxx",                    //name 属性用于传统的驱动与设备匹配,也就是检查驱动和设备的 name 字段是不是相同
 .of_match_table = xxx_of_match,   //of_match_table 属性就是用于设备树下的驱动与设备检查
 },
 .probe = xxx_probe,               // platform驱动的probe函数,驱动与设备匹配成功以后此函数就会执行。当设备的device 和其对应的driver 在总线上完成配对之后,系统就调用platform设备的probe函数完成驱动注册最后工作。资源、中断调用函数以及其他相关工作
 .remove = xxx_remove,              //当关闭 platform备驱动的时候此函数就会执行,使用 iounmap 释放内存、删除
 };

/* 驱动模块加载,platform总线就是使用platform_match函数来根据注册的设备来查找对应的驱动,或者根据注册的驱动来查找相应的设备 */
static int __init xxxdriver_init(void)
{
 return platform_driver_register(&xxx_driver); //向 Linux 内核注册一个 platform 驱动,Register 注册帐户,寄存器,登记
}

 /* 驱动模块卸载 */
 static void __exit xxxdriver_exit(void)
 {
 platform_driver_unregister(&xxx_driver);    //向 Linux 内核拆卸一个 platform 驱动,Register 注册帐户,寄存器,登记
 }

 module_init(xxxdriver_init);   //指定为驱动的入口函数xxxdriver_init
 module_exit(xxxdriver_exit);   //指定为驱动的出口函数xxxdriver_exit
 MODULE_LICENSE("GPL");         // 指定LICENSE许可为GPL
 MODULE_AUTHOR("zuozhongkai");  // 指定作者信息为zuozhongkai

设备树的使用与不使用

当 Linux 内核支持了设备树以后就不需要用户手动去注册 platform 设备了。因为设备信息都放到了设备树中去描述,Linux 内核启动的时候会从设备树中读取设备信息,然后将其组织成 platform_device 形式于内核某空间,至于设备树到 platform_device 的具体过程就不去详细的追究了。
总之Linux内核织成的platform_device,和我们写最终结果一样

不使用设备树就自己定义初始化platform_device并加载此设备模块到linux内核某空间 (Device 设备,装置):

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

/***************************************************************
Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
文件名		: leddevice.c
作者	  	: 左忠凯
版本	   	: V1.0
描述	   	: platform设备
其他	   	: 无
论坛 	   	: www.openedv.com
日志	   	: 初版V1.0 2019/8/13 左忠凯创建
***************************************************************/

/* 
 * 寄存器地址定义
 */
#define CCM_CCGR1_BASE				(0X020C406C)	
#define SW_MUX_GPIO1_IO03_BASE		(0X020E0068)
#define SW_PAD_GPIO1_IO03_BASE		(0X020E02F4)
#define GPIO1_DR_BASE				(0X0209C000)
#define GPIO1_GDIR_BASE				(0X0209C004)
#define REGISTER_LENGTH				4

/* @description		: 释放flatform设备模块的时候此函数会执行	
 * @param - dev 	: 要释放的设备 
 * @return 			: 无
 */
static void	led_release(struct device *dev)
{
	printk("led device released!\r\n");	
}

/*  
 * 设备资源信息,也就是LED0所使用的所有寄存器
 */
static struct resource led_resources[] = {
	[0] = {
		.start 	= CCM_CCGR1_BASE,
		.end 	= (CCM_CCGR1_BASE + REGISTER_LENGTH - 1),
		.flags 	= IORESOURCE_MEM,
	},	
	[1] = {
		.start	= SW_MUX_GPIO1_IO03_BASE,
		.end	= (SW_MUX_GPIO1_IO03_BASE + REGISTER_LENGTH - 1),
		.flags	= IORESOURCE_MEM,
	},
	[2] = {
		.start	= SW_PAD_GPIO1_IO03_BASE,
		.end	= (SW_PAD_GPIO1_IO03_BASE + REGISTER_LENGTH - 1),
		.flags	= IORESOURCE_MEM,
	},
	[3] = {
		.start	= GPIO1_DR_BASE,
		.end	= (GPIO1_DR_BASE + REGISTER_LENGTH - 1),
		.flags	= IORESOURCE_MEM,
	},
	[4] = {
		.start	= GPIO1_GDIR_BASE,
		.end	= (GPIO1_GDIR_BASE + REGISTER_LENGTH - 1),
		.flags	= IORESOURCE_MEM,
	},
};


/*
 * platform设备结构体 
 */
static struct platform_device leddevice = {
	.name = "imx6ul-led",
	.id = -1,
	.dev = {
		.release = &led_release,
	},
	.num_resources = ARRAY_SIZE(led_resources),
	.resource = led_resources,
};
		
/*
 * @description	: 设备模块加载 
 * @param 		: 无
 * @return 		: 无
 */
static int __init leddevice_init(void)
{
	return platform_device_register(&leddevice);
}

/*
 * @description	: 设备模块注销
 * @param 		: 无
 * @return 		: 无
 */
static void __exit leddevice_exit(void)
{
	platform_device_unregister(&leddevice);
}

 module_init(xxxdriver_init);   //指定为驱动的入口函数xxxdriver_init
 module_exit(xxxdriver_exit);   //指定为驱动的出口函数xxxdriver_exit
 MODULE_LICENSE("GPL");         // 指定LICENSE许可为GPL
 MODULE_AUTHOR("zuozhongkai");  // 指定作者信息为zuozhongkai

MISC框架

MISC杂项驱动,即驱动开发方式的杂项框架
input、 pinctrl、 gpio 子系统都是 Linux 内核针对某一类设备而创建的框架
当我们板子上的某些外设无法进行分类的时候就可以使用 MISC 驱动。
所有的 MISC 设备驱动的主设备号都为 10,不同的设备使用不同的从设备号

杂项设备画像

 struct miscdevice {
 int minor; /* 子设备号 */
 const char *name; /* 设备名字 */
 const struct file_operations *fops; /* 设备操作集 */
 struct list_head list;
 struct device *parent;
 struct device *this_device;
 const struct attribute_group **groups;
 const char *nodename;
 umode_t mode;
 };

杂项框架的API函数

int misc_register(要注册的 MISC 设备)  //注册一个 MISC 设备
int misc_deregister(要注销的 MISC 设备) //注销掉 MISC 设备

传统过时的删除设备的过程如下
cdev_del(); /* 删除 cdev */
unregister_chrdev_region(); /* 注销设备号 */
device_destroy(); /* 删除设备 */
class_destroy(); /* 删除类 */

Linux INPUT 子系统框架

input、 pinctrl、 gpio 子系统都是 Linux 内核针对某一类设备而创建的框架
platform框架--Linux MISC杂项框架--Linux INPUT子系统框架--串行集成电路总线I2C设备驱动框架--串行外设接口SPI 设备驱动框架---通用异步收发器UART驱动框架_第2张图片
驱动层:输入设备的具体驱动程序,比如按键驱动程序,向内核层报告输入内容。
核心层:承上启下,为驱动层提供输入设备注册和操作接口通知事件层对输入事件进行处理。
事件层:主要和用户空间进行交互

input 设备画像

struct input_dev {
 const char *name;
 const char *phys;
 const char *uniq;
 struct input_id id;

 unsigned long propbit[BITS_TO_LONGS(INPUT_PROP_CNT)];

 unsigned long evbit[BITS_TO_LONGS(EV_CNT)];   /* 事件类型的位图 */
 unsigned long keybit[BITS_TO_LONGS(KEY_CNT)]; /* 按键值的位图 */
 unsigned long relbit[BITS_TO_LONGS(REL_CNT)]; /* 相对坐标的位图 */
 unsigned long absbit[BITS_TO_LONGS(ABS_CNT)]; /* 绝对坐标的位图 */
 unsigned long mscbit[BITS_TO_LONGS(MSC_CNT)]; /* 杂项事件的位图 */
 unsigned long ledbit[BITS_TO_LONGS(LED_CNT)]; /*LED 相关的位图 */
 unsigned long sndbit[BITS_TO_LONGS(SND_CNT)]; /* sound 有关的位图 */
 unsigned long ffbit[BITS_TO_LONGS(FF_CNT)];   /* 压力反馈的位图 */
 unsigned long swbit[BITS_TO_LONGS(SW_CNT)];   /*开关状态的位图 */
  ......
  ......
 bool devres_managed;
 };

input框架的API函数

1注册 input_dev

struct input_dev *input_allocate_device(void)        //申请一个input设备结构体变量
int input_register_device(要注册的input_dev)          //初始化要注册的input设备结构体变量

void input_free_device(需要释放的input_dev)           //注销input设备
void input_unregister_device(struct input_dev *dev)  //注销掉前面注册的input设备结构体变量

void input_set_capability(需要上报的input_dev,设备可以上报的事件类型,上报这类事件中的那个事件) //设置输入设备可以上报哪些输入事件。一次只能设置一个具体事件,如果设备可以上报多个事件,则需要重复调用这个函数来进行设置
//input_set_capability(keyinputdev.inputdev, EV_KEY, KEY_0);设置 EV_KEY 事件以及 KEY 的按键类型,也就是 KEY 作为哪个按键?我们会在设备树里面设置指定的 KEY 作为哪个按键

2、上报输入事件
code: 事件码,也就是我们注册的按键值,比如 KEY_0、 KEY_1 等等。value:事件值,比如 1 表示按键按下, 0 表示按键松开。

//input_event 函数可以上报所有的事件类型和事件值, Linux 内核也提供了其他的针对具体事件的上报函数
void input_event(需要上报的input_dev,上报的事件类型,我们要注册的按键值,事件值)    //上报指定的事件以及对应的值
void input_report_rel(struct input_dev *dev, unsigned int code, int value)
void input_report_abs(struct input_dev *dev, unsigned int code, int value)
void input_report_ff_status(struct input_dev *dev, unsigned int code, int value)
void input_report_switch(struct input_dev *dev, unsigned int code, int value)
void input_mt_sync(struct input_dev *dev)

void input_sync(需要上报同步事件的 input_dev)         //告诉 Linux 内核 input 子系统上报结束

3

read(fd, &inputevent, sizeof(inputevent)输入事件信息会读到input_event 结构体对应位置

input 驱动编写流程=注册input_dev+上报输入事件

注册input_dev

 struct input_dev *inputdev; /* input 结构体变量 */

/* 驱动入口函数 */
 static int __init xxx_init(void)
 {
 ......
 inputdev = input_allocate_device(); /* 申请 input_dev */
 inputdev->name = "test_inputdev"; /* 设置 input_dev 名字 */

 /*********第一种设置事件和事件值的方法,这种方法比之第二种和第三种直白***********/
 __set_bit(EV_KEY, inputdev->evbit); /* 设置产生按键事件,实际是用宏展开值修改input结构体变量的成员 */
__set_bit(EV_REP, inputdev->evbit); /* 重复事件,实际是用宏展开值修改input结构体变量的成员 */
__set_bit(KEY_0, inputdev->keybit); /*设置产生哪些按键值,实际是用宏展开值修改input结构体变量的成员 */
/************************************************/

 /*********第二种设置事件和事件值的方法***********/
 keyinputdev.inputdev->evbit[0] = BIT_MASK(EV_KEY) |
 BIT_MASK(EV_REP);
 keyinputdev.inputdev->keybit[BIT_WORD(KEY_0)] |=
 BIT_MASK(KEY_0);
 /************************************************/

 /*********第三种设置事件和事件值的方法***********/
 keyinputdev.inputdev->evbit[0] = BIT_MASK(EV_KEY) |
 BIT_MASK(EV_REP);
 input_set_capability(keyinputdev.inputdev, EV_KEY, KEY_0);
/************************************************/

 /* 注册 input_dev */
 input_register_device(inputdev);
 ......
 return 0;
 }

 /* 驱动出口函数 */
 static void __exit xxx_exit(void)
{
 input_unregister_device(inputdev); /* 注销 input_dev */
 input_free_device(inputdev); /* 删除 input_dev */
 }

上报输入事件

事件上报参考代码:消抖定时器中断函数中将按键值上报给 Linux 内核,即按键抖动时过一段定时执行中断,中断调用上报函数

 /* 用于按键消抖的定时器服务函数 */
 void timer_function(unsigned long arg)
 {
 unsigned char value;

value = gpio_get_value(keydesc->gpio); /* 读取 IO 值 */
if(value == 0){ /* 按下按键 */
 /* 上报按键值 */
 input_report_key(inputdev, KEY_0, 1); /* 最后一个参数 1, 按下 */
 input_sync(inputdev); /* 同步事件 */
 } else { /* 按键松开 */
 input_report_key(inputdev, KEY_0, 0); /* 最后一个参数 0, 松开 */
 input_sync(inputdev); /* 同步事件 */
 }
 }

I2C 设备驱动编写流程

SOC 原厂编写 I2C 主机驱动

I2C 总线驱动重点是 I2C 适配器(也就是 SOC 的 I2C 接口控制器)驱动,这里要用到两个重要的数据结构: i2c_adapter 和 i2c_algorithm, Linux 内核将 SOC 的 I2C 适配器(控制器)抽象成 i2c_adapter.
IIC控制器访问IIC设备的API接口函数指针函数编写好后把指针放在i2c_adapter.i2c_algorithm。再用i2c_algorithm向Linux内核注册I2C适配器,即注册I2C 总线驱动
i2c_transfer 函数最终会调用 I2C 适配器中 i2c_algorithm 里面的 master_xfer 函数
i2c_master_recv和i2c_master_send这两个函数最终都会调用i2c_transfer

I2C 设备信息描述

使用设备树的时候 I2C 设备信息通过创建相应的节点就行了,
属性看/home/lj/linux/linux-imx-rel_imx_4.1.15_2.1.0_ga_zhengdianyuanzi/Documentation/devicetree/bindings/iio/iio-bindings.txt

I2C 设备数据收发处理流程

Message 信息,在线留言,消息,留言
Linux 内核使用 i2c_msg 结构体来描述一个消息

 struct i2c_msg {
 __u16 addr; /* 从机地址 */
 __u16 flags; /* 标志,标记为发送数据,标记为写数据 */
 #define I2C_M_TEN 0x0010
 #define I2C_M_RD 0x0001
 #define I2C_M_STOP 0x8000
 #define I2C_M_NOSTART 0x4000
 #define I2C_M_REV_DIR_ADDR 0x2000
 #define I2C_M_IGNORE_NAK 0x1000
 #define I2C_M_NO_RD_ACK 0x0800
 #define I2C_M_RECV_LEN 0x0400
 __u16 len; /* 消息(本 msg)长度 */
 __u8 *buf; /* 消息数据 */
 };

API函数

I2C 适配器(控制器)抽象成 i2c_adapter, i2c_client结构体就是描述设备信息的,即传感器长相,可根据设备树初始化,i2c_driver结构体描述驱动内容,类似于platform_driver,主要是一些函数指针。
int i2c_transfer(所使用的I2C适配器,I2C要发送的一个或多个消息,消息数量)    //使用I2C控制器对I2C设备发送消息。消息据标志分为读与写。int i2c_transfer(struct i2c_adapter *adap,struct i2c_msg *msgs,int num)
int i2c_master_send(I2C设备画像指针,要发送的数据所在地址,要发送的数据字节数) //用于I2C数据的发送操作。函数最终都会调用i2c_transfer.int i2c_master_send(const struct i2c_client *client,const char *buf,int count)
int i2c_master_recv(I2C设备画像指针,要接受的数据所在地址,要发送的数据字节数) //用于I2C数据的接受操作。函数最终都会调用i2c_transfer.int i2c_master_recv(const struct i2c_client *client,char *buf,int count)

SPI 设备驱动编写流程

SOC 原厂编写SPI 主机驱动

Linux 内核使用spi_master表示 SPI 主机。驱动SPI 主机驱动的核心就是申请 spi_master,然后初始化 spi_master,最后向 Linux 内核注册spi_master。

SPI 设备信息描述

采用设备树的情况下, SPI 设备信息描述就通过创建相应的设备子节点来完成

SPI 设备数据收发处理流程

用spi_transfer 结构体描述 SPI 传输信息,spi_transfer 需要组织成 spi_message

 struct spi_transfer {
 const void *tx_buf;   //tx_buf 保存着要发送的数据。
 void *rx_buf;         // rx_buf 用于保存接收到的数据。
 unsigned len;         // len是要进行传输的数据长度, SPI 是全双工通信,因此在一次通信中发送和接收的字节数都是一样的,所以 spi_transfer 中也就没有发送长度和接收长度之分。

 dma_addr_t tx_dma;
 dma_addr_t rx_dma;
 struct sg_table tx_sg;
 struct sg_table rx_sg;

 unsigned cs_change:1;
 unsigned tx_nbits:3;
 unsigned rx_nbits:3;
 #define SPI_NBITS_SINGLE 0x01 /* 1bit transfer */
 #define SPI_NBITS_DUAL 0x02 /* 2bits transfer */
 #define SPI_NBITS_QUAD 0x04 /* 4bits transfer */
 u8 bits_per_word;
 u16 delay_usecs;
 u32 speed_hz;

 struct list_head transfer_list;
 };


 struct spi_message {
 struct list_head transfers;

 struct spi_device *spi;

 unsigned is_dma_mapped:1;
......
 void (*complete)(void *context);
 void *context;
 unsigned frame_length;
 unsigned actual_length;
 int status;

 struct list_head queue;
 void *state;
 };

API函数

void spi_message_init(要初始化的 spi_message)                    //初始化 spi_message。
void spi_message_add_tail(要添加到队列中的spi_transfer, spi_transfer要加入的spi_message)   //将前面设置好的spi_transfer添加spi_message队列中。
int spi_sync(要进行数据传输的spi_device,要传输的spi_message)       //完成 SPI 数据同步传输
int spi_async(要进行数据传输的spi_device,要传输的spi_message)      //完成 SPI 数据异步步传输

SPI 数据传输步骤如下
①、申请并初始化 spi_transfer,
设置 spi_transfer 的 tx_buf 成员变量, tx_buf 为要发送的数据。然后设置 rx_buf 成员变量, rx_buf 保存着接收到的数据。最后设置 len 成员变量,也就是要进行数据通信的长度。
②、使用 spi_message_init 函数初始化 spi_message。
③、使用spi_message_add_tail函数将前面设置好的spi_transfer添加到spi_message队列中。
④、使用 spi_sync 函数完成 SPI 数据同步传输。

通用异步收发器驱动框架

画像uart_port和画像uart_driver


 struct uart_ops {
 unsigned int (*tx_empty)(struct uart_port *);
 void (*set_mctrl)(struct uart_port *, unsigned int mctrl);
 unsigned int (*get_mctrl)(struct uart_port *);
 void (*stop_tx)(struct uart_port *);
 void (*start_tx)(struct uart_port *);
 void (*throttle)(struct uart_port *);
 void (*unthrottle)(struct uart_port *);
 void (*send_xchar)(struct uart_port *, char ch);
 void (*stop_rx)(struct uart_port *);
 void (*enable_ms)(struct uart_port *);
 void (*break_ctl)(struct uart_port *, int ctl);
 int (*startup)(struct uart_port *);
 void (*shutdown)(struct uart_port *);
 void (*flush_buffer)(struct uart_port *);
 void (*set_termios)(struct uart_port *, struct ktermios *new,
 struct ktermios *old);
 void (*set_ldisc)(struct uart_port *, struct ktermios *);
 void (*pm)(struct uart_port *, unsigned int state,
unsigned int oldstate);

 /*
 * Return a string describing the type of the port
 */
 const char *(*type)(struct uart_port *);

/*
 * Release IO and memory resources used by the port.
* This includes iounmap if necessary.
 */
 void (*release_port)(struct uart_port *);

 /*
 * Request IO and memory resources used by the port.
 * This includes iomapping the port if necessary.
 */
 int (*request_port)(struct uart_port *);
 void (*config_port)(struct uart_port *, int);
 int (*verify_port)(struct uart_port *, struct serial_struct *);
 int (*ioctl)(struct uart_port *, unsigned int, unsigned long);
 #ifdef CONFIG_CONSOLE_POLL
 int (*poll_init)(struct uart_port *);
 void (*poll_put_char)(struct uart_port *, unsigned char);
 int (*poll_get_char)(struct uart_port *);
 #endif
 };

 struct uart_port {
 spinlock_t lock; /* port lock */
 unsigned long iobase; /* in/out[bwl] */
 unsigned char __iomem *membase; /* read/write[bwl] */
......
 const struct uart_ops *ops;
 unsigned int custom_divisor;
 unsigned int line; /* port index */
 unsigned int minor;
 resource_size_t mapbase; /* for ioremap */
 resource_size_t mapsize;
 struct device *dev; /* parent device */
......
 };

 struct uart_driver {
 struct module *owner; /* 模块所属者 */
 const char *driver_name; /* 驱动名字 */
 const char *dev_name; /* 设备名字 */
 int major; /* 主设备号 */
 int minor; /* 次设备号 */
 int nr; /* 设备数 */
 struct console *cons; /* 控制台 */

 /*
 * these are private; the low level driver should not
 * touch these; they should be initialised to NULL
 */
 struct uart_state *state;
 struct tty_driver *tty_driver;
 };

串口驱动的生成及与uart_port的匹配

1、 uart_driver 注册与注销
int uart_register_driver(要注册的uart_driver)  //向linux系统注册串口驱动uart_driver
void uart_unregister_driver(struct uart_driver *drv)  //向linux系统注销串口驱动uart_drive
2、 uart_port 的添加与移除
int uart_add_one_port(此 port对应的uart_driver,要添加到 uart_driver 中的 port)//使uart_port与uart_driver结合起来
int uart_remove_one_port(要卸载的 port 所对应的uart_driver, 要卸载的 uart_port)//将uart_port从相应的uart_driver中移除

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