2019-06-04 11:11:33 +03:00
// SPDX-License-Identifier: GPL-2.0-only
2006-03-27 13:16:47 +04:00
/*
* ST M48T86 / Dallas DS12887 RTC driver
* Copyright ( c ) 2006 Tower Technologies
*
* Author : Alessandro Zummo < a . zummo @ towertech . it >
*
* This drivers only supports the clock running in BCD and 24 H mode .
* If it will be ever adapted to binary and 12 H mode , care must be taken
* to not introduce bugs .
*/
# include <linux/module.h>
# include <linux/rtc.h>
# include <linux/platform_device.h>
# include <linux/bcd.h>
2017-02-10 21:11:56 +03:00
# include <linux/io.h>
2006-03-27 13:16:47 +04:00
2017-02-10 21:11:55 +03:00
# define M48T86_SEC 0x00
# define M48T86_SECALRM 0x01
# define M48T86_MIN 0x02
# define M48T86_MINALRM 0x03
# define M48T86_HOUR 0x04
# define M48T86_HOURALRM 0x05
# define M48T86_DOW 0x06 /* 1 = sunday */
# define M48T86_DOM 0x07
# define M48T86_MONTH 0x08 /* 1 - 12 */
# define M48T86_YEAR 0x09 /* 0 - 99 */
# define M48T86_A 0x0a
# define M48T86_B 0x0b
# define M48T86_B_SET BIT(7)
# define M48T86_B_DM BIT(2)
# define M48T86_B_H24 BIT(1)
# define M48T86_C 0x0c
# define M48T86_D 0x0d
# define M48T86_D_VRT BIT(7)
2017-02-10 21:11:57 +03:00
# define M48T86_NVRAM(x) (0x0e + (x))
# define M48T86_NVRAM_LEN 114
2006-03-27 13:16:47 +04:00
2017-02-10 21:11:56 +03:00
struct m48t86_rtc_info {
void __iomem * index_reg ;
void __iomem * data_reg ;
struct rtc_device * rtc ;
} ;
static unsigned char m48t86_readb ( struct device * dev , unsigned long addr )
{
struct m48t86_rtc_info * info = dev_get_drvdata ( dev ) ;
unsigned char value ;
2017-02-15 19:35:27 +03:00
writeb ( addr , info - > index_reg ) ;
value = readb ( info - > data_reg ) ;
2017-02-10 21:11:56 +03:00
return value ;
}
static void m48t86_writeb ( struct device * dev ,
unsigned char value , unsigned long addr )
{
struct m48t86_rtc_info * info = dev_get_drvdata ( dev ) ;
2017-02-15 19:35:27 +03:00
writeb ( addr , info - > index_reg ) ;
writeb ( value , info - > data_reg ) ;
2017-02-10 21:11:56 +03:00
}
2006-03-27 13:16:47 +04:00
static int m48t86_rtc_read_time ( struct device * dev , struct rtc_time * tm )
{
unsigned char reg ;
2017-02-10 21:11:56 +03:00
reg = m48t86_readb ( dev , M48T86_B ) ;
2006-03-27 13:16:47 +04:00
2017-02-10 21:11:55 +03:00
if ( reg & M48T86_B_DM ) {
2006-03-27 13:16:47 +04:00
/* data (binary) mode */
2017-02-10 21:11:56 +03:00
tm - > tm_sec = m48t86_readb ( dev , M48T86_SEC ) ;
tm - > tm_min = m48t86_readb ( dev , M48T86_MIN ) ;
tm - > tm_hour = m48t86_readb ( dev , M48T86_HOUR ) & 0x3f ;
tm - > tm_mday = m48t86_readb ( dev , M48T86_DOM ) ;
2006-03-27 13:16:47 +04:00
/* tm_mon is 0-11 */
2017-02-10 21:11:56 +03:00
tm - > tm_mon = m48t86_readb ( dev , M48T86_MONTH ) - 1 ;
tm - > tm_year = m48t86_readb ( dev , M48T86_YEAR ) + 100 ;
tm - > tm_wday = m48t86_readb ( dev , M48T86_DOW ) ;
2006-03-27 13:16:47 +04:00
} else {
/* bcd mode */
2017-02-10 21:11:56 +03:00
tm - > tm_sec = bcd2bin ( m48t86_readb ( dev , M48T86_SEC ) ) ;
tm - > tm_min = bcd2bin ( m48t86_readb ( dev , M48T86_MIN ) ) ;
tm - > tm_hour = bcd2bin ( m48t86_readb ( dev , M48T86_HOUR ) &
0x3f ) ;
tm - > tm_mday = bcd2bin ( m48t86_readb ( dev , M48T86_DOM ) ) ;
2006-03-27 13:16:47 +04:00
/* tm_mon is 0-11 */
2017-02-10 21:11:56 +03:00
tm - > tm_mon = bcd2bin ( m48t86_readb ( dev , M48T86_MONTH ) ) - 1 ;
tm - > tm_year = bcd2bin ( m48t86_readb ( dev , M48T86_YEAR ) ) + 100 ;
tm - > tm_wday = bcd2bin ( m48t86_readb ( dev , M48T86_DOW ) ) ;
2006-03-27 13:16:47 +04:00
}
/* correct the hour if the clock is in 12h mode */
2017-02-10 21:11:55 +03:00
if ( ! ( reg & M48T86_B_H24 ) )
2017-02-10 21:11:56 +03:00
if ( m48t86_readb ( dev , M48T86_HOUR ) & 0x80 )
2006-03-27 13:16:47 +04:00
tm - > tm_hour + = 12 ;
2018-02-19 18:23:56 +03:00
return 0 ;
2006-03-27 13:16:47 +04:00
}
static int m48t86_rtc_set_time ( struct device * dev , struct rtc_time * tm )
{
unsigned char reg ;
2017-02-10 21:11:56 +03:00
reg = m48t86_readb ( dev , M48T86_B ) ;
2006-03-27 13:16:47 +04:00
/* update flag and 24h mode */
2017-02-10 21:11:55 +03:00
reg | = M48T86_B_SET | M48T86_B_H24 ;
2017-02-10 21:11:56 +03:00
m48t86_writeb ( dev , reg , M48T86_B ) ;
2006-03-27 13:16:47 +04:00
2017-02-10 21:11:55 +03:00
if ( reg & M48T86_B_DM ) {
2006-03-27 13:16:47 +04:00
/* data (binary) mode */
2017-02-10 21:11:56 +03:00
m48t86_writeb ( dev , tm - > tm_sec , M48T86_SEC ) ;
m48t86_writeb ( dev , tm - > tm_min , M48T86_MIN ) ;
m48t86_writeb ( dev , tm - > tm_hour , M48T86_HOUR ) ;
m48t86_writeb ( dev , tm - > tm_mday , M48T86_DOM ) ;
m48t86_writeb ( dev , tm - > tm_mon + 1 , M48T86_MONTH ) ;
m48t86_writeb ( dev , tm - > tm_year % 100 , M48T86_YEAR ) ;
m48t86_writeb ( dev , tm - > tm_wday , M48T86_DOW ) ;
2006-03-27 13:16:47 +04:00
} else {
/* bcd mode */
2017-02-10 21:11:56 +03:00
m48t86_writeb ( dev , bin2bcd ( tm - > tm_sec ) , M48T86_SEC ) ;
m48t86_writeb ( dev , bin2bcd ( tm - > tm_min ) , M48T86_MIN ) ;
m48t86_writeb ( dev , bin2bcd ( tm - > tm_hour ) , M48T86_HOUR ) ;
m48t86_writeb ( dev , bin2bcd ( tm - > tm_mday ) , M48T86_DOM ) ;
m48t86_writeb ( dev , bin2bcd ( tm - > tm_mon + 1 ) , M48T86_MONTH ) ;
m48t86_writeb ( dev , bin2bcd ( tm - > tm_year % 100 ) , M48T86_YEAR ) ;
m48t86_writeb ( dev , bin2bcd ( tm - > tm_wday ) , M48T86_DOW ) ;
2006-03-27 13:16:47 +04:00
}
/* update ended */
2017-02-10 21:11:55 +03:00
reg & = ~ M48T86_B_SET ;
2017-02-10 21:11:56 +03:00
m48t86_writeb ( dev , reg , M48T86_B ) ;
2006-03-27 13:16:47 +04:00
return 0 ;
}
static int m48t86_rtc_proc ( struct device * dev , struct seq_file * seq )
{
unsigned char reg ;
2017-02-10 21:11:56 +03:00
reg = m48t86_readb ( dev , M48T86_B ) ;
2006-03-27 13:16:47 +04:00
seq_printf ( seq , " mode \t \t : %s \n " ,
2017-02-10 21:11:55 +03:00
( reg & M48T86_B_DM ) ? " binary " : " bcd " ) ;
2006-03-27 13:16:47 +04:00
2017-02-10 21:11:56 +03:00
reg = m48t86_readb ( dev , M48T86_D ) ;
2006-03-27 13:16:47 +04:00
seq_printf ( seq , " battery \t \t : %s \n " ,
2017-02-10 21:11:55 +03:00
( reg & M48T86_D_VRT ) ? " ok " : " exhausted " ) ;
2006-03-27 13:16:47 +04:00
return 0 ;
}
2006-10-01 10:28:17 +04:00
static const struct rtc_class_ops m48t86_rtc_ops = {
2006-03-27 13:16:47 +04:00
. read_time = m48t86_rtc_read_time ,
. set_time = m48t86_rtc_set_time ,
. proc = m48t86_rtc_proc ,
} ;
2017-10-13 01:04:21 +03:00
static int m48t86_nvram_read ( void * priv , unsigned int off , void * buf ,
size_t count )
2017-02-10 21:11:57 +03:00
{
2017-10-13 01:04:21 +03:00
struct device * dev = priv ;
2017-02-10 21:11:57 +03:00
unsigned int i ;
for ( i = 0 ; i < count ; i + + )
2017-10-13 01:04:21 +03:00
( ( u8 * ) buf ) [ i ] = m48t86_readb ( dev , M48T86_NVRAM ( off + i ) ) ;
2017-02-10 21:11:57 +03:00
2017-10-13 01:04:21 +03:00
return 0 ;
2017-02-10 21:11:57 +03:00
}
2017-10-13 01:04:21 +03:00
static int m48t86_nvram_write ( void * priv , unsigned int off , void * buf ,
size_t count )
2017-02-10 21:11:57 +03:00
{
2017-10-13 01:04:21 +03:00
struct device * dev = priv ;
2017-02-10 21:11:57 +03:00
unsigned int i ;
for ( i = 0 ; i < count ; i + + )
2017-10-13 01:04:21 +03:00
m48t86_writeb ( dev , ( ( u8 * ) buf ) [ i ] , M48T86_NVRAM ( off + i ) ) ;
2017-02-10 21:11:57 +03:00
2017-10-13 01:04:21 +03:00
return 0 ;
2017-02-10 21:11:57 +03:00
}
2017-02-15 19:35:23 +03:00
/*
* The RTC is an optional feature at purchase time on some Technologic Systems
* boards . Verify that it actually exists by checking if the last two bytes
* of the NVRAM can be changed .
*
* This is based on the method used in their rtc7800 . c example .
*/
static bool m48t86_verify_chip ( struct platform_device * pdev )
{
unsigned int offset0 = M48T86_NVRAM ( M48T86_NVRAM_LEN - 2 ) ;
unsigned int offset1 = M48T86_NVRAM ( M48T86_NVRAM_LEN - 1 ) ;
unsigned char tmp0 , tmp1 ;
tmp0 = m48t86_readb ( & pdev - > dev , offset0 ) ;
tmp1 = m48t86_readb ( & pdev - > dev , offset1 ) ;
m48t86_writeb ( & pdev - > dev , 0x00 , offset0 ) ;
m48t86_writeb ( & pdev - > dev , 0x55 , offset1 ) ;
if ( m48t86_readb ( & pdev - > dev , offset1 ) = = 0x55 ) {
m48t86_writeb ( & pdev - > dev , 0xaa , offset1 ) ;
if ( m48t86_readb ( & pdev - > dev , offset1 ) = = 0xaa & &
m48t86_readb ( & pdev - > dev , offset0 ) = = 0x00 ) {
m48t86_writeb ( & pdev - > dev , tmp0 , offset0 ) ;
m48t86_writeb ( & pdev - > dev , tmp1 , offset1 ) ;
return true ;
}
}
return false ;
}
2017-02-10 21:11:56 +03:00
static int m48t86_rtc_probe ( struct platform_device * pdev )
2006-03-27 13:16:47 +04:00
{
2017-02-10 21:11:56 +03:00
struct m48t86_rtc_info * info ;
2006-03-27 13:16:47 +04:00
unsigned char reg ;
2017-10-13 01:04:20 +03:00
int err ;
2018-02-13 01:47:27 +03:00
struct nvmem_config m48t86_nvmem_cfg = {
. name = " m48t86_nvram " ,
. word_size = 1 ,
. stride = 1 ,
. size = M48T86_NVRAM_LEN ,
. reg_read = m48t86_nvram_read ,
. reg_write = m48t86_nvram_write ,
. priv = & pdev - > dev ,
} ;
2013-04-30 03:19:42 +04:00
2017-02-10 21:11:56 +03:00
info = devm_kzalloc ( & pdev - > dev , sizeof ( * info ) , GFP_KERNEL ) ;
if ( ! info )
return - ENOMEM ;
2019-09-21 12:49:01 +03:00
info - > index_reg = devm_platform_ioremap_resource ( pdev , 0 ) ;
2017-02-15 19:35:27 +03:00
if ( IS_ERR ( info - > index_reg ) )
return PTR_ERR ( info - > index_reg ) ;
2019-09-21 12:49:01 +03:00
info - > data_reg = devm_platform_ioremap_resource ( pdev , 1 ) ;
2017-02-15 19:35:27 +03:00
if ( IS_ERR ( info - > data_reg ) )
return PTR_ERR ( info - > data_reg ) ;
2006-03-27 13:16:47 +04:00
2017-02-10 21:11:56 +03:00
dev_set_drvdata ( & pdev - > dev , info ) ;
2006-03-27 13:16:47 +04:00
2017-02-15 19:35:23 +03:00
if ( ! m48t86_verify_chip ( pdev ) ) {
dev_info ( & pdev - > dev , " RTC not present \n " ) ;
return - ENODEV ;
}
2017-10-13 01:04:20 +03:00
info - > rtc = devm_rtc_allocate_device ( & pdev - > dev ) ;
2017-02-10 21:11:56 +03:00
if ( IS_ERR ( info - > rtc ) )
return PTR_ERR ( info - > rtc ) ;
2006-03-27 13:16:47 +04:00
2017-10-13 01:04:20 +03:00
info - > rtc - > ops = & m48t86_rtc_ops ;
2017-10-13 01:04:21 +03:00
info - > rtc - > nvram_old_abi = true ;
2017-10-13 01:04:20 +03:00
err = rtc_register_device ( info - > rtc ) ;
if ( err )
return err ;
2018-02-13 01:47:26 +03:00
rtc_nvmem_register ( info - > rtc , & m48t86_nvmem_cfg ) ;
2006-03-27 13:16:47 +04:00
/* read battery status */
2017-02-10 21:11:56 +03:00
reg = m48t86_readb ( & pdev - > dev , M48T86_D ) ;
dev_info ( & pdev - > dev , " battery %s \n " ,
2017-02-10 21:11:55 +03:00
( reg & M48T86_D_VRT ) ? " ok " : " exhausted " ) ;
2006-03-27 13:16:47 +04:00
return 0 ;
}
static struct platform_driver m48t86_rtc_platform_driver = {
. driver = {
. name = " rtc-m48t86 " ,
} ,
. probe = m48t86_rtc_probe ,
} ;
2012-01-11 03:10:48 +04:00
module_platform_driver ( m48t86_rtc_platform_driver ) ;
2006-03-27 13:16:47 +04:00
MODULE_AUTHOR ( " Alessandro Zummo <a.zummo@towertech.it> " ) ;
MODULE_DESCRIPTION ( " M48T86 RTC driver " ) ;
MODULE_LICENSE ( " GPL " ) ;
2008-04-11 08:29:25 +04:00
MODULE_ALIAS ( " platform:rtc-m48t86 " ) ;