點擊上方「嵌入式大雜燴」,選擇「置頂公眾號」第一時間查看編程筆記!
前言
我們可以從LED程序中榨取很多知識:基本的驅動框架、驅動的簡單分層、驅動的分層+分離思想、總線設備驅動模型、設備樹等。這大多都是結合韋老師的教程學的。
這篇筆記結合第6個demo(基于設備樹
)來學習、分析:
框圖
下面是LED程序的幾個層次結構圖:
注意:層與層之間的箭頭指向是相對的,從哪指向哪看你怎么理解。比如有兩個函數(shù):函數(shù)A和函數(shù)B,我們可以說函數(shù)A調用函數(shù)B,也可以說函數(shù)B被函數(shù)A調用。
本篇筆記基于第⑤個圖來分析。
體驗設備樹
我們先來體驗一下使用設備樹描述引腳信息的方式來點燈。
以百問網(wǎng)開發(fā)板為例,修改內(nèi)核目錄Linux-4.9.88/arch/arm/boot/dts
下的100ask_imx6ull-14x14.dts
(百問科技開發(fā)板出廠帶的設備樹文件)設備樹文件。
把出廠帶的設備樹文件的led相關節(jié)點給屏蔽掉,然后添加如下節(jié)點信息至根節(jié)點:
#define GROUP_PIN(g,p) ((g<<16) | (p))
100ask_led@0 {
compatible = "100as,leddrv";
pin = <GROUP_PIN(5, 3)>;
};
修改后的設備樹文件內(nèi)容如:
在內(nèi)核根目錄下使用如下命令編譯設備樹源文件:
make dtbs V=1
然后把設備樹文件與可加載的led驅動模塊、led應用程序上傳到板子里:
上傳成功的文件如下:
運行測試:
實驗過程分析
這個實驗的led驅動同樣依賴的是總線設備驅動模型
。
我們在【Linux筆記】總線設備驅動模型中也有提到描述設備有兩種方法:一種是直接用platform_device結構體來指定,另一種是用設備樹來指定。
在本次的實驗中我們就是用設備樹來描述設備。
之前我們用platform_device結構體來指定設備信息時,platform_driver是直接從platform_device結構體里拿資源的,如:
現(xiàn)在我們用設備樹來指定設備信息時,platform_driver是如何獲取相關資源的呢?大致過程如下:
這里我們還需要注意的一點是:并不是所有的設備樹節(jié)點都可以轉換為platform_device。下面看看幾條規(guī)則:
-
根節(jié)點下含有 compatile 屬性的子節(jié)點 能轉換為platform_device
; -
含有特定 compatile 屬性(它的值是 "simple-bus","simplemfd","isa","arm,amba-bus" 四者之一)的節(jié)點的子節(jié)點 能轉換為platform_device
; -
I2C、 SPI 總線節(jié)點下的子節(jié)點 不不不能轉換為platform_device
,這些總線下的子節(jié)點, 應該交給對應的總線驅動程序來處理。
下面看一個例子:
接下來,我們簡單來看一下platform_device與platform_driver匹配的函數(shù):
這里有幾種匹配方式,其它幾種匹配方式在之前的筆記【Linux筆記】總線設備驅動模型中也有簡單地分析過。
這里,我們來看第二種匹配方式(使用設備樹時的匹配方式)。下面看看具體如何匹配:
其中過程①優(yōu)先匹配,其次是過程②,最后是過程③。
但是,實際上現(xiàn)在主要使用的是過程①的匹配,即匹配compatible屬性。
過程②與過程③已經(jīng)過時了,Linux內(nèi)核不推薦使用這兩種匹配方法。
這一點我們在上一篇的筆記【Linux筆記】設備樹基礎知識中也有簡單提到。
在本次實驗中,我們的匹配示意圖如下:
實驗代碼
代碼大多與之前的【Linux筆記】LED驅動(總線設備驅動模型)中的代碼一樣,這里也來簡單看一下。
1、應用程序ledtest.c:
int main(int argc, char **argv)
{
int fd;
char status;
/* 1. 判斷參數(shù) */
if (argc != 3)
{
printf("Usage: %s <dev> <on | off>\n", argv[0]);
return -1;
}
/* 2. 打開文件 */
fd = open(argv[1], O_RDWR);
if (fd == -1)
{
printf("can not open file %s\n", argv[1]);
return -1;
}
/* 3. 寫文件 */
if (0 == strcmp(argv[2], "on"))
{
status = 1;
write(fd, &status, 1);
}
else
{
status = 0;
write(fd, &status, 1);
}
close(fd);
return 0;
}
運行測試命令:
./ledtest /dev/100ask_led0 on
./ledtest /dev/100ask_led0 off
int main(int argc, char **argv)
形式的main函數(shù)相關筆記:main()函數(shù)有哪幾種形式?。
2、驅動層leddrv.c
這一層主要是放一些通用的驅動操作函數(shù),核心代碼如:
驅動程序入口函數(shù):
open、write函數(shù):
其它代碼:
其中l(wèi)ed的操作結構體如下:
3、硬件層:chip_demo_gpio.c
這一層主要是一些寄存器相關的操作,及platform_driver相關。與上一個實驗代碼不同的部分就是這個文件。
(1)驅動初始化函數(shù):
(2)probe函數(shù):
當設備樹的compatible屬性與platform_driver中的設備匹配表中的compatible成員互相匹配時會執(zhí)行此函數(shù)獲取設備信息。
這里的pin屬性與compatible屬性(標準屬性)類別不同,pin屬性是個自定義屬性。
我們可以使用of_property_read_u32
函數(shù)來獲取這些自定義屬性的內(nèi)容。
與設備樹相關的讀取函數(shù)我們在上一篇筆記【Linux筆記】設備樹基礎知識中也有詳細介紹。
這些函數(shù)大多在文件 include/linux/of.h
中可以找到:
(3)led寄存器操作相關的代碼:
/* 寄存器物理地址 */
#define CCM_CCGR1_BASE (0X020C406C)
#define SW_MUX_GPIO5_IO03_BASE (0X02290014)
#define GPIO5_DR_BASE (0X020AC000)
#define GPIO5_GDIR_BASE (0X020AC004)
/* 映射后的寄存器虛擬地址指針 */
static void __iomem *CCM_CCGR1;
static void __iomem *SW_MUX_GPIO5_IO03;
static void __iomem *GPIO5_DR;
static void __iomem *GPIO5_GDIR;
/* 初始化LED, which-哪個LED */
static int board_demo_led_init (int which)
{
int group, pin;
unsigned int val;
group = GROUP(g_ledpins[which]);
pin = PIN(g_ledpins[which]);
printk("init gpio: group %d, pin %d\n", group, pin);
/* 100ask_IMX6uLL_Board LED:GPIO5_3 */
if ((5 == group) && (3 == pin))
{
/* 相關寄存器物理地址與虛擬地址之間的映射 */
/* 1、地址映射:時鐘寄存器 */
CCM_CCGR1 = ioremap(CCM_CCGR1_BASE, 4);
/* 2、地址映射:模式寄存器 */
SW_MUX_GPIO5_IO03 = ioremap(SW_MUX_GPIO5_IO03_BASE, 4);
/* 3、地址映射:數(shù)據(jù)寄存器 */
GPIO5_DR = ioremap(GPIO5_DR_BASE, 4);
/* 地址映射:方向寄存器 */
GPIO5_GDIR = ioremap(GPIO5_GDIR_BASE, 4);
/* 使能GPIO5時鐘 */
val = readl(CCM_CCGR1); /* 讀出當前CCM_CCGR1配置值 */
val &= ~(3 << 30); /* 清除以前的設置 */
val |= (3 << 30); /* 設置新值 */
writel(val, CCM_CCGR1);
/* 設置GPIO5_IO03的為IO模式 */
writel(5, SW_MUX_GPIO5_IO03);
/* 設置GPIO5_IO03方向為輸出 */
val = readl(GPIO5_GDIR);
val &= ~(1 << 3);
val |= (1 << 3);
writel(val, GPIO5_GDIR);
}
else
{
printk("This is not 100ask_IMX6ULL_Board!\n");
}
return 0;
}
/* 控制LED, which-哪個LED, status:1-亮,0-滅 */
static int board_demo_led_ctl (int which, char status)
{
int group, pin;
unsigned int val;
group = GROUP(g_ledpins[which]);
pin = PIN(g_ledpins[which]);
printk("init gpio: group %d, pin %d\n", group, pin);
/* 100ask_IMX6uLL_Board LED:GPIO5_3 */
if ((5 == group) && (3 == pin))
{
/* 點燈 */
if (1 == status)
{
printk("<<<<<<<<led on>>>>>>>>>>\n");
val = readl(GPIO5_DR);
val &= ~(1 << 3);
writel(val, GPIO5_DR);
}
/* 滅燈 */
else if (0 == status)
{
printk("<<<<<<<<led off>>>>>>>>>>\n");
val = readl(GPIO5_DR);
val|= (1 << 3);
writel(val, GPIO5_DR);
}
else{}
}
else
{
printk("This is not 100ask_IMX6ULL_Board!\n");
}
return 0;
}
4、Makefile文件
運行測試
這在文章開頭的體驗設備樹
一節(jié)中也有演示測試結果:
(話說我好像還沒給板子露過面,這下來點個燈露個面)
同時,在目錄/sys/firmware/devicetree/base
下,我們可以查看設備樹節(jié)點:
可以看到,我們創(chuàng)建的設備樹節(jié)點100ask_led@0
也在該目錄下。100ask_led@0
節(jié)點本身就是一個文件夾,可以使用cd命令進入該文件夾查看該節(jié)點的屬性信息:
屬性值是字符串時,用 cat 命令可以打印出來;屬性值是數(shù)值時,用 hexdump 命令可以打印出來。
最后
以上就是本次的實驗分享。如有錯誤,歡迎指出!謝謝
本篇筆記會同步至我的個人博客:https://www.lizhengnian.cn/中,歡迎來訪。
原創(chuàng)不易,期待您的在看、分享~
參考/學習資料:
《嵌入式Linux應用開發(fā)完全手冊第2版_韋東山》
Linux-4.9.88
往期筆記:
【Linux筆記】pc機_開發(fā)板_ubuntu互ping實驗
【Linux筆記】掛載網(wǎng)絡文件系統(tǒng)
后臺回復:加群。添加ZhengN微信,加入技術交流群
點個贊,證明你還愛我
免責聲明:本文內(nèi)容由21ic獲得授權后發(fā)布,版權歸原作者所有,本平臺僅提供信息存儲服務。文章僅代表作者個人觀點,不代表本平臺立場,如有問題,請聯(lián)系我們,謝謝!