1. 程式人生 > >關於樹莓派核心編譯和驅動編寫(2)

關於樹莓派核心編譯和驅動編寫(2)

前幾天搞定了樹莓派2的核心編譯執行工作,這幾天集中研究了樹莓派的gpio操作,那麼現在是時候把它搞出來了

我們知道,gpio操作是驅動的基礎操作,那麼研究一塊板子,一個版本的核心,首先要從gpio入手。

基礎的gpio操作有以下:

ioremap對映暫存器地址,readl讀取暫存器上的數值,writel將資料寫入暫存器。

用核心提供的gpio操作巨集

現在接觸到了一種新的gpio操作方式:使用gpio_chip結構體,用面向物件的思維對gpio的功能進行操作。

在我使用的3.18.16核心中,樹莓派的晶片bcm2836的gpio操作是由核心中的bcm2708的gpio操作進行的,大概可以想象兩者的相似性很高。

從網上找到的驅動程式碼附上。

在最開始研究的時候,看晶片手冊,使用其提供的地址進行地址對映,然後改寫,劇情很狗血,就是失敗,無論是匯流排地址還是實體地址,都是失敗的。

原因不明,最後在網上找到了下面的程式碼,編譯執行之後成功了,分析得出其使用gpio_chip結構體的結論。

#include <linux/kernel.h>  
#include <linux/module.h>
#include <linux/device.h> 
#include <mach/platform.h>       
#include <linux/platform_device.h>
#include <linux/types.h>  
#include <linux/fs.h>   
#include <linux/ioctl.h>  
#include <linux/cdev.h>  
#include <linux/delay.h>  
#include <linux/uaccess.h>
#include <linux/init.h> 
#include <linux/gpio.h>
 
//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命令列。有興趣的朋友可以加關注。