来自朱有鹏老师的学习笔记,如有侵权,请告知删除。
gpiolib引入
(1)一个事实:很多硬件都要用到GPIO、GPIO会复用
1、缩在我们定时器内部的硬件不用到 GPIO
2、外面的硬件、按键、SD卡、LCD显示器等等都要用到 GPIO
(2)如果同一个GPIO被2个驱动同时控制了,就会出现bug
(3)内核提供gpiolib来统一管理系统中所有GPIO
(4)gpiolib本身属于驱动框架的一部分
gpiolib
一、linux内核的gpiolib学习
1、gpiolib学习重点
(1)gpiolib 的建立过程
(2)gpiolib的使用方法:申请、使用、释放
(3)gpiolib的架构:涉及哪些目录的哪些文件
2、gpiolib的学习方法
(1)以一条主线进去,坚持主线
(2)中途遇到杂碎知识,彻底搞定之,然后继续主线。
(3)随时做笔记以加深理解和记忆
(4)学习途中注意架构思想,提升自己大脑的空间复杂度
主线1 :gpiolib的建立
首先理解什么 gpiolib 的建立:
在内核开机启动的时候,一步一步的执行了那些函数?
第一阶段:先分析一些结构体
1、找到目标函数 :
arch/arm/mach-s5pv210/gpiolib.c ----> s5pv210_gpiolib_init
smdkc110_map_io()
s5pv210_gpiolib_init()
这个函数就是我们 gpiolib 初始化的函数
smdkc110_map_io
这个函数在静态映射中曾经经过。
__init int s5pv210_gpiolib_init(void)
{
struct s3c_gpio_chip *chip = s5pv210_gpio_4bit; //指向已经设置好的结构体数组
int nr_chips = ARRAY_SIZE(s5pv210_gpio_4bit); //算出有多少个GPIO端口,即数组元素个数
int i = 0;
for (i = 0; i < nr_chips; i++, chip++) { //查找每个端口
if (chip->config == NULL)//判断是否指定是否指定config
chip->config = &gpio_cfg;
if (chip->base == NULL)//判断是否指定是否指定基地址
chip->base = S5PV210_BANK_BASE(i);
}
samsung_gpiolib_add_4bit_chips(s5pv210_gpio_4bit, nr_chips);//把数组地址和个数做参数
return 0;
}
gpiolib 的初始化:就是建立一个 gpiolib (初始化整个gpio,也就是初始化所有端口)
1、指向已经设置好的结构体数组
2、算出有多少个GPIO端口,即数组元素个数,
3、然后填充一些结构体变量
4、samsung_gpiolib_add_4bit_chips
:最后是真正建立 gpiolib 的函数
2、struct s3c_gpio_chip
这里面包含了很多的结构体
struct s3c_gpio_chip {
struct gpio_chip chip; // gpio 的一些属性信息
struct s3c_gpio_cfg *config; // gpio 的配置:上拉,下拉,开漏
struct s3c_gpio_pm *pm; // power manage : gpio 的电源管理
void __iomem *base; // 操作gpio 1个端口的基地址(虚拟地址)
int eint_offset;
spinlock_t lock;
#ifdef CONFIG_PM
u32 pm_save[7];
#endif
};
s3c_gpio_chip
结构体
(1)这个结构体是一个GPIO端口的抽象,这个结构体的一个变量就可以完全的描述一个IO端口。
(2)端口和IO口 是两个概念。
譬如GPA0是一个端口,里面包含了8个IO口,我们一般记作:
GPA0_0
(或GPA0.0)、GPA0_1
、
struct gpio_chip
结构体:(被包含在s3c_gpio_chip
当中 )
struct gpio_chip {
const char *label; //记录gpio的名字
struct device *dev; //提供GPIOs的可选设备
struct module *owner; //帮助防止删除导出活动的GPIOs的模块
int (*request)(struct gpio_chip *chip, //申请gpio
unsigned offset);
void (*free)(struct gpio_chip *chip, //释放gpio
unsigned offset);
int (*direction_input)(struct gpio_chip *chip,
unsigned offset); //设置为输入模式
int (*get)(struct gpio_chip *chip, //获取IO值
unsigned offset);
int (*direction_output)(struct gpio_chip *chip,
unsigned offset, int value); //设置为输出模式
int (*set_debounce)(struct gpio_chip *chip,
unsigned offset, unsigned debounce);
void (*set)(struct gpio_chip *chip, //设置IO值
unsigned offset, int value);
int (*to_irq)(struct gpio_chip *chip,
unsigned offset);
//在debugfs中显示内容的可选例程;默认的代码当省略这个时将会使用,但是自定义代码可以显示额外的
//状态(如下拉/下拉配置)
void (*dbg_show)(struct seq_file *s,
struct gpio_chip *chip);
int base; //gpio的端口编号
u16 ngpio; //该端口有多少给IO端口;最后GPIO处理是
//(base+ngpio-1)
const char *const *names; //名字
unsigned can_sleep:1;
unsigned exported:1;
};
(1)内核中为每个GPIO分配了一个编号,编号是一个数字(譬如一共有160个IO时编号就可以从1到160连续分布),编号可以让程序很方便的去识别每一个GPIO。
3、s5pv210_gpio_4bit
(1)这个东西是一个结构体数组,数组中包含了很多个 struct s3c_gpio_chip
类型的变量。
struct s3c_gpio_chip
:表示一个端口
s5pv210_gpio_4bit
:表示所有端口,所有gpio
(2)填充了结构体中chip这个元素,这个元素是 struct gpio_chip 类型的,因此进一步细化为填充 struct gpio_chip
类型中的元素。其他元素好像没有太深入。
(3)个数组中其实就包含了当前系统中所有的IO端口的信息这些信息包含:
1、端口的名字、
2、端口中所有GPIO的编号、
3、端口操作寄存器组的虚拟地址基地址、
4、端口中IO口的数量、
5、端口上下拉等模式的配置函数、
6、端口中的IO口换算其对应的中断号的函数。
可以看出:我们主要操作 chip 当中的四个元素,个别会初始化一些其他元素
.chip = {
.base = S5PV210_GPA0(0), // 返回一个 GPIO 引脚的编号(基础编号)
.ngpio = S5PV210_GPIO_A0_NR, // 当前GPIO 端口的 GPIO 个数
.label = "GPA0", // 当前端口的名字
.to_irq = s5p_gpiolib_gpioint_to_irq, // 当前端口种 IO 口换成对应中断号的方法
},
{
.base = (S5P_VA_GPIO + 0xC20), //指定虚拟地址
.config = &gpio_cfg_noint, //
.eint_offset = IRQ_EINT(8), //得到中断号
.chip = {
.base = S5PV210_GPH1(0),
.ngpio = S5PV210_GPIO_H1_NR,
.label = "GPH1",
.to_irq = s5p_gpiolib_eint_to_irq,
}
S5PV210_GPA0
宏:
这个宏的返回值是GPA0端口的某一个IO口的基础编号值,传参是这个IO口在GPA0端口中的局部编号。
局部编号:当前GPIO端口的第几个 GPIO 。
基础编号值 : 从GPIO_A0
————GPIO_F0
,我们整体 GPIO 的编号。
比如:我们传入:
S5PV210_GPA0(0)
:返回 0
S5PV210_GPA0(1)
:返回 1
因为:GPA0
当中 一个有 8 个 GPIO,并且和GPA1
当中空了 1 个编号。
S5PV210_GPA1(0)
: 返回 9
2、第二阶段:注册这些结构体
s5pv210_gpiolib_init
samsung_gpiolib_add_4bit_chips
这个函数才是具体进行gpiolib的注册的。这个函数接收的参数是我们当前文件中定义好的结构体数组 s5pv210_gpio_4bit(其实2个参数分别是数组名 和 数组元素个数).
__init int s5pv210_gpiolib_init(void)
{
struct s3c_gpio_chip *chip = s5pv210_gpio_4bit;
// ARRAY_SIZE : 计算这个结构体到底有多大
int nr_chips = ARRAY_SIZE(s5pv210_gpio_4bit);
int i = 0;
// 循环去遍历这个数组
for (i = 0; i < nr_chips; i++, chip++) {
if (chip->config == NULL)
chip->config = &gpio_cfg;
if (chip->base == NULL)
chip->base = S5PV210_BANK_BASE(i);
}
// 进行注册
samsung_gpiolib_add_4bit_chips(s5pv210_gpio_4bit, nr_chips);
return 0;
}
》》》》samsung_gpiolib_add_4bit_chips
samsung_gpiolib_add_4bit_chips(s5pv210_gpio_4bit, nr_chips);
void __init samsung_gpiolib_add_4bit_chips(struct s3c_gpio_chip *chip,
int nr_chips)
{
for (; nr_chips > 0; nr_chips--, chip++) { //遍历每个数组
samsung_gpiolib_add_4bit(chip); //
s3c_gpiolib_add(chip);
}
}
1、分支 1
》》》》samsung_gpiolib_add_4bit
samsung_gpiolib_add_4bit(chip);
传入:我们整个端口结构体的基地址
void __init samsung_gpiolib_add_4bit(struct s3c_gpio_chip *chip)
{
chip->chip.direction_input = samsung_gpiolib_4bit_input; //添加输入函数
chip->chip.direction_output = samsung_gpiolib_4bit_output; //添加输出函数
chip->pm = __gpio_pm(&s3c_gpio_pm_4bit); //添加电源管理函数
}
struct gpio_chip {
int (*direction_input)(struct gpio_chip *chip,
unsigned offset);//设置为输入模式
int (*direction_output)(struct gpio_chip *chip,
unsigned offset, int value);//设置为输出模式
};
》》》》samsung_gpiolib_4bit_input
和 samsung_gpiolib_4bit_output
这两个函数,目的就是为了将我们的 gpio 设置为输入模式,或者输出模式
static int samsung_gpiolib_4bit_input(struct gpio_chip *chip,
unsigned int offset)
{
//to_s3c_gpio通过结构体变量中 某个成员 的首地址进而获得 整个 结构体变量的首地址
struct s3c_gpio_chip *ourchip = to_s3c_gpio(c hip);
void __iomem *base = ourchip->base; //指向端口基地址
unsigned long con;
con = __raw_readl(base + GPIOCON_OFF); //得到该GPIO_CON 寄存器的值 (读)
con &= ~(0xf << con_4bit_shift(offset)); //把oxff左移 offset * 4位(改)
(offset指的是当前GPIO在这个端口的偏移量)
__raw_writel(con, base + GPIOCON_OFF); //写ox00 ,所以就设置为了输入模式 (写)
gpio_dbg("%s: %p: CON now %08lx\n", __func__, base, con);
return 0;
}
static int samsung_gpiolib_4bit_output(struct gpio_chip *chip,
unsigned int offset, int value)
{
struct s3c_gpio_chip *ourchip = to_s3c_gpio(chip); //转换结构体地址
void __iomem *base = ourchip->base;
unsigned long con;
unsigned long dat;
con = __raw_readl(base + GPIOCON_OFF);
con &= ~(0xf << con_4bit_shift(offset)); //清零
con |= 0x1 << con_4bit_shift(offset); //得到ox1
dat = __raw_readl(base + GPIODAT_OFF); //写ox1
if (value) //判断是关还是开
dat |= 1 << offset;
else
dat &= ~(1 << offset);
__raw_writel(dat, base + GPIODAT_OFF); //把值写到DAT端口
__raw_writel(con, base + GPIOCON_OFF); //把值写到CON端口
__raw_writel(dat, base + GPIODAT_OFF);
gpio_dbg("%s: %p: CON %08lx, DAT %08lx\n", __func__, base, con, dat);
return 0;
}
2、分支 2
void __init samsung_gpiolib_add_4bit_chips(struct s3c_gpio_chip *chip,
int nr_chips)
{
for (; nr_chips > 0; nr_chips--, chip++) { //遍历每个数组
samsung_gpiolib_add_4bit(chip); //
s3c_gpiolib_add(chip);
}
}
传入的参数:
struct s3c_gpio_chip
*chip
只传入一个结构体,说明只传入一个端口
__init void s3c_gpiolib_add(struct s3c_gpio_chip *chip)
{
struct gpio_chip *gc = &chip->chip;
int ret;
BUG_ON(!chip->base); //检验是不是为空
BUG_ON(!gc->label);
BUG_ON(!gc->ngpio);
spin_lock_init(&chip->lock); //自旋锁初始化
if (!gc->direction_input) //判断为不为空
gc->direction_input = s3c_gpiolib_input;//添加本文件提供的输入函数
if (!gc->direction_output)
gc->direction_output = s3c_gpiolib_output;//添加本文件提供的输出函数
上面的两个已经被分支1绑定了一个函数
if (!gc->set)
gc->set = s3c_gpiolib_set; // 设置单独一个 gpio 引脚
if (!gc->get)
gc->get = s3c_gpiolib_get; // 读取单独一个 gpio 引脚
#ifdef CONFIG_PM
if (chip->pm != NULL) {
if (!chip->pm->save || !chip->pm->resume)
printk(KERN_ERR "gpio: %s has missing PM functions\n",
gc->label);
} else
printk(KERN_ERR "gpio: %s has no PM function\n", gc->label);
#endif
/* gpiochip_add() prints own failure message on error. */
ret = gpiochip_add(gc); //进入真正的内核添加
if (ret >= 0)
s3c_gpiolib_track(chip); //添加到一个轨迹数组
}
》》》》 gpiochip_add
和 s3c_gpiolib_track
int gpiochip_add(struct gpio_chip *chip)
{
unsigned long flags;
int status = 0;
unsigned id;
int base = chip->base;
if ((!gpio_is_valid(base) || !gpio_is_valid(base + chip->ngpio - 1))//基地址和最高编号的
&& base >= 0) { //IO端口不能为空
status = -EINVAL;
goto fail;
}
spin_lock_irqsave(&gpio_lock, flags);//上锁
if (base < 0) {
base = gpiochip_find_base(chip->ngpio);
if (base < 0) {
status = base;
goto unlock;
}
chip->base = base;
}
/* these GPIO numbers must not be managed by another gpio_chip */
for (id = base; id < base + chip->ngpio; id++) {
if (gpio_desc[id].chip != NULL) {
status = -EBUSY;
break;
}
}
if (status == 0) {
for (id = base; id < base + chip->ngpio; id++) {
gpio_desc[id].chip = chip; //在gpio设备数组中添加端口
/* REVISIT: most hardware initializes GPIOs as
* inputs (often with pullups enabled) so power
* usage is minimized. Linux code should set the
* gpio direction first thing; but until it does,
* we may expose the wrong direction in sysfs.
*/
gpio_desc[id].flags = !chip->direction_input
? (1 << FLAG_IS_OUT)
: 0;
}
}
unlock:
spin_unlock_irqrestore(&gpio_lock, flags);
if (status == 0)
status = gpiochip_export(chip);
fail:
/* failures here can mean systems won't boot... */
if (status)
pr_err("gpiochip_add: gpios %d..%d (%s) failed to register\n",
chip->base, chip->base + chip->ngpio - 1,
chip->label ? : "generic");
return status;
}
static __init void s3c_gpiolib_track(struct s3c_gpio_chip *chip)
{
unsigned int gpn;
int i;
gpn = chip->chip.base;//指向基地址
for (i = 0; i < chip->chip.ngpio; i++, gpn++) {
BUG_ON(gpn >= ARRAY_SIZE(s3c_gpios));
s3c_gpios[gpn] = chip;
}
}
主线2:gpiolib的使用方法
drivers/gpio/gpiolib.c
这个文件中所有的函数构成了内核开发者写的 gpiolib
框架部分。
我们先分析了 SOC 厂商的驱动工程师负责的一部分, 接下来分析 内核开发者的那一部分。
总结:对比之前的 led 框架
-
led框架:
我们先分析的是内核开发者提供的驱动框架,然后模拟 soc 厂商的驱动来写我们的驱动。 -
gpio框架:
我们先分析了 SOC 厂商的驱动工程师负责的一部分, 接下来分析 内核开发者的那一部分。
1、分析内核开发者写的框架
这个文件中提供的函数主要有以下部分:
gpiochip_add
: 添加gpio端口
是框架开出来的接口,给厂商驱动工程师用,用于向内核注册我们的gpio端口
gpiochip_remove
:删除gpio端口
用于内核删除gpio端口
gpio_request
: 使用申请
是框架开出来的接口,给使用gpiolib 来编写自己的驱动的驱动工程师用的,
驱动中要想使用某一个gpio:
1、就必须先调用 gpio_request 接口来向内核的gpiolib部分使用申请
2、得到允许后才可以去使用这个gpio。
gpio_request_one/gpio_request_array
:
这两个是gpio_request的变种
gpio_request_one:申请一个 gpio
gpio_request_array:申请一系列的 gpio
gpio_free
: 释放申请
对应gpio_request,用来释放申请后用完了的gpio
gpiochip_is_requested
: 判断
接口用来判断某一个gpio是否已经被申请了
gpio_direction_input
/gpio_direction_output
:
接口用来设置GPIO为输入/输出模式,
注意:
该函数内部实际并没有对硬件进行操作,只是通过chip结构体变量的函数指针调用了将来SoC厂商的驱动工程师写的真正的操作硬件实现gpio设置成输出模式的那个函数。
2、GPIO的attribute
(1)CONFIG_GPIO_SYSFS
(2)GPIO的attribute演示
- 添加属性的函数
二、使用 gpiolib 完成led驱动
1、流程分析
(1)第1步:使用gpio_request
申请要使用的一个GPIO
(2)第2步:gpio_direction_input
/gpio_direction_output
设置输入/输出模式
(3)第3步:设置输出值gpio_set_value
,获取IO口值gpio_get_value
1、只申请 led1 的资源
#include <linux/module.h> // module_init module_exit
#include <linux/init.h> // __init __exit
#include <linux/fs.h>
#include <linux/leds.h>
#include <mach/regs-gpio.h>
#include <mach/gpio-bank.h>
#include <linux/io.h>
#include <linux/ioport.h>
#include <mach/gpio.h>
#define GPIO_LED1 S5PV210_GPJ0(3)
#define GPIO_LED2 S5PV210_GPJ0(4)
#define GPIO_LED3 S5PV210_GPJ0(5)
#define X210_LED_OFF 1 // X210中LED是正极接电源,负极节GPIO
#define X210_LED_ON 0 // 所以1是灭,0是亮
static struct led_classdev mydev1; // 定义结构体变量
static struct led_classdev mydev2; // 定义结构体变量
static struct led_classdev mydev3; // 定义结构体变量
// 这个函数就是要去完成具体的硬件读写任务的
static void s5pv210_led1_set(struct led_classdev *led_cdev,
enum led_brightness value)
{
printk(KERN_INFO "s5pv210_led1_set\n");
// 在这里根据用户设置的值来操作硬件
// 用户设置的值就是value
if (value == LED_OFF)
{
gpio_set_value(GPIO_LED1, X210_LED_OFF);
}
else
{
gpio_set_value(GPIO_LED1, X210_LED_ON);
}
}
static void s5pv210_led2_set(struct led_classdev *led_cdev,
enum led_brightness value)
{
printk(KERN_INFO "s5pv2102_led_set\n");
if (value == LED_OFF)
{
gpio_set_value(GPIO_LED2, X210_LED_OFF);
}
else
{
gpio_set_value(GPIO_LED2, X210_LED_ON);
}
}
static void s5pv210_led3_set(struct led_classdev *led_cdev,
enum led_brightness value)
{
printk(KERN_INFO "s5pv210_led3_set\n");
if (value == LED_OFF)
{
gpio_set_value(GPIO_LED3, X210_LED_OFF);
}
else
{
gpio_set_value(GPIO_LED3, X210_LED_ON);
}
}
static int __init s5pv210_led_init(void)
{
// 用户insmod安装驱动模块时会调用该函数
// 该函数的主要任务就是去使用led驱动框架提供的设备注册函数来注册一个设备
int ret = -1;
// 在这里去申请驱动用到的各种资源,当前驱动中就是GPIO资源
if (gpio_request(GPIO_LED1, "led1_gpj0.3"))
{
printk(KERN_ERR "gpio_request failed\n");
}
else
{
// 设置为输出模式,并且默认输出1让LED灯灭
gpio_direction_output(GPIO_LED1, 1);
}
// led1
mydev1.name = "led1";
mydev1.brightness = 0;
mydev1.brightness_set = s5pv210_led1_set;
ret = led_classdev_register(NULL, &mydev1);
if (ret < 0) {
printk(KERN_ERR "led_classdev_register failed\n");
return ret;
}
// led2
mydev2.name = "led2";
mydev2.brightness = 0;
mydev2.brightness_set = s5pv210_led2_set;
ret = led_classdev_register(NULL, &mydev2);
if (ret < 0) {
printk(KERN_ERR "led_classdev_register failed\n");
return ret;
}
// led3
mydev3.name = "led3";
mydev3.brightness = 0;
mydev3.brightness_set = s5pv210_led3_set;
ret = led_classdev_register(NULL, &mydev3);
if (ret < 0) {
printk(KERN_ERR "led_classdev_register failed\n");
return ret;
}
return 0;
}
static void __exit s5pv210_led_exit(void)
{
led_classdev_unregister(&mydev1);
led_classdev_unregister(&mydev2);
led_classdev_unregister(&mydev3);
gpio_free(GPIO_LED1);
}
module_init(s5pv210_led_init);
module_exit(s5pv210_led_exit);
// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL"); // 描述模块的许可证
MODULE_AUTHOR("aston <1264671872@qq.com>"); // 描述模块的作者
MODULE_DESCRIPTION("s5pv210 led driver"); // 描述模块的介绍信息
MODULE_ALIAS("s5pv210_led"); // 描述模块的别名信息
- 注册GPIO 资源
// 在这里去申请驱动用到的各种资源,当前驱动中就是GPIO资源
if (gpio_request(GPIO_LED1, "led1_gpj0.3"))
{
printk(KERN_ERR "gpio_request failed\n");
}
else
{
// 设置为输出模式,并且默认输出1让LED灯灭
gpio_direction_output(GPIO_LED1, 1);
}
(1)如何得到一个 GPIO 的编号:使用一个宏定义:
linux/arch/arm/mach-s5pv210/include/mach/gpio.h
目录
#define S5PV210_GPJ0(_nr) (S5PV210_GPIO_J0_START + (_nr))
#define S5PV210_GPJ1(_nr) (S5PV210_GPIO_J1_START + (_nr))
我们自己定义一个宏:
#define GPIO_LED1 S5PV210_GPJ0(3)
#define GPIO_LED2 S5PV210_GPJ0(4)
#define GPIO_LED3 S5PV210_GPJ0(5)
然后去注册:
gpio_request(GPIO_LED1, "led1_gpj0.3")
- 当我们注册号 gpio 之后,很多函数我们就可以直接使用了:
设置为输入模式,之前我们是使用 寄存器来操作的
gpio_direction_output(GPIO_LED1, 1);
设置 gpio 的高低电平,之前我们也是拿 gpio 来操作的
gpio_set_value(GPIO_LED3, X210_LED_ON);
- 注册 leds 类的设备
leds :这个类,是内核帮我们实现的,但是里面的设备需要我们去添加
// led1
mydev1.name = "led1";
mydev1.brightness = 0;
mydev1.brightness_set = s5pv210_led1_set;
ret = led_classdev_register(NULL, &mydev1);
if (ret < 0) {
printk(KERN_ERR "led_classdev_register failed\n");
return ret;
}
mydev1.name : 设备的名字
mydev1.brightness :一个设备的属性
mydev1.brightness_set :一个函数
在 /derivers/leds
,可以看出 brightness
这个属性的 store
主要对应 led_set_brightness
函数。
所以相当于:
echo 1 > brightness
:像这个函数写入值的时候,先调用led_brightness_store
然后间接调用led_set_brightness
__ATTR(brightness, 0644, led_brightness_show, led_brightness_store),
static ssize_t led_brightness_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
ssize_t ret = -EINVAL;
char *after;
unsigned long state = simple_strtoul(buf, &after, 10);
size_t count = after - buf;
if (isspace(*after))
count++;
if (count == size) {
ret = count;
if (state == LED_OFF)
led_trigger_remove(led_cdev);
led_set_brightness(led_cdev, state);
}
return ret;
}
2、注册led1、led2、led3的资源
(1)可以使用 gpio_request
一个一个注册
(2)也可以使用 gpio_request_array
去一次性注册
#define GPIO_LED1 S5PV210_GPJ0(3)
#define GPIO_LED2 S5PV210_GPJ0(4)
#define GPIO_LED3 S5PV210_GPJ0(5)
struct gpio {
unsigned gpio;
unsigned long flags;
const char *label;
};
定义一个结构体数组:
struct gpio gpio_array[] = {
{
.gpio = GPIO_LED1,
.label = "led1_gpj0.3",
}
{
.gpio = GPIO_LED2,
.label = "led1_gpj0.4",
}
{
.gpio = GPIO_LED3,
.label = "led1_gpj0.5",
}
}
int gpio_request_array(struct gpio *array, size_t num)
注册:gpio_request_array(&gpio_array,3);
3、学习 linux 中查看 gpio 的使用情况的方法
(1)内核当中提供了虚拟文件系统 debugfs
, 里面有一个 gpio 文件,提供了使用 gpio 的方法。
(2)使用方法:
mount -t debugfs debugfs /tmp
三、将我们自己写的驱动程序添加到内核当中
1、驱动的存在形式
(1)野生
1、这个驱动不在内核里面
2、每次使用都得用insmod
来挂载
(2)家养
1、这个驱动会在内核启动的时候,就会被加载。
2、在make menuconfig
决定内核怎么编译,方便集成。
注:
(1)以模块的形式在外部编写,调试
(2)将调试好的驱动代码,集成到 kernel 当中。
2、操作
(1)找到我们要放的目录:
1、我们是按照驱动框架来开发的,所以应该放在
/drivers/leds
目录下面。
(2)修改 Makefile ( /drivers/leds
目录下面的 Makefile)
(3) 修改/drivers/leds
目录下面的 kconfig
文件。
tristate
: 三态: -y -m -n。
(4)make menuconfig
当中选中:
make menuconfig
当中的配置项,取决于.config
文件
(5)当我们 make menuconfig
更改配置项之后,.config
文件当中就会出现对应的配置宏。