電子產業(yè)一站式賦能平臺

PCB聯盟網

搜索
查看: 1349|回復: 0
收起左側

基于OMAPL138的Linux設備驅動程序開發(fā)入門

[復制鏈接]

678

主題

902

帖子

8293

積分

高級會員

Rank: 5Rank: 5

積分
8293
跳轉到指定樓層
樓主
發(fā)表于 2020-8-27 10:33:08 | 只看該作者 回帖獎勵 |倒序瀏覽 |閱讀模式
LED設備驅動程序 LED設備驅動程序解析開發(fā)板LED編號和GPIO對應關系如下:

表 1
開發(fā)板型號
GPIO0[0]
GPIO0[5]
GPIO0[1]
GPIO0[2]
TL138/1808-EVM
D7
D6
D9
D10
TL138/1808-EasyEVM
D7
D6
D9
D10
TL138/1808-EthEVM
D7
D6
D9
D10
TL138/1808F-EasyEVM
\
GD1
GD2
GD3
TL138/1808F-EVM
\
D1
D2
D3

開發(fā)板資料光盤中有LED設備驅動程序源碼,其路徑為:
led.c:demo\driver\linux-3.3\led\led.c
下面以TL138/1808-EVM開發(fā)板為例講解此設備驅動程序。
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/gpio.h>
#include <linux/platform_device.h>

/* 因為使用了平臺相關的頭文件,所以編譯時需要ARCH=arm */
#include <asm/mach-types.h>
#include <asm/mach/arch.h>
#include <mach/da8xx.h>
#include <mach/mux.h>

/*定義4個用戶LED對應的GPIO,開發(fā)板LED對應編號分別是D7,D6,D9,D10 */
#define DA850_USER_LED0 GPIO_TO_PIN(0, 0)
#define DA850_USER_LED1 GPIO_TO_PIN(0, 5)
#define DA850_USER_LED2 GPIO_TO_PIN(0, 1)
#define DA850_USER_LED3 GPIO_TO_PIN(0, 2)

/* assign the tl som board LED-GPIOs*/
static const short da850_evm_tl_user_led_pins[] = {
/* These pins are definition at <mach/mux.h> file */
DA850_GPIO0_0, DA850_GPIO0_1, DA850_GPIO0_2, DA850_GPIO0_5,
-1
};

/*定義4個LED對應的GPIO號、有效電平(熄燈電平)、名稱、觸發(fā)模式等*/
/*使用Linux提供的標準gpio-led框架*/
static struct gpio_led da850_evm_tl_leds[] = {
{
.active_low = 0, /*有效電平(熄燈電平):低電平*/
.gpio = DA850_USER_LED0, /*GPIO號:LED對應gpio管腳*/
.name = "user_led0", /*名稱:對應/sys/class/leds/下的名稱*/
.default_trigger = "default-on", /*觸發(fā)模式:默認點亮*/
},
{
.active_low = 0,
.gpio = DA850_USER_LED1,
.name = "user_led1",
.default_trigger = "default-on",
},
{
.active_low = 0,
.gpio = DA850_USER_LED2,
.name = "user_led2",
.default_trigger = "default-on",
},
{
.active_low = 0,
.gpio = DA850_USER_LED3,
.name = "user_led3",
.default_trigger = "default-on",
},
};

static struct gpio_led_platform_data da850_evm_tl_leds_pdata = {
.leds = da850_evm_tl_leds,
.num_leds = ARRAY_SIZE(da850_evm_tl_leds),
};

static void led_dev_release(struct device *dev)
{
};

/*使用Linux提供的標準platform_device 框架*/
static struct platform_device da850_evm_tl_leds_device = {
.name = "leds-gpio",
.id = 1, /*先確定id號是否被使用,此id是platform_device的id,跟LED個數無關*/
.dev = {
.platform_data = &da850_evm_tl_leds_pdata,
.release = led_dev_release,
}
};

static int __init led_platform_init(void)
{
int ret;

#if 0
/*使用davinci pinmux設置接口,把LED對應的管腳配置成gpio模式*/
ret = davinci_cfg_reg_list(da850_evm_tl_user_led_pins);
if (ret)
pr_warning("da850_evm_tl_leds_init : User LED mux failed :"
"%d\n", ret);
#endif

/*注冊LED device設備,系統(tǒng)LED框架將會接收到這個注冊,生成相應LED節(jié)點*/
ret = platform_device_register(&da850_evm_tl_leds_device);
if (ret)
pr_warning("Could not register som GPIO expander LEDS");
else
printk(KERN_INFO "LED register sucessful!\n");

return ret;
}

static void __exit led_platform_exit(void)
{
platform_device_unregister(&da850_evm_tl_leds_device);

printk(KERN_INFO "LED unregister!\n");
}

module_init(led_platform_init);
module_exit(led_platform_exit);

MODULE_DESCRIPTION("Led platform driver");
MODULE_AUTHOR("Tronlong");
MODULE_LICENSE("GPL");

以上是LED設備驅動程序解析,對于Linux對LED設備框架,這里稍微說明一下:
  • Linux的LED設備類在內核"Documentation/leds/leds-class.txt"文件有詳細說明。
  • 注冊一個LED設備成功后,會"/sys/class/leds/"生成相應的設備節(jié)點。
  • 用戶可以通過讀寫節(jié)點目錄下的brightness文件控制LED亮滅。
對于GPIO口的操作,有以下幾點步驟:
  • 查看開發(fā)板的原理圖,找到與LED連接的GPIO。TL138/1808-EVM開發(fā)板與LED連接的GPIO分別是GPIO0[5]、GPIO0[0]、GPIO0[1]、GPIO0[2]。
  • 查看OMAP-L138的數據手冊,查找對應PINMUX寄存器的地址,將對應的管腳的寄存器中相應位設置為GPIO的工作模式。本例中使用的是PINMUX1。
  • 設置GPIO的方向寄存器。本例程中將GPIO口配置為輸出。
  • 配置GPIO的數據寄存器,寫"1"表示輸出高電平,寫"0"表示輸出低電平。
編譯LED設備驅動程序此處使用Makefile編譯LED設備驅動程序。工程中源文件有時候很多,其按類型、功能、模塊分別放在若干個目錄中,Makefile定義了一系列的規(guī)則來指定,哪些文件需要先編譯,哪些文件需要后編譯,哪些文件需要重新編譯,甚至于進行更復雜的功能操作,因為Makefile就像一個Shell腳本一樣,其中也可以執(zhí)行操作系統(tǒng)的命令。
開發(fā)板資料光盤中有LED設備驅動程序Makefile文件,其路徑為:
Makefile: demo\driver\linux-3.3\led\Makefile
以下為LED設備驅動程序Makefile文件的解析:
ifneq ($(KERNELRELEASE),)
obj-m := led.o /*定義了要編譯的驅動文件為led.c,生成的模塊名字為led.ko*/
else
/*以下定義運行編譯命令時使用的內核源碼、驅動源碼路徑、平臺、使用的交叉編譯工具鏈等參數*/
all:
make -C $(KDIR) M=$(PWD) modules ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabi-
/*定義運行"make clean"時清除的文件*/
clean:
rm -rf *.ko *.o *.mod.o *.mod.c *.symvers  modul* .button.* .tmp_versions

#help: make KDIR=<you kernel path>
endif
&#8203;

圖 1

將光盤"demo\driver\linux-3.3\led"的led.c和Makefile文件復制到開發(fā)系統(tǒng)Ubuntu任意路徑,并在led.c和Makefile目錄運行以下命令編譯LED設備驅動程序:
Host#make KDIR=/home/tl/omapl138/linux-3.3
&#8203;

圖2

"KDIR=/home/tl/omapl138/linux-3.3"是內核源碼路徑,在運行前必須已正確編譯過內核源碼。運行以上命令后,系統(tǒng)會根據Makefile文件的規(guī)則去編譯整個驅動源碼,產生了驅動程序鏡像文件led.ko和其他中間文件。

LED設備驅動測試腳本解析開發(fā)板資料光盤有LED設備驅動測試腳本,運行此測試腳本LED會循環(huán)點亮。其路徑為:
led_loop.sh demo\app\led\led_loop.sh
以下為測試腳本的解析:
#init all user led #關閉所有LED燈
echo 0 > /sys/class/leds/user_led0/brightness
echo 0 > /sys/class/leds/user_led1/brightness
echo 0 > /sys/class/leds/user_led2/brightness
echo 0 > /sys/class/leds/user_led3/brightness

DELAY_TIME=0.5 #定義流水燈延時時間

#led loop
while true; do
    echo 1 > /sys/class/leds/user_led0/brightness #點亮LED0 D7
    sleep $DELAY_TIME
    echo 0 > /sys/class/leds/user_led0/brightness #關閉LED0 D7
    echo 1 > /sys/class/leds/user_led1/brightness
    sleep $DELAY_TIME
    echo 0 > /sys/class/leds/user_led1/brightness
    echo 1 > /sys/class/leds/user_led2/brightness
    sleep $DELAY_TIME
    echo 0 > /sys/class/leds/user_led2/brightness
    echo 1 > /sys/class/leds/user_led3/brightness
    sleep $DELAY_TIME
    echo 0 > /sys/class/leds/user_led3/brightness
done

具體的LED測試步驟請查看用戶手冊快速體驗相關小節(jié)。

&#8203;&#8203;&#8203;&#8203;&#8203;&#8203;&#8203;按鍵設備驅動程序&#8203;&#8203;&#8203;&#8203;&#8203;&#8203;&#8203;按鍵設備驅動程序解析
開發(fā)板資料光盤中有按鍵設備驅動程序源碼,對應的按鍵為SW5和SW6,以linux-3.3內核驅動為例,其路徑為:
button.c: demo\driver\linux-3.3\button\button.c
以下為此驅動程序的解析:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/gpio.h>
#include <linux/gpio_keys.h>
#include <linux/platform_device.h>
#include <linux/input.h>

#include <asm/mach-types.h>
#include <asm/mach/arch.h>
#include <mach/da8xx.h>
#include <mach/mux.h>

/* 定義兩個用戶按鍵對應的GPIO,在開發(fā)板上對應的是GPIO0_6 和 GPIO6_1 */
#define DA850_USER_KEY0 GPIO_TO_PIN(0, 6) //SW5
#define DA850_USER_KEY1 GPIO_TO_PIN(6, 1) //SW6

#define DA850_KEYS_DEBOUNCE_MS 10
/*
* At 200ms polling interval it is possible to miss an
* event by tapping very lightly on the push button but most
* pushes do result in an event; longer intervals require the
* user to hold the button whereas shorter intervals require
* more CPU time for polling.
*/
#define DA850_GPIO_KEYS_POLL_MS 200

#if 0
/* assign the tl base board KEY-GPIOs*/
static const short tl138_user_key_pins[] = {
DA850_GPIO0_6, DA850_GPIO6_1,
-1
};
#endif

/*定義兩個按鍵對應的GPIO號,有效電平(按下電平),名稱,觸發(fā)模式*/
/*使用linux提供的標準gpio-keys框架*/
static struct gpio_keys_button tl138_user_keys[] = {
[0] = {
.type = EV_KEY,
.active_low = 1,  /*有效電平(按下電平):高電平*/
.wakeup = 0,
.debounce_interval = DA850_KEYS_DEBOUNCE_MS,
.code = KEY_PROG1,
.desc = "user_key0", /*名稱*/
.gpio = DA850_USER_KEY0, /*GPIO號:按鍵對應GPIO管腳*/
},
[1] = {
.type = EV_KEY,
.active_low = 1,
.wakeup = 0,
.debounce_interval = DA850_KEYS_DEBOUNCE_MS,
.code = KEY_PROG2,
.desc = "user_key1",
.gpio = DA850_USER_KEY1,
},
};

/*使用linux提供的標準platform_device 框架*/
static struct gpio_keys_platform_data tl138_user_keys_pdata = {
.buttons = tl138_user_keys,
.nbuttons = ARRAY_SIZE(tl138_user_keys),
//.poll_interval = DA850_GPIO_KEYS_POLL_MS,
};

static void  tl138_user_keys_release(struct device *dev)
{
};

static struct platform_device  tl138_user_keys_device = {
.name = "gpio-keys",
.id = 1,  /*可以先確定id號是否已經被使用,注意這個id是platform_device的id,跟按鍵個數無關*/
.dev = {
.platform_data = &tl138_user_keys_pdata,
.release = tl138_user_keys_release,
},
};

static int  __init  tl138_user_keys_init(void)
{
int ret;
#if 0     
ret = davinci_cfg_reg_list(tl138_user_key_pins);
if (ret)
pr_warning("tl138_user_keys_init : User KEYS mux failed :"
"%d\n", ret);
#endif
/*注冊KEY device設備,系統(tǒng)中的KEY框架將會接收到這個注冊,生成相應在/dev/input下生成響應的設備節(jié)點*/
ret = platform_device_register(&tl138_user_keys_device);
if (ret)
pr_warning("Could not register baseboard GPIO tronlong keys");
        else
                printk(KERN_INFO "USER KEYS register sucessful!\n");
       return ret;
}

static void __exit tl138_user_keys_exit(void)
{
platform_device_unregister(&tl138_user_keys_device);

printk(KERN_INFO "KEYS unregister!\n");
}

module_init(tl138_user_keys_init);
module_exit(tl138_user_keys_exit);
MODULE_DESCRIPTION("USER KEYS platform driver");
MODULE_AUTHOR("Tronlong");
MODULE_LICENSE("GPL");

編譯按鍵設備驅動程序開發(fā)板資料光盤中有按鍵設備驅動程序Makefile文件,其路徑為:
Makefile: demo\driver\linux-3.3\button\Makefile
以下為按鍵設備驅動程序Makefile文件的解析:
ifneq ($(KERNELRELEASE),)
obj-m := button.o/*定義了要編譯的驅動文件為button.c,生成的模塊名字為button.ko*/
else
/*以下定義運行編譯命令時使用的內核源碼、驅動源碼路徑、平臺、使用的交叉編譯工具鏈等參數*/
all:
make -C $(KDIR) M=$(PWD) modules ARCH=arm CROSS_COMPILE=$(CROSS_COMPILE)
/*定義運行"make clean"時清除的文件*/
clean:
rm -rf *.ko *.o *.mod.o *.mod.c *.symvers  modul* .button.* .tmp_versions .*.*.cmd
help:
@echo "make KDIR=<you kernel path> CROSS_COMPILE=<your CROSS_COMPILE>"
endif
&#8203;

圖 3

將光盤"demo\driver\linux-3.3\button"中的button.c和Makefile文件復制到Ubuntu任意路徑,在button.c和Makefile文件所在目錄運行如下命令編譯按鍵設備驅動程序:
Host#make KDIR=/home/tl/omapl138/linux-3.3 CROSS_COMPILE=arm-none-linux-gnueabi-
&#8203;

圖4

即可看到已生成驅動程序鏡像文件button.ko。"KDIR=/home/tl/omapl138/linux-3.3"是內核源碼路徑,在運行前必須已正確編譯過內核源碼。
"CROSS_COMPILE=arm-none-linux-gnueabi-"是交叉編譯工具鏈,從此項可以看出,Makefile文件中的一些編譯參數可以以變量的形式,通過編譯命令參數傳遞進去。

&#8203;&#8203;&#8203;&#8203;&#8203;&#8203;&#8203;按鍵設備驅動測試程序解析開發(fā)板資料光盤中有按鍵設備驅動測試程序源碼,其路徑為:
button_test.c demo\app\button\button_test.c
以下為按鍵設備驅動測試程序解析:
/*頭文件*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <time.h>
#include <fcntl.h>
#include <linux/input.h>

int main(int argc, char **argv)
{
int key_state;
int fd;
int ret;
int code;
struct input_event buf;

/*打開按鍵設備節(jié)點*/
fd = open("/dev/input/event1", O_RDONLY);

if (fd < 0) {
printf("Open Gpio_Keys failed!\n");
return -1;
}

/*打印成功打開按鍵設備節(jié)點提示信息*/
printf("Open Gpio_Keys successed!\n");

while(1) {
/*監(jiān)聽按鍵狀態(tài)*/
ret = read(fd, &buf, sizeof(struct input_event));
if (ret <= 0) {
printf("read failed!\n");
return -1;
}
code = buf.code;
key_state = buf.value;

switch(code)
{
case KEY_PROG1:
code = '1';
break;
case KEY_PROG2:
code = '2';
break;
}

if(code != 0)
/*打印按鍵狀態(tài)信息*/
printf("KEY_PROG_%c state= %d.\n", code, key_state);

}

printf("Key test finished.\n");
close(fd);
return 0;
}

編譯設備驅動測試程序將button_test.c文件復制到Ubuntu任意路徑,在button_test.c文件所在目錄運行如下命令編譯按鍵設備驅動測試程序:
Host#arm-none-linux-gnueabi-gcc button_test.c -o button_test
&#8203;

圖 5

可以看到在當前目錄生成了測試程序鏡像文件button_test。具體按鍵測試步驟請看用戶手冊快速體驗相關小節(jié)。

設備驅動模塊靜態(tài)編譯進內核假如需要將設備驅動程序模塊靜態(tài)編譯進內核,請按照如下步驟操作。
以LED設備驅動程序為例,將光盤"demo\driver\linux-3.3\led"目錄下的設備驅動程序源代碼led.c放到內核源碼"drivers/char"目錄下,修改內核源碼"drivers/char"目錄下Kconfig菜單配置文件,在"menu "Character devices""行下面添加如下內容:
&#8203;

圖 6

config USER_LED:USER_LED是驅動程序的配置名稱。
tristate "user led":在使用"make menuconfig"配置內核時菜單欄出現的驅動名字。
depends on ARM:注明是ARM平臺下的驅動程序。
default y:默認是靜態(tài)編譯到內核鏡像的。
---help---:驅動程序的補充信息,讓用戶進一步了解此驅動程序的作用。
修改內核源碼"drivers/char"目錄下的Makefile編譯文件,在最后添加如下內容:
obj-$(CONFIG_USER_LED)    += led.o
&#8203;

圖7

obj-$(CONFIG_USER_LED):"USER_LED"此內容必須和前面步驟Kconfig文件中添加的內容一致。
+= led.o:這個前綴必須是"led",編譯驅動程序時,系統(tǒng)會去找"driver/char"目錄下的led.c文件。
在內核源碼頂層目錄執(zhí)行以下命令查看設備內核配置情況:
Host#make menuconfig ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabi-
可在"device drivers->character devices"下有"user led"的驅動配置選項,如下圖:

&#8203;

圖 8

前面的"*"符號代表將設備驅動模塊靜態(tài)編譯進內核。保存退出,并重新編譯內核,然后使用編譯得到的內核鏡像啟動開發(fā)板,可發(fā)現在不用安裝led.ko的情況下,可以直接運行l(wèi)ed_loop.sh來實現LED的循環(huán)點亮。
若需要將設備驅動模塊編譯成內核模塊的形式,按空格鍵將"*"變?yōu)?quot;M",變?yōu)榭毡硎静痪幾g。

嵌入式DSP、ARM、FPGA多核技術開發(fā),學習資料下載:http://site.tronlong.com/pfdownload
回復

使用道具 舉報

發(fā)表回復

您需要登錄后才可以回帖 登錄 | 立即注冊

本版積分規(guī)則

關閉

站長推薦上一條 /1 下一條


聯系客服 關注微信 下載APP 返回頂部 返回列表