基于正点原子i.mx Linux 驱动开发指南
一、pinctrl 子系统
1.1 简介
Linux 驱动讲究驱动分离与分层,
pinctrl
和
gpio
子系统就是驱动分离与分层思想下的产物,
驱动分离与分层其实就是按照面向对象编程的设计思想而设计的设备驱动框架。
大多数 SOC
的
pin
都是支持复用的,比如
I.MX6ULL
的
GPIO1_IO03
既可以作为普通的
GPIO
使用,也可以作为
I2C1
的
SDA
等等。此外我们还需要配置
pin
的电气特性,比如上
/下
拉、速度、驱动能力等等。传统的配置
pin
的方式就是直接操作相应的寄存器,但是这种配置
方式比较繁琐、而且容易出问题
(
比如
pin
功能冲突
)
。
pinctrl
子系统就是为了解决这个问题而引
入的,
pinctrl
子系统主要工作内容如下:
①、获取设备树中
pin
信息。
②、根据获取到的
pin
信息来设置
pin
的复用功能
③、根据获取到的
pin
信息来设置
pin
的电气特性,比如上
/
下拉、速度、驱动能力等。
对于我们使用者来讲,只需要在设备树里面设置好某个 pin
的相关属性即可,其他的初始
化工作均由
pinctrl
子系统来完成,
pinctrl
子系统源码目录为
drivers/pinctrl
。
1.2 I.MX6ULL 的 pinctrl 子系统驱动
1
、
PIN
配置信息详解
要使用 pinctrl
子系统,我们需要在设备树里面设置
PIN
的配置信息,毕竟
pinctrl
子系统要
根据你提供的信息来配置
PIN
功能,一般会在设备树里面创建一个节点来描述
PIN
的配置信
息。打开
imx6ull.dtsi
文件,找到一个叫做
iomuxc
的节点,如下所示:
iomuxc: iomuxc@020e0000 {
compatible = "fsl,imx6ul-iomuxc";
reg = <0x020e0000 0x4000>;
};
iomuxc 节点就是
I.MX6ULL
的
IOMUXC
外设对应的节点,看起来内容很少,没看出什么
跟
PIN
的配置有关的内容啊,别急!打开
imx6ull-lyh-emmc.dts
,找到如下所示内容:
&iomuxc {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_hog_1>;
imx6ul-evk {
pinctrl_hog_1: hoggrp-1 {
fsl,pins = <
MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x17059 /* SD1 CD */
MX6UL_PAD_GPIO1_IO05__USDHC1_VSELECT 0x17059 /* SD1 VSELECT */
MX6UL_PAD_GPIO1_IO00__ANATOP_OTG1_ID 0x13058 /* USB_OTG1_ID */
>;
};
pinctrl_csi1: csi1grp {
fsl,pins = <
MX6UL_PAD_CSI_MCLK__CSI_MCLK 0x1b008
MX6UL_PAD_CSI_PIXCLK__CSI_PIXCLK 0x1b008
MX6UL_PAD_CSI_VSYNC__CSI_VSYNC 0x1b008
MX6UL_PAD_CSI_HSYNC__CSI_HSYNC 0x1b008
MX6UL_PAD_CSI_DATA00__CSI_DATA02 0x1b008
MX6UL_PAD_CSI_DATA01__CSI_DATA03 0x1b008
MX6UL_PAD_CSI_DATA02__CSI_DATA04 0x1b008
MX6UL_PAD_CSI_DATA03__CSI_DATA05 0x1b008
MX6UL_PAD_CSI_DATA04__CSI_DATA06 0x1b008
MX6UL_PAD_CSI_DATA05__CSI_DATA07 0x1b008
MX6UL_PAD_CSI_DATA06__CSI_DATA08 0x1b008
MX6UL_PAD_CSI_DATA07__CSI_DATA09 0x1b008
>;
};
…………
上述代码就是向 iomuxc
节点追加数据,不同的外设使用的
PIN
不同、其配置也不
同,因此一个萝卜一个坑,将某个外设所使用的所有
PIN 都组织在一个子节点里面。
如果需要在
iomuxc
中添加我们自定义外设的
PIN
,那么需要新建一个子节点,然
后将这个自定义外设的所有
PIN
配置信息都放到这个子节点中。
以 MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x17059 为例。
对于一个 PIN
的配置主要包括两方面,一个是设置这个
PIN
的复用功能,另一个就是设置这个
PIN
的电气特性。所以我们可以大胆的
猜测
UART1_RTS_B
的这两部分配置信息一个是设置
UART1_RTS_B
的复用功能,一个是用来
设置
UART1_RTS_B
的电气特性。
首先来看一下 MX6UL_PAD_UART1_RTS_B__GPIO1_IO19
,这是一个宏定义,定义在文件
arch/arm/boot/dts/imx6ul-pinfunc.h
中,
imx6ull.dtsi
会引用
imx6ull-pinfunc.h
这个头文件,而
imx6ull-pinfunc.h
又会引用
imx6ul-pinfunc.h 这个头文件,相关定义如下:
#define MX6UL_PAD_UART1_RTS_B__UART1_DCE_RTS 0x0090 0x031C 0x0620 0x0 0x3
#define MX6UL_PAD_UART1_RTS_B__UART1_DTE_CTS 0x0090 0x031C 0x0000 0x0 0x0
#define MX6UL_PAD_UART1_RTS_B__ENET1_TX_ER 0x0090 0x031C 0x0000 0x1 0x0
#define MX6UL_PAD_UART1_RTS_B__USDHC1_CD_B 0x0090 0x031C 0x0668 0x2 0x1
#define MX6UL_PAD_UART1_RTS_B__CSI_DATA05 0x0090 0x031C 0x04CC 0x3 0x1
#define MX6UL_PAD_UART1_RTS_B__ENET2_1588_EVENT1_OUT 0x0090 0x031C 0x0000 0x4 0x0
#define MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x0090 0x031C 0x0000 0x5 0x0
#define MX6UL_PAD_UART1_RTS_B__USDHC2_CD_B 0x0090 0x031C 0x0674 0x8 0x2
有 8
个以“
MX6UL_PAD_UART1_RTS_B
”开头的宏定义,大家
仔细观察应该就能发现,这
8
个宏定义分别对应
UART1_RTS_B
这个
PIN
的
8
个复用
IO
。查
阅《
I.MX6ULL
参考手册》可以知
UART1_RTS_B
的可选复用
IO
如图
所示:
宏定义
MX6UL_PAD_UART1_RTS_B__GPIO1_IO19
表示将 UART1_RTS_B
这个
IO
复用为
GPIO1_IO19
。此宏定义后面跟着
5
个数字,也就是这个宏定义
的具体值:
0x0090 0x031C 0x0000 0x5 0x0
0x0090
:
mux_reg
寄存器偏移地址,设备树中的
iomuxc
节点就是
IOMUXC
外设对应的节点 , 根 据 其
reg
属 性 可 知
IOMUXC
外 设 寄 存 器 起 始 地 址 为
0x020e0000
。 因 此
0x020e0000+0x0090=0x020e0090
0x031C
:
conf_reg
寄存器偏移地址,和
mux_reg
一样,
0x020e0000+0x031c=0x020e031c
,
这个就是寄存器
IOMUXC_SW_PAD_CTL_PAD_UART1_RTS_B
的地址。
0x0000
:
input_reg
寄存器偏移地址,有些外设有
input_reg
寄存器,有
input_reg
寄存器的外设需要配置
input_reg
寄存器。没有的话就不需要设置,
UART1_RTS_B
这个
PIN
在做
GPIO1_IO19
的时候是没有
input_reg
寄存器,因此这里
intput_reg
是无效的。
0x5:
mux_reg
寄 存 器 值 , 在 这 里 就 相 当 于 设 置
IOMUXC_SW_MUX_CTL_PAD_UART1_RTS_B
寄存器为
0x5
,也即是设置
UART1_RTS_B
这
个
PIN
复用为
GPIO1_IO19
。
0x0
:
input_reg
寄存器值,在这里无效。
这就是宏 MX6UL_PAD_UART1_RTS_B__GPIO1_IO19
的含义,看的比较仔细的读者应该
会发现并没有
conf_reg 寄存器的值,其实
0x17059
就是
conf_reg
寄存器值。此值由用户自行设置,通
过此值来设置一个
IO
的上
/
下拉、驱动能力和速度等。在这里就相当于设置寄存器
IOMUXC_SW_PAD_CTL_PAD_UART1_RTS_B
的值为
0x17059
。
MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x17059
1.3 pin驱动程序讲解
此内容并不影响驱动开发,本文略去这部分内容,想深入了解可以参考正点原子I.MX6U嵌入式Linux驱动开发指南中的 45.1.2 小节。
1.4 添加 pinctrl 节点模板
这里我们虚拟一个名为“
test
”的设备,
test
使用了
GPIO1_IO00
这个
PIN
的
GPIO
功能,
pinctrl
节点添加过程如下
,打开
imx6ull-lyh-emmc.dts
,在
iomuxc
节点
中的“
imx6ul-evk
”子节点下添加“
pinctrl_test
”节点。添加完成以后如下所示:
pinctrl_test: testgrp {
fsl,pins = <
MX6UL_PAD_GPIO1_IO00__GPIO1_IO00 config /*config 是具体设置值*/
>;
};
注意:属性名字一定要为“
fsl,pins
”,
因为对于
I.MX
系列
SOC
而言,
pinctrl
驱动程序是通过读取“
fsl,pins
”属性值来获取
PIN
的配
置信息
二、gpio 子系统
2.1 简介
上一小节讲解了 pinctrl
子系统,
pinctrl
子系统重点是设置
PIN(
有的
SOC
叫做
PAD)
的复用
和电气属性,如果
pinctrl
子系统将一个
PIN
复用为
GPIO
的话,那么接下来就要用到
gpio
子系
统了。
gpio
子系统顾名思义,就是用于初始化
GPIO
并且提供相应的
API
函数,比如设置
GPIO
为输入输出,读取
GPIO
的值等。
gpio
子系统的主要目的就是方便驱动开发者使用
gpio
,驱动
开发者在设备树中添加
gpio
相关信息,然后就可以在驱动程序中使用
gpio
子系统提供的
API
函数来操作
GPIO
,
Linux
内核向驱动开发者屏蔽掉了
GPIO
的设置过程,极大的方便了驱动开
发者使用
GPIO
。
2.2 I.MX6ULL 的 gpio 子系统驱动
1
、设备树中的
gpio
信息
I.MX6ULL-ALPHA 开发板上的
UART1_RTS_B
做为
SD
卡的检测引脚,
UART1_RTS_B
复
用为
GPIO1_IO19
,通过读取这个
GPIO
的高低电平就可以知道
SD
卡有没有插入。首先肯定是
将
UART1_RTS_B
这个
PIN
复用为
GPIO1_IO19
,并且设置电气属性,也就是上一小节讲的
pinctrl
节点。
pinctrl 配置好以后就是设置
gpio
了,
SD
卡驱动程序通过读取
GPIO1_IO19
的值来判断
SD
卡有没有插入,但是
SD
卡驱动程序怎么知道
CD
引脚连接的
GPIO1_IO19
呢?肯定是需要设
备树告诉驱动啊!在设备树中
SD
卡节点下添加一个属性来描述
SD
卡的
CD 引脚就行了,
SD
卡连接在 I.MX6ULL
的
usdhc1
接口上,在
imx6ull-lyh-emmc.dts
中找到名为“
usdhc1
”的节点
&usdhc1 {
pinctrl-names = "default", "state_100mhz", "state_200mhz";
pinctrl-0 = <&pinctrl_usdhc1>;
pinctrl-1 = <&pinctrl_usdhc1_100mhz>;
pinctrl-2 = <&pinctrl_usdhc1_200mhz>;
cd-gpios = <&gpio1 19 GPIO_ACTIVE_LOW>;
keep-power-in-suspend;
enable-sdio-wakeup;
vmmc-supply = <®_sd1_vmmc>;
no-1-8-v;
status = "okay";
};
pinctrl-0~2 都是
SD
卡其他
PIN
的
pincrtl 节点信息。
属性“cd-gpios
”描述了
SD
卡的
CD
引脚使用的哪个
IO
。属性值一共有三个,
我们来看一下这三个属性值的含义,“
&gpio1
”表示
CD
引脚所使用的
IO
属于
GPIO1
组,“
19
”
表示
GPIO1
组的第
19
号
IO
,通过这两个值
SD
卡驱动程序就知道
CD
引脚使用了
GPIO1_IO19
。“
GPIO_ACTIVE_LOW
”表示低电平有效,如果改为“
GPIO_ACTIVE_HIGH
”就表
示高电平有效。
2
、
GPIO
驱动程序简介
此内容并不影响驱动开发,本文略去这部分内容,想深入了解可以参考正点原子I.MX6U嵌入式Linux驱动开发指南中的 45.2.2 小节。
2.3 gpio子系统api函数
对于驱动开发人员,设置好设备树以后就可以使用 gpio
子系统提供的
API
函数来操作指定
的
GPIO
,
gpio
子系统向驱动开发人员屏蔽了具体的读写寄存器过程。这就是驱动分层与分离
的好处。
gpio
子系统提供的常用的
API
函数有下面几个:
1
、
gpio_request
函数
gpio_request
函数用于申请一个
GPIO
管脚,在使用一个
GPIO
之前一定要使用
gpio_request
进行申请,函数原型如下:
int gpio_request(unsigned gpio, const char *label)
函数参数和返回值含义如下:
gpio
:要申请的
gpio
标号,使用
of_get_named_gpio
函数从设备树获取指定
GPIO
属性信
息,此函数会返回这个
GPIO
的标号。
label
:给
gpio
设置个名字。
返回值:
0
,申请成功;其他值,申请失败。
2
、
gpio_free
函数
如果不使用某个
GPIO
了,那么就可以调用
gpio_free
函数进行释放。函数原型如下:
void gpio_free(unsigned gpio)
函数参数和返回值含义如下:
gpio
:要释放的
gpio
标号。
返回值:
无。
3
、
gpio_direction_input
函数
此函数用于设置某个
GPIO
为输入,函数原型如下所示:
int gpio_direction_input(unsigned gpio)
函数参数和返回值含义如下:
gpio
:要设置为输入的
GPIO
标号。
返回值:
0
,设置成功;负值,设置失败。
4
、
gpio_direction_output
函数
此函数用于设置某个
GPIO
为输出,并且设置默认输出值,函数原型如下:
int gpio_direction_output(unsigned gpio, int value)
函数参数和返回值含义如下:
gpio
:要设置为输出的
GPIO
标号。
value
:
GPIO
默认输出值。
返回值:
0
,设置成功;负值,设置失败。
5
、
gpio_get_value
函数
此函数用于获取某个
GPIO
的值
(0
或
1)
,此函数是个宏,定义所示:
#define gpio_get_value __gpio_get_valueint __gpio_get_value(unsigned gpio)
函数参数和返回值含义如下:
gpio
:要获取的
GPIO
标号。
返回值:
非负值,得到的
GPIO
值;负值,获取失败。
6
、
gpio_set_value
函数
此函数用于设置某个
GPIO
的值,此函数是个宏,定义如下
#define gpio_set_value __gpio_set_valuevoid __gpio_set_value(unsigned gpio, int value)
函数参数和返回值含义如下:
gpio
:要设置的
GPIO
标号。
value
:
要设置的值。
返回值:
无
关于
gpio
子系统常用的
API
函数就讲这些,这些是我们用的最多的。
2.4 设备树中添加 gpio 节点模板
本节我们来学习一下如何创建
test
设备的
GPIO
节点。
test {
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_test>;
gpio = <&gpio1 0 GPIO_ACTIVE_LOW>;
}
2.5 gpio 相关的of函数
1
、
of_gpio_named_count
函数
of_gpio_named_count
函数用于获取设备树某个属性里面定义了几个
GPIO
信息,要注意的
是空的
GPIO
信息也会被统计到,比如:
gpios = <0&gpio1 1 20&gpio2 3 4>;
上述代码的“
gpios
”节点一共定义了
4
个
GPIO
,但是有
2
个是空的,没有实际的含义。
通过
of_gpio_named_count
函数统计出来的
GPIO
数量就是
4
个,此函数原型如下:
int of_gpio_named_count(struct device_node *np, const char *propname)
函数参数和返回值含义如下:
np
:设备节点。
propname
:要统计的
GPIO
属性。
返回值:
正值,统计到的
GPIO
数量;负值,失败。
2
、
of_gpio_count
函数
和
of_gpio_named_count
函数一样,但是不同的地方在于,此函数统计的是“
gpios
”这个属
性的
GPIO
数量,而
of_gpio_named_count
函数可以统计任意属性的
GPIO
信息,函数原型如下
所示:
int of_gpio_count(struct device_node *np)
函数参数和返回值含义如下:
np
:设备节点。
返回值:
正值,统计到的
GPIO
数量;负值,失败。
3
、
of_get_named_gpio
函数
此函数获取
GPIO
编号,因为
Linux
内核中关于
GPIO
的
API
函数都要使用
GPIO
编号,
此函数会将设备树中类似
<&gpio5 7 GPIO_ACTIVE_LOW>
的属性信息转换为对应的
GPIO
编
号,此函数在驱动中使用很频繁!函数原型如下:
int of_get_named_gpio(struct device_node *np,const char *propname,int index)
函数参数和返回值含义如下:
np
:设备节点。
propname
:包含要获取
GPIO
信息的属性名。
index
:
GPIO
索引,因为一个属性里面可能包含多个
GPIO
,此参数指定要获取哪个
GPIO
的编号,如果只有一个
GPIO
信息的话此参数为
0
。
返回值:
正值,获取到的
GPIO
编号;负值,失败。
三、LED 实验程序编写
本章实验我们使用 pinctrl
和
gpio
子系统来完成
LED 灯驱动,I.MX6U-ALPHA
开发板上的
LED
灯使用了
GPIO1_IO03
这个
PIN。
3.1 修改设备树文件
打开 imx6ull-lyh-emmc.dts
,在
iomuxc
节点的
imx6ul-evk
子节点下创建一个名为“
pinctrl_led
”的子节点,节点
内容如下所示:
pinctrl_led: ledgrp {
fsl,pins = <
MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x10B0 /*LED 0*/
>;
};
3.2 添加 led 设备节点
在根节点“
/
”下创建
LED
灯节点,节点名为“
gpioled
”,节点内容如下:
3.3 检查 PIN 是否已被使用
因为我们所使用的设备树基本都是在半导体厂商提供的设备树文件基础上修改而来的,而半导体厂商提供的设备树是根据自己官方开发板编写的,很多
PIN
的配置和我们所使用的开发板不一样。
检查 PIN
有没有被其他外设使用包括两个方
面:
①、检查 pinctrl 设置。
②、如果这个 PIN 配置为
GPIO
的话,检查这个
GPIO
有没有被别的外设使用。
①dts文件中搜索 GPIO1_IO03
pinctrl_tsc 节点是 TSC(电阻触摸屏接口)的 pinctrl 节点,默认情况下 GPIO1_IO03 作为了 TSC 外设的 PIN。所以我们需要将其屏蔽掉,和 C 语言一样,在要屏蔽的内容前后加上“/*”和“*/”符号即可。其实在 I.MX6U-ALPHA 开发板上并没有用到 TSC 接口,所以 TSC 的内容可以全部屏蔽掉。
②因为本章实验我们将
GPIO1_IO03
这个
PIN
配置为了
GPIO
,所以还需要查找一下有没有
其他的外设使用了
GPIO1_IO03
,在
imx6ull-lyh-emmc.dts
中搜索“
gpio1 3
”,屏蔽掉。
设备树编写完成以后使用“make dtbs
”命令重新编译设备树,然后使用新编译出来的 imx6ull-lyh-emmc.dtb
文件启动
Linux
系统。启动成功以后进入“
/proc/device-tree
”目录中
查看“
gpioled
”节点是否存在,如果存在的话就说明设备树基本修改成功
3.4 LED 驱动程序
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
/***************************************************************
Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
文件名 : gpioled.c
作者 : lyh
版本 : V1.0
描述 : LED驱动文件。
其他 : 无
日志 : 初版V1.0 2024/4/13 lyh创建
***************************************************************/
#define GPIOLED_CNT 1 /* 设备号个数 */
#define GPIOLED_NAME "gpioled" /* 名字 */
#define LEDOFF 0 /* 关灯 */
#define LEDON 1 /* 开灯 */
/* gpioled设备结构体 */
struct gpioled_dev{
dev_t devid; /* 设备号 */
struct cdev cdev; /* cdev */
struct class *class; /* 类 */
struct device *device; /* 设备 */
int major; /* 主设备号 */
int minor; /* 次设备号 */
struct device_node *nd; /* 设备节点 */
int led_gpio; /* gpio编号 */
};
struct gpioled_dev gpioled; /* led设备 */
/*
* @description : 打开设备
* @param - inode : 传递给驱动的inode
* @param - filp : 设备文件,file结构体有个叫做private_data的成员变量
* 一般在open的时候将private_data指向设备结构体。
* @return : 0 成功;其他 失败
*/
static int led_open(struct inode *inode, struct file *filp)
{
filp->private_data = &gpioled; /* 设置私有数据 */
return 0;
}
/*
* @description : 从设备读取数据
* @param - filp : 要打开的设备文件(文件描述符)
* @param - buf : 返回给用户空间的数据缓冲区
* @param - cnt : 要读取的数据长度
* @param - offt : 相对于文件首地址的偏移
* @return : 读取的字节数,如果为负值,表示读取失败
*/
static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
return 0;
}
/*
* @description : 向设备写数据
* @param - filp : 设备文件,表示打开的文件描述符
* @param - buf : 要写给设备写入的数据
* @param - cnt : 要写入的数据长度
* @param - offt : 相对于文件首地址的偏移
* @return : 写入的字节数,如果为负值,表示写入失败
*/
static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
int retvalue;
unsigned char databuf[1];
unsigned char ledstat;
struct gpioled_dev *dev = filp->private_data;
retvalue = copy_from_user(databuf, buf, cnt);
if (retvalue < 0){
printk("kernel write failed!\r\n");
return -EFAULT;
}
ledstat = databuf[0]; /* 获取状态值 */
if (ledstat == LEDON){
gpio_set_value(dev->led_gpio, 0); /* 打开 LED 灯 */
}
else if (ledstat == LEDOFF){
gpio_set_value(dev->led_gpio, 1); /* 关闭 LED 灯 */
}
return 0;
}
/*
* @description : 关闭/释放设备
* @param - filp : 要关闭的设备文件(文件描述符)
* @return : 0 成功;其他 失败
*/
static int led_release(struct inode *inode, struct file *filp)
{
return 0;
}
/* 设备操作函数 */
static struct file_operations gpioled_fops = {
.owner = THIS_MODULE,
.open = led_open,
.read = led_read,
.write = led_write,
.release = led_release,
};
/*
* @description : 驱动出口函数
* @param : 无
* @return : 无
*/
static int __init led_init(void)
{
int ret = 0;
gpioled.nd = of_find_node_by_path("/gpioled");
if(gpioled.nd == NULL) {
printk("gpioled node cant not found!\r\n");
return -EINVAL;
} else {
printk("gpioled node has been found!\r\n");
}
/* 2、 获取设备树中的 gpio 属性,得到 LED 所使用的 LED 编号 */
gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led-gpio", 0);
if(gpioled.led_gpio < 0) {
printk("can't get led-gpio");
return -EINVAL;
}
printk("led-gpio num = %d\r\n", gpioled.led_gpio);
/* 3、设置 GPIO1_IO03 为输出,并且输出高电平,默认关闭 LED 灯 */
ret = gpio_direction_output(gpioled.led_gpio, 1);
if(ret < 0) {
printk("can't set gpio!\r\n");
}
/* 注册字符设备驱动 */
/* 1、创建设备号 */
if (gpioled.major) { /* 定义了设备号 */
gpioled.devid = MKDEV(gpioled.major, 0);
register_chrdev_region(gpioled.devid, GPIOLED_CNT, GPIOLED_NAME);
} else { /* 没有定义设备号 */
alloc_chrdev_region(&gpioled.devid, 0, GPIOLED_CNT, GPIOLED_NAME); /* 申请设备号 */
gpioled.major = MAJOR(gpioled.devid); /* 获取分配号的主设备号 */
gpioled.minor = MINOR(gpioled.devid); /* 获取分配号的次设备号 */
}
printk("gpioled major=%d,minor=%d\r\n",gpioled.major, gpioled.minor);
/* 2、初始化cdev */
gpioled.cdev.owner = THIS_MODULE;
cdev_init(&gpioled.cdev, &gpioled_fops);
/* 3、添加一个cdev */
cdev_add(&gpioled.cdev, gpioled.devid, GPIOLED_CNT);
/* 4、创建类 */
gpioled.class = class_create(THIS_MODULE, GPIOLED_NAME);
if (IS_ERR(gpioled.class)) {
return PTR_ERR(gpioled.class);
}
/* 5、创建设备 */
gpioled.device = device_create(gpioled.class, NULL, gpioled.devid, NULL, GPIOLED_NAME);
if (IS_ERR(gpioled.device)) {
return PTR_ERR(gpioled.device);
}
return 0;
}
/*
* @description : 驱动出口函数
* @param : 无
* @return : 无
*/
static void __exit led_exit(void)
{
/* 注销字符设备驱动 */
cdev_del(&gpioled.cdev);/* 删除cdev */
unregister_chrdev_region(gpioled.devid, GPIOLED_CNT); /* 注销设备号 */
device_destroy(gpioled.class, gpioled.devid);
class_destroy(gpioled.class);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("lyh");
3.5 ledAPP 程序
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
int fd;
int ret;
if(argc != 3){
printf("error Usage\r\n");
return -1;
}
fd = open(argv[1], O_RDWR);
if(fd < 0){
printf("file %s open failed!\r\n", argv[1]);
return -1;
}
char databuf[1] = {0};
databuf[0] = (char)atoi(argv[2]);
ret = write(fd, databuf, sizeof(databuf));
if(ret < 0){
printf("LED Control Failed!\r\n");
close(fd);
return -1;
}
ret = close(fd); /* 关闭文件 */
if(ret < 0){
printf("file %s close failed!\r\n", argv[1]);
return -1;
}
return 0;
}
四、测试
编译加载驱动后,使用:
./ledApp /dev/gpioled 1 //打开 LED 灯./ledApp /dev/gpioled 1 //关闭 LED 灯