先是发个牢骚,书上有些地方看不明白的到网上去搜,可是发现网上的东西很多都是抄书的,甚至连章节号都还带着呢。然后网上的文章又都互相抄,一个看不懂就个个看不懂。论坛里有人问问题,真正问道我关心的问题和困难时,却鲜有人正面回答。真是让人窝火,最终遇到的重重困难还得靠自己去解决。
先上我的驱动代码,它注册了一个平台设备和一个混杂设备,实现了对 {读取时间/设定时间/蜂鸣器每秒一响/关闭蜂鸣器} 四个命令的反应。
【rtc_driver.c】
#include <linux/module.h>
#include <linux/init.h>
MODULE_LICENSE("GPL"); //使用GPL版权协议
MODULE_AUTHOR("wolf"); //作者
/*********驱动的装载卸载函数*********/
static int __init load_driver(void);
static void __exit unload_driver(void);
module_init(load_driver);
module_exit(unload_driver);
/*********驱动的装载卸载函数*********/
/*********rtc的寄存器地址*********/
#define pRTCBASE 0x57000040 //rtc寄存器物理基地址
#define oRTCCON 0x00 //rtc控制寄存器
#define oTICNT 0x04 //rtc滴答中断控制寄存器
#define oRTCALM 0x10 //rtc报警使能寄存器
/*rtc报警时间寄存器*/
#define oALMSEC 0x14
#define oALMMIN 0x18
#define oALMHOUR 0x1C
#define oALMDATE 0x20
#define oALMMON 0x24
#define oALMYEAR 0x28
/*rtc时间设定寄存器*/
#define oBCDSEC 0x30
#define oBCDMIN 0x34
#define oBCDHOUR 0x38
#define oBCDDATE 0x3C
#define oBCDMON 0x44
#define oBCDYEAR 0x48
static void __iomem * vRTCBASE; //rtc寄存器虚拟基地址
/*********rtc的寄存器地址*********/
/*********蜂鸣器寄存器地址*********/
#define pGPBBASE 0x56000010 //物理基地址
#define oGPBCON 0x00
#define oGPBDAT 0x04
#define oGPBUP 0x08
static void __iomem * vGPBBASE; //虚拟基地址
/*********蜂鸣器寄存器地址*********/
/*********作为平台设备的rtc*********/
#include <linux/platform_device.h>
#include <asm/irq.h>
static void release_inplat_device(struct device * inplat_device){}
//析构平台设备内嵌的device结构体
static struct resource rtc_resource[] = //rtc占用资源的数组
{
[0] = //rtc寄存器
{
.start = pRTCBASE+oRTCCON,
.end = pRTCBASE+oBCDYEAR,
.flags = IORESOURCE_MEM,
},
[1] = //rtc滴答中断
{
.start = IRQ_TICK,
.end = IRQ_TICK,
.flags = IORESOURCE_IRQ,
},
[2] = //rtc报警中断
{
.start = IRQ_RTC,
.end = IRQ_RTC,
.flags = IORESOURCE_IRQ,
},
[3] = //蜂鸣器寄存器
{
.start = pGPBBASE+oGPBCON,
.end = pGPBBASE+oGPBUP,
.flags = IORESOURCE_MEM,
},
};
static struct platform_device platform_rtc =
{
.name = "platform_rtc", //平台设备和平台驱动的name要一致
.id = -1, //一般都设为-1
.dev = //内嵌的device结构体,必须写其release函数
{
.release = release_inplat_device,
},
.resource = rtc_resource, //rtc占用资源的数组
.num_resources = ARRAY_SIZE(rtc_resource), //资源数组的长度
};
/*********作为平台设备的rtc*********/
/*********平台rtc的驱动*********/
static int rtc_probe(struct platform_device *);
static int rtc_remove(struct platform_device *);
static struct platform_driver platform_rtc_driver =
{
.probe = rtc_probe,
.remove = rtc_remove,
.driver =
{
.name = "platform_rtc", //平台设备和平台驱动的name要一致
.owner = THIS_MODULE,
},
};
/*********平台rtc的驱动*********/
/*********作为混杂设备的rtc*********/
#include <linux/fs.h>
#include <linux/miscdevice.h>
int rtc_open(struct inode *, struct file *);
int rtc_release(struct inode *, struct file *);
static int rtc_ioctl(struct inode *, struct file *, unsigned int, unsigned long);
static struct file_operations rtc_operation =
{
.owner = THIS_MODULE,
.open = rtc_open,
.release = rtc_release,
.ioctl = rtc_ioctl,
};
static struct miscdevice misc_rtc =
{
.minor = 255, //混杂设备主设备号就是10,255表示动态分配次设备号
.name = "misc_rtc",
.fops = &rtc_operation,
};
/*********作为混杂设备的rtc*********/
/*——————————————————以下是函数的具体实现——————————————————*/
/*********驱动的装载函数*********/
static int __init load_driver(void)
{
int ret;
/*注册平台rtc*/
ret = platform_device_register(&platform_rtc);
if(ret)
{
printk(KERN_ALERT "平台rtc注册失败");
return ret;
}
/*注册平台rtc驱动*/
ret = platform_driver_register(&platform_rtc_driver);
if(ret)
{
printk(KERN_ALERT "平台rtc驱动注册失败");
return ret;
}
/*注册混杂rtc*/
ret = misc_register(&misc_rtc);
if(ret)
{
printk(KERN_ALERT "混杂rtc注册失败");
return ret;
}
printk(KERN_ALERT "rtc注册成功");
return 0;
}
/*********驱动的装载函数*********/
/*********驱动的卸载函数*********/
static void __exit unload_driver(void)
{
platform_driver_unregister(&platform_rtc_driver);
platform_device_unregister(&platform_rtc);
misc_deregister(&misc_rtc);
printk(KERN_ALERT "rtc卸载完成");
}
/*********驱动的卸载函数*********/
/*********rtc的probe函数*********/
#include <linux/io.h>
#include <linux/interrupt.h>
static irqreturn_t rtc_tick_handle(int, void *); //rtc滴答中断的处理函数
static int rtc_probe(struct platform_device * rtc)
{
int ret;
struct resource *rtc_mem, *rtc_tick_irq, *beep_mem;
/*rtc寄存器地址映射*/
rtc_mem = platform_get_resource(rtc, IORESOURCE_MEM, 0);
if(rtc_mem == NULL)
{
printk(KERN_ALERT "platform_get_resource失败");
return -ENOENT;
}
if(request_mem_region(rtc_mem->start, rtc_mem->end-rtc_mem->start+1, rtc->name) == NULL)
{
printk(KERN_ALERT "request_mem_region失败");
return -ENOENT;
}
vRTCBASE = ioremap(rtc_mem->start, rtc_mem->end-rtc_mem->start+1);
if(vRTCBASE == NULL)
{
printk(KERN_ALERT "寄存器地址映射失败");
return -EINVAL;
}
/*申请滴答中断线*/
rtc_tick_irq = platform_get_resource(rtc, IORESOURCE_IRQ, 0);
if(rtc_tick_irq == NULL)
{
printk(KERN_ALERT "platform_get_resource失败");
return -ENOENT;
}
ret = request_irq(rtc_tick_irq->start, rtc_tick_handle, 0, rtc->name, rtc);
if(ret != 0)
{
printk(KERN_ALERT "申请滴答中断线失败");
return ret;
}
/*初始化rtc,一定要先使能RTCCON的第0位才能读写寄存器*/
writeb(0x01, vRTCBASE+oRTCCON); //合并BCD码,XTAL分配时钟,使能读写
writeb(0x00, vRTCBASE+oTICNT); //禁止滴答中断
writeb(0x00, vRTCBASE+oRTCALM); //禁止报警
writeb(0x00, vRTCBASE+oALMSEC); //报警时间寄存器清零
writeb(0x00, vRTCBASE+oALMMIN);
writeb(0x00, vRTCBASE+oALMHOUR);
writeb(0x00, vRTCBASE+oALMDATE);
writeb(0x00, vRTCBASE+oALMMON);
writeb(0x00, vRTCBASE+oALMYEAR);
writeb(0x00, vRTCBASE+oBCDSEC); //当前时间寄存器清零
writeb(0x00, vRTCBASE+oBCDMIN);
writeb(0x00, vRTCBASE+oBCDHOUR);
writeb(0x00, vRTCBASE+oBCDDATE);
writeb(0x00, vRTCBASE+oBCDMON);
writeb(0x00, vRTCBASE+oBCDYEAR);
printk(KERN_ALERT "rtc初始化完成");
/*蜂鸣器寄存器地址映射*/
beep_mem = platform_get_resource(rtc, IORESOURCE_MEM, 1);
if(beep_mem == NULL)
{
printk(KERN_ALERT "platform_get_resource失败");
return -ENOENT;
}
if(request_mem_region(beep_mem->start, beep_mem->end-beep_mem->start+1, rtc->name) == NULL)
{
printk(KERN_ALERT "request_mem_region失败");
return -ENOENT;
}
vGPBBASE = ioremap(beep_mem->start, beep_mem->end-beep_mem->start+1);
if(vGPBBASE == NULL)
{
printk(KERN_ALERT "寄存器地址映射失败");
return -EINVAL;
}
/*初始化蜂鸣器*/
writel(readl(vGPBBASE+oGPBCON) & ~2, vGPBBASE+oGPBCON);
writel(readl(vGPBBASE+oGPBCON) | 1, vGPBBASE+oGPBCON); //GPB0输出
writel(readl(vGPBBASE+oGPBDAT) & ~1, vGPBBASE+oGPBDAT); //关闭蜂鸣器
writel(readl(vGPBBASE+oGPBUP) & ~1, vGPBBASE+oGPBUP); //使能上拉电阻
return 0;
}
/*********rtc的probe函数*********/
/*********rtc的remove函数*********/
static int rtc_remove(struct platform_device * rtc)
{
struct resource *beep_mem, *rtc_tick_irq, *rtc_mem;
/*释放蜂鸣器io内存*/
iounmap(vGPBBASE);
beep_mem = platform_get_resource(rtc, IORESOURCE_MEM, 1);
if(beep_mem == NULL)
{
printk(KERN_ALERT "platform_get_resource失败");
return -ENOENT;
}
release_mem_region(beep_mem->start, beep_mem->end-beep_mem->start+1);
writeb(0x00, vRTCBASE+oTICNT); //禁止滴答中断
writeb(0x00, vRTCBASE+oRTCCON); //禁止读写rtc寄存器
/*释放滴答中断线*/
rtc_tick_irq = platform_get_resource(rtc, IORESOURCE_IRQ, 0);
if(rtc_tick_irq == NULL)
{
printk(KERN_ALERT "platform_get_resource失败");
return -ENOENT;
}
free_irq(rtc_tick_irq->start, rtc);
/*释放rtc的io内存*/
iounmap(vRTCBASE);
rtc_mem = platform_get_resource(rtc, IORESOURCE_MEM, 0);
if(rtc_mem == NULL)
{
printk(KERN_ALERT "platform_get_resource失败");
return -ENOENT;
}
release_mem_region(rtc_mem->start, rtc_mem->end-rtc_mem->start+1);
printk(KERN_ALERT "资源释放完成");
return 0;
}
/*********rtc的remove函数*********/
/*********rtc的open函数*********/
int rtc_open(struct inode * inode, struct file * file)
{
printk(KERN_ALERT "打开rtc成功");
return 0;
}
/*********rtc的open函数*********/
/*********rtc的release函数*********/
int rtc_release(struct inode * inode, struct file * file)
{
printk(KERN_ALERT "关闭rtc完成");
return 0;
}
/*********rtc的release函数*********/
/*********rtc的ioctl函数*********/
#include <linux/rtc.h>
unsigned bcd2bin(unsigned char val) //一字节合并bcd码转化为无符号数
{ return (val&0x0f) + (val>>4)*10; }
unsigned char bin2bcd(unsigned val) //一字节无符号数转化为合并bcd码
{ return ((val/10) << 4) + val%10; }
static int rtc_ioctl(struct inode * inode, struct file * file, unsigned int command, unsigned long arg)
{
void __user * user_arg = (void __user *)arg;
struct rtc_time time;
switch(command)
{
case RTC_RD_TIME: //读时间时考虑到1s误差,应该最后读秒,且判断秒是否为0
time.tm_min = bcd2bin(readb(vRTCBASE+oBCDMIN));
time.tm_hour = bcd2bin(readb(vRTCBASE+oBCDHOUR));
time.tm_mday = bcd2bin(readb(vRTCBASE+oBCDDATE));
time.tm_mon = bcd2bin(readb(vRTCBASE+oBCDMON));
time.tm_year = bcd2bin(readb(vRTCBASE+oBCDYEAR));
time.tm_sec = bcd2bin(readb(vRTCBASE+oBCDSEC));
if(time.tm_sec == 0) //如果秒为0,应该重读一次
{
time.tm_min = bcd2bin(readb(vRTCBASE+oBCDMIN));
time.tm_hour = bcd2bin(readb(vRTCBASE+oBCDHOUR));
time.tm_mday = bcd2bin(readb(vRTCBASE+oBCDDATE));
time.tm_mon = bcd2bin(readb(vRTCBASE+oBCDMON));
time.tm_year = bcd2bin(readb(vRTCBASE+oBCDYEAR));
time.tm_sec = bcd2bin(readb(vRTCBASE+oBCDSEC));
}
if (copy_to_user(user_arg, &time, sizeof(time)))
{
printk(KERN_ALERT "copy_to_user失败");
return -EFAULT;
}
printk(KERN_ALERT "读取时间成功");
return 0;
case RTC_SET_TIME:
if(copy_from_user(&time, user_arg, sizeof(time)))
{
printk(KERN_ALERT "copy_from_user失败");
return -EFAULT;
}
writeb(bin2bcd(time.tm_sec), vRTCBASE+oBCDSEC);
writeb(bin2bcd(time.tm_min), vRTCBASE+oBCDMIN);
writeb(bin2bcd(time.tm_hour), vRTCBASE+oBCDHOUR);
writeb(bin2bcd(time.tm_mday), vRTCBASE+oBCDDATE);
writeb(bin2bcd(time.tm_mon), vRTCBASE+oBCDMON);
writeb(bin2bcd(time.tm_year), vRTCBASE+oBCDYEAR);
printk(KERN_ALERT "设置时间成功");
return 0;
case 999999: //使能滴答中断
writeb(0xFF, vRTCBASE+oTICNT);
printk(KERN_ALERT "滴答中断开启");
return 0;
case 1000000: //禁止滴答中断
writeb(0x00, vRTCBASE+oTICNT);
printk(KERN_ALERT "滴答中断关闭");
return 0;
default:
printk(KERN_ALERT "未定义控制命令");
return -ENOTTY; //未定义命令
}
}
/*********rtc的ioctl函数*********/
/*********rtc滴答中断的处理函数*********/
static irqreturn_t rtc_tick_handle(int irq, void * dev_id)
{
volatile int i, j, k;
//蜂鸣器响一下
writel(readl(vGPBBASE+oGPBDAT) | 1, vGPBBASE+oGPBDAT);
for(i = 0; i < 256; i++)
for(j = 0; j < 256; j++)
for(k = 0; k < 16; k++);
writel(readl(vGPBBASE+oGPBDAT) & ~1, vGPBBASE+oGPBDAT);
printk(KERN_ALERT "滴答中断");
return 0;
}
/*********rtc滴答中断的处理函数*********/
当然这就是一个简单的驱动程序,只是象征性的实现几个功能,让rtc能跑起来就得了。像对临界资源的保护/代码的可重入性等等都在考虑的范围之外,虽然本不该这样。
另外,这里并没有用到suspend/rusume/shutdown等函数,所以这里的平台设备略鸡肋。
【Makefile文件】
KERNELDIR = /home/wolf/wolfkittools/linux-2.6.32.2
#到网上下载目标板内核版本的内核源码并解压编译,这里填源码目录
#驱动程序编译时需要从目标平台的内核获取一些信息
PWD := $(shell pwd) #输出到当前目录
CC = arm-linux-gcc #交叉编译器
#注意设置好PATH环境变量,让系统能找到arm-linux-gcc
obj-m := rtc_driver.o
#输出为模块led_driver.o,编译器自然会去找led_driver.c来编译
default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
rm -rf *.ko *.o *.moc.o *.mod.c *.sysvers *.order *~
【测试方法】
insmod rtc_driver.ko之后,hwclock -f /dev/misc_rtc -r显示rtc时间。(-r是显示时间,
hwclock命令会寻找/dev/misc/rtc设备,调用其ioctl函数,但是我们自己写的混杂rtc是在别处的,hwclock找不到,所以要-f指定路径)
1.用date MMDDHHmmYYYY命令设置系统时间,等一小会儿就可以从mini2440屏幕上看到系统时间改变了。
用hwclock -f /dev/misc_rtc -w将系统时间写给rtc时间,然后再次hwclock -f /dev/misc_rtc -r发现rtc时间和系统时间一致了。
2.再次用date MMDDHHmmYYYY命令设置系统时间为另外一个值,可以看到系统时间改变了。
用hwclock -f /dev/misc_rtc -s将rtc时间赋给系统时间,一会儿发现系统时间又变回来了。
3.不断地hwclock -f /dev/misc_rtc -r可以发现rtc一直在走。
4.运行测试程序,每秒中蜂鸣器响一下,并通过串口打印一句“滴答中断”,重复几次之后自行停止。
5.用rmmod rtc_driver卸载模块。
以下是测试程序:
【rtc_driver_test.c】
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
volatile int i, j, k;
int fd;
fd = open("/dev/misc_rtc", O_RDWR, S_IRUSR | S_IWUSR);
if(ioctl(fd, 999999) != 0)
{
printf("打开滴答中断失败\n");
close(fd);
return -1;
}
for(i = 0; i < 1000; i++)
for(j = 0; j < 1000; j++)
for(k = 0; k < 1000; k++);
if(ioctl(fd, 1000000) != 0)
printf("关闭滴答中断失败\n");
close(fd);
return 0;
}
【注意事项】
1.和之前的led驱动程序一样,由于系统内核自带rtc设备和驱动程序,会给我们自己写的驱动捣乱,所以要先干掉系统的rtc设备和驱动。
linux内核目录下make menuconfig,把Device Drivers中的Real Time Clock叉掉,顺带一提,在Character Devices里可以叉掉LED/按键和蜂鸣器驱动,叉掉watchdog timer可以不要看门狗驱动。但是光这样还不行,这样编译出来的内核烧进板子之后,cat /proc/iomem可以看到rtc寄存器们的物理地址还是被内核自带rtc设备占用了的,request_mem_region时会失败。所以还得把linux内核目录下的arch/arm/mach-s3c2440/mach-mini2440.c中第442行的&s3c_device_rtc注释掉,这样内核就不会自带rtc设备了,当然,也可以把&s3c_device_wdt注释掉,以便以后编看门狗驱动时不被内核自带看门狗捣乱。
2.ioremap返回的是void __iomem *型的地址,一定要用void __iomem *型的变量接收,读写时要用ioread.../iowrite...,否则会出现各种莫名其妙的错误,比如写不进去。
另外iowrite...的参数是值在前地址在后,搞反了的话会出现满屏的oops错误。
3.RTCCON的最低位是读写使能位,只有这一位为1才能读写别的寄存器,另外关机时要关上,以免误读写。
4.使用中断号要include <asm/irq.h>。中断的异常处理函数的原型(prototype)是
static irqreturn_t rtc_tick_handle(int, void *);
5.remove设备时一定要关闭一切中断,否则硬件还在不断地给出中断信号。下次insmod的时候probe中申请完中断线,中断就可以传给cpu,进入中断服务程序了,但是此时可能io内存映射还没有完成,如果中断服务程序中有操纵寄存器的操作,就会访问到无效地址,出现满屏的oops错误。
6.rtc时间也叫硬件时间/墙上时间,和操作系统时间是互相分立的,两者可以不一样。如果把驱动加进内核中编译,启动代码中加一句hwclock -f misc_rtc -s,那么每次开机时,系统时间就会读取硬件时间,就能实现关机后仍在计时的效果了。
【遗留问题】
1.书上的程序在读写BCD寄存器时,对于月有一个加减1的特殊处理,对于年有一个加减100的特殊处理,这是为什么?
2.resource结构的start和end不是指针,为什么end-start后面是总加1而不是加4?