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>
static struct class * rtc_dev_class ;
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
/* We keep the lock as long as the device is in use
* and return immediately if busy
*/
if ( ! ( mutex_trylock ( & rtc - > char_lock ) ) )
return - EBUSY ;
file - > private_data = & rtc - > class_dev ;
err = ops - > open ? ops - > open ( rtc - > class_dev . dev ) : 0 ;
if ( err = = 0 ) {
spin_lock_irq ( & rtc - > irq_lock ) ;
rtc - > irq_data = 0 ;
spin_unlock_irq ( & rtc - > irq_lock ) ;
return 0 ;
}
/* something has gone wrong, release the lock */
mutex_unlock ( & rtc - > char_lock ) ;
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
*/
static void rtc_uie_task ( void * data )
{
struct rtc_device * rtc = data ;
struct rtc_time tm ;
int num = 0 ;
int err ;
err = rtc_read_time ( & rtc - > class_dev , & tm ) ;
spin_lock_irq ( & rtc - > irq_lock ) ;
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 ;
}
spin_unlock_irq ( & rtc - > irq_lock ) ;
if ( num )
rtc_update_irq ( & rtc - > class_dev , num , RTC_UF | RTC_IRQF ) ;
}
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 ) ;
}
static void clear_uie ( struct rtc_device * rtc )
{
spin_lock_irq ( & rtc - > irq_lock ) ;
if ( rtc - > irq_active ) {
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 ) ;
}
rtc - > irq_active = 0 ;
}
spin_unlock_irq ( & rtc - > irq_lock ) ;
}
static int set_uie ( struct rtc_device * rtc )
{
struct rtc_time tm ;
int err ;
err = rtc_read_time ( & rtc - > class_dev , & tm ) ;
if ( err )
return err ;
spin_lock_irq ( & rtc - > irq_lock ) ;
if ( ! rtc - > irq_active ) {
rtc - > irq_active = 1 ;
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 ;
}
# 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 )
{
struct rtc_device * rtc = to_rtc_device ( file - > private_data ) ;
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 )
2006-05-01 23:16:16 +04:00
data = rtc - > ops - > read_callback ( rtc - > class_dev . dev ,
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 )
{
struct rtc_device * rtc = to_rtc_device ( file - > private_data ) ;
unsigned long data ;
poll_wait ( file , & rtc - > irq_queue , wait ) ;
data = rtc - > irq_data ;
return ( data ! = 0 ) ? ( POLLIN | POLLRDNORM ) : 0 ;
}
static int rtc_dev_ioctl ( struct inode * inode , struct file * file ,
unsigned int cmd , unsigned long arg )
{
int err = 0 ;
struct class_device * class_dev = file - > private_data ;
struct rtc_device * rtc = to_rtc_device ( class_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
struct rtc_time tm ;
struct rtc_wkalrm alarm ;
void __user * uarg = ( void __user * ) arg ;
2006-06-25 16:48:20 +04:00
/* check that the calles has appropriate permissions
* 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 ) )
return - EACCES ;
break ;
case RTC_IRQP_SET :
if ( arg > rtc - > max_user_freq & & ! capable ( CAP_SYS_RESOURCE ) )
return - EACCES ;
break ;
case RTC_PIE_ON :
if ( ! capable ( CAP_SYS_RESOURCE ) )
return - EACCES ;
break ;
}
2006-03-27 13:16:41 +04:00
/* avoid conflicting IRQ users */
if ( cmd = = RTC_PIE_ON | | cmd = = RTC_PIE_OFF | | cmd = = RTC_IRQP_SET ) {
spin_lock ( & rtc - > irq_task_lock ) ;
if ( rtc - > irq_task )
err = - EBUSY ;
spin_unlock ( & rtc - > irq_task_lock ) ;
if ( err < 0 )
return err ;
}
/* try the driver's ioctl interface */
if ( ops - > ioctl ) {
err = ops - > ioctl ( class_dev - > dev , cmd , arg ) ;
2006-05-21 02:00:29 +04:00
if ( err ! = - ENOIOCTLCMD )
2006-03-27 13:16:41 +04:00
return err ;
}
/* 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 .
2006-03-27 13:16:41 +04:00
*/
switch ( cmd ) {
case RTC_ALM_READ :
err = rtc_read_alarm ( class_dev , & alarm ) ;
if ( err < 0 )
return err ;
if ( copy_to_user ( uarg , & alarm . time , sizeof ( tm ) ) )
return - EFAULT ;
break ;
case RTC_ALM_SET :
if ( copy_from_user ( & alarm . time , uarg , sizeof ( tm ) ) )
return - EFAULT ;
alarm . enabled = 0 ;
alarm . pending = 0 ;
alarm . time . tm_mday = - 1 ;
alarm . time . tm_mon = - 1 ;
alarm . time . tm_year = - 1 ;
alarm . time . tm_wday = - 1 ;
alarm . time . tm_yday = - 1 ;
alarm . time . tm_isdst = - 1 ;
err = rtc_set_alarm ( class_dev , & alarm ) ;
break ;
case RTC_RD_TIME :
err = rtc_read_time ( class_dev , & tm ) ;
if ( err < 0 )
return err ;
if ( copy_to_user ( uarg , & tm , sizeof ( tm ) ) )
return - EFAULT ;
break ;
case RTC_SET_TIME :
if ( copy_from_user ( & tm , uarg , sizeof ( tm ) ) )
return - EFAULT ;
err = rtc_set_time ( class_dev , & tm ) ;
break ;
#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 :
if ( copy_from_user ( & alarm , uarg , sizeof ( alarm ) ) )
return - EFAULT ;
err = rtc_set_alarm ( class_dev , & alarm ) ;
break ;
case RTC_WKALM_RD :
err = rtc_read_alarm ( class_dev , & alarm ) ;
if ( err < 0 )
return err ;
if ( copy_to_user ( uarg , & alarm , sizeof ( alarm ) ) )
return - EFAULT ;
break ;
2006-06-25 16:48:17 +04:00
# ifdef CONFIG_RTC_INTF_DEV_UIE_EMUL
case RTC_UIE_OFF :
clear_uie ( rtc ) ;
return 0 ;
case RTC_UIE_ON :
return set_uie ( rtc ) ;
# endif
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 ;
}
return err ;
}
static int rtc_dev_release ( struct inode * inode , struct file * file )
{
struct rtc_device * rtc = to_rtc_device ( file - > private_data ) ;
2006-06-25 16:48:17 +04:00
# ifdef CONFIG_RTC_INTF_DEV_UIE_EMUL
clear_uie ( rtc ) ;
# endif
2006-03-27 13:16:41 +04:00
if ( rtc - > ops - > release )
rtc - > ops - > release ( rtc - > class_dev . dev ) ;
mutex_unlock ( & rtc - > char_lock ) ;
return 0 ;
}
static int rtc_dev_fasync ( int fd , struct file * file , int on )
{
struct rtc_device * rtc = to_rtc_device ( file - > private_data ) ;
return fasync_helper ( fd , file , on , & rtc - > async_queue ) ;
}
static struct file_operations rtc_dev_fops = {
. owner = THIS_MODULE ,
. llseek = no_llseek ,
. read = rtc_dev_read ,
. poll = rtc_dev_poll ,
. ioctl = rtc_dev_ioctl ,
. open = rtc_dev_open ,
. release = rtc_dev_release ,
. fasync = rtc_dev_fasync ,
} ;
/* insertion/removal hooks */
static int rtc_dev_add_device ( struct class_device * class_dev ,
struct class_interface * class_intf )
{
int err = 0 ;
struct rtc_device * rtc = to_rtc_device ( class_dev ) ;
if ( rtc - > id > = RTC_DEV_MAX ) {
dev_err ( class_dev - > dev , " too many RTCs \n " ) ;
return - EINVAL ;
}
mutex_init ( & rtc - > char_lock ) ;
spin_lock_init ( & rtc - > irq_lock ) ;
init_waitqueue_head ( & rtc - > irq_queue ) ;
2006-06-25 16:48:17 +04:00
# ifdef CONFIG_RTC_INTF_DEV_UIE_EMUL
INIT_WORK ( & rtc - > uie_task , rtc_uie_task , rtc ) ;
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 ;
if ( cdev_add ( & rtc - > char_dev , MKDEV ( MAJOR ( rtc_devt ) , rtc - > id ) , 1 ) ) {
dev_err ( class_dev - > dev ,
" failed to add char device %d:%d \n " ,
MAJOR ( rtc_devt ) , rtc - > id ) ;
return - ENODEV ;
}
rtc - > rtc_dev = class_device_create ( rtc_dev_class , NULL ,
MKDEV ( MAJOR ( rtc_devt ) , rtc - > id ) ,
class_dev - > dev , " rtc%d " , rtc - > id ) ;
if ( IS_ERR ( rtc - > rtc_dev ) ) {
dev_err ( class_dev - > dev , " cannot create rtc_dev device \n " ) ;
err = PTR_ERR ( rtc - > rtc_dev ) ;
goto err_cdev_del ;
}
dev_info ( class_dev - > dev , " rtc intf: dev (%d:%d) \n " ,
MAJOR ( rtc - > rtc_dev - > devt ) ,
MINOR ( rtc - > rtc_dev - > devt ) ) ;
return 0 ;
err_cdev_del :
cdev_del ( & rtc - > char_dev ) ;
return err ;
}
static void rtc_dev_remove_device ( struct class_device * class_dev ,
struct class_interface * class_intf )
{
struct rtc_device * rtc = to_rtc_device ( class_dev ) ;
if ( rtc - > rtc_dev ) {
dev_dbg ( class_dev - > dev , " removing char %d:%d \n " ,
MAJOR ( rtc - > rtc_dev - > devt ) ,
MINOR ( rtc - > rtc_dev - > devt ) ) ;
class_device_unregister ( rtc - > rtc_dev ) ;
cdev_del ( & rtc - > char_dev ) ;
}
}
/* interface registration */
static struct class_interface rtc_dev_interface = {
. add = & rtc_dev_add_device ,
. remove = & rtc_dev_remove_device ,
} ;
static int __init rtc_dev_init ( void )
{
int err ;
rtc_dev_class = class_create ( THIS_MODULE , " rtc-dev " ) ;
if ( IS_ERR ( rtc_dev_class ) )
return PTR_ERR ( rtc_dev_class ) ;
err = alloc_chrdev_region ( & rtc_devt , 0 , RTC_DEV_MAX , " rtc " ) ;
if ( err < 0 ) {
printk ( KERN_ERR " %s: failed to allocate char dev region \n " ,
__FILE__ ) ;
goto err_destroy_class ;
}
err = rtc_interface_register ( & rtc_dev_interface ) ;
if ( err < 0 ) {
printk ( KERN_ERR " %s: failed to register the interface \n " ,
__FILE__ ) ;
goto err_unregister_chrdev ;
}
return 0 ;
err_unregister_chrdev :
unregister_chrdev_region ( rtc_devt , RTC_DEV_MAX ) ;
err_destroy_class :
class_destroy ( rtc_dev_class ) ;
return err ;
}
static void __exit rtc_dev_exit ( void )
{
class_interface_unregister ( & rtc_dev_interface ) ;
class_destroy ( rtc_dev_class ) ;
unregister_chrdev_region ( rtc_devt , RTC_DEV_MAX ) ;
}
2006-10-01 10:28:15 +04:00
subsys_initcall ( rtc_dev_init ) ;
2006-03-27 13:16:41 +04:00
module_exit ( rtc_dev_exit ) ;
MODULE_AUTHOR ( " Alessandro Zummo <a.zummo@towertech.it> " ) ;
MODULE_DESCRIPTION ( " RTC class dev interface " ) ;
MODULE_LICENSE ( " GPL " ) ;