Linux GPIO文档

通用输入/输出(GPIO)

  • 文档网址: https://www.kernel.org/doc/html/v5.7/driver-api/gpio/index.html
  • 闲来无事,看了下官方的文档;当然是边看边用翻译翻的,特地发上来备个份

Core

struct gpio_irq_chip {
    struct irq_chip *chip;
    struct irq_domain *domain;
    const struct irq_domain_ops *domain_ops;
#ifdef CONFIG_IRQ_DOMAIN_HIERARCHY;
    struct fwnode_handle *fwnode;
    struct irq_domain *parent_domain;
    int (*child_to_parent_hwirq)(struct gpio_chip *gc,unsigned int child_hwirq,unsigned int child_type,unsigned int *parent_hwirq, unsigned int *parent_type);
    void *(*populate_parent_alloc_arg)(struct gpio_chip *gc,unsigned int parent_hwirq, unsigned int parent_type);
    unsigned int (*child_offset_to_irq)(struct gpio_chip *gc, unsigned int pin);
    struct irq_domain_ops child_irq_domain_ops;
#endif;
    irq_flow_handler_t handler;
    unsigned int default_type;
    struct lock_class_key *lock_key;
    struct lock_class_key *request_key;
    irq_flow_handler_t parent_handler;
    void *parent_handler_data;
    unsigned int num_parents;
    unsigned int *parents;
    unsigned int *map;
    bool threaded;
    int (*init_hw)(struct gpio_chip *gc);
    void (*init_valid_mask)(struct gpio_chip *gc,unsigned long *valid_mask, unsigned int ngpios);
    unsigned long *valid_mask;
    unsigned int first;
    void (*irq_enable)(struct irq_data *data);
    void (*irq_disable)(struct irq_data *data);
};

成员

chip
    GPIO IRQ芯片实现,由GPIO驱动程序提供
domain
    中断翻译域;负责GPIO hwirq号和Linux IRQ号之间的映射
domain_ops
    该IRQ芯片的中断域操作集合
fwnode
    对应于此gpiochip/irqchip的固件节点,这是分层irqdomain支持所必需的
parent_domain
    如果为非NULL,则将其设置为该GPIO中断控制器的IRQ域的父级,以建立分层中断域。这的存在将激活分级中断支持
child_to_parent_hwirq
    * 此回调将子硬件IRQ偏移转换为分层中断芯片上的父硬件IRQ偏移。子硬件IRQ对应于GPIO索引0..ngpio-1(请参见struct gpio_chip的ngpio字段),并且驱动程序应返回相应的父硬件IRQ和类型(例如IRQ_TYPE_ *)。驱动程序可以根据偏移量或使用查找表或最适合该芯片的任何方法来计算该值。在驱动程序中成功翻译后返回0
    * 如果某些范围的硬件IRQ没有相应的父HWIRQ,则返回-EINVAL,但还要确保填写valid_mask和 need_valid_mask以使这些GPIO行不可用于转换
populate_parent_alloc_arg
    此__可选回调__为父级的IRQ域分配并填充特定的结构。如果未指定, gpiochip_populate_parent_fwspec_twocell则将使用。gpiochip_populate_parent_fwspec_fourcell还提供了一个名为的四单元变体
child_offset_to_irq
    此可选回调用于将GPIO芯片上子代的GPIO线偏移转换为GPIO to_irq()回调的IRQ号。如果未指定,则将提供默认回调,该回调返回行偏移量
child_irq_domain_ops
    将用于此GPIO IRQ芯片的IRQ域操作。如果未提供任何操作,则将填充默认回调以设置IRQ层次结构。一些驱动程序需要提供自己的翻译功能
handler
    由GPIO驱动程序提供的用于GPIO IRQ的IRQ处理程序(通常是预定义的IRQ核心功能)。
default_type
    在GPIO驱动程序初始化期间应用的默认IRQ触发类型,由GPIO驱动程序提供。
lock_key
    每个GPIO IRQ芯片的lockdep类,用于IRQ锁定。
request_key
    针对IRQ请求的每个GPIO IRQ芯片lockdep类。
parent_handler
    GPIO芯片的父中断的中断处理程序,如果父中断是嵌套的而不是级联的,则可以为NULL。
parent_handler_data
    与父中断的处理程序相关联并传递给该数据的数据。
num_parents
    GPIO芯片的中断父节点数。
parents
    GPIO芯片的中断父级列表。它归驱动程序所有,因此内核将仅引用此列表,而不修改它。
map
    GPIO芯片每行的中断父级列表。
threaded
    如果设置为true,则中断处理使用嵌套线程。
init_hw
    在添加IRQ芯片之前初始化硬件的可选例程。当特定驱动程序要清除与IRQ相关的寄存器以避免不必要的事件时,这非常有用。
init_valid_mask
    可选的例程,用于初始化valid_mask,如果不是所有的GPIO线都是有效的中断,则使用该例程。有时有些行无法触发中断,并且此例程在定义后会在"valid_mask"中传递一个位图,并且它将有效的ngpios位从0 ..(ngpios-1)设置为"1"。然后,如果某些位不能用于中断,则回调函数可以将它们直接设置为"0"。
valid_mask
    如果不是,则NULL保持有效的GPIO的位掩码,该GPIO包含在芯片的IRQ域中。
first
    静态IRQ分配是必需的。如果设置,irq_domain_add_simple()将在初始化期间分配和映射所有IRQ。
irq_enable
    存储旧的irq_chip irq_enable回调
irq_disable
    存储旧的irq_chip irq_disable回调
struct gpio_chip {
    const char              *label;
    struct gpio_device      *gpiodev;
    struct device           *parent;
    struct module           *owner;
    int (*request)(struct gpio_chip *gc, unsigned offset);
    void (*free)(struct gpio_chip *gc, unsigned offset);
    int (*get_direction)(struct gpio_chip *gc, unsigned offset);
    int (*direction_input)(struct gpio_chip *gc, unsigned offset);
    int (*direction_output)(struct gpio_chip *gc, unsigned offset, int value);
    int (*get)(struct gpio_chip *gc, unsigned offset);
    int (*get_multiple)(struct gpio_chip *gc,unsigned long *mask, unsigned long *bits);
    void (*set)(struct gpio_chip *gc, unsigned offset, int value);
    void (*set_multiple)(struct gpio_chip *gc,unsigned long *mask, unsigned long *bits);
    int (*set_config)(struct gpio_chip *gc,unsigned offset, unsigned long config);
    int (*to_irq)(struct gpio_chip *gc, unsigned offset);
    void (*dbg_show)(struct seq_file *s, struct gpio_chip *gc);
    int (*init_valid_mask)(struct gpio_chip *gc,unsigned long *valid_mask, unsigned int ngpios);
    int (*add_pin_ranges)(struct gpio_chip *gc);
    int base;
    u16 ngpio;
    const char *const *names;
    bool can_sleep;
#if IS_ENABLED(CONFIG_GPIO_GENERIC);
    unsigned long (*read_reg)(void __iomem *reg);
    void (*write_reg)(void __iomem *reg, unsigned long data);
    bool be_bits;
    void __iomem *reg_dat;
    void __iomem *reg_set;
    void __iomem *reg_clr;
    void __iomem *reg_dir_out;
    void __iomem *reg_dir_in;
    bool bgpio_dir_unreadable;
    int bgpio_bits;
    spinlock_t bgpio_lock;
    unsigned long bgpio_data;
    unsigned long bgpio_dir;
#endif ;
#ifdef CONFIG_GPIOLIB_IRQCHIP;
    struct gpio_irq_chip irq;
#endif ;
    unsigned long *valid_mask;
#if defined(CONFIG_OF_GPIO);
    struct device_node *of_node;
    unsigned int of_gpio_n_cells;
    int (*of_xlate)(struct gpio_chip *gc, const struct of_phandle_args *gpiospec, u32 *flags);
#endif ;
};

成员

label
    GPIO设备的功能名称,例如部件号或实现它的SoC IP块的名称。
gpiodev
    内部状态持有者,不透明结构
parent
    提供GPIO的可选父设备
owner
    帮助防止删除导出活动GPIO的模块
request
    可选挂钩,用于特定于芯片的激活,例如启用模块电源和时钟;可以睡眠
free
    可选挂钩,用于特定于芯片的停用,例如禁用模块电源和时钟;可以睡眠
get_direction
    返回信号"偏移",0 =输出,1 =输入(与GPIOF_DIR_XXX相同)或负误差的方向。建议始终执行此功能,即使在仅输入或仅输出的gpio芯片上也是如此。
direction_input
    将信号"偏移"配置为输入,或返回错误在仅输入或仅输出的gpio芯片上可以忽略此信号。
direction_output
    将信号"偏移"配置为输出,或返回错误在仅输入或仅输出的gpio芯片上可以忽略此信号。
get
    返回信号"偏移"的值,0 =低,1 =高或负误差
get_multiple
    读取"掩码"定义的多个信号的值并将其存储在"位"中,如果成功或出现负错误,则返回0
set
    为信号"偏移"分配输出值
set_multiple
    为"mask"定义的多个信号分配输出值
set_config
    用于各种设置的可选挂钩。使用与通用pinconf相同的打包配置格式。
to_irq
    可选的钩子,支持非静态gpio_to_irq()映射;实现不能是睡眠
dbg_show
    在debugfs中显示内容的可选例程;省略时将使用默认代码,但是自定义代码可以显示额外的状态(例如上拉/下拉配置)。
init_valid_mask
    可选的例程,用于初始化validate_mask,如果并非所有GPIO都有效,则使用该例程。
add_pin_ranges
    可选的例程,用于初始化引脚范围,在需要提供GPIO功能的特殊引脚映射时使用。在添加GPIO芯片之后,在添加IRQ芯片之前调用它。
base
    标识此芯片处理的第一个GPIO编号;或者,如果在注册过程中为否,则请求动态ID分配。弃用:不提供任何非负数,并且不建议使用GPIO芯片的基本偏移量。请以-1为基数,让gpiolib在所有可能的情况下选择芯片基数。从长远来看,我们想摆脱静态GPIO编号空间。
ngpio
    该控制器处理的GPIO数量;最后处理的GPIO是(base + ngpio-1)。
names
    如果设置,则必须是字符串数组,以用作此芯片中GPIO的备用名称。如果没有GPIO的别名,则阵列中的任何条目都可以为NULL,但是阵列的长度必须为ngpio条目。名称可以包含一个无符号int的单个printk格式说明符。用gpio的实际编号代替。
can_sleep
    如果get()/set()方法在通过I2C或SPI访问GPIO扩展器芯片时必须睡眠,则必须设置该标志。这意味着,如果芯片支持IRQ,则需要对这些IRQ进行线程化,因为例如在读取IRQ状态寄存器时,芯片访问可能会休眠。
read_reg
    通用GPIO的读取器功能
write_reg
    通用GPIO的writer函数
be_bits
    如果通用GPIO具有大字节序位顺序(位31代表行0,位30代表行1…位0代表行31),则通用GPIO内核将其设置为true。仅用于内部清洁。
reg_dat
    通用GPIO的数据(输入)寄存器
reg_set
    通用GPIO的输出设置寄存器(out = high)
reg_clr
    通用GPIO的输出清除寄存器(out = low)
reg_dir_out
    通用GPIO的方向输出设置寄存器
reg_dir_in
    通用GPIO设置寄存器中的方向
bgpio_dir_unreadable
    表示无法读取方向寄存器,我们需要依靠内部状态跟踪。
bgpio_bits
    通用GPIO使用的寄存器位数,即<寄存器宽度> * 8
bgpio_lock
    用于锁定chip-> bgpio_data。同样,需要这样做以保持阴影和实际数据寄存器的写入在一起。
bgpio_data
    通用GPIO的影子数据寄存器,用于安全地清除/设置位。
bgpio_dir
    通用GPIO的阴影方向寄存器,用于安全清除/设置方向。该词中的"1"表示该行被设置为输出。
irq
    将中断芯片功能与GPIO芯片集成在一起。可用于处理大多数实际情况下的IRQ。
valid_mask
    如果没有,则NULL保持可从芯片使用的GPIO的位掩码。
of_node
    指向代表该GPIO控制器的设备树节点的指针。
of_gpio_n_cells
    构成GPIO说明符的单元数。
of_xlate
    回调将设备树的GPIO指定符转换为芯片相关的GPIO编号和标志。

描述

gpio_chip可以帮助平台抽象各种GPIO来源,以便可以通过通用的编程接口对其进行访问。来源示例包括SOC控制器,FPGA,多功能芯片,专用GPIO扩展器等。
每个芯片控制着许多信号,这些信号在方法调用中由0 …(ngpio -1)范围内的"偏移"值标识。当通过gpio_get_value(gpio)之类的调用引用这些信号时,偏移量是通过从gpio编号中减去基数来计算的。

    gpiochip_add_data(gc, data)     //注册一个 gpio_chip

参数

  • gc
    未描述
  • data
    与该芯片相关的驱动程序专用数据

语境
可能在irqs起作用之前
描述
如果gpiochip_add_data()在启动过程中很早就调用了,以便可以自由使用GPIO,则必须在gpio框架的arch_initcall()之前注册chip-> parent设备。否则,GPIO的sysfs初始化将完全失败。

gpiochip_add_data() 必须仅在gpiolib初始化之后(即在core_initcall()之后)调用。

如果chip-> base为负,则要求动态分配一定范围的有效GPIO。

返回
如果无法注册芯片,则为负errno,例如因为chip-> base无效或已与其他芯片关联。否则,它返回零作为成功代码

//GOIP范围由芯片控制
struct gpio_pin_range {
  struct list_head node;
  struct pinctrl_dev *pctldev;
  struct pinctrl_gpio_range range;
};

成员

  • node
    列表,用于维护一组引脚范围,内部使用
  • pctldev
    pinctrl设备,可处理相应的引脚
  • range
    由gpio控制器控制的引脚的实际范围
//将GPIO编号转换为其描述符
struct gpio_desc * gpio_to_desc(unsigned gpio)

参数

  • gpio
    全局GPIO编号

返回

  • 与给定GPIO关联的GPIO描述符,或者系统中NULL不存在具有给定编号的GPIO。
//获取与此芯片的给定硬件编号相对应的GPIO描述符
struct gpio_desc * gpiochip_get_desc(struct gpio_chip *gc,unsigned int  hwnum)

参数

  • struct gpio_chip * gc
    GPIO芯片
  • unsigned int hwnum
    该芯片的GPIO的硬件编号

返回

  • 获取与此芯片的给定硬件编号相对应的GPIO描述符
//将GPIO描述符转换为整数名称空间
int desc_to_gpio(const struct gpio_desc * desc)

参数

  • desc
    GPIO描述符

描述

  • 将来应该会消失,但由于我们仍然将GPIO编号用于错误消息和sysfs节点,因此这是必需的

返回

  • 由其描述符指定的GPIO的全局GPIO号。
//返回GPIO描述符所属的GPIO芯片
struct gpio_chip * gpiod_to_chip(const struct gpio_desc * desc)

参数

  • desc
    GPIO描述符

//返回GPIO的当前方向
int gpiod_get_direction(struct gpio_desc * desc)

参数

  • desc
    GPIO描述符

描述

  • 对于输出,返回0,对于输入,返回1,或者在出现错误的情况下返回错误代码。
    如果gpiod_cansleep()为true,则此功能可能会休眠。

//获取芯片的每个子驱动器数据
void * gpiochip_get_data(struct gpio_chip * gc)

参数

  • desc
    GPIO描述符

//注销gpio_chip
void gpiochip_remove(struct gpio_chip * gc)

//用于定位特定gpio_chip的迭代器
struct gpio_chip* gpiochip_find(void *data, int (*match)(struct gpio_chip *gc, void *data))

参数

  • data
    用于传给匹配函数的数据
  • match
    回调函数检查gpio_chip

描述

  • 类似于bus_find_device。它返回对gpio_chip的引用,该引用由用户提供的match回调确定。如果设备不匹配,则回调应返回0,如果不匹配,则回调应返回非零。如果回调为非零值,则此函数将返回到调用方,并且不会在其他gpio_chips上进行迭代。

//将嵌套的irqchip连接到gpiochip
void gpiochip_set_nested_irqchip(struct gpio_chip *  gc,struct irq_chip *  irqchip,unsigned int  parent_irq)

//将IRQ映射到GPIO irqchip
int gpiochip_irq_map(struct irq_domain * d, unsigned int irq, irq_hw_number_t hwirq)

参数

  • struct irq_domain *d
    此irqchip使用的irqdomain
  • unsigned int irq
    GPIO irqchip irq使用的全局irq编号
  • irq_hw_number_t hwirq
    该gpiochip上的本地IRQ/GPIO线偏移

描述

  • 通过将gpiochip分配为芯片数据,并使用gpiochip内存储的irqchip,此功能将为gpiochip上某个IRQ线设置映射

一些gpiod_*的函数

int gpiod_direction_input(struct gpio_desc *  desc)     //设置GPIO方向为输入
int gpiod_direction_output_raw(struct gpio_desc * desc, int value)  //设置GPIO输出模式和值
int gpiod_direction_output(struct gpio_desc * desc, int value)
int gpiod_set_config(struct gpio_desc * desc, unsigned long config) //为GPIO设置配置 -ENOTSUPP控制器不支持设置,则成功则为0 
int gpiod_set_debounce(struct gpio_desc * desc, unsigned debounce)  //设置消抖时间 单位微妙  -ENOTSUPP控制器不支持设置,则成功则为0 
int gpiod_set_transitory(struct gpio_desc * desc, bool transitory) //如果在挂起或重置时丢失状态,则为true;对于持久性,则为false
int gpiod_is_active_low(const struct gpio_desc * desc)  //测试GPIO是否是低电平有效
void gpiod_toggle_active_low(struct gpio_desc * desc)   //切换GPIO是否为低电平有效
int gpiod_get_raw_value(const struct gpio_desc * desc)  //获取GPIO的原始值
int gpiod_get_raw_value_cansleep(const struct gpio_desc * desc)
void gpiod_set_raw_value(struct gpio_desc * desc, int value)
void gpiod_set_raw_value_cansleep(struct gpio_desc * desc, int value)
int gpiod_get_value(const struct gpio_desc * desc)      //获取GPIO值
int gpiod_get_value_cansleep(const struct gpio_desc * desc)
void gpiod_set_value(struct gpio_desc * desc, int value)
void gpiod_set_value_cansleep(struct gpio_desc * desc, int value)
int gpiod_get_raw_array_value(unsigned int array_size, struct gpio_desc ** desc_array, struct gpio_array * array_info, unsigned long * value_bitmap) //从GPIO数组读取原始值
int gpiod_get_raw_array_value_cansleep(unsigned int array_size, struct gpio_desc ** desc_array, struct gpio_array * array_info, unsigned long * value_bitmap)
int gpiod_set_raw_array_value(unsigned int array_size, struct gpio_desc ** desc_array, struct gpio_array * array_info, unsigned long * value_bitmap)
int gpiod_set_raw_array_value_cansleep(unsigned int array_size, struct gpio_desc ** desc_array, struct gpio_array * array_info, unsigned long * value_bitmap)
int gpiod_get_array_value(unsigned int array_size, struct gpio_desc ** desc_array, struct gpio_array * array_info, unsigned long * value_bitmap) //从GPIO数组读取值
int gpiod_get_array_value_cansleep(unsigned int array_size, struct gpio_desc ** desc_array, struct gpio_array * array_info, unsigned long * value_bitmap)
int gpiod_set_array_value(unsigned int array_size, struct gpio_desc ** desc_array, struct gpio_array * array_info, unsigned long * value_bitmap)
int gpiod_set_array_value_cansleep(unsigned int array_size, struct gpio_desc ** desc_array, struct gpio_array * array_info, unsigned long * value_bitmap)
int gpiod_cansleep(const struct gpio_desc * desc)   //检测GPIO是否可以睡眠
int gpiod_set_consumer_name(struct gpio_desc * desc, const char * name) //设置使用者名称
int gpiod_to_irq(const struct gpio_desc * desc)     //返回对应于GPIO的中断号
int gpiod_count(struct device * dev, const char * con_id) //返回与功能关联的GPIO数量
struct gpio_desc * gpiod_get(struct device * dev, const char * con_id, enum gpiod_flags flags) //获取给定功能的GPIO
struct gpio_desc * gpiod_get_optional(struct device * dev, const char * con_id, enum gpiod_flags flags) //获取给定功能的可选GPIO,不一定成功,如果改功能未分配给GPIO会返回NULL
struct gpio_desc * fwnode_gpiod_get_index(struct fwnode_handle * fwnode, const char * con_id, int index, enum gpiod_flags flags, const char * label) 

从固件节点中获取GPIO

成员

  • struct fwnode_handle * fwnode
    固件节点的句柄
  • const char * con_id
    GPIO使用的功能
  • int index
    节点内GPIO的索引
  • enum gpiod_flags flags
    初始化标志
  • const char * label
    GPIO的标签

描述

  • 此功能可用于从不透明固件获取其配置的驱动程序。

  • 该函数使用底层固件接口中的任何一种正确地找到相应的GPIO,然后确保在将其返回给调用者之前已请求GPIO描述符。

struct gpio_desc * fwnode_get_named_gpiod(struct fwnode_handle * fwnode, const char * propname, int index, enum gpiod_flags dflags, const char * label) //从固件节点中获取GPIO

成员

  • struct fwnode_handle * fwnode
    固件节点的句柄
  • const char * propname
    代表GPIO的固件属性的名称
  • int index
    为使用者获取的GPIO的索引
  • enum gpiod_flags flags
    初始化标志
  • const char * label
    GPIO的标签

描述

  • 此功能可用于从不透明固件获取其配置的驱动程序。

  • 该函数使用底层固件接口中的任何一种正确地找到相应的GPIO,然后确保在将其返回给调用者之前已请求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) //从OF节点获取GPIO

Sysfs助手

int gpiod_export(struct gpio_desc * desc, bool direction_may_change)    //通过Sysfs导出GPIO
int gpiod_export_link(struct device * dev, const char * name, struct gpio_desc * desc) //创建sys  link 到 GPIO节点
void gpiod_unexport(struct gpio_desc * desc)


函数太多了 就不一一翻译了。


GPIO程序接口

头文件

    #include 

GPIO的内部表示

GPIO芯片可处理一条或多条GPIO线。要被视为GPIO芯片,这些线路必须符合以下定义:
    1.通用输入/输出。
    2.如果该线路不是通用线路,则它不是GPIO,并且不应由GPIO芯片处理。
    3.用例是指示性的:系统中的某些线路可以称为GPIO,但具有非常特殊的用途,因此不符合通用I/O的标准。另一方面,LED驱动器线可用作GPIO,因此仍应由GPIO芯片驱动器处理。
在GPIO驱动器内部,单个GPIO线由其硬件编号标识,有时也称为offset,它是0到n-1之间的唯一编号,n是芯片管理的GPIO的数量。

硬件GPIO编号对于硬件来说应该是直观的,例如,如果系统使用一组内存映射的I/O寄存器,其中32位寄存器中的每行一个位处理32个GPIO线,则是有意义的为此使用硬件偏移量0..31,对应于寄存器中的位0..31。

这个数字纯粹是内部的:特定GPIO线的硬件数字永远不会在驱动程序外部可见。

除了此内部编号之外,每条GPIO行还需要在整数GPIO名称空间中具有一个全局编号,以便可以与旧式GPIO接口一起使用。因此,每个芯片必须有一个"基本"编号(可以自动分配),对于每条GPIO线,全局编号将是(基本+硬件编号)。尽管整数表示形式已被弃用,但它仍然有许多用户,因此需要维护。

因此,例如,一个平台可以对GPIO使用全局编号32-159,而一个控制器在32的"基数"上定义128个GPIO。而另一个平台则将全局编号0..63用于一组GPIO控制器,将64-79用于另一种类型的GPIO控制器,并在一个特定的板80-95上使用FPGA。遗留数字不必是连续的。这些平台中的任何一个都可以使用数字2000-2063来识别I2C GPIO扩展器库中的GPIO线。

控制器驱动程序:gpio_chip

在gpiolib框架中,每个GPIO控制器都打包为"struct gpio_chip"(有关完整定义,请参见),该类型的每个控制器都具有公共成员,这些成员应由驱动程序代码分配:

建立GPIO线方向的方法
用于访问GPIO线值的方法
为给定的GPIO线设置电气配置的方法
返回与给定GPIO线关联的IRQ编号的方法
标记说明是否调用其方法可能会休眠
可选的行名数组,用于标识行
可选的debugfs转储方法(显示额外的状态信息)
可选基数(如果省略,将自动分配)
使用平台数据进行诊断和GPIO芯片映射的可选标签
实现gpio_chip的代码应支持控制器的多个实例,最好使用驱动程序模型。该代码将配置每个gpio_chip并发出gpiochip_add(),gpiochip_add_data()或devm_gpiochip_add_data()。删除GPIO控制器应该很少。使用 gpiochip_remove()时,它是不可避免的。

通常,gpio_chip是实例特定结构的一部分,其状态未通过GPIO接口公开,例如寻址,电源管理等。诸如音频编解码器之类的芯片将具有复杂的非GPIO状态。

任何debugfs转储方法通常应忽略未请求的行。他们可以使用gpiochip_is_requested(),返回NULL或在被请求时与该GPIO行关联的标签。

实时注意事项:GPIO驱动程序不应在其gpio_chip实现(.get/.set和方向控制回调)中使用spinlock_t或任何可睡眠的API(如PM运行时),如果希望从实时内核的原子上下文中调用GPIO API(内部)硬IRQ处理程序和类似的上下文)。通常,这不是必需的。

GPIO电气配置

可以使用.set_config()回调将GPIO线配置为几种电气操作模式。目前,此API支持设置:
1.防弹跳
2.单端模式(漏极开路/开源)
3.上拉和下拉电阻启用
这些设置如下所述。
.set_config()回调使用与通用引脚控制驱动程序相同的枚举数和配置语义。这不是巧合:可以将.set_config()分配给该函数gpiochip_generic_config() ,这将导致调用pinctrl_gpio_set_config()并最终终止于GPIO控制器"背后"的引脚控制后端,通常更靠近GPIO控制器。实际的针脚。这样,引脚控制器可以管理以下列出的GPIO配置。

如果使用了引脚控制器后端,则GPIO控制器或硬件说明需要提供"GPIO范围",以将GPIO线偏移量映射到引脚控制器上的引脚号,以便它们可以正确地相互引用。

支持去抖动的GPIO线

防弹跳是一种设置为用于指示其已连接到机械开关或按钮或可能会弹跳的类似物的引脚的配置。跳动意味着出于机械原因,会在很短的时间间隔内将线快速拉高/拉低。除非将行去抖动,否则可能导致值不稳定或irqs反复触发。

实际中的反跳操作涉及在线路上发生某些情况时设置一个计时器,稍等片刻,然后再次对该线路进行采样,以便查看其是否仍具有相同的值(低或高)。聪明的状态机也可以重复此过程,等待线路变得稳定。无论哪种情况,它都会设置一定数量的反跳时间,如果该时间不可配置,则设置为"on/off"。

提供IRQ的GPIO驱动程序

通常,GPIO驱动程序(GPIO芯片)还提供中断,通常是从父中断控制器级联而来的,在某些特殊情况下,GPIO逻辑与SoC的主中断控制器融为一体。

GPIO模块的IRQ部分使用 irq_chip 和头文件  来实现。因此,该组合驱动程序同时利用了两个子系统:gpio和irq。

任何IRQ使用者都可以从任何irqchip请求IRQ,这是合法的,即使它是GPIO + IRQ驱动程序的组合。基本前提是gpio_chip和irq_chip是正交的,并且提供彼此独立的服务。

gpiod_to_irq() 只是一种方便的功能,可以确定某个GPIO线的IRQ,在使用IRQ之前,不应依赖于已调用该IRQ。

始终做好硬件准备,并准备好在GPIO和irq_chip API的各个回调中采取措施。不要依靠gpiod_to_irq()先被称为。

我们可以将GPIO irqchips分为两大类:

级联中断芯片:这意味着GPIO芯片具有一条公共中断输出线,该中断输出线由该芯片上的任何使能的GPIO线触发。然后,中断输出线将被路由到上一级的父中断控制器,在最简单的情况下是系统的主中断控制器。这是由一个irqchip建模的,该芯片将检查GPIO控制器内的位以找出触发它的线路。驱动程序的irqchip部分需要检查寄存器以解决此问题,并且可能还需要通过清除一些位(有时隐式地仅通过读取状态寄存器)来确认它正在处理中断,并且通常需要将其置1。诸如边缘灵敏度(上升沿或下降沿,或高/低电平中断)之类的配置。
分层中断芯片:这意味着每条GPIO线都有一条专用的irq线,用于上一级父中断控制器。无需查询GPIO硬件即可确定触发了哪条线路,但仍然有必要确认中断并设置诸如边沿敏感度的配置。
实时注意事项:实时兼容的GPIO驱动程序不应将spinlock_t或任何可睡眠的API(例如PM运行时)用作irqchip实现的一部分。

spinlock_t应该替换为raw_spinlock_t。
如果必须使用可睡眠的API,则可以通过.irq_bus_lock()和.irq_bus_unlock()回调来完成,因为这是irqchip上唯一的慢路径回调。根据需要创建回调。

GPIO描述符使用者接口

本文档介绍了GPIO框架的使用者接口。请注意,它描述了新的基于描述符的接口。有关不推荐使用的基于整数的GPIO接口的说明,请参考 gpio-legacy.txt。

GPIO使用者指南

没有标准GPIO调用就无法工作的驱动程序应具有Kconfig条目,这些条目取决于GPIOLIB或选择GPIOLIB。通过包含以下文件,可以提供允许驱动程序获取和使用GPIO的功能:
    #include 
在禁用GPIOLIB的情况下,头文件中的所有功能都有静态内联存根。调用这些存根时,它们将发出警告。这些存根用于两个用例:

使用COMPILE_TEST等简单的编译覆盖-当前平台不启用或选择GPIOLIB都没关系,因为我们无论如何都不会执行系统。
真正的可选GPIOLIB支持-驱动程序在某些系统的某些编译时配置中并没有真正使用GPIO,而在其他编译时配置中使用了GPIOLIB。在这种情况下,用户必须确保不要调用这些功能,否则会遇到可能会被视为令人生畏的控制台警告。
与基于描述符的GPIO接口一起使用的所有功能均带有前缀gpiod_。该gpio_前缀用于旧版接口。内核中没有其他函数可以使用这些前缀。强烈建议不要使用旧版功能,新代码应仅使用和描述符。

获取和配置GPIO

使用基于描述符的接口,可以通过不透明,不可伪造的处理程序来识别GPIO,必须通过调用其中一个gpiod_get()函数来获得该处理程序 。像许多其他内核子系统一样,gpiod_get()采用将使用GPIO的设备以及请求的GPIO应该实现的功能:
struct gpio_desc *gpiod_get(struct device *dev, const char *con_id,
                            enum gpiod_flags flags)
如果通过一起使用多个GPIO来实现功能(例如,显示数字的简单LED设备),则可以指定附加的index参数:
struct gpio_desc *gpiod_get_index(struct device *dev,
                                  const char *con_id, unsigned int idx,
                                  enum gpiod_flags flags)

有关DeviceTree案例中的con_id参数的详细说明,请参见Documentation/driver-api/gpio/board.rst

flags参数用于为GPIO指定方向和初始值。值可以是:

  • GPIOD_ASIS或0表示完全不初始化GPIO。稍后必须使用专用功能之一设置方向。
  • GPIOD_IN将GPIO初始化为输入。
  • GPIOD_OUT_LOW以将GPIO初始化为值为0的输出。
  • GPIOD_OUT_HIGH将GPIO初始化为值为1的输出。
  • GPIOD_OUT_LOW_OPEN_DRAIN与GPIOD_OUT_LOW相同,但也强制将线路与开漏电连接使用。
  • GPIOD_OUT_HIGH_OPEN_DRAIN与GPIOD_OUT_HIGH相同,但也将线路强制与漏极开路一起使用。
    最后两个标志用于强制使用漏极开路的用例,例如I2C:如果该行尚未在映射中配置为漏极开路(请参阅board.txt),则无论如何都会强制使用漏极开路,并会发出警告。打印出需要更新电路板配置以匹配用例的情况。

这两个函数均返回有效的GPIO描述符或可通过IS_ERR()检查的错误代码(它们永远不会返回NULL指针)。-ENOENT仅当且仅当未将GPIO分配给设备/功能/索引三元组时,如果分配了GPIO但尝试获取它时发生错误,才会使用其他错误代码。这对于区分错误和可选的GPIO参数没有GPIO很有用。对于通用模式,其中GPIO是可选的,可以使用gpiod_get_optional()和 gpiod_get_index_optional()功能。如果未将GPIO分配给请求的函数,这些函数将返回NULL而不是-ENOENT:

struct gpio_desc *gpiod_get_optional(struct device *dev,
                                    const char *con_id,
                                    enum gpiod_flags flags)

struct gpio_desc *gpiod_get_index_optional(struct device *dev,
                                        const char *con_id,
                                        unsigned int index,
                                        enum gpiod_flags flags)

请注意,与gpiolib API的其余部分不同,gpio_get * _optional()函数(及其托管变体)在禁用gpiolib支持时也会返回NULL。这对驱动程序作者很有帮助,因为他们不需要特殊情况的-ENOSYS返回代码。但是,系统集成商应小心在需要它的系统上启用gpiolib。

对于使用多个GPIO的功能,所有这些都可以通过一次调用获得:

    struct gpio_descs *gpiod_get_array(struct device *dev,
                                    const char *con_id,
                                    enum gpiod_flags flags)

此函数返回包含描述符数组的结构gpio_descs。它还包含一个指向gpiolib私有结构的指针,如果将其传递回get/set数组函数,则可以加快I/O处理速度:

    struct gpio_descs {
            struct gpio_array *info;
            unsigned int ndescs;
            struct gpio_desc *desc[];
    }

如果未将GPIO分配给请求的函数,则以下函数返回NULL而不是-ENOENT:

    struct gpio_descs *gpiod_get_array_optional(struct device *dev,
                                                const char *con_id,
                                                enum gpiod_flags flags)

还定义了这些功能的设备管理变体:

    struct gpio_desc *devm_gpiod_get(struct device *dev, const char *con_id,
                                    enum gpiod_flags flags)

    struct gpio_desc *devm_gpiod_get_index(struct device *dev,
                                        const char *con_id,
                                        unsigned int idx,
                                        enum gpiod_flags flags)

    struct gpio_desc *devm_gpiod_get_optional(struct device *dev,
                                            const char *con_id,
                                            enum gpiod_flags flags)

    struct gpio_desc *devm_gpiod_get_index_optional(struct device *dev,
                                                    const char *con_id,
                                                    unsigned int index,
                                                    enum gpiod_flags flags)

    struct gpio_descs *devm_gpiod_get_array(struct device *dev,
                                            const char *con_id,
                                            enum gpiod_flags flags)

    struct gpio_descs *devm_gpiod_get_array_optional(struct device *dev,
                                                    const char *con_id,
                                                    enum gpiod_flags flags)

可以使用以下gpiod_put()函数来处理GPIO描述符:

void gpiod_put(struct gpio_desc *desc)
对于GPIO阵列,可以使用以下功能:

void gpiod_put_array(struct gpio_descs *descs)
严格禁止在调用这些函数后使用描述符。也不允许gpiod_put()从使用所获取的数组中单独释放描述符(使用)gpiod_get_array()。

毫无疑问,设备管理的变体是:

void devm_gpiod_put(struct device *dev, struct gpio_desc *desc)

void devm_gpiod_put_array(struct device *dev, struct gpio_descs *descs)

使用GPIO

设定方向

驱动程序必须使用GPIO做的第一件事就是设置其方向。如果没有给gpiod_get *()提供方向设置标志,则可以通过调用gpiod_direction _ *()函数之一来完成:

    int gpiod_direction_input(struct gpio_desc *desc)
    int gpiod_direction_output(struct gpio_desc *desc, int value)

成功的返回值为零,否则为负errno。应该进行检查,因为get/set调用不会返回错误,并且可能会配置错误。通常,您应该从任务上下文发出这些调用。但是,对于自旋锁安全GPIO,可以在启用任务之前使用它们,这是早期电路板设置的一部分。

对于输出GPIO,提供的值将成为初始输出值。这有助于避免系统启动期间的信号毛刺。

驱动程序还可以查询GPIO的当前方向:

    int gpiod_get_direction(const struct gpio_desc *desc)

此函数返回0表示输出,返回1表示输入,或者在出现错误的情况下返回错误代码。

请注意,GPIO没有默认方向。因此,使用GPIO而不先设置其方向是非法的,并且会导致不确定的行为!

自旋锁安全GPIO访问

可以通过存储器读/写指令访问大多数GPIO控制器。这些不需要休眠,可以从硬(非线程)IRQ处理程序和类似上下文中安全地完成操作。

使用以下调用从原子上下文访问GPIO:

    int gpiod_get_value(const struct gpio_desc *desc);
    void gpiod_set_value(struct gpio_desc *desc, int value);

值为布尔值,低表示零,高表示非零。读取输出引脚的值时,返回的值应为在引脚上看到的值。由于包括漏极开路信令和输出延迟在内的问题,该值并不总是与指定的输出值匹配。

get/set调用不会返回错误,因为应该早些时候从gpiod_direction _ *()报告了"无效的GPIO"。但是,请注意,并非所有平台都可以读取输出引脚的值。那些不能总是返回零。同样,将这些调用用于无法不睡眠就无法安全访问的GPIO(请参见下文)也是错误的。

可能会休眠的GPIO访问
必须使用基于消息的总线(例如I2C或SPI)来访问某些GPIO控制器。读取或写入这些GPIO值的命令需要等待到达队列的头部才能传输命令并获得其响应。这需要睡眠,这不能从IRQ处理程序内部完成。

支持此类型GPIO的平台通过从此调用返回非零值来将它们与其他GPIO区别开:

    int gpiod_cansleep(const struct gpio_desc *desc)
    为了访问此类GPIO,定义了一组不同的访问器:
    int gpiod_get_value_cansleep(const struct gpio_desc *desc)
    void gpiod_set_value_cansleep(struct gpio_desc *desc, int value)

访问此类GPIO需要一个可能处于休眠状态的上下文,例如线程IRQ处理程序,并且必须使用那些访问器代替无cansleep()名称后缀的自旋锁安全访问器。

除了这些访问器可能会休眠并且可以在无法从hardIRQ处理程序访问的GPIO上工作的事实之外,这些调用的作用与自旋锁安全调用相同。

低电平有效和开漏语义

由于使用者不必关心物理线路级别,因此所有gpiod_set_value_xxx()或gpiod_set_array_value_xxx()函数均使用逻辑值进行操作。这样,他们就考虑到了有源低端属性。这意味着他们检查GPIO是否配置为低电平有效,如果是,则在驱动物理线路电平之前对传递的值进行操作。

同样适用于漏极开路或开源输出线:那些不会主动将其输出驱动为高(漏极开路)或低电平(开源)的设备,它们只是将其输出切换为高阻抗值。使用者不需要关心。(有关详细信息,请阅读driver.txt中的漏极开路。)

这样,所有gpiod_set_(array)_value_xxx()函数都将参数"value"解释为"asserted"(“1”)或"de-asserted"(“0”)。物理线路电平将被相应地驱动。

例如,如果设置了专用GPIO的低电平有效属性,并且gpiod_set_(array)_value_xxx()传递了"有效"(“1”),则物理线路电平将被驱动为低电平。

总结一下:

函数 (example) 逻辑属性 物理属性
gpiod_set_raw_value(desc, 0); don’t care low
gpiod_set_raw_value(desc, 1); don’t care high
gpiod_set_value(desc, 0); default (active high) low
gpiod_set_value(desc, 1); default (active high) high
gpiod_set_value(desc, 0); active low high
gpiod_set_value(desc, 1); active low low
gpiod_set_value(desc, 0); open drain low
gpiod_set_value(desc, 1); open drain high impedance
gpiod_set_value(desc, 0); open source high impedance
gpiod_set_value(desc, 1); open source high

可以使用set_raw/get_raw函数覆盖这些语义,但是应尽可能避免使用它,尤其是与系统无关的驱动程序,它们不需要关心实际的物理线路级别,而不必担心逻辑值。

访问原始GPIO值

使用者需要管理GPIO线的逻辑状态,即,无论设备和GPIO线之间是什么,其设备实际将接收的值。

以下调用集忽略GPIO的低电平有效或开漏属性,并处理原始行值:

    int gpiod_get_raw_value(const struct gpio_desc *desc)
    void gpiod_set_raw_value(struct gpio_desc *desc, int value)
    int gpiod_get_raw_value_cansleep(const struct gpio_desc *desc)
    void gpiod_set_raw_value_cansleep(struct gpio_desc *desc, int value)
    int gpiod_direction_output_raw(struct gpio_desc *desc, int value)

GPIO的低电平有效状态也可以使用以下调用查询:

int gpiod_is_active_low(const struct gpio_desc *desc)
注意这些功能只能适度使用。驾驶员不必关心物理线路水平或漏极开路语义。

通过一个函数调用访问多个GPIO

以下函数获取或设置GPIO数组的值:

int gpiod_get_array_value(unsigned int array_size,
                          struct gpio_desc **desc_array,
                          struct gpio_array *array_info,
                          unsigned long *value_bitmap);
int gpiod_get_raw_array_value(unsigned int array_size,
                              struct gpio_desc **desc_array,
                              struct gpio_array *array_info,
                              unsigned long *value_bitmap);
int gpiod_get_array_value_cansleep(unsigned int array_size,
                                   struct gpio_desc **desc_array,
                                   struct gpio_array *array_info,
                                   unsigned long *value_bitmap);
int gpiod_get_raw_array_value_cansleep(unsigned int array_size,
                                   struct gpio_desc **desc_array,
                                   struct gpio_array *array_info,
                                   unsigned long *value_bitmap);

int gpiod_set_array_value(unsigned int array_size,
                          struct gpio_desc **desc_array,
                          struct gpio_array *array_info,
                          unsigned long *value_bitmap)
int gpiod_set_raw_array_value(unsigned int array_size,
                              struct gpio_desc **desc_array,
                              struct gpio_array *array_info,
                              unsigned long *value_bitmap)
int gpiod_set_array_value_cansleep(unsigned int array_size,
                                   struct gpio_desc **desc_array,
                                   struct gpio_array *array_info,
                                   unsigned long *value_bitmap)
int gpiod_set_raw_array_value_cansleep(unsigned int array_size,
                                       struct gpio_desc **desc_array,
                                       struct gpio_array *array_info,
                                       unsigned long *value_bitmap)

该阵列可以是任意一组GPIO。如果相应的芯片驱动程序支持,则这些功能将尝试同时访问属于同一存储体或芯片的GPIO。在那种情况下,可以预期性能会大大提高。如果无法同时访问,则将按顺序访问GPIO。

这些函数采用三个参数:

  • array_size-数组元素的数量
  • desc_array-GPIO描述符的数组
  • array_info-从以下位置获得的可选信息 gpiod_get_array()
  • value_bitmap-一个位图,用于存储GPIO的值(获取)或值的位图以分配给GPIO(set)
    描述符数组可以使用gpiod_get_array()函数或其变体之一获得。如果该函数返回的描述符组与所需的GPIO组匹配,则可以使用以下命令返回的struct gpio_descs来访问这些GPIO gpiod_get_array():
    struct gpio_descs *my_gpio_descs = gpiod_get_array(...);
    gpiod_set_array_value(my_gpio_descs->ndescs, my_gpio_descs->desc,
                        my_gpio_descs->info, my_gpio_value_bitmap);

也可以访问描述符的完全任意数组。该描述符可以使用的任何组合来获得gpiod_get()和 gpiod_get_array()。之后,必须先手动设置描述符数组,然后才能将其传递给上述功能之一。在这种情况下,应将array_info设置为NULL。

请注意,为了获得最佳性能,属于同一芯片的GPIO应该在描述符数组中是连续的。

如果描述符的数组索引与单个芯片的硬件引脚号匹配,则可以获得更好的性能。如果传递给get/set数组函数的数组与从get/set数组函数获得的数组匹配,gpiod_get_array()并且还传递了与该数组关联的array_info,则该函数可能会采用快速位图处理路径,将value_bitmap参数直接传递给相应的.get/set_multiple()芯片的回调。这样就可以将GPIO组用作数据I/O端口,而不会造成很多性能损失。

如果gpiod_get_array_value()成功及其返回值,返回值为0或错误返回为负。注意与之间的差异gpiod_get_value(),如果成功传递GPIO值,则返回0或1。使用数组函数时,GPIO值存储在value_array中,而不是作为返回值传递回去。

GPIO映射到IRQ

GPIO线经常可以用作IRQ。您可以使用以下调用获取与给定GPIO对应的IRQ号:

    int gpiod_to_irq(const struct gpio_desc *desc)

如果无法完成映射,它将返回一个IRQ号或一个错误的errno代码(最有可能是因为该特定的GPIO无法用作IRQ)。使用未设置为输入的GPIOgpiod_direction_input()或使用最初并非来自的IRQ编号,这是一个未经检验的错误 gpiod_to_irq()。gpiod_to_irq()不允许休眠。

从返回的非错误值gpiod_to_irq()可以传递给request_irq()或 free_irq()。它们通常会通过特定于板的初始化代码存储在平台设备的IRQ资源中。请注意,IRQ触发选项是IRQ接口的一部分,例如IRQF_TRIGGER_FALLING,以及系统唤醒功能

GPIO和ACPI

在ACPI系统上,GPIO由设备的_CRS配置对象列出的GpioIo()/GpioInt()资源描述。这些资源不提供GPIO的连接ID(名称),因此有必要为此使用其他机制。

符合ACPI 5.1或更高版本的系统可以提供_DSD配置对象,该对象可以用于为_CRS中的GpioIo()/GpioInt()资源描述的特定GPIO提供连接ID。如果是这种情况,它将由GPIO子系统自动处理。但是,如果不存在_DSD,则设备驱动程序需要提供GpioIo()/GpioInt()资源与GPIO连接ID之间的映射。

有关详细信息,请参阅文档/firmware-guide/acpi/gpio-properties.rst

与旧版GPIO子系统进行交互

许多内核子系统仍使用传统的基于整数的接口处理GPIO。尽管强烈建议您将它们升级到基于描述符的更安全的API,但是以下两个函数使您可以将GPIO描述符转换为GPIO整数名称空间,反之亦然:

    int desc_to_gpio(const struct gpio_desc *desc)
    struct gpio_desc *gpio_to_desc(unsigned gpio)

desc_to_gpio()只要尚未释放GPIO描述符,就可以安全地使用返回的GPIO编号。都是一样,gpio_to_desc()必须正确获取传递给的GPIO编号 ,并且只有在释放GPIO编号之后才能使用返回的GPIO描述符。

禁止释放一个API和另一个API所获得的GPIO,这是未经检查的错误。

GPIO映射

本文档说明了如何将GPIO分配给给定的设备和功能。

请注意,它仅适用于新的基于描述符的接口。有关不推荐使用的基于整数的GPIO接口的说明,请参阅gpio-legacy.txt(实际上,旧接口无法实现真正​​的映射;您只是从某个地方获取一个整数并请求相应的GPIO)。

所有平台都可以启用GPIO库,但是如果平台严格要求存在GPIO功能,则需要从其Kconfig中选择GPIOLIB。然后,GPIO的映射方式取决于平台用来描述其硬件布局的内容。当前,可以通过设备树,ACPI和平台数据来定义映射。

设备树

GPIO可以轻松映射到设备树中的设备和功能。确切的实现方法取决于提供GPIO的GPIO控制器,请参阅控制器的设备树绑定。

GPIO映射在用户设备的节点中定义为名为 -gpios的属性,其中是驱动程序将通过请求的功能gpiod_get()。例如:

    foo_device {
            compatible = "acme,foo";
            ...
            led-gpios = <&gpio 15 GPIO_ACTIVE_HIGH>, /* red */
                        <&gpio 16 GPIO_ACTIVE_HIGH>, /* green */
                        <&gpio 17 GPIO_ACTIVE_HIGH>; /* blue */

            power-gpios = <&gpio 1 GPIO_ACTIVE_LOW>;
    };

名为 -gpio的属性也被认为是有效的,并且旧的绑定也使用它,但是出于兼容性原因而受支持,并且由于不推荐使用,因此不应将其用于新的绑定。

该属性将使GPIO 15、16和17在"led"功能下对驱动程序可用,而GPIO 1作为"电源" GPIO:

struct gpio_desc *red, *green, *blue, *power;

red = gpiod_get_index(dev, "led", 0, GPIOD_OUT_HIGH);
green = gpiod_get_index(dev, "led", 1, GPIOD_OUT_HIGH);
blue = gpiod_get_index(dev, "led", 2, GPIOD_OUT_HIGH);

power = gpiod_get(dev, "power", GPIOD_OUT_HIGH);

led GPIO将为高电平有效,而电源GPIO将为低电平有效(即gpiod_is_active_low(power)为true)。

gpiod_get()函数的第二个参数con_id字符串必须是设备树中使用的GPIO后缀(“gpios"或"gpio”,由gpiod函数在内部自动查找)的-前缀。在上面的"led-gpios"示例中,使用不带"-"的前缀作为con_id参数:“led”。

在内部,GPIO子系统使用在con_id中传递的字符串作为GPIO后缀(“gpios"或"gpio”)的前缀,以获取结果字符串()。snprintf(… “%s-%s”, con_id, gpio_suffixes[])

ACPI

ACPI还以类似于DT的方式支持GPIO的功能名称。可以使用ACPI 5.1中引入的_DSD(设备专用数据)将上述DT示例转换为等效的ACPI描述:

    Device (FOO) {
            Name (_CRS, ResourceTemplate () {
                    GpioIo (Exclusive, ..., IoRestrictionOutputOnly,
                            "\\_SB.GPI0") {15} //red
                    GpioIo (Exclusive, ..., IoRestrictionOutputOnly,
                            "\\_SB.GPI0") {16} //green
                    GpioIo (Exclusive, ..., IoRestrictionOutputOnly,
                            "\\_SB.GPI0") {17} //blue
                    GpioIo (Exclusive, ..., IoRestrictionOutputOnly,
                            "\\_SB.GPI0") {1} //power
            })

            Name (_DSD, Package () {
                    ToUUID("daffd814-6eba-4d8c-8a91-bc9bbf4aa301"),
                    Package () {
                            Package () {
                                    "led-gpios",
                                    Package () {
                                            ^FOO, 0, 0, 1,
                                            ^FOO, 1, 0, 1,
                                            ^FOO, 2, 0, 1,
                                    }
                            },
                            Package () {
                                    "power-gpios",
                                    Package () {^FOO, 3, 0, 0},
                            },
                    }
            })
    }

有关ACPI GPIO绑定的更多信息,请参见Documentation/firmware-guide/acpi/gpio-properties.rst。

平台数据

最后,可以使用平台数据将GPIO绑定到设备和功能。希望这样做的板文件需要包含以下标头:

    #include 

GPIO通过查找表进行映射,其中包含gpiod_lookup结构的实例。定义了两个宏来帮助声明此类映射:

    GPIO_LOOKUP(chip_label, chip_hwnum, con_id, flags)
    GPIO_LOOKUP_IDX(chip_label, chip_hwnum, con_id, idx, flags)
chip_label
    是提供GPIO的gpiod_chip实例的标签
chip_hwnum
    是芯片内GPIO的硬件编号
    从设备的角度来看,con_id是GPIO函数的名称。它
    可以为NULL,在这种情况下它将与任何函数匹配。
idx
    是该函数中GPIO的索引。
flags
    定义标志以指定以下属性:
    GPIO_ACTIVE_HIGH-GPIO线为高电平有效
    GPIO_ACTIVE_LOW-GPIO线为低电平有效
    GPIO_OPEN_DRAIN-GPIO线设置为漏极开路
    GPIO_OPEN_SOURCE-GPIO线设置为开源
    GPIO_PERSISTENT-GPIO线在
    暂停/恢复并保持其价值
    GPIO_TRANSITORY-GPIO线是暂时的,可能会松动
    暂停/恢复期间的电气状态
    将来,这些标志可能会扩展为支持更多属性。

请注意,GPIO_LOOKUP()只是GPIO_LOOKUP_IDX()的快捷方式,其中idx = 0。

然后可以按以下方式定义查找表,并在其中定义一个空条目作为结尾。该表的’dev_id’字段是将使用这些GPIO的设备的标识符。它可以为NULL,在这种情况下,它将gpiod_get()与使用NULL设备的调用匹配。

    struct gpiod_lookup_table gpios_table = {
            .dev_id = "foo.0",
            .table = {
                    GPIO_LOOKUP_IDX("gpio.0", 15, "led", 0, GPIO_ACTIVE_HIGH),
                    GPIO_LOOKUP_IDX("gpio.0", 16, "led", 1, GPIO_ACTIVE_HIGH),
                    GPIO_LOOKUP_IDX("gpio.0", 17, "led", 2, GPIO_ACTIVE_HIGH),
                    GPIO_LOOKUP("gpio.0", 1, "power", GPIO_ACTIVE_LOW),
                    { },
            },
    };

该表可以通过以下板代码添加:

    gpiod_add_lookup_table(&gpios_table);
    然后,控制"foo.0"的驱动程序将能够按以下方式获取其GPIO:
    struct gpio_desc *red, *green, *blue, *power;

    red = gpiod_get_index(dev, "led", 0, GPIOD_OUT_HIGH);
    green = gpiod_get_index(dev, "led", 1, GPIOD_OUT_HIGH);
    blue = gpiod_get_index(dev, "led", 2, GPIOD_OUT_HIGH);

    power = gpiod_get(dev, "power", GPIOD_OUT_HIGH);

由于"led" GPIO被映射为高电平有效,因此本示例将其信号切换为1,即启用LED。对于映射为低电平有效的"电源" GPIO,此代码后其实际信号将为0。与传统的整数GPIO接口相反,低电平有效属性是在映射期间处理的,因此对GPIO使用者透明。

一组功能(例如gpiod_set_value())可与新的面向描述符的接口一起使用。

使用平台数据的电路板也可以通过定义GPIO占用表来占用GPIO线。

    struct gpiod_hog gpio_hog_table[] = {
            GPIO_HOG("gpio.0", 10, "foo", GPIO_ACTIVE_LOW, GPIOD_OUT_HIGH),
            { }
    };
    并且可以将该表添加到板代码中,如下所示:
    gpiod_add_hogs(gpio_hog_table);

一旦创建了gpiochip,或者在注册了hog表的情况下(如果是较早创建的芯片),该行将被暂停。

引脚排列
除了一个接一个地请求属于某个功能的引脚外,设备还可以请求一个分配给该功能的引脚阵列。这些引脚映射到设备的方式确定阵列是否符合快速位图处理的条件。如果是,则在调用者与GPIO芯片的相应.get/set_multiple()回调之间直接在get/set数组函数上传递位图。

为了有资格进行快速位图处理,该数组必须满足以下要求:

阵列成员0的引脚硬件编号也必须为0,
与成员0属于同一芯片的连续阵列成员的pin硬件编号也必须与其阵列索引匹配。
否则,将不使用快速位图处理路径,以避免属于同一芯片但不在硬件顺序上的连续引脚被单独处理。

如果该阵列适用于快速位图处理路径,则与输入成员0属于不同芯片的引脚以及索引与其硬件引脚号不同的引脚都将从快速路径中排除,包括输入和输出。此外,快速位图输出处理中不包括漏极开路和源极开路引脚。

使用GPIO的子系统驱动程序

请注意,存在标准的内核驱动程序来执行常见的GPIO任务,并将为该作业提供正确的内核和用户空间API/ABI,并且这些驱动程序可以使用诸如设备树或ACPI之类的硬件描述轻松地与其他内核子系统互连

  • leds-gpio:驱动程序/leds/leds-gpio.c将处理连接到GPIO线的LED,为您提供LED sysfs接口
  • ledtrig-gpio:驱动程序/leds/trigger/ledtrig-gpio.c将提供LED触发,即,LED将响应于GPIO线变高或变低而开启/关闭(并且该LED可能反过来使用leds- gpio如上)。
  • gpio-keys:当GPIO行可以响应按键按下而产生中断时,将使用drivers/input/keyboard/gpio_keys.c。还支持防抖动。
  • gpio-keys-polled:当GPIO线无法产生中断时使用driver/input/keyboard/gpio_keys_polled.c,因此需要由计时器定期对其进行轮询。
  • gpio_mouse:drivers/input/mouse/gpio_mouse.c用于通过仅使用GPIO而没有鼠标端口的方式为鼠标提供多达三个按钮。您可以切断鼠标电缆并将其连接到GPIO线,或将鼠标连接器焊接到这些线,以获得这种类型的更永久的解决方案。
  • gpio-beeper:drivers/input/misc/gpio-beeper.c用于从连接到GPIO线的外部扬声器发出蜂鸣声。
  • extcon-gpio:当您需要读取外部连接器状态(例如,音频驱动器的耳机线或HDMI连接器)时,可使用drivers/extcon/extcon-gpio.c。与GPIO相比,它将提供更好的用户空间sysfs接口。
  • restart-gpio:drivers/power/reset/gpio-restart.c用于通过拉GPIO线来重启/重启系统,并将注册一个重启处理程序,以便用户空间可以发出正确的系统调用以重启系统。
  • poweroff-gpio:drivers/power/reset/gpio-poweroff.c用于通过拉动GPIO线来关闭系统电源,并将注册pm_power_off()回调,以便用户空间可以发出正确的系统调用以关闭系统电源。
  • gpio-gate-clock:drivers/clk/clk-gpio.c用于控制使用GPIO并与时钟子系统集成的门控时钟(关闭/打开)。
  • i2c-gpio:drivers/i2c/busses/i2c-gpio.c用于通过模拟(bitbang)两条GPIO线来驱动I2C总线(两条线,SDA和SCL线)。它将像其他任何I2C总线一样出现在系统中,并可以像其他I2C总线驱动程序一样连接总线上I2C设备的驱动程序。
  • spi_gpio:drivers/spi/spi-gpio.c用于通过GPIO模拟(bitbang)来驱动SPI总线(可变数量的导线,至少为SCK,以及可选的MISO,MOSI和芯片选择线)。它会像系统上的任何其他SPI总线一样出现,并可以像其他任何SPI总线驱动程序一样连接总线上SPI设备的驱动程序。例如,然后可以使用MMC/SD卡子系统中的mmc_spi主机将任何MMC/SD卡连接到此SPI。
  • w1-gpio:drivers/w1/masters/w1-gpio.c用于通过GPIO线驱动单线总线,与W1子系统集成并像其他任何W1设备一样处理总线上的设备。
  • gpio-fan:drivers/hwmon/gpio-fan.c用于控制用于冷却系统的风扇,该风扇连接到GPIO线(以及可选的GPIO警报线),提供了所有正确的内核和sysfs接口您的系统不会过热。
  • gpio-regulator:drivers/regulator/gpio-regulator.c用于控制稳压器,方法是拉一条GPIO线,与稳压器子系统集成在一起,并为您提供所有正确的接口,从而提供一定的电压。
  • gpio-wdt:drivers/watchdog/gpio_wdt.c用于提供看门狗计时器,该计时器将通过从1到0到1的切换来周期性地" ping"与GPIO线相连的硬件。如果该硬件没有定期收到其" ping"信号,它将重置系统。
  • gpio-nand:drivers/mtd/nand/raw/gpio.c用于将NAND闪存芯片连接到一组简单的GPIO线:RDY,NCE,ALE,CLE,NWP。它与NAND闪存MTD子系统进行交互,并像其他任何NAND驱动硬件一样提供芯片访问和分区解析。
    ps2-gpio:drivers/input/serio/ps2-gpio.c用于通过位撞两个GPIO线来驱动PS/2(IBM)serio总线,数据和时钟线。它会像任何其他Serio总线一样出现在系统中,并可以连接例如键盘和其他基于PS/2协议的设备的驱动程序。
  • cec-gpio:driver/media/platform/cec-gpio /用于仅使用GPIO与CEC消费电子控制总线进行交互。它用于与HDMI总线上的设备进行通信。
    除此之外,在诸如MMC/SD之类的子系统中还具有特殊的GPIO驱动程序,用于读取卡检测和写保护GPIO线,而在TTY串行子系统中,还具有特殊的GPIO驱动器,以通过使用两条GPIO线来模拟MCTRL(调制解调器控制)信号CTS/RTS。尽管地址总线通​​常直接连接到闪存,但MTD NOR闪存也具有用于额外GPIO线的附加组件。

使用它们而不是直接从用户空间与GPIO通讯;它们比您的用户空间代码更好地与内核框架集成。不用说,仅使用适当的内核驱动程序,就可以通过提供现成的组件来简化和加快嵌入式黑客的攻击。

旧版GPIO接口

本文概述了Linux上的GPIO访问约定。

这些调用使用gpio_ *命名前缀。没有其他调用可以使用该前缀或相关的__gpio_ *前缀。

什么是GPIO?

“通用输入/输出”(GPIO)是一种灵活的软件控制的数字信号。它们由多种芯片提供,并且对于使用嵌入式和自定义硬件的Linux开发人员来说是熟悉的。每个GPIO代表一个连接到特定引脚或球栅阵列(BGA)封装上的"球"的位。电路板原理图显示了哪些外部硬件连接到了哪些GPIO。可以通用地编写驱动程序,以便电路板设置代码将此类引脚配置数据传递给驱动程序。

片上系统(SOC)处理器严重依赖GPIO。在某些情况下,每个非专用引脚都可以配置为GPIO。大多数芯片至少有几十个。可编程逻辑器件(如FPGA)可以轻松提供GPIO;诸如电源管理器之类的多功能芯片和音频编解码器通常具有一些这样的引脚,以帮助解决SOC上的引脚稀缺问题。还有" GPIO扩展器"芯片可使用I2C或SPI串行总线进行连接。大多数PC南桥都有几十个具有GPIO功能的引脚(只有BIOS固件知道它们的用法)。

GPIO的确切功能因系统而异。常用选项:

  • 输出值是可写的(高= 1,低= 0)。一些芯片还具有关于如何驱动该值的选项,因此,例如,仅可以驱动一个值…支持"线或"和类似的方案以用于另一值(特别是"开漏"信令)。
  • 输入值同样可读(1、0)。一些芯片支持配置为"输出"的引脚的回读,这在"线或"情况下非常有用(以支持双向信令)。GPIO控制器有时可能具有软件控制的输入去毛刺/去抖动逻辑。
  • 输入通常可以用作IRQ信号,通常是边沿触发,但有时是电平触发。此类IRQ可以配置为系统唤醒事件,以将系统从低功耗状态唤醒。
  • 通常,根据不同产品板的需要,可以将GPIO配置为输入或输出。也存在单向的。
  • 持有自旋锁时可以访问大多数GPIO,但通常无法通过串行总线访问。某些系统支持两种类型。
    在给定的板上,每个GPIO都用于一个特定目的,例如监视MMC/SD卡的插入/拔出,检测卡的写保护状态,驱动LED,配置收发器,对串行总线进行位冲击,戳硬件看门狗,感测开关以及很快。

GPIO约定

请注意,这被称为"惯例",因为您不需要以这种方式进行操作,如果不这样做,则不会构成犯罪。在某些情况下,可移植性不是主要问题;GPIO通常用于特定于电路板的胶合逻辑,甚至在不同的电路板修订版之间可能会发生变化,并且永远无法在接线方式不同的电路板上使用。只有最小公分母功能可以非常便携。其他功能是特定于平台的,这对于胶合逻辑可能至关重要。

另外,这不需要任何实现框架,仅需要一个接口。一个平台可以将其实现为访问芯片寄存器的简单内联函数;另一种可能是通过委派用于几种非常不同种类的GPIO控制器的抽象来实现的。(有一些可选的代码支持这种实现策略,如本文档后面所述,但是充当GPIO接口客户端的驱动程序不必关心它的实现方式。)

就是说,如果约定在其平台上受支持,则驱动程序应在可能的情况下使用它。如果严格要求GPIO功能,则平台必须选择GPIOLIB。没有标准GPIO调用就无法工作的驱动程序应具有Kconfig条目,该条目取决于GPIOLIB。当驱动程序使用包含文件时,GPIO调用可以作为"真实代码"或作为经过优化的存根使用:

#include 

如果您遵守此约定,那么其他开发人员将更容易看到您的代码在做什么,并帮助维护它。

请注意,这些操作包括需要使用它们的平台上的I/O屏障;驱动程序不需要显式添加它们。

识别GPIO

GPIO由范围为0…MAX_INT的无符号整数标识。这会将"负"数字保留用于其他目的,例如将信号标记为"此板不可用"或指示故障。不接触底层硬件的代码会将这些整数视为不透明的cookie。

平台定义它们如何使用这些整数,通常定义GPIO线的#define符号,以使特定于电路板的设置代码直接对应于相关原理图。相反,驱动程序仅应使用从该设置代码传递给它们的GPIO编号,并使用platform_data来保存板级特定的引脚配置数据(以及他们所需的其他板级特定数据)。这就避免了可移植性问题。

因此,例如,一个平台对GPIO使用数字32-159。另一个则使用0…63表示一组GPIO控制器,使用64-79表示另一种类型的GPIO控制器,使用80-95表示一个FPGA。这些数字不必是连续的。这些平台中的任何一个都可以使用数字2000-2063来识别一组I2C GPIO扩展器中的GPIO。

如果要使用无效的GPIO编号初始化结构,请使用一些负数(例如" -EINVAL");那将永远是无效的。要测试这种结构中的此类数字是否可以引用GPIO,可以使用以下谓词:

int gpio_is_valid(int number);
无效的数字将被可能会请求或释放GPIO的呼叫拒绝(请参见下文)。其他数字也可能会被拒绝;例如,一个数字可能有效,但在给定的板上暂时未使用。

平台是否支持多个GPIO控制器是特定于平台的实现问题,该支持是否会在GPIO编号的空间中留下"漏洞",以及是否可以在运行时添加新的控制器。此类问题可能会影响包括相邻GPIO号是否均有效的问题。

使用GPIO

系统使用GPIO要做的第一件事是使用gpio_request()调用对其进行分配。待会儿见。

与GPIO有关的下一件事(通常是在使用GPIO设置platform_device时的板级设置代码中)标明了其方向:

/* set as input or output, returning 0 or negative errno */
int gpio_direction_input(unsigned gpio);
int gpio_direction_output(unsigned gpio, int value);

成功的返回值为零,否则为负errno。由于get/set调用没有错误返回,并且可能配置错误,因此应进行检查。通常,您应该从任务上下文发出这些调用。但是,对于自旋锁安全GPIO,可以在启用任务之前使用它们,这是早期电路板设置的一部分。

对于输出GPIO,提供的值将成为初始输出值。这有助于避免系统启动期间的信号毛刺。

为了与GPIO的旧版接口兼容,如果尚未请求GPIO,则设置GPIO的方向会隐式请求该GPIO(请参见下文)。该兼容性已从可选的gpiolib框架中删除。

如果GPIO编号无效,或者无法在该模式下使用特定的GPIO,则设置方向可能会失败。依靠引导固件正确设置方向通常是一个坏主意,因为它可能没有经过验证可以做得比引导Linux还要多。(类似地,该板卡设置代码可能需要将该引脚复用为GPIO,并适当配置上拉/下拉。)

自旋锁安全GPIO访问

可以通过存储器读/写指令访问大多数GPIO控制器。这些不需要休眠,可以在硬(非线程)IRQ处理程序和类似上下文中安全地完成操作。

使用以下调用来访问此类GPIO,对于这些GPIO,gpio_cansleep()将始终返回false(请参见下文):

/* GPIO INPUT:  return zero or nonzero */
int gpio_get_value(unsigned gpio);

/* GPIO OUTPUT */
void gpio_set_value(unsigned gpio, int value);

值为布尔值,低表示零,高表示非零。读取输出引脚的值时,返回的值应该是在引脚上看到的值……由于漏极开路信号和输出等待时间等问题,它并不总是与指定的输出值匹配。

get/set调用没有错误返回,因为应该早些时候从gpio_direction _ *()报告了"无效的GPIO"。但是,请注意,并非所有平台都可以读取输出引脚的值。那些不能总是返回零。同样,将这些调用用于无法不睡眠就无法安全访问的GPIO(请参见下文)也是错误的。

在GPIO编号(以及用于输出的值)恒定的情况下,鼓励特定于平台的实现优化两个调用以访问GPIO值。在这种情况下(读取或写入硬件寄存器),通常只需要执行几条指令,而不需要旋转锁。与在子例程调用上花费数十条指令相比,这种优化的调用可使Bitbanging应用程序更加高效(在空间和时间上)。

可能会休眠的GPIO访问

必须使用基于消息的总线(例如I2C或SPI)来访问某些GPIO控制器。读取或写入这些GPIO值的命令需要等待到达队列的头部才能传输命令并获得其响应。这需要睡眠,这不能从IRQ处理程序内部完成。

支持此类型GPIO的平台通过从此调用返回非零来区分它们与其他GPIO(这需要一个有效的GPIO号,该编号应先前已通过gpio_request分配):

int gpio_cansleep(unsigned gpio);
为了访问此类GPIO,定义了一组不同的访问器:

/* GPIO INPUT:  return zero or nonzero, might sleep */
int gpio_get_value_cansleep(unsigned gpio);

/* GPIO OUTPUT, might sleep */
void gpio_set_value_cansleep(unsigned gpio, int value);

访问此类GPIO需要一个可能处于休眠状态的上下文,例如线程IRQ处理程序,并且必须使用那些访问器代替无cansleep()名称后缀的自旋锁安全访问器。

除了这些访问器可能会休眠并且可以在无法从hardIRQ处理程序访问的GPIO上工作的事实之外,这些调用的作用与自旋锁安全调用相同。

另外,必须从可能处于休眠状态的上下文进行设置和配置此类GPIO的调用,因为它们可能也需要访问GPIO控制器芯片(这些设置调用通常是从板设置或驱动器探测/拆卸代码进行的,因此这是一个简单的约束。):

        gpio_direction_input()
        gpio_direction_output()
        gpio_request()

##      gpio_request_one()
##      gpio_request_array()
##      gpio_free_array()

        gpio_free()
        gpio_set_debounce()

声明和释放GPIO

为了帮助捕获系统配置错误,定义了两个调用:

/* request GPIO, returning 0 or negative errno.
 * non-null labels may be useful for diagnostics.
 */
int gpio_request(unsigned gpio, const char *label);

/* release previously-claimed GPIO */
void gpio_free(unsigned gpio);

将无效的GPIO编号传​​递给gpio_request()将会失败,请求已被该调用声明的GPIO也会失败。必须检查gpio_request()的返回值。通常,您应该从任务上下文发出这些调用。但是,对于自旋锁安全GPIO,可以在启用任务之前请求GPIO,这是早期电路板设置的一部分。

这些调用有两个基本目的。一种是标记实际用作GPIO的信号,以进行更好的诊断;另一种是对信号进行标记。系统可能具有数百个潜在的GPIO,但在任何给定的板上通常只使用十二个。另一个是捕获冲突,并在以下情况下发现错误:(a)两个或更多驱动程序错误地认为自己专有使用该信号,或者(b)错误地认为删除管理有效使用的信号所需的驱动程序是安全的。也就是说,请求GPIO可以用作一种锁定。

一些平台还可能使用有关哪些GPIO用于电源管理的知识,例如通过关闭未使用的芯片扇区的电源,更容易地关闭未使用的时钟。

对于使用pinctrl子系统已知的引脚的GPIO,应将该子系统的使用情况告知该子系统。gpiolib驱动程序的.request()操作可以调用pinctrl_gpio_request(),而gpiolib驱动程序的.free()操作可以调用pinctrl_gpio_free()。pinctrl子系统允许pinctrl_gpio_request()在某个引脚或某个引脚组被某个设备"拥有"用于引脚多路复用的情况下同时成功执行。

将GPIO信号路由到适当引脚所需的任何引脚多路复用硬件编程都应在GPIO驱动程序的.direction_input()或.direction_output()操作内进行,并在对输出GPIO值进行任何设置之后进行。这允许从引脚的特殊功能到GPIO的无干扰移植。使用GPIO对通常由非GPIO硬件模块驱动的信号实施解决方法时,有时需要这样做。

一些平台允许将部分或全部GPIO信号路由到不同的引脚。同样,可能需要配置GPIO或引脚的其他方面,例如上拉/下拉。平台软件应安排在为这些GPIO调用gpio_request()之前配置任何此类详细信息,例如使用pinctrl子系统的映射表,以便GPIO用户无需知道这些详细信息。

另请注意,您有责任在释放它之前停止使用GPIO。

考虑到在大多数情况下,GPIO实际上是在声明所有权后立即配置的,因此定义了另外三个调用:

/* request a single GPIO, with initial configuration specified by
 * 'flags', identical to gpio_request() wrt other arguments and
 * return value
 */
int gpio_request_one(unsigned gpio, unsigned long flags, const char *label);

/* request multiple GPIOs in a single call
 */
int gpio_request_array(struct gpio *array, size_t num);

/* release multiple GPIOs in a single call
 */
void gpio_free_array(struct gpio *array, size_t num);

当前定义"标志"以指定以下属性:

  • GPIOF_DIR_IN-将方向配置为输入
  • GPIOF_DIR_OUT-将方向配置为输出
  • GPIOF_INIT_LOW-作为输出,将初始电平设置为LOW
  • GPIOF_INIT_HIGH-作为输出,将初始电平设置为HIGH
  • GPIOF_OPEN_DRAIN-gpio引脚为开漏型。
  • GPIOF_OPEN_SOURCE-gpio引脚是开源类型。
  • GPIOF_EXPORT_DIR_FIXED-将gpio导出到sysfs,保持方向
  • GPIOF_EXPORT_DIR_CHANGEABLE-也可以导出,允许改变方向

由于GPIOF_INIT_ *仅在配置为输出时才有效,因此将有效组合分组为:

  • GPIOF_IN-配置为输入
  • GPIOF_OUT_INIT_LOW-配置为输出,初始电平为LOW
  • GPIOF_OUT_INIT_HIGH-配置为输出,初始电平为高

当将标志设置为GPIOF_OPEN_DRAIN时,它将假定引脚为漏极开路类型。在输出模式下,此类引脚将不会被驱动为1。需要在这些引脚上连接上拉电阻。通过启用此标志,当在输出模式下要求将值设置为1以将引脚设置为高电平时,gpio lib将确定输入方向。在输出模式下,通过驱动值0将引脚设为LOW。

当将标志设置为GPIOF_OPEN_SOURCE时,它将假定引脚为开源类型。在输出模式下,此类引脚不会被驱动为0。需要在该引脚上连接下拉电路。通过启用此标志,当在输出模式下要求将值设置为0以使引脚为LOW时,gpio lib将使输入方向变为正确。在输出模式下,通过驱动值1将引脚设为高电平。

将来,可以扩展这些标志以支持更多属性。

此外,为了简化多个GPIO的声明/发布,引入了" struct gpio"以将所有三个字段封装为:

struct gpio {
        unsigned        gpio;
        unsigned long   flags;
        const char      *label;
};

用法的典型示例:

static struct gpio leds_gpios[] = {
        { 32, GPIOF_OUT_INIT_HIGH, "Power LED" }, /* default to ON */
        { 33, GPIOF_OUT_INIT_LOW,  "Green LED" }, /* default to OFF */
        { 34, GPIOF_OUT_INIT_LOW,  "Red LED"   }, /* default to OFF */
        { 35, GPIOF_OUT_INIT_LOW,  "Blue LED"  }, /* default to OFF */
        { ... },
};

err = gpio_request_one(31, GPIOF_IN, "Reset Button");
if (err)
        ...

err = gpio_request_array(leds_gpios, ARRAY_SIZE(leds_gpios));
if (err)
        ...

gpio_free_array(leds_gpios, ARRAY_SIZE(leds_gpios));

GPIO映射到IRQ

GPIO号是无符号整数;IRQ号也是如此。它们组成了两个逻辑上不同的名称空间(GPIO 0不需要使用IRQ 0)。您可以使用类似以下的调用在它们之间进行映射:

/* map GPIO numbers to IRQ numbers */
int gpio_to_irq(unsigned gpio);

/* map IRQ numbers to GPIO numbers (avoid using this) */
int irq_to_gpio(unsigned irq);

它们要么返回另一个名称空间中的对应数字,要么返回负的errno代码(如果无法完成映射)。(例如,某些GPIO不能用作IRQ。)使用没有通过gpio_direction_input()设置为输入的GPIO编号,或使用最初没有使用的IRQ编号,这是一个未经检查的错误。来自gpio_to_irq()。

预计这两个映射调用的成本约为一次加法或减法。他们不允许休眠。

从gpio_to_irq()返回的非错误值可以传递给request_irq() 或free_irq()。它们通常会通过特定于板的初始化代码存储在平台设备的IRQ资源中。请注意,IRQ触发选项是IRQ接口的一部分,例如IRQF_TRIGGER_FALLING,以及系统唤醒功能。

从irq_to_gpio()返回的非错误值通常会与gpio_get_value()一起使用,例如,在IRQ沿边沿触发时初始化或更新驱动程序状态。请注意,某些平台不支持此反向映射,因此应避免使用它。

模拟漏极开路信号

有时,共享信号需要使用"漏极开路"信令,其中实际上仅驱动低信号电平。(该术语适用于CMOS晶体管;“集电极开路"用于TTL。)上拉电阻引起高信号电平。有时称为"线与”;或更实际地,从否定逻辑(低=真)的角度来看,这是"线或"。

开漏信号的一个常见示例是共享的低电平有效IRQ线。同样,双向数据总线信号有时会使用漏极开路信号。

某些GPIO控制器直接支持开漏输出。许多没有。当您需要开漏信号发送但您的硬件不直接支持它时,您可以使用一个通用的习惯用法来与任何可用作输入或输出的GPIO引脚进行仿真:

  • low:gpio_direction_output(gpio,0) 驱动信号并覆盖上拉。
  • hight:gpio_direction_input(gpio) 这将关闭输出,因此上拉(或其他设备)控制信号。
    如果将信号"驱动"为高电平,但gpio_get_value(gpio)报告为低值(经过适当的上升时间后),则您知道其他一些组件正在将共享信号驱动为低电平。那不一定是错误。作为一个常见的示例,这就是I2C时钟的扩展方式:需要较慢时钟的从机延迟SCK的上升沿,而I2C主设备相应地调整其信令速率。

GPIO控制器和pinctrl子系统

SOC上的GPIO控制器可能与pinctrl子系统紧密耦合,从某种意义上说,这些引脚可以与其他功能以及可选的gpio功能一起使用。我们已经介绍了以下情况,例如,GPIO控制器需要通过调用以下任意一项来保留引脚或设置引脚方向:

pinctrl_gpio_request()
pinctrl_gpio_free()
pinctrl_gpio_direction_input()
pinctrl_gpio_direction_output()

但是,引脚控制子系统如何将GPIO编号(是全局性)与某个引脚控制器上的某个引脚互相关?

这是通过注册引脚的"范围"来完成的,这些引脚本质上是交叉引用表。这些内容在Documentation/driver-api/pinctl.rst中进行了描述

尽管引脚分配完全由pinctrl子系统管理,但gpio(在gpiolib下)仍由gpio驱动程序维护。SoC中不同的引脚范围可能由不同的gpio驱动程序管理。

这使得让gpio驱动程序在调用" pinctrl_gpio_request"之前向pin ctrl子系统宣布其引脚范围是合乎逻辑的,以便在使用任何gpio之前请求pinctrl子系统准备相应的引脚。

为此,gpio控制器可以向pinctrl子系统注册其引脚范围。当前有两种方法可以使用:使用DT或不使用DT。

有关DT支持的信息,请参阅Documentation/devicetree/bindings/gpio/gpio.txt。

对于不支持DT的情况,用户可以gpiochip_add_pin_range()使用适当的参数进行调用,以使用pinctrl驱动程序注册一系列gpio引脚。为此,必须将pinctrl设备的确切名称字符串作为该例程的参数之一传递。

这些约定遗漏了什么?

这些约定最大的遗漏之一就是引脚多路复用,因为这是高度特定于芯片且不可移植的。一个平台可能不需要显式多路复用;另一个可能只有两个选项可以使用任何给定的引脚;每个引脚可能有八个选项;另一个也许可以将给定的GPIO路由到几个引脚中的任何一个。(是的,这些示例都来自今天运行Linux的系统。)

与多路复用相关的是一些平台上集成的上拉或下拉的配置和启用。并非所有平台都支持它们或以相同的方式支持它们。而且任何给定的电路板都可能使用外部上拉(或下拉),因此不应使用片上电路。(当电路需要5 kOhm时,就不会使用片上100 kOhm的电阻。)同样,驱动强度(2 mA对20 mA)和电压(1.8V对3.3V)也是特定于平台的问题,例如类似模型(不)在可配置引脚和GPIO之间具有一一对应的关系。

这里没有指定其他特定于系统的机制,例如输入去毛刺和线或输出的上述选项。硬件可能支持在组中读取或写入GPIO,但这通常取决于配置:对于共享同一存储体的GPIO。(GPIO通常分为16或32个组,给定的SOC具有多个这样的组。)某些系统可以从输出GPIO触发IRQ,或者从不作为GPIO管理的引脚读取值。依赖于这种机制的代码必然是不可移植的。

GPIO的动态定义当前不是标准的。例如,作为使用某些GPIO扩展器配置附加板的副作用。

GPIO实现者的框架(可选)

如前所述,有一个可选的实现框架,使平台可以更轻松地使用同一编程接口来支持不同种类的GPIO控制器。该框架称为" gpiolib"。

作为调试帮助,如果debugfs可用,则可以在其中找到/sys/kernel/debug/gpio文件。这将列出通过此框架注册的所有控制器,以及当前使用的GPIO的状态。

控制器驱动程序:gpio_chip

在此框架中,每个GPIO控制器都打包为" struct gpio_chip",其中包含该类型的每个控制器共有的信息:

  • 建立GPIO方向的方法
  • 用于访问GPIO值的方法
  • 标记说明是否调用其方法可能会休眠
  • 可选的debugfs转储方法(显示额外的状态,如上拉配置)
  • 诊断标签
    还有每个实例的数据,这些数据可能来自device.platform_data:其第一个GPIO的数量以及它公开的GPIO数量。

实现gpio_chip的代码应支持控制器的多个实例,可能使用驱动程序模型。该代码将配置每个gpio_chip并发出gpiochip_add()。删除GPIO控制器应该很少。使用gpiochip_remove()时,它是不可避免的。

通常,gpio_chip是实例特定结构的一部分,其状态未通过GPIO接口公开,例如寻址,电源管理等。编解码器等芯片将具有复杂的非GPIO状态。

通常,任何debugfs转储方法都应忽略未被请求作为GPIO的信号。他们可以使用gpiochip_is_requested(),当请求时返回NULL或与该GPIO关联的标签。

Platform 支持

为了强制启用该框架,平台的Kconfig将"选择" GPIOLIB,否则用户可以配置对GPIO的支持。

它还可以为ARCH_NR_GPIOS提供自定义值,以便更好地反映该平台上实际使用的GPIO数量,而不会浪费静态表空间。(它应该同时包括内置/SoC GPIO和GPIO扩展器上的数量。

如果未选择这两个选项,则平台不通过GPIO-lib支持GPIO,并且用户无法启用代码。

这些功能的简单实现可以直接使用框架代码,该代码始终通过gpio_chip调度:

#define gpio_get_value        __gpio_get_value
#define gpio_set_value        __gpio_set_value
#define gpio_cansleep         __gpio_cansleep

相反,更高级的实现可以通过逻辑优化对特定基于SOC的GPIO的访问,将其定义为内联函数。例如,如果引用的GPIO为常数" 12",则获取或设置其值可能只​​需花费两三个指令,而永不休眠。当不可能进行这种优化时,这些调用必须委托给框架代码,至少要花费几十条指令。对于位撞的I/O,这样的指令节省可以是可观的。

对于SOC,特定于平台的代码为每组片上GPIO定义并注册了gpio_chip实例。这些GPIO应该编号/标记以匹配芯片供应商文档,并直接匹配电路板原理图。它们很可能从零开始并上升到特定于平台的限制。通常将这些GPIO集成到平台初始化中,以使它们始终可以从arch_initcall()或更早的版本开始使用。它们通常可以用作IRQ。

Board 支持

对于外部GPIO控制器(例如I2C或SPI扩展器,ASIC,多功能设备,FPGA或CPLD),大多数特定于电路板的代码都会处理注册控制器设备,并确保其驱动程序知道与gpiochip_add()配合使用的GPIO编号。它们的数量通常在平台特定的GPIO之后开始。

例如,电路板设置代码可以创建用于识别芯片将公开的GPIO范围的结构,并使用platform_data将其传递给每个GPIO扩展器芯片。然后,芯片驱动程序的probe()例程可以将该数据传递给gpiochip_add()。

初始化顺序可能很重要。例如,当设备依赖于基于I2C的GPIO时,应仅在该GPIO可用后才调用其probe()例程。这可能意味着在对该GPIO的调用可以正常工作之前,不应注册该设备。解决这种依赖关系的一种方法是让此类gpio_chip控制器提供setup()和teardown()回调以提供特定于电路板的代码。这些板专用的回调将在所有必要资源可用后注册设备,并在GPIO控制器设备不可用时稍后将其删除。

用户空间的Sysfs接口(可选)

使用" gpiolib"实现程序框架的平台可以选择配置GPIO的sysfs用户界面。这与debugfs接口不同,因为它提供了对GPIO方向和值的控制,而不仅仅是显示gpio状态摘要。另外,它可以在没有调试支持的情况下出现在生产系统上。

给定适合系统的硬件文档,用户空间可能知道例如GPIO#23控制用于保护闪存中引导加载程序段的写保护线。系统升级过程可能需要临时删除该保护,首先导入GPIO,然后更改其输出状态,然后更新代码,然后重新启用写保护。在正常使用中,将永远不会碰到GPIO#23,并且内核也不需要知道它。

再次取决于适当的硬件文档,在某些系统上,用户空间GPIO可用于确定标准内核不知道的系统配置数据。对于某些任务,系统真正需要的只是简单的用户空间GPIO驱动程序。

请注意,对于常见的" LED和按钮" GPIO任务,存在标准的内核驱动程序:分别为" leds-gpio"和" gpio_keys"。使用这些而不是直接与GPIO通讯;它们比您的用户空间代码更好地与内核框架集成。

Sysfs中的路径

/sys/class/gpio中有三个条目:

  • 用于通过GPIO获得用户空间控制的控制接口;
  • GPIO本身;
  • GPIO控制器(" gpio_chip"实例)。
    这是标准文件(包括"设备"符号链接)的补充。

控制接口是只写的:

/sys/class/gpio/

  • “export” 用户空间可能要求内核导出对
    通过将GPIO编号写入该文件来将其输入到用户空间。

    示例:如果内核代码未要求," echo 19> export"将为GPIO#19创建一个" gpio19"节点。

  • “unexport” 反转导出到用户空间的效果。

    示例:" echo 19> unexport"将删除使用" export"文件导出的" gpio19"节点。
    GPIO信号的路径类似于/sys/class/gpio/gpio42 /(对于GPIO#42),并具有以下读/写属性:

/sys/class/gpio/gpioN /

  • “direction” 该值可能为"in"或"out"。默认写为" out"时将值初始化为低。为了确保无干扰操作,可以写入值" low"和" high"以将GPIO配置为具有该初始值的输出。

    请注意,如果内核不支持更改GPIO的方向,或者该属性是由未明确允许用户空间重新配置GPIO方向的内核代码导出的,则此属性将不存在。

  • “value” 0(low)或1(hight)。如果是GPIO配置为输出,可以写入该值;任何非零值都被视为高。

    如果该引脚可以配置为产生中断的中断,并且已经配置为生成中断(请参见" edge"的描述),则可以对该文件执行poll(2),并且只要中断发生,poll(2)就会返回。触发。如果使用poll(2),则设置事件POLLPRI。如果使用select(2),请在exceptfds中设置文件描述符。在poll(2)返回之后,要么lseek(2)到sysfs文件的开头并读取新值,要么关闭该文件并重新打开以读取该值。

  • “edge” 可以为"none",“rising”,“falling"或”“both”。编写这些字符串以选择将使" value"文件上的poll (2)返回的信号边沿。

    仅当该引脚可以配置为产生中断的输入引脚时,该文件才存在。

  • “active_low” 为0(flase)或1(true)。写任何非零值都可以同时反转value属性以进行读取和写入。现有的和后续的poll(2)通过edge属性的"上升"和"下降"边缘支持配置将遵循此设置。GPIO控制器具有/sys/class/gpio/gpiochip42 /之类的路径(用于实现从#42开始的实现GPIO的控制器),并具有以下只读属性:

/sys/class/gpio/gpiochipN /

  • “base” 与N相同,它是该芯片管理的第一个GPIO

  • “label” 用于诊断(并非总是唯一的)

  • “ngpio” 要处理多少个GPIO(从N到N + ngpio-1)

电路板文档在大多数情况下应涵盖将GPIO用于什么目的。但是,这些数字并不总是稳定的。子卡上的GPIO可能会有所不同,具体取决于所使用的基板或堆栈中的其他卡。在这种情况下,您可能需要使用gpiochip节点(可能与原理图结合使用)来确定用于给定信号的正确GPIO号。

从内核代码导出

内核代码可以显式管理已使用gpio_request()请求的GPIO的导出:

/* export the GPIO to userspace */
int gpio_export(unsigned gpio, bool direction_may_change);

/* reverse gpio_export() */
void gpio_unexport();

/* create a sysfs link to an exported GPIO node */
int gpio_export_link(struct device *dev, const char *name,
        unsigned gpio)

内核驱动程序请求GPIO后,只能通过gpio_export()在sysfs接口中使它可用。驾驶员可以控制信号方向是否可能改变。这有助于驱动程序防止用户空间代码意外破坏重要的系统状态。

这种显式导出可以帮助调试(通过简化一些实验),或者可以提供一个始终存在的接口,适合作为板级支持软件包的一部分进行记录。

导出GPIO后,gpio_export_link()允许创建从sysfs中其他位置到GPIO sysfs节点的符号链接。驱动程序可以使用它在sysfs中的自己设备下为接口提供描述性名称。

API参考

不建议使用本节中列出的功能。基于GPIO描述符的API应该在新代码中使用。

int gpio_request_one( unsigned  gpio,unsigned long 标志,const char *  label )

请求具有初始配置的单个GPIO

参数

  • unsigned gpio
    GPIO号
  • unsigned long flags
    GPIOF_ *指定的GPIO配置
  • const char * label
    该GPIO的文字描述字符串
int gpio_request_array( const struct gpio *  array,size_t  num )
    在一个调用中请求多个GPIO

参数

  • const struct gpio * array
    'struct gpio’的数组
  • size_t num
    阵列中有多少个GPIO
void gpio_free_array( const struct gpio *  array,size_t  num )

一次调用即可释放多个GPIO

参数

  • const struct gpio * array
    'struct gpio’的数组
  • size_t num
    阵列中有多少个GPIO

你可能感兴趣的:(Linux内核,内核,驱动程序)