Linux驱动之 pinctrl和GPIO子系统

15、pinctrl 和 GPIO子系统

在没有使用这两个子系统之前,我们控制GPIO是直接操作寄存器来完成的,例如 LED灯,就是直接对寄存器进行操作

1、什么是pinctrl 和 gpio子系统

​ pinctrl 这个是linux用来控制引脚相关的

​ GPIO 通用输入输出

在引入设备树之后,GPIO子系统是通过pinctrl子系统来实现的,这一点要牢记。

pinctrl 主要是用来进行pin脚的初始化

2、Linux pinctrl 子系统提供的功能是什么

​ (1) 管理系统中所有可以控制的pin, 在系统初始化的时候,枚举所有可以控制的pin,并标识这些pin

枚举所有可用的pin 脚 ,于是每个引脚就有的唯一的 ID (num) ,这个ID 很关键,对于以后的操作

​ (2) 管理这些pin的复用(Multiplexing)。 对于SOC来说,其引脚除了配置成普通的GPIO之外,若干个引脚还可组成一个pin group,形成特定的功能。

​ (3) 配置这些pin的特性。例如使能或关闭引脚上的pull-up、pull-down电阻,配置引脚的driver strength

​ 主要的功能, 1、设置引脚功能的复用

2、 配置pin脚的状态。

也就是对引脚进行初始化

GPIO子系统结构

在这里插入图片描述

pinctrl 子系统结构

pinctrl 子系统结构 与 GPIO子系统的结构功能类似, 但是内部结构有所差异

在这里插入图片描述

pinctrl 子系统与GPIO的关系

在引入设备树之后,GPIO子系统是通过pinctrl子系统来实现的,这一点要牢记。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-G0JKAFYN-1619337100405)(linux驱动.assets/image-20210419094243088.png)]

pinctrl 内部结构图

pinctrl的内部主要分为两部分功能pin config管脚配置和pin mux管脚复用
驱动调用pin脚(不仅指GPIO,例如uart也需要用到pin脚),那么需要用到两部分硬件的功能:
A: 设置引脚功能的复用;
B:配置pin脚的状态。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wHVoItYO-1619337100409)(linux驱动.assets/image-20210419094412173.png)]

3、pinctl 中的几个概念

与pinctl相关的设备树文件为exynos4412-pinctrl.dtsi 以下的操作均在该文件中

3.1 pin bank

​ 以引脚名为依据,这些引脚分为若干个组,每组称为一个bank

​ 例如 GPA 是一个bank

​ 每个bank中有若干个引脚例如 GPA0、GPA1 等等例如下面就被分为了11个bank

Port A(GPA) : 25-output port
Port B(GPB) : 9-input/output port
Port C(GPC) : 16-input/output port
Port D(GPD) : 16-input/output port
Port E(GPE) : 16-input/output port
Port F(GPF) : 8-input/output port
Port G(GPG) : 8-input/output port
Port H(GPH) : 15-input/output port
Port K(GPK) : 16-input/output port
Port L(GPL) : 7-input/output port
Port M(GPM) : 2-input port

之所以分成bank,主要是把特性相同的GPIO进行分组,方便控制。例如:这些bank中,只有GPF和GPG这两个bank上的引脚有中断功能,其他的都没有。

BANK属性描述实例
gpio-controller说明该节点为 GPIO controller GPIO控制
interrupt-controller说明该节点为 interrupt controller 中断控制
#gpio-cells属性是一个GPIO controller的必须定义的属性,它描述了需要多少个cell来具体描述一个GPIO(这是和具体的GPIO controller相关的)。
#interrupt-cells和gpio-cells 的概念类似

cell表示一个无符号的32位整数,xxxx-cells指定用多少个cell描述xxxx属性。

gpio-controller;

#gpio-cells = <2>;

“gpio-controller”表示这个节点是一个GPIO Controller,它下面有很多引脚。

“#gpio-cells = <2>”表示这个控制器下每一个引脚要用2个32位的数(cell)来描述。

为什么要用2个数?其实使用多个cell来描述一个引脚,这是GPIO Controller自己决定的。比如可以用其中一个cell来表示那是哪一个引脚,用另一个cell来表示它是高电平有效还是低电平有效,甚至还可以用更多的cell来示其他特性。

普遍的用法是,用第1个cell来表示哪一个引脚,用第2个cell来表示有效电平:

GPIO_ACTIVE_HIGH :高电平有效

GPIO_ACTIVE_LOW : 低电平有效

phandle(linux,phandle这个属性和phandle是一样的,只不过linux,phandle是old-style,多定义一个属性是为了兼容)定义了一个句柄,当其他的device node想要引用这个node的时候就可以使用该句柄。具体的例子参考下节client device的DTS的描述。

pin bank 的例子

		gpa0: gpa0 {
			gpio-controller;		// 表明该节点为GPIO控制
			#gpio-cells = <2>;		// 槽为2  这里是根据文档获得的
//#interrupt-cells – 与#address-cells 和 #size-cells相似,它表明连接此中断控制器的设备的interrupts属性的cell大小。	#address-cells 表示子节点的reg address属性的大小 如果为1 那么代表只有一个address
			interrupt-controller;	// 中断控制
			#interrupt-cells = <2>;	
		};
	gpj0: gpj0 { gpio-controller;
		#gpio-cells = <2>;
		interrupt-controller;
		#interrupt-cells = <2>; };
3.2 pin group

​ 以功能为依据,具有相同功能的引脚称为一个Group

比如4412的串口 TxD、RxD 引脚使用 GPA0_0, GPA0_1 那么这2个引脚可以列一组

属性名称功能实例
samsung,pins用来引用管脚[pin bank name]-[pin number within the bank]
samsung,pin-function功能复用 设置功能samsung,pin-function = <EXYNOS_PIN_FUNC_2>;
samsung,pin-val设置输出缓冲区的初始值samsung,pin-val = <1> 例如使用LED时,输出1 高电平
samsung,pin-pud上拉/下拉配置samsung,pin-pud = <EXYNOS_PIN_PULL_NONE>;
samsung,pin-drv驱动器强度配置samsung,pin-drv = <EXYNOS4_PIN_DRV_LV1>;
samsung,pin-pud-pdn掉电模式下的上拉/下拉配置
samsung,pin-drv-pdn电源关闭模式下的驱动器强度配置
		/*pin group*/
		uart0_data: uart0-data {
			samsung,pins = "gpa0-0", "gpa0-1";			  //引用管脚	[pin bank name]-[pin number within the bank]	
			samsung,pin-function = <EXYNOS_PIN_FUNC_2>;   // 设置功能复用模式
			samsung,pin-pud = <EXYNOS_PIN_PULL_NONE>;	  // pud 上拉下拉配置
			samsung,pin-drv = <EXYNOS4_PIN_DRV_LV1>;	  // 驱动强度为LV1
		};

这里的宏定义EXYNOS_PIN_FUNC_2 定义在了 include/dt-bindings/pinctrl/samsung.h中

/*
 * Samsung's Exynos pinctrl bindings
 *
 * Copyright (c) 2016 Samsung Electronics Co., Ltd.
 *		http://www.samsung.com
 * Author: Krzysztof Kozlowski <krzk@kernel.org>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
*/
#ifndef __DT_BINDINGS_PINCTRL_SAMSUNG_H__
#define __DT_BINDINGS_PINCTRL_SAMSUNG_H__

#define EXYNOS_PIN_PULL_NONE		0
#define EXYNOS_PIN_PULL_DOWN		1
#define EXYNOS_PIN_PULL_UP		3

#define S3C64XX_PIN_PULL_NONE		0
#define S3C64XX_PIN_PULL_DOWN		1
#define S3C64XX_PIN_PULL_UP		2

/* Pin function in power down mode */
#define EXYNOS_PIN_PDN_OUT0		0
#define EXYNOS_PIN_PDN_OUT1		1
#define EXYNOS_PIN_PDN_INPUT		2
#define EXYNOS_PIN_PDN_PREV		3

#define EXYNOS_PIN_FUNC_INPUT		0		//输入
#define EXYNOS_PIN_FUNC_OUTPUT		1		//输出
#define EXYNOS_PIN_FUNC_2		2
#define EXYNOS_PIN_FUNC_3		3
#define EXYNOS_PIN_FUNC_4		4
#define EXYNOS_PIN_FUNC_5		5
#define EXYNOS_PIN_FUNC_6		6
#define EXYNOS_PIN_FUNC_EINT		0xf
#define EXYNOS_PIN_FUNC_F		EXYNOS_PIN_FUNC_EINT


#endif /* __DT_BINDINGS_PINCTRL_SAMSUNG_H__ */

上面的FUNC 就是用来设置GPx_CON寄存器的

例如上面的pin group 使用的GPA0_0 、 GPA0_1 这个是串口的引脚,所以使用 FUNC_2 0x2 代表串口的RxD、TxD

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8CGjvQdy-1619337100411)(linux驱动.assets/image-20210419123836690.png)]

3.3 State:

设备的某种状态, 比如内核自己定义的"default",“init”,“idel”,"sleep"状态;
也可以是其他自己定义的状态, 比如串口的"flow_ctrl"状态(使用流量控制);
设备处于某种状态时, 它可以使用若干个Group引脚。

这个是在设备树节点下使用的 , 上面的两个都是定义在了pinctrl节点中

serial@50000000 {

pinctrl-names = “default”, “sleep”; /* 既是名字, 也称为state(状态) */
pinctrl-0 = <&uart0_data>;
pinctrl-1 = <&uart0_sleep>;
};

pinctrl-names中定义了2种state: default 和 sleep,
default 对应的引脚是: pinctrl-0, 它指定了使用哪些pin group: uart0_data
sleep 对应的引脚是: pinctrl-1, 它指定了使用哪些pin group: uart0_sleep

实例 添加LED pinctrl节点

因为使用LED需要用到GPL2 寄存器,而GPL2 BANK是定义已经定义好的,所以我们直接在pin group中使用即可

exynos4412-pinctrl-dtsi 文件

/*在pinctrl_1节点中*/
/* 因为gpl2定义在了pinctrl_1 节点里面*/
	/*添加自定义的LED pinctrl节点*/
	/*pin bank*/
pinctrl_1: pinctrl@11000000 {
			gpl1: gpl1 {
			gpio-controller;
			#gpio-cells = <2>;

			interrupt-controller;
			#interrupt-cells = <2>;
		};
		/*pin group*/
		my_led: my_led {
			samsung,pins = "gpl2-0"; //引用GPL2_0
			samsung,pin-function = <EXYNOS_PIN_FUNC_OUTPUT>; // 设置为输出模式
			samsung,pin-val = <0x1>; 		//初始值输出高电平			
		};
};

在设备树节点中,使用pin group 节点

	/* 添加自定义节点*/
	
	myled:myled {
		#address-cells = <1>;		
		#size-cells = <1>;
		compatible = "myledxxx";  //描述
//		reg = <0x11000100 0x4 0x11000104 0x1>; //GPL2CON GPL2DAT
		status = "okay";
		pinctrl-names = "default";	/* 即是名字也是 状态status*/
		pinctrl-0 = <&my_led>;		/* 当使用default状态时,就会使用 所引用节点的 配置*/
        /*这里的配置主要是将gpl2_0 引脚设置为GPIO*/
        
        /*管脚的描述信息  主要是*/
		gpios = <&gpl2 0 GPIO_ACTIVE_HIGH>;	//GPL2_0   GPIO_ACTIVE_HIGH 表示高电平有效
	};
参数功能
pinctrl-names 是names配置的名字,也是状态, 在driver中使用该参数中的 名字 就可以引用相应的配置对管脚进行初始化
pinctrl-0当pinctrl-names的参数只有一个的时候,例如pinctrl-names = “default”; 在driver中 使用"default就会使用这个配置",
pinctrl-1当pinctrl-names 的参数有两个的时候,pinctrl-names = “default”, “led_on”; 在driver中 使用"led_on"就会使用这个配置

4、pinctrl 的API

struct pinctrl 结构体 每个设备引脚控制状态
#include <linux/pinctrl/consumer.h> 
struct pinctrl {
	struct list_head node;	//全局列表节点
	struct device *dev;		//使用这个pin控制的设备
	struct list_head states;//这个设备的状态列表
	struct pinctrl_state *state;//当前状态
	struct list_head dt_maps;	//:从设备树动态解析的映射表块
	struct kref users;			//引用计数
};
struct pinctrl_state 设备的pinctrl状态
struct pinctrl_state {
	struct list_head node;	//states字段的列表节点
	const char *name;		//该状态的名称
	struct list_head settings;	//这个状态的设置列表
};
1、 获取pinctrl 句柄
#include <linux/pinctrl/consumer.h> 
struct pinctrl *devm_pinctrl_get(struct device *dev)  ;
//返回值 成功返回pinctrl句柄  失败返回NULL

参数是dev是包含这个pin的device结构体即xxx这个设备的device

devm机制

​ 内核自动分配内存,回收内存

2、获取引脚状态
#include <linux/pinctrl/consumer.h> 
struct pinctrl_state *pinctrl_lookup_state(struct pinctrl *p, const char *name) ;
// 返回值  成功返回pinctrl 状态  失败返回NULL
   
参数功能
ppinctrl句柄
name配置的名字,用于pinctrl检索
3、设置引脚状态
#include <linux/pinctrl/consumer.h> 
int pinctrl_select_state(struct pinctrl *p, struct pinctrl_state *state)// 成功返回 0  失败返回错误码
参数功能
ppinctrl句柄
state要设置的引脚状态
4、回收pinctrl句柄资源
void devm_pinctrl_put(struct pinctrl *p);
5、实例

这里以LED为例

1、在pinctrl.dtsi 中添加pinctrl group节点

		/*添加自定义的LED pinctrl节点*/
	/* 在pinctrl_1 中 ,因为 gpl2 在pinctrl_1中*/	
		my_led_on: my_led-on {
			samsung,pins = "gpl2-0"; //引用GPL2_0
			samsung,pin-function = <EXYNOS_PIN_FUNC_OUTPUT>; // 设置为输出模式
			samsung,pin-val = <0x1>; 		//初始值输出高电平			
			samsung,pin-pud = <EXYNOS_PIN_PULL_UP>;
		};
		my_led_off: my_led-off {
			samsung,pins = "gpl2-0"; //引用GPL2_0
			samsung,pin-function = <EXYNOS_PIN_FUNC_OUTPUT>; // 设置为输出模式
			samsung,pin-val = <0x0>; 		//初始值输出低电平			
			samsung,pin-pud = <EXYNOS_PIN_PULL_UP>;
		};

2、在 dts中添加节点

	myled:myled {
		compatible = "myledxxx";  //描述
		status = "okay";
		pinctrl-names = "default", "my_led_off";	/* 即是名字也是 状态status*/
		pinctrl-0 = <&my_led_on>;		/* 当使用default状态时,就会使用所引用节点的配置*/
		pinctrl-1 = <&my_led_off>;
	};

3、driver的probe函数中使用pinctrl API 进行pin的初始化

int ledprobe(struct platform_device *pdev)
{
    struct pinctrl * led_pinctrl;
    struct pinctrl_state * led_pinctrl_state;
    int ret;
    /*1、获取pinctrl句柄*/
    led_pinctrl = devm_pinctrl_get(&pdev->dev);
    if (NULL == led_pinctrl)
    {
        printk("devm pinctrl get is error!\n");
        return -1;
    }
    /*2、获取 指定的name的 state*/
    /*	pinctrl-name = "default, led_off";	 即是名字也是 状态status*/
    led_pinctrl_state = pinctrl_lookup_state(led_pinctrl, "default"); //获取pinctrl-0的配置状态
    if (NULL == led_pinctrl_state)
    {
        printk("pinctrl lookup state is error!\n");
        return -1;
    }
    /*3、设置引脚状态*/
    ret = pinctrl_select_state(led_pinctrl, led_pinctrl_state);
    if (0 != ret)
    {
        printk("pinctrl select state is error!\n");
        return -1;
    }
    /*4、 句柄销毁*/
   // devm_pinctrl_put(led_pinctrl);  //     
    return 0;    
}
划重点:

对于probe函数中设置pinctrl,如果说在设备树中 pinctrl-name = “default”,“init”,“idel”,“sleep” 那么我们在设备树中就不需要调用pinctrl的相关API,内核会自动帮我们设置

pinctrl子系统总结

pin bank 用来设置引脚的功能 例如 GPIO功能

pin group 主要是用来设置pin的复用

pinctrl 子系统主要是用来对管脚进行配置和复用初始化 设置引脚的复用关系和电气属性

真正操作GPIO 还需要使用GPIO子系统来完成,在引入了设备树的内核中,GPIO子系统是由pinctrl子系统来实现的

在这里插入图片描述

16、GPIO子系统

​ 上一节我们学习了pinctrl子系统(主要是用来对管脚进行配置和复用初始化 复用关系和电气属性的初始化),linux内核提供了pinctrl子系统和gpio子系统用于GPIO驱动,

当我们在pin bank中使用了gpio-controller的时候,我们就可以使用GPIO子系统来操作管脚了,也就是说pinctrl子系统初始化的时候把引脚设置为gpio的时候,那么就可以使用GPIO子系统来操作管脚

​ 当然,pinctrl子系统负责的不仅仅是GPIO子系统负责的就不仅仅是GPIO的驱动,而是所有pin脚的配置

pinctrl子系统是随着设备树的加入而加入的,依赖于设备树。GPIO子系统在之前的内核中也是存在的,但是pinctrl子系统的加入GPIO子系统也有很大的改变。

​ 在以前的内核版本中,如果要配置GPIO的话,一般要使用SOC厂家实现的配置函数,例如三星的配置函数s3c_gpio_cfgpin等,这样带来的问题就是各家有各家的接口函数,与实现方法,但是内核的代码复用率低而且开发者很难记住这么多函数,如果使用多种平台的话背函数是很麻烦的,所以在引入了设备树后对GPIO子系统进行了很大的改造,使用设备树来实现,并提供统一的接口通过GPIO子系统功能主要实现引脚功能的配置如设置为GPIO特殊功能,GPIO的方向,设置为中断等等。

probe代码中如何引用pinctrl

这是透明的,我们的驱动基本不用管。当设备切换状态时,对应的pinctrl就会被调用。

比如在platform_device和platform_driver的枚举过程中,流程如下:

这个经过实验,发现即使init 和 default同时存在,还是会优先使用default状态的引脚

1、设备树使用pinctrl和GPIO子系统描述一个GPIO

pinctrl.dtsi

pinctrl_1: pinctrl@11000000 {
		gpl2: gpl2 {
			gpio-controller;
			#gpio-cells = <2>;

			interrupt-controller;
			#interrupt-cells = <2>;
		};
		
		my_led_on: my_led-on {
			samsung,pins = "gpl2-0"; //引用GPL2_0
			samsung,pin-function = <EXYNOS_PIN_FUNC_OUTPUT>; // 设置为输出模式
			samsung,pin-val = <0x1>; 		//初始值输出高电平			
			samsung,pin-pud = <EXYNOS_PIN_PULL_UP>;		//电气属性, 上拉/下拉
		};
		my_led_off: my_led-off {
			samsung,pins = "gpl2-0"; //引用GPL2_0
			samsung,pin-function = <EXYNOS_PIN_FUNC_OUTPUT>; // 设置为输出模式
			samsung,pin-val = <0x0>; 		//初始值输出低电平			
			samsung,pin-pud = <EXYNOS_PIN_PULL_UP>;
		};
};

pin Bank中我们只关心两个属性

gpio-controller;

#gpio-cells = <2>;

“gpio-controller”表示这个节点是一个GPIO Controller,它下面有很多引脚。

“#gpio-cells = <2>”表示这个控制器下每一个引脚要用2个32位的数(cell)来描述。

为什么要用2个数?其实使用多个cell来描述一个引脚,这是GPIO Controller自己决定的。比如可以用其中一个cell来表示那是哪一个引脚,用另一个cell来表示它是高电平有效还是低电平有效,甚至还可以用更多的cell来示其他特性。

普遍的用法是,用第1个cell来表示哪一个引脚,用第2个cell来表示有效电平:

GPIO_ACTIVE_HIGH : 高电平有效

GPIO_ACTIVE_LOW : 低电平有效

pin group 中 设置复用关系和电气属性

	my_led_on: my_led-on {
		samsung,pins = "gpl2-0"; //引用GPL2_0
		samsung,pin-function = <EXYNOS_PIN_FUNC_OUTPUT>; // 设置为输出模式
		samsung,pin-val = <0x1>; 		//初始值输出高电平			
		samsung,pin-pud = <EXYNOS_PIN_PULL_UP>;		//电气属性, 上拉/下拉
	};

定义GPIO Controller是芯片厂家的事,我们怎么引用某个引脚呢?在自己的设备节点中使用属性" [ name- ]gpios",示例如下:

exynos4412-itop-elit.dts

	myled:myled {
		compatible = "myledxxx";  //描述
		reg = <0x11000100 0x4 0x11000104 0x1>; //GPL2CON GPL2DAT
		status = "okay";
		/*pinctrl*/
		pinctrl-names = "default", "my_led_off";	/* 即是名字也是 状态status*/
		pinctrl-0 = <&my_led_on>;		/* 当使用default状态时,就会使用所引用节点的配置*/
		pinctrl-1 = <&my_led_off>;
		/*GPIO*/	
		led-gpios = <&gpl2 0 GPIO_ACTIVE_HIGH>;	//GPL2_0   GPIO_ACTIVE_HIGH 表示高电平有效
		//gpios  = <&gpl2 0 GPIO_ACTIVE_HIGH>;
	};

上图中,可以使用gpios属性,也可以使用name-gpios属性。

2、GPIO子系统提供的API函数

1、of_get_named_gpio 函数 获取GPIO标号

作用:此函数获取 GPIO 编号,因为Linux内核中关于GPIO的API函数都要使用GPIO编号,此函数会将设备数中类似 <&gpl2 0 GPIO_ACTIVE_HIGH> 的属性信息转换为对应的GPIO编号

#include<linux/of_gpio.h>

int of_get_named_gpio(struct device_node *np,const char *propname, int index)// 返回值  成功返回 GPIO编号,  失败返回 负数
参数描述
np设备节点
propname包含要获取GPIO信息的属性名
index因为一个属性里面可能包含多个GPIO,此参数指定要获取哪个GPIO的编号,如果只有一个GPIO信息的话此参数为0
2、gpio_request 函数

作用:用于申请一个GPIO管脚

#include<linux/gpio.h>
int gpio_request(unsigned gpio, const char *label);
// 返回值  成功 0,  失败返回 非0
参数描述
gpio要申请的gpio标号,使用of_get_named_gpio函数从设备树中获取指定GPIO属性信息,此函数会返回这个GPIO的标号
label给这个gpio标号 起个名字 最好起与硬件相关的名字
3、gpio_free 函数

作用: 如果不使用某个GPIO了,那么就可以调用gpio_free函数进行释放

#include<linux/gpio.h>
void gpio_free(unsigned gpio);
参数描述
gpio要释放的gpio号
4、gpio_direction_input 函数

作用: 此函数用于设置某个GPIO为输入

#include <linux/gpio.h>
int gpio_direction_input(unsigned gpio);
//返回值  成功返回0 , 失败返回负数	
参数描述
gpio要设置为输入的gpio的标号
5、gpio_direction_output 函数

作用:此函数用于设置某个GPIO为输出, 并且设置默认输出

#include <linux/gpio.h>
int gpio_direction_output(unsigned gpio, int value);
//返回值 成功返回0 ,失败返回 负数
参数描述
gpio要设置为输出的GPIO标号
valueGPIO 默认输出值。 例如 1 默认输出高电平, 0 输出低电平
6、gpio_get_value 函数

作用: 此函数用于获取某个GPIO的值(0 或 1)

#include <linux/gpio.h>
int gpio_get_value(unsigned gpio);
//返回值, 成功返回GPIO的值, 失败返回 负数
参数描述
gpio要获取的GPIO标号
7、gpio_set_value 函数
#include<linux/gpio.h>
void gpio_set_value(unsigned gpio, int value);
参数描述
gpio要设置的GPIO标号
value要设置的值

3、实例

#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/pinctrl/consumer.h> 
#include <dt-bindings/pinctrl/samsung.h>	
#include <linux/of_gpio.h>		

/*  字符设备框架
    1、注册 platform driver
    2、构建file_operations
    3、获取硬件资源
    4、注册字符设备驱动
    4、生成字符设备节点
*/  

struct device *device_cdev; 
struct class *class_cdev;

int GPIO_ID;

ssize_t led_read (struct file *file, char __user * user, size_t size, loff_t * loff )
{
    printk("read is success!\n");
    return 0;
}
ssize_t led_write (struct file *file, const char __user *user, size_t size, loff_t * loff)
{   char buf[128] = {0};
    int ret ;
    printk("write is success!\n");
    ret  = copy_from_user(buf, user, size);
    if (0 != ret)
    {
        printk("copy form user is error!\n");
        return ret;
    }
    if (buf[0] == 1 || buf[0] == 0)
        gpio_set_value(GPIO_ID, buf[0]);
    return 0;
}
int led_open(struct inode *inode, struct file *file)
{
    printk("open is success!\n");
    return 0;
}
int led_release (struct inode *inode, struct file *file)
{
    printk("release is success!\n");
    return 0;
}

/*2、获取硬件资源 */
int ledprobe(struct platform_device *pdev)
{
    int ret;
    /*1、获取GPIO号*/
    GPIO_ID = of_get_named_gpio(pdev->dev.of_node, "led-gpios", 0);//index=0 ,因为在设备树中只引用了一个
    if (GPIO_ID < 0)
    {
        printk("of get named gpio is error!\n");
        return -1;
    }
    /*2、申请一个GPIO管脚*/
    ret = gpio_request(GPIO_ID, "led_gpio");
    if (ret != 0)
    {
        printk("gpio request is error!\n");
        return -1;
    }
    /*3、 将管脚设置为输出*/
    /* 这里先不设置,因为在pinctrl复用中已经将管脚设置为了OUTPUT*/
    return 0;    
}
int ledremove(struct platform_device *pdev)
{
    return 0;
}
struct of_device_id of_match_table = {      // 与设备树节点进行匹配
    .compatible = "myledxxx"
};
/*1、初始化platform driver*/
struct platform_driver pdev = {
    .probe = ledprobe,          // 与 of_match_table 匹配成功后进入probe函数获取硬件资源
    .remove = ledremove,
    .driver = {
        .name = "myledxxx",     //无设备树时 使用.name 与device进行匹配
        .owner = THIS_MODULE,
        .of_match_table = &of_match_table,
    }
};
//3、注册字符设备驱动
/*3.1 分配设备号*/
dev_t dev_number;
/*3.2 定义cdev*/
struct cdev cdev_;
/*3.3 构建file_operation结构体*/
struct file_operations fop = {
    .owner = THIS_MODULE,
    .open    = led_open,
    .release = led_release,
    .write   = led_write,
    .read    = led_read,
};
static int char_driver_init(void)
{
    /*1、注册platform driver*/
    int ret = platform_driver_register(&pdev);
    if (0 != ret)
    {   
        printk("platform driver register is error!\n");
        return -1;
    }
    /*3.1 分配设备号(动态分配设备号)*/
    ret = alloc_chrdev_region(&dev_number, 0, 1, "my_led");
    if (0 != ret)
    {
        printk("alloc chrdev region is error!\n");
        return ret;
    }
    /*3.4 初始化cdev*/
    cdev_.owner = THIS_MODULE;
    cdev_init(&cdev_, &fop);
    /*3.5 注册字符设备到内核*/
    ret = cdev_add(&cdev_, dev_number, 1);
    if (0 != ret)
    {
        printk("cdev add is error!\n");
        return -1;
    }
    /*4、生成设备节点*/
    /*4.1 创建字符设备类*/
   class_cdev =  class_create(THIS_MODULE, "my_led");
    if (NULL == class_cdev)
    {
        printk("class create is error!\n");
        return -1;
    }
    /*生成设备节点*/
    device_cdev = device_create (class_cdev, NULL, dev_number, NULL,  "my_led");
    if (NULL == device_cdev)
    {
        printk("device create is error!\n");
    }
    return 0;
};

static void char_driver_exit(void)
{
    gpio_free(GPIO_ID);                     //释放GPIO
    device_destroy(class_cdev, dev_number); // 卸载设备节点
    class_destroy(class_cdev);              //卸载设备类
    cdev_del(&cdev_);                       //卸载cdev
    unregister_chrdev_region(dev_number, 1);// 注销设备号 
    platform_driver_unregister(&pdev);       // 注销platform driver
   
}
module_init(char_driver_init);
module_exit(char_driver_exit);
MODULE_LICENSE("GPL");
  • 2
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值