您的位置:首页 > 运维架构 > Linux

Linux设备驱动开发基础---字符设备驱动程序开发之mini2440_LED驱动

2013-06-29 20:49 946 查看
一、硬件原理分析

在mini2440 的原理图中,我们可以看到LED 的硬件连接是这样的:



在S3C2440 芯片中,很多的端口都是可复用的,GPIO 只是其中的一个功能,比如这里的GPB5 端口,既可以用作普通的GPIO,也可以用作专用功能nXBACK,因此我们需要设置相应的端口寄存器,以改变引脚的用途。

在这里,四个LED 是采用GPBCON 寄存器上的4 组2bit 位来配置对应引脚的用途。4组2bit 位的功能都一样:00 表示输入,01 表示输出,10 为特殊功能,11 是保留的。

在mini2440 开发板中,

LED1 对应的是GPB5, GPB5 使用[11:10]位

LED2 对应的是GPB6, GPB6 使用[12:13]位

LED3 对应的是GPB7, GPB7 使用[14:15]位

LED4 对应的是GPB8, GPB8 使用[16:17]位

因此,驱动程序中需要先设置LED 为输出状态,也就是要把对应的GPBX 设置为01。

而 GPBDAT 寄存器用来对应4 个LED 的数值状态,GPBDAT5 就对应GPB5,GPBDAT6就对应GPB6,以此类推。根据原理图可以看出,当GPIO 输出为低电平时有效,就是说当GPBDAT 寄存器位置为0 时,GPB5,6,7,8 将输出低电平,对应的LED 就发光。

在软件中,要操作所用到的 IO 口,我们通常调用一些现成的函数或者宏,例如:s3c2410_gpio_cfgpin,为什么是S3C2410 的呢?因为三星出品的S3C2440 芯片所用的寄存器名称以及资源分配大部分和S3C2410 是相同的,在目前各个版本的Linux 系统中,也大都采用了相同的函数定义和宏定义。

它们从哪里定义?细心的用户或许很快就想到它们和体系结构有关,因此你可以在

linux-2.6.32.2/arch/arm/plat-s3c24x/gpio.c 文件中找到该函数的定义和实现,由于内核版本的变更,该函数的定义及实现有可能也会变动,但在此我们只要大概了解就可以了,因为该函数的名称一般是不会变的,我们可以在其他驱动源代码中找到包含该函数的包含头文件,然后依此照搬就可以了。

我们并不需要关心s3c2410_gpio_setpin、s3c2410_gpio_cfgpin等是怎么实现的,写驱动时只要会使用他们就可以了,除非你所使用的CPU 体系平台尚没有被Linux 所支持,因为大部分常见的嵌入式平台都已经有了很完善的类似定义,你不需要自己去编写。

二、实现方式

1、这里LED将被注册成misc设备,代码放在drivers/misc 目录下,驱动程序文件mini2440_leds.c,内容如下:

#include <linux/miscdevice.h>

#include <linux/delay.h>

#include <asm/irq.h>

#include <mach/regs-gpio.h>

#include <mach/hardware.h>

#include <linux/kernel.h>

#include <linux/module.h>

#include <linux/init.h>

#include <linux/mm.h>

#include <linux/fs.h>

#include <linux/types.h>

#include <linux/delay.h>

#include <linux/moduleparam.h>

#include <linux/slab.h>

#include <linux/errno.h>

#include <linux/ioctl.h>

#include <linux/cdev.h>

#include <linux/string.h>

#include <linux/list.h>

#include <linux/pci.h>

#include <linux/gpio.h>

#include <asm/uaccess.h>

#include <asm/atomic.h>

#include <asm/unistd.h>

#define DEVICE_NAME "leds" //设备名(/dev/leds)

//LED 对应的GPIO 端口列表

static signed long led_table [] = {

S3C2410_GPB(5),

S3C2410_GPB(6),

S3C2410_GPB(7),

S3C2410_GPB(8), /*引脚定义见arch/arm/mach-s3c2410/include/mach/gpio-nrs.h*/

};

//LED 对应端口将要输出的状态列表

static unsigned int led_cfg_table [] = {

S3C2410_GPIO_OUTPUT,

S3C2410_GPIO_OUTPUT,

S3C2410_GPIO_OUTPUT,

S3C2410_GPIO_OUTPUT, /*定义见arch/arm/mach-s3c2410/include/mach/regs-gpio.h*/

};

/*

* 应用程序对设备文件/dev/leds执行open(...)时,

* 就会调用mini2440_leds_open函数

*/

static int mini2440_leds_open(struct inode *inode, struct file *file)

{

int i;

for (i = 0; i < 4; i++) {

// 设置GPIO引脚的功能:本驱动中LED所涉及的GPIO引脚设为输出功能

s3c2410_gpio_cfgpin(led_table[i], led_cfg_table[i]);

}

return 0;

}

/*

*ioctl 函数的实现

* 在应用/用户层将通过ioctl 函数向内核传递参数,以控制LED 的输出状态

*/

static int mini2440_leds_ioctl(struct inode *inode,

struct file *file,

unsigned int cmd, /*LED灯状态*/

unsigned long arg) /*LED灯编号*/

{

switch(cmd) {

case 0:

case 1:

if (arg > 4) {

return -EINVAL;

}

//根据应用/用户层传递来的参数(取反),通过s3c2410_gpio_setpin 函数设置LED 对应的端口寄存器

s3c2410_gpio_setpin(led_table[arg], !cmd);

return 0;

default:

return -EINVAL;

}

}

/*

* 这个结构是字符设备驱动程序的核心

* 当应用程序操作设备文件时所调用的open、read、write等函数,

* 最终会调用这个结构中指定的对应函数

*/

static struct file_operations mini2440_leds_fops = {

.owner = THIS_MODULE, /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */

.open = mini2440_leds_open,

.ioctl = mini2440_leds_ioctl,

};

/*

* 把LED 驱动注册为MISC 设备

*/

static struct miscdevice mini2440_leds_misc = {

.minor = MISC_DYNAMIC_MINOR, //动态设备号

.name = DEVICE_NAME,

.fops = &mini2440_leds_fops,

};

/*

*设备初始化

* 执行“insmod mini240_leds.ko”命令时就会调用这个函数

*/

static int __init mini2440_leds_init(void)

{

int ret;

/* 注册字符设备驱动程序

* 参数为主设备号、设备名字、file_operations结构;

* 这样,主设备号就和具体的file_operations结构联系起来了,

* 操作主设备为LED_MAJOR的设备文件时,就会调用mini2440_leds_fops中的相关成员函数

*/

ret = misc_register(&mini2440_leds_misc); //注册设备,告诉内核

if(ret < 0)

{

printk(DEVICE_NAME "register falid!\n");

return ret;

}

printk (DEVICE_NAME " initialized\n"); //打印初始化信息

return 0;

}

/*

* 执行”rmmod mini240_leds.ko”命令时就会调用这个函数

*/

static void __exit mini2440_leds_exit(void)

{

misc_deregister(&mini2440_leds_misc);

printk (DEVICE_NAME " exit sucessful\n"); //打印卸载信息

}

//模块初始化,仅当使用insmod/podprobe 命令加载时有用,如果设备不是通过模块方式加载,此处将不会被调用

module_init(mini2440_leds_init);

//卸载模块,当该设备通过模块方式加载后,可以通过rmmod 命令卸载,将调用此函数

module_exit(mini2440_leds_exit);

/* 描述驱动程序的一些信息,不是必须的 */

MODULE_AUTHOR("DreamCatcher"); // 驱动程序的作者

MODULE_DESCRIPTION("MINI2440 LED Driver"); // 一些描述信息

MODULE_LICENSE("GPL"); // 遵循的协议

注:不在模块的初始化函数中设置引脚初始化的原因是:虽然加载了模块,但是这个模块却不一定会被用到,就是说引脚不一定用于这些用途,他们可能在其它弄块中另做他用。所以,在使用时才去设置它,我们把对引脚的初始化放在open操作中。

2、为内核添加LED 设备的内核配置选项,打开drivers/misc/Kconfig 文件,添加如下红色部分内容:

if MISC_DEVICES

config LEDS_MINI2440

tristate "LED Support for Mini2440 GPIO LEDs"

depends on MACH_MINI2440

default y if MACH_MINI2440

help

This option enables support for LEDs connected to GPIO lines

on Mini2440 boards.

3、把驱动目标文件加入内核

接下来,再根据该驱动的配置定义,把对应的驱动目标文件加入内核中,打开linux-2.6.32.2/drivers/misc/Makefile 文件,添加如下红色部分内容:

obj-$(CONFIG_LEDS_MINI2440) += mini2440_leds.o

这样,我们就在内核中添加做好了LED 驱动

三、编译测试

1、在内核源代码目录下执行:make menuconfig 重新配置内核,依次选择进入如下子菜单项:

Device Drivers --->

[*] Misc devices --->

<M> LED Support for Mini2440 GPIO LEDs //选项默认是选中的,这里我们将其编译为模块。

退出并保存内核配置。

在源代码目录下执行:clx@think:/work/armlinux/linux-2.6.32.2$ make modules

将生成的drivers/misc/mini2440_leds.ko拷贝到/nfsboot/rootfs/usr/modules目录下,通过网络根文件系统引导内核,执行如下操作。

rroot@MINI2440:/usr/modules# insmod mini2440_leds.ko

leds initialized

root@MINI2440:/usr/modules# lsmod

mini2440_leds 838 0 - Live 0xbf000000

可以看到LED驱动成功加载。

2、用应用程序测试驱动模块

为了测试该驱动程序,我们还需要编写一个简单的测试程序,用来调用驱动程序中的ioctl 函数,以达到通过应用程序控制LED 的目的。

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

#include <sys/ioctl.h>

void usage()

{

printf("Usage:\n");

printf(" %s 1 | 0 \n", "LDE sate");

printf(" led_no = 0, 1, 2 or 3\n");

}

int main(int argc, char **argv)

{

int on;

int led_no;

int fd;

if (argc != 3 || sscanf(argv[1], "%d", &led_no) != 1 || sscanf(argv[2],"%d", &on) != 1 ||

on < 0 || on > 1 || led_no < 0 || led_no > 3) {

usage();

exit(1);

}

fd = open("/dev/leds", 0);

if (fd < 0) {

perror("open device leds");

exit(1);

}

ioctl(fd, on, led_no);

close(fd);

return 0;

}

root@MINI2440:/opt# ls

Helloword backlight_test led_test

root@MINI2440:/opt# ./led_test 0 0

root@MINI2440:/opt# ./led_test 1 0

其中第一个参数为要控制的LED 序号(0~3分别对应开发板上Led1~Led4),第二个参数代表关闭(0)或者打开(1)对应的LED。

注:对于驱动程序中用到的一些与硬件操作相关的函数请看一下解释:(驱动程序存在一些问题,运行modinfo查看不到驱动设置的一些信息声明,模块也卸载不掉,目前没有找出原因)1、解决的一个问题可以显示信息声明的方法创建modules.dep, /nfsboot/rootfs/lib/modules/2.6.32.2$ sudo vi modules.dep 目前模块还是没法卸载。

20130707 2、模块不能卸载的问题目前已经找到,因为驱动中好多函数的名字也是以mini2440_leds开头导致模块的名字和一些函数的名字一样而导致错误。驱动中函数的名字统一以led_开头即可避免此类问题。修改后执行:

root@MINI2440:/lib/modules/2.6.32.2# rmmod mini2440_leds

leds exit sucessful

/work/armlinux/linux-2.6.32.2/Documentation/arm/Samsung-S3C24XX$ gedit GPIO.txt

S3C2410 GPIO Control

====================

Introduction

------------

The s3c2410 kernel provides an interface to configure and

manipulate the state of the GPIO pins, and find out other

information about them.

There are a number of conditions attached to the configuration

of the s3c2410 GPIO system, please read the Samsung provided

data-sheet/users manual to find out the complete list.

GPIOLIB

-------

With the event of the GPIOLIB in drivers/gpio, support for some of the GPIO functions such as reading and writing a pin will be removed in favour of this common access method.

Once all the extant drivers have been converted, the functions listed below will be removed (they may be marked as __deprecated in the near future).

- s3c2410_gpio_getpin

- s3c2410_gpio_setpin

Headers

-------

See arch/arm/mach-s3c2410/include/mach/regs-gpio.h for the list of GPIO pins, and the configuration values for them. This is included by using

#include <mach/regs-gpio.h>

The GPIO management functions are defined in the hardware header arch/arm/mach-s3c2410/include/mach/hardware.h which can be included by #include<mach/hardware.h>

A useful amount of documentation can be found in the hardware header on how the GPIO functions (and others) work.

Whilst a number of these functions do make some checks on what is passed to them, for speed of use, they may not always ensure that the user supplied data to them is correct.

PIN Numbers

-----------

Each pin has an unique number associated with it in regs-gpio.h, eg
S3C2410_GPA(0) or S3C2410_GPF(1). These defines are used to tell the GPIO functions which pin is to be used.

Configuring a pin

-----------------

The following function allows the configuration of a given pin to be changed.

void s3c2410_gpio_cfgpin(unsigned int pin, unsigned int function);

Eg:

s3c2410_gpio_cfgpin(S3C2410_GPA(0), S3C2410_GPA0_ADDR0);

s3c2410_gpio_cfgpin(S3C2410_GPE(8), S3C2410_GPE8_SDDAT1);

which would turn GPA(0) into the lowest Address line A0, and set GPE(8) to be connected to the SDIO/MMC controller's SDDAT1 line.

Reading the current configuration

---------------------------------

The current configuration of a pin can be read by using:

s3c2410_gpio_getcfg(unsigned int pin);

The return value will be from the same set of values which can be passed to s3c2410_gpio_cfgpin().

Configuring a pull-up resistor

------------------------------

A large proportion of the GPIO pins on the S3C2410 can have weak pull-up resistors enabled. This can be configured by the following function:

void s3c2410_gpio_pullup(unsigned int pin, unsigned int to);

Where the to value is zero to set the pull-up off, and 1 to enable the specified pull-up. Any other values are currently undefined.

Getting the state of a PIN

--------------------------

The state of a pin can be read by using the function:

unsigned int s3c2410_gpio_getpin(unsigned int pin);

This will return either zero or non-zero. Do not count on this function returning 1 if the pin is set.

Setting the state of a PIN

--------------------------

The value an pin is outputing can be modified by using the following:

void s3c2410_gpio_setpin(unsigned int pin, unsigned int to);

Which sets the given pin to the value. Use 0 to write 0, and 1 to

set the output to 1.

Getting the IRQ number associated with a PIN

--------------------------------------------

The following function can map the given pin number to an IRQ

number to pass to the IRQ system.

int s3c2410_gpio_getirq(unsigned int pin);

Note, not all pins have an IRQ.

Authour

-------

Ben Dooks, 03 October 2004

(c) 2004 Ben Dooks, Simtec Electronics
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐