前言
GPIO是嵌入式开发中常用的一个模块,Linux下的任何外设驱动,最终目的都是要配置相应的硬件寄存器。在Linux中,不管是内核空间代码,还是用户空间代码,访问的都是虚拟地址。
gpio子系统是Linux内核中用于管理GPIO资源的一套系统,提供了很多GPIO相关的API接口,虽然方便驱动开发者使用,但最终还是得操作寄存器;在使用此系统前,需要向内核注册这一套操作的方法。
pinctrl子系统通常用于管理SoC中各PIN脚的复用功能和电气特性。对于 ZYNQ MPSoC 来说,使用了 vivado 图形化完成了对 PIN 的配置并在 fsbl 阶段将
配置信息写入了硬件寄存器中,所以不需要在内核阶段进行配置。
一、MMU是什么?
开始前我们要了解什么是MMU。Linux中一个重要的部分就是MMU(内存管理单元),其作用是将虚拟空间映射到物理空间,让我们可以方便的操作各个硬件。像一般32位的系统,虚拟地址范围即为2^32=4GB。这样我们就可以通过程序中的虚拟地址对硬件进行操作。
二、操作GPIO
内核中操作GPIO有两种方法,一种是比较硬核的不使用linux为我们提供的标准GPIO接口,还有一种就是用标准接口,下面来说明这两种方式。
1.不使用GPIO标准接口
既然不用标准接口,我们就只能通过看数据手册,查找对应寄存器的地址和各bit对应功能,映射地址的方式操作对应的物理设备,主要用到的接口有:
将一个IO地址空间映射到内核的虚拟地址空间
#define ioremap(phys_addr,size) __arm_ioremap((phys_addr), (size), MT_DEVICE)
void __iomem *__ioremap(unsigned long phys_addr, size_t size, unsigned long flags);
phys_addr:物理起始地址
size:映射内存空间大小
返回虚拟地址首地址
释放映射:
void iounmap (volatile void __iomem *addr);
addr:虚拟地址首地址
读操作:
u8 readb(const volatile void __iomem *addr); 8bit
u16 readw(const volatile void __iomem *addr); 16bit
u32 readl(const volatile void __iomem *addr); 32bit
写操作:
void writeb(u8 value, volatile void __iomem *addr);
void writew(u16 value, volatile void __iomem *addr);
void writel(u32 value, volatile void __iomem *addr);
例子:
假设有个GPIO口控制LED灯的开关,寄存器地址为0x3000001C,第12bit为0时gpio拉低,1时拉高。
#define GPIO_LED_REG ((unsigned int)0x3000001C)
unsigned int *addr = (unsigned int *)ioremap(GPIO_LED_REG,4);
int val = 0;
拉低:
val = readl(addr);
val &= ~(0x01U<<12);
writel(val,addr);
拉高:
val = readl(addr);
val |= (0x01U<<12);
writel(val,addr);
iounmap(GPIO_LED_REG);
2.使用GPIO标准接口
代码如下(示例):
1.gpio申请
int gpio_request(unsigned gpio, const char *label)
成功返回0,失败返回负数
释放gpio
void gpio_free(unsigned gpio)
2.GPIO的方向设置
int gpio_direction_input(unsigned gpio);
int gpio_direction_output(unsigned gpio, int value);
3.GPIO输出值设置与输入值获取
/* GPIO INPUT: return zero or nonzero */
int gpio_get_value(unsigned gpio);
/* GPIO OUTPUT */
void gpio_set_value(unsigned gpio, int value);
例子:
假设该GPIO对应Soc上的引脚为66,则可以有:
int led_gpio = 66;
gpio_request(led_gpio,"LED");
gpio_direction_output(led_gpio, 0);
拉高:
gpio_set_value(led_gpio,1);
拉低:
gpio_set_value(led_gpio,0);
gpio_free(led_gpio);
三、与gpio相关的OF函数
1、 of_gpio_named_count 函数
of_gpio_named_count 函数用于获取设备树某个属性里面定义了几个 GPIO 信息,要注意
的是空的 GPIO 信息也会被统计到,比如:
gpios = <0
&gpio1 1 2
0
&gpio2 3 4>;
上述代码的“ gpios”节点一共定义了 4 个 GPIO,但是有 2 个是空的,没有实际的含义。
通过 of_gpio_named_count 函数统计出来的 GPIO 数量就是 4 个,此函数原型如下:
int of_gpio_named_count(struct device_node *np, const char *propname)
函数参数和返回值含义如下:
np:设备节点。
propname:要统计的 GPIO 属性。
返回值: 正值,统计到的 GPIO 数量;负值,失败。
2、 of_gpio_count 函数
和 of_gpio_named_count 函数一样,但是不同的地方在于,此函数统计的是“ gpios”这个
属性的 GPIO 数量,而 of_gpio_named_count 函数可以统计任意属性的 GPIO 信息,函数原型
如下所示:
int of_gpio_count(struct device_node *np)
函数参数和返回值含义如下:
np:设备节点。
返回值: 正值,统计到的 GPIO 数量;负值,失败。
3、 of_get_named_gpio 函数
此函数获取 GPIO 编号, gpio 子系统为了方便管理系统中的 GPIO 资源,每一个 GPIO 管
脚都有一个对应的编号, Linux 内核中关于 GPIO 的 API 函数都要使用 GPIO 编号,此函数会
将设备树中类似<&gpio0 38 GPIO_ACTIVE_LOW>的属性信息转换为对应的 GPIO 编号,此函
数在驱动中使用很频繁!函数原型如下:
int of_get_named_gpio(struct device_node *np, const char *propname, int index)
函数参数和返回值含义如下:
np:设备节点。
propname:包含要获取 GPIO 信息的属性名。
index: GPIO 索引,因为一个属性里面可能包含多个 GPIO,此参数指定要获取哪个
GPIO 的编号,如果只有一个 GPIO 信息的话此参数为 0。
返回值: 正值,获取到的 GPIO 编号;负值,失败。
四、GPIO驱动
通常GPIO在设备树中的节点如下:
gpio: gpio@ff0a0000 {
compatible = "xlnx,zynqmp-gpio-1.0"; //对应驱动程序
status = "disabled";
#gpio-cells = <0x2>; //类似#address-cells,使用gpio时,要传递两个参数过去<&gpio 38 HIGH>
interrupt-parent = <&gic>;
interrupts = <0 16 4>;
interrupt-controller;
#interrupt-cells = <2>;
reg = <0x0 0xff0a0000 0x0 0x1000>; //基地址为0xFF0A0000(查手册可知)
gpio-controller; //表明是个GPIO控制器,对应驱动是GPIO驱动
power-domains = <&zynqmp_firmware 46>;
};
通常驱动程序都在内核目录的driver/gpio/下,gpiolib-开始的是就是gpio驱动的核心文件了。
五、设备树与驱动的使用
首先是在设备树中的常见节点案例:
gpio-led {
compatible = "test_led";
status = "okay";
default-status = "off";
led-gpio = <&gpio 38 GPIO_ACTIVE_HIGH>;
};
在驱动中,我们可以这样使用内核提供的API写来控制引脚的方法:
struct led_dev {
struct device_node * nd;
int gpio_led;
};
struct led_dev led = {0};
int __init led_init(void)
{
led.nd = of_find_node_by_name(NULL,"gpio-led");
if(led.nd == NULL)
{
printk("led gpio of_find_node_by_name = NULL !\n");
return -1;
}
ret = of_property_read_string(led.nd,"status",&str);
if(ret < 0)
printk("led gpioread status failed !\n");
else
printk("led gpio read status success , str = %s\n",str);
if(!strcmp(str,"okay"))
printk("led gpio status on !\n");
else
{
printk("led gpio status off !\n");
return -1;
}
led.gpio_led = of_get_named_gpio(led.nd,"led-gpio",0);
if(!gpio_is_valid(led.gpio_led))
{
printk("led gpio of_get_named_gpio failed !\n");
return -1;
}
printk("led gpio = %d\n",led.gpio_led);
ret = gpio_request(led.gpio_led,"LED-GPIO");
if(ret)
{
printk("led gpio gpio_request failed !\n");
return -1;
}
gpio_direction_output(led.gpio_led,0);
ret = of_property_read_string(led.nd,"default-status",&str);
if(ret < 0)
{
printk("led read default-status failed !\n");
gpio_set_value(led.gpio_led,0);
}
else
{
printk("led read default-status success , str = %s\n",str);
if(!strcmp(str,"on"))
gpio_set_value(led.gpio_led,1);
else
gpio_set_value(led.gpio_led,0);
}
printk("led init success ~~~\n");
return 0;
}
void __exit led_exit(void)
{
gpio_free(led.gpio_led);
return;
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
则加载驱动有如下打印:
总结
以上就是今天要讲的内容,本文简单介绍了内核中对GPIO的使用,制作不易,多多包涵。