前几天搞定了树莓派2的内核编译运行工作,这几天集中研究了树莓派的gpio操作,那么现在是时候把它搞出来了
我们知道,gpio操作是驱动的基础操作,那么研究一块板子,一个版本的内核,首先要从gpio入手。
基础的gpio操作有以下:
ioremap映射寄存器地址,readl读取寄存器上的数值,writel将数据写入寄存器。
用内核提供的gpio操作宏
现在接触到了一种新的gpio操作方式:使用gpio_chip结构体,用面向对象的思维对gpio的功能进行操作。
在我使用的3.18.16内核中,树莓派的芯片bcm2836的gpio操作是由内核中的bcm2708的gpio操作进行的,大概可以想象两者的相似性很高。
从网上找到的驱动代码附上。
在最开始研究的时候,看芯片手册,使用其提供的地址进行地址映射,然后改写,剧情很狗血,就是失败,无论是总线地址还是物理地址,都是失败的。
原因不明,最后在网上找到了下面的代码,编译运行之后成功了,分析得出其使用gpio_chip结构体的结论。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
//class声明内核模块驱动信息,是UDEV能够自动生成/dev下相应文件
static dev_t pi_led_devno; //设备号
static struct class *pi_led_class;
static struct cdev pi_led_class_dev;
struct gpio_chip *gpiochip;
#define led_pin 17 //gpio 4
static int is_right_chip(struct gpio_chip *chip, void *data) //此函数是 gpiochip_find函数调用内核,之后由内核调用的一个查找gpio_chip结构体的函数
{
if (strcmp(data, chip->label) == 0)
return 1;
return 0;
}
//内核加载后的初始化函数.
static int __init pi_led_init(void)
{
struct device *dev;
int major; //自动分配主设备号
major = alloc_chrdev_region(&pi_led_devno,0,1,DRIVER_NAME);
//register_chrdev 注册字符设备使系统知道有LED这个模块在.
cdev_init(&pi_led_class_dev, &pi_led_dev_fops);
major = cdev_add(&pi_led_class_dev,pi_led_devno,1);
//注册class
pi_led_class = class_create(THIS_MODULE,DRIVER_NAME);
dev = device_create(pi_led_class ,NULL,pi_led_devno,NULL,DRIVER_NAME);
gpiochip = gpiochip_find("bcm2708_gpio", is_right_chip); //此函数是用来找到寄存器的gpio_chip结构体的函数,此函数由内核实现 *(1)
gpiochip->direction_output(gpiochip, led_pin, 1);
gpiochip->set(gpiochip, led_pin, 1); //*(2)
printk("pi led init ok!\n");
return 0;
}
//内核卸载后的销毁函数.
void pi_led_exit(void)
{
gpiochip->set(gpiochip, led_pin, 0);
gpio_free(led_pin);
device_destroy(pi_led_class,pi_led_devno);
class_destroy(pi_led_class);
cdev_del(&pi_led_class_dev);
unregister_chrdev_region(pi_led_devno, 1);
printk("pi led exit ok!\n");
}
module_init(pi_led_init);
module_exit(pi_led_exit);
MODULE_DESCRIPTION("Rasp Led Driver");
MODULE_AUTHOR("52pi.net");
MODULE_LICENSE("GPL");
*(1)"bcm2708_gpio"字符串是要使用的gpio_chip结构体的lable成员,可以理解为其名字,当找到这个gpio结构体之后,可以看到其在bcm2708_gpio.c文件当中,但是这个文件有两个,分别在mach-bcm2708和mach-bcm2709两个文件夹中,简单看了一下,两个文件基本相同,在里面找到:
static void bcm2708_gpio_set(struct gpio_chip *gc, unsigned offset, int value)
函数,此函数就是*(2)中调用的函数的最终目的地,其实现方式仍然是readl和writel函数,那么也就是说,这两个函数仍然能够使用,我对其进行了打印修改,重新编译内核,内核打印如下信息
[ 52.190213] *********addr---0xf320001c //实际读取和写入的地址
[ 52.195631] *******1****0x6770696f //读取寄存器上的数据
[ 52.200735] *******2****0x6770696f
芯片手册中描述了gpio对应寄存器物理地址应该是0x2020 001c,总线地址0x7e20 001c
查看打印信息发现地址跟着两个都不同,分析可能是bcm2708的对应地址。或者是由内核映射好的寄存器地址,后者可能性更大。
那么之前使用ioremap映射的地址不能用的原因不出意外应该是内核对其寄存器进行限制,不允许随意映射了,这个我还没有找到相关证据。
于是按照0xf320001c的地址重写了自己以前用readl和writel实现的驱动,发现这次成功了,并且重启树莓派之后,这个地址还能用,那么我分析这个
地址已经被内核控制住了,不能够重新映射,或者说即使重新映射,也不能够使用。
很多朋友使用三种应用层的gpio调用方式对其进行操作,然而这并不是内核驱动,而且我分析过wiringPi的源代码,发现其是通过调用mem的驱动程序进行地址映射,从而达到操作寄存器的目的。
mem的设备节点是/dev/mem,主设备号为1,内存,属于字符设备,可以通过内核源码的major 查看主设备号为1的宏,然后搜索此宏,就能找到其驱动程序。
现在找到了一个gpio寄存器的可操作地址,那么有时间我还会分析为什么是这个地址,以及接下来可以对其进行操作,可能写一些其他的驱动程序,但可能不会再发上来,下一步要开始研究uboot,目的是希望能够启动一个bootloader命令行。有兴趣的朋友可以加关注。