linux内核 GPIO操作 您所在的位置:网站首页 驱动optional linux内核 GPIO操作

linux内核 GPIO操作

2023-08-18 09:00| 来源: 网络整理| 查看: 265

大概10年前学习过linux 内核,但当时的GPIO的操作简单粗暴,现在pin-ctrl gpio驱动 dts 一堆,搞得不知从何下手,折腾了好几天,做个笔记,免得忘记了又得重来。

一 操作寄存器

根据芯片手册得到GPIO相关的寄存器地址(这里s5p6818为例)

//这是芯片手册里给出的真实物理地址 #define PHYS_BASE_GPIOA_OUTDAT (0xC001A000) // #define PHYS_BASE_GPIOA_OUTEN (0xC001A004) #define PHYS_BASE_GPIOA_PAD (0xC001A018) #define PHYS_BASE_GPIOA_FUNC0 (0xC001A020) #define PHYS_BASE_GPIOA_FUNC1 (0xC001A024)

物理地址不能直接操作需要用ioremap 处理: 参数1 :真实物理地址 参数2 :地址空间长度,以byte为单位,arm寄存器一般为32为,所以为4

void __iomem *gpioa_outdat_addr; void __iomem *gpioa_outen_addr; void __iomem *gpioa_pad_addr; void __iomem *gpioa_func0_addr; void __iomem *gpioa_func1_addr; gpioa_outdat_addr = ioremap(PHYS_BASE_GPIOA_OUTDAT,4); gpioa_outen_addr = ioremap(PHYS_BASE_GPIOA_OUTEN,4); gpioa_pad_addr = ioremap(PHYS_BASE_GPIOA_PAD,4); gpioa_func0_addr =ioremap(PHYS_BASE_GPIOA_FUNC0,4); gpioa_func1_addr =ioremap(PHYS_BASE_GPIOA_FUNC1,4);

得到的void __iomem * 值就可以用readl 读(地址可读),用writel 写(地址可写)

u32 data_en = readl(gpioa_outen_addr); data_en = 0x000FFFE2; writel(data_en,gpioa_outen_addr);

如何使用这些寄存器,看芯片手册 用完后记得释放:

iounmap(gpioa_outdat_addr); iounmap(gpioa_outen_addr); iounmap(gpioa_pad_addr); iounmap(gpioa_func0_addr); iounmap(gpioa_func1_addr);

readl ,writel操作寄存器的速度很慢,在GPIO模拟时序时可能达不到速度要求,但其实寄存器读写速度是非常快的,只是这两个函数加入了一些安全措施。追求速度可以用: __raw_writel __raw_readl 这是两个内联函数,只做了地址取值和赋值:

static inline void __raw_writel(u32 value, volatile void __iomem *addr) { *(volatile u32 __force *)addr = value; } static inline u32 __raw_readl(const volatile void __iomem *addr) { return *(const volatile u32 __force *)addr; }

所以可以更直接的读写:

u32 data_en =*(const volatile u32 __force *)gpioa_outen_addr; data_en = 0x000FFFE2; *(volatile u32 __force *)gpioa_outen_addr = value;

这么做的弊端是在某些情况下,可能会被编译器优化掉,造成赋值不成功

二 用linux gpio驱动

直接操作寄存器的优点是速度快,可同时操作多个gpio脚位,弊端是寄存器太多,设置值非常繁琐,上面的例子中操作一个GPIO 需要设置 5个寄存器,还要找对相应的bit位,一不小心就错了,可能一天的时间都耗费在位运算中。gpio 驱动的作用就体现出来了。 GPIO 操作API:

//申请GPIO,参数1:gpio 编号,参数2:gpio 名字 (自定义) int gpio_request(unsigned gpio, const char *label) //释放GPIO ,参数1:gpio编号 void gpio_free(unsigned gpio) //设置GPIO为输出模式,同时将输出设为value //参数1:gpio编号,参数2:输出的值 (0或1) static inline int gpio_direction_output(unsigned gpio, int value) //设置GPIO为输入模式 static inline int gpio_direction_input(unsigned gpio) //判断gpio是否有效,就是判断gpio的编号是否在范围内 //比如soc的所有gpio编号是0~128,如果输入编号在此范围则认为有效 //返回值:true 为有效,false为无效。 static inline int gpio_is_valid(unsigned int gpio) //设置GPIO的状态,参数1:GPIO编号,参数2:设置的值(0或1) static inline void gpio_set_value(unsigned int gpio, int value) //获取GPIO的状态,参数1:GPIO编号 //返回值;GPIO 状态,0:GPIO低点位,1:GPIO高电位 static inline int gpio_get_value(unsigned int gpio)

GPIO编号跟soc的BSP有关,可以阅读相关代码获得。 s5p6818有5个GPIO口(A,B,C,D,E)每个口有32个引脚,总共160个,所以依次编号为0~159,(A0=0,A1=1------E31=159) 这个编码规则应该是统一的,GPIO口的名字各芯片不一样,有些叫GPIO0 GPIO1------ gpio_request 的第二个参数是给申请到的gpio取个别名,自定义的,名字没什么要求,最好加一些前缀或后缀特异化,避免重复。

//这段代码将GPIOE_30 设置为输出模式并拉低,20us后再拉高 #define FPGA_POWER_GPIONUM 158 //GPIOE_30 //request PWOER GPIO if(gpio_request(FPGA_POWER_GPIONUM,"FPGA_POWER")){ gpio_direction_output(FPGA_POWER_GPIONUM,0); usleep(20); //因为已经设置为output模式,所以可以直接调用gpio_set_value //即使gpio为input模式也可以设置,只是不能作用到引脚 gpio_set_value(FPGA_POWER_GPIONUM,1); }

gpio_request 这个主要是判断有没有被其他模块占用,没有则注册,告诉GPIO系统我占用了,该函数如果失败,基本上就是有其他模块也用到了这个GPIO。这个问题在移植系统时非常常见,因为自己的板卡走线跟demo区别很大,用demo移植时经常会出现GPIO被占用,但是demo代码一般都是在dts中设置gpio脚,所以只要检查DTS哪里配置了这个脚就可以了。

三GPIO 中断

也是用GPIO驱动API来实现,直接操作寄存器太繁琐,不弄了,头发不多了! 中断单独讲,因为它牵涉到中断API,(这个超纲了*_*)

//根据gpio编号得到irq编号 static inline int gpio_to_irq(unsigned int gpio) //使能中断,参数:irq编号 void enable_irq(unsigned int irq) //禁用中断,参数:irq编号 void disable_irq(unsigned int irq) // 申请中断 //参数1:中断号,参数2:中断处理函数,参数3:触发方式, 参数4:中断名(自定义) //参数5:传递给中断处理函数的参数 //返回值:不为0表示失败。 static inline int __must_check request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev) //释放中断 //参数1 :中断号,参数2 :中断处理函数 void free_irq(unsigned int irq, void *dev_id)

gpio_to_irq 获得的中断号贯穿整个中断操作,输入参数就是上面提到的GPIO编号,前提是该GPIO能做中断引脚,这个跟soc有关,具体看芯片手册。 request_irq 的第二个参数是中断处理函数,其定义方式如下:

irqreturn_t bc_irq_handler(int irq, void *dev_id)

第一个参数就是中断号,不同的中断处理函数可以相同,具体是哪一个中断就用中断号来区分。第二个参数及request_irq 的最后一个参数。

以下代码是将gpioa-20引脚设置为中断模式,下降沿触发,处理函数为bc_irq_handler,中断处理函数中打印出函数名及中断号。

irqreturn_t bc_irq_handler(int irq, void *dev_id){ printk("%s %d\n",__func__,irq); } #define FPGA_B_INT 20 // gpioa-20 intrupt int b_irq = gpio_to_irq(FPGA_B_INT); ret = request_irq(g_privdat->b_irq,bc_irq_handler,IRQF_TRIGGER_FALLING,"B int",NULL); if(ret){ printk("request_irq error: %d\n",b_irq); return; } enable_irq(b_irq); 四 gpio与dts

这一节也超纲了,会涉及到DTS(设备树),但是主要还是列举一些API的用法,不会深入解释。头发还够的话,再写DTS的笔记。

简单说一下dts note 与代码的对应关系。 每一个dts note 都有一个compatible 字段,如下列dts note描述:

// bt_bcm { compatible = "bluetooth-platdata"; uart_rts_gpios = ; uart_cts_gpios = ; pinctrl-names = "default", "rts_gpio"; pinctrl-0 = ; pinctrl-1 = ; reset-gpios = ; wake-gpios = ; status = "okay"; };

compatible是自己定义的,它与驱动一一对应

static struct of_device_id bt_platdata_of_match[] = { { .compatible = "bluetooth-platdata" }, { } };

驱动中定义上面的结构体,并注册到系统中,那么系统就会匹配dts note 和驱动,并调用驱动中的probe函数:

static int rfkill_rk_probe(struct platform_device *pdev){ ...... }

probe函数的输入参数platform_device 结构体中有 pdev->dev->of_node 及dts的note内容 从note中获取gpio的两种方法:

//参数1 :对应的probe 函数输入参数下的pdev->dev->of_node //参数2 :note 中GPIO 列表的字段名 //参数3 :要取的gpio在列表中的下标 //参数4:gpio默认电位值 //返回值:GPIO编号 int of_get_named_gpio_flags(struct device_node *np, const char *list_name, int index, enum of_gpio_flags *flags) //参数1:probe函数输入参数中的设备结构体pdev->dev //参数2:note中的字段名 //参数3:要将gpio设置为何种状态 //返回值:gpio_desc 结构体,该结构体是对GPIO编号的包装。 struct gpio_desc *__must_check devm_gpiod_get_optional(struct device *dev, const char *con_id, enum gpiod_flags flags)

示例: 第一种:

enum of_gpio_flags flags; gpio = of_get_named_gpio_flags(node, "reset_gpios", 0, &flags);

note不用解释了, “BT,reset_gpio” 及note中的字段,上面的note示例中可以看到。 0表示所在列表的下标。 这里解释一下,设置GPIO的字段其实是一个数组,这里的 "reset_gpios"数组中只设置了一个GPIO reset_gpios = ; 它可以填充多个gpio:

reset_gpios = ; //index=2

这种情况就可以通过index 获取对应的gpio 编号了。 flags 是输出参数,对应GPIO_ACTIVE_HIGH

第二种;

struct gpio_desc *bt_reset; bt_reset = devm_gpiod_get_optional(&pdev->dev, "reset", GPIOD_OUT_LOW);

这种方法与第一种区别比较大,两种方法获取的是同一个gpio: 1 但是第二个参数代入名称并不一样,这是因为devm_gpiod_get_optional会对名称补全,即他会搜索reset-gpios; 2没有代入参数index,默认获取的是index 0 ,所以不能处理多个gpio设置的情况 3 第三个参数是输入,它将把gpio设置为该状态,而不是获取DTS中设置的状态,就是说它忽略了DTS中对引脚状态的配置。 4 返回值是gpio_desc 结构体而不是GPIO编号。这个结构体是与对应的GPIO绑定的,及第一种方法获取的GPIO编号与这里获取的gpio_desc指向的是同一个GPIO脚

gpio_desc 结构体是用另外一组api设置:

int gpiod_request(struct gpio_desc *desc, const char *label) void gpiod_set_value(struct gpio_desc *desc, int value) int gpiod_get_value(const struct gpio_desc *desc) int gpiod_direction_input(struct gpio_desc *desc) int gpiod_direction_output(struct gpio_desc *desc, int value)

它与第二节中的方法不同就在函数名多了一个‘d’,输入的GPIO编号改为了gpio_desc结构体

关于GPIO的API还有很多,这里记录了一些常用的。



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有