2010-11-10 20:29:17 +03:00
/*
* rtc - mrst . c : Driver for Moorestown virtual RTC
*
* ( C ) Copyright 2009 Intel Corporation
* Author : Jacob Pan ( jacob . jun . pan @ intel . com )
* Feng Tang ( feng . tang @ intel . com )
*
* 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 ; version 2
* of the License .
*
* Note :
* VRTC is emulated by system controller firmware , the real HW
* RTC is located in the PMIC device . SCU FW shadows PMIC RTC
* in a memory mapped IO space that is visible to the host IA
* processor .
*
* This driver is based upon drivers / rtc / rtc - cmos . c
*/
/*
* Note :
* * vRTC only supports binary mode and 24 H mode
* * vRTC only support PIE and AIE , no UIE , and its PIE only happens
* at 23 : 59 : 59 pm everyday , no support for adjustable frequency
* * Alarm function is also limited to hr / min / sec .
*/
# include <linux/mod_devicetable.h>
# include <linux/platform_device.h>
# include <linux/interrupt.h>
# include <linux/spinlock.h>
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/init.h>
# include <linux/sfi.h>
# include <asm-generic/rtc.h>
# include <asm/intel_scu_ipc.h>
2013-10-18 02:35:27 +04:00
# include <asm/intel-mid.h>
# include <asm/intel_mid_vrtc.h>
2010-11-10 20:29:17 +03:00
struct mrst_rtc {
struct rtc_device * rtc ;
struct device * dev ;
int irq ;
struct resource * iomem ;
u8 enabled_wake ;
u8 suspend_ctrl ;
} ;
static const char driver_name [ ] = " rtc_mrst " ;
# define RTC_IRQMASK (RTC_PF | RTC_AF)
static inline int is_intr ( u8 rtc_intr )
{
if ( ! ( rtc_intr & RTC_IRQF ) )
return 0 ;
return rtc_intr & RTC_IRQMASK ;
}
2011-02-14 19:13:32 +03:00
static inline unsigned char vrtc_is_updating ( void )
{
unsigned char uip ;
unsigned long flags ;
spin_lock_irqsave ( & rtc_lock , flags ) ;
uip = ( vrtc_cmos_read ( RTC_FREQ_SELECT ) & RTC_UIP ) ;
spin_unlock_irqrestore ( & rtc_lock , flags ) ;
return uip ;
}
2010-11-10 20:29:17 +03:00
/*
* rtc_time ' s year contains the increment over 1900 , but vRTC ' s YEAR
* register can ' t be programmed to value larger than 0x64 , so vRTC
2011-11-10 17:23:39 +04:00
* driver chose to use 1972 ( 1970 is UNIX time start point ) as the base ,
2010-11-17 15:11:24 +03:00
* and does the translation at read / write time .
*
2011-11-10 17:23:39 +04:00
* Why not just use 1970 as the offset ? it ' s because using 1972 will
2010-11-17 15:11:24 +03:00
* make it consistent in leap year setting for both vrtc and low - level
2011-11-10 17:23:39 +04:00
* physical rtc devices . Then why not use 1960 as the offset ? If we use
* 1960 , for a device ' s first use , its YEAR register is 0 and the system
* year will be parsed as 1960 which is not a valid UNIX time and will
* cause many applications to fail mysteriously .
2010-11-10 20:29:17 +03:00
*/
static int mrst_read_time ( struct device * dev , struct rtc_time * time )
{
unsigned long flags ;
2011-02-14 19:13:32 +03:00
if ( vrtc_is_updating ( ) )
2010-11-10 20:29:17 +03:00
mdelay ( 20 ) ;
spin_lock_irqsave ( & rtc_lock , flags ) ;
time - > tm_sec = vrtc_cmos_read ( RTC_SECONDS ) ;
time - > tm_min = vrtc_cmos_read ( RTC_MINUTES ) ;
time - > tm_hour = vrtc_cmos_read ( RTC_HOURS ) ;
time - > tm_mday = vrtc_cmos_read ( RTC_DAY_OF_MONTH ) ;
time - > tm_mon = vrtc_cmos_read ( RTC_MONTH ) ;
time - > tm_year = vrtc_cmos_read ( RTC_YEAR ) ;
spin_unlock_irqrestore ( & rtc_lock , flags ) ;
2011-11-10 17:23:39 +04:00
/* Adjust for the 1972/1900 */
time - > tm_year + = 72 ;
2010-11-10 20:29:17 +03:00
time - > tm_mon - - ;
2011-11-10 17:23:39 +04:00
return rtc_valid_tm ( time ) ;
2010-11-10 20:29:17 +03:00
}
static int mrst_set_time ( struct device * dev , struct rtc_time * time )
{
int ret ;
unsigned long flags ;
unsigned char mon , day , hrs , min , sec ;
unsigned int yrs ;
yrs = time - > tm_year ;
mon = time - > tm_mon + 1 ; /* tm_mon starts at zero */
day = time - > tm_mday ;
hrs = time - > tm_hour ;
min = time - > tm_min ;
sec = time - > tm_sec ;
2011-11-10 17:23:39 +04:00
if ( yrs < 72 | | yrs > 138 )
2010-11-10 20:29:17 +03:00
return - EINVAL ;
2011-11-10 17:23:39 +04:00
yrs - = 72 ;
2010-11-10 20:29:17 +03:00
spin_lock_irqsave ( & rtc_lock , flags ) ;
vrtc_cmos_write ( yrs , RTC_YEAR ) ;
vrtc_cmos_write ( mon , RTC_MONTH ) ;
vrtc_cmos_write ( day , RTC_DAY_OF_MONTH ) ;
vrtc_cmos_write ( hrs , RTC_HOURS ) ;
vrtc_cmos_write ( min , RTC_MINUTES ) ;
vrtc_cmos_write ( sec , RTC_SECONDS ) ;
spin_unlock_irqrestore ( & rtc_lock , flags ) ;
ret = intel_scu_ipc_simple_command ( IPCMSG_VRTC , IPC_CMD_VRTC_SETTIME ) ;
return ret ;
}
static int mrst_read_alarm ( struct device * dev , struct rtc_wkalrm * t )
{
struct mrst_rtc * mrst = dev_get_drvdata ( dev ) ;
unsigned char rtc_control ;
if ( mrst - > irq < = 0 )
return - EIO ;
/* Basic alarms only support hour, minute, and seconds fields.
* Some also support day and month , for alarms up to a year in
* the future .
*/
t - > time . tm_mday = - 1 ;
t - > time . tm_mon = - 1 ;
t - > time . tm_year = - 1 ;
/* vRTC only supports binary mode */
spin_lock_irq ( & rtc_lock ) ;
t - > time . tm_sec = vrtc_cmos_read ( RTC_SECONDS_ALARM ) ;
t - > time . tm_min = vrtc_cmos_read ( RTC_MINUTES_ALARM ) ;
t - > time . tm_hour = vrtc_cmos_read ( RTC_HOURS_ALARM ) ;
rtc_control = vrtc_cmos_read ( RTC_CONTROL ) ;
spin_unlock_irq ( & rtc_lock ) ;
t - > enabled = ! ! ( rtc_control & RTC_AIE ) ;
t - > pending = 0 ;
return 0 ;
}
static void mrst_checkintr ( struct mrst_rtc * mrst , unsigned char rtc_control )
{
unsigned char rtc_intr ;
/*
* NOTE after changing RTC_xIE bits we always read INTR_FLAGS ;
* allegedly some older rtcs need that to handle irqs properly
*/
rtc_intr = vrtc_cmos_read ( RTC_INTR_FLAGS ) ;
rtc_intr & = ( rtc_control & RTC_IRQMASK ) | RTC_IRQF ;
if ( is_intr ( rtc_intr ) )
rtc_update_irq ( mrst - > rtc , 1 , rtc_intr ) ;
}
static void mrst_irq_enable ( struct mrst_rtc * mrst , unsigned char mask )
{
unsigned char rtc_control ;
/*
* Flush any pending IRQ status , notably for update irqs ,
* before we enable new IRQs
*/
rtc_control = vrtc_cmos_read ( RTC_CONTROL ) ;
mrst_checkintr ( mrst , rtc_control ) ;
rtc_control | = mask ;
vrtc_cmos_write ( rtc_control , RTC_CONTROL ) ;
mrst_checkintr ( mrst , rtc_control ) ;
}
static void mrst_irq_disable ( struct mrst_rtc * mrst , unsigned char mask )
{
unsigned char rtc_control ;
rtc_control = vrtc_cmos_read ( RTC_CONTROL ) ;
rtc_control & = ~ mask ;
vrtc_cmos_write ( rtc_control , RTC_CONTROL ) ;
mrst_checkintr ( mrst , rtc_control ) ;
}
static int mrst_set_alarm ( struct device * dev , struct rtc_wkalrm * t )
{
struct mrst_rtc * mrst = dev_get_drvdata ( dev ) ;
unsigned char hrs , min , sec ;
int ret = 0 ;
if ( ! mrst - > irq )
return - EIO ;
hrs = t - > time . tm_hour ;
min = t - > time . tm_min ;
sec = t - > time . tm_sec ;
spin_lock_irq ( & rtc_lock ) ;
/* Next rtc irq must not be from previous alarm setting */
mrst_irq_disable ( mrst , RTC_AIE ) ;
/* Update alarm */
vrtc_cmos_write ( hrs , RTC_HOURS_ALARM ) ;
vrtc_cmos_write ( min , RTC_MINUTES_ALARM ) ;
vrtc_cmos_write ( sec , RTC_SECONDS_ALARM ) ;
spin_unlock_irq ( & rtc_lock ) ;
ret = intel_scu_ipc_simple_command ( IPCMSG_VRTC , IPC_CMD_VRTC_SETALARM ) ;
if ( ret )
return ret ;
spin_lock_irq ( & rtc_lock ) ;
if ( t - > enabled )
mrst_irq_enable ( mrst , RTC_AIE ) ;
spin_unlock_irq ( & rtc_lock ) ;
return 0 ;
}
/* Currently, the vRTC doesn't support UIE ON/OFF */
2011-02-03 04:02:41 +03:00
static int mrst_rtc_alarm_irq_enable ( struct device * dev , unsigned int enabled )
2010-11-10 20:29:17 +03:00
{
struct mrst_rtc * mrst = dev_get_drvdata ( dev ) ;
unsigned long flags ;
spin_lock_irqsave ( & rtc_lock , flags ) ;
2011-02-03 04:02:41 +03:00
if ( enabled )
2010-11-10 20:29:17 +03:00
mrst_irq_enable ( mrst , RTC_AIE ) ;
2011-02-03 04:02:41 +03:00
else
mrst_irq_disable ( mrst , RTC_AIE ) ;
2010-11-10 20:29:17 +03:00
spin_unlock_irqrestore ( & rtc_lock , flags ) ;
return 0 ;
}
# if defined(CONFIG_RTC_INTF_PROC) || defined(CONFIG_RTC_INTF_PROC_MODULE)
static int mrst_procfs ( struct device * dev , struct seq_file * seq )
{
unsigned char rtc_control , valid ;
spin_lock_irq ( & rtc_lock ) ;
rtc_control = vrtc_cmos_read ( RTC_CONTROL ) ;
valid = vrtc_cmos_read ( RTC_VALID ) ;
spin_unlock_irq ( & rtc_lock ) ;
2015-04-16 02:17:51 +03:00
seq_printf ( seq ,
" periodic_IRQ \t : %s \n "
" alarm \t \t : %s \n "
" BCD \t \t : no \n "
" periodic_freq \t : daily (not adjustable) \n " ,
( rtc_control & RTC_PIE ) ? " on " : " off " ,
( rtc_control & RTC_AIE ) ? " on " : " off " ) ;
return 0 ;
2010-11-10 20:29:17 +03:00
}
# else
# define mrst_procfs NULL
# endif
static const struct rtc_class_ops mrst_rtc_ops = {
. read_time = mrst_read_time ,
. set_time = mrst_set_time ,
. read_alarm = mrst_read_alarm ,
. set_alarm = mrst_set_alarm ,
. proc = mrst_procfs ,
2011-02-03 04:02:41 +03:00
. alarm_irq_enable = mrst_rtc_alarm_irq_enable ,
2010-11-10 20:29:17 +03:00
} ;
static struct mrst_rtc mrst_rtc ;
/*
* When vRTC IRQ is captured by SCU FW , FW will clear the AIE bit in
* Reg B , so no need for this driver to clear it
*/
static irqreturn_t mrst_rtc_irq ( int irq , void * p )
{
u8 irqstat ;
spin_lock ( & rtc_lock ) ;
/* This read will clear all IRQ flags inside Reg C */
irqstat = vrtc_cmos_read ( RTC_INTR_FLAGS ) ;
spin_unlock ( & rtc_lock ) ;
irqstat & = RTC_IRQMASK | RTC_IRQF ;
if ( is_intr ( irqstat ) ) {
rtc_update_irq ( p , 1 , irqstat ) ;
return IRQ_HANDLED ;
}
return IRQ_NONE ;
}
2012-12-22 01:09:38 +04:00
static int vrtc_mrst_do_probe ( struct device * dev , struct resource * iomem ,
int rtc_irq )
2010-11-10 20:29:17 +03:00
{
int retval = 0 ;
unsigned char rtc_control ;
/* There can be only one ... */
if ( mrst_rtc . dev )
return - EBUSY ;
if ( ! iomem )
return - ENODEV ;
2011-06-09 20:13:32 +04:00
iomem = request_mem_region ( iomem - > start , resource_size ( iomem ) ,
driver_name ) ;
2010-11-10 20:29:17 +03:00
if ( ! iomem ) {
dev_dbg ( dev , " i/o mem already in use. \n " ) ;
return - EBUSY ;
}
mrst_rtc . irq = rtc_irq ;
mrst_rtc . iomem = iomem ;
2011-04-07 05:39:44 +04:00
mrst_rtc . dev = dev ;
dev_set_drvdata ( dev , & mrst_rtc ) ;
2010-11-10 20:29:17 +03:00
mrst_rtc . rtc = rtc_device_register ( driver_name , dev ,
& mrst_rtc_ops , THIS_MODULE ) ;
if ( IS_ERR ( mrst_rtc . rtc ) ) {
retval = PTR_ERR ( mrst_rtc . rtc ) ;
goto cleanup0 ;
}
rename_region ( iomem , dev_name ( & mrst_rtc . rtc - > dev ) ) ;
spin_lock_irq ( & rtc_lock ) ;
mrst_irq_disable ( & mrst_rtc , RTC_PIE | RTC_AIE ) ;
rtc_control = vrtc_cmos_read ( RTC_CONTROL ) ;
spin_unlock_irq ( & rtc_lock ) ;
if ( ! ( rtc_control & RTC_24H ) | | ( rtc_control & ( RTC_DM_BINARY ) ) )
dev_dbg ( dev , " TODO: support more than 24-hr BCD mode \n " ) ;
if ( rtc_irq ) {
retval = request_irq ( rtc_irq , mrst_rtc_irq ,
2012-03-24 02:02:34 +04:00
0 , dev_name ( & mrst_rtc . rtc - > dev ) ,
2010-11-10 20:29:17 +03:00
mrst_rtc . rtc ) ;
if ( retval < 0 ) {
dev_dbg ( dev , " IRQ %d is already in use, err %d \n " ,
rtc_irq , retval ) ;
goto cleanup1 ;
}
}
dev_dbg ( dev , " initialised \n " ) ;
return 0 ;
cleanup1 :
rtc_device_unregister ( mrst_rtc . rtc ) ;
cleanup0 :
2011-04-07 05:39:44 +04:00
mrst_rtc . dev = NULL ;
2011-05-27 03:25:08 +04:00
release_mem_region ( iomem - > start , resource_size ( iomem ) ) ;
2010-11-10 20:29:17 +03:00
dev_err ( dev , " rtc-mrst: unable to initialise \n " ) ;
return retval ;
}
static void rtc_mrst_do_shutdown ( void )
{
spin_lock_irq ( & rtc_lock ) ;
mrst_irq_disable ( & mrst_rtc , RTC_IRQMASK ) ;
spin_unlock_irq ( & rtc_lock ) ;
}
2012-12-22 01:09:38 +04:00
static void rtc_mrst_do_remove ( struct device * dev )
2010-11-10 20:29:17 +03:00
{
struct mrst_rtc * mrst = dev_get_drvdata ( dev ) ;
struct resource * iomem ;
rtc_mrst_do_shutdown ( ) ;
if ( mrst - > irq )
free_irq ( mrst - > irq , mrst - > rtc ) ;
rtc_device_unregister ( mrst - > rtc ) ;
mrst - > rtc = NULL ;
iomem = mrst - > iomem ;
2011-05-27 03:25:08 +04:00
release_mem_region ( iomem - > start , resource_size ( iomem ) ) ;
2010-11-10 20:29:17 +03:00
mrst - > iomem = NULL ;
mrst - > dev = NULL ;
}
2015-03-26 01:55:09 +03:00
# ifdef CONFIG_PM_SLEEP
static int mrst_suspend ( struct device * dev )
2010-11-10 20:29:17 +03:00
{
struct mrst_rtc * mrst = dev_get_drvdata ( dev ) ;
unsigned char tmp ;
/* Only the alarm might be a wakeup event source */
spin_lock_irq ( & rtc_lock ) ;
mrst - > suspend_ctrl = tmp = vrtc_cmos_read ( RTC_CONTROL ) ;
if ( tmp & ( RTC_PIE | RTC_AIE ) ) {
unsigned char mask ;
if ( device_may_wakeup ( dev ) )
mask = RTC_IRQMASK & ~ RTC_AIE ;
else
mask = RTC_IRQMASK ;
tmp & = ~ mask ;
vrtc_cmos_write ( tmp , RTC_CONTROL ) ;
mrst_checkintr ( mrst , tmp ) ;
}
spin_unlock_irq ( & rtc_lock ) ;
if ( tmp & RTC_AIE ) {
mrst - > enabled_wake = 1 ;
enable_irq_wake ( mrst - > irq ) ;
}
dev_dbg ( & mrst_rtc . rtc - > dev , " suspend%s, ctrl %02x \n " ,
( tmp & RTC_AIE ) ? " , alarm may wake " : " " ,
tmp ) ;
return 0 ;
}
/*
* We want RTC alarms to wake us from the deep power saving state
*/
static inline int mrst_poweroff ( struct device * dev )
{
2015-03-26 01:55:09 +03:00
return mrst_suspend ( dev ) ;
2010-11-10 20:29:17 +03:00
}
static int mrst_resume ( struct device * dev )
{
struct mrst_rtc * mrst = dev_get_drvdata ( dev ) ;
unsigned char tmp = mrst - > suspend_ctrl ;
/* Re-enable any irqs previously active */
if ( tmp & RTC_IRQMASK ) {
unsigned char mask ;
if ( mrst - > enabled_wake ) {
disable_irq_wake ( mrst - > irq ) ;
mrst - > enabled_wake = 0 ;
}
spin_lock_irq ( & rtc_lock ) ;
do {
vrtc_cmos_write ( tmp , RTC_CONTROL ) ;
mask = vrtc_cmos_read ( RTC_INTR_FLAGS ) ;
mask & = ( tmp & RTC_IRQMASK ) | RTC_IRQF ;
if ( ! is_intr ( mask ) )
break ;
rtc_update_irq ( mrst - > rtc , 1 , mask ) ;
tmp & = ~ RTC_AIE ;
} while ( mask & RTC_AIE ) ;
spin_unlock_irq ( & rtc_lock ) ;
}
dev_dbg ( & mrst_rtc . rtc - > dev , " resume, ctrl %02x \n " , tmp ) ;
return 0 ;
}
2015-03-26 01:55:09 +03:00
static SIMPLE_DEV_PM_OPS ( mrst_pm_ops , mrst_suspend , mrst_resume ) ;
# define MRST_PM_OPS (&mrst_pm_ops)
2010-11-10 20:29:17 +03:00
# else
2015-03-26 01:55:09 +03:00
# define MRST_PM_OPS NULL
2010-11-10 20:29:17 +03:00
static inline int mrst_poweroff ( struct device * dev )
{
return - ENOSYS ;
}
# endif
2012-12-22 01:09:38 +04:00
static int vrtc_mrst_platform_probe ( struct platform_device * pdev )
2010-11-10 20:29:17 +03:00
{
return vrtc_mrst_do_probe ( & pdev - > dev ,
platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ,
platform_get_irq ( pdev , 0 ) ) ;
}
2012-12-22 01:09:38 +04:00
static int vrtc_mrst_platform_remove ( struct platform_device * pdev )
2010-11-10 20:29:17 +03:00
{
rtc_mrst_do_remove ( & pdev - > dev ) ;
return 0 ;
}
static void vrtc_mrst_platform_shutdown ( struct platform_device * pdev )
{
if ( system_state = = SYSTEM_POWER_OFF & & ! mrst_poweroff ( & pdev - > dev ) )
return ;
rtc_mrst_do_shutdown ( ) ;
}
MODULE_ALIAS ( " platform:vrtc_mrst " ) ;
static struct platform_driver vrtc_mrst_platform_driver = {
. probe = vrtc_mrst_platform_probe ,
2012-12-22 01:09:38 +04:00
. remove = vrtc_mrst_platform_remove ,
2010-11-10 20:29:17 +03:00
. shutdown = vrtc_mrst_platform_shutdown ,
. driver = {
2015-03-26 01:55:09 +03:00
. name = driver_name ,
. pm = MRST_PM_OPS ,
2010-11-10 20:29:17 +03:00
}
} ;
2012-01-11 03:10:48 +04:00
module_platform_driver ( vrtc_mrst_platform_driver ) ;
2010-11-10 20:29:17 +03:00
MODULE_AUTHOR ( " Jacob Pan; Feng Tang " ) ;
MODULE_DESCRIPTION ( " Driver for Moorestown virtual RTC " ) ;
MODULE_LICENSE ( " GPL " ) ;