公眾號(hào):嵌入式攻城獅(ID:andyxi_linux)
作者:安迪西
設(shè)備樹下的字符設(shè)備驅(qū)動(dòng)框架
沒有引入設(shè)備樹時(shí),相關(guān)寄存器物理地址是直接定義在驅(qū)動(dòng)文件中的,通過地址映射成為虛擬地址后,再操作虛擬地址完成GPIO的初始化。設(shè)備樹的本質(zhì)也是操作寄存器,只不過寄存器的相關(guān)信息放在了設(shè)備樹中,配置寄存器時(shí)使用OF函數(shù)從設(shè)備樹中讀取寄存器數(shù)據(jù)后再進(jìn)行配置
下圖為設(shè)備樹下的字符設(shè)備驅(qū)動(dòng)框架圖:
接下來根據(jù)上面的框架圖,以驅(qū)動(dòng)LED (GPIO1_IO03)為例,分步介紹具體的代碼編寫流程
1. 修改設(shè)備樹文件
在內(nèi)核源碼的/arch/arm/boot/dts/文件夾中復(fù)制一份官方I.MX6ULL EVK EMMC版的設(shè)備樹文件imx6ull-14x14-evk-emmc.dts,并自定義文件名,此處重命名為了imx6ull-andyxi-emmc.dts,在根節(jié)點(diǎn)中添加LED設(shè)備節(jié)點(diǎn)
andyxiled {
#address-cells = <1>; /*reg中起始地址占用一個(gè)字長*/
#size-cells = <1>; /*reg中地址長度占用一個(gè)字長*/
compatible = "andyxi-led";
status = "okay";
reg = < 0X020C406C 0x04 /*CCM_CCGR1_BASE*/
0X020E0068 0x04 /*SW_MUX_GPIO1_IO03_BASE*/
0X020E02F4 0x04 /*SW_PAD_GPIO1_IO03_BASE*/
0X0209C000 0x04 /*GPIO1_DR_BASE*/
0X0209C004 0x04 >; /*GPIO1_GDIR_BASE*/
};
設(shè)備樹修改完成后,在內(nèi)核源碼的根目錄下執(zhí)行make命令編譯設(shè)備樹
make dtbs #編譯設(shè)備樹
make imx6ull-andyxi-emmc.dtb #單獨(dú)編譯指定設(shè)備樹
編譯完成后,使用新的設(shè)備樹啟動(dòng)Linux內(nèi)核,之后可進(jìn)入/proc/device-tree文件夾查看dtsled節(jié)點(diǎn)是否存在
#啟動(dòng)Linux系統(tǒng)后,在開發(fā)板中查看節(jié)點(diǎn)
cd /proc/device-tree #查看andyxiled節(jié)點(diǎn)是否存在
2. 編寫驅(qū)動(dòng)程序
創(chuàng)建驅(qū)動(dòng)程序文件dtsled.c,添加如下代碼
? 宏定義及設(shè)備結(jié)構(gòu)體定義
#define DTSLED_CNT 1 //設(shè)備號(hào)個(gè)數(shù)
#define DTSLED_NAME "dtsled" //名字
#define LEDOFF 0 //關(guān)燈
#define LEDON 1 //開燈
/* 映射后的寄存器虛擬地址指針 */
static void __iomem *IMX6U_CCM_CCGR1;
static void __iomem *SW_MUX_GPIO1_IO03;
static void __iomem *SW_PAD_GPIO1_IO03;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;
/* dtsled 設(shè)備結(jié)構(gòu)體 */
struct dtsled_dev{
dev_t devid; //設(shè)備號(hào)
struct cdev cdev; //cdev
struct class *class; //類
struct device *device; //設(shè)備
int major; //主設(shè)備號(hào)
int minor; //次設(shè)備號(hào)
struct device_node *nd; //設(shè)備節(jié)點(diǎn)
};
struct dtsled_dev dtsled; //led設(shè)備
? 編寫設(shè)備操作函數(shù):設(shè)備操作函數(shù)和LED開關(guān)函數(shù),具體代碼可參考Linux點(diǎn)燈一文相關(guān)部分? 驅(qū)動(dòng)入口函數(shù)中:使用OF函數(shù)獲取設(shè)備樹中的屬性值,并初始化
static int __init led_init(void) {
u32 val = 0;
int ret;
u32 regdata[14];
const char *str;
struct property *proper;
/* 獲取設(shè)備樹中的屬性數(shù)據(jù) */
/* 1、獲取設(shè)備節(jié)點(diǎn):andyxiled */
dtsled.nd = of_find_node_by_path("/andyxiled");
if(dtsled.nd == NULL) {
printk("andyxiled node can not found!rn");
return -EINVAL;
} else {
printk("andyxiled node has been found!rn");
}
/* 2、獲取compatible屬性內(nèi)容 */
proper = of_find_property(dtsled.nd, "compatible", NULL);
if(proper == NULL) {
printk("compatible property find failedrn");
} else {
printk("compatible = %srn", (char*)proper->value);
}
/* 3、獲取status屬性內(nèi)容 */
ret = of_property_read_string(dtsled.nd, "status", &str);
if(ret < 0){
printk("status read failed!rn");
} else {
printk("status = %srn",str);
}
/* 4、獲取reg屬性內(nèi)容 */
ret = of_property_read_u32_array(dtsled.nd, "reg", regdata, 10);
if(ret < 0) {
printk("reg property read failed!rn");
} else {
u8 i = 0;
printk("reg data:rn");
for(i = 0; i < 10; i++)
printk("%#X ", regdata[i]);
printk("rn");
}
/* 初始化LED */
#if 0
/* 1、寄存器地址映射 */
IMX6U_CCM_CCGR1 = ioremap(regdata[0], regdata[1]);
SW_MUX_GPIO1_IO03 = ioremap(regdata[2], regdata[3]);
SW_PAD_GPIO1_IO03 = ioremap(regdata[4], regdata[5]);
GPIO1_DR = ioremap(regdata[6], regdata[7]);
GPIO1_GDIR = ioremap(regdata[8], regdata[9]);
#else
IMX6U_CCM_CCGR1 = of_iomap(dtsled.nd, 0);
SW_MUX_GPIO1_IO03 = of_iomap(dtsled.nd, 1);
SW_PAD_GPIO1_IO03 = of_iomap(dtsled.nd, 2);
GPIO1_DR = of_iomap(dtsled.nd, 3);
GPIO1_GDIR = of_iomap(dtsled.nd, 4);
#endif
/* 2、使能GPIO1時(shí)鐘 */
val = readl(IMX6U_CCM_CCGR1);
val &= ~(3 << 26); //之前的設(shè)置
val |= (3 << 26); //設(shè)置新值
writel(val, IMX6U_CCM_CCGR1);
/* 3、設(shè)置GPIO1_IO03復(fù)用功能,并設(shè)置IO屬性 */
writel(5, SW_MUX_GPIO1_IO03);
writel(0x10B0, SW_PAD_GPIO1_IO03);
/* 4、設(shè)置GPIO1_IO03為輸出功能 */
val = readl(GPIO1_GDIR);
val &= ~(1 << 3); //之前的設(shè)置
val |= (1 << 3); //設(shè)置為輸出
writel(val, GPIO1_GDIR);
/* 5、默認(rèn)關(guān)閉LED */
val = readl(GPIO1_DR);
val |= (1 << 3);
writel(val, GPIO1_DR);
? 驅(qū)動(dòng)入口函數(shù)中:注冊(cè)字符設(shè)備驅(qū)動(dòng),代碼與Linux點(diǎn)燈一文中的一樣? 驅(qū)動(dòng)出口函數(shù)中:注銷設(shè)備驅(qū)動(dòng),刪除類和設(shè)備,代碼可參考Linux點(diǎn)燈一文
3. 編寫測序程序
實(shí)現(xiàn)操作驅(qū)動(dòng)文件對(duì)外設(shè)進(jìn)行控制的功能。創(chuàng)建測試程序文件dtsledApp.c,代碼內(nèi)容與Linux點(diǎn)燈一文中的測試程序代碼一致,此處不再贅述
4. 編譯測試
? 編譯驅(qū)動(dòng)程序:當(dāng)前目錄下創(chuàng)建Makefile文件,并make編譯
KERNELDIR := /home/andyxi/linux/kernel/linux-imx-rel_imx_4.1.15_2.1.0_ga_andyxi
CURRENT_PATH := $(shell pwd)
obj-m := dtsled.o
build: kernel_modules
kernel_modules:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
? 編譯測試程序:無需內(nèi)核參與,直接編譯即可
arm-linux-gnueabihf-gcc dtsledApp.c -o dtsledApp
? 運(yùn)行測試:拷貝驅(qū)動(dòng)模塊和測試程序到開發(fā)板,啟動(dòng)開發(fā)板,加載驅(qū)動(dòng)模塊后,使用應(yīng)用程序測試驅(qū)動(dòng)是否正常工作
depmod #第一次加載驅(qū)動(dòng)的時(shí)候需運(yùn)行此命令
modprobe dtsled.ko #加載驅(qū)動(dòng)
./dtsledApp /dev/dtsled 1 #打開LED燈
./dtsledApp /dev/dtsled 0 #關(guān)閉LED燈
rmmod dtsled.ko #卸載驅(qū)動(dòng)模塊