1. 程式人生 > >15 核心裡控制IO口

15 核心裡控制IO口

核心裡控制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`