15 核心裡控制IO口
阿新 • • 發佈:2018-11-25
核心裡控制IO口
在linux核心裡mmu已經啟用,不能直接訪問實體地址,必須要把實體地址對映到一個虛擬地址上,然後通過該虛擬地址來訪問原實體地址。
相關函式:
void *ioremap(cookie, size) //用於把指定的實體地址對映到一個虛擬地址上
//cookie用於指定要對映的實體地址,size表示對映的大小範圍
//返回值為對映得到的虛擬地址
iounmap(void *addr) //用於取消虛擬地址的對映關係
ioread8(地址)/readb() //讀地址上的8位值,readb是比較老的函式
ioread16(地址)/readw() //讀地址上的16位值
ioread32(地址)/readl() //讀32位值
iowrite8(值, 地址)/writeb //把8位的值寫到指定的地址上,writeb是比較老的函式
iowrite16(值, 地址)/writew //把16位的值寫到指定的地址上
iowrite32(值, 地址)/writel //把32位的值寫到指定的地址上
事例程式碼(控制板子上的LED燈亮滅)(xxx.c):
#include <linux/init.h>
#include <linux/module.h>
#include <asm/io.h>
#define BASE_DDR 0x01c20800 //基地址
#define PA_CFG1_OFFSET 0x04 //PA組IO口的功能配置暫存器1對基地址偏移為4位元組
#define PA_DATA_OFFSET 0x10 //PA組IO口的資料暫存器對基地址偏移為0x10位元組
u8 *vaddr;//用於記錄對映得到的虛擬地址
static int __init myled_init(void)
{
u32 val;
vaddr = ioremap(BASEDDR, SZ_4K); //對映暫存器的基地址到一個虛擬地址上
val = ioread32(vaddr+PA_CFG1_OFFSET) & (~(7 <<28));//vaddr加上相應暫存器地址偏移就是對應著暫存器原實體地址
iowrite32(val|(1<<28),vaddr+PA_CFG1_OFFSET);//PA15設為輸出功能
val = ioread32(vaddr+PA_DATA_OFFSET);
iowrite32(val|(1<<15), vaddr+PA_DATA_OFFSET);// PA15輸出高電平,LED亮
printk("myled init success\n");
return 0;
}
static void __exit myled_exit(void)
{
u32 val;
val = ioread32(vaddr+PA_DATA_OFFSET);
iowrite32(val & (~(1<<15)), vaddr+PA_DATA_OFFSET); //PA15輸出低電平,LED滅
iounmap(vaddr);//取消虛擬地址的對映關係
printk("myled exited\n");
}
module_init(myled_init);
module_exit(myled_exit);
MODULE_LICENSE("GPL");
linux核心裡有標準的GPIO操作方法,其中有對晶片廠商的要求,晶片廠商需要在核心裡實現相關的GPIO控制器的驅動配置,讓核心裡的gpiolib(drivers/gpio/目錄下)可以統一管理整個晶片的gpio口,讓我們驅動人員可以用核心提供的gpio標準操作函式,通過gpiolib來呼叫控制晶片的io口。
gpio提供io口的呼叫函式包括:
#include <linux/gpio.h> //裡面宣告io口的操作函式
int gpio_request(unsigned gpio, const char *label);//每個io只能被請求一次,可防止多個驅動來控制同一個IO口
void gpio_free(unsigned gpio);//釋放已請求的io口
int gpio_direction_input(unsigned gpio);//把指定的IO口作輸入功能,gpio用於指定具體哪個io口
int gpio_direction_output(unsigned gpio, int value);//作輸出功能,並根據value的值輸出高低電平
int gpio_get_value(unsigned gpio);//獲取指定IO口的電平
void gpio_set_value(unsigned gpio, int value);//設定IO口的電平為value(0/1)
int gpio_to_irq(unsigned gpio);//根據io口,獲取到它對應的中斷號(io口大都有外部中斷功能)
一般情況下,io口的定義是在核心原始碼的arch/arm/mach-xxxx/include/mach/gpio.h
Orange Pi(H3)io口定義是在核心原始碼的arch/arm/mach-sunxi/include/mach/gpio.h
包括以下的巨集(n表示這組裡的第幾個IO口):
#define GPIOA(n) (SUNXI_PA_BASE + (n))
#define GPIOB(n) (SUNXI_PB_BASE + (n))
#define GPIOC(n) (SUNXI_PC_BASE + (n))
#define GPIOD(n) (SUNXI_PD_BASE + (n))
#define GPIOE(n) (SUNXI_PE_BASE + (n))
#define GPIOF(n) (SUNXI_PF_BASE + (n))
#define GPIOG(n) (SUNXI_PG_BASE + (n))
#define GPIOH(n) (SUNXI_PH_BASE + (n))
#define GPIOI(n) (SUNXI_PI_BASE + (n))
#define GPIOJ(n) (SUNXI_PJ_BASE + (n))
#define GPIOK(n) (SUNXI_PK_BASE + (n))
#define GPIOL(n) (SUNXI_PL_BASE + (n))
#define GPIOM(n) (SUNXI_PM_BASE + (n))
#define GPION(n) (SUNXI_PN_BASE + (n))
#define GPIOO(n) (SUNXI_PO_BASE + (n))
#define GPIO_AXP(n) (AXP_PIN_BASE + (n))
使用gpio實現在驅動模組初始化時led亮燈,解除安裝時滅燈(xxx.c):
#include <linux/init.h>
#include <linux/module.h>
#include <mach/gpio.h> //晶片io口的巨集定義
#include <linux/gpio.h> //io口的呼叫函式
#define LED_GPIO GPIOA(15) //PA15
static int __init test_init(void)
{
int ret;
ret = gpio_request(LED_GPIO, "myled");//每個io只能被請求一次,可防止多個驅動來控制同一個IO口;如請求失敗,則表示此io口已被其它驅動使用
if (ret < 0)
return ret;
gpio_direction_output(LED_GPIO, 1);//亮燈
return 0;
}
static void __exit test_exit(void)
{
gpio_set_value(LED_GPIO, 0);//滅燈
gpio_free(LED_GPIO);//最後記得釋放請求的io,不然之後再申請時會被佔用
}
module_init(test_init);
module_exit(test_exit);
MODULE_LICENSE("GPL");
Makefile檔案:
obj-m += xxx.o
KSRC := /目錄路徑/orangepi_sdk/source/linux-3.4.112/
export ARCH := arm
export CROSS_COMPILE := arm-linux-gnueabihf-
all :
make -C $(KSRC) modules M=`pwd`
.PHONY : clean
clean :
make -C $(KSRC) modules clean M=`pwd`