在上一章節我們使用了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的節點

節點正常,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對應的函數中添加個列印資訊檢查下是否成功配對。
驅動的檔案名我沿用上面章節leddrive沒改,是以ko子產品的檔案名就是leddriver,加載以後如果和裝置比對成功就會執行probe對應的函數,列印出調試資訊。
probe、release函數
如果能完成配對,剩下的就是probe函數了。和前面一樣,probe主要就是完成裝置的初始化、節點的生成什麼的。可以把GPIO子系統試驗裡的初始化過程拿過來放在一個函數裡,最後在probe裡調用一下就可以了。解除安裝子產品要釋放資源的過程都放在release函數裡,這樣就可以了
/**
* @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為我們提供了很多的接口去擷取裝置資訊,我們可以慢慢去琢磨。