2006-06-28 15:26:47 +04:00
/*
* A SPI driver for the Ricoh RS5C348 RTC
*
* Copyright ( C ) 2006 Atsushi Nemoto < anemo @ mba . ocn . ne . jp >
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation .
*
* The board specific init code should provide characteristics of this
* device :
* Mode 1 ( High - Active , Shift - Then - Sample ) , High Avtive CS
*/
# include <linux/bcd.h>
# include <linux/delay.h>
# include <linux/device.h>
# include <linux/errno.h>
# include <linux/init.h>
# include <linux/kernel.h>
# include <linux/string.h>
# include <linux/rtc.h>
# include <linux/workqueue.h>
# include <linux/spi/spi.h>
2006-09-29 13:01:37 +04:00
# define DRV_VERSION "0.2"
2006-06-28 15:26:47 +04:00
# define RS5C348_REG_SECS 0
# define RS5C348_REG_MINS 1
# define RS5C348_REG_HOURS 2
# define RS5C348_REG_WDAY 3
# define RS5C348_REG_DAY 4
# define RS5C348_REG_MONTH 5
# define RS5C348_REG_YEAR 6
# define RS5C348_REG_CTL1 14
# define RS5C348_REG_CTL2 15
# define RS5C348_SECS_MASK 0x7f
# define RS5C348_MINS_MASK 0x7f
# define RS5C348_HOURS_MASK 0x3f
# define RS5C348_WDAY_MASK 0x03
# define RS5C348_DAY_MASK 0x3f
# define RS5C348_MONTH_MASK 0x1f
# define RS5C348_BIT_PM 0x20 /* REG_HOURS */
# define RS5C348_BIT_Y2K 0x80 /* REG_MONTH */
# define RS5C348_BIT_24H 0x20 /* REG_CTL1 */
# define RS5C348_BIT_XSTP 0x10 /* REG_CTL2 */
# define RS5C348_BIT_VDET 0x40 /* REG_CTL2 */
# define RS5C348_CMD_W(addr) (((addr) << 4) | 0x08) /* single write */
# define RS5C348_CMD_R(addr) (((addr) << 4) | 0x0c) /* single read */
# define RS5C348_CMD_MW(addr) (((addr) << 4) | 0x00) /* burst write */
# define RS5C348_CMD_MR(addr) (((addr) << 4) | 0x04) /* burst read */
struct rs5c348_plat_data {
struct rtc_device * rtc ;
int rtc_24h ;
} ;
static int
rs5c348_rtc_set_time ( struct device * dev , struct rtc_time * tm )
{
struct spi_device * spi = to_spi_device ( dev ) ;
struct rs5c348_plat_data * pdata = spi - > dev . platform_data ;
u8 txbuf [ 5 + 7 ] , * txp ;
int ret ;
/* Transfer 5 bytes before writing SEC. This gives 31us for carry. */
txp = txbuf ;
txbuf [ 0 ] = RS5C348_CMD_R ( RS5C348_REG_CTL2 ) ; /* cmd, ctl2 */
txbuf [ 1 ] = 0 ; /* dummy */
txbuf [ 2 ] = RS5C348_CMD_R ( RS5C348_REG_CTL2 ) ; /* cmd, ctl2 */
txbuf [ 3 ] = 0 ; /* dummy */
txbuf [ 4 ] = RS5C348_CMD_MW ( RS5C348_REG_SECS ) ; /* cmd, sec, ... */
txp = & txbuf [ 5 ] ;
2008-10-19 07:28:41 +04:00
txp [ RS5C348_REG_SECS ] = bin2bcd ( tm - > tm_sec ) ;
txp [ RS5C348_REG_MINS ] = bin2bcd ( tm - > tm_min ) ;
2006-06-28 15:26:47 +04:00
if ( pdata - > rtc_24h ) {
2008-10-19 07:28:41 +04:00
txp [ RS5C348_REG_HOURS ] = bin2bcd ( tm - > tm_hour ) ;
2006-06-28 15:26:47 +04:00
} else {
/* hour 0 is AM12, noon is PM12 */
2008-10-19 07:28:41 +04:00
txp [ RS5C348_REG_HOURS ] = bin2bcd ( ( tm - > tm_hour + 11 ) % 12 + 1 ) |
2006-06-28 15:26:47 +04:00
( tm - > tm_hour > = 12 ? RS5C348_BIT_PM : 0 ) ;
}
2008-10-19 07:28:41 +04:00
txp [ RS5C348_REG_WDAY ] = bin2bcd ( tm - > tm_wday ) ;
txp [ RS5C348_REG_DAY ] = bin2bcd ( tm - > tm_mday ) ;
txp [ RS5C348_REG_MONTH ] = bin2bcd ( tm - > tm_mon + 1 ) |
2006-06-28 15:26:47 +04:00
( tm - > tm_year > = 100 ? RS5C348_BIT_Y2K : 0 ) ;
2008-10-19 07:28:41 +04:00
txp [ RS5C348_REG_YEAR ] = bin2bcd ( tm - > tm_year % 100 ) ;
2006-06-28 15:26:47 +04:00
/* write in one transfer to avoid data inconsistency */
ret = spi_write_then_read ( spi , txbuf , sizeof ( txbuf ) , NULL , 0 ) ;
udelay ( 62 ) ; /* Tcsr 62us */
return ret ;
}
static int
rs5c348_rtc_read_time ( struct device * dev , struct rtc_time * tm )
{
struct spi_device * spi = to_spi_device ( dev ) ;
struct rs5c348_plat_data * pdata = spi - > dev . platform_data ;
u8 txbuf [ 5 ] , rxbuf [ 7 ] ;
int ret ;
/* Transfer 5 byte befores reading SEC. This gives 31us for carry. */
txbuf [ 0 ] = RS5C348_CMD_R ( RS5C348_REG_CTL2 ) ; /* cmd, ctl2 */
txbuf [ 1 ] = 0 ; /* dummy */
txbuf [ 2 ] = RS5C348_CMD_R ( RS5C348_REG_CTL2 ) ; /* cmd, ctl2 */
txbuf [ 3 ] = 0 ; /* dummy */
txbuf [ 4 ] = RS5C348_CMD_MR ( RS5C348_REG_SECS ) ; /* cmd, sec, ... */
/* read in one transfer to avoid data inconsistency */
ret = spi_write_then_read ( spi , txbuf , sizeof ( txbuf ) ,
rxbuf , sizeof ( rxbuf ) ) ;
udelay ( 62 ) ; /* Tcsr 62us */
if ( ret < 0 )
return ret ;
2008-10-19 07:28:41 +04:00
tm - > tm_sec = bcd2bin ( rxbuf [ RS5C348_REG_SECS ] & RS5C348_SECS_MASK ) ;
tm - > tm_min = bcd2bin ( rxbuf [ RS5C348_REG_MINS ] & RS5C348_MINS_MASK ) ;
tm - > tm_hour = bcd2bin ( rxbuf [ RS5C348_REG_HOURS ] & RS5C348_HOURS_MASK ) ;
2006-06-28 15:26:47 +04:00
if ( ! pdata - > rtc_24h ) {
tm - > tm_hour % = 12 ;
if ( rxbuf [ RS5C348_REG_HOURS ] & RS5C348_BIT_PM )
tm - > tm_hour + = 12 ;
}
2008-10-19 07:28:41 +04:00
tm - > tm_wday = bcd2bin ( rxbuf [ RS5C348_REG_WDAY ] & RS5C348_WDAY_MASK ) ;
tm - > tm_mday = bcd2bin ( rxbuf [ RS5C348_REG_DAY ] & RS5C348_DAY_MASK ) ;
2006-06-28 15:26:47 +04:00
tm - > tm_mon =
2008-10-19 07:28:41 +04:00
bcd2bin ( rxbuf [ RS5C348_REG_MONTH ] & RS5C348_MONTH_MASK ) - 1 ;
2006-06-28 15:26:47 +04:00
/* year is 1900 + tm->tm_year */
2008-10-19 07:28:41 +04:00
tm - > tm_year = bcd2bin ( rxbuf [ RS5C348_REG_YEAR ] ) +
2006-06-28 15:26:47 +04:00
( ( rxbuf [ RS5C348_REG_MONTH ] & RS5C348_BIT_Y2K ) ? 100 : 0 ) ;
if ( rtc_valid_tm ( tm ) < 0 ) {
dev_err ( & spi - > dev , " retrieved date/time is not valid. \n " ) ;
rtc_time_to_tm ( 0 , tm ) ;
}
return 0 ;
}
2006-10-01 10:28:17 +04:00
static const struct rtc_class_ops rs5c348_rtc_ops = {
2006-06-28 15:26:47 +04:00
. read_time = rs5c348_rtc_read_time ,
. set_time = rs5c348_rtc_set_time ,
} ;
static struct spi_driver rs5c348_driver ;
static int __devinit rs5c348_probe ( struct spi_device * spi )
{
int ret ;
struct rtc_device * rtc ;
struct rs5c348_plat_data * pdata ;
pdata = kzalloc ( sizeof ( struct rs5c348_plat_data ) , GFP_KERNEL ) ;
if ( ! pdata )
return - ENOMEM ;
spi - > dev . platform_data = pdata ;
/* Check D7 of SECOND register */
ret = spi_w8r8 ( spi , RS5C348_CMD_R ( RS5C348_REG_SECS ) ) ;
if ( ret < 0 | | ( ret & 0x80 ) ) {
dev_err ( & spi - > dev , " not found. \n " ) ;
goto kfree_exit ;
}
dev_info ( & spi - > dev , " chip found, driver version " DRV_VERSION " \n " ) ;
dev_info ( & spi - > dev , " spiclk %u KHz. \n " ,
( spi - > max_speed_hz + 500 ) / 1000 ) ;
/* turn RTC on if it was not on */
ret = spi_w8r8 ( spi , RS5C348_CMD_R ( RS5C348_REG_CTL2 ) ) ;
if ( ret < 0 )
goto kfree_exit ;
if ( ret & ( RS5C348_BIT_XSTP | RS5C348_BIT_VDET ) ) {
u8 buf [ 2 ] ;
2006-09-29 13:01:37 +04:00
struct rtc_time tm ;
2006-06-28 15:26:47 +04:00
if ( ret & RS5C348_BIT_VDET )
dev_warn ( & spi - > dev , " voltage-low detected. \n " ) ;
2006-09-29 13:01:37 +04:00
if ( ret & RS5C348_BIT_XSTP )
dev_warn ( & spi - > dev , " oscillator-stop detected. \n " ) ;
rtc_time_to_tm ( 0 , & tm ) ; /* 1970/1/1 */
ret = rs5c348_rtc_set_time ( & spi - > dev , & tm ) ;
if ( ret < 0 )
goto kfree_exit ;
2006-06-28 15:26:47 +04:00
buf [ 0 ] = RS5C348_CMD_W ( RS5C348_REG_CTL2 ) ;
buf [ 1 ] = 0 ;
ret = spi_write_then_read ( spi , buf , sizeof ( buf ) , NULL , 0 ) ;
if ( ret < 0 )
goto kfree_exit ;
}
ret = spi_w8r8 ( spi , RS5C348_CMD_R ( RS5C348_REG_CTL1 ) ) ;
if ( ret < 0 )
goto kfree_exit ;
if ( ret & RS5C348_BIT_24H )
pdata - > rtc_24h = 1 ;
rtc = rtc_device_register ( rs5c348_driver . driver . name , & spi - > dev ,
& rs5c348_rtc_ops , THIS_MODULE ) ;
if ( IS_ERR ( rtc ) ) {
ret = PTR_ERR ( rtc ) ;
goto kfree_exit ;
}
pdata - > rtc = rtc ;
return 0 ;
kfree_exit :
kfree ( pdata ) ;
return ret ;
}
static int __devexit rs5c348_remove ( struct spi_device * spi )
{
struct rs5c348_plat_data * pdata = spi - > dev . platform_data ;
struct rtc_device * rtc = pdata - > rtc ;
if ( rtc )
rtc_device_unregister ( rtc ) ;
kfree ( pdata ) ;
return 0 ;
}
static struct spi_driver rs5c348_driver = {
. driver = {
2007-08-19 17:32:10 +04:00
. name = " rtc-rs5c348 " ,
2006-06-28 15:26:47 +04:00
. bus = & spi_bus_type ,
. owner = THIS_MODULE ,
} ,
. probe = rs5c348_probe ,
. remove = __devexit_p ( rs5c348_remove ) ,
} ;
static __init int rs5c348_init ( void )
{
return spi_register_driver ( & rs5c348_driver ) ;
}
static __exit void rs5c348_exit ( void )
{
spi_unregister_driver ( & rs5c348_driver ) ;
}
module_init ( rs5c348_init ) ;
module_exit ( rs5c348_exit ) ;
MODULE_AUTHOR ( " Atsushi Nemoto <anemo@mba.ocn.ne.jp> " ) ;
MODULE_DESCRIPTION ( " Ricoh RS5C348 RTC driver " ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_VERSION ( DRV_VERSION ) ;