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