天天看點

Linux驅動開發十三.platform裝置驅動——3.裝置樹下的platform驅動

在上一章節我們使用了platform架構在沒有裝置樹的時候是如何使用的,不過現在的大多數半導體廠商都把裝置樹給我們完善了。差別就是在沒有裝置樹資訊的時候需要我們自己想總線注冊platform裝置,裝置裡主要包含寄存器位址資訊等資源,而在有裝置樹支援的條件下,就不需要我們使用platform_device_register函數去向總線裡注冊裝置了,我們隻需要修改裝置樹然後編寫驅動就行了。

裝置樹資訊

在結合裝置樹完成platform驅動架構時,主要的裝置樹屬性就是相容性節點compatible

1 /*gpio蜂鳴器節點*/
2 beep{
3     compatible = "alientek,beep";
4     pinctrl-names = "default";
5     pinctrl-0 = <&pinctrl_gpiobeep>;
6     beep-gpios = <&gpio5 1 GPIO_ACTIVE_HIGH>;
7     status = "okay";
8 };      

這次的驅動我們使用前面講GPIO子系統時候的蜂鳴器來示範(還是因為LED點亮失敗~)。裝置樹就不用更改了,一定要注意compatible節點的屬性值,驅動是通過這個值來和裝置進行比對的。啟動開發闆以後列印一下裝置樹資訊,看看有沒有這個beep的節點

Linux驅動開發十三.platform裝置驅動——3.裝置樹下的platform驅動

  節點正常,compatible屬性的值也沒問題。

 platform_driver對象

和前面一樣,我們必須先聲明一個platform_driver類型的變量來描述驅動,先回顧一下platform_driver的形式

1 struct platform_driver {
 2     int (*probe)(struct platform_device *);
 3     int (*remove)(struct platform_device *);
 4     void (*shutdown)(struct platform_device *);
 5     int (*suspend)(struct platform_device *, pm_message_t state);
 6     int (*resume)(struct platform_device *);
 7     struct device_driver driver;
 8     const struct platform_device_id *id_table;
 9     bool prevent_deferred_probe;
10 };      

上回我們在沒有裝置樹的時候使用的成員是driver裡面的name成員,這次我們要用到另一個

const struct of_device_id    *of_match_table;      

從名稱可以看出來,of是和裝置樹有關的,match是和比對有關系的。可以再看看of_device_id的類型

1 /*
2  * Struct used for matching a device
3  */
4 struct of_device_id {
5     char    name[32];
6     char    type[32];
7     char    compatible[128];
8     const void *data;
9 };      

可以從注釋上看到,這是用來比對裝置的結構體,在和裝置樹資訊進行比對的時候,我們就要用到裡面的compatible成員,這個值一定要和前面我強調的裝置樹裡的compatible屬性一緻。

1 struct of_device_id beep_of_match[] = {
2     {.compatible = "alientek,beep"},
3     {/*Sentinel*/},                  //結尾辨別符
4 };      

這個compatible的内容一定要注意,必須和裝置樹裡的内容相同順序也要相同,即便是内容一樣但是逗号左右的值反過來也無法比對成功。

因為在device結構體這個match的是一個table,是以我們要把它聲明成一個數組,好用來進行多個相容性的比對。要注意的是數組最後要用那個結尾的辨別符來結束table的定義。在最後的platform_driver的成員就可以定義成下面這樣了

1 static struct platform_driver beepdriver = {
2     .driver = {
3         .name = "imx6ull-led",              //無裝置樹時比對驅動名稱
4 
5         .of_match_table = beep_of_match,  //裝置樹比對表
6     },
7     .probe = beep_probe,
8     .remove = beep_remove,
9 };      

主要就是那個driver成員裡的of_match_table了。其他就沒什麼了。

到這一步我們就可以試驗一下driver的加載了,準備好ko檔案後,啟動開發闆,可以先在/proc/device-tree下看看有沒有我們要使用的裝置,這個裝置是從裝置樹中提取的,再加載驅動,可以在probe對應的函數中添加個列印資訊檢查下是否成功配對。

Linux驅動開發十三.platform裝置驅動——3.裝置樹下的platform驅動

驅動的檔案名我沿用上面章節leddrive沒改,是以ko子產品的檔案名就是leddriver,加載以後如果和裝置比對成功就會執行probe對應的函數,列印出調試資訊。

probe、release函數

如果能完成配對,剩下的就是probe函數了。和前面一樣,probe主要就是完成裝置的初始化、節點的生成什麼的。可以把GPIO子系統試驗裡的初始化過程拿過來放在一個函數裡,最後在probe裡調用一下就可以了。解除安裝子產品要釋放資源的過程都放在release函數裡,這樣就可以了

Linux驅動開發十三.platform裝置驅動——3.裝置樹下的platform驅動
Linux驅動開發十三.platform裝置驅動——3.裝置樹下的platform驅動
/**
 * @file leddriver.c
 * @author your name ([email protected])
 * @brief platfrom結合裝置樹驅動架構
 * @version 0.1
 * @date 2022-08-18
 * 
 * @copyright Copyright (c) 2022
 * 
 */
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/types.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/fcntl.h>
#include <linux/ide.h>
#include <linux/platform_device.h>

#define LEDOFF  0
#define LEDON   1

#define DEVICE_NAME "dtsplf_beep"
#define DEVICE_CNT 1
//記憶體映射後的位址指針


struct beep_dev
{
    dev_t dev_id;
    int major;
    int minor;
    struct class *class;
    struct device *device;
    struct cdev cdev;
    struct device_node *dev_nd;
    int beep_gpio;
};

struct beep_dev beep_dev;

static ssize_t beep_write(struct file *filp,const char __user *buf,
                            size_t count,loff_t *ppos)
{
    int ret;
    unsigned char databuf[1];
    ret = copy_from_user(databuf,buf,count);
    if(databuf[0] == 1){
        gpio_set_value(beep_dev.beep_gpio,0);
    }
    else{
        gpio_set_value(beep_dev.beep_gpio,1);
    }

    return ret;
}


/**
 * @brief 檔案操作集合
 * 
 */
static const struct file_operations gpiobeep_fops = {
    .owner = THIS_MODULE,
    .write = beep_write,
    // .open =  beep_dev_open,
    // .release = beep_dev_release,
};


static int led_open(struct inode *inode, struct file *filp)
{
    printk("dev open!\r\n");
    return 0;
}


static ssize_t led_read(struct file *filp, 
                               __user char *buf,
                               size_t count, 
                               loff_t *ppos)
{
    int ret = 0;
    printk("dev read data!\r\n");

    if (ret == 0){
        return 0;
    }
    else{
        printk("kernel read data error!");
        return -1;
    }
}

static void led_switch(u8 sta)
{
    printk("led sta change %d\r\n",sta);
}


/**
 * @brief 改變LED狀态
 * 
 * @param file 
 * @param buf 
 * @param count 
 * @param ppos 
 * @return ssize_t 
 */
static ssize_t led_write(struct file *file, 
                        const char __user *buf, 
                        size_t count, 
                        loff_t *ppos)
{   
    int ret = 0;
    printk("led write called\r\n");
    unsigned char databuf[1];                   //待寫入的參數
    ret = copy_from_user(databuf,buf,count);    //擷取從使用者空間傳遞來的參數

    if (ret == 0){
        led_switch(databuf[0]);                 //根據參數改變LED狀态
    }
    else{
        printk("kernelwrite err!\r\n");
        return -EFAULT;
    }
} 

static struct file_operations dev_fops= {
    .owner = THIS_MODULE,
    .open = led_open,
    // .release = led_release,
    .read = led_read,
    .write = led_write,
};

static int beep_gpio_init(struct platform_device *p_dev){
    int ret=0;

    //從裝置樹搜尋裝置節點
    beep_dev.dev_nd = of_find_node_by_path("/beep");
    if(beep_dev.dev_nd == 0){
        printk("no device found\r\n");
        ret = -100;     //errno-base.h中定義的異常數值到34,這裡從100開始使用防止沖突
    }

    //擷取beep對應GPIO
    beep_dev.beep_gpio =of_get_named_gpio(beep_dev.dev_nd,"beep-gpios",0);
    printk("beep_gpio=%d\r\n",beep_dev.beep_gpio);
    if(beep_dev.beep_gpio<0){
        printk("no GPIO get\r\n");
        ret = -100;     //errno-base.h中定義的異常數值到34,這裡從100開始使用防止沖突
        return ret;
    }

    //請求GPIO
    ret = gpio_request(beep_dev.beep_gpio,"beep-gpio");
    if(ret){
        printk("gpio request err\r\n");
        ret = -100;
        return ret;
    }

    //設定GPIO為輸出,預設輸出狀态為低電平
    ret = gpio_direction_output(beep_dev.beep_gpio,0);
    if(ret<0){
        ret = -101;     //對應異常護理為fail_set
        return ret;
    }

    //GPIO輸出低電平,蜂鳴器發聲
    gpio_set_value(beep_dev.beep_gpio,0);

    return ret;
}

static int __init beep_init(struct platform_device *p_dev){

    int ret = 0; 

    //申請裝置号
    beep_dev.major = 0;
    if(beep_dev.major){
        //手動指定裝置号,使用指定的裝置号
        beep_dev.dev_id = MKDEV(beep_dev.major,0);
        ret = register_chrdev_region(beep_dev.dev_id,DEVICE_CNT,DEVICE_NAME);
    }
    else{
        //裝置号未指定,申請裝置号
        ret = alloc_chrdev_region(&beep_dev.dev_id,0,DEVICE_CNT,DEVICE_NAME);
        beep_dev.major = MAJOR(beep_dev.dev_id);
        beep_dev.minor = MINOR(beep_dev.dev_id);
    }
    printk("dev id geted!\r\n");

    if(ret<0){
        //裝置号申請異常,跳轉至異常處理
        goto faile_devid;
    }

    //字元裝置cdev初始化
    beep_dev.cdev.owner = THIS_MODULE;

    cdev_init(&beep_dev.cdev,&gpiobeep_fops);                 //檔案操作集合映射

    ret = cdev_add(&beep_dev.cdev,beep_dev.dev_id,DEVICE_CNT);
    if(ret<0){
        //cdev初始化異常,跳轉至異常處理
        goto fail_cdev;
    }

    printk("chr dev inited!\r\n");

    //自動建立裝置節點
    beep_dev.class = class_create(THIS_MODULE,DEVICE_NAME);
    if(IS_ERR(beep_dev.class)){
        //class建立異常處理
        printk("class err!\r\n");
        ret = PTR_ERR(beep_dev.class);
        goto fail_class;
    }
    printk("dev class created\r\n");
    beep_dev.device = device_create(beep_dev.class,NULL,beep_dev.dev_id,NULL,DEVICE_NAME);
    if(IS_ERR(beep_dev.device)){
        //裝置建立異常處理
        printk("device err!\r\n");
        ret = PTR_ERR(beep_dev.device);
        goto fail_device;
    }
    printk("device created!\r\n");


    ret = beep_gpio_init(p_dev);
    if(ret == -101){
        goto fail_set;
    }
    else if(ret == -100){
        goto fail_nd;
    }
    return 0;

fail_set:
    gpio_free(beep_dev.beep_gpio);
fail_nd:
    device_destroy(beep_dev.class,beep_dev.dev_id);
fail_device:
    //device建立失敗,意味着class建立成功,應該将class銷毀
    printk("device create err,class destroyed\r\n");
    class_destroy(beep_dev.class);
fail_class:
    //類建立失敗,意味着裝置應該已經建立成功,此刻應将其釋放掉
    printk("class create err,cdev del\r\n");
    cdev_del(&beep_dev.cdev);
fail_cdev:
    //cdev初始化異常,意味着裝置号已經申請完成,應将其釋放
    printk("cdev init err,chrdev register\r\n");
    unregister_chrdev_region(beep_dev.dev_id,DEVICE_CNT);
faile_devid:
    //裝置号申請異常,由于是第一步操作,不需要進行其他處理
    printk("dev id err\r\n");
    return ret;
}



static int beep_probe(struct platform_device *dev)
{
    printk("beep driver device match\r\n");
    beep_init(dev);
    return 0;
}

static int beep_remove(struct platform_device *plt_dev)
{
    printk("beep driver remove\r\n");

    gpio_set_value(beep_dev.beep_gpio,1);

    cdev_del(&beep_dev.cdev);
    unregister_chrdev_region(beep_dev.dev_id,DEVICE_CNT);


    device_destroy(beep_dev.class,beep_dev.dev_id);
    class_destroy(beep_dev.class);

    gpio_free(beep_dev.beep_gpio);
    return 0;
}

struct of_device_id beep_of_match[] = {
    {.compatible = "alientek,beep"},
    {/*Sentinel*/},                  //結尾辨別符
};

static struct platform_driver beepdriver = {
    .driver = {
        .name = "imx6ull-led",              //無裝置樹時比對驅動名稱

        .of_match_table = beep_of_match,  //裝置樹比對表
    },
    .probe = beep_probe,
    .remove = beep_remove,
};


static int __init leddriver_init(void)
{
    //注冊platform驅動
    return platform_driver_register(&beepdriver);
}

static void __exit leddriver_exit(void)
{

    platform_driver_unregister(&beepdriver);
}

module_init(leddriver_init);
module_exit(leddriver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ZZQ");      

platform裝置樹驅動

其實作在這樣就可以了,但是可以注意一下上面折疊的代碼裡有個地方

static int __init beep_init(struct platform_device *p_dev){}      

在調用裝置初始化的時候我往裡面穿了個platform_device的裝置指針,這時因為這個platform_device結構體裡是包含了很多裝置樹資訊供我們直接調用的,就不用再使用of函數從裝置樹擷取資訊了。

 可以看一下device結構體定義裡面有下面一個成員

struct device_node    *of_node; /* associated device tree node */      

是以我們可以直接用這個值來擷取裝置節點,而不用of函數

beep_dev.dev_nd = of_find_node_by_path("/beep");
beep_dev.dev_nd = p_dev->dev.of_node;      

platform為我們提供了很多的接口去擷取裝置資訊,我們可以慢慢去琢磨。