input、 pinctrl、 gpio 子系统都是 Linux 内核针对某一类设备而创建的框架,
input子系统是管理输入的子系统
pinctrl 子系统重点是设置 PIN(有的 SOC 叫做 PAD)的复用和电气属性
gpio 子系统用于初始化 GPIO 并且提供相应的 API 函数,比如设置 GPIO为输入输出,读取 GPIO 的值等
platform是 Linux 内核针对不同的系统级芯片对鼠标,打印机等设备的控制器使用方法一样而创建的框架
人用指令控制系统级芯片的若干控制器,系统级芯片的若干控制器控制鼠标,打印机等设备的控制器使用方法一样
不同架构有不同指令集
不同的系统级芯片的若干控制器的使用方法不同
鼠标,打印机等设备的控制器使用方法一样
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_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_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;
};
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(); /* 删除类 */
/* 寄存器地址定义*/
#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");
/* 设备结构体 */
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,和我们写最终结果一样
#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杂项驱动,即驱动开发方式的杂项框架
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;
};
int misc_register(要注册的 MISC 设备) //注册一个 MISC 设备
int misc_deregister(要注销的 MISC 设备) //注销掉 MISC 设备
传统过时的删除设备的过程如下
cdev_del(); /* 删除 cdev */
unregister_chrdev_region(); /* 注销设备号 */
device_destroy(); /* 删除设备 */
class_destroy(); /* 删除类 */
input、 pinctrl、 gpio 子系统都是 Linux 内核针对某一类设备而创建的框架
驱动层:输入设备的具体驱动程序,比如按键驱动程序,向内核层报告输入内容。
核心层:承上启下,为驱动层提供输入设备注册和操作接口。通知事件层对输入事件进行处理。
事件层:主要和用户空间进行交互。
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;
};
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 结构体对应位置
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 总线驱动重点是 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 设备信息通过创建相应的节点就行了,
属性看/home/lj/linux/linux-imx-rel_imx_4.1.15_2.1.0_ga_zhengdianyuanzi/Documentation/devicetree/bindings/iio/iio-bindings.txt
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)
Linux 内核使用spi_master表示 SPI 主机。驱动SPI 主机驱动的核心就是申请 spi_master,然后初始化 spi_master,最后向 Linux 内核注册spi_master。
采用设备树的情况下, 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 数据同步传输。
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;
};
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中移除