Chinaunix首页 | 论坛 | 博客
  • 博客访问: 119686
  • 博文数量: 26
  • 博客积分: 1441
  • 博客等级: 上尉
  • 技术积分: 265
  • 用 户 组: 普通用户
  • 注册时间: 2009-09-27 20:35
文章存档

2011年(2)

2010年(5)

2009年(19)

我的朋友

分类: LINUX

2010-04-12 15:28:53

驱动要实现的功能:对外提供对/dev/leds 设备进行ioctl时的ioctl函数。也就是简单的led驱动。

 

编写驱动主要思想:编写驱动主要思想:1。使用misc设备类型,misc设备类型主要涉及到驱动的注册注销,文件操作函数;2 设备是几个GPIO ,对设备的操作就是对GPIO的操作,对 S3C2440 GPIO的操作常用的内核函数有:s3c2410_gpio_cfgpin(),s3c2410_gpio_setpin,等,函数的定义在文件/arch/arm/plat-s3c24xx3 设备文件的生成,主次主词设备号,probe()discnect()函数

了解设备及连接情况:

开发板上4LED 引脚使用了CPUGPB5 GPB6 GPB7 GPB8端口,关于GPB端口的描述及使用在S3C2440数据手册中有如下描述

 

 GPB8  Input/Output  nXDREQ1
 GPB7  Input/Output  nXDACK1
 GPB6  Input/Output  nXBREQ
 GPB5  Input/Output  nXBACK
要使用GPB5-8,需要:

(1)       设置GPBCON,以使引脚功能为IO功能输出

(2)       要点亮LEDGPBDAT 对应的位置要设置为 0

(3)       要熄灭LEDGPBDAT 对应的位置要设置为 1

 

驱动源代码luo_led.c如下:

#include

#include

#include

#include //s3c2410_gpio_setpin() s3c2410_gpio_cfgpin()

#include   //struct file_operations

#include //struct misc

 

#define DEVICE_NAME "leds"

 

static unsigned long led_table [] = {

       S3C2410_GPB(5),

       S3C2410_GPB(6),

       S3C2410_GPB(7),

       S3C2410_GPB(8),

};

 

static unsigned int led_cfg_table [] = {

       S3C2410_GPIO_OUTPUT,

       S3C2410_GPIO_OUTPUT,

       S3C2410_GPIO_OUTPUT,

       S3C2410_GPIO_OUTPUT,

};

 

static int sbc2440_leds_ioctl(

       struct inode *inode,

       struct file *file,

       unsigned int cmd,

       unsigned long arg)

{

       switch(cmd) {

       case 0:

       case 1:

              if (arg > 4) {

                     return -EINVAL;

              }

              s3c2410_gpio_setpin(led_table[arg], !cmd);

              return 0;

       default:

              return -EINVAL;

       }

}

 

static struct file_operations dev_fops = {

       .owner    =     THIS_MODULE,

       .ioctl       =     sbc2440_leds_ioctl,

};

 

static struct miscdevice misc = {

       .minor = MISC_DYNAMIC_MINOR,

       .name = DEVICE_NAME,

       .fops = &dev_fops,

};

 

static int __init dev_init(void)

{

       int ret;

 

       int i;

      

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

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

              s3c2410_gpio_setpin(led_table[i], 0);

       }

 

       ret = misc_register(&misc);

 

       printk (DEVICE_NAME"\tinitialized\n");

 

       return ret;

}

 

static void __exit dev_exit(void)

{

       misc_deregister(&misc);

}

 

module_init(dev_init);

module_exit(dev_exit);

MODULE_LICENSE("GPL");

MODULE_AUTHOR("FriendlyARM Inc.");

 

Makefile(M大写)如下:

#在安装好内核头文件后,就是把内核头文件拷贝到/usr/src/文件加下面,可以是删除平台相关文件厚的内核源代码(如友善自带的linux2.6.32-2,它的跟目录下的makefile 已经指明用arm-lninux-gcc),然后编写此MakefileM要大写,而且不能定义 编译器,因为这里的makefile是和内核头文件中的makefile们关联的。

obj-m:=luo_led.o

 

KERNELDIR ?=/lib/modules/2.6.32.2-FriendlyARM/build

PWD:=$(shell pwd)

default:

       $(MAKE) -C $(KERNELDIR) M=$(PWD) modules

 

 

驱动分析:S3C2410_GPB(),是个宏,作用就是将所有的GPIO进行分组编号,从0开始每32个算一组,一共分为ABCDEFGH J这些组,S3C2410_GPB(),该宏就是逻辑上的分组,地址映射有s3c2410_gpio_setpin来决定。S3C2410_GPIO_OUTPUT,也是一个宏,定义为:#define S3C2410_GPIO_OUTPUT  (0xFFFFFFF1),大部分GPIO都是复用的,都会留两位进行配置,如输入还是输出还是功能模式如TCLK,一般还有一位是保留的。s3c2410_gpio_setpin/arch/arm/plat-s3c24xx/gpio.c中实现,如下:

void s3c2410_gpio_setpin(unsigned int pin, unsigned int to)

{

       void __iomem *base = S3C24XX_GPIO_BASE(pin);

       unsigned long offs = S3C2410_GPIO_OFFSET(pin);

       unsigned long flags;

       unsigned long dat;

 

       local_irq_save(flags);

 

       dat = __raw_readl(base + 0x04);

       dat &= ~(1 << offs);

       dat |= to << offs;

       __raw_writel(dat, base + 0x04);

 

       local_irq_restore(flags);

}

s3c2410_gpio_cfgpin函数同样也在/arch/arm/plat-s3c24xx/gpio.c中实现,如下:

void s3c2410_gpio_cfgpin(unsigned int pin, unsigned int function)

{

       void __iomem *base = S3C24XX_GPIO_BASE(pin);

       unsigned long mask;

       unsigned long con;

       unsigned long flags;

 

       if (pin < S3C2410_GPIO_BANKB) {

              mask = 1 << S3C2410_GPIO_OFFSET(pin);

       } else {

              mask = 3 << S3C2410_GPIO_OFFSET(pin)*2;

       }

 

       switch (function) {

       case S3C2410_GPIO_LEAVE:

              mask = 0;

              function = 0;

              break;

 

       case S3C2410_GPIO_INPUT:

       case S3C2410_GPIO_OUTPUT:

       case S3C2410_GPIO_SFN2:

       case S3C2410_GPIO_SFN3:

              if (pin < S3C2410_GPIO_BANKB) {

                     function -= 1;

                     function &= 1;

                     function <<= S3C2410_GPIO_OFFSET(pin);

              } else {

                     function &= 3;

                     function <<= S3C2410_GPIO_OFFSET(pin)*2;

              }

       }

 

       /* modify the specified register wwith IRQs off */

 

       local_irq_save(flags);

 

       con  = __raw_readl(base + 0x00);

       con &= ~mask;

       con |= function;

 

       __raw_writel(con, base + 0x00);

 

       local_irq_restore(flags);

}

EXPORT_SYMBOL(s3c2410_gpio_cfgpin);

 

misc_registermisc_deregister都在/drivers/char/misc.c中实现,如下:

/**

 *    misc_register  -      register a miscellaneous device

 *    @misc: device structure

 *   

 *    Register a miscellaneous device with the kernel. If the minor

 *    number is set to %MISC_DYNAMIC_MINOR a minor number is assigned

 *    and placed in the minor field of the structure. For other cases

 *    the minor number requested is used.

 *    The structure passed is linked into the kernel and may not be

 *    destroyed until it has been unregistered.

 *

 *    A zero is returned on success and a negative errno code for

 *    failure.

 */

int misc_register(struct miscdevice * misc)

{

       struct miscdevice *c;

       dev_t dev;

       int err = 0;

 

       INIT_LIST_HEAD(&misc->list);

 

       mutex_lock(&misc_mtx);

       list_for_each_entry(c, &misc_list, list) {

              if (c->minor == misc->minor) {

                     mutex_unlock(&misc_mtx);

                     return -EBUSY;

              }

       }

       if (misc->minor == MISC_DYNAMIC_MINOR) {

              int i = DYNAMIC_MINORS;

              while (--i >= 0)

                     if ( (misc_minors[i>>3] & (1 << (i&7))) == 0)

                            break;

              if (i<0) {

                     mutex_unlock(&misc_mtx);

                     return -EBUSY;

              }

              misc->minor = i;

       }

       if (misc->minor < DYNAMIC_MINORS)

              misc_minors[misc->minor >> 3] |= 1 << (misc->minor & 7);

       dev = MKDEV(MISC_MAJOR, misc->minor);

 

       misc->this_device = device_create(misc_class, misc->parent, dev,

                                     misc, "%s", misc->name);

       if (IS_ERR(misc->this_device)) {

              err = PTR_ERR(misc->this_device);

              goto out;

       }

       /*

        * Add it to the front, so that later devices can "override"

        * earlier defaults

        */

       list_add(&misc->list, &misc_list);

 out:

       mutex_unlock(&misc_mtx);

       return err;

}

/**

 *    misc_deregister - unregister a miscellaneous device

 *    @misc: device to unregister

 *

 *    Unregister a miscellaneous device that was previously

 *    successfully registered with misc_register(). Success

 *    is indicated by a zero return, a negative errno code

 *    indicates an error.

 */

 

int misc_deregister(struct miscdevice *misc)

{

       int i = misc->minor;

 

       if (list_empty(&misc->list))

              return -EINVAL;

 

       mutex_lock(&misc_mtx);

       list_del(&misc->list);

       device_destroy(misc_class, MKDEV(MISC_MAJOR, misc->minor));

       if (i < DYNAMIC_MINORS && i>0) {

              misc_minors[i>>3] &= ~(1 << (misc->minor & 7));

       }

       mutex_unlock(&misc_mtx);

       return 0;

}

 

EXPORT_SYMBOL(misc_register);

EXPORT_SYMBOL(misc_deregister);

实验:

led驱动程序所在目录中 make一下,然后把luo_led.ko 文件下载到开发板,先改变模块的权限              chmod +x luo_led.ko,通过insmod luo_led.ko 加载内核,rmmod luo_led 卸载内核模块(不带 .ko.

遇到的问题:

1.       先看源代码,然后自己在写一遍,发现不知道用哪些头文件?-------à包含某个头文件之后,我可用哪些相关的函数 变量 宏,而不用自己去写,或者说是想用某个已经实现的函数 变量 那么必须包含哪些头文件,之后就可以使用了?头文件与头文件之间的包含关系--à只要知道最上层的头文件并把它包含在我们的模块中就可以。   S3C2410_GPB这个宏在哪个头文件定义的?这个头文件又被哪个相对上层的头文件包含?--à最顶层的头文件是哪个? 根据gcc的警告或者提示把错误改了,缺少的头文件加上。

S3c2410_gpio_setpin()函数查找实例:在删除不需要代码后用cscope 查找到S3c2410_gpio_setpin()所在的文件的头文件(使用cscopeegrep方式---不仅可以找到函数定义还可以找到函数的使用被包含在哪个头文件中,若用正则表达式则可以家关键字 extern),发现只有一个头文件gpio-fns.h中包含s3c2410_gpio_setpin函数,然后再找包含此头文件的头文件 发现只有 mach/gpio.h头文件中包含这个头文件,继续找哪个文件中包含mach/gpio.h ,发现只有 asm/gpio.h包含mach/gpio.h就是单纯的包含: #iclude 好了,我们用 mach/gpio.h asm/gpio.h 都可以找到我们要使用的s3c2410_gpio_setpin函数。这就是我们要找的头文件!

1.1    如何知道一个已近存在的变量 函数 宏是否能通过头文件包含来使用?一些代码不想让另一些代码通过头文件包含来使用怎么办?--------àlinux内核中变量 函数的作用域?----------à内核中哪些函数能在模块中调用,哪些函数不能被模块调用?---------àstatic extern 关键字,EXPORT_SYMBOL(var)EXPORT_SYMBOL(func)----à什么样的内核空间函数(什么关键词修饰)可以被模块调用?linux2.4内核下,默认情况时模块中的非静态全局变量级及非文件作用域函数在模块加载后会输出到内核空间。Linux2.6内核中,默认情况是模块中的非静态全局变量及非文件作用域函数在模块加载后会输出到内核空间中。输出到内核空间有意味着什么?

1.2    Linux内核函数与C函数是不是在需要的时候都需要给出头文件?是,但是本文中有些头文件并没有给出,原因是内核驱动程序,添加内核是根据编译器出错提示添加的头文件,原因是我们的makefile是内核头文件中所有makefile的成员,所以虽然没有包含许多头文件但是编译器也没有出错。

 

TIPS: 问题:cscope 如何查找上述问题的结果呢?在munu界面键入 可以得到cscope的使用说明命令行界面软件都有这个特点,如vivi help minicom 中按下 ctrl+z 会出现命令行菜单,友善supervivi中键入 menu可以回到 menu菜单。vim的命令菜单如何调出?在vim的底行模式下键入help就可以弹出帮助菜单。vim选中某一写文字,就表示复制了,在gedit中按中键表示粘贴。Vim有好几个缓存区。

1.3 怎么使用cscopeegrep功能?-----à正则表达式的使用.---àman文档中对命令的说明是通过正则表达式的方法来说明的。可以阅读此文:  正则表达式30分钟入门教程。比如查找一行中既包含extern关键字又包含 s3c2410_gpio_setpin 函数名,可以用正则表达式 /bextern/b.*/bs3c2410_gpio_setpin/b 来表示,其中两个 /b 之间是一个单词的开始与结束,  点(.)表示除了换行符以外的任意字符,* 代表数量表示可以重复前面内容(本表达式是 ,除了换行符以外的任意字符)任意次。

 

2 编译时出错:error:implicit declaration of function 's3c2410_gpio_setpin'

gcc编译c程序的时候 经常会出现

implicit declaration of function '...' warning

一般是下面三种情况造成:

1  没有把函数所在的c文件生成.o目标文件;

2  在函数所在的c文件中声明了,但是没有在调用它的.h.c文件中声明;

3  其头文件都声明过了,所调用的函数的原型与所传的实参类型不匹配.

3 设备如何获得设备号?

   注册设备的时候,有两种方式:一种是使用register_chrdev(LED_MAJOR,DEVICE_NAME,&dev_fops)LED_MAJOR为定义的主设备号,DEVICE_NAME为定义的设备名称,dev_fops为定义的文件操作结构体。使用该函数向系统注册字符型设备驱动程序,主设备号LED_MAJOR自己定义,如该值为0则系统自动分配主设备号;另一种是使用misc_register(&misc)。如果是非标准设备则使用 misc_register,即一些字符设备不符合预先确定的字符设备范畴,就用这种方式,它固定使用主设备号10注册,如果多个设备次设备号不同。

   使用register_chrdev(LED_MAJOR,DEVICE_NAME,&dev_fops)注册字符设备驱动程序时,如果有多个设备使用该函数注册驱动程序,LED_MAJOR不能相同,否则几个设备都无法注册(我已在友善的板子上验证)。如果模块使用该方式注册并且LED_MAJOR0(自动分配主设备号),使用insmod命令加载模块时会在终端显示分配的主设备号和次设备号,在/dev目录下建立该节点,比如设备leds,如果加载该模块时分配的主设备号和次设备号为2530,则建立节点:mknod leds c 253 0。使用register_chrdev(LED_MAJOR,DEVICE_NAME,&dev_fops)注册字符设备驱动程序时都要手动建立节点,否则在应用程序无法打开该设备。

 

补充:6410GPIO使用

下面的文章来自:http://blog.csdn.net/zhandoushi1982/archive/2010/03/20/5396754.aspx



    #define S3C64XX_GPIO_A_NR (8)
       #define S3C64XX_GPIO_B_NR (7)
       #define S3C64XX_GPIO_C_NR (8)
       #define S3C64XX_GPIO_D_NR (5)
       #define S3C64XX_GPIO_E_NR (5)
       #define S3C64XX_GPIO_F_NR (16)
       #define S3C64XX_GPIO_G_NR (7)
       #define S3C64XX_GPIO_H_NR (10)
       #define S3C64XX_GPIO_I_NR (16)
       #define S3C64XX_GPIO_J_NR (12)
       #define S3C64XX_GPIO_K_NR (16)
       #define S3C64XX_GPIO_L_NR (15)
       #define S3C64XX_GPIO_M_NR (6)
       #define S3C64XX_GPIO_N_NR (16)
       #define S3C64XX_GPIO_O_NR (16)
       #define S3C64XX_GPIO_P_NR (15)
       #define S3C64XX_GPIO_Q_NR (9)
二,每组GPIO的起始号码
    #define S3C64XX_GPIO_NEXT(__gpio) \
    ((__gpio##_START) + (__gpio##_NR) + CONFIG_S3C_GPIO_SPACE + 1)
      
用黏贴符号来运算的,以A组的0起始,依次加每组的GPIO个数
     enum s3c_gpio_number {
        S3C64XX_GPIO_A_START = 0,
        S3C64XX_GPIO_B_START = S3C64XX_GPIO_NEXT(S3C64XX_GPIO_A),
        S3C64XX_GPIO_C_START = S3C64XX_GPIO_NEXT(S3C64XX_GPIO_B),
        S3C64XX_GPIO_D_START = S3C64XX_GPIO_NEXT(S3C64XX_GPIO_C),
        S3C64XX_GPIO_E_START = S3C64XX_GPIO_NEXT(S3C64XX_GPIO_D),
        S3C64XX_GPIO_F_START = S3C64XX_GPIO_NEXT(S3C64XX_GPIO_E),
        S3C64XX_GPIO_G_START = S3C64XX_GPIO_NEXT(S3C64XX_GPIO_F),
        S3C64XX_GPIO_H_START = S3C64XX_GPIO_NEXT(S3C64XX_GPIO_G),
        S3C64XX_GPIO_I_START = S3C64XX_GPIO_NEXT(S3C64XX_GPIO_H),
        S3C64XX_GPIO_J_START = S3C64XX_GPIO_NEXT(S3C64XX_GPIO_I),
        S3C64XX_GPIO_K_START = S3C64XX_GPIO_NEXT(S3C64XX_GPIO_J),
        S3C64XX_GPIO_L_START = S3C64XX_GPIO_NEXT(S3C64XX_GPIO_K),
        S3C64XX_GPIO_M_START = S3C64XX_GPIO_NEXT(S3C64XX_GPIO_L),
        S3C64XX_GPIO_N_START = S3C64XX_GPIO_NEXT(S3C64XX_GPIO_M),
        S3C64XX_GPIO_O_START = S3C64XX_GPIO_NEXT(S3C64XX_GPIO_N),
        S3C64XX_GPIO_P_START = S3C64XX_GPIO_NEXT(S3C64XX_GPIO_O),
        S3C64XX_GPIO_Q_START = S3C64XX_GPIO_NEXT(S3C64XX_GPIO_P),
       };
三,单个GPIO脚的号码
    
以单组的起始号
         #define S3C64XX_GPA(_nr) (S3C64XX_GPIO_A_START + (_nr))
         #define S3C64XX_GPB(_nr) (S3C64XX_GPIO_B_START + (_nr))
         #define S3C64XX_GPC(_nr) (S3C64XX_GPIO_C_START + (_nr))
         #define S3C64XX_GPD(_nr) (S3C64XX_GPIO_D_START + (_nr))
         #define S3C64XX_GPE(_nr) (S3C64XX_GPIO_E_START + (_nr))
         #define S3C64XX_GPF(_nr) (S3C64XX_GPIO_F_START + (_nr))
         #define S3C64XX_GPG(_nr) (S3C64XX_GPIO_G_START + (_nr))
         #define S3C64XX_GPH(_nr) (S3C64XX_GPIO_H_START + (_nr))
         #define S3C64XX_GPI(_nr) (S3C64XX_GPIO_I_START + (_nr))
         #define S3C64XX_GPJ(_nr) (S3C64XX_GPIO_J_START + (_nr))
         #define S3C64XX_GPK(_nr) (S3C64XX_GPIO_K_START + (_nr))
         #define S3C64XX_GPL(_nr) (S3C64XX_GPIO_L_START + (_nr))
         #define S3C64XX_GPM(_nr) (S3C64XX_GPIO_M_START + (_nr))
         #define S3C64XX_GPN(_nr) (S3C64XX_GPIO_N_START + (_nr))
         #define S3C64XX_GPO(_nr) (S3C64XX_GPIO_O_START + (_nr))
         #define S3C64XX_GPP(_nr) (S3C64XX_GPIO_P_START + (_nr))
         #define S3C64XX_GPQ(_nr) (S3C64XX_GPIO_Q_START + (_nr))
四,判断GPIO是否有效

     比如:if (gpio_is_valid(S3C64XX_GPB(0)))
      static inline int gpio_is_valid(int number)    
      {                                              
            return ((unsigned)number) < ARCH_NR_GPIOS;   
      }   
     
因为定义了
#define ARCH_NR_GPIOS (S3C64XX_GPQ(S3C64XX_GPIO_Q_NR) + 1)    
     
同样类似的范围定义有:

      #define S3C64XX_GPIO_END (S3C64XX_GPQ(S3C64XX_GPIO_Q_NR) + 1)                                     
      #define S3C_GPIO_END  S3C64XX_GPIO_END    
五,判断GPIO是否被占用
     
比如:if(gpio_request(S3C64XX_GPB(0), "GPB")),通过查看该GPIO保存的记录标志是否为NULL来判断。
六,配置GPIO用途
     
由于GPIO多复用,所以不管是把它当作GPIO使用时,还是当作中断亦或UART口,需要配置它的用途。比如:s3c_gpio_cfgpin(S3C64XX_GPB(0), 0x2);根据芯片规格书来配置其端口。所有的这些配置工作在Gpiolib.c (\arch\arm\plat-s3c64xx) 来完成的。
七,上下拉

      有的GPIO口可以内部配置成上拉或者下拉,这样就不需要外部再接电阻连线。配置成上拉时,驱动能力更强。配置上下拉对外部接口来说呈现的只是一种默认的电平,其本身可以对外输出高低由软件控制,就像I2C
     
比如:s3c_gpio_setpull(S3C64XX_GPB(0), S3C_GPIO_PULL_DOWN);
八,配置成外中断

     s3c_gpio_cfgpin(HdPhone_GpioPin, S3C_GPIO_SFN(2));
     set_irq_type(HdPhone_EintPin, IRQ_TYPE_EDGE_BOTH); //
中断边沿类型
    
具体应用的话还需要用request_irq注册对应的中断处理函数。
八,配置GPIO方向,输入和输出
     
比如:gpio_direction_output(S3C64XX_GPL(0), 1);
九,使用
GPIO
    
比如输出:gpio_set_value(S3C64XX_GPG(1), 1);  或者是输入:gpio_get_value(S3C64XX_GPQ(8))

十,释放GPIO
    
比如:gpio_free(S3C64XX_GPF(15));就是把对应GPIO口的控制标志FLAG_REQUESTED清掉,成NULL,之后可以再被其他调用。

阅读(3337) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~