2005-06-04 01:36:06 +04:00
/*
* drivers / i2c / chips / ds1374 . c
*
* I2C client / driver for the Maxim / Dallas DS1374 Real - Time Clock
*
* Author : Randy Vinson < rvinson @ mvista . com >
*
* Based on the m41t00 . c by Mark Greer < mgreer @ mvista . com >
*
* 2005 ( c ) MontaVista Software , Inc . This file is licensed under
* the terms of the GNU General Public License version 2. This program
* is licensed " as is " without any warranty of any kind , whether express
* or implied .
*/
/*
* This i2c client / driver wedges between the drivers / char / genrtc . c RTC
* interface and the SMBus interface of the i2c subsystem .
* It would be more efficient to use i2c msgs / i2c_transfer directly but , as
* recommened in . . . / Documentation / i2c / writing - clients section
* " Sending and receiving " , using SMBus level communication is preferred .
*/
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/interrupt.h>
# include <linux/i2c.h>
# include <linux/rtc.h>
# include <linux/bcd.h>
2006-01-11 12:50:26 +03:00
# include <linux/mutex.h>
2005-06-04 01:36:06 +04:00
# define DS1374_REG_TOD0 0x00
# define DS1374_REG_TOD1 0x01
# define DS1374_REG_TOD2 0x02
# define DS1374_REG_TOD3 0x03
# define DS1374_REG_WDALM0 0x04
# define DS1374_REG_WDALM1 0x05
# define DS1374_REG_WDALM2 0x06
# define DS1374_REG_CR 0x07
# define DS1374_REG_SR 0x08
# define DS1374_REG_SR_OSF 0x80
# define DS1374_REG_TCR 0x09
# define DS1374_DRV_NAME "ds1374"
2006-01-11 12:50:26 +03:00
static DEFINE_MUTEX ( ds1374_mutex ) ;
2005-06-04 01:36:06 +04:00
static struct i2c_driver ds1374_driver ;
static struct i2c_client * save_client ;
static unsigned short ignore [ ] = { I2C_CLIENT_END } ;
static unsigned short normal_addr [ ] = { 0x68 , I2C_CLIENT_END } ;
static struct i2c_client_address_data addr_data = {
. normal_i2c = normal_addr ,
. probe = ignore ,
. ignore = ignore ,
} ;
static ulong ds1374_read_rtc ( void )
{
ulong time = 0 ;
int reg = DS1374_REG_WDALM0 ;
while ( reg - - ) {
s32 tmp ;
if ( ( tmp = i2c_smbus_read_byte_data ( save_client , reg ) ) < 0 ) {
dev_warn ( & save_client - > dev ,
" can't read from rtc chip \n " ) ;
return 0 ;
}
time = ( time < < 8 ) | ( tmp & 0xff ) ;
}
return time ;
}
static void ds1374_write_rtc ( ulong time )
{
int reg ;
for ( reg = DS1374_REG_TOD0 ; reg < DS1374_REG_WDALM0 ; reg + + ) {
if ( i2c_smbus_write_byte_data ( save_client , reg , time & 0xff )
< 0 ) {
dev_warn ( & save_client - > dev ,
" can't write to rtc chip \n " ) ;
break ;
}
time = time > > 8 ;
}
}
static void ds1374_check_rtc_status ( void )
{
s32 tmp ;
tmp = i2c_smbus_read_byte_data ( save_client , DS1374_REG_SR ) ;
if ( tmp < 0 ) {
dev_warn ( & save_client - > dev ,
" can't read status from rtc chip \n " ) ;
return ;
}
if ( tmp & DS1374_REG_SR_OSF ) {
dev_warn ( & save_client - > dev ,
" oscillator discontinuity flagged, time unreliable \n " ) ;
tmp & = ~ DS1374_REG_SR_OSF ;
tmp = i2c_smbus_write_byte_data ( save_client , DS1374_REG_SR ,
tmp & 0xff ) ;
if ( tmp < 0 )
dev_warn ( & save_client - > dev ,
" can't clear discontinuity notification \n " ) ;
}
}
ulong ds1374_get_rtc_time ( void )
{
ulong t1 , t2 ;
int limit = 10 ; /* arbitrary retry limit */
2006-01-11 12:50:26 +03:00
mutex_lock ( & ds1374_mutex ) ;
2005-06-04 01:36:06 +04:00
/*
* Since the reads are being performed one byte at a time using
* the SMBus vs a 4 - byte i2c transfer , there is a chance that a
* carry will occur during the read . To detect this , 2 reads are
* performed and compared .
*/
do {
t1 = ds1374_read_rtc ( ) ;
t2 = ds1374_read_rtc ( ) ;
} while ( t1 ! = t2 & & limit - - ) ;
2006-01-11 12:50:26 +03:00
mutex_unlock ( & ds1374_mutex ) ;
2005-06-04 01:36:06 +04:00
if ( t1 ! = t2 ) {
dev_warn ( & save_client - > dev ,
" can't get consistent time from rtc chip \n " ) ;
t1 = 0 ;
}
return t1 ;
}
static void ds1374_set_tlet ( ulong arg )
{
ulong t1 , t2 ;
int limit = 10 ; /* arbitrary retry limit */
t1 = * ( ulong * ) arg ;
2006-01-11 12:50:26 +03:00
mutex_lock ( & ds1374_mutex ) ;
2005-06-04 01:36:06 +04:00
/*
* Since the writes are being performed one byte at a time using
* the SMBus vs a 4 - byte i2c transfer , there is a chance that a
* carry will occur during the write . To detect this , the write
* value is read back and compared .
*/
do {
ds1374_write_rtc ( t1 ) ;
t2 = ds1374_read_rtc ( ) ;
} while ( t1 ! = t2 & & limit - - ) ;
2006-01-11 12:50:26 +03:00
mutex_unlock ( & ds1374_mutex ) ;
2005-06-04 01:36:06 +04:00
if ( t1 ! = t2 )
dev_warn ( & save_client - > dev ,
" can't confirm time set from rtc chip \n " ) ;
}
2005-09-02 05:12:04 +04:00
static ulong new_time ;
2005-06-04 01:36:06 +04:00
2005-10-26 23:09:41 +04:00
static DECLARE_TASKLET_DISABLED ( ds1374_tasklet , ds1374_set_tlet ,
( ulong ) & new_time ) ;
2005-06-04 01:36:06 +04:00
int ds1374_set_rtc_time ( ulong nowtime )
{
new_time = nowtime ;
if ( in_interrupt ( ) )
tasklet_schedule ( & ds1374_tasklet ) ;
else
ds1374_set_tlet ( ( ulong ) & new_time ) ;
return 0 ;
}
/*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Driver Interface
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*/
static int ds1374_probe ( struct i2c_adapter * adap , int addr , int kind )
{
struct i2c_client * client ;
int rc ;
2005-10-18 01:09:43 +04:00
client = kzalloc ( sizeof ( struct i2c_client ) , GFP_KERNEL ) ;
2005-06-04 01:36:06 +04:00
if ( ! client )
return - ENOMEM ;
strncpy ( client - > name , DS1374_DRV_NAME , I2C_NAME_SIZE ) ;
client - > addr = addr ;
client - > adapter = adap ;
client - > driver = & ds1374_driver ;
if ( ( rc = i2c_attach_client ( client ) ) ! = 0 ) {
kfree ( client ) ;
return rc ;
}
save_client = client ;
ds1374_check_rtc_status ( ) ;
return 0 ;
}
static int ds1374_attach ( struct i2c_adapter * adap )
{
return i2c_probe ( adap , & addr_data , ds1374_probe ) ;
}
static int ds1374_detach ( struct i2c_client * client )
{
int rc ;
if ( ( rc = i2c_detach_client ( client ) ) = = 0 ) {
kfree ( i2c_get_clientdata ( client ) ) ;
tasklet_kill ( & ds1374_tasklet ) ;
}
return rc ;
}
static struct i2c_driver ds1374_driver = {
2005-11-26 22:36:00 +03:00
. driver = {
. name = DS1374_DRV_NAME ,
} ,
2005-06-04 01:36:06 +04:00
. id = I2C_DRIVERID_DS1374 ,
. attach_adapter = ds1374_attach ,
. detach_client = ds1374_detach ,
} ;
static int __init ds1374_init ( void )
{
return i2c_add_driver ( & ds1374_driver ) ;
}
static void __exit ds1374_exit ( void )
{
i2c_del_driver ( & ds1374_driver ) ;
}
module_init ( ds1374_init ) ;
module_exit ( ds1374_exit ) ;
MODULE_AUTHOR ( " Randy Vinson <rvinson@mvista.com> " ) ;
MODULE_DESCRIPTION ( " Maxim/Dallas DS1374 RTC I2C Client Driver " ) ;
MODULE_LICENSE ( " GPL " ) ;