驱动 控制GPIO子系统 框架

本文介绍了Linux内核如何通过Gpiolib管理GPIO,它作为一个中间层提供统一的GPIO操作API,隐藏了不同硬件的具体实现。GPIO由gpio_device结构体表示,每个GPIO控制器包含多个由gpio_desc结构体表示的功能引脚。gpio_chip结构体提供了GPIO控制器的操作方法,包括配置、上下拉、方向和数据读写。此外,文章还展示了旧版和新版GPIO操作API的使用方式。

Linux Kernel 中,对于 GPIO 功能用 Gpiolib 来管理,作为中间层的 Gpiolib ,对上(其他 Drivers)提供一套统一的 操作 GPIO 的软件API,屏蔽了不同芯片的具体实现,对下(硬件)针对不同的芯片提供对应的一套 framework 用以物理实现功能(怎样增加 framework :需先实现 Specific Chip Driver ,再用 Gpiolib 提供的注册函数将其挂接到 Gpiolib 上即可)

一个 gpio_device 结构体表示一个 GPIO 控制器(Bank),多个结构体在内核中用 gpio_devices 链表存储。

//  Linux内核/drivers/gpio/gpiolib.h

struct gpio_device {
	int			id;					//它是系统中第几个gpio controller
	struct device		dev;
	struct cdev		chrdev;
	struct device		*mockdev;
	struct module		*owner;
	struct gpio_chip	*chip;		//含有各类操作函数
	struct gpio_desc	*descs;		//用来描述引脚,每个引脚对应一个gpio_desc
	int			base;		        //每个 Bank 的起始编号,加偏移量成为每个引脚的编号
	u16			ngpio;		        //这个 Bank 有多少个引脚
	char			*label;		    //标签、名字
	void			*data;
	struct list_head        list;

#ifdef CONFIG_PINCTRL
	/*
	 * If CONFIG_PINCTRL is enabled, then gpio controllers can optionally
	 * describe the actual pin range which they serve in an SoC. This
	 * information would be used by pinctrl subsystem to configure
	 * corresponding pins for gpio usage.
	 */
	struct list_head pin_ranges;
#endif
};

一个 GPIO 控制器(Bank)包含多个 GPIO 功能引脚,每个引脚用一个 gpio_desc 结构体表示

struct gpio_desc {
	struct gpio_device	*gdev;		//属于哪个 Bank
	unsigned long		flags;      //属性状态
/* flag symbols are bit numbers */
#define FLAG_REQUESTED	0
#define FLAG_IS_OUT	1
#define FLAG_EXPORT	2	/* protected by sysfs_lock */
#define FLAG_SYSFS	3	/* exported via /sys/class/gpio/control */
#define FLAG_ACTIVE_LOW	6	/* value has active low */
#define FLAG_OPEN_DRAIN	7	/* Gpio is open drain type */
#define FLAG_OPEN_SOURCE 8	/* Gpio is open source type */
#define FLAG_USED_AS_IRQ 9	/* GPIO is connected to an IRQ */
#define FLAG_IS_HOGGED	11	/* GPIO is hogged */
#define FLAG_SLEEP_MAY_LOOSE_VALUE 12	/* GPIO may loose value in sleep */

	/* Connection label */
	const char		*label;		    //一般等于gpio_chip的label,标签,名称
	/* Name of the GPIO */
	const char		*name;			//引脚名
};

gpio_chip 是操作方法结构体,抽象出所有 GPIO 操作,向上提供统一接口,适配不同的芯片,注意它是以一个 GPIO 控制器(Bank)为单位,该结构体抽象的是一个控制器(Bank)的操作方法,而不是一个引脚的,定义在 include/linux/gpio/driver.h 中,每个 GPIO Bank 都有 N 个寄存器来表示不同的操作。如:

        SoC 将 I/O 分为了 2 个 Bank:

                Bank 1:GPIOA ~ GPIOB

                Bank 2:GPIOC ~ GPIOD

        每个 Bank 都有 N 组寄存器来表示 GPIO 的操作:

                针对 Bank 1 中的 GPIOA:             

                        GPIOA_CFG 表示 GPIO A 的配置

                        GPIOA_PULL 表示 GPIO A 的上下拉的配置

                        GPIOA_DIR 表示 GPIO A 配置成为输入或者输出

                        GPIOA_DATA 表示 GPIO A 为输出时写高低或输入时读高低

                对于 Bank 1 中的 GPIOB 同理

                对于 Bank 2 同理

任何一种芯片的所有 I/O 接口都有相应的默认配置,有些芯片的 I/O 接口全部默认为 GPIO,此外再提供管脚复用功能(后期的 Linux 版本中,使用 pin control 来抽象)

对于管脚要实现 GPIO,需要把此管脚配置成 GPIO ,其他功能(如 I2C,SPI 等)同理

GPIO 有一些通用的特性,如 设置管脚方向,读写管脚电平,作为外部中断输入 等

struct gpio_chip {
  const char *label;        //标签,取别名
  struct device *dev;
  struct module *owner;

/*request是特定芯片激活的可选回调函数。如果提供了,
在调用gpio_request()或gpiod_get()时,它会在分配GPIO之前执行。*/
  int (*request)(struct gpio_chip *chip, unsigned offset);

/*free 是一个可选的回调函数,用于特定芯片的释放。如果提供了,
那么在调用gpiod_put()或gpio_free()时,它会在GPIO被释放之前执行。*/
  void (*free)(struct gpio_chip *chip, unsigned offset);

/*get_direction 在需要知道方向的时候执行GPIO偏移量。返回值应为0表示out,
 1表示in(与GPIOF_DIR_XXX相同),或负错误。*/
  int (*get_direction)(struct gpio_chip *chip, unsigned offset);

//direction_input 将信号偏移量offset配置为输入,否则返回错误。
  int (*direction_input)(struct gpio_chip *chip, unsigned offset);
  int (*direction_output)(struct gpio_chip *chip, unsigned offset,int value);

//get 返回GPIO offset 的值;对于输出信号,这将返回实际感知到的值或0。
  int (*get)(struct gpio_chip *chip,unsigned offset);

//set 指定一个输出值给GPIO offset。
  void (*set)(struct gpio_chip *chip, unsigned offset, int value);

/*当需要为 mask 定义的多个信号分配输出值时,调用 set_multiple。如果没有提供,
内核将安装一个通用回调函数,它将遍历掩码位并在每个位执行chip->set(i)。*/
  void (*set_multiple)(struct gpio_chip *chip, unsigned long *mask,
                        unsigned long *bits);
  int (*set_debounce)(struct gpio_chip *chip, unsigned offset,unsigned debounce);
  int (*to_irq)(struct gpio_chip *chip, unsigned offset);

  int base;                    //每个 Bank 的起始编号,加偏移量成为每个引脚的编号
  u16 ngpio;                   //个数
  const char *const *names;    //每个引脚的名字
  bool can_sleep;
  bool irq_not_threaded;
  bool exported;

#ifdef CONFIG_GPIOLIB_IRQCHIP
  /*
   * With CONFIG_GPIOLIB_IRQCHIP we get an irqchip
   * inside the gpiolib to handle IRQs for most practical cases.
   */
  struct irq_chip *irqchip;
  struct irq_domain *irqdomain;
  unsigned int irq_base;            
  irq_flow_handler_t irq_handler;
  unsigned int irq_default_type;
#endif
#if defined(CONFIG_OF_GPIO)
  /*
   * If CONFIG_OF is enabled, then all GPIO controllers described in the
    * device tree automatically may have an OF translation
   */
  struct device_node *of_node;
  int of_gpio_n_cells;
  int (*of_xlate)(struct gpio_chip *gc,
  const struct of_phandle_args *gpiospec, u32 *flags);
};

旧版

1.获取节点结构体(方法很多,本次通过路径获得节点结构体 )

struct device_node *of_find_node_by_path(const char *path)

path:在设备树中节点的绝对路径

返回值:成功=节点结构体首地址,失败=NULL

2.从节点结构体中获得gpio编号(每个gpio引脚唯一,不同 Bank 中也不相同,只是在gpio子系统中所有引脚的编号,并非引脚编号,从0开始,板子上应该有写)

int of_get_named_gpio(struct device_node *np,const char *propname,int index)

np:节点指针

propname:键(属性)名字,即使用gpio的键

index:索引号,当属性=多个值时,0=第一个,1=第二个

返回值:成功=gpio编号,失败=错误码

3.向内核申请gpio引脚,将指定的gpio管脚添加到pin_ctrl子系统中管理,避免其他外设使用

int gpio_request(unsigned gpio,const char *label)

gpio:获得的gpio引脚的编号

label:标签名,给gpio设置个名字,可填NULL

返回值:成功=0,失败=错误码

4.释放在pin_ctrl子系统中注册的gpio编号(驱动结束 static void __exit XXX_exit(void)中使用)

void gpio_free(unsigned gpio)

gpio:获得的gpio编号

返回值:无

操作GPIO的API

1.设置gpio管脚的方向(输入/输出)

输入:

int gpio_direction_input(unsigned gpio)

输出:

int gpio_direction_output(unsigned gpio, int value)

gpio:获得的gpio编号

value:默认电平的状态,1=高电平,0=低电平

返回值:成功=0,失败=错误码

2.读取管脚的值

int gpio_get_value(unsigned gpio)

gpio:获得的gpio编号

返回值:1=高电平,0=低电平

3.设置管脚的输出电平(记住在驱动结束 static void __exit XXX_exit(void)中要恢复电平)

void gpio_set_value(unsigned gpio, int value)

gpio:获得的gpio编号

value:要设置的电平状态,1=高电平,0=低电平

返回值:无

示例

#include <linux/init.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
/*
myleds{
    led1=<&gpioe 10 0>;  //10 --->gpioe10   0 管脚的默认状态
    led2=<&gpiof 10 0>;  //10 --->gpioe10   0 管脚的默认状态
    led3=<&gpioe 8 0>;   //10 --->gpioe10   0 管脚的默认状态
};
*/
struct device_node *node;
int gpiono;
static int __init mycdev_init(void)
{
    //1.获取节点
    node = of_find_node_by_path("/myleds");
    if(node == NULL){
        printk("parse node error\n");
        return -EINVAL;
    }
    //2.解析得到gpio
    gpiono = of_get_named_gpio(node,"led1",0);
    if(gpiono < 0){
        printk("get gpio number error\n");
        return gpiono;
    } 
    //3.申请使用的gpio
    if(gpio_request(gpiono,NULL)){
        printk("request gpio error\n");
        return gpiono;
    }
    //4.通过gpio操作设备
    gpio_direction_output(gpiono,1);

    return 0;
}
static void __exit mycdev_exit(void)
{    
    //5.设置输出电平
    gpio_set_value(gpiono,0);
    //6.释放gpio编号
    gpio_free(gpiono);

}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");

新版:增加了更多电平设置,enum gpiod_flags{ }

           拥有gpiod_set_config(struct gpio_desc* desc,unsigned long config)函数来设置管脚

1.获取节点结构体(方法很多,本次通过路径获得节点结构体 )

struct device_node *of_find_node_by_path(const char *path)

path:在设备树中节点的绝对路径

返回值:成功=节点结构体首地址,失败=NULL

2.获取一个引脚,对应设备树中键的值,即完成gpio引脚和返回值的一一对应

struct gpio_desc *gpiod_get_from_of_node(struct device_node *node,const char *propname, int index,enum gpiod_flags dflags,const char *label)

node:节点指针

propname:键(属性)名字,即使用gpio的键

index:索引号,当属性=多个值时,0=第一个,1=第二个

dflags:设置电平的旗标

enum gpiod_flags {
	GPIOD_ASIS	= 0,
	GPIOD_IN	= GPIOD_FLAGS_BIT_DIR_SET,
	GPIOD_OUT_LOW	= GPIOD_FLAGS_BIT_DIR_SET | GPIOD_FLAGS_BIT_DIR_OUT,
	GPIOD_OUT_HIGH	= GPIOD_FLAGS_BIT_DIR_SET | GPIOD_FLAGS_BIT_DIR_OUT | GPIOD_FLAGS_BIT_DIR_VAL,
	GPIOD_OUT_LOW_OPEN_DRAIN = GPIOD_OUT_LOW | GPIOD_FLAGS_BIT_OPEN_DRAIN,
	GPIOD_OUT_HIGH_OPEN_DRAIN = GPIOD_OUT_HIGH | GPIOD_FLAGS_BIT_OPEN_DRAIN,
};

label:标签名,给gpio设置个名字,可填NULL

返回值:成功=引脚结构体首地址,失败=错误码指针(用IS_ERR()检错)

操作GPIO的API

1.设置gpio管脚的方向(输入/输出)

输入:

int gpiod_direction_input(struct gpio_desc *desc)

输出:

int gpiod_direction_output(struct gpio_desc *desc, int value)

desc:获取的引脚结构体指针

value:默认电平的状态,1=高电平,0=低电平

返回值:成功=0,失败=错误码

2.读取管脚的值

int gpiod_get_value(const struct gpio_desc *desc)

desc:获取的引脚结构体指针

返回值:1=高电平,0=低电平

3.设置管脚的输出电平(记住在驱动结束 static void __exit XXX_exit(void)中要恢复电平)

void gpiod_set_value(struct gpio_desc *desc, int value)

desc:获取的引脚结构体指针

value:要设置的电平状态,1=高电平,0=低电平

返回值:无

4.释放gpio(驱动结束 static void __exit XXX_exit(void)中使用)

void gpiod_put(struct gpio_desc *desc)

desc:获取的引脚结构体指针

返回值:无

示例

#include <linux/init.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
/*
myleds{
    led1=<&gpioe 10 0>;
    led2=<&gpiof 10 0>;
    led3=<&gpioe 8 0>;
};
*/
struct device_node* node;
struct gpio_desc* desc;
static int __init mycdev_init(void)
{
    // 1.获取节点
    node = of_find_node_by_path("/myleds");
    if (node == NULL) {
        printk("parse node error\n");
        return -EINVAL;
    }
    
    //2.获取一个引脚
    desc = gpiod_get_from_of_node(node,"led2",0,GPIOD_OUT_HIGH,0);
    if (IS_ERR(desc)) {
        printk("get gpio desc error\n");
        return PTR_ERR(desc);
    }

    //3.操作gpio

    return 0;
}
static void __exit mycdev_exit(void)
{    
    //设置电平
    gpiod_set_value(desc, 0);
    //释放gpio
    gpiod_put(desc);
}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值