您现在的位置是:首页 >学无止境 >gpio_device,gpio_chip 和 gpio_desc网站首页学无止境

gpio_device,gpio_chip 和 gpio_desc

Vane Zhang 2024-06-15 00:01:02
简介gpio_device,gpio_chip 和 gpio_desc

Linux version: 4.14

Code link: Linux source code (v4.14) - Bootlin


1  gpio_device,gpio_chip 和 gpio_desc

(1)每个 GPIO 控制器 (Bank) 对应一个 gpio_device 结构。在这个结构中,包含了 gpio_chip 和 gpio_desc 。由于 gpio_device 代表一个 Bank ,一般的 GPIO 外设有多个 Bank ,所以在内核中用链表来组织 gpio_device。

struct gpio_device {           /* 每个 GPIO 控制器对应一个 gpio_device 结构 */
	int			     id;       /* GPIO 控制器对应的 id 号 */
	struct device	 dev;      /* GPIO 设备结构 */
	struct cdev		 chrdev;   /* GPIO 设备的字符设备 */
	struct device	 *mockdev; /* class设备被弃用的sysfs接口使用(可能是空)*/
	struct module	 *owner;   /* 有助于防止删除导出活动GPIO的模块 */
	struct gpio_chip *chip;    /* 指向相应gpiochip的指针,保存该设备的静态数据 */
	struct gpio_desc *descs;   /* ngpio描述符数组(gpio 引脚描述符数组) */
	int			base;   /* DEPRECATED全局GPIO数字空间中的GPIO基数,在设备创建时分配 */
	u16			ngpio;  /* 该GPIO控制器上GPIO引脚的数量,等于descs数组的大小 */
	char	    *label; /* GPIO设备的描述性名称,例如片上系统中IP组件的部件号或名称 */
	void	    *data;  /* 驱动程序分配的每个实例数据 */
	struct list_head list;     /* 用于将gpio_device链接在一起进行遍历 */

#ifdef CONFIG_PINCTRL
	/*
     * 如果CONFIG_PINCTRL被启用,
     * 那么gpio控制器可以选择性地描述它们在SoC中服务的实际引脚范围。 
     * PINCTRL子系统将使用这些信息来配置gpio使用的相应引脚。
	 */
	struct list_head pin_ranges;
#endif
};

(2)gpio_chip 结构是对 gpio 引脚相关控制操作的抽象。在底层对接到 Gpiolib 的时候,就是对 gpio_chip 结构体相关的成员进行赋值,并最终调用 gpiochip_add_data 或者,将其注册到了内核的 Gpiolib 子系统。该结构主要包含:

①引脚相关的控制函数

②引脚相关的中断函数

③引脚相关信息

struct gpio_chip {          // 抽象gpio控制器

    // GPIO设备的功能名称,例如部件号或实现它的SoC IP块的名称。
	const char		    *label; 

    // 内部状态持有者,不透明结构
	struct gpio_device	*gpiodev; 

    // 提供GPIO的可选父设备
	struct device		*parent;  

    // 有助于防止删除导出活动GPIO的模块
	struct module		*owner;   

    // 用于芯片特定激活的可选钩子函数,例如启用模块电源和时钟;可以休眠
	int			(*request)(struct gpio_chip *chip, unsigned offset);

	// 用于芯片特定停用的钩子函数,例如禁用模块电源和时钟;可以休眠
    void		(*free)(struct gpio_chip *chip, unsigned offset);

    // 返回信号“offset”的方向,0=out,1=in,(与GPIOF_DIR_XXX相同),或负错误
	int			(*get_direction)(struct gpio_chip *chip, unsigned offset);

    // 将信号“偏移”配置为输入,或返回错误
	int			(*direction_input)(struct gpio_chip *chip, unsigned offset);

    // 将信号“偏移”配置为输出,或返回错误
	int			(*direction_output)(struct gpio_chip *chip, 
                                           unsigned offset, int value);

    // 返回信号“偏移”的值,0=低,1=高,或负错误
	int			(*get)(struct gpio_chip *chip, unsigned offset);

    // 为信号“偏移”设置输出值
	void			(*set)(struct gpio_chip *chip, unsigned offset, int value);

    // 为“掩码”定义的多个信号分配输出值
	void			(*set_multiple)(struct gpio_chip *chip, 
                                       unsigned long *mask, unsigned long *bits);
    // 用于各种设置的可选挂钩。使用与通用pinconf相同的压缩配置格式
	int			(*set_config)(struct gpio_chip *chip,
					                 unsigned offset, unsigned long config);

    // 支持非静态gpio_to_irq()映射的可选钩子;实现可能无法休眠
	int			(*to_irq)(struct gpio_chip *chip, unsigned offset);

    // 在debugfs中显示内容的可选例程;省略时将使用默认代码,但自定义代码可以显示额外的状态
    // 如上拉/下拉配置
	void			(*dbg_show)(struct seq_file *s, struct gpio_chip *chip);

	int			base;           // 识别由该芯片处理的第一个GPIO号码;或者,
                                // 如果在注册过程中为负数,则请求动态ID分配。
                                // DEPRECATION:
                                // 不推荐提供任何非负数的内容并固定GPIO芯片的基本偏移量。
                                // 请将-1作为基数,让gpiolib在所有可能的情况下选择芯片基数。
                                // 从长远来看,我们希望摆脱静态GPIO数字空间。

	u16			ngpio;  // 该控制器处理的GPIO数量;最后处理的GPIO是(base+ngpio-1)

	const char  *const *names;  // 如果设置,必须是一个字符串数组,用作该芯片中GPIO
                                // 的替代名称。如果GPIO没有别名,则数组中的任何条目
                                // 都可能为NULL,但数组的长度必须为@ngpio条目。
                                // 一个名称可以包含一个用于无符号int的printk格式说明符。
                                // 它被gpio的实际数字所取代。 

	bool		can_sleep;      // 必须在get()/set()方法休眠时设置flag,因为
                                // 它们在通过I2C或SPI访问GPIO扩展器芯片时必须这样做。
                                // 这意味着,如果芯片支持IRQ,则这些IRQ需要被线程化,
                                // 因为芯片访问可能在例如读取IRQ状态寄存器时休眠。

#if IS_ENABLED(CONFIG_GPIO_GENERIC)

    // 通用GPIO的读函数
	unsigned long (*read_reg)(void __iomem *reg); 

    // 通用GPIO的写函数
	void (*write_reg)(void __iomem *reg, unsigned long data); 
    
    // 一些通用GPIO控制器使用big-endian位表示法,例如在8位寄存器中,GPIO7是最低有效位。
    // 此回调分配正确的位掩码。
	unsigned long (*pin2mask)(struct gpio_chip *gc, unsigned int pin);
	
    void __iomem *reg_dat; // 通用GPIO的数据(in)寄存器
	void __iomem *reg_set; // 通用GPIO的输出设置寄存器(out=高)
	void __iomem *reg_clr; // 通用GPIO的输出清除寄存器(out=低)
	void __iomem *reg_dir; // 通用GPIO的方向设置寄存器
	int bgpio_bits;        // 用于通用GPIO的寄存器位数,即<寄存器宽度>*8

    // 用于锁定芯片->bgpio_data。此外,
    // 这也是保持影子数据寄存器和真实数据寄存器一起写入所必需的
	spinlock_t bgpio_lock;  

    // 通用GPIO的阴影数据寄存器,用于安全地清除/设置位。
	unsigned long bgpio_data; 

    // 通用GPIO的阴影方向寄存器,用于安全地清除/设置方向。
	unsigned long bgpio_dir;
#endif

#ifdef CONFIG_GPIOLIB_IRQCHIP
	/*
	 * 通过CONFIG_GPIOLIB_IRQCHIP,我们在GPIOLIB中获得了一个irq芯片,
     * 用于处理大多数实际情况下的IRQ。
	 */
    // GPIO IRQ芯片impl,由GPIO驱动程序提供
	struct irq_chip		*irqchip;    

    // 中断翻译域;负责GPIO hwirq号码和linux irq号码之间的映射
	struct irq_domain	*irqdomain;

    // 分配给GPIO irq芯片的第一个linux irq编号(已弃用)
	unsigned int		irq_base;

    // 用于GPIO irq的irq处理程序(通常是预定义的irq核心函数),由GPIO驱动程序提供
	irq_flow_handler_t	irq_handler;
    
    // GPIO驱动程序初始化时应用的默认irq触发类型,由GPIO驱动提供
	unsigned int		irq_default_type;

    // GPIO irq芯片父级/库linux irq号,由GPIO驱动程序提供,用于链式中断(不用于嵌套中断)
	unsigned int		irq_chained_parent;

    // 如果设置中断处理是嵌套的,则为True。
	bool			    irq_nested;

    // 如果set core分配irq_valid_mask时所有位都设置为1
	bool			    irq_need_valid_mask;

    // 如果不是%NULL,则保留GPIO的位掩码,这些GPIO有效地包含在芯片的irq域中
	unsigned long		*irq_valid_mask;
    
    // 每个GPIO IRQ芯片lockdep类
	struct lock_class_key	*lock_key;
#endif

#if defined(CONFIG_OF_GPIO)
	/*
	 * 如果启用CONFIG_OF,则设备树中描述的所有GPIO控制器都可能自动进行OF转换
     *
	 */

	/**
	 * @of_node:
	 *
	 * 指向表示此GPIO控制器的设备树节点的指针。
	 */
	struct device_node *of_node;

	/**
	 * @of_gpio_n_cells:
	 *
	 * 用于形成GPIO说明符的单元格数。
	 */
	unsigned int of_gpio_n_cells;

	/**
	 * @of_xlate:
	 *
	 * 回调,将 gpiospec 转换为 gpio 编号和标志
	 * 返回值就是 gpio 引脚编号,标志信息保存在 flags 中
     *
	 */
	int (*of_xlate)(struct gpio_chip *gc,
			const struct of_phandle_args *gpiospec, u32 *flags);
#endif
};

// gpio_chip可以帮助平台抽象各种gpio源,以便通过公共编程接口访问它们。
// 示例源包括SOC控制器、FPGA、多功能芯片、专用gpio扩展器等。

// 每个芯片控制多个信号,这些信号在方法调用中标识通过在0..(@ngpio-1)范围内的“偏移”值。
// 当这些信号通过gpio_get_value(gpio)等调用引用时,
// 偏移量是通过从gpio数中减去@base来计算的。

(3)gpio_desc 结构可以认为是一个gpio引脚的描述符。这个结构比较简单,它包含了 gdev、flag、label 和 name 属性。其中 gdev 指针指向了 gpio_desc 所属的 gpio_device,而 flag 描述了当前这个gpio 引脚的属性状态。

struct gpio_desc {
	struct gpio_device	*gdev;
	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;
	/* Name of the GPIO */
	const char		*name;
};

(4)编写 GPIO 驱动程序的大致思路

① gpiod_get获得一个gpiod指针

② 根据gpiod找到GPIO控制器,使用gpiod_set_value来设置输出的高低电平 

2  参考:

GPIO子系统层次与数据结构_gpio_desc_习惯就好zz的博客-CSDN博客

Linux内核4.14版本——GPIO子系统(1)——gpiolib分析_linux gpio子系统_风雨兼程8023的博客-CSDN博客

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。