关于树莓派内核编译和驱动编写(2)

前几天搞定了树莓派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命令行。有兴趣的朋友可以加关注。

你可能感兴趣的:(树莓派)