2010-06-19 22:29:50 +04:00
/*
* Copyright ( C ) 2009 - 2010 , Lars - Peter Clausen < lars @ metafoo . de >
2010-10-28 02:33:12 +04:00
* Copyright ( C ) 2010 , Paul Cercueil < paul @ crapouillou . net >
2010-06-19 22:29:50 +04:00
* JZ4740 SoC RTC driver
*
* 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 .
*
* You should have received a copy of the GNU General Public License along
* with this program ; if not , write to the Free Software Foundation , Inc . ,
* 675 Mass Ave , Cambridge , MA 0213 9 , USA .
*
*/
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/platform_device.h>
# include <linux/rtc.h>
# include <linux/slab.h>
# include <linux/spinlock.h>
# define JZ_REG_RTC_CTRL 0x00
# define JZ_REG_RTC_SEC 0x04
# define JZ_REG_RTC_SEC_ALARM 0x08
# define JZ_REG_RTC_REGULATOR 0x0C
# define JZ_REG_RTC_HIBERNATE 0x20
# define JZ_REG_RTC_SCRATCHPAD 0x34
# define JZ_RTC_CTRL_WRDY BIT(7)
# define JZ_RTC_CTRL_1HZ BIT(6)
# define JZ_RTC_CTRL_1HZ_IRQ BIT(5)
# define JZ_RTC_CTRL_AF BIT(4)
# define JZ_RTC_CTRL_AF_IRQ BIT(3)
# define JZ_RTC_CTRL_AE BIT(2)
# define JZ_RTC_CTRL_ENABLE BIT(0)
struct jz4740_rtc {
struct resource * mem ;
void __iomem * base ;
struct rtc_device * rtc ;
unsigned int irq ;
spinlock_t lock ;
} ;
static inline uint32_t jz4740_rtc_reg_read ( struct jz4740_rtc * rtc , size_t reg )
{
return readl ( rtc - > base + reg ) ;
}
static int jz4740_rtc_wait_write_ready ( struct jz4740_rtc * rtc )
{
uint32_t ctrl ;
int timeout = 1000 ;
do {
ctrl = jz4740_rtc_reg_read ( rtc , JZ_REG_RTC_CTRL ) ;
} while ( ! ( ctrl & JZ_RTC_CTRL_WRDY ) & & - - timeout ) ;
return timeout ? 0 : - EIO ;
}
static inline int jz4740_rtc_reg_write ( struct jz4740_rtc * rtc , size_t reg ,
uint32_t val )
{
int ret ;
ret = jz4740_rtc_wait_write_ready ( rtc ) ;
if ( ret = = 0 )
writel ( val , rtc - > base + reg ) ;
return ret ;
}
static int jz4740_rtc_ctrl_set_bits ( struct jz4740_rtc * rtc , uint32_t mask ,
bool set )
{
int ret ;
unsigned long flags ;
uint32_t ctrl ;
spin_lock_irqsave ( & rtc - > lock , flags ) ;
ctrl = jz4740_rtc_reg_read ( rtc , JZ_REG_RTC_CTRL ) ;
/* Don't clear interrupt flags by accident */
ctrl | = JZ_RTC_CTRL_1HZ | JZ_RTC_CTRL_AF ;
if ( set )
ctrl | = mask ;
else
ctrl & = ~ mask ;
ret = jz4740_rtc_reg_write ( rtc , JZ_REG_RTC_CTRL , ctrl ) ;
spin_unlock_irqrestore ( & rtc - > lock , flags ) ;
return ret ;
}
static int jz4740_rtc_read_time ( struct device * dev , struct rtc_time * time )
{
struct jz4740_rtc * rtc = dev_get_drvdata ( dev ) ;
uint32_t secs , secs2 ;
int timeout = 5 ;
/* If the seconds register is read while it is updated, it can contain a
* bogus value . This can be avoided by making sure that two consecutive
* reads have the same value .
*/
secs = jz4740_rtc_reg_read ( rtc , JZ_REG_RTC_SEC ) ;
secs2 = jz4740_rtc_reg_read ( rtc , JZ_REG_RTC_SEC ) ;
while ( secs ! = secs2 & & - - timeout ) {
secs = secs2 ;
secs2 = jz4740_rtc_reg_read ( rtc , JZ_REG_RTC_SEC ) ;
}
if ( timeout = = 0 )
return - EIO ;
rtc_time_to_tm ( secs , time ) ;
return rtc_valid_tm ( time ) ;
}
static int jz4740_rtc_set_mmss ( struct device * dev , unsigned long secs )
{
struct jz4740_rtc * rtc = dev_get_drvdata ( dev ) ;
return jz4740_rtc_reg_write ( rtc , JZ_REG_RTC_SEC , secs ) ;
}
static int jz4740_rtc_read_alarm ( struct device * dev , struct rtc_wkalrm * alrm )
{
struct jz4740_rtc * rtc = dev_get_drvdata ( dev ) ;
uint32_t secs ;
uint32_t ctrl ;
secs = jz4740_rtc_reg_read ( rtc , JZ_REG_RTC_SEC_ALARM ) ;
ctrl = jz4740_rtc_reg_read ( rtc , JZ_REG_RTC_CTRL ) ;
alrm - > enabled = ! ! ( ctrl & JZ_RTC_CTRL_AE ) ;
alrm - > pending = ! ! ( ctrl & JZ_RTC_CTRL_AF ) ;
rtc_time_to_tm ( secs , & alrm - > time ) ;
return rtc_valid_tm ( & alrm - > time ) ;
}
static int jz4740_rtc_set_alarm ( struct device * dev , struct rtc_wkalrm * alrm )
{
int ret ;
struct jz4740_rtc * rtc = dev_get_drvdata ( dev ) ;
unsigned long secs ;
rtc_tm_to_time ( & alrm - > time , & secs ) ;
ret = jz4740_rtc_reg_write ( rtc , JZ_REG_RTC_SEC_ALARM , secs ) ;
if ( ! ret )
2010-10-28 02:33:12 +04:00
ret = jz4740_rtc_ctrl_set_bits ( rtc ,
JZ_RTC_CTRL_AE | JZ_RTC_CTRL_AF_IRQ , alrm - > enabled ) ;
2010-06-19 22:29:50 +04:00
return ret ;
}
static int jz4740_rtc_alarm_irq_enable ( struct device * dev , unsigned int enable )
{
struct jz4740_rtc * rtc = dev_get_drvdata ( dev ) ;
return jz4740_rtc_ctrl_set_bits ( rtc , JZ_RTC_CTRL_AF_IRQ , enable ) ;
}
static struct rtc_class_ops jz4740_rtc_ops = {
. read_time = jz4740_rtc_read_time ,
. set_mmss = jz4740_rtc_set_mmss ,
. read_alarm = jz4740_rtc_read_alarm ,
. set_alarm = jz4740_rtc_set_alarm ,
. alarm_irq_enable = jz4740_rtc_alarm_irq_enable ,
} ;
static irqreturn_t jz4740_rtc_irq ( int irq , void * data )
{
struct jz4740_rtc * rtc = data ;
uint32_t ctrl ;
unsigned long events = 0 ;
ctrl = jz4740_rtc_reg_read ( rtc , JZ_REG_RTC_CTRL ) ;
if ( ctrl & JZ_RTC_CTRL_1HZ )
events | = ( RTC_UF | RTC_IRQF ) ;
if ( ctrl & JZ_RTC_CTRL_AF )
events | = ( RTC_AF | RTC_IRQF ) ;
rtc_update_irq ( rtc - > rtc , 1 , events ) ;
jz4740_rtc_ctrl_set_bits ( rtc , JZ_RTC_CTRL_1HZ | JZ_RTC_CTRL_AF , false ) ;
return IRQ_HANDLED ;
}
void jz4740_rtc_poweroff ( struct device * dev )
{
struct jz4740_rtc * rtc = dev_get_drvdata ( dev ) ;
jz4740_rtc_reg_write ( rtc , JZ_REG_RTC_HIBERNATE , 1 ) ;
}
EXPORT_SYMBOL_GPL ( jz4740_rtc_poweroff ) ;
static int __devinit jz4740_rtc_probe ( struct platform_device * pdev )
{
int ret ;
struct jz4740_rtc * rtc ;
uint32_t scratchpad ;
rtc = kzalloc ( sizeof ( * rtc ) , GFP_KERNEL ) ;
if ( ! rtc )
return - ENOMEM ;
rtc - > irq = platform_get_irq ( pdev , 0 ) ;
if ( rtc - > irq < 0 ) {
ret = - ENOENT ;
dev_err ( & pdev - > dev , " Failed to get platform irq \n " ) ;
goto err_free ;
}
rtc - > mem = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
if ( ! rtc - > mem ) {
ret = - ENOENT ;
dev_err ( & pdev - > dev , " Failed to get platform mmio memory \n " ) ;
goto err_free ;
}
rtc - > mem = request_mem_region ( rtc - > mem - > start , resource_size ( rtc - > mem ) ,
pdev - > name ) ;
if ( ! rtc - > mem ) {
ret = - EBUSY ;
dev_err ( & pdev - > dev , " Failed to request mmio memory region \n " ) ;
goto err_free ;
}
rtc - > base = ioremap_nocache ( rtc - > mem - > start , resource_size ( rtc - > mem ) ) ;
if ( ! rtc - > base ) {
ret = - EBUSY ;
dev_err ( & pdev - > dev , " Failed to ioremap mmio memory \n " ) ;
goto err_release_mem_region ;
}
spin_lock_init ( & rtc - > lock ) ;
platform_set_drvdata ( pdev , rtc ) ;
2010-10-28 02:33:12 +04:00
device_init_wakeup ( & pdev - > dev , 1 ) ;
2010-06-19 22:29:50 +04:00
rtc - > rtc = rtc_device_register ( pdev - > name , & pdev - > dev , & jz4740_rtc_ops ,
THIS_MODULE ) ;
if ( IS_ERR ( rtc - > rtc ) ) {
ret = PTR_ERR ( rtc - > rtc ) ;
dev_err ( & pdev - > dev , " Failed to register rtc device: %d \n " , ret ) ;
goto err_iounmap ;
}
ret = request_irq ( rtc - > irq , jz4740_rtc_irq , 0 ,
pdev - > name , rtc ) ;
if ( ret ) {
dev_err ( & pdev - > dev , " Failed to request rtc irq: %d \n " , ret ) ;
goto err_unregister_rtc ;
}
scratchpad = jz4740_rtc_reg_read ( rtc , JZ_REG_RTC_SCRATCHPAD ) ;
if ( scratchpad ! = 0x12345678 ) {
ret = jz4740_rtc_reg_write ( rtc , JZ_REG_RTC_SCRATCHPAD , 0x12345678 ) ;
ret = jz4740_rtc_reg_write ( rtc , JZ_REG_RTC_SEC , 0 ) ;
if ( ret ) {
dev_err ( & pdev - > dev , " Could not write write to RTC registers \n " ) ;
goto err_free_irq ;
}
}
return 0 ;
err_free_irq :
free_irq ( rtc - > irq , rtc ) ;
err_unregister_rtc :
rtc_device_unregister ( rtc - > rtc ) ;
err_iounmap :
platform_set_drvdata ( pdev , NULL ) ;
iounmap ( rtc - > base ) ;
err_release_mem_region :
release_mem_region ( rtc - > mem - > start , resource_size ( rtc - > mem ) ) ;
err_free :
kfree ( rtc ) ;
return ret ;
}
static int __devexit jz4740_rtc_remove ( struct platform_device * pdev )
{
struct jz4740_rtc * rtc = platform_get_drvdata ( pdev ) ;
free_irq ( rtc - > irq , rtc ) ;
rtc_device_unregister ( rtc - > rtc ) ;
iounmap ( rtc - > base ) ;
release_mem_region ( rtc - > mem - > start , resource_size ( rtc - > mem ) ) ;
kfree ( rtc ) ;
platform_set_drvdata ( pdev , NULL ) ;
return 0 ;
}
2010-10-28 02:33:12 +04:00
# ifdef CONFIG_PM
static int jz4740_rtc_suspend ( struct device * dev )
{
struct jz4740_rtc * rtc = dev_get_drvdata ( dev ) ;
if ( device_may_wakeup ( dev ) )
enable_irq_wake ( rtc - > irq ) ;
return 0 ;
}
static int jz4740_rtc_resume ( struct device * dev )
{
struct jz4740_rtc * rtc = dev_get_drvdata ( dev ) ;
if ( device_may_wakeup ( dev ) )
disable_irq_wake ( rtc - > irq ) ;
return 0 ;
}
static const struct dev_pm_ops jz4740_pm_ops = {
. suspend = jz4740_rtc_suspend ,
. resume = jz4740_rtc_resume ,
} ;
# define JZ4740_RTC_PM_OPS (&jz4740_pm_ops)
# else
# define JZ4740_RTC_PM_OPS NULL
# endif /* CONFIG_PM */
2010-06-19 22:29:50 +04:00
struct platform_driver jz4740_rtc_driver = {
2010-10-28 02:33:12 +04:00
. probe = jz4740_rtc_probe ,
. remove = __devexit_p ( jz4740_rtc_remove ) ,
. driver = {
. name = " jz4740-rtc " ,
2010-06-19 22:29:50 +04:00
. owner = THIS_MODULE ,
2010-10-28 02:33:12 +04:00
. pm = JZ4740_RTC_PM_OPS ,
2010-06-19 22:29:50 +04:00
} ,
} ;
static int __init jz4740_rtc_init ( void )
{
return platform_driver_register ( & jz4740_rtc_driver ) ;
}
module_init ( jz4740_rtc_init ) ;
static void __exit jz4740_rtc_exit ( void )
{
platform_driver_unregister ( & jz4740_rtc_driver ) ;
}
module_exit ( jz4740_rtc_exit ) ;
MODULE_AUTHOR ( " Lars-Peter Clausen <lars@metafoo.de> " ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_DESCRIPTION ( " RTC driver for the JZ4740 SoC \n " ) ;
MODULE_ALIAS ( " platform:jz4740-rtc " ) ;