2019-05-27 09:55:06 +03:00
// SPDX-License-Identifier: GPL-2.0-or-later
2015-05-20 18:49:31 +03:00
/*
2017-05-30 10:53:32 +03:00
* Faraday Technology FTRTC010 driver
2015-05-20 18:49:31 +03:00
*
* Copyright ( C ) 2009 Janos Laube < janos . dev @ gmail . com >
*
* Original code for older kernel 2.6 .15 are from Stormlinksemi
* first update from Janos Laube for > 2.6 .29 kernels
*
* checkpatch fixes and usage of rtc - lib code
* Hans Ulli Kroll < ulli . kroll @ googlemail . com >
*/
# include <linux/rtc.h>
# include <linux/io.h>
# include <linux/slab.h>
# include <linux/platform_device.h>
# include <linux/kernel.h>
# include <linux/module.h>
2018-06-20 08:47:28 +03:00
# include <linux/mod_devicetable.h>
2017-05-30 10:53:30 +03:00
# include <linux/clk.h>
2015-05-20 18:49:31 +03:00
2017-05-30 10:53:32 +03:00
# define DRV_NAME "rtc-ftrtc010"
2015-05-20 18:49:31 +03:00
MODULE_AUTHOR ( " Hans Ulli Kroll <ulli.kroll@googlemail.com> " ) ;
MODULE_DESCRIPTION ( " RTC driver for Gemini SoC " ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_ALIAS ( " platform: " DRV_NAME ) ;
2017-05-30 10:53:32 +03:00
struct ftrtc010_rtc {
2015-05-20 18:49:31 +03:00
struct rtc_device * rtc_dev ;
void __iomem * rtc_base ;
int rtc_irq ;
2017-05-30 10:53:30 +03:00
struct clk * pclk ;
struct clk * extclk ;
2015-05-20 18:49:31 +03:00
} ;
2017-05-30 10:53:32 +03:00
enum ftrtc010_rtc_offsets {
FTRTC010_RTC_SECOND = 0x00 ,
FTRTC010_RTC_MINUTE = 0x04 ,
FTRTC010_RTC_HOUR = 0x08 ,
FTRTC010_RTC_DAYS = 0x0C ,
FTRTC010_RTC_ALARM_SECOND = 0x10 ,
FTRTC010_RTC_ALARM_MINUTE = 0x14 ,
FTRTC010_RTC_ALARM_HOUR = 0x18 ,
FTRTC010_RTC_RECORD = 0x1C ,
FTRTC010_RTC_CR = 0x20 ,
2015-05-20 18:49:31 +03:00
} ;
2017-05-30 10:53:32 +03:00
static irqreturn_t ftrtc010_rtc_interrupt ( int irq , void * dev )
2015-05-20 18:49:31 +03:00
{
return IRQ_HANDLED ;
}
/*
* Looks like the RTC in the Gemini SoC is ( totaly ) broken
* We can ' t read / write directly the time from RTC registers .
* We must do some " offset " calculation to get the real time
*
* This FIX works pretty fine and Stormlinksemi aka Cortina - Networks does
* the same thing , without the rtc - lib . c calls .
*/
2017-05-30 10:53:32 +03:00
static int ftrtc010_rtc_read_time ( struct device * dev , struct rtc_time * tm )
2015-05-20 18:49:31 +03:00
{
2017-05-30 10:53:32 +03:00
struct ftrtc010_rtc * rtc = dev_get_drvdata ( dev ) ;
2015-05-20 18:49:31 +03:00
2018-06-04 17:15:27 +03:00
u32 days , hour , min , sec , offset ;
timeu64_t time ;
2015-05-20 18:49:31 +03:00
2017-05-30 10:53:32 +03:00
sec = readl ( rtc - > rtc_base + FTRTC010_RTC_SECOND ) ;
min = readl ( rtc - > rtc_base + FTRTC010_RTC_MINUTE ) ;
hour = readl ( rtc - > rtc_base + FTRTC010_RTC_HOUR ) ;
days = readl ( rtc - > rtc_base + FTRTC010_RTC_DAYS ) ;
offset = readl ( rtc - > rtc_base + FTRTC010_RTC_RECORD ) ;
2015-05-20 18:49:31 +03:00
time = offset + days * 86400 + hour * 3600 + min * 60 + sec ;
2018-06-04 17:15:27 +03:00
rtc_time64_to_tm ( time , tm ) ;
2015-05-20 18:49:31 +03:00
return 0 ;
}
2017-05-30 10:53:32 +03:00
static int ftrtc010_rtc_set_time ( struct device * dev , struct rtc_time * tm )
2015-05-20 18:49:31 +03:00
{
2017-05-30 10:53:32 +03:00
struct ftrtc010_rtc * rtc = dev_get_drvdata ( dev ) ;
2018-06-04 17:15:27 +03:00
u32 sec , min , hour , day , offset ;
timeu64_t time ;
2015-05-20 18:49:31 +03:00
2018-06-04 17:15:27 +03:00
time = rtc_tm_to_time64 ( tm ) ;
2015-05-20 18:49:31 +03:00
2017-05-30 10:53:32 +03:00
sec = readl ( rtc - > rtc_base + FTRTC010_RTC_SECOND ) ;
min = readl ( rtc - > rtc_base + FTRTC010_RTC_MINUTE ) ;
hour = readl ( rtc - > rtc_base + FTRTC010_RTC_HOUR ) ;
day = readl ( rtc - > rtc_base + FTRTC010_RTC_DAYS ) ;
2015-05-20 18:49:31 +03:00
offset = time - ( day * 86400 + hour * 3600 + min * 60 + sec ) ;
2017-05-30 10:53:32 +03:00
writel ( offset , rtc - > rtc_base + FTRTC010_RTC_RECORD ) ;
writel ( 0x01 , rtc - > rtc_base + FTRTC010_RTC_CR ) ;
2015-05-20 18:49:31 +03:00
return 0 ;
}
2017-05-30 10:53:32 +03:00
static const struct rtc_class_ops ftrtc010_rtc_ops = {
. read_time = ftrtc010_rtc_read_time ,
. set_time = ftrtc010_rtc_set_time ,
2015-05-20 18:49:31 +03:00
} ;
2017-05-30 10:53:32 +03:00
static int ftrtc010_rtc_probe ( struct platform_device * pdev )
2015-05-20 18:49:31 +03:00
{
2018-06-04 17:15:28 +03:00
u32 days , hour , min , sec ;
2017-05-30 10:53:32 +03:00
struct ftrtc010_rtc * rtc ;
2015-05-20 18:49:31 +03:00
struct device * dev = & pdev - > dev ;
struct resource * res ;
int ret ;
rtc = devm_kzalloc ( & pdev - > dev , sizeof ( * rtc ) , GFP_KERNEL ) ;
if ( unlikely ( ! rtc ) )
return - ENOMEM ;
platform_set_drvdata ( pdev , rtc ) ;
2017-05-30 10:53:30 +03:00
rtc - > pclk = devm_clk_get ( dev , " PCLK " ) ;
if ( IS_ERR ( rtc - > pclk ) ) {
dev_err ( dev , " could not get PCLK \n " ) ;
} else {
ret = clk_prepare_enable ( rtc - > pclk ) ;
if ( ret ) {
dev_err ( dev , " failed to enable PCLK \n " ) ;
return ret ;
}
}
rtc - > extclk = devm_clk_get ( dev , " EXTCLK " ) ;
if ( IS_ERR ( rtc - > extclk ) ) {
dev_err ( dev , " could not get EXTCLK \n " ) ;
} else {
ret = clk_prepare_enable ( rtc - > extclk ) ;
if ( ret ) {
dev_err ( dev , " failed to enable EXTCLK \n " ) ;
return ret ;
}
}
2021-12-20 04:15:24 +03:00
rtc - > rtc_irq = platform_get_irq ( pdev , 0 ) ;
if ( rtc - > rtc_irq < 0 )
return rtc - > rtc_irq ;
2015-05-20 18:49:31 +03:00
res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
if ( ! res )
return - ENODEV ;
rtc - > rtc_base = devm_ioremap ( dev , res - > start ,
2015-06-13 16:16:56 +03:00
resource_size ( res ) ) ;
2017-04-23 15:48:07 +03:00
if ( ! rtc - > rtc_base )
return - ENOMEM ;
2015-05-20 18:49:31 +03:00
2018-06-04 17:15:26 +03:00
rtc - > rtc_dev = devm_rtc_allocate_device ( dev ) ;
if ( IS_ERR ( rtc - > rtc_dev ) )
return PTR_ERR ( rtc - > rtc_dev ) ;
rtc - > rtc_dev - > ops = & ftrtc010_rtc_ops ;
2018-06-04 17:15:28 +03:00
sec = readl ( rtc - > rtc_base + FTRTC010_RTC_SECOND ) ;
min = readl ( rtc - > rtc_base + FTRTC010_RTC_MINUTE ) ;
hour = readl ( rtc - > rtc_base + FTRTC010_RTC_HOUR ) ;
days = readl ( rtc - > rtc_base + FTRTC010_RTC_DAYS ) ;
rtc - > rtc_dev - > range_min = ( u64 ) days * 86400 + hour * 3600 +
min * 60 + sec ;
rtc - > rtc_dev - > range_max = U32_MAX + rtc - > rtc_dev - > range_min ;
2017-05-30 10:53:32 +03:00
ret = devm_request_irq ( dev , rtc - > rtc_irq , ftrtc010_rtc_interrupt ,
2015-05-20 18:49:31 +03:00
IRQF_SHARED , pdev - > name , dev ) ;
if ( unlikely ( ret ) )
return ret ;
2020-11-09 19:34:08 +03:00
return devm_rtc_register_device ( rtc - > rtc_dev ) ;
2015-05-20 18:49:31 +03:00
}
2017-05-30 10:53:32 +03:00
static int ftrtc010_rtc_remove ( struct platform_device * pdev )
2015-05-20 18:49:31 +03:00
{
2017-05-30 10:53:32 +03:00
struct ftrtc010_rtc * rtc = platform_get_drvdata ( pdev ) ;
2015-05-20 18:49:31 +03:00
2017-05-30 10:53:30 +03:00
if ( ! IS_ERR ( rtc - > extclk ) )
clk_disable_unprepare ( rtc - > extclk ) ;
if ( ! IS_ERR ( rtc - > pclk ) )
clk_disable_unprepare ( rtc - > pclk ) ;
2015-05-20 18:49:31 +03:00
return 0 ;
}
2017-05-30 10:53:32 +03:00
static const struct of_device_id ftrtc010_rtc_dt_match [ ] = {
2017-01-22 15:19:50 +03:00
{ . compatible = " cortina,gemini-rtc " } ,
2017-05-30 10:53:32 +03:00
{ . compatible = " faraday,ftrtc010 " } ,
2017-01-22 15:19:50 +03:00
{ }
} ;
2017-05-30 10:53:32 +03:00
MODULE_DEVICE_TABLE ( of , ftrtc010_rtc_dt_match ) ;
2017-01-22 15:19:50 +03:00
2017-05-30 10:53:32 +03:00
static struct platform_driver ftrtc010_rtc_driver = {
2015-05-20 18:49:31 +03:00
. driver = {
. name = DRV_NAME ,
2017-05-30 10:53:32 +03:00
. of_match_table = ftrtc010_rtc_dt_match ,
2015-05-20 18:49:31 +03:00
} ,
2017-05-30 10:53:32 +03:00
. probe = ftrtc010_rtc_probe ,
. remove = ftrtc010_rtc_remove ,
2015-05-20 18:49:31 +03:00
} ;
2017-05-30 10:53:32 +03:00
module_platform_driver_probe ( ftrtc010_rtc_driver , ftrtc010_rtc_probe ) ;