2006-03-27 13:16:41 +04:00
/*
* RTC subsystem , dev interface
*
* Copyright ( C ) 2005 Tower Technologies
* Author : Alessandro Zummo < a . zummo @ towertech . it >
*
* based on arch / arm / common / rtctime . c
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation .
*/
# include <linux/module.h>
# include <linux/rtc.h>
2007-05-08 11:33:27 +04:00
# include "rtc-core.h"
2006-03-27 13:16:41 +04:00
static dev_t rtc_devt ;
# define RTC_DEV_MAX 16 /* 16 RTCs should be enough for everyone... */
static int rtc_dev_open ( struct inode * inode , struct file * file )
{
int err ;
struct rtc_device * rtc = container_of ( inode - > i_cdev ,
struct rtc_device , char_dev ) ;
2006-10-01 10:28:17 +04:00
const struct rtc_class_ops * ops = rtc - > ops ;
2006-03-27 13:16:41 +04:00
2008-08-13 02:08:41 +04:00
if ( test_and_set_bit_lock ( RTC_DEV_BUSY , & rtc - > flags ) )
return - EBUSY ;
2006-03-27 13:16:41 +04:00
2007-05-08 11:33:30 +04:00
file - > private_data = rtc ;
2006-03-27 13:16:41 +04:00
2007-05-08 11:33:40 +04:00
err = ops - > open ? ops - > open ( rtc - > dev . parent ) : 0 ;
2006-03-27 13:16:41 +04:00
if ( err = = 0 ) {
spin_lock_irq ( & rtc - > irq_lock ) ;
rtc - > irq_data = 0 ;
spin_unlock_irq ( & rtc - > irq_lock ) ;
2008-08-13 02:08:41 +04:00
return 0 ;
2006-03-27 13:16:41 +04:00
}
2007-11-29 03:22:03 +03:00
/* something has gone wrong */
2007-12-05 10:45:05 +03:00
clear_bit_unlock ( RTC_DEV_BUSY , & rtc - > flags ) ;
2006-03-27 13:16:41 +04:00
return err ;
}
2006-06-25 16:48:17 +04:00
# ifdef CONFIG_RTC_INTF_DEV_UIE_EMUL
/*
* Routine to poll RTC seconds field for change as often as possible ,
* after first RTC_UIE use timer to reduce polling
*/
2006-11-22 17:57:56 +03:00
static void rtc_uie_task ( struct work_struct * work )
2006-06-25 16:48:17 +04:00
{
2006-11-22 17:57:56 +03:00
struct rtc_device * rtc =
container_of ( work , struct rtc_device , uie_task ) ;
2006-06-25 16:48:17 +04:00
struct rtc_time tm ;
int num = 0 ;
int err ;
2007-05-08 11:33:30 +04:00
err = rtc_read_time ( rtc , & tm ) ;
2006-11-25 22:09:28 +03:00
local_irq_disable ( ) ;
spin_lock ( & rtc - > irq_lock ) ;
2006-06-25 16:48:17 +04:00
if ( rtc - > stop_uie_polling | | err ) {
rtc - > uie_task_active = 0 ;
} else if ( rtc - > oldsecs ! = tm . tm_sec ) {
num = ( tm . tm_sec + 60 - rtc - > oldsecs ) % 60 ;
rtc - > oldsecs = tm . tm_sec ;
rtc - > uie_timer . expires = jiffies + HZ - ( HZ / 10 ) ;
rtc - > uie_timer_active = 1 ;
rtc - > uie_task_active = 0 ;
add_timer ( & rtc - > uie_timer ) ;
} else if ( schedule_work ( & rtc - > uie_task ) = = 0 ) {
rtc - > uie_task_active = 0 ;
}
2006-11-25 22:09:28 +03:00
spin_unlock ( & rtc - > irq_lock ) ;
2006-06-25 16:48:17 +04:00
if ( num )
2007-05-08 11:33:30 +04:00
rtc_update_irq ( rtc , num , RTC_UF | RTC_IRQF ) ;
2006-11-25 22:09:28 +03:00
local_irq_enable ( ) ;
2006-06-25 16:48:17 +04:00
}
static void rtc_uie_timer ( unsigned long data )
{
struct rtc_device * rtc = ( struct rtc_device * ) data ;
unsigned long flags ;
spin_lock_irqsave ( & rtc - > irq_lock , flags ) ;
rtc - > uie_timer_active = 0 ;
rtc - > uie_task_active = 1 ;
if ( ( schedule_work ( & rtc - > uie_task ) = = 0 ) )
rtc - > uie_task_active = 0 ;
spin_unlock_irqrestore ( & rtc - > irq_lock , flags ) ;
}
2009-01-04 23:00:54 +03:00
static int clear_uie ( struct rtc_device * rtc )
2006-06-25 16:48:17 +04:00
{
spin_lock_irq ( & rtc - > irq_lock ) ;
2009-01-04 23:00:54 +03:00
if ( rtc - > uie_irq_active ) {
2006-06-25 16:48:17 +04:00
rtc - > stop_uie_polling = 1 ;
if ( rtc - > uie_timer_active ) {
spin_unlock_irq ( & rtc - > irq_lock ) ;
del_timer_sync ( & rtc - > uie_timer ) ;
spin_lock_irq ( & rtc - > irq_lock ) ;
rtc - > uie_timer_active = 0 ;
}
if ( rtc - > uie_task_active ) {
spin_unlock_irq ( & rtc - > irq_lock ) ;
flush_scheduled_work ( ) ;
spin_lock_irq ( & rtc - > irq_lock ) ;
}
2009-01-04 23:00:54 +03:00
rtc - > uie_irq_active = 0 ;
2006-06-25 16:48:17 +04:00
}
spin_unlock_irq ( & rtc - > irq_lock ) ;
2009-01-04 23:00:54 +03:00
return 0 ;
2006-06-25 16:48:17 +04:00
}
static int set_uie ( struct rtc_device * rtc )
{
struct rtc_time tm ;
int err ;
2007-05-08 11:33:30 +04:00
err = rtc_read_time ( rtc , & tm ) ;
2006-06-25 16:48:17 +04:00
if ( err )
return err ;
spin_lock_irq ( & rtc - > irq_lock ) ;
2009-01-04 23:00:54 +03:00
if ( ! rtc - > uie_irq_active ) {
rtc - > uie_irq_active = 1 ;
2006-06-25 16:48:17 +04:00
rtc - > stop_uie_polling = 0 ;
rtc - > oldsecs = tm . tm_sec ;
rtc - > uie_task_active = 1 ;
if ( schedule_work ( & rtc - > uie_task ) = = 0 )
rtc - > uie_task_active = 0 ;
}
rtc - > irq_data = 0 ;
spin_unlock_irq ( & rtc - > irq_lock ) ;
return 0 ;
}
2009-01-04 23:00:54 +03:00
int rtc_dev_update_irq_enable_emul ( struct rtc_device * rtc , unsigned int enabled )
{
if ( enabled )
return set_uie ( rtc ) ;
else
return clear_uie ( rtc ) ;
}
EXPORT_SYMBOL ( rtc_dev_update_irq_enable_emul ) ;
2006-06-25 16:48:17 +04:00
# endif /* CONFIG_RTC_INTF_DEV_UIE_EMUL */
2006-03-27 13:16:41 +04:00
static ssize_t
rtc_dev_read ( struct file * file , char __user * buf , size_t count , loff_t * ppos )
{
2007-10-16 12:28:17 +04:00
struct rtc_device * rtc = file - > private_data ;
2006-03-27 13:16:41 +04:00
DECLARE_WAITQUEUE ( wait , current ) ;
unsigned long data ;
ssize_t ret ;
2006-05-01 23:16:16 +04:00
if ( count ! = sizeof ( unsigned int ) & & count < sizeof ( unsigned long ) )
2006-03-27 13:16:41 +04:00
return - EINVAL ;
add_wait_queue ( & rtc - > irq_queue , & wait ) ;
do {
__set_current_state ( TASK_INTERRUPTIBLE ) ;
spin_lock_irq ( & rtc - > irq_lock ) ;
data = rtc - > irq_data ;
rtc - > irq_data = 0 ;
spin_unlock_irq ( & rtc - > irq_lock ) ;
if ( data ! = 0 ) {
ret = 0 ;
break ;
}
if ( file - > f_flags & O_NONBLOCK ) {
ret = - EAGAIN ;
break ;
}
if ( signal_pending ( current ) ) {
ret = - ERESTARTSYS ;
break ;
}
schedule ( ) ;
} while ( 1 ) ;
set_current_state ( TASK_RUNNING ) ;
remove_wait_queue ( & rtc - > irq_queue , & wait ) ;
if ( ret = = 0 ) {
/* Check for any data updates */
if ( rtc - > ops - > read_callback )
2007-05-08 11:33:40 +04:00
data = rtc - > ops - > read_callback ( rtc - > dev . parent ,
2006-05-01 23:16:16 +04:00
data ) ;
if ( sizeof ( int ) ! = sizeof ( long ) & &
count = = sizeof ( unsigned int ) )
ret = put_user ( data , ( unsigned int __user * ) buf ) ? :
sizeof ( unsigned int ) ;
else
ret = put_user ( data , ( unsigned long __user * ) buf ) ? :
sizeof ( unsigned long ) ;
2006-03-27 13:16:41 +04:00
}
return ret ;
}
static unsigned int rtc_dev_poll ( struct file * file , poll_table * wait )
{
2007-10-16 12:28:17 +04:00
struct rtc_device * rtc = file - > private_data ;
2006-03-27 13:16:41 +04:00
unsigned long data ;
poll_wait ( file , & rtc - > irq_queue , wait ) ;
data = rtc - > irq_data ;
return ( data ! = 0 ) ? ( POLLIN | POLLRDNORM ) : 0 ;
}
2008-07-24 08:30:33 +04:00
static long rtc_dev_ioctl ( struct file * file ,
2006-03-27 13:16:41 +04:00
unsigned int cmd , unsigned long arg )
{
int err = 0 ;
2007-05-08 11:33:30 +04:00
struct rtc_device * rtc = file - > private_data ;
2006-10-01 10:28:17 +04:00
const struct rtc_class_ops * ops = rtc - > ops ;
2006-03-27 13:16:41 +04:00
struct rtc_time tm ;
struct rtc_wkalrm alarm ;
void __user * uarg = ( void __user * ) arg ;
2008-07-24 08:30:33 +04:00
err = mutex_lock_interruptible ( & rtc - > ops_lock ) ;
if ( err )
2008-07-30 09:33:30 +04:00
return err ;
2008-07-24 08:30:33 +04:00
2006-11-25 22:09:27 +03:00
/* check that the calling task has appropriate permissions
2006-06-25 16:48:20 +04:00
* for certain ioctls . doing this check here is useful
* to avoid duplicate code in each driver .
*/
switch ( cmd ) {
case RTC_EPOCH_SET :
case RTC_SET_TIME :
if ( ! capable ( CAP_SYS_TIME ) )
2008-07-24 08:30:33 +04:00
err = - EACCES ;
2006-06-25 16:48:20 +04:00
break ;
case RTC_IRQP_SET :
if ( arg > rtc - > max_user_freq & & ! capable ( CAP_SYS_RESOURCE ) )
2008-07-24 08:30:33 +04:00
err = - EACCES ;
2006-06-25 16:48:20 +04:00
break ;
case RTC_PIE_ON :
2007-10-16 12:28:23 +04:00
if ( rtc - > irq_freq > rtc - > max_user_freq & &
! capable ( CAP_SYS_RESOURCE ) )
2008-07-24 08:30:33 +04:00
err = - EACCES ;
2006-06-25 16:48:20 +04:00
break ;
}
2008-07-24 08:30:33 +04:00
if ( err )
goto done ;
2006-03-27 13:16:41 +04:00
/* try the driver's ioctl interface */
if ( ops - > ioctl ) {
2007-05-08 11:33:40 +04:00
err = ops - > ioctl ( rtc - > dev . parent , cmd , arg ) ;
2008-07-24 08:30:33 +04:00
if ( err ! = - ENOIOCTLCMD ) {
mutex_unlock ( & rtc - > ops_lock ) ;
2006-03-27 13:16:41 +04:00
return err ;
2008-07-24 08:30:33 +04:00
}
2006-03-27 13:16:41 +04:00
}
/* if the driver does not provide the ioctl interface
* or if that particular ioctl was not implemented
2006-05-21 02:00:29 +04:00
* ( - ENOIOCTLCMD ) , we will try to emulate here .
2008-02-06 12:38:45 +03:00
*
* Drivers * SHOULD NOT * provide ioctl implementations
* for these requests . Instead , provide methods to
* support the following code , so that the RTC ' s main
* features are accessible without using ioctls .
*
* RTC and alarm times will be in UTC , by preference ,
* but dual - booting with MS - Windows implies RTCs must
* use the local wall clock time .
2006-03-27 13:16:41 +04:00
*/
switch ( cmd ) {
case RTC_ALM_READ :
2008-07-24 08:30:33 +04:00
mutex_unlock ( & rtc - > ops_lock ) ;
2007-05-08 11:33:30 +04:00
err = rtc_read_alarm ( rtc , & alarm ) ;
2006-03-27 13:16:41 +04:00
if ( err < 0 )
return err ;
if ( copy_to_user ( uarg , & alarm . time , sizeof ( tm ) ) )
2008-07-24 08:30:33 +04:00
err = - EFAULT ;
return err ;
2006-03-27 13:16:41 +04:00
case RTC_ALM_SET :
2008-07-24 08:30:33 +04:00
mutex_unlock ( & rtc - > ops_lock ) ;
2006-03-27 13:16:41 +04:00
if ( copy_from_user ( & alarm . time , uarg , sizeof ( tm ) ) )
return - EFAULT ;
alarm . enabled = 0 ;
alarm . pending = 0 ;
alarm . time . tm_wday = - 1 ;
alarm . time . tm_yday = - 1 ;
alarm . time . tm_isdst = - 1 ;
2007-05-08 11:34:07 +04:00
/* RTC_ALM_SET alarms may be up to 24 hours in the future.
* Rather than expecting every RTC to implement " don't care "
* for day / month / year fields , just force the alarm to have
* the right values for those fields .
*
* RTC_WKALM_SET should be used instead . Not only does it
* eliminate the need for a separate RTC_AIE_ON call , it
* doesn ' t have the " alarm 23:59:59 in the future " race .
*
* NOTE : some legacy code may have used invalid fields as
* wildcards , exposing hardware " periodic alarm " capabilities .
* Not supported here .
*/
{
unsigned long now , then ;
err = rtc_read_time ( rtc , & tm ) ;
if ( err < 0 )
return err ;
rtc_tm_to_time ( & tm , & now ) ;
alarm . time . tm_mday = tm . tm_mday ;
alarm . time . tm_mon = tm . tm_mon ;
alarm . time . tm_year = tm . tm_year ;
err = rtc_valid_tm ( & alarm . time ) ;
if ( err < 0 )
return err ;
rtc_tm_to_time ( & alarm . time , & then ) ;
/* alarm may need to wrap into tomorrow */
if ( then < now ) {
rtc_time_to_tm ( now + 24 * 60 * 60 , & tm ) ;
alarm . time . tm_mday = tm . tm_mday ;
alarm . time . tm_mon = tm . tm_mon ;
alarm . time . tm_year = tm . tm_year ;
}
}
2008-07-24 08:30:33 +04:00
return rtc_set_alarm ( rtc , & alarm ) ;
2006-03-27 13:16:41 +04:00
case RTC_RD_TIME :
2008-07-24 08:30:33 +04:00
mutex_unlock ( & rtc - > ops_lock ) ;
2007-05-08 11:33:30 +04:00
err = rtc_read_time ( rtc , & tm ) ;
2006-03-27 13:16:41 +04:00
if ( err < 0 )
return err ;
if ( copy_to_user ( uarg , & tm , sizeof ( tm ) ) )
2008-07-24 08:30:33 +04:00
err = - EFAULT ;
return err ;
2006-03-27 13:16:41 +04:00
case RTC_SET_TIME :
2008-07-24 08:30:33 +04:00
mutex_unlock ( & rtc - > ops_lock ) ;
2006-03-27 13:16:41 +04:00
if ( copy_from_user ( & tm , uarg , sizeof ( tm ) ) )
return - EFAULT ;
2008-07-24 08:30:33 +04:00
return rtc_set_time ( rtc , & tm ) ;
2006-11-25 22:09:27 +03:00
2007-10-16 12:28:15 +04:00
case RTC_PIE_ON :
err = rtc_irq_set_state ( rtc , NULL , 1 ) ;
break ;
case RTC_PIE_OFF :
err = rtc_irq_set_state ( rtc , NULL , 0 ) ;
2006-11-25 22:09:27 +03:00
break ;
2009-01-04 23:00:54 +03:00
case RTC_AIE_ON :
mutex_unlock ( & rtc - > ops_lock ) ;
return rtc_alarm_irq_enable ( rtc , 1 ) ;
case RTC_AIE_OFF :
mutex_unlock ( & rtc - > ops_lock ) ;
return rtc_alarm_irq_enable ( rtc , 0 ) ;
case RTC_UIE_ON :
mutex_unlock ( & rtc - > ops_lock ) ;
return rtc_update_irq_enable ( rtc , 1 ) ;
case RTC_UIE_OFF :
mutex_unlock ( & rtc - > ops_lock ) ;
return rtc_update_irq_enable ( rtc , 0 ) ;
2006-11-25 22:09:27 +03:00
case RTC_IRQP_SET :
2007-10-16 12:28:15 +04:00
err = rtc_irq_set_freq ( rtc , NULL , arg ) ;
break ;
case RTC_IRQP_READ :
err = put_user ( rtc - > irq_freq , ( unsigned long __user * ) uarg ) ;
2006-11-25 22:09:27 +03:00
break ;
2006-03-27 13:16:41 +04:00
#if 0
case RTC_EPOCH_SET :
# ifndef rtc_epoch
/*
* There were no RTC clocks before 1900.
*/
if ( arg < 1900 ) {
err = - EINVAL ;
break ;
}
rtc_epoch = arg ;
err = 0 ;
# endif
break ;
case RTC_EPOCH_READ :
err = put_user ( rtc_epoch , ( unsigned long __user * ) uarg ) ;
break ;
# endif
case RTC_WKALM_SET :
2008-07-24 08:30:33 +04:00
mutex_unlock ( & rtc - > ops_lock ) ;
2006-03-27 13:16:41 +04:00
if ( copy_from_user ( & alarm , uarg , sizeof ( alarm ) ) )
return - EFAULT ;
2008-07-24 08:30:33 +04:00
return rtc_set_alarm ( rtc , & alarm ) ;
2006-03-27 13:16:41 +04:00
case RTC_WKALM_RD :
2008-07-24 08:30:33 +04:00
mutex_unlock ( & rtc - > ops_lock ) ;
2007-05-08 11:33:30 +04:00
err = rtc_read_alarm ( rtc , & alarm ) ;
2006-03-27 13:16:41 +04:00
if ( err < 0 )
return err ;
if ( copy_to_user ( uarg , & alarm , sizeof ( alarm ) ) )
2008-07-24 08:30:33 +04:00
err = - EFAULT ;
return err ;
2006-03-27 13:16:41 +04:00
default :
2006-05-21 02:00:29 +04:00
err = - ENOTTY ;
2006-03-27 13:16:41 +04:00
break ;
}
2008-07-24 08:30:33 +04:00
done :
mutex_unlock ( & rtc - > ops_lock ) ;
2006-03-27 13:16:41 +04:00
return err ;
}
2008-10-04 02:23:36 +04:00
static int rtc_dev_fasync ( int fd , struct file * file , int on )
{
struct rtc_device * rtc = file - > private_data ;
return fasync_helper ( fd , file , on , & rtc - > async_queue ) ;
}
2006-03-27 13:16:41 +04:00
static int rtc_dev_release ( struct inode * inode , struct file * file )
{
2007-10-16 12:28:17 +04:00
struct rtc_device * rtc = file - > private_data ;
2006-03-27 13:16:41 +04:00
2008-10-16 09:03:04 +04:00
/* We shut down the repeating IRQs that userspace enabled,
* since nothing is listening to them .
* - Update ( UIE ) . . . currently only managed through ioctls
* - Periodic ( PIE ) . . . also used through rtc_ * ( ) interface calls
*
* Leave the alarm alone ; it may be set to trigger a system wakeup
* later , or be used by kernel code , and is a one - shot event anyway .
*/
2009-01-04 23:00:54 +03:00
/* Keep ioctl until all drivers are converted */
2008-10-16 09:03:04 +04:00
rtc_dev_ioctl ( file , RTC_UIE_OFF , 0 ) ;
2009-01-04 23:00:54 +03:00
rtc_update_irq_enable ( rtc , 0 ) ;
2008-07-30 09:33:51 +04:00
rtc_irq_set_state ( rtc , NULL , 0 ) ;
2006-03-27 13:16:41 +04:00
if ( rtc - > ops - > release )
2007-05-08 11:33:40 +04:00
rtc - > ops - > release ( rtc - > dev . parent ) ;
2006-03-27 13:16:41 +04:00
2007-12-05 10:45:05 +03:00
clear_bit_unlock ( RTC_DEV_BUSY , & rtc - > flags ) ;
2006-03-27 13:16:41 +04:00
return 0 ;
}
2007-02-12 11:55:34 +03:00
static const struct file_operations rtc_dev_fops = {
2006-03-27 13:16:41 +04:00
. owner = THIS_MODULE ,
. llseek = no_llseek ,
. read = rtc_dev_read ,
. poll = rtc_dev_poll ,
2008-07-24 08:30:33 +04:00
. unlocked_ioctl = rtc_dev_ioctl ,
2006-03-27 13:16:41 +04:00
. open = rtc_dev_open ,
. release = rtc_dev_release ,
. fasync = rtc_dev_fasync ,
} ;
/* insertion/removal hooks */
2007-05-08 11:33:46 +04:00
void rtc_dev_prepare ( struct rtc_device * rtc )
2006-03-27 13:16:41 +04:00
{
2007-05-08 11:33:27 +04:00
if ( ! rtc_devt )
return ;
2006-03-27 13:16:41 +04:00
if ( rtc - > id > = RTC_DEV_MAX ) {
2007-05-08 11:33:27 +04:00
pr_debug ( " %s: too many RTC devices \n " , rtc - > name ) ;
return ;
2006-03-27 13:16:41 +04:00
}
2007-05-08 11:33:40 +04:00
rtc - > dev . devt = MKDEV ( MAJOR ( rtc_devt ) , rtc - > id ) ;
2007-05-08 11:33:27 +04:00
2006-06-25 16:48:17 +04:00
# ifdef CONFIG_RTC_INTF_DEV_UIE_EMUL
2006-11-22 17:57:56 +03:00
INIT_WORK ( & rtc - > uie_task , rtc_uie_task ) ;
2006-06-25 16:48:17 +04:00
setup_timer ( & rtc - > uie_timer , rtc_uie_timer , ( unsigned long ) rtc ) ;
# endif
2006-03-27 13:16:41 +04:00
cdev_init ( & rtc - > char_dev , & rtc_dev_fops ) ;
rtc - > char_dev . owner = rtc - > owner ;
2007-05-08 11:33:46 +04:00
}
2006-03-27 13:16:41 +04:00
2007-05-08 11:33:46 +04:00
void rtc_dev_add_device ( struct rtc_device * rtc )
{
2007-05-08 11:33:40 +04:00
if ( cdev_add ( & rtc - > char_dev , rtc - > dev . devt , 1 ) )
2007-05-08 11:33:27 +04:00
printk ( KERN_WARNING " %s: failed to add char device %d:%d \n " ,
rtc - > name , MAJOR ( rtc_devt ) , rtc - > id ) ;
else
pr_debug ( " %s: dev (%d:%d) \n " , rtc - > name ,
2006-03-27 13:16:41 +04:00
MAJOR ( rtc_devt ) , rtc - > id ) ;
}
2007-05-08 11:33:27 +04:00
void rtc_dev_del_device ( struct rtc_device * rtc )
2006-03-27 13:16:41 +04:00
{
2007-05-08 11:33:40 +04:00
if ( rtc - > dev . devt )
2006-03-27 13:16:41 +04:00
cdev_del ( & rtc - > char_dev ) ;
}
2007-05-08 11:33:27 +04:00
void __init rtc_dev_init ( void )
2006-03-27 13:16:41 +04:00
{
int err ;
err = alloc_chrdev_region ( & rtc_devt , 0 , RTC_DEV_MAX , " rtc " ) ;
2007-05-08 11:33:27 +04:00
if ( err < 0 )
2006-03-27 13:16:41 +04:00
printk ( KERN_ERR " %s: failed to allocate char dev region \n " ,
__FILE__ ) ;
}
2007-05-08 11:33:27 +04:00
void __exit rtc_dev_exit ( void )
2006-03-27 13:16:41 +04:00
{
2007-05-08 11:33:27 +04:00
if ( rtc_devt )
unregister_chrdev_region ( rtc_devt , RTC_DEV_MAX ) ;
2006-03-27 13:16:41 +04:00
}