2009-04-01 02:24:48 +04:00
/*
* rtc - efi : RTC Class Driver for EFI - based systems
*
* Copyright ( C ) 2009 Hewlett - Packard Development Company , L . P .
*
2015-04-24 20:54:20 +03:00
* Author : dann frazier < dannf @ dannf . org >
2009-04-01 02:24:48 +04:00
* Based on efirtc . c by Stephane Eranian
*
* This program is free software ; you can redistribute it and / or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation ; either version 2 of the License , or ( at your
* option ) any later version .
*
*/
2013-02-22 04:45:25 +04:00
# define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
2009-04-01 02:24:48 +04:00
# include <linux/kernel.h>
# include <linux/module.h>
2014-08-09 01:20:09 +04:00
# include <linux/stringify.h>
2009-04-01 02:24:48 +04:00
# include <linux/time.h>
# include <linux/platform_device.h>
# include <linux/rtc.h>
# include <linux/efi.h>
# define EFI_ISDST (EFI_TIME_ADJUST_DAYLIGHT|EFI_TIME_IN_DAYLIGHT)
/*
* returns day of the year [ 0 - 365 ]
*/
static inline int
compute_yday ( efi_time_t * eft )
{
/* efi_time_t.month is in the [1-12] so, we need -1 */
2014-06-07 01:35:48 +04:00
return rtc_year_days ( eft - > day , eft - > month - 1 , eft - > year ) ;
2009-04-01 02:24:48 +04:00
}
2015-06-09 12:15:35 +03:00
2009-04-01 02:24:48 +04:00
/*
* returns day of the week [ 0 - 6 ] 0 = Sunday
*/
static int
2015-06-09 12:15:35 +03:00
compute_wday ( efi_time_t * eft , int yday )
2009-04-01 02:24:48 +04:00
{
2015-06-09 12:15:35 +03:00
int ndays = eft - > year * ( 365 % 7 )
+ ( eft - > year - 1 ) / 4
- ( eft - > year - 1 ) / 100
+ ( eft - > year - 1 ) / 400
+ yday ;
2009-04-01 02:24:48 +04:00
/*
2015-06-09 12:15:35 +03:00
* 1 / 1 / 0000 may or may not have been a Sunday ( if it ever existed at
* all ) but assuming it was makes this calculation work correctly .
2009-04-01 02:24:48 +04:00
*/
2015-06-09 12:15:35 +03:00
return ndays % 7 ;
2009-04-01 02:24:48 +04:00
}
static void
convert_to_efi_time ( struct rtc_time * wtime , efi_time_t * eft )
{
eft - > year = wtime - > tm_year + 1900 ;
eft - > month = wtime - > tm_mon + 1 ;
eft - > day = wtime - > tm_mday ;
eft - > hour = wtime - > tm_hour ;
eft - > minute = wtime - > tm_min ;
2013-02-22 04:45:25 +04:00
eft - > second = wtime - > tm_sec ;
2009-04-01 02:24:48 +04:00
eft - > nanosecond = 0 ;
eft - > daylight = wtime - > tm_isdst ? EFI_ISDST : 0 ;
eft - > timezone = EFI_UNSPECIFIED_TIMEZONE ;
}
2014-08-09 01:20:09 +04:00
static bool
2009-04-01 02:24:48 +04:00
convert_from_efi_time ( efi_time_t * eft , struct rtc_time * wtime )
{
memset ( wtime , 0 , sizeof ( * wtime ) ) ;
2014-08-09 01:20:09 +04:00
if ( eft - > second > = 60 )
return false ;
2009-04-01 02:24:48 +04:00
wtime - > tm_sec = eft - > second ;
2014-08-09 01:20:09 +04:00
if ( eft - > minute > = 60 )
return false ;
2009-04-01 02:24:48 +04:00
wtime - > tm_min = eft - > minute ;
2014-08-09 01:20:09 +04:00
if ( eft - > hour > = 24 )
return false ;
2009-04-01 02:24:48 +04:00
wtime - > tm_hour = eft - > hour ;
2014-08-09 01:20:09 +04:00
if ( ! eft - > day | | eft - > day > 31 )
return false ;
2009-04-01 02:24:48 +04:00
wtime - > tm_mday = eft - > day ;
2014-08-09 01:20:09 +04:00
if ( ! eft - > month | | eft - > month > 12 )
return false ;
2009-04-01 02:24:48 +04:00
wtime - > tm_mon = eft - > month - 1 ;
2015-06-09 12:15:35 +03:00
if ( eft - > year < 1900 | | eft - > year > 9999 )
2014-08-09 01:20:09 +04:00
return false ;
2015-06-09 12:15:35 +03:00
wtime - > tm_year = eft - > year - 1900 ;
2009-04-01 02:24:48 +04:00
/* day in the year [1-365]*/
wtime - > tm_yday = compute_yday ( eft ) ;
2015-06-09 12:15:35 +03:00
/* day of the week [0-6], Sunday=0 */
wtime - > tm_wday = compute_wday ( eft , wtime - > tm_yday ) ;
2009-04-01 02:24:48 +04:00
switch ( eft - > daylight & EFI_ISDST ) {
case EFI_ISDST :
wtime - > tm_isdst = 1 ;
break ;
case EFI_TIME_ADJUST_DAYLIGHT :
wtime - > tm_isdst = 0 ;
break ;
default :
wtime - > tm_isdst = - 1 ;
}
2014-08-09 01:20:09 +04:00
return true ;
2009-04-01 02:24:48 +04:00
}
static int efi_read_alarm ( struct device * dev , struct rtc_wkalrm * wkalrm )
{
efi_time_t eft ;
efi_status_t status ;
/*
* As of EFI v1 .10 , this call always returns an unsupported status
*/
status = efi . get_wakeup_time ( ( efi_bool_t * ) & wkalrm - > enabled ,
( efi_bool_t * ) & wkalrm - > pending , & eft ) ;
if ( status ! = EFI_SUCCESS )
return - EINVAL ;
2014-08-09 01:20:09 +04:00
if ( ! convert_from_efi_time ( & eft , & wkalrm - > time ) )
return - EIO ;
2009-04-01 02:24:48 +04:00
return rtc_valid_tm ( & wkalrm - > time ) ;
}
static int efi_set_alarm ( struct device * dev , struct rtc_wkalrm * wkalrm )
{
efi_time_t eft ;
efi_status_t status ;
convert_to_efi_time ( & wkalrm - > time , & eft ) ;
/*
* XXX Fixme :
* As of EFI 0.92 with the firmware I have on my
* machine this call does not seem to work quite
* right
*
* As of v1 .10 , this call always returns an unsupported status
*/
status = efi . set_wakeup_time ( ( efi_bool_t ) wkalrm - > enabled , & eft ) ;
2013-02-22 04:45:25 +04:00
dev_warn ( dev , " write status is %d \n " , ( int ) status ) ;
2009-04-01 02:24:48 +04:00
return status = = EFI_SUCCESS ? 0 : - EINVAL ;
}
static int efi_read_time ( struct device * dev , struct rtc_time * tm )
{
efi_status_t status ;
efi_time_t eft ;
efi_time_cap_t cap ;
status = efi . get_time ( & eft , & cap ) ;
if ( status ! = EFI_SUCCESS ) {
/* should never happen */
2013-02-22 04:45:25 +04:00
dev_err ( dev , " can't read time \n " ) ;
2009-04-01 02:24:48 +04:00
return - EINVAL ;
}
2014-08-09 01:20:09 +04:00
if ( ! convert_from_efi_time ( & eft , tm ) )
return - EIO ;
2009-04-01 02:24:48 +04:00
return rtc_valid_tm ( tm ) ;
}
static int efi_set_time ( struct device * dev , struct rtc_time * tm )
{
efi_status_t status ;
efi_time_t eft ;
convert_to_efi_time ( tm , & eft ) ;
status = efi . set_time ( & eft ) ;
return status = = EFI_SUCCESS ? 0 : - EINVAL ;
}
2015-11-07 07:00:22 +03:00
static int efi_procfs ( struct device * dev , struct seq_file * seq )
{
efi_time_t eft , alm ;
efi_time_cap_t cap ;
efi_bool_t enabled , pending ;
memset ( & eft , 0 , sizeof ( eft ) ) ;
memset ( & alm , 0 , sizeof ( alm ) ) ;
memset ( & cap , 0 , sizeof ( cap ) ) ;
efi . get_time ( & eft , & cap ) ;
efi . get_wakeup_time ( & enabled , & pending , & alm ) ;
seq_printf ( seq ,
" Time \t \t : %u:%u:%u.%09u \n "
" Date \t \t : %u-%u-%u \n "
" Daylight \t : %u \n " ,
eft . hour , eft . minute , eft . second , eft . nanosecond ,
eft . year , eft . month , eft . day ,
eft . daylight ) ;
if ( eft . timezone = = EFI_UNSPECIFIED_TIMEZONE )
seq_puts ( seq , " Timezone \t : unspecified \n " ) ;
else
/* XXX fixme: convert to string? */
seq_printf ( seq , " Timezone \t : %u \n " , eft . timezone ) ;
seq_printf ( seq ,
" Alarm Time \t : %u:%u:%u.%09u \n "
" Alarm Date \t : %u-%u-%u \n "
" Alarm Daylight \t : %u \n "
" Enabled \t \t : %s \n "
" Pending \t \t : %s \n " ,
alm . hour , alm . minute , alm . second , alm . nanosecond ,
alm . year , alm . month , alm . day ,
alm . daylight ,
enabled = = 1 ? " yes " : " no " ,
pending = = 1 ? " yes " : " no " ) ;
if ( eft . timezone = = EFI_UNSPECIFIED_TIMEZONE )
seq_puts ( seq , " Timezone \t : unspecified \n " ) ;
else
/* XXX fixme: convert to string? */
seq_printf ( seq , " Timezone \t : %u \n " , alm . timezone ) ;
/*
* now prints the capabilities
*/
seq_printf ( seq ,
" Resolution \t : %u \n "
" Accuracy \t : %u \n "
" SetstoZero \t : %u \n " ,
cap . resolution , cap . accuracy , cap . sets_to_zero ) ;
return 0 ;
}
2009-04-01 02:24:48 +04:00
static const struct rtc_class_ops efi_rtc_ops = {
2015-11-07 07:00:22 +03:00
. read_time = efi_read_time ,
. set_time = efi_set_time ,
. read_alarm = efi_read_alarm ,
. set_alarm = efi_set_alarm ,
. proc = efi_procfs ,
2009-04-01 02:24:48 +04:00
} ;
static int __init efi_rtc_probe ( struct platform_device * dev )
{
struct rtc_device * rtc ;
2016-06-05 12:35:56 +03:00
efi_time_t eft ;
efi_time_cap_t cap ;
/* First check if the RTC is usable */
if ( efi . get_time ( & eft , & cap ) ! = EFI_SUCCESS )
return - ENODEV ;
2009-04-01 02:24:48 +04:00
2013-04-30 03:19:36 +04:00
rtc = devm_rtc_device_register ( & dev - > dev , " rtc-efi " , & efi_rtc_ops ,
2009-04-01 02:24:48 +04:00
THIS_MODULE ) ;
if ( IS_ERR ( rtc ) )
return PTR_ERR ( rtc ) ;
2015-01-13 15:28:53 +03:00
rtc - > uie_unsupported = 1 ;
2009-04-01 02:24:48 +04:00
platform_set_drvdata ( dev , rtc ) ;
return 0 ;
}
static struct platform_driver efi_rtc_driver = {
. driver = {
. name = " rtc-efi " ,
} ,
} ;
2013-04-30 03:18:40 +04:00
module_platform_driver_probe ( efi_rtc_driver , efi_rtc_probe ) ;
2009-04-01 02:24:48 +04:00
2014-09-26 03:05:22 +04:00
MODULE_ALIAS ( " platform:rtc-efi " ) ;
2015-04-24 20:54:20 +03:00
MODULE_AUTHOR ( " dann frazier <dannf@dannf.org> " ) ;
2009-04-01 02:24:48 +04:00
MODULE_LICENSE ( " GPL " ) ;
MODULE_DESCRIPTION ( " EFI RTC driver " ) ;
2014-09-11 12:20:40 +04:00
MODULE_ALIAS ( " platform:rtc-efi " ) ;