mini2440 led驱动程序经典分析

发布者:悠闲之旅最新更新时间:2024-06-19 来源: elecfans关键字:mini2440  led  驱动程序 手机看文章 扫描二维码
随时随地手机看文章

Linux内核为2.6.32.2

源码分析工具source insight

前言:在裸机中操作几个gpio口很简单,对控制寄存器数据寄存器进行配置即可,但要在linux系统中实现同样的功能还是得费上一番周折的。

以下是驱动的源码。

#include <……>
#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'tinitializedn');

 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.');              //作者

 以上列出了加入简单注释的MINI2440led驱动程序的源码,头文件已被省去。对于上述的驱动,我们主要分析这个驱动的整体框架以及里面涉及到的一些重要的函数和宏定义内容。


1、misc设备

    misc设备设备被译成杂项设备或者是混杂设备,关于杂项设备在我的一篇文章里面已经专门介绍,这里根据本驱动在详细说明下。Linux设备驱动主要分为字符设备,块设备和网络设备。但是上面的驱动程序并不属于上面三大类中的常见形式,我们把上述驱动程序中的称为杂项设备。什么是杂项设备呢?linux包含了很多的设备类型,但不管怎么分类,总不能完全概括所有设备,我们把这些不属于上述三大形式的归为一类叫做杂项设备。杂项设备共用相同的主设备号(MISC_MAJOR,也就是10),但次设备号不同,所有的杂项设备形成一个链表,对设备访问的时候,内核根据此设备号找到其对应的设备,然后调用其相应的file_operations结构体中注册的各个函数。对于杂项设备,linux内核专门提供了这样的一个结构体miscdevice,其有很强的包容性。结构如下:

以下文件定义在/linux2.6.32.2/include/linux/Miscdivice.h

struct miscdevice  {

  int minor;

  const char *name;

  const struct file_operations *fops;

  struct list_head list;

  struct device *parent;

  struct device *this_device;

  const char *nodename;

  mode_t mode;

};

同时提供的miscdevice注册和注销函数如下所示。

int misc_register(struct miscdevice * misc);

int misc_deregister(struct miscdevice *misc);

其实,杂项设备的本质仍然是字符设备,只是将这种设备驱动增加了一层封装而已,杂项设备中的主体还是file_operations结构的实现,所以,其并不神秘。但要注意是是杂项设备在嵌入式系统中常被用到。其使用的基本形式如本驱动程序所示,不再敖述。

2.LED对应的GPIO端口列表

static unsigned long led_table [] = {
 S3C2410_GPB(5),
 S3C2410_GPB(6),
 S3C2410_GPB(7),
 S3C2410_GPB(8),
};

led设备驱动程序中主要是对上述的几个端口进行s3c2410_gpio_cfgpin(led_table[i], led_cfg_table[i]);(配置管脚功能)

s3c2410_gpio_setpin(led_table[i], 0);(设置管脚电平状态)

操作。先来弄清楚这几个端口的定义。

以下文件定义在/arch/arm/mach-s3c2410/include/mach/gpio-nrs.h 

/* S3C2410 GPIO number definitions. */

#define S3C2410_GPA(_nr) (S3C2410_GPIO_A_START + (_nr))
#define S3C2410_GPB(_nr) (S3C2410_GPIO_B_START + (_nr))
#define S3C2410_GPC(_nr) (S3C2410_GPIO_C_START + (_nr))
#define S3C2410_GPD(_nr) (S3C2410_GPIO_D_START + (_nr))
#define S3C2410_GPE(_nr) (S3C2410_GPIO_E_START + (_nr))
#define S3C2410_GPF(_nr) (S3C2410_GPIO_F_START + (_nr))
#define S3C2410_GPG(_nr) (S3C2410_GPIO_G_START + (_nr))
#define S3C2410_GPH(_nr) (S3C2410_GPIO_H_START + (_nr))

enum s3c_gpio_number {
 S3C2410_GPIO_A_START = 0,
 S3C2410_GPIO_B_START = S3C2410_GPIO_NEXT(S3C2410_GPIO_A),
 S3C2410_GPIO_C_START = S3C2410_GPIO_NEXT(S3C2410_GPIO_B),
 S3C2410_GPIO_D_START = S3C2410_GPIO_NEXT(S3C2410_GPIO_C),
 S3C2410_GPIO_E_START = S3C2410_GPIO_NEXT(S3C2410_GPIO_D),
 S3C2410_GPIO_F_START = S3C2410_GPIO_NEXT(S3C2410_GPIO_E),
 S3C2410_GPIO_G_START = S3C2410_GPIO_NEXT(S3C2410_GPIO_F),
 S3C2410_GPIO_H_START = S3C2410_GPIO_NEXT(S3C2410_GPIO_G),
};


#define S3C2410_GPIO_NEXT(__gpio)
 ((__gpio##_START) + (__gpio##_NR) + CONFIG_S3C_GPIO_SPACE + 0)

CONFIG_S3C_GPIO_SPAC是内核配置选项,在.config中可以找到,我的配置为:

CONFIG_S3C_GPIO_SPACE = 0

因此,以S3C2410_GPB(5)为例,其宏展开为:

S3C2410_GPIO_NEXT(S3C2410_GPIO_A) +5 =>

(S3C2410_GPIO_A_START + S3C2410_GPIO_A_NR +  CONFIG_S3C_GPIO_SPACE + 0) + 5 =>

很显然,S3C2410_GPB(5)就是从GPA的首地址+GPA个数+GPB的offset就是当前GPB的IO偏移量,即

0+32+5=37, 同理

S3C2410_GPB(0) 相当于 32

S3C2410_GPB(5) 相当于 37 

S3C2410_GPB(6) 相当于 38   

S3C2410_GPB(7) 相当于 39   

S3C2410_GPB(8) 相当于 40

到这里我们应该明白,这个宏的作用就是对端口进行编号,对于GPA其端口编号的范围是0~31,GPB端口编号范围是32~63,以此类推,当然这里所有的编号不一定都被使用。因为每组的端口的个数不一样,所以给每组都定义32个,以保证每组都够用。在得到端口号后,除以32得到的结果就可以确定这个端口是哪组的了。比如得到端口编号38,除以32后得到1,就知道是属于GPB里面的I/O口了。这在后面进一步分析中会看到。


3. LED 对应端口将要输出的状态列表分析

static unsigned int led_cfg_table [] = {

  S3C2410_GPIO_OUTPUT,

  S3C2410_GPIO_OUTPUT,

  S3C2410_GPIO_OUTPUT,

  S3C2410_GPIO_OUTPUT,

};

S3C2410_GPIO_OUTPUT定义在mach/regs-gpio.h

这里主要看最后的两位,表示了端口的状态。

00代表输入,01代表输出,10代表功能2,,1代表功能3.注意提示,GPA是没有输入功能的。

 

#define S3C2410_GPIO_LEAVE  (0xFFFFFFFF)

#define S3C2410_GPIO_INPUT  (0xFFFFFFF0) /* not available on A */

#define S3C2410_GPIO_OUTPUT  (0xFFFFFFF1)

#define S3C2410_GPIO_IRQ    (0xFFFFFFF2)  /* not available for all */

#define S3C2410_GPIO_SFN2    (0xFFFFFFF2) /* bank A => addr/cs/nand */

#define S3C2410_GPIO_SFN3    (0xFFFFFFF3) /* not available on A */

 

4、s3c2410_gpio_cfgpin(led_table[i], led_cfg_table[i])分析

函数源码定义在linux/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) {  //判断I/O口是不是属于GPA,

      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);

}

先看一下主体框架,主体通过switch(function)找到要设置的相应的功能进行对应的操作。这个估计很容易看懂。下面将里面几个不好搞懂的地方具体说一下。

对于void __iomem *base = S3C24XX_GPIO_BASE(pin);先来看它的实现

以下内容定义在/linux-2.6.32.2/arch/arm/mach-s3c2410includemachRegs-gpio.h

#define S3C24XX_GPIO_BASE(x)  S3C2410_GPIO_BASE(x)

#define S3C2410_GPIO_BASE(pin)  ((((pin) & ~31) >> 1) + S3C24XX_VA_GPIO)

以下内容定义在/linux-2.6.32.2/arch/arm/plat-s3c24xx/include/plat/map.h

#define S3C24XX_VA_GPIO    ((S3C24XX_PA_GPIO - S3C24XX_PA_UART) + S3C24XX_VA_UART)

以下内容定义在/linux-2.6.32.2/arch/arm/mach-s3c2410/include/mach/map.h

#define S3C24XX_PA_GPIO    S3C2410_PA_GPIO

#define S3C24XX_PA_UART    S3C2410_PA_UART

以下内容定义在/linux-2.6.32.2/arch/arm/plat-s3c24xx/include/plat/map.h

#define S3C2410_PA_GPIO    (0x56000000)

#define S3C2410_PA_UART    (0x50000000)

以下内容定义在linux-2.6.32.2/arch/arm/plat-s3c24xx/include/plat/map.h

#define S3C24XX_VA_UART    S3C_VA_UART

以下内容定义在linux-2.6.32.2/arch/arm/plat-s3c/include/plat/map.h

#define S3C_VA_UART S3C_ADDR(0x01000000)  /* UART */

以下内容定义在linux-2.6.32.2/arch/arm/plat-s3c/include/plat/Map-base.h

#ifndef __ASSEMBLY__

#define S3C_ADDR(x) ((void __iomem __force *)S3C_ADDR_BASE + (x))

#else

#define S3C_ADDR(x) (S3C_ADDR_BASE + (x))

#endif

#define S3C_ADDR_BASE (0xF4000000)

 

到这找出了定义S3C24XX_GPIO_BASE(x)全部的宏,从此处可以发现,linux中文件的定义分布是比较散乱的,这也是让很多初学者头疼的地方。接着分析

S3C24XX_VA_GPIO=((S3C24XX_PA_GPIO - S3C24XX_PA_UART) + S3C24XX_VA_UART)

              =((0x56000000 - 0x50000000) + (0xF4000000 + 0x01000000))

              = (0x06000000 + 0xF5000000)

              = (0xFB000000)

#define S3C_VA_UART S3C_ADDR(0x01000000)  /* UART */

这句话看出在虚拟地址的基地址上偏移0x01000000

对下面两个进行解释:

#define S3C_ADDR_BASE (0xF4000000)  所有寄存器虚拟地址首地址

#S3C24XX_VA_GPIO                    GPIO的虚拟地址首地址

S3C2410_GPB(5)通过上面的计算其数值为37,

S3C24XX_GPIO_BASE(S3C2410_GPB(5))= S3C24XX_GPIO_BASE(37)

=((((37) & ~31) >> 1) + S3C24XX_VA_GPIO)

=((((37) & ~31) >> 1) + (0xFB000000))= 0xFB000010

所以最终*base =0xFB000010,这个就是GPBCON的虚拟地址,查看其手册我们知道GPBCON物理地址为0X56000010, GPACON的虚拟地址0xFB000000,查看其手册我们知道GPACON物理地址为0X56000000,下面的程序通过访问这个虚拟地址,来访问控制寄存器,实现对I/O端口的配置,此时有人就会问,我访问这个虚拟地址,为什么就能实现了对该物理地址的访问?这是MMU的虚拟地址和物理地址的映射问题,关于这个的问题比较复杂,我专门找了一篇文章来介绍这个虚实映射关系,看本站的《2410下寄存器地址虚实映射的实现》http://www.linuxidc.com/Linux/2013-01/77977.htm 的这一文章,因为内容较多,我不在这里介绍。


还一个问题

((((pin) & ~31) >> 1)到底是神马意思?这个主要靠理解,刚才上面说了每组端口定义为32个,((pin) & ~31)相当于就是把低五位全部清零,而第五位所能代表的范围正好是32,有点以大小32进行对其的意思。如果将得到的数值右移5位的话,得到的数值(设为ppvalue)能正好代表是哪组I/O口。这里为什么右移1位呢,我们看下

[1] [2]
关键字:mini2440  led  驱动程序 引用地址:mini2440 led驱动程序经典分析

上一篇:mini2440 Norflash驱动移植过程
下一篇:mini2440重新烧写supervivi方法

推荐阅读最新更新时间:2024-11-12 10:23

X25045驱动程序
/********************************************************************************************/ /* This is a demo for X25045. /* /* /* /* By Dragon.W /* Jun.2005 /*********************************************************************************************/ //#ifndef MASTER_FILE //#i nclude REG52.H //#i nclude STDIO
[单片机]
【技术解析】如何设置iPhone来点LED杯灯闪烁提醒
众所周知,iPhone手机没有前置呼吸灯,当我们有未接来电或者短信时iPhone只会震动并且点亮屏幕一次来提醒,如果我们没有注意到,那iPhone是不会二次提醒的。但当下大多数的Android手机都具备前置呼吸灯,当有未接来电或者未读短信时,前置呼吸灯会显示不同颜色来提醒用户。   不过,iPhone用户也不用羡慕Android用户,今天小编就教大家一招如何将iPhone的后置 LED 等设置成来电闪烁提醒,虽然功能略有不足,但却能够让我们的手机更炫。   首先找到手机中的“设置”,如下图所示。   点击设置,然后找到"通用",如下图红框标注所示。   点击打开“通用”,用手指向下滑动,找到“辅助功能”,如下图红框
[电源管理]
【技术解析】如何设置iPhone来点<font color='red'>LED</font>杯灯闪烁提醒
LED文字指挥棒
来自设计师Chih Wei Lai等人的设计,LED文字指挥棒(Swing Baton)是一款好用的交警指挥设备,相当于是一根镶嵌有LED的棒子,可以预先设定好要显示的文字,然后,只要你左右挥舞它,文字就能依靠视觉残留的原理显示在空中! 是的,就像那些旋转的时候能显示出文字的风扇~
[电源管理]
<font color='red'>LED</font>文字指挥棒
51单片机用三种方法实现流水灯
一、数组流水灯 定义一组数组分别对应点亮LED1~7 然后利用for循环赋值给p2从而实现流水灯 #include reg52.h #define uchar unsigned char #define uint unsigned int uchar code table = {0xfe,0xfd,0xfb,0xf7,0xef,0xdf,0xbf,0x7f}; //数组 // 函数功能:毫秒延时 void delay(uint z) { uint x,y; for(x = 0; x z; x++) for(y = 0; y 113; y++); } void main() { uchar
[单片机]
51单片机用三种方法实现流水灯
车用LED照明技术及现状分析
随着全球经济的发展和人民生活水平的提高,汽车在日常生活中的使用越来越多,因此汽车节能减排及行车安全的要求日益提高。汽车照明系统是保障汽车安全行驶的关键部件,光源又是汽车照明系统的关键。发光二极管(LED)作为第四代车用光源具有寿命长、能耗低、体积小、响应快、单色性好等诸多优点,顺应了未来汽车的安全、节能、紧凑、时尚的发展趋势。相信随着汽车工业的成熟以及LED 芯片、封装、散热等技术突飞猛进的发展,LED 在汽车照明系统中的应用会越来越广,规模会越来越大。    1 车用LED 照明的可行性和先进性   在汽车上使用照明光源大约开始于20 世纪初。最先使用的是煤油灯和乙炔灯,1910 年开始使用电光源,先后经历了白炽灯、卤钨灯及高
[电源管理]
车用<font color='red'>LED</font>照明技术及现状分析
一种恒流LED驱动系统的设计方案
  所有发光二极管无论其灯光颜色、尺寸大小或功率有甚不同,只要驱动的电流恒定不变,它们都能充分发挥其性能。发光二极管生产商都会列明产品的规格,例如,数据表上会列出产品在指定正向电流(IF)而非正向电压(VF)驱动下的流明、光束波形及颜色。发光二极管的亮度随电流的大小而不同,且制造出来的发光二极管,其电压与电流曲线稍有差异,因而 LED照明 的亮度常随电源电压的变动而无法稳定。为维持亮度稳定一致,需要发光二极管恒流驱动器来实现。恒流驱动器可以使得发光二极管工作在固定电流模式,因而亮度稳定性高。恒流驱动器也让发光二极管长期工作在一定电流下,使其维持较长寿命。发光二极管 照明 优点是节能、安全,但由于恒定电流工作考虑,能耗亦相对增加,因
[电源管理]
一种恒流<font color='red'>LED</font>驱动系统的设计方案
为什么安防巨头纷纷进入超小间距LED显示市场?
    6月,国际安防巨头泰科,携手中国区总代发布旗下品牌美国动力全系列显示与控制产品,包括DLP/LCD及小间距LED,并表示看好小间距LED未来空间;视讯与安防领导企业,科达公司在2016全国巡展期间,对外发布晶艳超小间距LED显示屏,包括两个系列产品——无限拼接的超小间距LED拼接屏&LED高清大尺寸显示器。   小间距LED显示屏一般是指点间距(即像素点间距=两个LED灯珠中心间距,以P表示)在2.5mm以下的室内LED显示屏,主要包括P2.5、P2.0、P1.9、P1.6、P1.2 等型号,目前最小间距已可达0.7mm,P0.9 已经开始量产。由于小间距LED 具有无拼缝(DLP技术目前可做到0.2mm拼缝)、显示效果好、
[安防电子]
利用高功率Buck LED控制器实现优异的汽车外部照明
得益于优异的照明特性和效率,高功率 LED 在汽车外部照明设计中越来越流行。支持 LED 的电子器件必须快速、高效、高精度,以控制照明强度、方向和聚焦。这些器件必须支持较宽的输入电压范围,且能够在汽车无线电的 AM 频段范围之外工作,以避免电磁干扰( EMI )。电子器件还必须支持 LED 矩阵中要求的复杂照明模式,以支持自适应前灯照明系统。本文回顾典型的 LED 电源管理方案,并介绍支持快速、高效、高精度 LED 照明 方案的创新 buck 控制器 IC。 LED 在汽车外部照明中的应用 由于相对于传统技术具有显著优势,LED 正在汽车行业掀起一场风暴。LED 前灯中的白光具有优异的清晰度,从而减少驾驶员反应时间。
[汽车电子]
利用高功率Buck <font color='red'>LED</font>控制器实现优异的汽车外部照明
小广播
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

电子工程世界版权所有 京ICP证060456号 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved