2006-06-25 16:48:18 +04:00
/*
* drivers / rtc / rtc - pcf8583 . c
*
* Copyright ( C ) 2000 Russell King
*
* 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 .
*
* Driver for PCF8583 RTC & RAM chip
*
* Converted to the generic RTC susbsystem by G . Liakhovetski ( 2006 )
*/
# include <linux/module.h>
# include <linux/i2c.h>
# include <linux/slab.h>
# include <linux/string.h>
# include <linux/mc146818rtc.h>
# include <linux/init.h>
# include <linux/errno.h>
# include <linux/bcd.h>
struct rtc_mem {
unsigned int loc ;
unsigned int nr ;
unsigned char * data ;
} ;
struct pcf8583 {
struct i2c_client client ;
struct rtc_device * rtc ;
unsigned char ctrl ;
} ;
# define CTRL_STOP 0x80
# define CTRL_HOLD 0x40
# define CTRL_32KHZ 0x00
# define CTRL_MASK 0x08
# define CTRL_ALARMEN 0x04
# define CTRL_ALARM 0x02
# define CTRL_TIMER 0x01
2007-03-04 23:33:07 +03:00
static unsigned short normal_i2c [ ] = { 0x50 , I2C_CLIENT_END } ;
2006-06-25 16:48:18 +04:00
/* Module parameters */
I2C_CLIENT_INSMOD ;
static struct i2c_driver pcf8583_driver ;
# define get_ctrl(x) ((struct pcf8583 *)i2c_get_clientdata(x))->ctrl
# define set_ctrl(x, v) get_ctrl(x) = v
# define CMOS_YEAR (64 + 128)
# define CMOS_CHECKSUM (63)
static int pcf8583_get_datetime ( struct i2c_client * client , struct rtc_time * dt )
{
unsigned char buf [ 8 ] , addr [ 1 ] = { 1 } ;
struct i2c_msg msgs [ 2 ] = {
{
. addr = client - > addr ,
. flags = 0 ,
. len = 1 ,
. buf = addr ,
} , {
. addr = client - > addr ,
. flags = I2C_M_RD ,
. len = 6 ,
. buf = buf ,
}
} ;
int ret ;
memset ( buf , 0 , sizeof ( buf ) ) ;
ret = i2c_transfer ( client - > adapter , msgs , 2 ) ;
if ( ret = = 2 ) {
dt - > tm_year = buf [ 4 ] > > 6 ;
dt - > tm_wday = buf [ 5 ] > > 5 ;
buf [ 4 ] & = 0x3f ;
buf [ 5 ] & = 0x1f ;
2007-03-04 23:12:07 +03:00
dt - > tm_sec = BCD2BIN ( buf [ 1 ] ) ;
dt - > tm_min = BCD2BIN ( buf [ 2 ] ) ;
dt - > tm_hour = BCD2BIN ( buf [ 3 ] ) ;
dt - > tm_mday = BCD2BIN ( buf [ 4 ] ) ;
2007-03-04 23:13:13 +03:00
dt - > tm_mon = BCD2BIN ( buf [ 5 ] ) - 1 ;
2006-06-25 16:48:18 +04:00
}
return ret = = 2 ? 0 : - EIO ;
}
static int pcf8583_set_datetime ( struct i2c_client * client , struct rtc_time * dt , int datetoo )
{
unsigned char buf [ 8 ] ;
int ret , len = 6 ;
buf [ 0 ] = 0 ;
buf [ 1 ] = get_ctrl ( client ) | 0x80 ;
buf [ 2 ] = 0 ;
2007-03-04 23:12:07 +03:00
buf [ 3 ] = BIN2BCD ( dt - > tm_sec ) ;
buf [ 4 ] = BIN2BCD ( dt - > tm_min ) ;
buf [ 5 ] = BIN2BCD ( dt - > tm_hour ) ;
2006-06-25 16:48:18 +04:00
if ( datetoo ) {
len = 8 ;
2007-03-04 23:12:07 +03:00
buf [ 6 ] = BIN2BCD ( dt - > tm_mday ) | ( dt - > tm_year < < 6 ) ;
2007-03-04 23:13:13 +03:00
buf [ 7 ] = BIN2BCD ( dt - > tm_mon + 1 ) | ( dt - > tm_wday < < 5 ) ;
2006-06-25 16:48:18 +04:00
}
ret = i2c_master_send ( client , ( char * ) buf , len ) ;
if ( ret ! = len )
return - EIO ;
buf [ 1 ] = get_ctrl ( client ) ;
ret = i2c_master_send ( client , ( char * ) buf , 2 ) ;
return ret = = 2 ? 0 : - EIO ;
}
static int pcf8583_get_ctrl ( struct i2c_client * client , unsigned char * ctrl )
{
* ctrl = get_ctrl ( client ) ;
return 0 ;
}
static int pcf8583_set_ctrl ( struct i2c_client * client , unsigned char * ctrl )
{
unsigned char buf [ 2 ] ;
buf [ 0 ] = 0 ;
buf [ 1 ] = * ctrl ;
set_ctrl ( client , * ctrl ) ;
return i2c_master_send ( client , ( char * ) buf , 2 ) ;
}
static int pcf8583_read_mem ( struct i2c_client * client , struct rtc_mem * mem )
{
unsigned char addr [ 1 ] ;
struct i2c_msg msgs [ 2 ] = {
{
. addr = client - > addr ,
. flags = 0 ,
. len = 1 ,
. buf = addr ,
} , {
. addr = client - > addr ,
. flags = I2C_M_RD ,
. len = mem - > nr ,
. buf = mem - > data ,
}
} ;
if ( mem - > loc < 8 )
return - EINVAL ;
addr [ 0 ] = mem - > loc ;
return i2c_transfer ( client - > adapter , msgs , 2 ) = = 2 ? 0 : - EIO ;
}
static int pcf8583_write_mem ( struct i2c_client * client , struct rtc_mem * mem )
{
unsigned char addr [ 1 ] ;
struct i2c_msg msgs [ 2 ] = {
{
. addr = client - > addr ,
. flags = 0 ,
. len = 1 ,
. buf = addr ,
} , {
. addr = client - > addr ,
. flags = I2C_M_NOSTART ,
. len = mem - > nr ,
. buf = mem - > data ,
}
} ;
if ( mem - > loc < 8 )
return - EINVAL ;
addr [ 0 ] = mem - > loc ;
return i2c_transfer ( client - > adapter , msgs , 2 ) = = 2 ? 0 : - EIO ;
}
static int pcf8583_rtc_read_time ( struct device * dev , struct rtc_time * tm )
{
struct i2c_client * client = to_i2c_client ( dev ) ;
unsigned char ctrl , year [ 2 ] ;
struct rtc_mem mem = { CMOS_YEAR , sizeof ( year ) , year } ;
int real_year , year_offset , err ;
/*
* Ensure that the RTC is running .
*/
pcf8583_get_ctrl ( client , & ctrl ) ;
if ( ctrl & ( CTRL_STOP | CTRL_HOLD ) ) {
unsigned char new_ctrl = ctrl & ~ ( CTRL_STOP | CTRL_HOLD ) ;
printk ( KERN_WARNING " RTC: resetting control %02x -> %02x \n " ,
ctrl , new_ctrl ) ;
if ( ( err = pcf8583_set_ctrl ( client , & new_ctrl ) ) < 0 )
return err ;
}
if ( pcf8583_get_datetime ( client , tm ) | |
pcf8583_read_mem ( client , & mem ) )
return - EIO ;
real_year = year [ 0 ] ;
/*
* The RTC year holds the LSB two bits of the current
* year , which should reflect the LSB two bits of the
* CMOS copy of the year . Any difference indicates
* that we have to correct the CMOS version .
*/
year_offset = tm - > tm_year - ( real_year & 3 ) ;
if ( year_offset < 0 )
/*
* RTC year wrapped . Adjust it appropriately .
*/
year_offset + = 4 ;
2007-03-04 23:13:13 +03:00
tm - > tm_year = ( real_year + year_offset + year [ 1 ] * 100 ) - 1900 ;
2006-06-25 16:48:18 +04:00
return 0 ;
}
static int pcf8583_rtc_set_time ( struct device * dev , struct rtc_time * tm )
{
struct i2c_client * client = to_i2c_client ( dev ) ;
unsigned char year [ 2 ] , chk ;
struct rtc_mem cmos_year = { CMOS_YEAR , sizeof ( year ) , year } ;
struct rtc_mem cmos_check = { CMOS_CHECKSUM , 1 , & chk } ;
2007-03-04 23:13:13 +03:00
unsigned int proper_year = tm - > tm_year + 1900 ;
2006-06-25 16:48:18 +04:00
int ret ;
/*
* The RTC ' s own 2 - bit year must reflect the least
* significant two bits of the CMOS year .
*/
ret = pcf8583_set_datetime ( client , tm , 1 ) ;
if ( ret )
return ret ;
ret = pcf8583_read_mem ( client , & cmos_check ) ;
if ( ret )
return ret ;
ret = pcf8583_read_mem ( client , & cmos_year ) ;
if ( ret )
return ret ;
chk - = year [ 1 ] + year [ 0 ] ;
2007-03-04 23:13:13 +03:00
year [ 1 ] = proper_year / 100 ;
year [ 0 ] = proper_year % 100 ;
2006-06-25 16:48:18 +04:00
chk + = year [ 1 ] + year [ 0 ] ;
ret = pcf8583_write_mem ( client , & cmos_year ) ;
if ( ret )
return ret ;
ret = pcf8583_write_mem ( client , & cmos_check ) ;
return ret ;
}
2006-10-01 10:28:17 +04:00
static const struct rtc_class_ops pcf8583_rtc_ops = {
2006-06-25 16:48:18 +04:00
. read_time = pcf8583_rtc_read_time ,
. set_time = pcf8583_rtc_set_time ,
} ;
static int pcf8583_probe ( struct i2c_adapter * adap , int addr , int kind ) ;
static int pcf8583_attach ( struct i2c_adapter * adap )
{
return i2c_probe ( adap , & addr_data , pcf8583_probe ) ;
}
static int pcf8583_detach ( struct i2c_client * client )
{
int err ;
struct pcf8583 * pcf = i2c_get_clientdata ( client ) ;
struct rtc_device * rtc = pcf - > rtc ;
if ( rtc )
rtc_device_unregister ( rtc ) ;
if ( ( err = i2c_detach_client ( client ) ) )
return err ;
kfree ( pcf ) ;
return 0 ;
}
static struct i2c_driver pcf8583_driver = {
. driver = {
. name = " pcf8583 " ,
} ,
. id = I2C_DRIVERID_PCF8583 ,
. attach_adapter = pcf8583_attach ,
. detach_client = pcf8583_detach ,
} ;
static int pcf8583_probe ( struct i2c_adapter * adap , int addr , int kind )
{
struct pcf8583 * pcf ;
struct i2c_client * client ;
struct rtc_device * rtc ;
unsigned char buf [ 1 ] , ad [ 1 ] = { 0 } ;
int err ;
struct i2c_msg msgs [ 2 ] = {
{
. addr = addr ,
. flags = 0 ,
. len = 1 ,
. buf = ad ,
} , {
. addr = addr ,
. flags = I2C_M_RD ,
. len = 1 ,
. buf = buf ,
}
} ;
pcf = kzalloc ( sizeof ( * pcf ) , GFP_KERNEL ) ;
if ( ! pcf )
return - ENOMEM ;
client = & pcf - > client ;
client - > addr = addr ;
client - > adapter = adap ;
client - > driver = & pcf8583_driver ;
strlcpy ( client - > name , pcf8583_driver . driver . name , I2C_NAME_SIZE ) ;
if ( i2c_transfer ( client - > adapter , msgs , 2 ) ! = 2 ) {
err = - EIO ;
goto exit_kfree ;
}
err = i2c_attach_client ( client ) ;
if ( err )
goto exit_kfree ;
rtc = rtc_device_register ( pcf8583_driver . driver . name , & client - > dev ,
& pcf8583_rtc_ops , THIS_MODULE ) ;
if ( IS_ERR ( rtc ) ) {
err = PTR_ERR ( rtc ) ;
goto exit_detach ;
}
pcf - > rtc = rtc ;
i2c_set_clientdata ( client , pcf ) ;
set_ctrl ( client , buf [ 0 ] ) ;
return 0 ;
exit_detach :
i2c_detach_client ( client ) ;
exit_kfree :
kfree ( pcf ) ;
return err ;
}
static __init int pcf8583_init ( void )
{
return i2c_add_driver ( & pcf8583_driver ) ;
}
static __exit void pcf8583_exit ( void )
{
i2c_del_driver ( & pcf8583_driver ) ;
}
module_init ( pcf8583_init ) ;
module_exit ( pcf8583_exit ) ;
MODULE_AUTHOR ( " Russell King " ) ;
MODULE_DESCRIPTION ( " PCF8583 I2C RTC driver " ) ;
MODULE_LICENSE ( " GPL " ) ;