2015-02-14 01:41:11 +03:00
/*
* RTC driver for the Armada 38 x Marvell SoCs
*
* Copyright ( C ) 2015 Marvell
*
* Gregory Clement < gregory . clement @ free - electrons . 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 ; either version 2 of the
* License , or ( at your option ) any later version .
*
*/
# include <linux/delay.h>
# include <linux/io.h>
# include <linux/module.h>
# include <linux/of.h>
# include <linux/platform_device.h>
# include <linux/rtc.h>
# define RTC_STATUS 0x0
# define RTC_STATUS_ALARM1 BIT(0)
# define RTC_STATUS_ALARM2 BIT(1)
# define RTC_IRQ1_CONF 0x4
# define RTC_IRQ1_AL_EN BIT(0)
# define RTC_IRQ1_FREQ_EN BIT(1)
# define RTC_IRQ1_FREQ_1HZ BIT(2)
# define RTC_TIME 0xC
# define RTC_ALARM1 0x10
# define SOC_RTC_INTERRUPT 0x8
# define SOC_RTC_ALARM1 BIT(0)
# define SOC_RTC_ALARM2 BIT(1)
# define SOC_RTC_ALARM1_MASK BIT(2)
# define SOC_RTC_ALARM2_MASK BIT(3)
struct armada38x_rtc {
struct rtc_device * rtc_dev ;
void __iomem * regs ;
void __iomem * regs_soc ;
spinlock_t lock ;
int irq ;
} ;
/*
* According to the datasheet , the OS should wait 5u s after every
* register write to the RTC hard macro so that the required update
* can occur without holding off the system bus
*/
static void rtc_delayed_write ( u32 val , struct armada38x_rtc * rtc , int offset )
{
writel ( val , rtc - > regs + offset ) ;
udelay ( 5 ) ;
}
static int armada38x_rtc_read_time ( struct device * dev , struct rtc_time * tm )
{
struct armada38x_rtc * rtc = dev_get_drvdata ( dev ) ;
2015-08-06 18:18:48 +03:00
unsigned long time , time_check , flags ;
2015-02-14 01:41:11 +03:00
2015-08-06 18:18:48 +03:00
spin_lock_irqsave ( & rtc - > lock , flags ) ;
2015-02-14 01:41:11 +03:00
time = readl ( rtc - > regs + RTC_TIME ) ;
/*
* WA for failing time set attempts . As stated in HW ERRATA if
* more than one second between two time reads is detected
* then read once again .
*/
time_check = readl ( rtc - > regs + RTC_TIME ) ;
if ( ( time_check - time ) > 1 )
time_check = readl ( rtc - > regs + RTC_TIME ) ;
2015-08-06 18:18:48 +03:00
spin_unlock_irqrestore ( & rtc - > lock , flags ) ;
2015-02-14 01:41:11 +03:00
rtc_time_to_tm ( time_check , tm ) ;
return 0 ;
}
static int armada38x_rtc_set_time ( struct device * dev , struct rtc_time * tm )
{
struct armada38x_rtc * rtc = dev_get_drvdata ( dev ) ;
int ret = 0 ;
2015-08-06 18:18:48 +03:00
unsigned long time , flags ;
2015-02-14 01:41:11 +03:00
ret = rtc_tm_to_time ( tm , & time ) ;
if ( ret )
goto out ;
/*
2015-08-06 18:18:48 +03:00
* According to errata FE - 3124064 , Write to RTC TIME register
* may fail . As a workaround , after writing to RTC TIME
* register , issue a dummy write of 0x0 twice to RTC Status
* register .
2015-02-14 01:41:11 +03:00
*/
2015-08-06 18:18:48 +03:00
spin_lock_irqsave ( & rtc - > lock , flags ) ;
2015-02-14 01:41:11 +03:00
rtc_delayed_write ( time , rtc , RTC_TIME ) ;
2015-08-06 18:18:48 +03:00
rtc_delayed_write ( 0 , rtc , RTC_STATUS ) ;
rtc_delayed_write ( 0 , rtc , RTC_STATUS ) ;
spin_unlock_irqrestore ( & rtc - > lock , flags ) ;
2015-02-14 01:41:11 +03:00
out :
return ret ;
}
static int armada38x_rtc_read_alarm ( struct device * dev , struct rtc_wkalrm * alrm )
{
struct armada38x_rtc * rtc = dev_get_drvdata ( dev ) ;
unsigned long time , flags ;
u32 val ;
spin_lock_irqsave ( & rtc - > lock , flags ) ;
time = readl ( rtc - > regs + RTC_ALARM1 ) ;
val = readl ( rtc - > regs + RTC_IRQ1_CONF ) & RTC_IRQ1_AL_EN ;
spin_unlock_irqrestore ( & rtc - > lock , flags ) ;
alrm - > enabled = val ? 1 : 0 ;
rtc_time_to_tm ( time , & alrm - > time ) ;
return 0 ;
}
static int armada38x_rtc_set_alarm ( struct device * dev , struct rtc_wkalrm * alrm )
{
struct armada38x_rtc * rtc = dev_get_drvdata ( dev ) ;
unsigned long time , flags ;
int ret = 0 ;
u32 val ;
ret = rtc_tm_to_time ( & alrm - > time , & time ) ;
if ( ret )
goto out ;
spin_lock_irqsave ( & rtc - > lock , flags ) ;
rtc_delayed_write ( time , rtc , RTC_ALARM1 ) ;
if ( alrm - > enabled ) {
rtc_delayed_write ( RTC_IRQ1_AL_EN , rtc , RTC_IRQ1_CONF ) ;
val = readl ( rtc - > regs_soc + SOC_RTC_INTERRUPT ) ;
writel ( val | SOC_RTC_ALARM1_MASK ,
rtc - > regs_soc + SOC_RTC_INTERRUPT ) ;
}
spin_unlock_irqrestore ( & rtc - > lock , flags ) ;
out :
return ret ;
}
static int armada38x_rtc_alarm_irq_enable ( struct device * dev ,
unsigned int enabled )
{
struct armada38x_rtc * rtc = dev_get_drvdata ( dev ) ;
unsigned long flags ;
spin_lock_irqsave ( & rtc - > lock , flags ) ;
if ( enabled )
rtc_delayed_write ( RTC_IRQ1_AL_EN , rtc , RTC_IRQ1_CONF ) ;
else
rtc_delayed_write ( 0 , rtc , RTC_IRQ1_CONF ) ;
spin_unlock_irqrestore ( & rtc - > lock , flags ) ;
return 0 ;
}
static irqreturn_t armada38x_rtc_alarm_irq ( int irq , void * data )
{
struct armada38x_rtc * rtc = data ;
u32 val ;
int event = RTC_IRQF | RTC_AF ;
dev_dbg ( & rtc - > rtc_dev - > dev , " %s:irq(%d) \n " , __func__ , irq ) ;
spin_lock ( & rtc - > lock ) ;
val = readl ( rtc - > regs_soc + SOC_RTC_INTERRUPT ) ;
writel ( val & ~ SOC_RTC_ALARM1 , rtc - > regs_soc + SOC_RTC_INTERRUPT ) ;
val = readl ( rtc - > regs + RTC_IRQ1_CONF ) ;
/* disable all the interrupts for alarm 1 */
rtc_delayed_write ( 0 , rtc , RTC_IRQ1_CONF ) ;
/* Ack the event */
rtc_delayed_write ( RTC_STATUS_ALARM1 , rtc , RTC_STATUS ) ;
spin_unlock ( & rtc - > lock ) ;
if ( val & RTC_IRQ1_FREQ_EN ) {
if ( val & RTC_IRQ1_FREQ_1HZ )
event | = RTC_UF ;
else
event | = RTC_PF ;
}
rtc_update_irq ( rtc - > rtc_dev , 1 , event ) ;
return IRQ_HANDLED ;
}
static struct rtc_class_ops armada38x_rtc_ops = {
. read_time = armada38x_rtc_read_time ,
. set_time = armada38x_rtc_set_time ,
. read_alarm = armada38x_rtc_read_alarm ,
. set_alarm = armada38x_rtc_set_alarm ,
. alarm_irq_enable = armada38x_rtc_alarm_irq_enable ,
} ;
static __init int armada38x_rtc_probe ( struct platform_device * pdev )
{
struct resource * res ;
struct armada38x_rtc * rtc ;
int ret ;
rtc = devm_kzalloc ( & pdev - > dev , sizeof ( struct armada38x_rtc ) ,
GFP_KERNEL ) ;
if ( ! rtc )
return - ENOMEM ;
spin_lock_init ( & rtc - > lock ) ;
res = platform_get_resource_byname ( pdev , IORESOURCE_MEM , " rtc " ) ;
rtc - > regs = devm_ioremap_resource ( & pdev - > dev , res ) ;
if ( IS_ERR ( rtc - > regs ) )
return PTR_ERR ( rtc - > regs ) ;
res = platform_get_resource_byname ( pdev , IORESOURCE_MEM , " rtc-soc " ) ;
rtc - > regs_soc = devm_ioremap_resource ( & pdev - > dev , res ) ;
if ( IS_ERR ( rtc - > regs_soc ) )
return PTR_ERR ( rtc - > regs_soc ) ;
rtc - > irq = platform_get_irq ( pdev , 0 ) ;
if ( rtc - > irq < 0 ) {
dev_err ( & pdev - > dev , " no irq \n " ) ;
return rtc - > irq ;
}
if ( devm_request_irq ( & pdev - > dev , rtc - > irq , armada38x_rtc_alarm_irq ,
0 , pdev - > name , rtc ) < 0 ) {
dev_warn ( & pdev - > dev , " Interrupt not available. \n " ) ;
rtc - > irq = - 1 ;
/*
* If there is no interrupt available then we can ' t
* use the alarm
*/
armada38x_rtc_ops . set_alarm = NULL ;
armada38x_rtc_ops . alarm_irq_enable = NULL ;
}
platform_set_drvdata ( pdev , rtc ) ;
if ( rtc - > irq ! = - 1 )
device_init_wakeup ( & pdev - > dev , 1 ) ;
rtc - > rtc_dev = devm_rtc_device_register ( & pdev - > dev , pdev - > name ,
& armada38x_rtc_ops , THIS_MODULE ) ;
if ( IS_ERR ( rtc - > rtc_dev ) ) {
ret = PTR_ERR ( rtc - > rtc_dev ) ;
dev_err ( & pdev - > dev , " Failed to register RTC device: %d \n " , ret ) ;
return ret ;
}
return 0 ;
}
# ifdef CONFIG_PM_SLEEP
static int armada38x_rtc_suspend ( struct device * dev )
{
if ( device_may_wakeup ( dev ) ) {
struct armada38x_rtc * rtc = dev_get_drvdata ( dev ) ;
return enable_irq_wake ( rtc - > irq ) ;
}
return 0 ;
}
static int armada38x_rtc_resume ( struct device * dev )
{
if ( device_may_wakeup ( dev ) ) {
struct armada38x_rtc * rtc = dev_get_drvdata ( dev ) ;
return disable_irq_wake ( rtc - > irq ) ;
}
return 0 ;
}
# endif
static SIMPLE_DEV_PM_OPS ( armada38x_rtc_pm_ops ,
armada38x_rtc_suspend , armada38x_rtc_resume ) ;
# ifdef CONFIG_OF
static const struct of_device_id armada38x_rtc_of_match_table [ ] = {
{ . compatible = " marvell,armada-380-rtc " , } ,
{ }
} ;
2015-08-27 14:52:02 +03:00
MODULE_DEVICE_TABLE ( of , armada38x_rtc_of_match_table ) ;
2015-02-14 01:41:11 +03:00
# endif
static struct platform_driver armada38x_rtc_driver = {
. driver = {
. name = " armada38x-rtc " ,
. pm = & armada38x_rtc_pm_ops ,
. of_match_table = of_match_ptr ( armada38x_rtc_of_match_table ) ,
} ,
} ;
module_platform_driver_probe ( armada38x_rtc_driver , armada38x_rtc_probe ) ;
MODULE_DESCRIPTION ( " Marvell Armada 38x RTC driver " ) ;
MODULE_AUTHOR ( " Gregory CLEMENT <gregory.clement@free-electrons.com> " ) ;
MODULE_LICENSE ( " GPL " ) ;