2006-03-27 13:16:37 +04:00
/*
* RTC subsystem , base class
*
* Copyright ( C ) 2005 Tower Technologies
* Author : Alessandro Zummo < a . zummo @ towertech . it >
*
* class skeleton from drivers / hwmon / hwmon . 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>
# include <linux/kdev_t.h>
# include <linux/idr.h>
2007-05-08 11:33:27 +04:00
# include "rtc-core.h"
2006-03-27 13:16:37 +04:00
static DEFINE_IDR ( rtc_idr ) ;
static DEFINE_MUTEX ( idr_lock ) ;
struct class * rtc_class ;
2007-05-08 11:33:40 +04:00
static void rtc_device_release ( struct device * dev )
2006-03-27 13:16:37 +04:00
{
2007-05-08 11:33:40 +04:00
struct rtc_device * rtc = to_rtc_device ( dev ) ;
2006-03-27 13:16:37 +04:00
mutex_lock ( & idr_lock ) ;
idr_remove ( & rtc_idr , rtc - > id ) ;
mutex_unlock ( & idr_lock ) ;
kfree ( rtc ) ;
}
2007-05-08 11:33:42 +04:00
# if defined(CONFIG_PM) && defined(CONFIG_RTC_HCTOSYS_DEVICE)
/*
* On suspend ( ) , measure the delta between one RTC and the
* system ' s wall clock ; restore it on resume ( ) .
*/
static struct timespec delta ;
static time_t oldtime ;
static int rtc_suspend ( struct device * dev , pm_message_t mesg )
{
struct rtc_device * rtc = to_rtc_device ( dev ) ;
struct rtc_time tm ;
2007-07-25 04:47:43 +04:00
struct timespec ts = current_kernel_time ( ) ;
2007-05-08 11:33:42 +04:00
if ( strncmp ( rtc - > dev . bus_id ,
CONFIG_RTC_HCTOSYS_DEVICE ,
BUS_ID_SIZE ) ! = 0 )
return 0 ;
rtc_read_time ( rtc , & tm ) ;
rtc_tm_to_time ( & tm , & oldtime ) ;
/* RTC precision is 1 second; adjust delta for avg 1/2 sec err */
set_normalized_timespec ( & delta ,
2007-07-25 04:47:43 +04:00
ts . tv_sec - oldtime ,
ts . tv_nsec - ( NSEC_PER_SEC > > 1 ) ) ;
2007-05-08 11:33:42 +04:00
return 0 ;
}
static int rtc_resume ( struct device * dev )
{
struct rtc_device * rtc = to_rtc_device ( dev ) ;
struct rtc_time tm ;
time_t newtime ;
struct timespec time ;
if ( strncmp ( rtc - > dev . bus_id ,
CONFIG_RTC_HCTOSYS_DEVICE ,
BUS_ID_SIZE ) ! = 0 )
return 0 ;
rtc_read_time ( rtc , & tm ) ;
if ( rtc_valid_tm ( & tm ) ! = 0 ) {
pr_debug ( " %s: bogus resume time \n " , rtc - > dev . bus_id ) ;
return 0 ;
}
rtc_tm_to_time ( & tm , & newtime ) ;
if ( newtime < = oldtime ) {
if ( newtime < oldtime )
pr_debug ( " %s: time travel! \n " , rtc - > dev . bus_id ) ;
return 0 ;
}
/* restore wall clock using delta against this RTC;
* adjust again for avg 1 / 2 second RTC sampling error
*/
set_normalized_timespec ( & time ,
newtime + delta . tv_sec ,
( NSEC_PER_SEC > > 1 ) + delta . tv_nsec ) ;
do_settimeofday ( & time ) ;
return 0 ;
}
# else
# define rtc_suspend NULL
# define rtc_resume NULL
# endif
2006-03-27 13:16:37 +04:00
/**
* rtc_device_register - register w / RTC class
* @ dev : the device to register
*
* rtc_device_unregister ( ) must be called when the class device is no
* longer needed .
*
* Returns the pointer to the new struct class device .
*/
struct rtc_device * rtc_device_register ( const char * name , struct device * dev ,
2006-10-01 10:28:17 +04:00
const struct rtc_class_ops * ops ,
2006-03-27 13:16:37 +04:00
struct module * owner )
{
struct rtc_device * rtc ;
int id , err ;
if ( idr_pre_get ( & rtc_idr , GFP_KERNEL ) = = 0 ) {
err = - ENOMEM ;
goto exit ;
}
mutex_lock ( & idr_lock ) ;
err = idr_get_new ( & rtc_idr , NULL , & id ) ;
mutex_unlock ( & idr_lock ) ;
if ( err < 0 )
goto exit ;
id = id & MAX_ID_MASK ;
rtc = kzalloc ( sizeof ( struct rtc_device ) , GFP_KERNEL ) ;
if ( rtc = = NULL ) {
err = - ENOMEM ;
goto exit_idr ;
}
rtc - > id = id ;
rtc - > ops = ops ;
rtc - > owner = owner ;
2006-06-25 16:48:20 +04:00
rtc - > max_user_freq = 64 ;
2007-05-08 11:33:40 +04:00
rtc - > dev . parent = dev ;
rtc - > dev . class = rtc_class ;
rtc - > dev . release = rtc_device_release ;
2006-03-27 13:16:37 +04:00
mutex_init ( & rtc - > ops_lock ) ;
spin_lock_init ( & rtc - > irq_lock ) ;
spin_lock_init ( & rtc - > irq_task_lock ) ;
2007-10-16 12:28:15 +04:00
init_waitqueue_head ( & rtc - > irq_queue ) ;
2006-03-27 13:16:37 +04:00
strlcpy ( rtc - > name , name , RTC_DEVICE_NAME_SIZE ) ;
2007-05-08 11:33:40 +04:00
snprintf ( rtc - > dev . bus_id , BUS_ID_SIZE , " rtc%d " , id ) ;
2006-03-27 13:16:37 +04:00
2007-05-08 11:33:46 +04:00
rtc_dev_prepare ( rtc ) ;
2007-05-08 11:33:40 +04:00
err = device_register ( & rtc - > dev ) ;
2006-03-27 13:16:37 +04:00
if ( err )
goto exit_kfree ;
2007-05-08 11:33:27 +04:00
rtc_dev_add_device ( rtc ) ;
2007-05-08 11:33:33 +04:00
rtc_sysfs_add_device ( rtc ) ;
2007-05-08 11:33:38 +04:00
rtc_proc_add_device ( rtc ) ;
2007-05-08 11:33:27 +04:00
2006-03-27 13:16:37 +04:00
dev_info ( dev , " rtc core: registered %s as %s \n " ,
2007-05-08 11:33:40 +04:00
rtc - > name , rtc - > dev . bus_id ) ;
2006-03-27 13:16:37 +04:00
return rtc ;
exit_kfree :
kfree ( rtc ) ;
exit_idr :
2006-06-27 13:54:06 +04:00
mutex_lock ( & idr_lock ) ;
2006-03-27 13:16:37 +04:00
idr_remove ( & rtc_idr , id ) ;
2006-06-27 13:54:06 +04:00
mutex_unlock ( & idr_lock ) ;
2006-03-27 13:16:37 +04:00
exit :
2006-04-11 09:54:45 +04:00
dev_err ( dev , " rtc core: unable to register %s, err = %d \n " ,
name , err ) ;
2006-03-27 13:16:37 +04:00
return ERR_PTR ( err ) ;
}
EXPORT_SYMBOL_GPL ( rtc_device_register ) ;
/**
* rtc_device_unregister - removes the previously registered RTC class device
*
* @ rtc : the RTC class device to destroy
*/
void rtc_device_unregister ( struct rtc_device * rtc )
{
2007-05-08 11:33:40 +04:00
if ( get_device ( & rtc - > dev ) ! = NULL ) {
2007-03-01 07:12:40 +03:00
mutex_lock ( & rtc - > ops_lock ) ;
/* remove innards of this RTC, then disable it, before
* letting any rtc_class_open ( ) users access it again
*/
2007-05-08 11:33:33 +04:00
rtc_sysfs_del_device ( rtc ) ;
2007-05-08 11:33:27 +04:00
rtc_dev_del_device ( rtc ) ;
2007-05-08 11:33:38 +04:00
rtc_proc_del_device ( rtc ) ;
2007-05-08 11:33:40 +04:00
device_unregister ( & rtc - > dev ) ;
2007-03-01 07:12:40 +03:00
rtc - > ops = NULL ;
mutex_unlock ( & rtc - > ops_lock ) ;
2007-05-08 11:33:40 +04:00
put_device ( & rtc - > dev ) ;
2007-03-01 07:12:40 +03:00
}
2006-03-27 13:16:37 +04:00
}
EXPORT_SYMBOL_GPL ( rtc_device_unregister ) ;
static int __init rtc_init ( void )
{
rtc_class = class_create ( THIS_MODULE , " rtc " ) ;
if ( IS_ERR ( rtc_class ) ) {
printk ( KERN_ERR " %s: couldn't create class \n " , __FILE__ ) ;
return PTR_ERR ( rtc_class ) ;
}
2007-05-08 11:33:42 +04:00
rtc_class - > suspend = rtc_suspend ;
rtc_class - > resume = rtc_resume ;
2007-05-08 11:33:27 +04:00
rtc_dev_init ( ) ;
2007-05-08 11:33:33 +04:00
rtc_sysfs_init ( rtc_class ) ;
2006-03-27 13:16:37 +04:00
return 0 ;
}
static void __exit rtc_exit ( void )
{
2007-05-08 11:33:27 +04:00
rtc_dev_exit ( ) ;
2006-03-27 13:16:37 +04:00
class_destroy ( rtc_class ) ;
}
2006-10-01 10:28:15 +04:00
subsys_initcall ( rtc_init ) ;
2006-03-27 13:16:37 +04:00
module_exit ( rtc_exit ) ;
2006-10-01 10:28:15 +04:00
MODULE_AUTHOR ( " Alessandro Zummo <a.zummo@towertech.it> " ) ;
2006-03-27 13:16:37 +04:00
MODULE_DESCRIPTION ( " RTC class support " ) ;
MODULE_LICENSE ( " GPL " ) ;