[2014.3.25]自己写的mini2440的RTC驱动及测试程序

先是发个牢骚,书上有些地方看不明白的到网上去搜,可是发现网上的东西很多都是抄书的,甚至连章节号都还带着呢。然后网上的文章又都互相抄,一个看不懂就个个看不懂。论坛里有人问问题,真正问道我关心的问题和困难时,却鲜有人正面回答。真是让人窝火,最终遇到的重重困难还得靠自己去解决。

先上我的驱动代码,它注册了一个平台设备和一个混杂设备,实现了对 {读取时间/设定时间/蜂鸣器每秒一响/关闭蜂鸣器} 四个命令的反应。

【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?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值