设备驱动程序是软件概念和硬件电路之间的一个抽象层,软件操作硬件的关键就是对寄存器的操作。笔者使用的S5PV210是IO与内存统一编址的,在裸机中直接操作IO端口的物理地址,而在驱动中必须使用虚拟地址。直接基于IO的虚拟地址用指针解引用的方式来读写有两种方式,静态映射和动态映射。除了可以直接将指针解引用的方式,内核中提供了专用的读写接口来读写寄存器。考虑到GPIO作为硬件资源,存在着被多个驱动使用,还有复用的问题,所以内核提供了GPIO驱动gpiolib框架来统一管控GPIO资源,gpiolib在内核中作为一个驱动所实现。
静态映射操作寄存器
内核在启动的时候会建立一张静态映射表,三星版本内核中S5PV210的静态映射主表位于arch/arm/plat-s5p/include/plat/map-s5p.h
,其本质就是一堆宏定义。CPU在安排寄存器地址时是按照模块去区分的。每一个模块内部很多寄存器地址是连续的。该表定义的是各个模块的基地址,具体的寄存器的地址是通过基地址 偏移量来寻址的。
代码语言:javascript复制map-s5p.h
// 静态映射表的各模块总映射表: (此处的地址皆为虚拟地址)
#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
。
代码语言:javascript复制regs-gpio.h
/* 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
。
代码语言:javascript复制gpio-bank.h
/* 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直接当做一个变量读写即可。
// 静态映射操作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 国际许可协议 进行许可。转载请注明出处!