打印
[ARM入门]

基于OMAPL138的Linux设备驱动程序开发入门

[复制链接]
3021|0
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
LED设备驱动程序 LED设备驱动程序解析开发板LED编号和GPIO对应关系如下:

表 1
开发板型号
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

开发板资料光盘中有LED设备驱动程序源码,其路径为:
led.c:demo\driver\linux-3.3\led\led.c
下面以TL138/1808-EVM开发板为例讲解此设备驱动程序。
#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,开发板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号、有效电平(熄灯电平)、名称、触发模式等*/
/*使用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", /*触发模式:默认点亮*/
},
{
.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设备,系统LED框架将会接收到这个注册,生成相应LED节点*/
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/"生成相应的设备节点。
  • 用户可以通过读写节点目录下的brightness文件控制LED亮灭。
对于GPIO口的操作,有以下几点步骤:
  • 查看开发板的原理图,找到与LED连接的GPIO。TL138/1808-EVM开发板与LED连接的GPIO分别是GPIO0[5]、GPIO0[0]、GPIO0[1]、GPIO0[2]。
  • 查看OMAP-L138的数据手册,查找对应PINMUX寄存器的地址,将对应的管脚的寄存器中相应位设置为GPIO的工作模式。本例中使用的是PINMUX1。
  • 设置GPIO的方向寄存器。本例程中将GPIO口配置为输出。
  • 配置GPIO的数据寄存器,写"1"表示输出高电平,写"0"表示输出低电平。
编译LED设备驱动程序此处使用Makefile编译LED设备驱动程序。工程中源文件有时候很多,其按类型、功能、模块分别放在若干个目录中,Makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为Makefile就像一个Shell脚本一样,其中也可以执行操作系统的命令。
开发板资料光盘中有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

图 1

将光盘"demo\driver\linux-3.3\led"的led.c和Makefile文件复制到开发系统Ubuntu任意路径,并在led.c和Makefile目录运行以下命令编译LED设备驱动程序:
Host#make KDIR=/home/tl/omapl138/linux-3.3

图2

"KDIR=/home/tl/omapl138/linux-3.3"是内核源码路径,在运行前必须已正确编译过内核源码。运行以上命令后,系统会根据Makefile文件的规则去编译整个驱动源码,产生了驱动程序镜像文件led.ko和其他中间文件。

LED设备驱动测试脚本解析开发板资料光盘有LED设备驱动测试脚本,运行此测试脚本LED会循环点亮。其路径为:
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测试步骤请查看用户手册快速体验相关小节。

​​​​​​​按键设备驱动程序​​​​​​​按键设备驱动程序解析
开发板资料光盘中有按键设备驱动程序源码,对应的按键为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,在开发板上对应的是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号,有效电平(按下电平),名称,触发模式*/
/*使用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设备,系统中的KEY框架将会接收到这个注册,生成相应在/dev/input下生成响应的设备节点*/
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");

编译按键设备驱动程序开发板资料光盘中有按键设备驱动程序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

图 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-

图4

即可看到已生成驱动程序镜像文件button.ko。"KDIR=/home/tl/omapl138/linux-3.3"是内核源码路径,在运行前必须已正确编译过内核源码。
"CROSS_COMPILE=arm-none-linux-gnueabi-"是交叉编译工具链,从此项可以看出,Makefile文件中的一些编译参数可以以变量的形式,通过编译命令参数传递进去。

​​​​​​​按键设备驱动测试程序解析开发板资料光盘中有按键设备驱动测试程序源码,其路径为:
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;

/*打开按键设备节点*/
fd = open("/dev/input/event1", O_RDONLY);

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

/*打印成功打开按键设备节点提示信息*/
printf("Open Gpio_Keys successed!\n");

while(1) {
/*监听按键状态*/
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)
/*打印按键状态信息*/
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

图 5

可以看到在当前目录生成了测试程序镜像文件button_test。具体按键测试步骤请看用户手册快速体验相关小节。

设备驱动模块静态编译进内核假如需要将设备驱动程序模块静态编译进内核,请按照如下步骤操作。
以LED设备驱动程序为例,将光盘"demo\driver\linux-3.3\led"目录下的设备驱动程序源代码led.c放到内核源码"drivers/char"目录下,修改内核源码"drivers/char"目录下Kconfig菜单配置文件,在"menu "Character devices""行下面添加如下内容:

图 6

config USER_LED:USER_LED是驱动程序的配置名称。
tristate "user led":在使用"make menuconfig"配置内核时菜单栏出现的驱动名字。
depends on ARM:注明是ARM平台下的驱动程序。
default y:默认是静态编译到内核镜像的。
---help---:驱动程序的补充信息,让用户进一步了解此驱动程序的作用。
修改内核源码"drivers/char"目录下的Makefile编译文件,在最后添加如下内容:
obj-$(CONFIG_USER_LED)    += led.o

图7

obj-$(CONFIG_USER_LED):"USER_LED"此内容必须和前面步骤Kconfig文件中添加的内容一致。
+= led.o:这个前缀必须是"led",编译驱动程序时,系统会去找"driver/char"目录下的led.c文件。
在内核源码顶层目录执行以下命令查看设备内核配置情况:
Host#make menuconfig ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabi-
可在"device drivers->character devices"下有"user led"的驱动配置选项,如下图:

图 8

前面的"*"符号代表将设备驱动模块静态编译进内核。保存退出,并重新编译内核,然后使用编译得到的内核镜像启动开发板,可发现在不用安装led.ko的情况下,可以直接运行led_loop.sh来实现LED的循环点亮。
若需要将设备驱动模块编译成内核模块的形式,按空格键将"*"变为"M",变为空表示不编译。

使用特权

评论回复

相关帖子

发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

63

主题

63

帖子

0

粉丝