2019-05-20 20:08:01 +03:00
// SPDX-License-Identifier: GPL-2.0-or-later
2015-04-09 17:47:33 +03:00
/*
* rtc - st - lpc . c - ST ' s LPC RTC , powered by the Low Power Timer
*
* Copyright ( C ) 2014 STMicroelectronics Limited
*
* Author : David Paris < david . paris @ st . com > for STMicroelectronics
* Lee Jones < lee . jones @ linaro . org > for STMicroelectronics
*
* Based on the original driver written by Stuart Menefy .
*/
# include <linux/clk.h>
# include <linux/delay.h>
# include <linux/init.h>
# include <linux/io.h>
# include <linux/irq.h>
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/of.h>
# include <linux/of_irq.h>
# include <linux/platform_device.h>
# include <linux/rtc.h>
# include <dt-bindings/mfd/st-lpc.h>
/* Low Power Timer */
# define LPC_LPT_LSB_OFF 0x400
# define LPC_LPT_MSB_OFF 0x404
# define LPC_LPT_START_OFF 0x408
/* Low Power Alarm */
# define LPC_LPA_LSB_OFF 0x410
# define LPC_LPA_MSB_OFF 0x414
# define LPC_LPA_START_OFF 0x418
/* LPC as WDT */
# define LPC_WDT_OFF 0x510
# define LPC_WDT_FLAG_OFF 0x514
struct st_rtc {
struct rtc_device * rtc_dev ;
struct rtc_wkalrm alarm ;
struct clk * clk ;
unsigned long clkrate ;
void __iomem * ioaddr ;
bool irq_enabled : 1 ;
spinlock_t lock ;
short irq ;
} ;
static void st_rtc_set_hw_alarm ( struct st_rtc * rtc ,
unsigned long msb , unsigned long lsb )
{
unsigned long flags ;
spin_lock_irqsave ( & rtc - > lock , flags ) ;
writel_relaxed ( 1 , rtc - > ioaddr + LPC_WDT_OFF ) ;
writel_relaxed ( msb , rtc - > ioaddr + LPC_LPA_MSB_OFF ) ;
writel_relaxed ( lsb , rtc - > ioaddr + LPC_LPA_LSB_OFF ) ;
writel_relaxed ( 1 , rtc - > ioaddr + LPC_LPA_START_OFF ) ;
writel_relaxed ( 0 , rtc - > ioaddr + LPC_WDT_OFF ) ;
spin_unlock_irqrestore ( & rtc - > lock , flags ) ;
}
static irqreturn_t st_rtc_handler ( int this_irq , void * data )
{
struct st_rtc * rtc = ( struct st_rtc * ) data ;
rtc_update_irq ( rtc - > rtc_dev , 1 , RTC_AF ) ;
return IRQ_HANDLED ;
}
static int st_rtc_read_time ( struct device * dev , struct rtc_time * tm )
{
struct st_rtc * rtc = dev_get_drvdata ( dev ) ;
unsigned long lpt_lsb , lpt_msb ;
unsigned long long lpt ;
unsigned long flags ;
spin_lock_irqsave ( & rtc - > lock , flags ) ;
do {
lpt_msb = readl_relaxed ( rtc - > ioaddr + LPC_LPT_MSB_OFF ) ;
lpt_lsb = readl_relaxed ( rtc - > ioaddr + LPC_LPT_LSB_OFF ) ;
} while ( readl_relaxed ( rtc - > ioaddr + LPC_LPT_MSB_OFF ) ! = lpt_msb ) ;
spin_unlock_irqrestore ( & rtc - > lock , flags ) ;
lpt = ( ( unsigned long long ) lpt_msb < < 32 ) | lpt_lsb ;
do_div ( lpt , rtc - > clkrate ) ;
2017-06-19 12:36:22 +03:00
rtc_time64_to_tm ( lpt , tm ) ;
2015-04-09 17:47:33 +03:00
return 0 ;
}
static int st_rtc_set_time ( struct device * dev , struct rtc_time * tm )
{
struct st_rtc * rtc = dev_get_drvdata ( dev ) ;
2017-06-19 12:36:22 +03:00
unsigned long long lpt , secs ;
unsigned long flags ;
2015-04-09 17:47:33 +03:00
2017-06-19 12:36:22 +03:00
secs = rtc_tm_to_time64 ( tm ) ;
2015-04-09 17:47:33 +03:00
lpt = ( unsigned long long ) secs * rtc - > clkrate ;
spin_lock_irqsave ( & rtc - > lock , flags ) ;
writel_relaxed ( lpt > > 32 , rtc - > ioaddr + LPC_LPT_MSB_OFF ) ;
writel_relaxed ( lpt , rtc - > ioaddr + LPC_LPT_LSB_OFF ) ;
writel_relaxed ( 1 , rtc - > ioaddr + LPC_LPT_START_OFF ) ;
spin_unlock_irqrestore ( & rtc - > lock , flags ) ;
return 0 ;
}
static int st_rtc_read_alarm ( struct device * dev , struct rtc_wkalrm * wkalrm )
{
struct st_rtc * rtc = dev_get_drvdata ( dev ) ;
unsigned long flags ;
spin_lock_irqsave ( & rtc - > lock , flags ) ;
memcpy ( wkalrm , & rtc - > alarm , sizeof ( struct rtc_wkalrm ) ) ;
spin_unlock_irqrestore ( & rtc - > lock , flags ) ;
return 0 ;
}
static int st_rtc_alarm_irq_enable ( struct device * dev , unsigned int enabled )
{
struct st_rtc * rtc = dev_get_drvdata ( dev ) ;
if ( enabled & & ! rtc - > irq_enabled ) {
enable_irq ( rtc - > irq ) ;
rtc - > irq_enabled = true ;
} else if ( ! enabled & & rtc - > irq_enabled ) {
disable_irq ( rtc - > irq ) ;
rtc - > irq_enabled = false ;
}
return 0 ;
}
static int st_rtc_set_alarm ( struct device * dev , struct rtc_wkalrm * t )
{
struct st_rtc * rtc = dev_get_drvdata ( dev ) ;
struct rtc_time now ;
2017-06-19 12:36:22 +03:00
unsigned long long now_secs ;
unsigned long long alarm_secs ;
2015-04-09 17:47:33 +03:00
unsigned long long lpa ;
st_rtc_read_time ( dev , & now ) ;
2017-06-19 12:36:22 +03:00
now_secs = rtc_tm_to_time64 ( & now ) ;
alarm_secs = rtc_tm_to_time64 ( & t - > time ) ;
2015-04-09 17:47:33 +03:00
memcpy ( & rtc - > alarm , t , sizeof ( struct rtc_wkalrm ) ) ;
/* Now many secs to fire */
alarm_secs - = now_secs ;
lpa = ( unsigned long long ) alarm_secs * rtc - > clkrate ;
st_rtc_set_hw_alarm ( rtc , lpa > > 32 , lpa ) ;
st_rtc_alarm_irq_enable ( dev , t - > enabled ) ;
return 0 ;
}
2020-09-13 15:26:44 +03:00
static const struct rtc_class_ops st_rtc_ops = {
2015-04-09 17:47:33 +03:00
. read_time = st_rtc_read_time ,
. set_time = st_rtc_set_time ,
. read_alarm = st_rtc_read_alarm ,
. set_alarm = st_rtc_set_alarm ,
. alarm_irq_enable = st_rtc_alarm_irq_enable ,
} ;
static int st_rtc_probe ( struct platform_device * pdev )
{
struct device_node * np = pdev - > dev . of_node ;
struct st_rtc * rtc ;
uint32_t mode ;
int ret = 0 ;
ret = of_property_read_u32 ( np , " st,lpc-mode " , & mode ) ;
if ( ret ) {
dev_err ( & pdev - > dev , " An LPC mode must be provided \n " ) ;
return - EINVAL ;
}
2015-05-12 15:58:15 +03:00
/* LPC can either run as a Clocksource or in RTC or WDT mode */
2015-04-09 17:47:33 +03:00
if ( mode ! = ST_LPC_MODE_RTC )
return - ENODEV ;
rtc = devm_kzalloc ( & pdev - > dev , sizeof ( struct st_rtc ) , GFP_KERNEL ) ;
if ( ! rtc )
return - ENOMEM ;
2018-05-20 15:33:36 +03:00
rtc - > rtc_dev = devm_rtc_allocate_device ( & pdev - > dev ) ;
if ( IS_ERR ( rtc - > rtc_dev ) )
return PTR_ERR ( rtc - > rtc_dev ) ;
2015-04-09 17:47:33 +03:00
spin_lock_init ( & rtc - > lock ) ;
2019-10-06 13:29:20 +03:00
rtc - > ioaddr = devm_platform_ioremap_resource ( pdev , 0 ) ;
2015-04-09 17:47:33 +03:00
if ( IS_ERR ( rtc - > ioaddr ) )
return PTR_ERR ( rtc - > ioaddr ) ;
rtc - > irq = irq_of_parse_and_map ( np , 0 ) ;
if ( ! rtc - > irq ) {
dev_err ( & pdev - > dev , " IRQ missing or invalid \n " ) ;
return - EINVAL ;
}
ret = devm_request_irq ( & pdev - > dev , rtc - > irq , st_rtc_handler , 0 ,
pdev - > name , rtc ) ;
if ( ret ) {
dev_err ( & pdev - > dev , " Failed to request irq %i \n " , rtc - > irq ) ;
return ret ;
}
enable_irq_wake ( rtc - > irq ) ;
disable_irq ( rtc - > irq ) ;
rtc - > clk = clk_get ( & pdev - > dev , NULL ) ;
if ( IS_ERR ( rtc - > clk ) ) {
dev_err ( & pdev - > dev , " Unable to request clock \n " ) ;
return PTR_ERR ( rtc - > clk ) ;
}
clk_prepare_enable ( rtc - > clk ) ;
rtc - > clkrate = clk_get_rate ( rtc - > clk ) ;
if ( ! rtc - > clkrate ) {
dev_err ( & pdev - > dev , " Unable to fetch clock rate \n " ) ;
return - EINVAL ;
}
device_set_wakeup_capable ( & pdev - > dev , 1 ) ;
platform_set_drvdata ( pdev , rtc ) ;
2018-05-20 15:33:36 +03:00
rtc - > rtc_dev - > ops = & st_rtc_ops ;
2018-05-21 23:49:05 +03:00
rtc - > rtc_dev - > range_max = U64_MAX ;
do_div ( rtc - > rtc_dev - > range_max , rtc - > clkrate ) ;
2018-05-20 15:33:36 +03:00
2020-11-09 19:34:08 +03:00
ret = devm_rtc_register_device ( rtc - > rtc_dev ) ;
2018-05-20 15:33:36 +03:00
if ( ret ) {
2015-04-09 17:47:33 +03:00
clk_disable_unprepare ( rtc - > clk ) ;
2018-05-20 15:33:36 +03:00
return ret ;
2015-04-09 17:47:33 +03:00
}
return 0 ;
}
# ifdef CONFIG_PM_SLEEP
static int st_rtc_suspend ( struct device * dev )
{
struct st_rtc * rtc = dev_get_drvdata ( dev ) ;
if ( device_may_wakeup ( dev ) )
return 0 ;
writel_relaxed ( 1 , rtc - > ioaddr + LPC_WDT_OFF ) ;
writel_relaxed ( 0 , rtc - > ioaddr + LPC_LPA_START_OFF ) ;
writel_relaxed ( 0 , rtc - > ioaddr + LPC_WDT_OFF ) ;
return 0 ;
}
static int st_rtc_resume ( struct device * dev )
{
struct st_rtc * rtc = dev_get_drvdata ( dev ) ;
rtc_alarm_irq_enable ( rtc - > rtc_dev , 0 ) ;
/*
* clean ' rtc - > alarm ' to allow a new
* . set_alarm to the upper RTC layer
*/
memset ( & rtc - > alarm , 0 , sizeof ( struct rtc_wkalrm ) ) ;
writel_relaxed ( 0 , rtc - > ioaddr + LPC_LPA_MSB_OFF ) ;
writel_relaxed ( 0 , rtc - > ioaddr + LPC_LPA_LSB_OFF ) ;
writel_relaxed ( 1 , rtc - > ioaddr + LPC_WDT_OFF ) ;
writel_relaxed ( 1 , rtc - > ioaddr + LPC_LPA_START_OFF ) ;
writel_relaxed ( 0 , rtc - > ioaddr + LPC_WDT_OFF ) ;
return 0 ;
}
# endif
static SIMPLE_DEV_PM_OPS ( st_rtc_pm_ops , st_rtc_suspend , st_rtc_resume ) ;
static const struct of_device_id st_rtc_match [ ] = {
{ . compatible = " st,stih407-lpc " } ,
{ }
} ;
MODULE_DEVICE_TABLE ( of , st_rtc_match ) ;
static struct platform_driver st_rtc_platform_driver = {
. driver = {
. name = " st-lpc-rtc " ,
. pm = & st_rtc_pm_ops ,
. of_match_table = st_rtc_match ,
} ,
. probe = st_rtc_probe ,
} ;
module_platform_driver ( st_rtc_platform_driver ) ;
MODULE_DESCRIPTION ( " STMicroelectronics LPC RTC driver " ) ;
MODULE_AUTHOR ( " David Paris <david.paris@st.com> " ) ;
MODULE_LICENSE ( " GPL " ) ;