单片机 > 单片机程序设计 > 详情

Mini2440通过GPIO驱动LED

发布时间:2024-11-07 发布时间:
|

以下是驱动的源码。
#includelinux/config.h//配置头文件
#includelinux/kernel.h//用于调用kmalloc和kfree
#includelinux/sched.h//调度,进程睡眠,唤醒,中断申请,中断释放
#includelinux/timer.h/
#includelinux/init.h//用户定义模块初始函数名需引用的头文件
#includelinux/module.h
#includeasm/hardware.h
#includeasm/arch/S3C2440.h

#defineDEVICE_NAMEquot;ledsquot;

staticunsignedlongled_table[]={
S3C2410_GPB(5),
S3C2410_GPB(6),
S3C2410_GPB(7),
S3C2410_GPB(8),
};

staticunsignedintled_cfg_table[]={
S3C2410_GPIO_OUTPUT,
S3C2410_GPIO_OUTPUT,
S3C2410_GPIO_OUTPUT,
S3C2410_GPIO_OUTPUT,
};

staticintsbc2440_leds_ioctl(
structinode*inode,
structfile*file,
unsignedintcmd,
unsignedlongarg)
{
switch(cmd){
case0:
case1:
if(arg4){
return-EINVAL;
}
s3c2410_gpio_setpin(led_table[arg],!cmd);
return0;

default:
return-EINVAL;
}
}
staticstructfile_operationsdev_fops={
.owner=THIS_MODULE,
.ioctl=sbc2440_leds_ioctl,
};

staticstructmiscdevicemisc={//杂项设备结构体
.minor=MISC_DYNAMIC_MINOR,//次设备号
.name=DEVICE_NAME,
.fops=dev_fops,
};

staticint__initdev_init(void)
{
intret;
inti;

for(i=0;i4;i  ){
s3c2410_gpio_cfgpin(led_table[i],led_cfg_table[i]);
s3c2410_gpio_setpin(led_table[i],0);
}

ret=misc_register(misc);
printk(DEVICE_NAMEquot;\tinitialized\nquot;);
returnret;
}
staticvoid__exitdev_exit(void)
{
misc_deregister(misc);
}

module_init(dev_init);
module_exit(dev_exit);
MODULE_LICENSE(quot;GPLquot;);
MODULE_AUTHOR(quot;FriendlyARMInc.quot;);

1.misc设备
misc设备设备被译成杂项设备或者是混杂设备。Linux设备驱动主要分为字符设备,块设备和网络设备。我们把那些不属于上述三大形式的归为一类叫做杂项设备。杂项设备共用相同的主设备号(MISC_MAJOR,也就是10),但次设备号不同,对于杂项设备,linux内核专门提供了这样的一个结构体miscdevice,其有很强的包容性。结构如下:
以下文件定义在/linux2.6.32.2/include/linux/Miscdivice.h
structmiscdevice{
intminor;
constchar*name;
conststructfile_operations*fops;
structlist_headlist;
structdevice*parent;
structdevice*this_device;
constchar*nodename;
mode_tmode;
};
同时提供的miscdevice注册和注销函数如下所示。
intmisc_register(structmiscdevice*misc);
intmisc_deregister(structmiscdevice*misc);
其实,杂项设备的本质仍然是字符设备,只是将这种设备驱动增加了一层封装而已,杂项设备中的主体还是file_operations结构的实现。

2.LED对应的GPIO端口列表
staticunsignedlongled_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
/*S3C2410GPIOnumberdefinitions.*/
#defineS3C2410_GPA(_nr)(S3C2410_GPIO_A_START (_nr))#defineS3C2410_GPB(_nr)(S3C2410_GPIO_B_START (_nr))#defineS3C2410_GPC(_nr)(S3C2410_GPIO_C_START (_nr))#defineS3C2410_GPD(_nr)(S3C2410_GPIO_D_START (_nr))#defineS3C2410_GPE(_nr)(S3C2410_GPIO_E_START (_nr))#defineS3C2410_GPF(_nr)(S3C2410_GPIO_F_START (_nr))#defineS3C2410_GPG(_nr)(S3C2410_GPIO_G_START (_nr))#defineS3C2410_GPH(_nr)(S3C2410_GPIO_H_START (_nr))
enums3c_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),};
#defineS3C2410_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对应端口将要输出的状态列表分析
staticunsignedintled_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是没有输入功能的。

#defineS3C2410_GPIO_LEAVE(0xFFFFFFFF)
#defineS3C2410_GPIO_INPUT(0xFFFFFFF0)/*notavailableonA*/
#defineS3C2410_GPIO_OUTPUT(0xFFFFFFF1)
#defineS3C2410_GPIO_IRQ(0xFFFFFFF2)/*notavailableforall*/
#defineS3C2410_GPIO_SFN2(0xFFFFFFF2)/*bankA=addr/cs/nand*/
#defineS3C2410_GPIO_SFN3(0xFFFFFFF3)/*notavailableonA*/

4.s3c2410_gpio_cfgpin(led_table[i],led_cfg_table[i])分析
函数源码定义在linux/arch/arm/plat-s3c24xx/gpio.c
函数原型:
voids3c2410_gpio_cfgpin(unsignedintpin,unsignedintfunction)
{
void__iomem*base=S3C24XX_GPIO_BASE(pin);
unsignedlongmask;
unsignedlongcon;
unsignedlongflags;

if(pinS3C2410_GPIO_BANKB){//判断I/O口是不是属于GPA,
mask=1S3C2410_GPIO_OFFSET(pin);
}else{
mask=3S3C2410_GPIO_OFFSET(pin)*2;
}

switch(function){//根据要设置的管脚的功能进行相应的操作
caseS3C2410_GPIO_LEAVE:
mask=0;
function=0;
break;

caseS3C2410_GPIO_INPUT:
caseS3C2410_GPIO_OUTPUT:
caseS3C2410_GPIO_SFN2:
caseS3C2410_GPIO_SFN3:
if(pinS3C2410_GPIO_BANKB){
function-=1;
function=1;
function=S3C2410_GPIO_OFFSET(pin);
}else{
function=3;
function=S3C2410_GPIO_OFFSET(pin)*2;
}
}

/*modifythespecifiedregisterwwithIRQsoff*/

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-s3c2410\include\mach\Regs-gpio.h
#defineS3C24XX_GPIO_BASE(x)S3C2410_GPIO_BASE(x)
#defineS3C2410_GPIO_BASE(pin)((((pin)~31)1) S3C24XX_VA_GPIO)
以下内容定义在/linux-2.6.32.2/arch/arm/plat-s3c24xx/include/plat/map.h
#defineS3C24XX_VA_GPIO((S3C24XX_PA_GPIO-S3C24XX_PA_UART) S3C24XX_VA_UART)
以下内容定义在/linux-2.6.32.2/arch/arm/mach-s3c2410/include/mach/map.h
#defineS3C24XX_PA_GPIOS3C2410_PA_GPIO
#defineS3C24XX_PA_UARTS3C2410_PA_UART
以下内容定义在/linux-2.6.32.2/arch/arm/plat-s3c24xx/include/plat/map.h
#defineS3C2410_PA_GPIO(0x56000000)
#defineS3C2410_PA_UART(0x50000000)
以下内容定义在linux-2.6.32.2/arch/arm/plat-s3c24xx/include/plat/map.h
#defineS3C24XX_VA_UARTS3C_VA_UART
以下内容定义在linux-2.6.32.2/arch/arm/plat-s3c/include/plat/map.h
#defineS3C_VA_UARTS3C_ADDR(0x01000000)/*UART*/
以下内容定义在linux-2.6.32.2/arch/arm/plat-s3c/include/plat/Map-base.h
#ifndef__ASSEMBLY__
#defineS3C_ADDR(x)((void__iomem__force*)S3C_ADDR_BASE (x))
#else
#defineS3C_ADDR(x)(S3C_ADDR_BASE (x))
#endif
#defineS3C_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)
#defineS3C_VA_UARTS3C_ADDR(0x01000000)/*UART*/
这句话看出在虚拟地址的基地址上偏移0x01000000
对下面两个进行解释:
#defineS3C_ADDR_BASE(0xF4000000)所有寄存器虚拟地址首地址
#S3C24XX_VA_GPIOGPIO的虚拟地址首地址
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端口的配置。
还一个问题,((((pin)~31)1)到底是神马意思?这个主要靠理解,刚才上面说了每组端口定义为32个,((pin)~31)相当于就是把低五位全部清零,而第五位所能代表的范围正好是32,有点以大小32进行对其的意思。如果将得到的数值右移5位的话,得到的数值(设为ppvalue)能正好代表是哪组I/O口。这里为什么右移1位呢,我们看下
几个GPXCON寄存器的物理地址。
GPACON0X56000000
GPBCON0X56000010
GPCCON0X56000020
GPDCON0X56000030
其他的以此类推,可以看出这个I/O口控制寄存器的规律,如果将ppvalue左移四位,加上GPIO虚拟基地址,就能得到GPXCON控制寄存器的虚拟地址了。顺便说下,这里的虚实地址的映射只是相差了一个偏移量。

分析:if(pinS3C2410_GPIO_BANKB)
S3C2410_GPIO_BANKB的定义如下
#defineS3C2410_GPIO_BANKA(32*0)
#defineS3C2410_GPIO_BANKB(32*1)
#defineS3C2410_GPIO_BANKC(32*2)
#defineS3C2410_GPIO_BANKD(32*3)
#defineS3C2410_GPIO_BANKE(32*4)
#defineS3C2410_GPIO_BANKF(32*5)
#defineS3C2410_GPIO_BANKG(32*6)
#defineS3C2410_GPIO_BANKH(32*7)
用于判断此I/O口是否为GPA端口,这是为了区分开GPA与其他各组端口,因为GPA控制寄存器的操作和其他的有点区别,另外要注意,它是没有输入功能的。看datasheet能够更好的了解。

分析:S3C2410_GPIO_OFFSET(pin)
#defineS3C2410_GPIO_OFFSET(pin)((pin)31)//用此宏能得出偏移量
if(pinS3C2410_GPIO_BANKB){//判断I/O口是不是属于GPA,mask=1S3C2410_GPIO_OFFSET(pin);//设置屏蔽码
}else{
mask=3S3C2410_GPIO_OFFSET(pin)*2;//设置屏蔽码
}

分析:local_irq_save(flags);
这个与下面出现的local_irq_restore(flags);成对使用,用于关闭、打开中断,同时将中断的标志存储在flags中。
分析:__raw_readl(base 0x00);__raw_writel(con,base 0x00);
con=__raw_readl(base 0x00);//读取控制寄存器数据
con=~mask;//屏蔽掉相应的位
con|=function;//设置要设置的位

__raw_writel(con,base 0x00);//把改变后的数据写回控制寄存器

上面的是两个函数宏,定义如下
#define__raw_writeb(v,a)(__chk_io_ptr(a),*(volatileunsignedchar__force*)(a)=(v))#define__raw_writew(v,a)(__chk_io_ptr(a),*(volatileunsignedshort__force*)(a)=(v))#define__raw_writel(v,a)(__chk_io_ptr(a),*(volatileunsignedint__force*)(a)=(v))PS(ZXX):先检查指针a是否合法,然后将数值v写入a所指向的空间。
三种类型分别对应char,short,int#define__raw_readb(a)(__chk_io_ptr(a),*(volatileunsignedchar__force*)(a))#define__raw_readw(a)(__chk_io_ptr(a),*(volatileunsignedshort__force*)(a))#define__raw_readl(a)(__chk_io_ptr(a),*(volatileunsignedint__force*)(a))PS(ZXX):先检查指针a是否合法,然后读取a所指向的空间的数值。三种类型分别对应char,short,int

5.分析s3c2410_gpio_setpin(led_table[i],0)
voids3c2410_gpio_setpin(unsignedintpin,unsignedintto)
{
void__iomem*base=S3C24XX_GPIO_BASE(pin);
unsignedlongoffs=S3C2410_GPIO_OFFSET(pin);
unsignedlongflags;
unsignedlongdat;

local_irq_save(flags);

dat=__raw_readl(base 0x04);
dat=~(1offs);
dat|=tooffs;
__raw_writel(dat,base 0x04);
local_irq_restore(flags);
}
有了上述的对s3c2410_gpio_cfgpin(led_table[i],led_cfg_table[i])的分析,上面的代码大同小异罢了,只是说一下__raw_readl(base 0x04);这个,这是对数据寄存器进行操作,看datasheet就知道,每组的GPXDAT的地址值都比GPXCON的地址值大4。




『本文转载自网络,版权归原作者所有,如有侵权请联系删除』

热门文章 更多
STM32学习笔记4:外部中断