1. 程式人生 > >Ti437x LED匯流排驅動模型程式+裝置樹

Ti437x LED匯流排驅動模型程式+裝置樹

本文主要記錄AM437X驅動的LED。含簡單的字元裝置驅動、裝置驅動模型、裝置樹以及LED子系統。

目前就Linux驅動的理解是:

Linux驅動 = 裸機 + 框架

關於框架,目前的理解是:

以LED驅動為例,之前印象中就是韋老大的思路,現在init裡註冊、硬體初始化,然後應用層open()、read()就呼叫了file_operations裡面的drv_open()、drv_write()等,算是最簡單的驅動框架。
然後韋老大又提及了匯流排裝置驅動模型,將裝置和驅動分離,感受到了新的框架。不久前,簡單接觸了下裝置樹,感覺就是匯流排裝置驅動模型的修改(升級),將原來的裝置部分,不再單獨放在程式碼裡,而是放在dts裡面,開機載入,然後驅動匹配獲取硬體資源。因此,感覺驅動的框架在一步一步的發展,優化,最原始的註冊、open等框架,還是不變。
同時,瞭解到了除輸入子系統的其它子系統,加深了對這一模式的理解。感覺就是,將某個硬體資源無縫的融入現有的環境中,而無須改變應用層的程式。

這就是目前的一點小小理解吧,算是打開了個入口,希望以後瞭解得更加全面、細緻。

1.搭建開發環境

1.1安裝TI_SDK

先在TI官網下載ti-processor-sdk-linux-am437x-evm-01.00.00.03-Linux-x86-Install.bin
在Ubuntu(only Ubuntu 12.04 LTS and Ubuntu 14.04 LTS are supported)下,對該檔案加入可執行許可權,然後直接執行。安裝目錄選擇預設即可。完成之後,便在當前使用者的home目錄生成了所有所需檔案。

1.2編譯核心

在當前生成ti-processor-sdk-linux-am437x-evm-01.00.00.03目錄下,有個Makefile,開啟後可以看到相關的編譯選項,如:

  • 編譯全部檔案:make all
  • 編譯核心:make linux
  • 編譯u-boot:make u-boot-spl
  • 以及make的依賴:-include Rules.make。在本層目錄裡,開啟Rules.make,可以知道核心的預設配置檔案:
#defconfig
DEFCONFIG=tisdk_am437x-evm_defconfig

通過查詢,tisdk_am437x-evm_defconfig在~/ti-processor-sdk-linux-am437x-evm-01.00.00.03/board-support/linux-3.14.43+gitAUTOINC+875c69b2c3-g875c69b/arch/arm/configs

裡。
這裡通過修改該配置檔案,然後重新編譯核心,即可關閉系統LED相關的驅動,在後面自己寫LED驅動時,防止互相干擾。
因此將tisdk_am437x-evm_defconfig配置檔案裡的所有有關LED的配置都關閉掉。

最後在頂層目錄執行make linux,編譯完成後,生成~/ti-processor-sdk-linux-am437x-evm-01.00.00.03/board-support/linux-3.14.43+gitAUTOINC+875c69b2c3-g875c69b/arch/arm/boot/zImage檔案。

1.3燒寫SD卡

回到~/ti-processor-sdk-linux-am437x-evm-01.00.00.03/bin下,TI製作了很多指令碼,其中的create-sdcard.sh就是製作SD卡的。Ubuntu插上SD卡,然後切換成root使用者,執行該指令碼,根據提示一路選擇下去即可。

這裡燒寫完了,測試發現並沒有使用之前編譯的核心,分析指令碼後發現,該指令碼直接使用的~/ti-processor-sdk-linux-am437x-evm-01.00.00.03/filesystem下的tisdk-rootfs-image-am437x-evm.tar.gz。指令碼將該檔案作為根檔案系統放入SD卡,因此並沒有使用之前編譯的核心。解決方法要麼在執行指令碼的過程中根據提示輸入相關的路徑,要麼在製作好SD卡後,將編譯好的核心覆蓋掉SD卡的核心即可。我選擇的後者:cp ~/ti-processor-sdk-linux-am437x-evm-01.00.00.03/board-support/linux-3.14.43+gitAUTOINC+875c69b2c3-g875c69b/arch/arm/boot/zImage /media/hceng/rootfs/boot/

最後將製作好的SD卡插上開發板啟動即可。

2.簡單的字元驅動

先記錄下幾個重要型別或結構體:

  • 表示裝置號(32位機中:高12位表示主裝置號,低20位表示次裝置號)
typedef __kernel_dev_t  dev_t; 
  • 描述字元裝置
struct cdev {
    struct kobject kobj; //內嵌kobject結構體,用於裝置驅動模型管理
    struct module *owner; //包含指向該結構的模組的指標,用於引用計數
    const struct file_operations *ops; //指向字元裝置操作函式集的指標
    struct list_head list; //該結構將使用該驅動的字元裝置連線成一個連結串列
    dev_t dev; //該字元裝置的其實裝置號,一個裝置可能有多個裝置號
    unsigned int count; //使用該字元裝置驅動的裝置數量
};
  • 描述類
struct class{
    const char *name; //類名稱
    struct module *owner; //對應模組
    struct subsystem subsys; //對應的subsystem;
    struct list_head children; //class_device連結串列
    struct list_head  interfaces; //class_interface連結串列
    struct semaphore  sem; /用於同步的訊號鎖
    struct class_attribute *class_attrs; //類屬性
    int (*uevent)(struct class_device *dev,char **envp,int num_envp,
                  char *buffer,int buffer_size); //事件
    void (*release)(struct class_device *dev); //釋放類裝置
    void (*class_release)(struct class *class); //釋放類
}

總結下,目前理解的字元裝置編寫流程:

1)驅動載入函式:xx_drv_init()
  1.1)申請裝置號:alloc_chrdev_region()
  1.2)cde初始化(繫結fops):cdev_init()
  1.3)註冊到核心:cdev_add()
  1.4)建立類:class_create()
  1.5)向類中新增裝置(mdev自動建立裝置節點):device_create()
  1.6)硬體相關(記憶體對映):ioremap()
2)驅動解除安裝函式:xx_drv_exit()
  2.1)移除裝置:device_destroy()
  2.2)移除類:class_destroy()
  2.3)登出cdev:cdev_del()
  2.4)釋放裝置號:unregister_chrdev()
  2.5)釋放記憶體:iounmap()
3)必要修飾:module_init(xx_drv_init);module_exit(xx_drv_exit);MODULE_LICENSE("GPL");
4)構造file_operations:struct file_operations xx_drv_fops;
5)實現file_operations裡每個函式:xx_open()、xx_write()……

2.1驅動程式碼

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/device.h>
#include <linux/cdev.h>


#define TI_LEDS_CNT     4

int major;
static struct cdev leds_cdev;
static struct class *leds_cls;

static volatile unsigned long *PRCM_CM_PER_GPIO5_CLKCTRL = NULL;  
static volatile unsigned long *CTRL_CONF_UART3_RXD = NULL;  
static volatile unsigned long *CTRL_CONF_UART3_TXD = NULL;  
static volatile unsigned long *CTRL_CONF_UART3_CTSN = NULL;
static volatile unsigned long *CTRL_CONF_UART3_RTSN = NULL;
static volatile unsigned long *GPIO_OE = NULL;
static volatile unsigned long *GPIO_SETDATAOUT = NULL;
static volatile unsigned long *GPIO_DATAOUT = NULL; 

static int leds_drv_open(struct inode *inode, struct file *file)  
{    
    int minor = iminor(file->f_inode);

    printk(KERN_INFO"%s OK.\n",__func__);

    *PRCM_CM_PER_GPIO5_CLKCTRL  = (0x01<<1);

    *CTRL_CONF_UART3_RXD  &= ~(0x7<<0 | 0x01<<16 | 0x01<<17 | 0x01<<18);
    *CTRL_CONF_UART3_RXD  |=  (0x7<<0 | 0x01<<17);

    *GPIO_OE              &= ~(0x01<<minor);
    *GPIO_SETDATAOUT      |=  (0x01<<minor);

    return 0;     
}   

static ssize_t leds_drv_write(struct file *file, const char __user *user_buf, size_t count, loff_t * ppos)  
{  
    int  minor = iminor(file->f_inode);
    char buf;  

    printk(KERN_INFO"%s OK.\n",__func__);

    if(count != 1){
        printk(KERN_INFO"write count != 1.\n"); 
        return 1;
    }

    if (copy_from_user(&buf, user_buf, count))
        return -EFAULT;

    if (0x01 == buf)  
        *GPIO_DATAOUT |=  (0x01<<minor);    
    else if(0x00 == buf)
        *GPIO_DATAOUT &= ~(0x01<<minor);

    return 0;  
}  

static struct file_operations leds_fops = {
    .owner  =   THIS_MODULE,  
    .open   =   leds_drv_open,     
    .write  =   leds_drv_write,    
};

static int leds_drv_init(void)
{
    //1.申請裝置號
    dev_t devid;

    printk(KERN_INFO"%s OK.\n",__func__);

    if(alloc_chrdev_region(&devid, 0, TI_LEDS_CNT, "ti_leds") < 0)
    {
        printk(KERN_INFO"%s ERROR.\n",__func__);
        goto error;
    }

    major = MAJOR(devid);

    //2.註冊到系統中
    cdev_init(&leds_cdev, &leds_fops);        
    cdev_add(&leds_cdev, devid, TI_LEDS_CNT);   

    leds_cls = class_create(THIS_MODULE, "ti_leds");

    device_create(leds_cls, NULL, MKDEV(major, 0), NULL, "ti_led0"); 
    device_create(leds_cls, NULL, MKDEV(major, 1), NULL, "ti_led1"); 
    device_create(leds_cls, NULL, MKDEV(major, 2), NULL, "ti_led2"); 
    device_create(leds_cls, NULL, MKDEV(major, 3), NULL, "ti_led3");

    //3.硬體相關
    PRCM_CM_PER_GPIO5_CLKCTRL = ioremap(0x44DF8800+0x498, 0x04*1);

    CTRL_CONF_UART3_RXD       = ioremap(0x44E10000+0xA28, 0x04*4);
    CTRL_CONF_UART3_TXD       = CTRL_CONF_UART3_RXD + 1;
    CTRL_CONF_UART3_CTSN      = CTRL_CONF_UART3_RXD + 2;
    CTRL_CONF_UART3_RTSN      = CTRL_CONF_UART3_RXD + 3; 

    GPIO_OE                   = ioremap(0x48322000+0x134, 0x04); 
    GPIO_DATAOUT              = ioremap(0x48322000+0x13C, 0x04);
    GPIO_SETDATAOUT           = ioremap(0x48322000+0x194, 0x04);

error:
    unregister_chrdev_region(MKDEV(major, 0), TI_LEDS_CNT);

    return 0;
}

static void leds_drv_exit(void)
{
    unsigned i;
    printk(KERN_INFO"%s OK.\n",__func__);

    for(i=0;i<TI_LEDS_CNT;i++)
    {
        device_destroy(leds_cls,  MKDEV(major, i)); 
    }
    class_destroy(leds_cls);
    cdev_del(&leds_cdev);
    unregister_chrdev(major, "ti_leds"); 

    iounmap(PRCM_CM_PER_GPIO5_CLKCTRL);
    iounmap(CTRL_CONF_UART3_RXD);
    iounmap(GPIO_OE);
    iounmap(GPIO_DATAOUT);
    iounmap(GPIO_SETDATAOUT);
}

module_init(leds_drv_init);
module_exit(leds_drv_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("hceng <[email protected]>");
MODULE_DESCRIPTION("TI am437x board leds drvice");
MODULE_ALIAS("character device:ti_leds");
MODULE_VERSION("V1.0");

2.2測試程式碼(跑馬燈)

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>

#define msleep(x) usleep(x*1000)

int main(int argc, char **argv)
{
    int fd[4];
    int val = 0;
    int i = 0;
    //const char *dev[] = {"/dev/ti_led0", "/dev/ti_led1", "/dev/ti_led2", "/dev/ti_led3"};
    const char *dev[] = {"/dev/ti_led2", "/dev/ti_led0", "/dev/ti_led3", "/dev/ti_led1"};

    for(i=0; i<4; i++)
    {
        fd[i] = open(dev[i], O_RDWR);
        if (fd[i] < 0)
        {
            printf("can't open %s\n", *dev[i]);
            return 0;
        }
    }

    //leds off all.
    for(i=0; i<4; i++)
    {
        write(fd[i], &val, 1);
    }   

    //flicker leds.
    while(1)
    {
        val = !val;
        for(i=0; i<4; i++)
        {
            write(fd[i], &val, 1);
            msleep(300);
        }   
    }
}

2.3關於printk除錯

核心的printk定義瞭如下的列印等級:

#define KERN_EMERG        "<0>" /* system is unusable */
#define KERN_ALERT        "<1>" /* action must be taken immediately */
#define KERN_CRIT         "<2>" /* critical conditions */
#define KERN_ERR          "<3>" /* error conditions */
#define KERN_WARNING      "<4>" /* warning conditions */
#define KERN_NOTICE       "<5>" /* normal but significant condition */
#define KERN_INFO         "<6>" /* informational */
#define KERN_DEBUG        "<7>" /* debug-level messages */
  • 如果使用串列埠登陸,可通過修改/proc/sys/kernel/printk裡的引數進行設定:
echo "8  4    1    7" >/proc/sys/kernel/printk

上面的四個數字分別代表:

控制檯日誌級別:優先順序[s1] 高於該值的訊息將被列印至控制檯,[s1]數值越小,優先順序越高;
預設的訊息日誌級別:將用該優先順序來列印沒有優先順序的訊息;
最低的控制檯日誌級別:控制檯日誌級別可被設定的最小值(最高優先順序);
預設的控制檯日誌級別:控制檯日誌級別的預設值;

  • 如果使用SSH登陸,是無法顯示printk的列印資訊的,但列印的資料會被放在/var/log/messages/proc/kmsg中,利用這一特性,可以後臺執行tail命令進行偵測:
tail -f /var/log/messages &

缺點是不能設定列印等級,同時核心的其它資訊也會被打印出來。

3.裝置驅動模型驅動

關於裝置驅動模型前面以及有點了解,在寫驅動的時候,主要體現在將原本的硬體資源資訊抽取了出來,單獨放在了一個檔案裡,當兩個檔案的分別載入的時候,根據名字匹配,匹配成功則呼叫probe()函式,進行類似前面的init()進行初始化。其它的內容基本一樣,該幹嘛就幹嘛。

3.1驅動程式碼

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/delay.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <linux/device.h>
#include <linux/platform_device.h>


/**************************************************
  TI_BOARD
  ---------------------------------------------------
  Ball     Color       Mode             Pin
  ---------------------------------------------------
  H24     D7_Blue      0x07     uart3_txd(GPIO5_3)
  H25     D8_Blue      0x07     uart3_rxd(GPIO5_2)
  K24     D9_Green     0x07     uart3_rtsn(GPIO5_1)
  H22     D10_Red      0x07     uart3_ctsn(GPIO5_0)
 **************************************************/
static struct resource leds_resource[] = {  
//PRCM_CM_PER_GPIO5_CLKCTRL(498h)
    [0] = {  
        .start = 0x44DF8800,  
        .end   = 0x44DFFFFF, 
        .name  = "CM_PER",
        .flags = IORESOURCE_MEM,
    },  

//CTRL_CONF_UART3_RXD(A28h)、CTRL_CONF_UART3_TXD(A2Ch)、CTRL_CONF_UART3_CTSN(A30h)、CTRL_CONF_UART3_RTSN((A34h))
    [1] = {  
        .start = 0x44E10000,  
        .end   = 0x44E1FFFF, 
        .name  = "CONTROL_MODULE",
        .flags = IORESOURCE_MEM,
    },  
//GPIO_OE(134h)、GPIO_SETDATAOUT(194h)、GPIO_DATAOUT(13Ch)
    [2] = { 
        .start = 0x48322000,  
        .end   = 0x48322FFF, 
        .name  = "GOIP5",
        .flags = IORESOURCE_MEM,
    },
    [3] = { 
        .start = 0,  
        .end   = 3, 
        .name  = "GOIP5_PIN",
        .flags = IORESOURCE_IO,
    }

};  

static void leds_release(struct device * dev)  
{  
    printk(KERN_INFO"%s OK.\n",__func__);
}

static struct platform_device leds_dev = {
    .name          = "ti_am437x_leds_platform",
    .id            = -1,  
    .num_resources = ARRAY_SIZE(leds_resource),  
    .resource      = leds_resource,  
    .dev = {   
        .release = leds_release,   
    },     
};

static int leds_dev_init(void)  
{  
    printk(KERN_INFO"%s OK.\n",__func__);
    return platform_device_register(&leds_dev);;  
}  

static void leds_dev_exit(void)  
{  
    printk(KERN_INFO"%s OK.\n",__func__);
    platform_device_unregister(&leds_dev);  
}  


module_init(leds_dev_init);
module_exit(leds_dev_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("hceng <[email protected]>");
MODULE_DESCRIPTION("TI am437x board leds drvice");
MODULE_ALIAS("platform:ti_leds");
MODULE_VERSION("V2.0");
#include <linux/module.h>  
#include <linux/version.h>  
#include <linux/init.h>  
#include <linux/fs.h>  
#include <linux/interrupt.h>  
#include <linux/irq.h>  
#include <linux/sched.h>  
#include <linux/pm.h>  
#include <linux/sysctl.h>  
#include <linux/proc_fs.h>  
#include <linux/delay.h>  
#include <linux/platform_device.h>  
#include <linux/input.h>  
#include <linux/irq.h>  
#include <asm/uaccess.h>  
#include <asm/io.h> 
#include <linux/cdev.h>
#include <asm/uaccess.h>


#define TI_LEDS_CNT     4

int major;
static struct cdev leds_cdev;
static struct class *leds_cls;

static volatile unsigned long *PRCM_CM_PER_GPIO5_CLKCTRL = NULL;  
static volatile unsigned long *CTRL_CONF_UART3_RXD = NULL;  
static volatile unsigned long *CTRL_CONF_UART3_TXD = NULL;  
static volatile unsigned long *CTRL_CONF_UART3_CTSN = NULL;
static volatile unsigned long *CTRL_CONF_UART3_RTSN = NULL;
static volatile unsigned long *GPIO_OE = NULL;
static volatile unsigned long *GPIO_SETDATAOUT = NULL;
static volatile unsigned long *GPIO_DATAOUT = NULL;   

static int leds_drv_open(struct inode *inode, struct file *file)  
{    
    int minor = iminor(file->f_inode);

    printk(KERN_INFO"%s OK.\n",__func__);

    *PRCM_CM_PER_GPIO5_CLKCTRL  = (0x01<<1);

    *CTRL_CONF_UART3_RXD  &= ~(0x7<<0 | 0x01<<16 | 0x01<<17 | 0x01<<18);
    *CTRL_CONF_UART3_RXD  |=  (0x7<<0 | 0x01<<17);

    *GPIO_OE              &= ~(0x01<<minor);
    *GPIO_SETDATAOUT      |=  (0x01<<minor);

    return 0;     
}   

static ssize_t leds_drv_write(struct file *file, const char __user *user_buf, size_t count, loff_t * ppos)  
{  
    int minor = iminor(file->f_inode);
    char buf; 

    printk(KERN_INFO"%s OK.\n",__func__);

    if(count != 1){
        printk(KERN_INFO"write count != 1.\n"); 
        return 1;
    }

    if (copy_from_user(&buf, user_buf, count))
        return -EFAULT;

    if (0x01 == buf)  
        *GPIO_DATAOUT |=  (0x01<<minor);    
    else if(0x00 == buf)
        *GPIO_DATAOUT &= ~(0x01<<minor);

    return 0;  
}  


static struct file_operations leds_fops = {  
    .owner  =   THIS_MODULE,   
    .open   =   leds_drv_open,       
    .write  =   leds_drv_write,       
}; 


static int leds_probe(struct platform_device *pdev)  
{  
    struct resource *res;  
    dev_t devid;

    printk(KERN_INFO"%s OK.\n",__func__);

    //1.申請裝置號
    if(alloc_chrdev_region(&devid, 0, TI_LEDS_CNT, "ti_leds") < 0)
    {
        printk("%s ERROR\n",__func__);
        goto error;
    }

    major = MAJOR(devid);

    //2.註冊到系統中
    cdev_init(&leds_cdev, &leds_fops);        
    cdev_add(&leds_cdev, devid, TI_LEDS_CNT);   

    leds_cls = class_create(THIS_MODULE, "ti_leds");

    device_create(leds_cls, NULL, MKDEV(major, 0), NULL, "ti_led0"); 
    device_create(leds_cls, NULL, MKDEV(major, 1), NULL, "ti_led1"); 
    device_create(leds_cls, NULL, MKDEV(major, 2), NULL, "ti_led2"); 
    device_create(leds_cls, NULL, MKDEV(major, 3), NULL, "ti_led3");

    //3.硬體相關
    res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "CM_PER");
    if (!res) 
        return -EINVAL; 
    PRCM_CM_PER_GPIO5_CLKCTRL = ioremap(res->start+0x498, 0x04*1);

    res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "CONTROL_MODULE"); 
    if (!res) 
        return -EINVAL; 
    CTRL_CONF_UART3_RXD         = ioremap(res->start+0xA28, 0x04*4);
    CTRL_CONF_UART3_TXD         = CTRL_CONF_UART3_RXD + 1;
    CTRL_CONF_UART3_CTSN        = CTRL_CONF_UART3_RXD + 2;
    CTRL_CONF_UART3_RTSN        = CTRL_CONF_UART3_RXD + 3; 

    res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "GOIP5"); 
    if (!res) 
        return -EINVAL; 
    GPIO_OE                     = ioremap(res->start+0x134, 0x04); 
    GPIO_DATAOUT                = ioremap(res->start+0x13C, 0x04);
    GPIO_SETDATAOUT             = ioremap(res->start+0x194, 0x04);

    *PRCM_CM_PER_GPIO5_CLKCTRL  = (0x01<<1);//使能GPIO外設時鐘

error:
    unregister_chrdev_region(MKDEV(major, 0), TI_LEDS_CNT); 

    return 0;  
}  

static int leds_remove(struct platform_device *pdev)  
{  
    unsigned i;
    printk(KERN_INFO"%s OK.\n",__func__);

    for(i=0;i<TI_LEDS_CNT;i++)
    {
        device_destroy(leds_cls,  MKDEV(major, i)); 
    }

    class_destroy(leds_cls);
    cdev_del(&leds_cdev);
    unregister_chrdev(major, "ti_leds"); 

    iounmap(PRCM_CM_PER_GPIO5_CLKCTRL);
    iounmap(CTRL_CONF_UART3_RXD);
    iounmap(GPIO_OE);
    iounmap(GPIO_DATAOUT);
    iounmap(GPIO_SETDATAOUT);

    return 0;  
}

struct platform_driver leds_drv = {  
    .probe      = leds_probe,  
    .remove     = leds_remove,  
    .driver     = {  
        .name   = "ti_am437x_leds_platform",  
    }  
};  

static int leds_drv_init(void)  
{  
    printk(KERN_INFO"%s OK.\n",__func__);
    return platform_driver_register(&leds_drv);  
}  

static void leds_drv_exit(void)  
{  
    printk(KERN_INFO"%s OK.\n",__func__);
    platform_driver_unregister(&leds_drv);  
}  


module_init(leds_drv_init);
module_exit(leds_drv_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("hceng <[email protected]>");
MODULE_DESCRIPTION("TI am437x board leds drvice");
MODULE_ALIAS("platform:ti_leds");
MODULE_VERSION("V2.0");

測試程式同前面的一樣。

4.裝置樹驅動

4.1初識裝置樹

1)前面的匯流排裝置驅動模型中,硬體資源來自於leds_dev.c裡面的資訊,這樣會導致不同的板子,會新增不同的硬體資源資訊,造成核心的臃腫。
2)使用裝置樹後,核心不再包含硬體的描述,硬體描述放在單獨的DTS裡面,然後編譯成二進位制的DTB,在U-Boot啟動的時候載入進去,然後核心進行解析。
3)DTS、DTC和DTB之間的關係:
DTS經過DTC編譯得到DTB,DTB通過DTC反編譯得到DTS.

4)ARM中,所有的DTS檔案放在arch/arm/boot/dts目錄中,為了簡化,將Soc公用部分提取了出來作為dtsi,類似標頭檔案。
5)DTC編譯工具的原始碼在scripts/dtc目錄中,編譯核心時,編譯核心時,需要使能才能將原始碼編譯成工具,對應於scripts/dtc/Makefile"hostprogs-y:=dtc"。Ubuntu也可直接安裝DTC工具:

sudo apt-get install device-tree-compiler

6)核心的arch/arm/boot/dts/Makefile中,描述了當某種Soc被選中後,哪些.dtb會編譯出來。執行make dtbs,會根據arch/arm/Makefile編譯指定目標。
7)單獨編譯與反編譯:

./scripts/dtc/dtc -I dts -O dtb -o xxx.dtb arch/arm/boot/dts/xxx.dts   //dts->dtb
./scripts/dtc/dtc -I dtb -O dts -o xxx.dts arch/arm/boot/dts/xxx.dtb   //dtb->dts

8)後面認識深刻了,再總結總結。

4.2修改AM437x裝置樹

AM437x的裝置樹檔案在~/ti-processor-sdk-linux-am437x-evm-01.00.00.03/board-support/linux-3.14.43+gitAUTOINC+875c69b2c3-g875c69b/arch/arm/boot/dts/中,主要是am4372.dtsiam437x-sk-evm.dts

  • 我的目的是希望寫個裝置樹框架的LED程式,因此想讓am437x-sk-evm.dts乾淨點,只包含LED硬體描述,因此我需要刪除am437x-sk-evm.dts裡面的其它硬體描述。經過測試,am437x-sk-evm.dts裡面包含部分MMC的描述,一旦刪除將不能成功啟動核心。而且,後面除錯的時候,希望開發板通過NFS掛載的方式,直接載入編譯的驅動模組,因此需要保留網絡卡描述部分。最後,將MMC和網絡卡必須的部分,提取了出來,放在了am4372.dtsi中。精簡後的am437x-sk-evm.dts內容如下:
/* AM437x SK EVM */

/dts-v1/;

#include "am4372.dtsi"
#include <dt-bindings/pinctrl/am43xx.h>
#include <dt-bindings/gpio/gpio.h>

/ {
    model = "TI AM437x SK EVM";
    compatible = "ti,am437x-sk-evm","ti,am4372","ti,am43";

    led_pin {
        compatible    = "ti_leds";
        pinctrl-names = "default";
        pinctrl-0 = <&leds_pins>;
        am437x,led_gpio0 = <&gpio5 0 GPIO_ACTIVE_HIGH>;
        am437x,led_gpio1 = <&gpio5 1 GPIO_ACTIVE_HIGH>;
        am437x,led_gpio2 = <&gpio5 2 GPIO_ACTIVE_HIGH>;
        am437x,led_gpio3 = <&gpio5 3 GPIO_ACTIVE_HIGH>;
    };
};

&am43xx_pinmux {

    leds_pins: leds_pins {
        pinctrl-single,pins = <
            0x228 (PIN_OUTPUT | MUX_MODE7)  /* uart3_rxd.gpio5_2 */
            0x22c (PIN_OUTPUT | MUX_MODE7)  /* uart3_txd.gpio5_3 */
            0x230 (PIN_OUTPUT | MUX_MODE7)  /* uart3_ctsn.gpio5_0 */
            0x234 (PIN_OUTPUT | MUX_MODE7)  /* uart3_rtsn.gpio5_1 */
        >;
    };
};

&gpio5 {
    status = "okay";
};

額,在除錯的過程中,需要不斷編譯新的DTB和複製到SD卡的rootfs分割槽中,仿照前面寫了個指令碼進行自動編譯和複製,同時檢查檔案的生成時間間隔,實際中,確實減少了焦躁的重複操作。

4.3驅動程式碼

#include <linux/module.h>  
#include <linux/version.h>  
#include <linux/init.h>  
#include <linux/fs.h>  
#include <linux/interrupt.h>  
#include <linux/irq.h>  
#include <linux/sched.h>  
#include <linux/pm.h>  
#include <linux/sysctl.h>  
#include <linux/proc_fs.h>  
#include <linux/delay.h>  
#include <linux/platform_device.h>  
#include <linux/input.h>  
#include <linux/irq.h>  
#include <asm/uaccess.h>  
#include <asm/io.h> 
#include <linux/cdev.h>
#include <asm/uaccess.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>


#define TI_LEDS_CNT     4

int major;
static struct cdev leds_cdev;
static struct class *leds_cls;
static int led0,led1,led2,led3; 

static int leds_drv_open(struct inode *inode, struct file *file)  
{    
    printk(KERN_INFO"%s OK.\n",__func__); 

    return 0;     
}   

static ssize_t leds_drv_write(struct file *file, const char __user *user_buf, size_t count, loff_t * ppos)  
{  
    int minor = iminor(file->f_inode); 
    char buf;

    printk(KERN_INFO"%s OK.\n",__func__);

    if(count != 1){
        printk(KERN_INFO"write count != 1.\n"); 
        return 1;
    }

    if (copy_from_user(&buf, user_buf, count))
        return -EFAULT;

    if(0x01 == buf)
    {
        switch(minor){
        case 0:
            gpio_set_value(led0, 0);
            break;
        case 1:
            gpio_set_value(led1, 0);
            break;
        case 2:
            gpio_set_value(led2, 0);
            break;
        case 3:
            gpio_set_value(led3, 0);
            break;
        default:
            printk(KERN_INFO"%s receive minor error.\n",__func__);
        }                       
    }
    else if(0x00 == buf)
    {
        switch(minor){
        case 0:
            gpio_set_value(led0, 1);
            break;
        case 1:
            gpio_set_value(led1, 1);
            break;
        case 2:
            gpio_set_value(led2, 1);
            break;
        case 3:
            gpio_set_value(led3, 1);
            break;
        default:
            printk(KERN_INFO"%s receive minor error\n",__func__);
        }       
    }

    return 0;  
}  

static struct file_operations leds_fops = {  
    .owner  =   THIS_MODULE,   
    .open   =   leds_drv_open,       
    .write  =   leds_drv_write,       
}; 

static int leds_probe(struct platform_device *pdev)  
{  
    struct device *dev = &pdev->dev;
    dev_t devid;

    printk(KERN_INFO"%s OK.\n",__func__);

    //1.申請裝置號
    if(alloc_chrdev_region(&devid, 0, TI_LEDS_CNT, "ti_leds") < 0)
    {
        printk(KERN_INFO"%s ERROR.\n",__func__);
        goto error;
    }

    major = MAJOR(devid);

    //2.註冊到系統中
    cdev_init(&leds_cdev, &leds_fops);        
    cdev_add(&leds_cdev, devid, TI_LEDS_CNT);   

    leds_cls = class_create(THIS_MODULE, "ti_leds");

    device_create(leds_cls, NULL, MKDEV(major, 0), NULL, "ti_led0"); 
    device_create(leds_cls, NULL, MKDEV(major, 1), NULL, "ti_led1"); 
    device_create(leds_cls, NULL, MKDEV(major, 2), NULL, "ti_led2"); 
    device_create(leds_cls, NULL, MKDEV(major, 3), NULL, "ti_led3");

    //3.硬體相關
    led0 = of_get_named_gpio(dev->of_node, "am437x,led_gpio0", 0);;
    led1 = of_get_named_gpio(dev->of_node, "am437x,led_gpio1", 0);;
    led2 = of_get_named_gpio(dev->of_node, "am437x,led_gpio2", 0);;
    led3 = of_get_named_gpio(dev->of_node, "am437x,led_gpio3", 0);

    //printk(KERN_INFO"led0 = %d\n",led0);
    //printk(KERN_INFO"led1 = %d\n",led1);
    //printk(KERN_INFO"led2 = %d\n",led2);
    //printk(KERN_INFO"led3 = %d\n",led3);

    devm_gpio_request_one(dev, led0, GPIOF_OUT_INIT_HIGH, "LED0");
    devm_gpio_request_one(dev, led1, GPIOF_OUT_INIT_HIGH, "LED1");
    devm_gpio_request_one(dev, led2, GPIOF_OUT_INIT_HIGH, "LED2");
    devm_gpio_request_one(dev, led3, GPIOF_OUT_INIT_HIGH, "LED3");

error:
    unregister_chrdev_region(MKDEV(major, 0), TI_LEDS_CNT); 

    return 0;  
}  

static int leds_remove(struct platform_device *pdev)  
{  
    unsigned i;
    printk(KERN_INFO"%s OK.\n",__func__);


    for(i=0;i<TI_LEDS_CNT;i++)
    {
        device_destroy(leds_cls,  MKDEV(major, i)); 
    }

    class_destroy(leds_cls);
    cdev_del(&leds_cdev);
    unregister_chrdev(major, "ti_leds"); 

    return 0;  
}


static const struct of_device_id of_gpio_leds_match[] = {
    { .compatible = "ti_leds", },
    {},
};

static struct platform_driver leds_drv = {
    .probe      = leds_probe,
    .remove     = leds_remove,
    .driver     = {
        .name   = "ti_am437x_leds_platform",
        .owner  = THIS_MODULE,
        .of_match_table = of_match_ptr(of_gpio_leds_match),
    },
};


static int leds_drv_init(void)  
{  
    printk(KERN_INFO"%s OK.\n",__func__);
    return platform_driver_register(&leds_drv);  
}  

static void leds_drv_exit(void)  
{  
    printk(KERN_INFO"%s OK.\n",__func__);
    platform_driver_unregister(&leds_drv);  
}  


module_init(leds_drv_init);
module_exit(leds_drv_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("hceng <[email protected]>");
MODULE_DESCRIPTION("TI am437x board leds drvice");
MODULE_ALIAS("platform:device tree:ti_leds");
MODULE_VERSION("V3.0");

測試程式同前面的一樣。

6.心得

在我理解到驅動=裸機+軟體框架的時候,我對之前的裸機也就沒那麼排斥了。
而且這個軟體框架,就現在來看,核心的那幾步像:申請裝置號、註冊裝置、建立類和建立節點這些都不變