全部博文(40)
2011年(40)
分类: LINUX
2011-09-05 10:19:40
10-1 ADC驱动程序 10-1-1 说明
程序源代码说明:
驱动源代码所在目录 |
drivers/spi/ |
驱动程序名称 |
mcp3201.c |
设备号 |
mcp3201属于杂项设备,设备自动生成 |
设备名 |
/dev/mcp3201 |
测试程序源代码目录 |
Examples/Drivers/ADC |
测试程序名称 |
test-mcp3201.c |
测试程序可执行文件名称 |
test-mcp3201 |
要写实际的驱动,就必须了解相关的硬件资源,比如用到的寄存器,物理地址,中断等,在这里,mcp3201 是一个很简单的例子,它用到了如下硬件资源。
开发板上所用到的 1个ADC 的硬件资源:
ADC |
对应的 IO 寄存器名称 |
对应的 CPU 引脚 |
Mcp3201 |
Spi0 |
G16 |
要操作所用到的 IO 口,就要设置它们所用到的寄存器,我们可以调用一些现成的函数或者宏,在我们的驱动中使用的是mcp3201_open()来进行控制的。
函数mcp3201_open()的定义在我们的驱动程序中,接下来的驱动程序代码将给出示意。
在下面的驱动代码中,你将看到调用mcp3201_open()函数来对设备实施初始化并打开,除此之外,还需要调用一些和设备驱动密切相关的基本函数,如注册设备misc_register,填写驱动函数结构file_operations,以及像Hello,Module 中那样的 module_init 和 module_exit 函数等。
有些函数并不是必须的,随着你对 Linux 驱动开发的进一步了解和阅读更多的代码,你自然明白。下面是我们为 mcp3201 编写的驱动代码清单。
10-1-2 驱动程序清单
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define SB2F_BOARD_SPI0_BASE 0xbfe80000
struct spi_device *adc;
static int mcp3201_open(struct inode *inode, struct file *file) {
//取消片选CS3
writeb(0xff, SB2F_BOARD_SPI0_BASE + REG_SPCSR);
//复位SPI控制寄存器工作
writeb(0x10, SB2F_BOARD_SPI0_BASE + REG_SPCR);
//重置状态寄存器SPSR
writeb(0xc0, SB2F_BOARD_SPI0_BASE + REG_SPSR);
//设置外部寄存器
writeb(0x02, SB2F_BOARD_SPI0_BASE + REG_SPER);
//禁用SPI FLASH读
writeb(0x30, SB2F_BOARD_SPI0_BASE + REG_SPPR);
//配置SPI时序
writeb(0xd3, SB2F_BOARD_SPI0_BASE + REG_SPCR);
//设置片选CS3
writeb(0x7f, SB2F_BOARD_SPI0_BASE + REG_SPCSR);
return 0;
}
static int mcp3201_close(struct inode *inode, struct file *file) {
//取消对CS3的片选
writeb(0xff, SB2F_BOARD_SPI0_BASE + REG_SPCSR);
return 0;
}
static ssize_t mcp3201_read(struct file *file, char __user *buf, size_t count, loff_t *ptr)
{
ssize_t retval;
unsigned char val[2] = {0x0};
unsigned char tx_buf[1] = {0x0};
unsigned char rx_buf[2] = {0};
//发送0个字节的命令到spi从机,并从spi从机中读取2字节的数据到缓冲区
retval = spi_write_then_read(adc, tx_buf, 0, rx_buf, 2);
if (retval < 0) {
dev_err(&adc->dev, "error %d reading SR\n", (int)retval);
return retval;
}
//对从mcp3201读来的数据,按照其datasheet的要求取出编码
val[0] = rx_buf[1] >> 1;
val[0] |= (rx_buf[0] & 0x01) << 7;
val[1] = (rx_buf[0] >> 1) & 0xf;
//将从mcp3201取出的数据合理编码,并传送到用户空间供用户使用
if (copy_to_user(buf, val, 2))
{
printk("Copy data to userspace error!\n");
return -EFAULT;
}
return 0;
}
static int __devinit mcp3201_probe(struct spi_device *spi)
{
struct spi_device *spi0;
spi0 = spi;
adc = spi0;
return 0;
}
static int __devexit mcp3201_remove(struct spi_device *spi)
{
writeb(0xff, SB2F_BOARD_SPI0_BASE + REG_SPCSR);
return 0;
}
static struct spi_driver mcp3201_driver = {
.driver = {
.name = "mcp3201",
.bus = &spi_bus_type,
.owner = THIS_MODULE,
},
.probe = mcp3201_probe,
.remove = __devexit_p(mcp3201_remove),
};
static const struct file_operations mcp3201_ops = {
.owner = THIS_MODULE,
.open = mcp3201_open,
.release = mcp3201_close,
.read = mcp3201_read,
};
static struct miscdevice mcp3201_miscdev = {
MISC_DYNAMIC_MINOR,
"mcp3201",
&mcp3201_ops,
};
static int mcp3201_init(void)
{
if (misc_register(&mcp3201_miscdev)) {
printk(KERN_WARNING "buzzer:Couldn't register device 10, %d.\n", 255);
return -EBUSY;
}
return spi_register_driver(&mcp3201_driver);
}
static void mcp3201_exit(void)
{
spi_unregister_driver(&mcp3201_driver);
}
module_init(mcp3201_init);
module_exit(mcp3201_exit);
MODULE_LICENSE("GPL");
10-2 外部按键驱动 10-2-1 说明
程序源代码说明:
驱动源程序所在目录 |
driver/input/ |
驱动程序名 |
74LV165_button.c 74LV165_button.h |
设备名 |
ls1b_buttons |
测试程序源代码目录 |
|
测试程序代码名 |
74LV165_test.c |
测试程序可执行文件名 |
74LV165_test |
说明:按键驱动已经被编译到缺省内核中,因此不用使用insmod方式加载 |
开发板用到的资源:
gpio0 |
KEY_DATA |
gpio1 |
KEY_EN |
gpio2 |
KEY_SCL |
按键驱动在硬件上没有接外部中断,且通过两片并行输入串行输出的移位芯片74LV165连接,因此,按键通过轮询的方式实现。应用程序直接读取/dev/ls1b_buttons设备节点来对按键值进行读取。在启动linux内核后,在根目录下执行当前目录下的可执行文件74LV165_test。
10-2-2 驱动程序清单
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "74LV165_button.h"
#define BUF_MAXSIZE 16
#define TIMER_DELAY 5
static unsigned int BReadBuf = 0;
static int delay(int time){
while(--time);
return 0;
}
static void prepare_for_read(void){
int reg = 0;
/* CP =0 */
LS1B_74LV165_READ(reg, LS1B_74LV165_CP);
LS1B_74LV165_WRITE(LS1B_74LV165_CP, reg & (~(1 << 2)));
/* PL = 0 */
LS1B_74LV165_READ(reg, LS1B_74LV165_PL);
LS1B_74LV165_WRITE(LS1B_74LV165_PL, reg & ~(1 << 1));
/* delay 100 */
delay(10000);
/* PL = 1 */
LS1B_74LV165_READ(reg, LS1B_74LV165_PL);
LS1B_74LV165_WRITE(LS1B_74LV165_PL, reg | (1 << 1));
}
static void scanf_keyboard(void){
int reg = 0, val = 0;
int time;
delay(10);
LS1B_74LV165_READ(reg, LS1B_74LV165_DATA);
val = ((~reg) & (1 << 0));
BReadBuf = val >> 0;
for(time = 1; time < 16; time ++){
/* delay */
delay(100);
/* CP = 1 */
LS1B_74LV165_READ(reg, LS1B_74LV165_CP);
LS1B_74LV165_WRITE(LS1B_74LV165_CP, reg | (1 << 2));
delay(100);
LS1B_74LV165_READ(reg, LS1B_74LV165_DATA);
val = ((~reg) & (1 << 0));
BReadBuf |= val << time;
/* CP = 0 */
LS1B_74LV165_READ(reg, LS1B_74LV165_CP);
LS1B_74LV165_WRITE(LS1B_74LV165_CP, reg & (~(1 << 2)));
}
}
static void button_read(void){
prepare_for_read();
/* scanf keyboard */
scanf_keyboard();
if(BReadBuf){
printk("\nBReadBuf value is 0x%08x\n",BReadBuf);
}
}
static int LS1B_74LV165_button_open(struct inode *inode, struct file *filp){
int reg;
printk("Welcome to use 74LV165 driver\n");
//enalbe pin
LS1B_74LV165_EN_GPIO(GPIO_KEY_DATA);
LS1B_74LV165_EN_GPIO(GPIO_KEY_EN);
LS1B_74LV165_EN_GPIO(GPIO_KEY_SCL);
//enable output
LS1B_74LV165_OEN_GPIO(GPIO_KEY_SCL);
LS1B_74LV165_OEN_GPIO(GPIO_KEY_EN);
LS1B_74LV165_IEN_GPIO(GPIO_KEY_DATA);
printk("init 74LV165 is done\n");
return 0;
}
static ssize_t LS1B_74LV165_button_read(struct file *filp, char __user *buf, size_t count, loff_t *oppos){
button_read();
if(BReadBuf){
copy_to_user(buf,&BReadBuf,count);
BReadBuf= 0;
delay(10000000);
return count;
}
return -EFAULT;
}
static int LS1B_74LV165_button_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg){
return 0;
}
static struct file_operations ls1b_74Llv165_button_fops = {
.open = LS1B_74LV165_button_open,
.read = LS1B_74LV165_button_read,
.ioctl = LS1B_74LV165_button_ioctl,
};
static struct miscdevice ls1b_74lv165_button = {
.minor = LS1B_BUTTON_MINOR,
.name = "ls1b_buttons",
.fops = &ls1b_74Llv165_button_fops,
};
static int __init LS1B_74LV165_button_init(void){
int ret;
printk("======================button init=========================\n");
ret = misc_register(&ls1b_74lv165_button);
if(ret < 0){
printk("74LV165_button can't get major number !\n");
return ret;
}
#ifdef CONFIG_DEVFS_FS
devfs_button_dir = devfs_mk_dir(NULL,"LS1B_74LV165_button",NULL);
devfs_buttonraw = devfs_register(devfs_kbd_dir,"0raw",DEVFS_FL_DEFAULT,kbdMajor,KBDRAW_MINOR,S_IFCHR|S_IRUSR|S_IWUSR,&LS1B_74LV165_button_fops,NULL);
#endif
return 0;
}
static void __exit LS1B_74LV165_button_exit(void){
misc_deregister(&ls1b_74lv165_button);
}
module_init(LS1B_74LV165_button_init);
module_exit(LS1B_74LV165_button_exit);
其中module_init()和module_exit()函数注册了LS1B_74LV165_button_init及LS1B_74LV165_button_exit函数。当模块加载时将会调用这两个函数。
在LS1B_74LV165_button_init()函数中调用misc_register()函数注册按键驱动为misc设备。并将struct miscdevice的数据结构传递给该函数,在该结构中对minor字段及name字段进行赋值,这样该驱动会在/dev目录下创建名为ls1b_buttons的设备文件,其中主设备号为10,次设备号为minor的值,即为143。
在struct miscdevice数据结构中,还有一个fops的字段,该字段为指向struct file_operations数据结构的指针。其中包括open、read、ioctl等字段,当用户程序调用open、write、ioctl函数时,最终调用该驱动的相应函数。
在LS1B_74LV165_button_open()函数中使能芯片74LV165用到的3个gpio口,并将端口设置为输出。当用户调用read函数时,运行驱动程序的 LS1B_74LV165_button_read()函数,该函数调用button_read(),读取按键编码。在button_read()函数中调用prepare_for_read()根据芯片74LV165的时序将按键信息写入芯片74LV165的移位寄存器,然后调用scanf_keyboard()函数根据芯片74LV165的时序读写移位寄存器中的按键编码。最后通过LS1B_74LV165_button_read()函数中的copy_to_user()函数将按键编码返回给用户空间。
10-3 RTC驱动程序 10-3-1 说明
程序源代码说明:
驱动源代码所在目录 |
/driver/rtc |
驱动程序名称 |
rtc-gs2fsb.c |
该驱动的主设备号 |
RTC设备,设备号将自动生成 |
设备名 |
/dev/rtc0 |
测试程序可执行文件名称 |
date hwclock |
RTC模块寄存器位于0xbfe64000——0xbfe67fff的16KB地址空间内,其基地址为0xbfe64000,所有寄存器位宽均为32位。详细的寄存器描述可参考loongson 1B的数据手册。
10-3-2 驱动程序清单#include
#include
#include
#include
#include
#define RTC_TOYIM 0x00
#define RTC_TOYWLO 0x04
#define RTC_TOYWHI 0x08
#define RTC_TOYRLO 0x0c
#define RTC_TOYRHI 0x10
#define RTC_TOYMH0 0x14
#define RTC_TOYMH1 0x18
#define RTC_TOYMH2 0x1c
#define RTC_CNTL 0x20
#define RTC_RTCIM 0x40
#define RTC_WRITE0 0x44
#define RTC_READ0 0x48
#define RTC_RTCMH0 0x4c
#define RTC_RTCMH1 0x50
#define RTC_RTCMH2 0x54
struct rtc_sb2f{
struct rtc_device *rtc;
void __iomem *regs;
unsigned long alarm_time;
unsigned long irq;
spinlock_t lock;
};
static int sb2f_rtc_read_register(struct rtc_sb2f *rtc, unsigned long reg)
{
int data;
data = (*(volatile unsigned int *)(rtc->regs + reg));
return data;
}
static int sb2f_rtc_write_register(struct rtc_sb2f *rtc, unsigned long reg, int data)
{
(*(volatile unsigned int *)(rtc->regs + reg)) = data;
}
static int sb2f_rtc_readtime(struct device *dev, struct rtc_time *tm)
{
struct rtc_sb2f *rtc = dev_get_drvdata(dev);
unsigned long now, now1;
now = sb2f_rtc_read_register(rtc, RTC_TOYRLO);
tm->tm_sec = ((now >> 4) & 0x3f);
tm->tm_min = ((now >> 10) & 0x3f);
tm->tm_hour = ((now >> 16) & 0x1f);
tm->tm_mday = ((now >> 21) & 0x1f);
tm->tm_mon = ((now >> 26) & 0x3f) -1;
now1 = sb2f_rtc_read_register(rtc, RTC_TOYRHI);
tm->tm_year = (now1 -1900) ;
return 0;
}
static int sb2f_rtc_settime(struct device *dev, struct rtc_time *tm)
{
struct rtc_sb2f *rtc = dev_get_drvdata(dev);
unsigned long now;
int ret;
now = ((tm->tm_sec << 4) | (tm->tm_min << 10) | (tm->tm_hour << 16) |
(tm->tm_mday << 21) | (((tm->tm_mon+1)<< 26) ));
spin_lock_irq(&rtc->lock);
sb2f_rtc_write_register(rtc, RTC_TOYWLO, now);
//set year
sb2f_rtc_write_register(rtc, RTC_TOYWHI, (tm->tm_year+1900) );
spin_unlock_irq(&rtc->lock);
return 0;
}
static int sb2f_rtc_readalarm(struct device *dev, struct rtc_wkalrm *alrm)
{
struct rtc_sb2f *rtc = dev_get_drvdata(dev);
unsigned long time;
spin_lock_irq(&rtc->lock);
time = sb2f_rtc_read_register(rtc, RTC_TOYMH0);
spin_unlock_irq(&rtc->lock);
alrm->time.tm_sec = (time & 0x3f);
alrm->time.tm_min = ((time >> 6) & 0x3f);
alrm->time.tm_hour = ((time >> 12) & 0x1f);
alrm->time.tm_mday = ((time >> 17) & 0x1f);
alrm->time.tm_mon = ((time >> 22) & 0x1f);
alrm->time.tm_year = ((time >> 26) & 0x3f);
return 0;
}
static int sb2f_rtc_setalarm(struct device *dev, struct rtc_wkalrm *alrm)
{
struct rtc_sb2f *rtc = dev_get_drvdata(dev);
int time;
time =(( alrm->time.tm_sec & 0x3f) | ((alrm->time.tm_min & 0x3f) << 6)
| ((alrm->time.tm_hour & 0x1f) << 12) | ((alrm->time.tm_mday
& 0x1f) << 17) | ((alrm->time.tm_mon & 0xf) << 22) |
((alrm->time.tm_year & 0x3f) << 26));
spin_lock_irq(&rtc->lock);
sb2f_rtc_write_register(rtc, RTC_TOYMH0, time);
spin_unlock_irq(&rtc->lock);
return 0;
}
static int sb2f_rtc_ioctl(struct device *dev, unsigned int cmd,
unsigned long arg)
{
switch(cmd) {
case RTC_PIE_ON:
break;
case RTC_PIE_OFF:
break;
case RTC_UIE_ON:
break;
case RTC_UIE_OFF:
break;
case RTC_AIE_ON:
break;
case RTC_AIE_OFF:
break;
default:
return -ENOIOCTLCMD;
}
return 0;
}
static irqreturn_t sb2f_rtc_interrupt(int irq, void *dev_id)
{
struct rtc_sb2f *rtc = (struct rtc_sb2f *)dev_id;/*接收申请中断时传递过来的rtc_dev参数*/
unsigned long events = 0;
spin_lock(&rtc->lock);
events = RTC_AF | RTC_IRQF;
/*节拍时间中断到来的时候,去设定RTC中节拍时间的相关信息,具体设定的方法,RTC核心部分已经在rtc_update_irq接口函数中实现,函数定义实现在interface.c中*/
rtc_update_irq(rtc->rtc, 1, events);
spin_unlock(&rtc->lock);
}
static struct rtc_class_ops sb2f_rtc_ops = {
.ioctl = sb2f_rtc_ioctl,
.read_time = sb2f_rtc_readtime,
.set_time = sb2f_rtc_settime,
.read_alarm = sb2f_rtc_readalarm,
.set_alarm = sb2f_rtc_setalarm,
};
static int __init sb2f_rtc_probe(struct platform_device *pdev)
{
struct resource *regs;/*定义一个资源,用来保存获取的RTC的资源*/
struct rtc_sb2f *rtc;
int irq = -1;
int ret;
rtc = kzalloc(sizeof(struct rtc_sb2f), GFP_KERNEL);
if (!rtc) {
dev_dbg(&pdev->dev, "out of memory\n");
return -ENOMEM;
}
/*获取RTC平台设备所使用的IO端口资源,注意这个IORESOURCE_MEM标志和RTC平台设备定义中的一致*/
regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!regs) {
dev_dbg(&pdev->dev, "no nmio resource defined\n");
ret = -ENXIO;
goto out;
}
printk("the regs->start is 0x%x", regs->start);
//在系统定义的RTC平台设备中获取TICK节拍时间中断号
irq = platform_get_irq(pdev, 0);
printk("the irq is %d\n", irq);
rtc->irq = irq;
//把I/O资源IORESOURCE_MEM 起始地址到结束地址范围的 资源映射到虚拟地址 ioremap定义在io.h中。
//注意:IO空间要映射后才能使用,以后对虚拟地址的操作就是对IO空间的操作
rtc->regs = ioremap(regs->start, regs->end - regs->start + 1);
if (!rtc->regs) {
ret = -ENOMEM;
dev_dbg(&pdev->dev, "could not map i/o memory\n");
goto out;
}
//初始化自旋锁
spin_lock_init(&rtc->lock);
// next set control register
// sb2f_rtc_write_register(rtc, RTC_CNTL, 0x800);
(*(volatile int *)(0xbfe64040)) = 0x2d00; //TOY和RTC控制寄存器
//0010 1101 0000 0000
//申请中断 (这个可以放到open()函数里)
//res->start 中断号(在具体的平台设备platform_device定义时赋值)
//s3c2410wdt_irq 中断处理函数 IRQF_DISABLED中断处理属性 快速慢速共享
//pdev->name 这个传递给 request_irq 的字串用在 /proc/interrupts 来显示中断的拥有者
//pdev 用作共享中断线的指针 被驱动用来指向它自己的私有数据区(来标识哪个设备在中断).
ret = request_irq(irq, sb2f_rtc_interrupt, IRQF_SHARED, "rtc", rtc);
if (ret) {
dev_dbg(&pdev->dev, "could not request irq %d", irq);
goto out_iounmap;
}
//注册RTC设备
/* register RTC and exit */
/*将RTC注册为RTC设备类,RTC设备类在RTC驱动核心部分中由系统定义好的,
注意rtcops这个参数是一个结构体,该结构体的作用和里面的接口函数实现在第③步中。
rtc_device_register函数在rtc.h中定义,在drivers/rtc/class.c中实现*/
rtc->rtc = rtc_device_register(pdev->name, &pdev->dev, &sb2f_rtc_ops, THIS_MODULE);
/*里的IS_ERR(),它就是判断kthread_run()返回的指针是否有错,如果指针并不是指向最后一个page,那么没有问题,申请成功了,如果指针指向了最后一个page,那么说明实际上这不是一个有效的指针,这个指针里保存的实际上是一种错误代码.而通常很常用的方法就是先用IS_ERR()来判断是否是错误,然后如果是,那么就调用PTR_ERR()来返回这个错误代码.*/
if (IS_ERR(rtc->rtc)) {
dev_dbg(&pdev->dev, "could not register rtc device\n");
//PTR_ERR()只是返回错误代码,也就是提供一个信息给调用者,如果你只需要知道是否出错,而不在乎因为什么而出错
ret = PTR_ERR(rtc->rtc);
goto out_free_irq;
}
/*将RTC设备类的数据传递给系统平台设备。
platform_set_drvdata是定义在platform_device.h的宏,如下:
#define platform_set_drvdata(_dev,data) dev_set_drvdata(&(_dev)->dev, (data))
而dev_set_drvdata又被定义在include/linux/device.h中,如下:
static inline void dev_set_drvdata (struct device *dev, void *data){
dev->driver_data = data;
}*/
platform_set_drvdata(pdev, rtc);
/*device_init_wakeup该函数定义在pm_wakeup.h中,定义如下:
static inline void device_init_wakeup(struct device *dev, int val){
dev->power.can_wakeup = dev->power.should_wakeup = !!val;}
显然这个函数是让驱动支持电源管理的,这里只要知道,can_wakeup为1时表明这个设备可以被唤醒,设备驱动为了支持Linux中的电源管理,有责任调用device_init_wakeup()来初始化can_wakeup,而should_wakeup则是在设备的电源状态发生变化的时候被device_may_wakeup()用来测试,测试它该不该变化,因此can_wakeup表明的是一种能力,而should_wakeup表明的是有了这种能力以后去不去做某件事。好了,我们没有必要深入研究电源管理的内容了,要不就扯远了,电源管理以后再讲*/
device_init_wakeup(&pdev->dev, 1);
//头文件include/linux/device.h中所提供的宏(包括dev_printk()、dev_dbg()、dev_warn()、dev_info()和dev_err())来决定何种类型的设备访问消息需要被记录。 printk()
dev_info(&pdev->dev, "SB2F RTC at %08lx irq %ld \n", (unsigned long)rtc->regs, rtc->irq);
return 0;
out_free_irq:
free_irq(irq, rtc);
out_iounmap:
iounmap(rtc->regs);
out:
kfree(rtc);
return ret;
}
/*注意:这是使用了一个__devexit。
我们还是先来讲讲这个:
在Linux内核中,使用了大量不同的宏来标记具有不同作用的函数和数据结构,
这些宏在include/linux/init.h 头文件中定义,编译器通过这些宏可以把代码优化放到合适的内存位置,
以减少内存占用和提高内核效率。__devinit、__devexit就是这些宏之一,在probe()和remove()函数中
应该使用__devinit和__devexit宏。又当remove()函数使用了__devexit宏时,则在驱动结构体中一定要
使用__devexit_p宏来引用remove(),所以在第①步中就用__devexit_p来引用rtc_remove*/
static int __exit sb2f_rtc_remove(struct platform_device *pdev)
{
/*从系统平台设备中获取RTC设备类的数据*/
struct rtc_sb2f *rtc = platform_get_drvdata(pdev);
device_init_wakeup(&pdev->dev, 0);
free_irq(rtc->irq, rtc);
iounmap(rtc->regs);/*释放RTC虚拟地址映射空间*/
rtc_device_unregister(rtc->rtc);/*注销RTC设备类*/
kfree(rtc);/*销毁保存RTC平台设备的资源内存空间*/
platform_set_drvdata(pdev, NULL);/*清空平台设备中RTC驱动数据*/
return 0;
}
MODULE_ALIAS("platform:sb2f-rtc");
static struct platform_driver sb2f_rtc_driver = {
.remove = __exit_p(sb2f_rtc_remove),
.driver = {
.name = "sb2f-rtc",
.owner = THIS_MODULE,
},
};
static int __init sb2f_rtc_init(void)
{
/*将RTC注册成平台设备驱动*/
return platform_driver_probe(&sb2f_rtc_driver, sb2f_rtc_probe);
}
module_init(sb2f_rtc_init);
static void __exit sb2f_rtc_exit(void)
{
/*注销RTC平台设备驱动*/
platform_driver_unregister(&sb2f_rtc_driver);
}
module_exit(sb2f_rtc_exit);
MODULE_AUTHOR("
MODULE_DESCRIPTION("Real Time clock for loongson2fsb");
MODULE_LICENSE("GPL");