驱动GPIO操作总结

2022-11-15 21:25:26 浏览数 (1)

设备驱动程序是软件概念和硬件电路之间的一个抽象层,软件操作硬件的关键就是对寄存器的操作。笔者使用的S5PV210是IO与内存统一编址的,在裸机中直接操作IO端口的物理地址,而在驱动中必须使用虚拟地址。直接基于IO的虚拟地址用指针解引用的方式来读写有两种方式,静态映射动态映射。除了可以直接将指针解引用的方式,内核中提供了专用的读写接口来读写寄存器。考虑到GPIO作为硬件资源,存在着被多个驱动使用,还有复用的问题,所以内核提供了GPIO驱动gpiolib框架来统一管控GPIO资源,gpiolib在内核中作为一个驱动所实现。

静态映射操作寄存器

内核在启动的时候会建立一张静态映射表,三星版本内核中S5PV210的静态映射主表位于arch/arm/plat-s5p/include/plat/map-s5p.h,其本质就是一堆宏定义。CPU在安排寄存器地址时是按照模块去区分的。每一个模块内部很多寄存器地址是连续的。该表定义的是各个模块的基地址,具体的寄存器的地址是通过基地址 偏移量来寻址的。

map-s5p.h

代码语言:javascript复制
// 静态映射表的各模块总映射表: (此处的地址皆为虚拟地址)

#define S5P_VA_CHIPID       S3C_ADDR(0x00700000)
#define S5P_VA_GPIO         S3C_ADDR(0x00500000)  // 0xFD000000   0x00500000 = 0xFD500000
#define S5P_VA_SYSTIMER     S3C_ADDR(0x01200000)
#define S5P_VA_SROMC        S3C_ADDR(0x01100000)
#define S5P_VA_AUDSS        S3C_ADDR(0X01600000)

#define S5P_VA_UART0        (S3C_VA_UART   0x0)
#define S5P_VA_UART1        (S3C_VA_UART   0x400)
#define S5P_VA_UART2        (S3C_VA_UART   0x800)
#define S5P_VA_UART3        (S3C_VA_UART   0xC00)

#define S3C_UART_OFFSET     (0x400)

#define VA_VIC(x)       (S3C_VA_IRQ   ((x) * 0x10000))
#define VA_VIC0         VA_VIC(0)
#define VA_VIC1         VA_VIC(1)
#define VA_VIC2         VA_VIC(2)
#define VA_VIC3         VA_VIC(3)

其中GPIO模块的映射表位于arch/arm/mach-s5pv210/include/mach/regs-gpio.h

regs-gpio.h

代码语言:javascript复制
/* Base addresses for each of the banks */

#define S5PV210_GPA0_BASE       (S5P_VA_GPIO   0x000) // 0xFD500000   0x000 = 0xFD500000
#define S5PV210_GPA1_BASE       (S5P_VA_GPIO   0x020) // 0xFD500000   0x020 = 0xFD500020
#define S5PV210_GPB_BASE        (S5P_VA_GPIO   0x040) // 0xFD500000   0x020 = 0xFD500040
#define S5PV210_GPC0_BASE       (S5P_VA_GPIO   0x060) // 0xFD500000   0x020 = 0xFD500060
#define S5PV210_GPC1_BASE       (S5P_VA_GPIO   0x080)
#define S5PV210_GPD0_BASE       (S5P_VA_GPIO   0x0A0)
#define S5PV210_GPD1_BASE       (S5P_VA_GPIO   0x0C0)
#define S5PV210_GPE0_BASE       (S5P_VA_GPIO   0x0E0)
......

最终具体寄存器定义位于arch/arm/mach-s5pv210/include/mach/gpio-bank.h

gpio-bank.h

代码语言:javascript复制
/* mach/gpio-bank.h */

#define S5PV210_GPA0CON         (S5PV210_GPA0_BASE   0x00)
#define S5PV210_GPA0DAT         (S5PV210_GPA0_BASE   0x04)
#define S5PV210_GPA0PUD         (S5PV210_GPA0_BASE   0x08)
#define S5PV210_GPA0DRV         (S5PV210_GPA0_BASE   0x0c)
#define S5PV210_GPA0CONPDN      (S5PV210_GPA0_BASE   0x10)
#define S5PV210_GPA0PUDPDN      (S5PV210_GPA0_BASE   0x14)

#define S5PV210_GPA1CON         (S5PV210_GPA1_BASE   0x00)
#define S5PV210_GPA1DAT         (S5PV210_GPA1_BASE   0x04)
#define S5PV210_GPA1PUD         (S5PV210_GPA1_BASE   0x08)
#define S5PV210_GPA1DRV         (S5PV210_GPA1_BASE   0x0c)
#define S5PV210_GPA1CONPDN      (S5PV210_GPA1_BASE   0x10)
#define S5PV210_GPA1PUDPDN      (S5PV210_GPA1_BASE   0x14)
......

静态映射方式操作寄存器简单暴力,直接包含mach/gpio-bank.h头文件,使用其中定义的宏转成指针后解引用即可,例如下面的GPJ0CON直接当做一个变量读写即可。

代码语言:javascript复制
// 静态映射操作GPIO
#define GPJ0CON   (*((volatile unsigned int *)S5PV210_GPJ0CON))
#define GPJ0DAT   (*((volatile unsigned int *)S5PV210_GPJ0DAT))

动态映射操作寄存器

request_mem_region

向内核申请需要映射的一片连续内存资源

代码语言:javascript复制
/* linux/ioport.h */
#define request_mem_region(start, n, name) __request_region(&iomem_resource, (start), (n), (name), 0)
struct resource * __request_region(struct resource *parent, resource_size_t start, resource_size_t n, const char *name, int flags)

start: 起始物理地址

n: 映射的字节数

name: 名称字符串

返回值: 成功返回非NULL指针,失败返回NULL

ioremap

代码语言:javascript复制
/* asm/io.h */
#define ioremap(cookie, size)        __arm_ioremap(cookie, size, MT_DEVICE)
void __iomem *__arm_ioremap(unsigned long phys_addr, size_t size, unsigned int mtype)

phys_addr: 起始物理地址

size: 映射的字节数

返回值: 映射的虚拟地址

iounmap

解除映射

代码语言:javascript复制
/* asm/io.h */
#define iounmap(cookie)         __iounmap(cookie)
void __iounmap(volatile void __iomem *io_addr)

io_addr: 需要释放的虚拟地址

release_mem_region

释放申请

代码语言:javascript复制
/* linux/ioport.h */
release_mem_region(start, n)

start: 起始物理地址

n: 映射的字节数

使用示例

代码语言:javascript复制
#define GPJ0CON_PA  0xe0200240    // 物理地址
#define GPJ0DAT_PA  0xe0200244

unsigned int *GPJ0CON_VA;         // 虚拟地址
unsigned int *GPJ0DAT_VA;

#define GPJ0CON   (*GPJ0CON_VA)   // 解引用
#define GPJ0DAT   (*GPJ0DAT_VA)

// 建立动态映射
    if (!request_mem_region(GPJ0CON_PA, 4, "GPJ0CON"))
        return -EBUSY;
    if (!request_mem_region(GPJ0DAT_PA, 4, "GPJ0DAT"))
        return -EBUSY;

    GPJ0CON_VA = ioremap(GPJ0CON_PA, 4);
    GPJ0DAT_VA = ioremap(GPJ0DAT_PA, 4);

// 销毁动态映射
    iounmap(GPJ0CON_VA);
    iounmap(GPJ0DAT_VA);

    release_mem_region(GPJ0CON_PA, 4);
    release_mem_region(GPJ0DAT_PA, 4);

寄存器读写接口

考虑到尽可能强的程序移植性,应使用内核提供的专有的读写接口来读写寄存器。readb/readw/readl分别表示读取1Byte/1Word(2Byte)/2Word(4Bye),write函数同理。同时内核也提供了ioread8/ioread16/ioread32等价的接口。以4Byte的接口为例。

readl

代码语言:javascript复制
/* asm/io.h */
readl(c)

c: 寄存器虚拟地址

writel

代码语言:javascript复制
/* asm/io.h */
writel(v, c)

v: 写入寄存器的值

c: 寄存器虚拟地址

gpiolib框架

对外接口的标准头文件为linux/gpio.h,其中根据是否定义CONFIG_GENERIC_GPIO宏来判断是否支持gpiolib。定义了CONFIG_GENERIC_GPIO宏则包含头文件asm/gpio.h,而asm/gpio.h中又包含了相应平台下的头文件mach/gpio.h,其中又包含了asm-generic/gpio.h,该头文件才是真正的接口导出。gpiolib的代码实现在driver/gpio/gpiolib.c路径下。

gpio_request

申请GPIO

代码语言:javascript复制
/* linux/gpio.h */
int gpio_request(unsigned gpio, const char *label)

gpio: gpio编号,相关宏定义在linux/gpio.h

label: 名称

返回值: 成功返回0,失败返回负值

gpio_free

释放GPIO

代码语言:javascript复制
/* linux/gpio.h */
void gpio_free(unsigned gpio)

gpio: gpio编号,相关宏定义在linux/gpio.h

gpio_direction_input

设置GPIO为输入模式

代码语言:javascript复制
/* linux/gpio.h */
int gpio_direction_input(unsigned gpio)

gpio: gpio编号,相关宏定义在linux/gpio.h

返回值: 成功返回0,失败返回负值

gpio_direction_output

设置GPIO为输出模式

代码语言:javascript复制
/* linux/gpio.h */
int gpio_direction_output(unsigned gpio, int value)

gpio: gpio编号,相关宏定义在linux/gpio.h

value: 设置为输出模式时的初始值

返回值: 成功返回0,失败返回负值

gpio_set_value

设置(写)GPIO的值

代码语言:javascript复制
/* linux/gpio.h */
void __gpio_set_value(unsigned gpio, int value)
#define gpio_set_value  __gpio_set_value

gpio: gpio编号,相关宏定义在linux/gpio.h

value: 设置的值

返回值: 成功返回0,失败返回负值

gpio_get_value

获取(读)GPIO的值

代码语言:javascript复制
/* linux/gpio.h */
int __gpio_get_value(unsigned gpio)
#define gpio_get_value  __gpio_get_value

gpio: gpio编号,相关宏定义在linux/gpio.h

返回值: 获取的值

使用示例

代码语言:javascript复制
if (gpio_request(S5PV210_GPJ0(3), "gpj0.3") < 0) {    // 申请GPIO
    printk(KERN_ERR "gpio_request failedn");
} else {
    gpio_direction_output(S5PV210_GPJ0(3), 1);    // 设置为输出模式,并且默认输出为1
}

// 释放GPIO
gpio_free(S5PV210_GPJ0(3));

// 设置GPIO的值为1
gpio_set_value(S5PV210_GPJ0(3), 1);

gpio_request_array

功能同gpio_request,不同的是一次性申请多个

代码语言:javascript复制
/* linux/gpio.h */
int gpio_request_array(struct gpio *array, size_t num)

gpio_free_array

功能同gpio_free,不同的是一次性释放多个

代码语言:javascript复制
/* linux/gpio.h */
void gpio_free_array(struct gpio *array, size_t num)

本文作者: Ifan Tsai  (菜菜)

本文链接: https://cloud.tencent.com/developer/article/2164588

版权声明: 本文采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。转载请注明出处!

0 人点赞