Linux下的任何外設(shè)驅(qū)動(dòng),最終都是要配置相應(yīng)的硬件寄存器。 前面的文章中介紹了新舊字符設(shè)備的驅(qū)動(dòng)開發(fā)框架,也介紹了IMX6ULL處理器GPIO的工作原理及配置方法,本篇我們將實(shí)際操作一個(gè)GPIO,點(diǎn)亮Linux驅(qū)動(dòng)開發(fā)路上的第一個(gè)燈
1. 地址映射
1.1 MMU介紹
MMU (Memory Manage Unit),即內(nèi)存管理單元,它提供統(tǒng)一的內(nèi)存空間抽象,程序訪問虛擬內(nèi)存中的地址,MMU將虛擬地址翻譯成實(shí)際的物理地址,之后CPU即可操作實(shí)際的物理地址
MMU具有如下功能:
- 保護(hù)內(nèi)存:MMU給一些指定的內(nèi)存塊設(shè)置了讀、寫以及可執(zhí)行的權(quán)限,這些權(quán)限存儲(chǔ)在頁(yè)表當(dāng)中,MMU會(huì)檢查CPU當(dāng)前所處的是特權(quán)模式還是用戶模式,只有權(quán)限匹配才可以訪問
- 提供方便統(tǒng)一的內(nèi)存空間抽象,實(shí)現(xiàn)虛擬地址到物理地址的轉(zhuǎn)換:CPU可以運(yùn)行在虛擬的內(nèi)存當(dāng)中,虛擬內(nèi)存一般比物理內(nèi)存大很多,使得CPU可以運(yùn)行比較大的應(yīng)用程序
1.2 IO映射函數(shù)
Linux內(nèi)核啟動(dòng)時(shí)會(huì)初始化MMU,設(shè)置內(nèi)存映射,之后CPU訪問的都是虛擬地址。 在程序編寫時(shí),可使用下面兩個(gè)函數(shù)進(jìn)行物理內(nèi)存和虛擬內(nèi)存之間的轉(zhuǎn)換
ioremap():將物理地址映射為虛擬地址
#define ioremap(cookie,size) __arm_ioremap((cookie), (size), MT_DEVICE)
void __iomem * __arm_ioremap(phys_addr_t phys_addr, size_t size, unsigned int mtype){
return arch_ioremap_caller(phys_addr, size, mtype, __builtin_return_address(0));
}
//phys_addr:被映射的IO起始地址(物理地址)
//size:需要映射的空間大小,以字節(jié)為單位
//mtype: ioremap的類型
//return: __iomem類型的指針,指向映射成功后返回的虛擬空間起始地址
ioremap():將物理地址映射為虛擬地址
void iounmap (volatile void __iomem *addr)
//addr: 要取消映射的虛擬地址空間首地址
//return: void
1.3 IO內(nèi)存訪問函數(shù)
使用ioremap函數(shù)將寄存器的物理地址映射到虛擬地址以后,理論上就可以直接通過指針訪問這些地址了,但是為了符合驅(qū)動(dòng)的跨平臺(tái)以及可移植性,推薦使用一組操作函數(shù)來(lái)對(duì)映射后的內(nèi)存進(jìn)行讀寫操作
u8 readb(const volatile void __iomem *addr); /*讀取一個(gè)字節(jié)*/
u16 readw(const volatile void __iomem *addr); /*讀取一個(gè)字*/
u32 readl(const volatile void __iomem *addr); /*讀取一個(gè)雙字*/
void writeb(u8 value, volatile void __iomem *addr); /*寫入一個(gè)字節(jié)*/
void writew(u16 value, volatile void __iomem *addr); /*寫入一個(gè)字*/
void writel(u32 value, volatile void __iomem *addr); /*寫入一個(gè)雙字*/
2. 程序編寫
本實(shí)驗(yàn)?zāi)康模壕帉慙inux下的LED燈驅(qū)動(dòng),通過應(yīng)用程序?qū).MX6U開發(fā)板上的LED燈(GPIO1_IO03)進(jìn)行開關(guān)操作
2.1 驅(qū)動(dòng)程序編寫
LED驅(qū)動(dòng)屬于字符設(shè)備驅(qū)動(dòng),之前介紹了新舊兩種字符驅(qū)動(dòng)的寫法,本篇中按照新字符設(shè)備驅(qū)動(dòng)的框架來(lái)編寫
接下來(lái)分步驟完善具體的驅(qū)動(dòng)代碼:
GPIO寄存器宏定義、設(shè)備結(jié)構(gòu)體定義
#define NEWCHRLED_CNT 1 //設(shè)備號(hào)個(gè)數(shù)
#define NEWCHRLED_NAME "newchrled" //名字
#define LEDOFF 0 //關(guān)燈
#define LEDON 1 //開燈
/* 寄存器物理地址 */
#define CCM_CCGR1_BASE (0X020C406C)
#define SW_MUX_GPIO1_IO03_BASE (0X020E0068)
#define SW_PAD_GPIO1_IO03_BASE (0X020E02F4)
#define GPIO1_DR_BASE (0X0209C000)
#define GPIO1_GDIR_BASE (0X0209C004)
/* 映射后的寄存器虛擬地址指針 */
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;
/* newchrled設(shè)備結(jié)構(gòu)體 */
struct newchrled_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 newchrled_dev newchrled; //led設(shè)備
控制LED亮滅函數(shù)
void led_switch(u8 sta){
u32 val = 0;
if(sta == LEDON) {
val = readl(GPIO1_DR);
val &= ~(1 << 3);
writel(val, GPIO1_DR);
}else if(sta == LEDOFF) {
val = readl(GPIO1_DR);
val|= (1 << 3);
writel(val, GPIO1_DR);
}
}
設(shè)備操作函數(shù)集合
/* 打開設(shè)備 */
static int led_open(struct inode *inode, struct file *filp){
filp->private_data = &newchrled; //設(shè)置私有數(shù)據(jù)
return 0;
}
/* 從設(shè)備讀取數(shù)據(jù) */
static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt){
return 0;
}
/* 向設(shè)備寫數(shù)據(jù) */
static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt){
int retvalue;
unsigned char databuf[1];
unsigned char ledstat;
retvalue = copy_from_user(databuf, buf, cnt);
if(retvalue < 0) {
printk("kernel write failed!\\r\\n");
return -EFAULT;
}
ledstat = databuf[0]; //獲取狀態(tài)值
if(ledstat == LEDON) {
led_switch(LEDON); //打開LED燈
} else if(ledstat == LEDOFF) {
led_switch(LEDOFF); //關(guān)閉LED燈
}
return 0;
}
/* 關(guān)閉設(shè)備 */
static int led_release(struct inode *inode, struct file *filp){
return 0;
}
/* 設(shè)備操作函數(shù) */
static struct file_operations newchrled_fops = {
.owner = THIS_MODULE,
.open = led_open,
.read = led_read,
.write = led_write,
.release = led_release,
};
在驅(qū)動(dòng)入口函數(shù)中:初始化GPIO外設(shè)
/* 驅(qū)動(dòng)入口函數(shù) */
static int __init led_init(void){
u32 val = 0;
/* 初始化LED */
/* 1、寄存器地址映射 */
IMX6U_CCM_CCGR1 = ioremap(CCM_CCGR1_BASE, 4);
SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE, 4);
SW_PAD_GPIO1_IO03 = ioremap(SW_PAD_GPIO1_IO03_BASE, 4);
GPIO1_DR = ioremap(GPIO1_DR_BASE, 4);
GPIO1_GDIR = ioremap(GPIO1_GDIR_BASE, 4);
/* 2、使能GPIO1時(shí)鐘 */
val = readl(IMX6U_CCM_CCGR1);
val &= ~(3 << 26); //之前的設(shè)置
val |= (3 << 26); //設(shè)置新值
writel(val, IMX6U_CCM_CCGR1);
/* 3、設(shè)置復(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è)備
/* 注冊(cè)字符設(shè)備驅(qū)動(dòng) */
/* 1、創(chuàng)建設(shè)備號(hào) */
if (newchrled.major) { //定義了設(shè)備號(hào)
newchrled.devid = MKDEV(newchrled.major, 0);
register_chrdev_region(newchrled.devid, NEWCHRLED_CNT, NEWCHRLED_NAME);
} else { //沒有定義設(shè)備號(hào)
alloc_chrdev_region(&newchrled.devid, 0, NEWCHRLED_CNT, NEWCHRLED_NAME);
newchrled.major = MAJOR(newchrled.devid);
newchrled.minor = MINOR(newchrled.devid);
}
printk("newcheled major=%d,minor=%d\\r\\n",newchrled.major, newchrled.minor);
/* 2、初始化cdev */
newchrled.cdev.owner = THIS_MODULE;
cdev_init(&newchrled.cdev, &newchrled_fops);
/* 3、添加一個(gè)cdev */
cdev_add(&newchrled.cdev, newchrled.devid, NEWCHRLED_CNT);
/* 4、創(chuàng)建類 */
newchrled.class = class_create(THIS_MODULE, NEWCHRLED_NAME);
if (IS_ERR(newchrled.class)) {
return PTR_ERR(newchrled.class);
}
/* 5、創(chuàng)建設(shè)備 */
newchrled.device = device_create(newchrled.class, NULL, newchrled.devid, NULL, NEWCHRLED_NAME);
if (IS_ERR(newchrled.device)) {
return PTR_ERR(newchrled.device);
}
return 0;
}
在驅(qū)動(dòng)出口函數(shù)中:取消地址映射,注銷字符設(shè)備驅(qū)動(dòng)
/* 驅(qū)動(dòng)出口函數(shù) */
static void __exit led_exit(void){
/* 取消映射 */
iounmap(IMX6U_CCM_CCGR1);
iounmap(SW_MUX_GPIO1_IO03);
iounmap(SW_PAD_GPIO1_IO03);
iounmap(GPIO1_DR);
iounmap(GPIO1_GDIR);
/* 注銷字符設(shè)備驅(qū)動(dòng) */
cdev_del(&newchrled.cdev);
unregister_chrdev_region(newchrled.devid, NEWCHRLED_CNT);
device_destroy(newchrled.class, newchrled.devid);
class_destroy(newchrled.class);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
2.2 應(yīng)用程序編寫
LED驅(qū)動(dòng)加載成功以自動(dòng)創(chuàng)建設(shè)備節(jié)點(diǎn),應(yīng)用程序通過向節(jié)點(diǎn)文件寫0或1,關(guān)閉/打開LED燈
#define LEDOFF 0
#define LEDON 1
int main(int argc, char *argv[]){
int fd, retvalue;
char *filename;
unsigned char databuf[1];
if(argc != 3){
printf("Error Usage!\\r\\n");
return -1;
}
filename = argv[1];
/* 打開led驅(qū)動(dòng) */
fd = open(filename, O_RDWR);
if(fd < 0){
printf("file %s open failed!\\r\\n", argv[1]);
return -1;
}
databuf[0] = atoi(argv[2]); //要執(zhí)行的操作:打開或關(guān)閉
/* 向/dev/led文件寫入數(shù)據(jù) */
retvalue = write(fd, databuf, sizeof(databuf));
if(retvalue < 0){
printf("LED Control Failed!\\r\\n");
close(fd);
return -1;
}
retvalue = close(fd);
if(retvalue < 0){
printf("file %s close failed!\\r\\n", argv[1]);
return -1;
}
return 0;
}
3. 編譯測(cè)試
3.1 程序編譯
驅(qū)動(dòng)程序編譯:創(chuàng)建Makefile文件,使用make命令,編譯驅(qū)動(dòng)程序
KERNELDIR := /home/andyxi/linux/kernel/linux-imx-rel_imx_4.1.15_2.1.0_ga_andyxi
CURRENT_PATH := $(shell pwd)
obj-m := newchrled.o
build: kernel_modules
kernel_modules:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
應(yīng)用程序編譯:無(wú)需內(nèi)核參與,直接編譯即可
arm-linux-gnueabihf-gcc newchrledApp.c -o newchrledApp
3.2 運(yùn)行測(cè)試
為了方便,選擇通過TFTP從網(wǎng)絡(luò)啟動(dòng),并使用NFS掛載網(wǎng)絡(luò)根文件系統(tǒng)。 確保開發(fā)板能正常啟動(dòng),在Ubuntu中將驅(qū)動(dòng)和測(cè)試文件復(fù)制到modules/4.1.15目錄中
加載驅(qū)動(dòng)模塊
depmod #第一次加載驅(qū)動(dòng)的時(shí)候需要運(yùn)行此命令
modprobe newchrled.ko #加載驅(qū)動(dòng)
驅(qū)動(dòng)加載成功后會(huì)自動(dòng)在/dev目錄下創(chuàng)建設(shè)備節(jié)點(diǎn)文件/dev/newchrdev,輸入如下命令查看
ls /dev/newchrled -l
之后就可使用應(yīng)用程序來(lái)測(cè)試驅(qū)動(dòng)是否能正常工作
./newchrledApp /dev/newchrled 1 #打開LED燈
./newchrledApp /dev/newchrled 0 #關(guān)閉LED燈
若要卸載驅(qū)動(dòng)輸入如下命令
rmmod newchrled.ko
至此,Linux 驅(qū)動(dòng)開發(fā)路上的第一個(gè)燈被成功點(diǎn)亮。 本文主要是通過操作寄存器來(lái)點(diǎn)亮開發(fā)板上的LED,通過編寫相應(yīng)的驅(qū)動(dòng)程序和應(yīng)用程序,實(shí)現(xiàn)程序設(shè)計(jì)的分層
-
led燈
+關(guān)注
關(guān)注
22文章
1592瀏覽量
108007 -
Linux
+關(guān)注
關(guān)注
87文章
11304瀏覽量
209521 -
開發(fā)板
+關(guān)注
關(guān)注
25文章
5050瀏覽量
97484 -
GPIO
+關(guān)注
關(guān)注
16文章
1204瀏覽量
52104 -
MMU
+關(guān)注
關(guān)注
0文章
91瀏覽量
18291
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論