韦东山嵌入式Liunx入门驱动开发二(设备树、按键驱动)

本文详细介绍了Linux设备树(DTS)的结构、作用、语法,以及如何在内核中处理设备树信息,包括LED驱动框架、GPIO按键的多种读取方法,如查询、休眠唤醒、poll和异步通知。还提供了具体的代码示例,展示了设备驱动与平台设备的匹配过程和GPIO按键驱动的实现方法。
摘要由CSDN通过智能技术生成


本人学习完韦老师的视频,因此来复习巩固,写以笔记记之。
韦老师的课比较难,第一遍不知道在说什么,但是坚持看完一遍,再来复习,基本上就水到渠成了。
看完视频复习的同学观看最佳!
基于 IMX6ULL-PRO
参考视频 Linux快速入门到精通视频
参考资料:01_嵌入式Linux应用开发完全手册V5.1_IMX6ULL_Pro开发板.pdf

一、设备树

1-1 设备树的引入与作用

设备树DTS(device tree source):设备树只是用来给内核里的驱动程序,指定硬件的信息。比如 LED驱动,在内核的驱动程序里去操作寄存器,但是操作哪一个引脚?这由设备树指定。
一种描述硬件 连接信息的数据结构,采用**结点(node)属性(property)**嵌套构成树形结构。

编写设备树文件,需要编译为dtb(device tree blob)文件,内核使用的是dtb文件。
/sys/firmware/devicetree 目录下是以目录结构程现的 dtb 文件 , 根节点对应base目录,base目录下ls可呈现出子节点。
arm板是设备树信息 cd /sys/firmware/devicetree/base/
在这里插入图片描述
字符串打印:使用cat命令;数值打印:使用hexdump命令

总线树结构
在这里插入图片描述

1-2 设备树dts

在这里插入图片描述
/dts-v1: 表示版本信息
/:根节点
cpus: CPU节点
memory: memory节点

格式:name=value
value可以为32位数据,可以没有,可以是字符串。
(1) cell表示设备树中数据的最小单位,一个32位数据,用尖括号包围

interrupts = <17 0xc>;

(2) 64bit 数据使用 2 个cell 来表示,用尖括号包围

reg= <0 0x20000000>;

(3) 字符串,用双引号包围起来

compatible="ns16550";

(4) 字节序列用中括号包围起来

local mac address = [00 00 12 34 56 78]; //每个byte使用2个16进制数来表示

(5) 可以是各种值的组合 , 用逗号隔开

example = <0xf00f0000 19>, "a st range property format";
1-3 设备树dts语法
(1) 基本单元node

设备树中的基本单元,被称为”node"

[label:] node name[@unit address] {
		[properties definitions]
		[child nodes]
};

label是标号,可以省略。 label的作用是为了方便地引用node
形如

uart0: uart@fe001000 {
	compatible="ns16550";
	reg=<0xfe001000 0x100>;
}

可以在根节点外使用uart0引用uart

&uart0{
	status = “disabled”;
};
(2) 属性

① #address cells 、#size cells
cell指一个32位的数值
address cells :address要用多少个32位数来表示起始地址
size cells: size要用多少个32位数来表示大小

② reg
reg的本意是register,用来表示寄存器地址。
用多少个32位的数来表示address和size,由其父节点的address cells 、size cells 决定。

/{
#address cells = <1>;
#size cells = <1>;
memory{
	reg = <0x80000000 0x20000000>;
	}}

③ compatible
表示兼容,compatible的值,建议取这样的形式:“manufacturer, model”,即“厂家名 模块名”。
④ model
model属性与 compatible 属性有些类似,但是有差别。model用来准确地定义这个硬件是什么。

/{
	compatible = "samsung,smdk2440", "samsung,
	model = "jz2440_v3";
};

⑤ status
定义设备的运行状态
okay:设备正常允许;disabled:不可操作;fail:发生严重错误,需修复

&uart0{
	status = “disabled”;
};
1-4 内核对设备树的处理

一个单板启动时,uboot 先运行,它的作用是启动内核。 Uboot 会把内核和设备树文件都读入内存,然后启动内核。在启动内核时会把设备树在内存中的地址告诉内核。
内核处理过程
1、配置文件dts在 PC机上被编译为 dtb文件;( 编译设备树命令 make dtbs)
2、u-boot把 dtb文件传给内核;
3、内核解析 dtb文件,把每一个节点都转换为 device_node结构体;
4、对于某些 device_node结构体,会被转换为 platform_device结构体。
根节点被保存在全局变量of_root 中,从of_root 开始可以访问到任意节点

哪些设备树节点会被转换为 platform_device
① 根节点下含有 compatile属性的子节点
② 含有特定 compatile属性的节点的子节点
一个节点的compatile 属性 ,它的值是4者之一:“simple-bus”, “simple-mfd”, “isa”, “arm,amba-bus”, 那么它的子节点(需含compatile 属性)也可以转换为 platform_device
③ 总线 I2C、SPI节点下的子节点不转换为platform_device

platform_device 中含有resource 数组 , 它来自device_node的reg, interrupts 属性
platform_device.dev.of_node 指向 device_node, 可以通过它获得其他属性

device_node结构体转换为platform_device结构体图解

请添加图片描述

含有设备树信息的platform_device和platform_driver完整匹配过程
请添加图片描述
请添加图片描述

比较设备树信息如下
platform_device结构体

struct platform_device->struct device dev->struct device_node *of_node

在这里插入图片描述
platform_driver结构体

struct platform_driver->struct device_driver driver->const struct of_device_id	*of_match_table

在这里插入图片描述
设备树中的一些函数
设备树中的reg属性、interrupts 属性也会被转换为resource。这时使用这个函数取出这些资源。

struct resource *platform_get_resource(struct platform_device *dev,
				       unsigned int type, unsigned int num)

根据路径找到节点,根据路径找到节点,比如“/”就对应根节点,“ /memory ”对应 memory 节点。

static inline struct device_node *of_find_node_by_path(const char *path);

根据名字找到节点,节点如果定义了name 属性,那我们可以根据名字找到它。

extern struct device_node *of_find_node_by_name(struct device_node *from,const char *name);
1-5 设备树的LED驱动程序

设备树LED驱动框架
在这里插入图片描述
一般性匹配方法,设备树中的compatible属性与platform_driver结构体中的struct device_driver driver中的const struct of_device_id *of_match_table中的成员变量compatible进行匹配

在这里插入图片描述

读某个整数 u32/u64 函数原型为

extern int of_property_read_u32(const struct device_node *np,
const char *propname,  u32 *out_value);

在设备树中,节点大概是这样

xxx_node {
name2 = <0x60000000>;
};

调用 of_property_read_u32 (np, “name2”, &val) 时,val将得到值0x60000000

(1) 查看注册进内核的所有platform_device

/sys/devices/platform

一个设备对应一个目录,进入某个目录后,如果它有"driver"子目录软连接,就
表示这个 platform_device跟某个platform_driver配对了。
在这里插入图片描述
(2) 查看注册进内核的所有platform_driver

/sys/bus/platform/drivers

只需要在设备树中设置硬件信息,platform_dirver中获取硬件信息

static int chip_demo_gpio_probe(struct platform_device *pdev)
{
	struct device_node *np;
    int err = 0;
	int led_pin;

	np = pdev->dev.of_node;  /* associated device tree node */
	if(!np)
		return -1;
	err = of_property_read_u32(np, "pin", &led_pin);
	
    g_ledpins[g_ledcnt] = led_pin;
    led_class_create_device(g_ledcnt);  /*向上层注册引脚*/
    g_ledcnt++;
	
    return 0; 
}

二、按键

2-1 APP读取按键的4种方法

在做单片机开发时,要读取GPIO 按键,我们通常是执行一个循环,不断地检测 GPIO 引脚电平有没有发生变化。
但是在Linux系统中,读取GPIO按键要考虑到效率,引入了很多种方法:查询方式(非阻塞)休眠-唤醒(阻塞方式)poll方式异步通知方式

(1) 查询方式

在这里插入图片描述

(2) 休眠-唤醒方式

在这里插入图片描述
驱动程序中构造、注册一个file_operations 结构体,里面提供有对应的open、read函数。
(1) APP调用open时,导致驱动中对应的open函数被调用,在里面配置GPIO为输入引脚;并且注册GPIO的中断处理函数
(2) APP调用read时,导致驱动中对应的read函数被调用,如果有按键数据则直接返回给APP;否则APP在内核态休眠
当用户按下按键时,GPIO中断被触发,导致驱动程序之前注册的中断服务程序被执行。它会记录按键数据,并唤醒休眠中的APP
APP被唤醒后继续在内核态运行,即继续执行驱动代码,把按键数据返回给APP的用户空间。

(3) poll 方式

在这里插入图片描述
APP调用poll 或 select 函数,意图是“查询”是否有数据,这2个函数都可以指定一个超时时间,即在这段时间内没有数据的话就返回错误。这会导致驱动中对应的poll 函数被调用,如果有按键数据则直接返回给APP ;否则APP在内核态休眠一段时间
(1) 当用户按下按键时,GPIO中断被触发,导致驱动程序之前注册的中断服务程序被执行。它会记录按键数据,并唤醒休眠中的APP 。
(2) 如果用户没按下按键,但是超时时间到了,内核也会唤醒APP 。
所以APP被唤醒有 2 种原因:用户操作了按键或者超时。被唤醒的APP在内核态继续运行,即继续执行驱动代码,把“状态”返回给APP的用户空间 。
(3) APP得到poll/select 函数的返回结果后,如果确认是有数据的,则再调用read函数,这会导致驱动中的read函数被调用,这时驱动程序中含有数据,会直接返回数据。

(4) 异步通知方式

在这里插入图片描述
异步通知的实现原理是:内核给APP发信号。信号有很多种,如:SIGIO( I/O 事件发生)。
驱动程序中构造、注册一个file_operations 结构体,里面提供有对应的open、read、fasync 函 数。
(1) APP调用open时,导致驱动中对应的open函数被调用,在里面配置GPIO为输入引脚;并且注册GPIO的中断处理函数。
(2) APP给信号SIGIO注册自己的处理函数:my_signal_fun
(3) APP调用fcntl函数,把驱动程序的flag 改为FASYNC,这会导致驱动程序的fasync 函数被调用,它只是简单记录进程PID
(4) 当用户按下按键时,GPIO中断被触发,导致驱动程序之前注册的中断服务程序被执行。它会记录按键数据,然后给进程PID发送SIGIO信号。
(5) APP收到信号后会被打断,先执行信号处理函数:在信号处理函数中可以去调用read函数读取按键值。
(6) 信号处理函数返回后,APP会继续执行原先被打断的代码。

列出所有进程

ps -A

给某个进程发信号

kill -SIGIO 进程PID
2-2 查询方式的按键驱动程序框架

怎么操作寄存器?从芯片手册得到对应寄存器的物理地址,在驱动程序中使用ioremap函数映射得到虚拟地址。驱动程序中使用虚拟地址去访问寄存器。
在这里插入图片描述
(1) button_drv.c分配 /设置 /注册 file_operations结构体起承上启下的作用,向上提供button_open、button_read 供 APP调用。而这2个函数又会调用底层硬件提供的p_button_opr中的init、read函数操作硬件。
button_drv.c

static int major = 0;
static struct button_operations *p_button_opr;
static struct class *button_class;

static int button_open (struct inode *inode, struct file *file)
{
    int minor = iminor(inode);
    p_button_opr->init(minor);
    return 0;
}


static ssize_t button_read (struct file *file, char __user *buf, size_t size, loff_t *off)
{
    unsigned int minor = iminor(file_inode(file));
    char level;
    int err;
    
    level = p_button_opr->read(minor);
    err = copy_to_user(buf, &level, 1);
    return 0;
}

static struct file_operations button_fops = {
	.open = button_open,
	.read = button_read,
};

void register_button_operation(struct button_operations *opr)
{
	int i;
	p_button_opr = opr;   /*底层代码来向上一层提供这个结构体*/
	for(i =0; i < opr->count; i++){
		/*自动构造设备节点*/
		device_create(button_class, NULL, MKDEV(major,i), NULL, "100ask_button%d", i);
	}
}

void unregister_button_operation(void)
{
	int i;
	for(i =0; i < p_button_opr->count; i++){	
		device_destroy(button_class, MKDEV(major,i));
	}
}
EXPORT_SYMBOL(register_button_operation);
EXPORT_SYMBOL(unregister_button_operation);

int __init button_init(void)
{
	major = register_chrdev(0, "100ask_button", &button_fops);
	button_class = class_create(THIS_MODULE, "cardman_4040");
	if (IS_ERR(button_class))
		return -1;
	
	return 0;
}

void __exit button_exit(void)
{
	class_destroy(button_class);
	unregister_chrdev(major, "100ask_button");
}

module_init(button_init);
module_exit(button_exit);
MODULE_LICENSE("GPL");

(2) board_xxx.c分配 /设置 /注册 button_operations结构体,这个结构体是我们自己抽象出来的,里面定义单板xxx的按键操作函数。这样的结构易于扩展,对于不同的单板,只需要替换board_xxx.c 提供自己的button_operations 结构体即可。
board_xxx.c

static void board_xxx_button_init_gpio(int which)
{	
	printk("%s %s %d, init gpio for button %d\n", __FILE__, __FUNCTION__, __LINE__, which);
}

static int board_xxx_button_read_gpio(int which)
{
	printk("%s %s %d, read gpio for button %d\n", __FILE__, __FUNCTION__, __LINE__, which);
	return 1;
}

static struct button_operations my_buttons_ops = {
	.count = 2,
	.init = board_xxx_button_init_gpio,
	.read = board_xxx_button_read_gpio,
};

int board_xxx_button_init(void)
{
	register_button_operation(&my_buttons_ops);
	return 0;
}


void board_xxx_button_exit(void)
{
	unregister_button_operation();
}

module_init(board_xxx_button_init);
module_exit(board_xxx_button_exit);
MODULE_LICENSE("GPL");
2-3 具体单板的按键驱动程序(详解)

硬件配置部分
(1) 首先查看开发板的原理图,确认按键的GPIO引脚,如下图所示分别为GPIO5_IO01、GPIO4_IO14
在这里插入图片描述
(2) 使能GPIO引脚,找到芯片手册第18章CCM时钟控制模块,翻阅手册
在这里插入图片描述
在这里插入图片描述
得知GPIO5使能需要CCGR1寄存器的CG15、GPIO4使能需要CCGR3寄存器的CG6
在这里插入图片描述
在这里插入图片描述
因此GPIO5默认使能,最终只需要配置CCGR3寄存器的位12、13为1即可使能GPIO4。
(3) 设置GPIO5_IO01、GPIO4 _IO14为GPIO模式,访问IO端口复用章节,即:IOMUX Controller (IOMUXC)
通过Ctrl+F快速搜索
在这里插入图片描述
GPIO4 _IO14同理可得
在这里插入图片描述
(4) 设置GPIO5_IO01、GPIO4 _IO14为输入引脚,读取引脚电平。
查询28章General Purpose Input/Output (GPIO)。通过配置GPIOx_GDIR和GPIOx_PSR寄存器
在这里插入图片描述
在这里插入图片描述

struct imx6ull_gpio {
    volatile unsigned int dr;
    volatile unsigned int gdir;
    volatile unsigned int psr;
    volatile unsigned int icr1;
    volatile unsigned int icr2;
    volatile unsigned int imr;
    volatile unsigned int isr;
    volatile unsigned int edge_sel;
};

/* enable GPIO4 */
static volatile unsigned int *CCM_CCGR3; 
/* enable GPIO5 */
static volatile unsigned int *CCM_CCGR1; 
/* set GPIO5_IO03 as GPIO */
static volatile unsigned int *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER1;
/* set GPIO4_IO14 as GPIO */
static volatile unsigned int *IOMUXC_SW_MUX_CTL_PAD_NAND_CE1_B;

//static struct iomux *iomux;
static struct imx6ull_gpio *gpio4;
static struct imx6ull_gpio *gpio5;

static void board_imx6ull_button_init (int which) /* 初始化button, which-哪个button */      
{
    if (!CCM_CCGR1)
    {
        CCM_CCGR1 = ioremap(0x20C406C, 4);
        CCM_CCGR3 = ioremap(0x20C4074, 4);
        IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER1 = ioremap(0x229000C, 4);
		IOMUXC_SW_MUX_CTL_PAD_NAND_CE1_B        = ioremap(0x20E01B0, 4);

        //iomux = ioremap(0x20e0000, sizeof(struct iomux));
        gpio4 = ioremap(0x020A8000, sizeof(struct imx6ull_gpio));
        gpio5 = ioremap(0x20AC000, sizeof(struct imx6ull_gpio));
    }

    if (which == 0)
    {
        /* 1. enable GPIO5 
         * CG15, b[31:30] = 0b11
         */
        *CCM_CCGR1 |= (3<<30);
        
        /* 2. set GPIO5_IO01 as GPIO 
         * MUX_MODE, b[3:0] = 0b101
         */
        *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER1 = 5;

        /* 3. set GPIO5_IO01 as input 
         * GPIO5 GDIR, b[1] = 0b0
         */
        gpio5->gdir &= ~(1<<1);
    }
    else if(which == 1)
    {
        /* 1. enable GPIO4 
         * CG6, b[13:12] = 0b11
         */
        *CCM_CCGR3 |= (3<<12);
        
        /* 2. set GPIO4_IO14 as GPIO 
         * MUX_MODE, b[3:0] = 0b101
         */
        *IOMUXC_SW_MUX_CTL_PAD_NAND_CE1_B = 5;

        /* 3. set GPIO4_IO14 as input 
         * GPIO4 GDIR, b[14] = 0b0
         */
        gpio4->gdir &= ~(1<<14);
    }
    
}

static int board_imx6ull_button_read (int which) /* 读button, which-哪个 */
{
    //printk("%s %s line %d, button %d, 0x%x\n", __FILE__, __FUNCTION__, __LINE__, which, *GPIO1_DATAIN);
    if (which == 0)
        return (gpio5->psr & (1<<1)) ? 1 : 0;
    else
        return (gpio4->psr & (1<<14)) ? 1 : 0;
}
    
static struct button_operations my_buttons_ops = {
    .count = 2,
    .init = board_imx6ull_button_init,
    .read = board_imx6ull_button_read,
};

int board_imx6ull_button_drv_init(void)
{
    register_button_operations(&my_buttons_ops);
    return 0;
}

void board_imx6ull_button_drv_exit(void)
{
    unregister_button_operations();
}

module_init(board_imx6ull_button_drv_init);
module_exit(board_imx6ull_button_drv_exit);
MODULE_LICENSE("GPL");
  • 30
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值