2008-10-20 23:50:05 +02:00
/*
* rtc - twl4030 . c - - TWL4030 Real Time Clock interface
*
* Copyright ( C ) 2007 MontaVista Software , Inc
* Author : Alexandre Rusev < source @ mvista . com >
*
* Based on original TI driver twl4030 - rtc . c
* Copyright ( C ) 2006 Texas Instruments , Inc .
*
* Based on rtc - omap . c
* Copyright ( C ) 2003 MontaVista Software , Inc .
* Author : George G . Davis < gdavis @ mvista . com > or < source @ mvista . com >
* Copyright ( C ) 2006 David Brownell
*
* 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 .
*/
# include <linux/kernel.h>
2009-01-06 14:42:11 -08:00
# include <linux/errno.h>
2008-10-20 23:50:05 +02:00
# include <linux/init.h>
# include <linux/module.h>
# include <linux/types.h>
# include <linux/rtc.h>
# include <linux/bcd.h>
# include <linux/platform_device.h>
# include <linux/interrupt.h>
# include <linux/i2c/twl4030.h>
/*
* RTC block register offsets ( use TWL_MODULE_RTC )
*/
# define REG_SECONDS_REG 0x00
# define REG_MINUTES_REG 0x01
# define REG_HOURS_REG 0x02
# define REG_DAYS_REG 0x03
# define REG_MONTHS_REG 0x04
# define REG_YEARS_REG 0x05
# define REG_WEEKS_REG 0x06
# define REG_ALARM_SECONDS_REG 0x07
# define REG_ALARM_MINUTES_REG 0x08
# define REG_ALARM_HOURS_REG 0x09
# define REG_ALARM_DAYS_REG 0x0A
# define REG_ALARM_MONTHS_REG 0x0B
# define REG_ALARM_YEARS_REG 0x0C
# define REG_RTC_CTRL_REG 0x0D
# define REG_RTC_STATUS_REG 0x0E
# define REG_RTC_INTERRUPTS_REG 0x0F
# define REG_RTC_COMP_LSB_REG 0x10
# define REG_RTC_COMP_MSB_REG 0x11
/* RTC_CTRL_REG bitfields */
# define BIT_RTC_CTRL_REG_STOP_RTC_M 0x01
# define BIT_RTC_CTRL_REG_ROUND_30S_M 0x02
# define BIT_RTC_CTRL_REG_AUTO_COMP_M 0x04
# define BIT_RTC_CTRL_REG_MODE_12_24_M 0x08
# define BIT_RTC_CTRL_REG_TEST_MODE_M 0x10
# define BIT_RTC_CTRL_REG_SET_32_COUNTER_M 0x20
# define BIT_RTC_CTRL_REG_GET_TIME_M 0x40
/* RTC_STATUS_REG bitfields */
# define BIT_RTC_STATUS_REG_RUN_M 0x02
# define BIT_RTC_STATUS_REG_1S_EVENT_M 0x04
# define BIT_RTC_STATUS_REG_1M_EVENT_M 0x08
# define BIT_RTC_STATUS_REG_1H_EVENT_M 0x10
# define BIT_RTC_STATUS_REG_1D_EVENT_M 0x20
# define BIT_RTC_STATUS_REG_ALARM_M 0x40
# define BIT_RTC_STATUS_REG_POWER_UP_M 0x80
/* RTC_INTERRUPTS_REG bitfields */
# define BIT_RTC_INTERRUPTS_REG_EVERY_M 0x03
# define BIT_RTC_INTERRUPTS_REG_IT_TIMER_M 0x04
# define BIT_RTC_INTERRUPTS_REG_IT_ALARM_M 0x08
/* REG_SECONDS_REG through REG_YEARS_REG is how many registers? */
# define ALL_TIME_REGS 6
/*----------------------------------------------------------------------*/
/*
* Supports 1 byte read from TWL4030 RTC register .
*/
static int twl4030_rtc_read_u8 ( u8 * data , u8 reg )
{
int ret ;
ret = twl4030_i2c_read_u8 ( TWL4030_MODULE_RTC , data , reg ) ;
if ( ret < 0 )
pr_err ( " twl4030_rtc: Could not read TWL4030 "
" register %X - error %d \n " , reg , ret ) ;
return ret ;
}
/*
* Supports 1 byte write to TWL4030 RTC registers .
*/
static int twl4030_rtc_write_u8 ( u8 data , u8 reg )
{
int ret ;
ret = twl4030_i2c_write_u8 ( TWL4030_MODULE_RTC , data , reg ) ;
if ( ret < 0 )
pr_err ( " twl4030_rtc: Could not write TWL4030 "
" register %X - error %d \n " , reg , ret ) ;
return ret ;
}
/*
* Cache the value for timer / alarm interrupts register ; this is
* only changed by callers holding rtc ops lock ( or resume ) .
*/
static unsigned char rtc_irq_bits ;
/*
2009-01-15 13:50:52 -08:00
* Enable 1 / second update and / or alarm interrupts .
2008-10-20 23:50:05 +02:00
*/
static int set_rtc_irq_bit ( unsigned char bit )
{
unsigned char val ;
int ret ;
val = rtc_irq_bits | bit ;
2009-01-15 13:50:52 -08:00
val & = ~ BIT_RTC_INTERRUPTS_REG_EVERY_M ;
2008-10-20 23:50:05 +02:00
ret = twl4030_rtc_write_u8 ( val , REG_RTC_INTERRUPTS_REG ) ;
if ( ret = = 0 )
rtc_irq_bits = val ;
return ret ;
}
/*
2009-01-15 13:50:52 -08:00
* Disable update and / or alarm interrupts .
2008-10-20 23:50:05 +02:00
*/
static int mask_rtc_irq_bit ( unsigned char bit )
{
unsigned char val ;
int ret ;
val = rtc_irq_bits & ~ bit ;
ret = twl4030_rtc_write_u8 ( val , REG_RTC_INTERRUPTS_REG ) ;
if ( ret = = 0 )
rtc_irq_bits = val ;
return ret ;
}
2009-01-15 13:50:52 -08:00
static int twl4030_rtc_alarm_irq_enable ( struct device * dev , unsigned enabled )
2008-10-20 23:50:05 +02:00
{
int ret ;
if ( enabled )
ret = set_rtc_irq_bit ( BIT_RTC_INTERRUPTS_REG_IT_ALARM_M ) ;
else
ret = mask_rtc_irq_bit ( BIT_RTC_INTERRUPTS_REG_IT_ALARM_M ) ;
return ret ;
}
2009-01-15 13:50:52 -08:00
static int twl4030_rtc_update_irq_enable ( struct device * dev , unsigned enabled )
2008-10-20 23:50:05 +02:00
{
int ret ;
if ( enabled )
ret = set_rtc_irq_bit ( BIT_RTC_INTERRUPTS_REG_IT_TIMER_M ) ;
else
ret = mask_rtc_irq_bit ( BIT_RTC_INTERRUPTS_REG_IT_TIMER_M ) ;
return ret ;
}
/*
* Gets current TWL4030 RTC time and date parameters .
*
* The RTC ' s time / alarm representation is not what gmtime ( 3 ) requires
* Linux to use :
*
* - Months are 1. .12 vs Linux 0 - 11
* - Years are 0. .99 vs Linux 1900. . N ( we assume 21 st century )
*/
static int twl4030_rtc_read_time ( struct device * dev , struct rtc_time * tm )
{
unsigned char rtc_data [ ALL_TIME_REGS + 1 ] ;
int ret ;
u8 save_control ;
ret = twl4030_rtc_read_u8 ( & save_control , REG_RTC_CTRL_REG ) ;
if ( ret < 0 )
return ret ;
save_control | = BIT_RTC_CTRL_REG_GET_TIME_M ;
ret = twl4030_rtc_write_u8 ( save_control , REG_RTC_CTRL_REG ) ;
if ( ret < 0 )
return ret ;
ret = twl4030_i2c_read ( TWL4030_MODULE_RTC , rtc_data ,
REG_SECONDS_REG , ALL_TIME_REGS ) ;
if ( ret < 0 ) {
dev_err ( dev , " rtc_read_time error %d \n " , ret ) ;
return ret ;
}
tm - > tm_sec = bcd2bin ( rtc_data [ 0 ] ) ;
tm - > tm_min = bcd2bin ( rtc_data [ 1 ] ) ;
tm - > tm_hour = bcd2bin ( rtc_data [ 2 ] ) ;
tm - > tm_mday = bcd2bin ( rtc_data [ 3 ] ) ;
tm - > tm_mon = bcd2bin ( rtc_data [ 4 ] ) - 1 ;
tm - > tm_year = bcd2bin ( rtc_data [ 5 ] ) + 100 ;
return ret ;
}
static int twl4030_rtc_set_time ( struct device * dev , struct rtc_time * tm )
{
unsigned char save_control ;
unsigned char rtc_data [ ALL_TIME_REGS + 1 ] ;
int ret ;
rtc_data [ 1 ] = bin2bcd ( tm - > tm_sec ) ;
rtc_data [ 2 ] = bin2bcd ( tm - > tm_min ) ;
rtc_data [ 3 ] = bin2bcd ( tm - > tm_hour ) ;
rtc_data [ 4 ] = bin2bcd ( tm - > tm_mday ) ;
rtc_data [ 5 ] = bin2bcd ( tm - > tm_mon + 1 ) ;
rtc_data [ 6 ] = bin2bcd ( tm - > tm_year - 100 ) ;
/* Stop RTC while updating the TC registers */
ret = twl4030_rtc_read_u8 ( & save_control , REG_RTC_CTRL_REG ) ;
if ( ret < 0 )
goto out ;
save_control & = ~ BIT_RTC_CTRL_REG_STOP_RTC_M ;
twl4030_rtc_write_u8 ( save_control , REG_RTC_CTRL_REG ) ;
if ( ret < 0 )
goto out ;
/* update all the time registers in one shot */
ret = twl4030_i2c_write ( TWL4030_MODULE_RTC , rtc_data ,
REG_SECONDS_REG , ALL_TIME_REGS ) ;
if ( ret < 0 ) {
dev_err ( dev , " rtc_set_time error %d \n " , ret ) ;
goto out ;
}
/* Start back RTC */
save_control | = BIT_RTC_CTRL_REG_STOP_RTC_M ;
ret = twl4030_rtc_write_u8 ( save_control , REG_RTC_CTRL_REG ) ;
out :
return ret ;
}
/*
* Gets current TWL4030 RTC alarm time .
*/
static int twl4030_rtc_read_alarm ( struct device * dev , struct rtc_wkalrm * alm )
{
unsigned char rtc_data [ ALL_TIME_REGS + 1 ] ;
int ret ;
ret = twl4030_i2c_read ( TWL4030_MODULE_RTC , rtc_data ,
REG_ALARM_SECONDS_REG , ALL_TIME_REGS ) ;
if ( ret < 0 ) {
dev_err ( dev , " rtc_read_alarm error %d \n " , ret ) ;
return ret ;
}
/* some of these fields may be wildcard/"match all" */
alm - > time . tm_sec = bcd2bin ( rtc_data [ 0 ] ) ;
alm - > time . tm_min = bcd2bin ( rtc_data [ 1 ] ) ;
alm - > time . tm_hour = bcd2bin ( rtc_data [ 2 ] ) ;
alm - > time . tm_mday = bcd2bin ( rtc_data [ 3 ] ) ;
alm - > time . tm_mon = bcd2bin ( rtc_data [ 4 ] ) - 1 ;
alm - > time . tm_year = bcd2bin ( rtc_data [ 5 ] ) + 100 ;
/* report cached alarm enable state */
if ( rtc_irq_bits & BIT_RTC_INTERRUPTS_REG_IT_ALARM_M )
alm - > enabled = 1 ;
return ret ;
}
static int twl4030_rtc_set_alarm ( struct device * dev , struct rtc_wkalrm * alm )
{
unsigned char alarm_data [ ALL_TIME_REGS + 1 ] ;
int ret ;
2009-01-15 13:50:52 -08:00
ret = twl4030_rtc_alarm_irq_enable ( dev , 0 ) ;
2008-10-20 23:50:05 +02:00
if ( ret )
goto out ;
alarm_data [ 1 ] = bin2bcd ( alm - > time . tm_sec ) ;
alarm_data [ 2 ] = bin2bcd ( alm - > time . tm_min ) ;
alarm_data [ 3 ] = bin2bcd ( alm - > time . tm_hour ) ;
alarm_data [ 4 ] = bin2bcd ( alm - > time . tm_mday ) ;
alarm_data [ 5 ] = bin2bcd ( alm - > time . tm_mon + 1 ) ;
alarm_data [ 6 ] = bin2bcd ( alm - > time . tm_year - 100 ) ;
/* update all the alarm registers in one shot */
ret = twl4030_i2c_write ( TWL4030_MODULE_RTC , alarm_data ,
REG_ALARM_SECONDS_REG , ALL_TIME_REGS ) ;
if ( ret ) {
dev_err ( dev , " rtc_set_alarm error %d \n " , ret ) ;
goto out ;
}
if ( alm - > enabled )
2009-01-15 13:50:52 -08:00
ret = twl4030_rtc_alarm_irq_enable ( dev , 1 ) ;
2008-10-20 23:50:05 +02:00
out :
return ret ;
}
static irqreturn_t twl4030_rtc_interrupt ( int irq , void * rtc )
{
unsigned long events = 0 ;
int ret = IRQ_NONE ;
int res ;
u8 rd_reg ;
# ifdef CONFIG_LOCKDEP
/* WORKAROUND for lockdep forcing IRQF_DISABLED on us, which
* we don ' t want and can ' t tolerate . Although it might be
* friendlier not to borrow this thread context . . .
*/
local_irq_enable ( ) ;
# endif
res = twl4030_rtc_read_u8 ( & rd_reg , REG_RTC_STATUS_REG ) ;
if ( res )
goto out ;
/*
* Figure out source of interrupt : ALARM or TIMER in RTC_STATUS_REG .
* only one ( ALARM or RTC ) interrupt source may be enabled
* at time , we also could check our results
* by reading RTS_INTERRUPTS_REGISTER [ IT_TIMER , IT_ALARM ]
*/
if ( rd_reg & BIT_RTC_STATUS_REG_ALARM_M )
events | = RTC_IRQF | RTC_AF ;
else
events | = RTC_IRQF | RTC_UF ;
res = twl4030_rtc_write_u8 ( rd_reg | BIT_RTC_STATUS_REG_ALARM_M ,
REG_RTC_STATUS_REG ) ;
if ( res )
goto out ;
/* Clear on Read enabled. RTC_IT bit of TWL4030_INT_PWR_ISR1
* needs 2 reads to clear the interrupt . One read is done in
* do_twl4030_pwrirq ( ) . Doing the second read , to clear
* the bit .
*
* FIXME the reason PWR_ISR1 needs an extra read is that
* RTC_IF retriggered until we cleared REG_ALARM_M above .
* But re - reading like this is a bad hack ; by doing so we
* risk wrongly clearing status for some other IRQ ( losing
* the interrupt ) . Be smarter about handling RTC_UF . . .
*/
res = twl4030_i2c_read_u8 ( TWL4030_MODULE_INT ,
& rd_reg , TWL4030_INT_PWR_ISR1 ) ;
if ( res )
goto out ;
/* Notify RTC core on event */
rtc_update_irq ( rtc , 1 , events ) ;
ret = IRQ_HANDLED ;
out :
return ret ;
}
static struct rtc_class_ops twl4030_rtc_ops = {
. read_time = twl4030_rtc_read_time ,
. set_time = twl4030_rtc_set_time ,
. read_alarm = twl4030_rtc_read_alarm ,
. set_alarm = twl4030_rtc_set_alarm ,
2009-01-15 13:50:52 -08:00
. alarm_irq_enable = twl4030_rtc_alarm_irq_enable ,
. update_irq_enable = twl4030_rtc_update_irq_enable ,
2008-10-20 23:50:05 +02:00
} ;
/*----------------------------------------------------------------------*/
static int __devinit twl4030_rtc_probe ( struct platform_device * pdev )
{
struct rtc_device * rtc ;
int ret = 0 ;
int irq = platform_get_irq ( pdev , 0 ) ;
u8 rd_reg ;
2009-01-06 14:42:11 -08:00
if ( irq < = 0 )
return - EINVAL ;
2008-10-20 23:50:05 +02:00
rtc = rtc_device_register ( pdev - > name ,
& pdev - > dev , & twl4030_rtc_ops , THIS_MODULE ) ;
if ( IS_ERR ( rtc ) ) {
2009-01-15 13:50:52 -08:00
ret = PTR_ERR ( rtc ) ;
2008-10-20 23:50:05 +02:00
dev_err ( & pdev - > dev , " can't register RTC device, err %ld \n " ,
PTR_ERR ( rtc ) ) ;
goto out0 ;
}
platform_set_drvdata ( pdev , rtc ) ;
ret = twl4030_rtc_read_u8 ( & rd_reg , REG_RTC_STATUS_REG ) ;
if ( ret < 0 )
goto out1 ;
if ( rd_reg & BIT_RTC_STATUS_REG_POWER_UP_M )
dev_warn ( & pdev - > dev , " Power up reset detected. \n " ) ;
if ( rd_reg & BIT_RTC_STATUS_REG_ALARM_M )
dev_warn ( & pdev - > dev , " Pending Alarm interrupt detected. \n " ) ;
/* Clear RTC Power up reset and pending alarm interrupts */
ret = twl4030_rtc_write_u8 ( rd_reg , REG_RTC_STATUS_REG ) ;
if ( ret < 0 )
goto out1 ;
ret = request_irq ( irq , twl4030_rtc_interrupt ,
IRQF_TRIGGER_RISING ,
2009-03-24 16:38:22 -07:00
dev_name ( & rtc - > dev ) , rtc ) ;
2008-10-20 23:50:05 +02:00
if ( ret < 0 ) {
dev_err ( & pdev - > dev , " IRQ is not free. \n " ) ;
goto out1 ;
}
/* Check RTC module status, Enable if it is off */
ret = twl4030_rtc_read_u8 ( & rd_reg , REG_RTC_CTRL_REG ) ;
if ( ret < 0 )
goto out2 ;
if ( ! ( rd_reg & BIT_RTC_CTRL_REG_STOP_RTC_M ) ) {
dev_info ( & pdev - > dev , " Enabling TWL4030-RTC. \n " ) ;
rd_reg = BIT_RTC_CTRL_REG_STOP_RTC_M ;
ret = twl4030_rtc_write_u8 ( rd_reg , REG_RTC_CTRL_REG ) ;
if ( ret < 0 )
goto out2 ;
}
/* init cached IRQ enable bits */
ret = twl4030_rtc_read_u8 ( & rtc_irq_bits , REG_RTC_INTERRUPTS_REG ) ;
if ( ret < 0 )
goto out2 ;
return ret ;
out2 :
free_irq ( irq , rtc ) ;
out1 :
rtc_device_unregister ( rtc ) ;
out0 :
return ret ;
}
/*
* Disable all TWL4030 RTC module interrupts .
* Sets status flag to free .
*/
static int __devexit twl4030_rtc_remove ( struct platform_device * pdev )
{
/* leave rtc running, but disable irqs */
struct rtc_device * rtc = platform_get_drvdata ( pdev ) ;
int irq = platform_get_irq ( pdev , 0 ) ;
mask_rtc_irq_bit ( BIT_RTC_INTERRUPTS_REG_IT_ALARM_M ) ;
mask_rtc_irq_bit ( BIT_RTC_INTERRUPTS_REG_IT_TIMER_M ) ;
free_irq ( irq , rtc ) ;
rtc_device_unregister ( rtc ) ;
platform_set_drvdata ( pdev , NULL ) ;
return 0 ;
}
static void twl4030_rtc_shutdown ( struct platform_device * pdev )
{
2009-01-15 13:50:56 -08:00
/* mask timer interrupts, but leave alarm interrupts on to enable
power - on when alarm is triggered */
mask_rtc_irq_bit ( BIT_RTC_INTERRUPTS_REG_IT_TIMER_M ) ;
2008-10-20 23:50:05 +02:00
}
# ifdef CONFIG_PM
static unsigned char irqstat ;
static int twl4030_rtc_suspend ( struct platform_device * pdev , pm_message_t state )
{
irqstat = rtc_irq_bits ;
2009-05-12 13:19:38 -07:00
mask_rtc_irq_bit ( BIT_RTC_INTERRUPTS_REG_IT_TIMER_M ) ;
2008-10-20 23:50:05 +02:00
return 0 ;
}
static int twl4030_rtc_resume ( struct platform_device * pdev )
{
set_rtc_irq_bit ( irqstat ) ;
return 0 ;
}
# else
# define twl4030_rtc_suspend NULL
# define twl4030_rtc_resume NULL
# endif
MODULE_ALIAS ( " platform:twl4030_rtc " ) ;
static struct platform_driver twl4030rtc_driver = {
. probe = twl4030_rtc_probe ,
. remove = __devexit_p ( twl4030_rtc_remove ) ,
. shutdown = twl4030_rtc_shutdown ,
. suspend = twl4030_rtc_suspend ,
. resume = twl4030_rtc_resume ,
. driver = {
. owner = THIS_MODULE ,
. name = " twl4030_rtc " ,
} ,
} ;
static int __init twl4030_rtc_init ( void )
{
return platform_driver_register ( & twl4030rtc_driver ) ;
}
module_init ( twl4030_rtc_init ) ;
static void __exit twl4030_rtc_exit ( void )
{
platform_driver_unregister ( & twl4030rtc_driver ) ;
}
module_exit ( twl4030_rtc_exit ) ;
MODULE_AUTHOR ( " Texas Instruments, MontaVista Software " ) ;
MODULE_LICENSE ( " GPL " ) ;