Chinaunix首页 | 论坛 | 博客
  • 博客访问: 316608
  • 博文数量: 40
  • 博客积分: 892
  • 博客等级: 准尉
  • 技术积分: 445
  • 用 户 组: 普通用户
  • 注册时间: 2011-01-25 10:20
文章存档

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 是一个很简单的例子,它用到了如下硬件资源。

开发板上所用到的 1ADC 的硬件资源

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_initLS1B_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数据结构的指针。其中包括openreadioctl等字段,当用户程序调用openwriteioctl函数时,最终调用该驱动的相应函数。

LS1B_74LV165_button_open()函数中使能芯片74LV165用到的3gpio口,并将端口设置为输出。当用户调用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——0xbfe67fff16KB地址空间内,其基地址为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; //TOYRTC控制寄存器

//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_wakeup1时表明这个设备可以被唤醒,设备驱动为了支持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")

阅读(2236) | 评论(0) | 转发(2) |
给主人留下些什么吧!~~