linux驱动开发—— 5、gpiolib 学习

来自朱有鹏老师的学习笔记,如有侵权,请告知删除。

gpiolib引入
(1)一个事实:很多硬件都要用到GPIO、GPIO会复用

1、缩在我们定时器内部的硬件不用到 GPIO
2、外面的硬件、按键、SD卡、LCD显示器等等都要用到 GPIO

(2)如果同一个GPIO被2个驱动同时控制了,就会出现bug

(3)内核提供gpiolib来统一管理系统中所有GPIO
(4)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_inputsamsung_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_adds3c_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 文件当中就会出现对应的配置宏。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

想文艺一点的程序员

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值