目录
[一. Linux 驱动分层思想](#一. Linux 驱动分层思想)
[1.1 Linux 系统驱动设计三大思想](#1.1 Linux 系统驱动设计三大思想)
[1.1.1 面向对象思想](#1.1.1 面向对象思想)
[1.1.2 设备资源与驱动分层思想](#1.1.2 设备资源与驱动分层思想)
[1.1.3 驱动分层思想](#1.1.3 驱动分层思想)
[1.2 PinCtrl 与 GPIO 子系统](#1.2 PinCtrl 与 GPIO 子系统)
[二. PinCtrl 子系统](#二. PinCtrl 子系统)
[2.1 回顾设备树](#2.1 回顾设备树)
[2.2 PinCtrl 子系统介绍](#2.2 PinCtrl 子系统介绍)
[2.3 PIN 配置信息](#2.3 PIN 配置信息)
[2.3.1 如何添加配置信息](#2.3.1 如何添加配置信息)
[2.4 添加 PinCtrl 的 PIN 信息](#2.4 添加 PinCtrl 的 PIN 信息)
[三. GPIO 子系统](#三. GPIO 子系统)
[3.1 GPIO 子系统简介](#3.1 GPIO 子系统简介)
[3.2 设备树中的 GPIO 信息](#3.2 设备树中的 GPIO 信息)
[3.3 GPIO 子系统 API 函数](#3.3 GPIO 子系统 API 函数)
[3.3.1 of_get_named_gpio](#3.3.1 of_get_named_gpio)
[3.3.2 gpio_request](#3.3.2 gpio_request)
[3.3.3 gpio_free](#3.3.3 gpio_free)
[3.3.4 gpio_get_value](#3.3.4 gpio_get_value)
[3.3.5 gpio_set_value](#3.3.5 gpio_set_value)
[3.4 添加 gpio 配置示例](#3.4 添加 gpio 配置示例)
[四. 使用 PinCtrl 与 GPIO 子系统驱动LED](#四. 使用 PinCtrl 与 GPIO 子系统驱动LED)
[4.1 设备树增添](#4.1 设备树增添)
[4.2 驱动部分](#4.2 驱动部分)
[4.3 用户部分](#4.3 用户部分)
基于设备树的 LED 驱动是配置 LED 灯所使用的 GPIO 寄存器,驱动开发方式和裸机基本没什么区别。Linux 是一个庞大而完善的系统, 尤其是驱动框架,像 GPIO 这种最基本的驱动不可能采用"原始"的裸机驱动开发方式。针对此问题,Linux 内核提供了 pinctrl 和 gpio 子系统用于 GPIO 驱动
一. Linux 驱动分层思想
1.1 Linux 系统驱动设计三大思想
1.1.1 面向对象思想
更接近人的思维方式,从抽象到具体的过程
1.1.2 设备资源与驱动分层思想
设备资源与驱动分离 思想 :设备树
驱动(drv.c) +设备树(dtb,驱动相关的资源)
设备与驱动分离:每个硬件平台不同,形成对应的设备树文件,在内核运行时交给驱
动,驱动获得硬件信息再进行后续操作,方便了驱动的移植,减少重复的驱动代码编写。
1.1.3 驱动分层思想
驱动分层思想: Linux内核大量使用了面向对象的设计思想,可以为某一类相似的事物定义一个基类,而具体的事物可以继承这个基类中的函数。如果对于继承的这个事物而言,某成员函数的实现与基类一致,那它就可以直接继承基类的函数。相反,它也可以重写,对父类的函数进行重新定义。若子类中的方法与父类中的某方法具有相同的方法名、返回类型和参数表,则新方法将覆盖原有的方法,这种面向对象的"多态"设计思想极大地提升了代码的可重用能力,是对现实世界中事物之间关系的一种良好呈现
pinctrl 和 gpio 子系统就是驱动分离与分层思想下的产物
1.2 PinCtrl 与 GPIO 子系统
在此结构中,每个功能都通过子系统的方式来进行管理, pinctrl子系统负责管理引脚复用及电气特性配置 ,如果配置为GPIO功能,则GPIO子系统再对GPIO进行管理。如果配置为I2c功能,则通过I2c子系统对I2c功能管理

二. PinCtrl 子系统
2.1 回顾设备树
在上一节的设备树中,初始化一个 LED 外设需要以下步骤:
1、修改设备树,增加相应的节点,设备树中重要是 reg 属性,reg 里面记录了与 LED 相关的 GPIO 寄存器的地址,是进行初始化的关键
2、在驱动中获取 reg 属性中获取 IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 和 IOMUXC_SW_PAD_ CTL_PAD_GPIO1_IO03 这两个寄存器地址,并且初始化这两个寄存器。这两个寄存器用于设置 GPIO1_IO03 这个 PIN 的复用功能与电气属性
3、继续获取 reg 属性中的 DR 与 GDIR 寄存器的地址,配置 GPIO 的输入输出模式,并且初始化输出电平。
可以看到初始化一个 GPIO 就是引脚复用、电气属性和配置 GPIO 的输入输出两部分。引脚的设置基本都是这两方面,因此 Linux 内核针对 PIN 的配置推出了 pinctrl 子系统 ,对于 GPIO 的配置推出了 gpio 子系统
2.2 PinCtrl 子系统介绍
PinCtrl 子系统的主要工作:
1、获取设备树中的 pin 信息
2、根据获取到的 pin 信息来初始化对应引脚的引脚复用与电气属性
2.3 PIN 配置信息
要使用 pinctrl 子系统,需要在设备树里面设置 PIN 的配置信息,因为 pinctrl 子系统要根据用户提供的信息来配置 PIN 功能,一般会在设备树里面创建一个节点来描述 PIN 的配置信息
在 imx6ull.dtsi 文件,找到一个叫做 iomuxc 的节点,如下所示:
iomuxc 节点就是 IMX6ULL 的 IOMUXC 外设对应的节点
cpp
iomuxc: iomuxc@020e0000 {
compatible = "fsl,imx6ul-iomuxc";
reg = <0x020e0000 0x4000>;
};
同样在 imx6ull-alientek-emmc.dts 这个文件中也有 iomuxc 这个节点:
下图就是向 iomuxc 节点追加数据,不同的外设使用的 PIN 不同、其配置也不同,将某个外设所使用的所有 PIN 都组织在一个子节点里面。示例中 pinctrl_hog_1 子节点就是和热插拔有关的 PIN 集合,比如 USB OTG 的 ID 引脚。pinctrl_flexcan1 子节点是 flexcan1 这个外设所使用的 PIN,pinctrl_wdog 子节点是 wdog 外设所使 用的 PIN。
如果需要在 iomuxc 中添加自定义外设的 PIN,那么需要新建一个子节点,然后将这个自定义外设的所有 PIN 配置信息都放到这个子节点中。

2.3.1 如何添加配置信息
以下面这个 pin 的配置信息为例,说明如何添加 pinctrl 的 pin 配置信息:
cpp
pinctrl_ptled: ptledgrp {
fsl,pins = <
MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x10B0
>;
};
对于一个 PIN 的配置主要包括两方面, 一个是设置这个 PIN 的复用功能,另一个就是设置这个 PIN 的电气特性。所以,上面的这两部分配置信息一个是设置复用功能,一个是用来设置电气特性
1、引脚复用的配置
MX6UL_PAD_GPIO1_IO03__GPIO1_IO03
首先,MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 这是一个宏定义,定义在头文件 arch/arm/boot/dts/imx6ul-pinfunc.h 中,部分内容如下:
这个宏的意思就是将GPIO1_IO03复用为GPIO1_IO03

其他参数的意思:
此宏定义后面跟着 5 个数字,也就是这个宏定义的具体值,含义如下:
bash
0x0068 0x02F4 0x0000 0x5 0x0

1、0x0068:mux_reg 寄存器偏移地址 ,设备树中的 iomuxc 节点就是 IOMUXC 外设对应的节点,根据其 reg 属性可知 IOMUXC 外设寄存器起始地址为 0x020e0000。因此 0x020e0000 + 0x0068= 0x020e0068,IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03寄存器地址正好是 0x020e0068,可以在《IMX6ULL 参考手册》中找到,如图所示:

2、0x02F4:conf_reg 寄存器偏移地址,和 mux_reg 一样,

3、0x0000:input_reg 寄存器偏移地址 ,有些外设有 input_reg 寄存器,有 input_reg 寄存器的外设需要配置 input_reg 寄存器。没有的话就不需要设置,GPIO1_IO03 这个 PIN 在做 GPIO1_IO03 的时候是没有 input_reg 寄存器,因此这里 intput_reg 是无效的
4、0x5:mux_reg 寄存器值 ,在这里就相当于设置 IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 寄存器为 0x5,也即是设置 GPIO1_IO03 这个 PIN 复用为 GPIO1_IO03。
5、0x0:input_reg 寄存器值,在这里无效。
2、电气属性的配置
cpp
pinctrl_ptled: ptledgrp {
fsl,pins = <
MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x10B0
>;
};
MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x10B0
0x10B0 就是 conf_reg 寄存器值
2.4 添加 PinCtrl 的 PIN 信息
示例:
cpp
pinctrl_ptled: ptledgrp {
fsl,pins = <
MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x10B0
>;
};
注意点:
1、标签:节点名称:设备树内部引用其他节点一般使用的是标签,在驱动中一般使用节点名称
2、添加"fsl,pins"属性: 设备树是通过属性来保存信息的,因此我们需要添加一个属性,属性名字一定要为 "fsl,pi ns",因为对于 I.MX 系列 SOC 而言,pinctrl 驱动程序是通过读取"fsl,pins"属性值来获取 PIN 的配置信息
3、注意在之前设备树的节点中一定要引用此 PIN 配置,即可在驱动中省去对引脚复用设置与电气配置这两步骤,简化代码 <&pinctrl_led>
语法示例:
cpp
/* 节点标签 :节点分组, 多用标签,可以被引用 &pinctrl_led */
pinctrl_led: ledgrp {
fsl,pins = <
MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x10B0 /* LED0 ,电气特性的配置, 这里的
宏名,由fsl事先写好 */
>;
};
gpioleds {
#address-cells = <1>;
#size-cells = <1>;
compatible = "pute-gpioleds";
pinctrl-names = "default"; //定义引脚状态的名称列表,引脚默认状态
pinctrl-0 = <&pinctrl_led>; // &pinctrl_led,引用节点pinctrl_led
};
三. GPIO 子系统
3.1 GPIO 子系统简介
上一小节讲解了 pinctrl 子系统,pinctrl 子系统重点是设置 PIN的复用和电气属性,如果pinctrl 子系统将一个 PIN 复用为 GPIO 的话,那么接下来就要用到 gpio 子系统。gpio 就是用于初始化 GPIO 并且提供相应的 API 函数,比如设置 GPIO 为输入输出,读取 GPIO 的值等。gpio 子系统的主要目的就是方便驱动开发者使用 gpio,驱动开发者在设备树中添加 gpio 相关信息,然后就可以在驱动程序中使用 gpio 子系统提供的 API 函数来操作 GPIO,Linux 内核向驱动开发者屏蔽掉了 GPIO 的设置过程,极大的方便了驱动开发者使用 GPIO
3.2 设备树中的 GPIO 信息
这里以 SD 卡的驱动为例子进行说明:
cpp
pinctrl_hog_1: hoggrp-1 {
fsl,pins = <
MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x17059 /* SD1 CD */
>;
};
pinctrl 配置好以后就是设置 gpio 了,SD 卡驱动程序通过读取 GPIO1_IO19 的值来判断 SD 卡有没有插入,**但是 SD 卡驱动程序怎么知道 CD 引脚连接的 GPIO1_IO19 呢?**肯定是需要设备树告诉驱动,在设备树中 SD 卡节点下添加一个属性来描述 SD 卡的 CD 引脚就行了,SD 卡驱动直接读取这个属性值就知道 SD 卡的 CD 引脚使用的是哪个 GPIO 了。SD 卡连接在 IMX6ULL 的 usdhc1 接口上,在 imx6ull-alientek-emmc.dts 中找到名为"usdhc1"的节点,这个节点就是 SD 卡设备节点,如下所示:
cpp
&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>;
status = "okay";
};
解释如下:
属性 "cd-gpios" 描述了 SD 卡的 CD 引脚使用的哪个 IO。属性值一共有三个
1、"&gpio1" 表示 CD 引脚所使用的 IO 属于 GPIO1 组,
**2、"19"**表示 GPIO1 组的第 19 号 IO,通过这两个值 SD 卡驱动程序就知道 CD 引脚使用了 GPIO1_IO19 这 GPIO。
3、"GPIO_ACTIVE_LOW" 表示低电平有效,如果改为 **"GPIO_ACTIVE_HIGH"**就表示高电平有效。
cpp
#ifndef _DT_BINDINGS_GPIO_GPIO_H
#define _DT_BINDINGS_GPIO_GPIO_H
#define GPIO_ACTIVE_HIGH 0
#define GPIO_ACTIVE_LOW 1
#endif
在文件 imx6ull.dtsi 中,关于 gpio1 节点的定义如下:
cpp
gpio1: gpio@0209c000 {
compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";
reg = <0x0209c000 0x4000>;
interrupts = <GIC_SPI 66 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 67 IRQ_TYPE_LEVEL_HIGH>;
gpio-controller;
#gpio-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
};
设置 gpio1 节点的 compatible 属性有两个,分别为"fsl,imx6ul-gpio"和"fsl,im x35-gpio",在 Linux 内核中搜索这两个字符串就可以找到 I.MX6UL 的 GPIO 驱动程序。
1、reg 属性设置了 GPIO1 控制器的寄存器基地址为 0X0209C000
2、**"gpio-controller"**表示 gpio1 节点是个 GPIO 控制器
3、"#gpio-cells"属性和"#address-cells"类似,#gpio-cells 应该为 2,表示一共有两个 cell,
第一个 cell 为 GPIO 编号 ,比如 "&gpio1 3" 就表示 GPIO1_IO03。
第二个 cell 表示 GPIO 极性,如果为 0(GPIO_ACTIVE_HIGH)的话表示高电平有效,如
果为 1(GPIO_ACTIVE_L OW)的话表示低电平有效
3.3 GPIO 子系统 API 函数
对于驱动开发,设置好设备树以后就可以使用 gpio 子系统提供的 API 函数来操作指定的 GPIO,gpio 子系统向驱动开发人员屏蔽了具体的读写寄存器过程。这就是驱动分层与分离的好处。gpio 子系统提供的常用的 API 函数有下面几个:

3.3.1 of_get_named_gpio

作用: 此函数获取 GPIO 编号,将设备树中类似**<&gpio1 3 GPIO_ACTIVE_LOW>** 的属性信息转换为对应的 GPIO 编号
函数参数和返回值含义如下:
**np:**设备节点。
**propname:**要获取 GPIO 信息的属性名
**index:**GPIO 索引,因为一个属性里面可能包含多个 GPIO,此参数指定要获取哪个 GPIO 的编号,如果只有一个 GPIO 信息的话此参数为 0。
**返回值:**正值,获取到的 GPIO 编号;负值,失败
使用方法:
cpp
puteled {
#address-cells = <1>;
#size-cells = <1>;
compatible = "pute,pute_led";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_led>;
gpio-led=<&gpio1 3 GPIO_ACTIVE_LOW>;/* 引用gpio1 pin3, 逻辑有效
电平,使用gpio子系统,输入输出,在驱动中使用代码指定*/
status = "okay";
};
cpp
//硬件编号
static int led_gpio;
//在设备树中取出GPIO的硬件编号
led_gpio = of_get_named_gpio(pnode, "gpio-led", 0);
if(led_gpio < 0)
{
ret = led_gpio;
pr_err("of_get_named_gpio error,errno: %d\n", ret);
goto del_misc;
}
3.3.2 gpio_request

**作用:**gpio_request 函数用于申请一个 GPIO 管脚,在使用一个 GPIO 之前一定要使用 gpio_reque st 进行申请
函数参数和返回值含义如下:
**gpio:**要申请的 gpio 标号,使用 of_get_named_gpio 函数从设备树获取指定 GPIO 属性信 息,此函数会返回这个 GPIO 的标号。
**label:**给 gpio 设置个名字,随便写
**返回值:**0,申请成功;其他值,申请失败
使用示例:
cpp
//有可能gpio1_io3已经在设备树中复用为其他的引脚,就会报错
ret = gpio_request(led_gpio, "led_gpio");
if(ret)
{
pr_err("gpio_request error,errno: %d\n", ret);
goto del_misc;
}
注意:
gpio1_io3已经在设备树中复用为其他的引脚,就会报错

3.3.3 gpio_free

**作用:**如果不使用某个 GPIO 了,那么就可以调用 gpio_free 函数进行释放
函数参数和返回值含义如下:
**gpio:**要释放的 gpio 标号。
**返回值:**无
使用示例:
cpp
gpio_free(led_gpio);
3.3.4 gpio_get_value

**作用:**此函数用于获取某个 GPIO 的值(0 或 1),此函数是个宏
函数参数和返回值含义如下:
**gpio:**要获取的 GPIO 标号。
**返回值:**非负值,得到的 GPIO 值;负值,获取失败
3.3.5 gpio_set_value

**作用:**此函数用于设置某个 GPIO 的值,此函数是个宏
函数参数和返回值含义如下:
**gpio:**要设置的 GPIO 标号。
**value:**要设置的值。
**返回值:**无
使用示例:
cpp
static ssize_t mystore(struct device *dev, struct device_attribute *attr,const char *buf, size_t count)
{
if(0 == strcmp(buf, "1\n")) //1:led on
{
gpio_set_value(led_gpio, 0);
led_status = LED_ON;
}
else if(0 == strcmp(buf, "0\n")) //0:led off
{
gpio_set_value(led_gpio, 1);
led_status = LED_OFF;
}
return count;
}
3.4 添加 gpio 配置示例
cpp
puteled {
#address-cells = <1>;
#size-cells = <1>;
compatible = "pute,pute_led";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_led>;
gpio-led=<&gpio1 3 GPIO_ACTIVE_LOW>;/* 引用gpio1 pin3, 逻辑有效
电平,使用gpio子系统,输入输出,在驱动中使用代码指定*/
status = "okay";
};
四. 使用 PinCtrl 与 GPIO 子系统驱动LED
4.1 设备树增添
cpp
puteled {
#address-cells = <1>;
#size-cells = <1>;
compatible = "pute,pute_led";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_led>;
gpio-led=<&gpio1 3 GPIO_ACTIVE_LOW>;/* 引用gpio1 pin3, 逻辑有效
电平,使用gpio子系统,输入输出,在驱动中使用代码指定*/
status = "okay";
};
pinctrl_ptled: ptledgrp {
fsl,pins = <
MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x10B0
>;
};
4.2 驱动部分
cpp
#define DEV_NAME "myled0"
//设备树节点
struct device_node *pnode = NULL;
//LED 状态枚举
typedef enum {
LED_OFF,
LED_ON
}LED_STATE;
//LED状态变量
static LED_STATE led_status;
static ssize_t myread(struct file *f, char __user *userdata, size_t size, loff_t *off);
static ssize_t mywrite(struct file *f, const char __user *userdata, size_t size,loff_t *off);
static int myopen(struct inode *inode, struct file *f);
static int myrelease(struct inode *inode, struct file *f);
static ssize_t myshow(struct device *dev, struct device_attribute *attr,char *buf);
static ssize_t mystore(struct device *dev, struct device_attribute *attr,const char *buf, size_t count);
//设备属性结构体
static struct device_attribute attr = __ATTR(led_status, 0664, myshow, mystore);
//硬件编号
static int led_gpio;
//文件操作结构体
struct file_operations fops = {
.owner = THIS_MODULE,
.open = myopen,
.release = myrelease,
.read = myread,
.write = mywrite,
};
//杂项设备结构体
static struct miscdevice misc_dev = {
.minor = MISC_DYNAMIC_MINOR, //这个宏为255,但是次设备号不一定是255;自动获取次设备号
.name = DEV_NAME, //设备节点名 和 /sys/class/misc/xxx 两者名称相同
.fops = &fops //杂项设备文件操作函数集合
};
/**
* @brief 在命令行对设备属性进行读取时候调用此函数-读属性
*
* @param dev 本设备
* @param attr 本设备的属性结构体,暂时不使用
* @param buf 返回给用户层命令行的字符串信息
* @return ssize_t buf的字符串长度
*/
static ssize_t myshow(struct device *dev, struct device_attribute *attr,char *buf)
{
ssize_t len = 0;
if(led_status == LED_ON)
{
strcpy(buf, "LED_ON\n");
}
else if(led_status == LED_OFF)
{
strcpy(buf, "LED_OFF\n");
}
len = strlen(buf);
return len;
}
/**
* @brief 当命令行向该属性写入指令时,会调用此函数-写属性
*
* @param dev
* @param attr
* @param buf 命令行写入本属性的指令字符串
* @param count 命令行的长度
* @return ssize_t 返回count
*/
static ssize_t mystore(struct device *dev, struct device_attribute *attr,const char *buf, size_t count)
{
if(0 == strcmp(buf, "1\n")) //1:led on
{
gpio_set_value(led_gpio, 0);
led_status = LED_ON;
}
else if(0 == strcmp(buf, "0\n")) //0:led off
{
gpio_set_value(led_gpio, 1);
led_status = LED_OFF;
}
return count;
}
ssize_t myread(struct file *f, char __user *userdata, size_t size, loff_t *off)
{
unsigned long ret = 0;
ret = copy_to_user(userdata, &led_status, sizeof(led_status));
if(ret)
{
pr_err("copy_to_user error\n");
return ret;
}
//pr_info("read caller\n");
return sizeof(led_status);
}
ssize_t mywrite(struct file *f, const char __user *userdata, size_t size, loff_t *off)
{
int sw = 0;
int ret = 0;
ret = copy_from_user(&sw, userdata, size);
if(ret)
{
pr_err("copy_from_user error\n");
return ret;
}
if(sw == 1) //led on
{
gpio_set_value(led_gpio, 0);
led_status = LED_ON;
}
else if(sw == 0) //led off
{
gpio_set_value(led_gpio, 1);
led_status = LED_OFF;
}
pr_info("write caller\n");
return size;
}
int myopen(struct inode *inode, struct file *f)
{
pr_info("open caller\n");
return 0;
}
int myrelease(struct inode *inode, struct file *f)
{
pr_info("release caller\n");
return 0;
}
static int __init mydrv_init(void)
{
int ret = 0;
const char *compa = NULL;
const char *led_status = NULL;
const char *pinctrl_names = NULL;
//注册杂项设备
ret = misc_register(&misc_dev);
if(ret)
{
pr_err("misc_register error, errno:%d\n", ret);
return ret;
}
pnode = of_find_node_by_path("/puteled");
if(!pnode)
{
ret = -ENODEV;
pr_err("of_find_node_by_path error,errno:%d\n", ret);
goto del_misc;
}
// compatible status
ret = of_property_read_string(pnode, "compatible", &compa);
if(ret)
{
pr_err("of_property_read_string error, errno:%d\n", ret);
return ret;
}
ret = of_property_read_string(pnode, "status", &led_status);
if(ret)
{
pr_err("of_property_read_string error, errno:%d\n", ret);
return ret;
}
ret = of_property_read_string(pnode, "pinctrl-names", &pinctrl_names);
if(ret)
{
pr_err("of_property_read_string error, errno:%d\n", ret);
return ret;
}
pr_info("compatible:%s, status:%s, pinctrl-names:%s\n", compa, led_status, pinctrl_names);
//在设备树中取出GPIO的硬件编号
led_gpio = of_get_named_gpio(pnode, "gpio-led", 0);
if(led_gpio < 0)
{
ret = led_gpio;
pr_err("of_get_named_gpio error,errno: %d\n", ret);
goto del_misc;
}
//有可能gpio1_io3已经在设备树中复用为其他的引脚,就会报错
ret = gpio_request(led_gpio, "led_gpio");
if(ret)
{
pr_err("gpio_request error,errno: %d\n", ret);
goto del_misc;
}
ret = gpio_direction_output(led_gpio, 1);
if(ret)
{
pr_err("gpio_direction_output error,errno: %d\n", ret);
goto del_gpio;
}
pr_info("major: %d, minor: %d\n", 10, misc_dev.minor);
ret = device_create_file(misc_dev.this_device, &attr);
if(ret)
{
pr_err("device_create_file error, errno : %d\n", ret);
goto del_gpio;
}
pr_info("hello, aaa\n");
return 0;
device_remove_file(misc_dev.this_device, &attr);
del_gpio:
gpio_free(led_gpio);
del_misc:
misc_deregister(&misc_dev);
return ret;
}
static void __exit mydrv_exit(void)
{
device_remove_file(misc_dev.this_device, &attr);
gpio_free(led_gpio);
misc_deregister(&misc_dev);
pr_info("hello, bbb\n");
return;
}
module_init(mydrv_init);
module_exit(mydrv_exit);
MODULE_LICENSE("GPL");
4.3 用户部分
cpp
int main(int argc, char **argv)
{
int fd = 0;
fd = open("/dev/myled0", O_RDWR);
if (fd < 0) {
perror("open");
return -1;
}
char buffer[10] = {0};
int sw = 0;
int led_status = 0;
while (1)
{
sw = 1; // led on
write(fd, &sw, sizeof(sw));
read(fd, &led_status, sizeof(led_status));
printf("cmd led on, led_status is %d\n", led_status);
sleep(1);
sw = 0; // led off
write(fd, &sw, sizeof(sw));
read(fd, &led_status, sizeof(led_status));
printf("cmd led off, led_status is %d\n", led_status);
sleep(1);
}
close(fd);
return 0;
}