2006-07-14 00:24:11 -07:00
/*
* Intersil ISL1208 rtc class driver
*
* Copyright 2005 , 2006 Hebert Valerio Riedel < hvr @ gnu . org >
*
* This program is free software ; you can redistribute it and / or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation ; either version 2 of the License , or ( at your
* option ) any later version .
*
*/
# include <linux/module.h>
# include <linux/i2c.h>
# include <linux/bcd.h>
# include <linux/rtc.h>
# define DRV_NAME "isl1208"
# define DRV_VERSION "0.2"
/* Register map */
/* rtc section */
# define ISL1208_REG_SC 0x00
# define ISL1208_REG_MN 0x01
# define ISL1208_REG_HR 0x02
# define ISL1208_REG_HR_MIL (1<<7) /* 24h/12h mode */
# define ISL1208_REG_HR_PM (1<<5) /* PM/AM bit in 12h mode */
# define ISL1208_REG_DT 0x03
# define ISL1208_REG_MO 0x04
# define ISL1208_REG_YR 0x05
# define ISL1208_REG_DW 0x06
# define ISL1208_RTC_SECTION_LEN 7
/* control/status section */
# define ISL1208_REG_SR 0x07
# define ISL1208_REG_SR_ARST (1<<7) /* auto reset */
# define ISL1208_REG_SR_XTOSCB (1<<6) /* crystal oscillator */
# define ISL1208_REG_SR_WRTC (1<<4) /* write rtc */
# define ISL1208_REG_SR_ALM (1<<2) /* alarm */
# define ISL1208_REG_SR_BAT (1<<1) /* battery */
# define ISL1208_REG_SR_RTCF (1<<0) /* rtc fail */
# define ISL1208_REG_INT 0x08
# define ISL1208_REG_09 0x09 /* reserved */
# define ISL1208_REG_ATR 0x0a
# define ISL1208_REG_DTR 0x0b
/* alarm section */
# define ISL1208_REG_SCA 0x0c
# define ISL1208_REG_MNA 0x0d
# define ISL1208_REG_HRA 0x0e
# define ISL1208_REG_DTA 0x0f
# define ISL1208_REG_MOA 0x10
# define ISL1208_REG_DWA 0x11
# define ISL1208_ALARM_SECTION_LEN 6
/* user section */
# define ISL1208_REG_USR1 0x12
# define ISL1208_REG_USR2 0x13
# define ISL1208_USR_SECTION_LEN 2
/* i2c configuration */
# define ISL1208_I2C_ADDR 0xde
static unsigned short normal_i2c [ ] = {
ISL1208_I2C_ADDR > > 1 , I2C_CLIENT_END
} ;
I2C_CLIENT_INSMOD ; /* defines addr_data */
static int isl1208_attach_adapter ( struct i2c_adapter * adapter ) ;
static int isl1208_detach_client ( struct i2c_client * client ) ;
static struct i2c_driver isl1208_driver = {
. driver = {
. name = DRV_NAME ,
} ,
. id = I2C_DRIVERID_ISL1208 ,
. attach_adapter = & isl1208_attach_adapter ,
. detach_client = & isl1208_detach_client ,
} ;
/* block read */
static int
isl1208_i2c_read_regs ( struct i2c_client * client , u8 reg , u8 buf [ ] ,
unsigned len )
{
u8 reg_addr [ 1 ] = { reg } ;
struct i2c_msg msgs [ 2 ] = {
{ client - > addr , client - > flags , sizeof ( reg_addr ) , reg_addr } ,
{ client - > addr , client - > flags | I2C_M_RD , len , buf }
} ;
int ret ;
BUG_ON ( len = = 0 ) ;
BUG_ON ( reg > ISL1208_REG_USR2 ) ;
BUG_ON ( reg + len > ISL1208_REG_USR2 + 1 ) ;
ret = i2c_transfer ( client - > adapter , msgs , 2 ) ;
if ( ret > 0 )
ret = 0 ;
return ret ;
}
/* block write */
static int
isl1208_i2c_set_regs ( struct i2c_client * client , u8 reg , u8 const buf [ ] ,
unsigned len )
{
u8 i2c_buf [ ISL1208_REG_USR2 + 2 ] ;
struct i2c_msg msgs [ 1 ] = {
{ client - > addr , client - > flags , len + 1 , i2c_buf }
} ;
int ret ;
BUG_ON ( len = = 0 ) ;
BUG_ON ( reg > ISL1208_REG_USR2 ) ;
BUG_ON ( reg + len > ISL1208_REG_USR2 + 1 ) ;
i2c_buf [ 0 ] = reg ;
memcpy ( & i2c_buf [ 1 ] , & buf [ 0 ] , len ) ;
ret = i2c_transfer ( client - > adapter , msgs , 1 ) ;
if ( ret > 0 )
ret = 0 ;
return ret ;
}
/* simple check to see wether we have a isl1208 */
static int isl1208_i2c_validate_client ( struct i2c_client * client )
{
u8 regs [ ISL1208_RTC_SECTION_LEN ] = { 0 , } ;
u8 zero_mask [ ISL1208_RTC_SECTION_LEN ] = {
0x80 , 0x80 , 0x40 , 0xc0 , 0xe0 , 0x00 , 0xf8
} ;
int i ;
int ret ;
ret = isl1208_i2c_read_regs ( client , 0 , regs , ISL1208_RTC_SECTION_LEN ) ;
if ( ret < 0 )
return ret ;
for ( i = 0 ; i < ISL1208_RTC_SECTION_LEN ; + + i ) {
if ( regs [ i ] & zero_mask [ i ] ) /* check if bits are cleared */
return - ENODEV ;
}
return 0 ;
}
static int isl1208_i2c_get_sr ( struct i2c_client * client )
{
return i2c_smbus_read_byte_data ( client , ISL1208_REG_SR ) = = - 1 ? - EIO : 0 ;
}
static int isl1208_i2c_get_atr ( struct i2c_client * client )
{
int atr = i2c_smbus_read_byte_data ( client , ISL1208_REG_ATR ) ;
if ( atr < 0 )
return - EIO ;
/* The 6bit value in the ATR register controls the load
* capacitance C_load * in steps of 0.25 pF
*
* bit ( 1 < < 5 ) of the ATR register is inverted
*
* C_load ( ATR = 0x20 ) = 4.50 pF
* C_load ( ATR = 0x00 ) = 12.50 pF
* C_load ( ATR = 0x1f ) = 20.25 pF
*
*/
atr & = 0x3f ; /* mask out lsb */
atr ^ = 1 < < 5 ; /* invert 6th bit */
atr + = 2 * 9 ; /* add offset of 4.5pF; unit[atr] = 0.25pF */
return atr ;
}
static int isl1208_i2c_get_dtr ( struct i2c_client * client )
{
int dtr = i2c_smbus_read_byte_data ( client , ISL1208_REG_DTR ) ;
if ( dtr < 0 )
return - EIO ;
/* dtr encodes adjustments of {-60,-40,-20,0,20,40,60} ppm */
dtr = ( ( dtr & 0x3 ) * 20 ) * ( dtr & ( 1 < < 2 ) ? - 1 : 1 ) ;
return dtr ;
}
static int isl1208_i2c_get_usr ( struct i2c_client * client )
{
u8 buf [ ISL1208_USR_SECTION_LEN ] = { 0 , } ;
int ret ;
ret = isl1208_i2c_read_regs ( client , ISL1208_REG_USR1 , buf ,
ISL1208_USR_SECTION_LEN ) ;
if ( ret < 0 )
return ret ;
return ( buf [ 1 ] < < 8 ) | buf [ 0 ] ;
}
static int isl1208_i2c_set_usr ( struct i2c_client * client , u16 usr )
{
u8 buf [ ISL1208_USR_SECTION_LEN ] ;
buf [ 0 ] = usr & 0xff ;
buf [ 1 ] = ( usr > > 8 ) & 0xff ;
return isl1208_i2c_set_regs ( client , ISL1208_REG_USR1 , buf ,
ISL1208_USR_SECTION_LEN ) ;
}
static int isl1208_rtc_proc ( struct device * dev , struct seq_file * seq )
{
struct i2c_client * const client = to_i2c_client ( dev ) ;
int sr , dtr , atr , usr ;
sr = isl1208_i2c_get_sr ( client ) ;
if ( sr < 0 ) {
dev_err ( & client - > dev , " %s: reading SR failed \n " , __func__ ) ;
return sr ;
}
seq_printf ( seq , " status_reg \t :%s%s%s%s%s%s (0x%.2x) \n " ,
( sr & ISL1208_REG_SR_RTCF ) ? " RTCF " : " " ,
( sr & ISL1208_REG_SR_BAT ) ? " BAT " : " " ,
( sr & ISL1208_REG_SR_ALM ) ? " ALM " : " " ,
( sr & ISL1208_REG_SR_WRTC ) ? " WRTC " : " " ,
( sr & ISL1208_REG_SR_XTOSCB ) ? " XTOSCB " : " " ,
( sr & ISL1208_REG_SR_ARST ) ? " ARST " : " " ,
sr ) ;
seq_printf ( seq , " batt_status \t : %s \n " ,
( sr & ISL1208_REG_SR_RTCF ) ? " bad " : " okay " ) ;
dtr = isl1208_i2c_get_dtr ( client ) ;
if ( dtr > = 0 - 1 )
seq_printf ( seq , " digital_trim \t : %d ppm \n " , dtr ) ;
atr = isl1208_i2c_get_atr ( client ) ;
if ( atr > = 0 )
seq_printf ( seq , " analog_trim \t : %d.%.2d pF \n " ,
atr > > 2 , ( atr & 0x3 ) * 25 ) ;
usr = isl1208_i2c_get_usr ( client ) ;
if ( usr > = 0 )
seq_printf ( seq , " user_data \t : 0x%.4x \n " , usr ) ;
return 0 ;
}
static int isl1208_i2c_read_time ( struct i2c_client * client ,
struct rtc_time * tm )
{
int sr ;
u8 regs [ ISL1208_RTC_SECTION_LEN ] = { 0 , } ;
sr = isl1208_i2c_get_sr ( client ) ;
if ( sr < 0 ) {
dev_err ( & client - > dev , " %s: reading SR failed \n " , __func__ ) ;
return - EIO ;
}
sr = isl1208_i2c_read_regs ( client , 0 , regs , ISL1208_RTC_SECTION_LEN ) ;
if ( sr < 0 ) {
dev_err ( & client - > dev , " %s: reading RTC section failed \n " ,
__func__ ) ;
return sr ;
}
tm - > tm_sec = BCD2BIN ( regs [ ISL1208_REG_SC ] ) ;
tm - > tm_min = BCD2BIN ( regs [ ISL1208_REG_MN ] ) ;
{ /* HR field has a more complex interpretation */
const u8 _hr = regs [ ISL1208_REG_HR ] ;
if ( _hr & ISL1208_REG_HR_MIL ) /* 24h format */
tm - > tm_hour = BCD2BIN ( _hr & 0x3f ) ;
else { // 12h format
tm - > tm_hour = BCD2BIN ( _hr & 0x1f ) ;
if ( _hr & ISL1208_REG_HR_PM ) /* PM flag set */
tm - > tm_hour + = 12 ;
}
}
tm - > tm_mday = BCD2BIN ( regs [ ISL1208_REG_DT ] ) ;
tm - > tm_mon = BCD2BIN ( regs [ ISL1208_REG_MO ] ) - 1 ; /* rtc starts at 1 */
tm - > tm_year = BCD2BIN ( regs [ ISL1208_REG_YR ] ) + 100 ;
tm - > tm_wday = BCD2BIN ( regs [ ISL1208_REG_DW ] ) ;
return 0 ;
}
static int isl1208_i2c_read_alarm ( struct i2c_client * client ,
struct rtc_wkalrm * alarm )
{
struct rtc_time * const tm = & alarm - > time ;
u8 regs [ ISL1208_ALARM_SECTION_LEN ] = { 0 , } ;
int sr ;
sr = isl1208_i2c_get_sr ( client ) ;
if ( sr < 0 ) {
dev_err ( & client - > dev , " %s: reading SR failed \n " , __func__ ) ;
return sr ;
}
sr = isl1208_i2c_read_regs ( client , ISL1208_REG_SCA , regs ,
ISL1208_ALARM_SECTION_LEN ) ;
if ( sr < 0 ) {
dev_err ( & client - > dev , " %s: reading alarm section failed \n " ,
__func__ ) ;
return sr ;
}
/* MSB of each alarm register is an enable bit */
tm - > tm_sec = BCD2BIN ( regs [ ISL1208_REG_SCA - ISL1208_REG_SCA ] & 0x7f ) ;
tm - > tm_min = BCD2BIN ( regs [ ISL1208_REG_MNA - ISL1208_REG_SCA ] & 0x7f ) ;
tm - > tm_hour = BCD2BIN ( regs [ ISL1208_REG_HRA - ISL1208_REG_SCA ] & 0x3f ) ;
tm - > tm_mday = BCD2BIN ( regs [ ISL1208_REG_DTA - ISL1208_REG_SCA ] & 0x3f ) ;
tm - > tm_mon = BCD2BIN ( regs [ ISL1208_REG_MOA - ISL1208_REG_SCA ] & 0x1f ) - 1 ;
tm - > tm_wday = BCD2BIN ( regs [ ISL1208_REG_DWA - ISL1208_REG_SCA ] & 0x03 ) ;
return 0 ;
}
static int isl1208_rtc_read_time ( struct device * dev , struct rtc_time * tm )
{
return isl1208_i2c_read_time ( to_i2c_client ( dev ) , tm ) ;
}
static int isl1208_i2c_set_time ( struct i2c_client * client ,
struct rtc_time const * tm )
{
int sr ;
u8 regs [ ISL1208_RTC_SECTION_LEN ] = { 0 , } ;
regs [ ISL1208_REG_SC ] = BIN2BCD ( tm - > tm_sec ) ;
regs [ ISL1208_REG_MN ] = BIN2BCD ( tm - > tm_min ) ;
regs [ ISL1208_REG_HR ] = BIN2BCD ( tm - > tm_hour ) | ISL1208_REG_HR_MIL ;
regs [ ISL1208_REG_DT ] = BIN2BCD ( tm - > tm_mday ) ;
regs [ ISL1208_REG_MO ] = BIN2BCD ( tm - > tm_mon + 1 ) ;
regs [ ISL1208_REG_YR ] = BIN2BCD ( tm - > tm_year - 100 ) ;
regs [ ISL1208_REG_DW ] = BIN2BCD ( tm - > tm_wday & 7 ) ;
sr = isl1208_i2c_get_sr ( client ) ;
if ( sr < 0 ) {
dev_err ( & client - > dev , " %s: reading SR failed \n " , __func__ ) ;
return sr ;
}
/* set WRTC */
sr = i2c_smbus_write_byte_data ( client , ISL1208_REG_SR ,
sr | ISL1208_REG_SR_WRTC ) ;
if ( sr < 0 ) {
dev_err ( & client - > dev , " %s: writing SR failed \n " , __func__ ) ;
return sr ;
}
/* write RTC registers */
sr = isl1208_i2c_set_regs ( client , 0 , regs , ISL1208_RTC_SECTION_LEN ) ;
if ( sr < 0 ) {
dev_err ( & client - > dev , " %s: writing RTC section failed \n " ,
__func__ ) ;
return sr ;
}
/* clear WRTC again */
sr = i2c_smbus_write_byte_data ( client , ISL1208_REG_SR ,
sr & ~ ISL1208_REG_SR_WRTC ) ;
if ( sr < 0 ) {
dev_err ( & client - > dev , " %s: writing SR failed \n " , __func__ ) ;
return sr ;
}
return 0 ;
}
static int isl1208_rtc_set_time ( struct device * dev , struct rtc_time * tm )
{
return isl1208_i2c_set_time ( to_i2c_client ( dev ) , tm ) ;
}
static int isl1208_rtc_read_alarm ( struct device * dev , struct rtc_wkalrm * alarm )
{
return isl1208_i2c_read_alarm ( to_i2c_client ( dev ) , alarm ) ;
}
2006-09-30 23:28:17 -07:00
static const struct rtc_class_ops isl1208_rtc_ops = {
2006-07-14 00:24:11 -07:00
. proc = isl1208_rtc_proc ,
. read_time = isl1208_rtc_read_time ,
. set_time = isl1208_rtc_set_time ,
. read_alarm = isl1208_rtc_read_alarm ,
//.set_alarm = isl1208_rtc_set_alarm,
} ;
/* sysfs interface */
static ssize_t isl1208_sysfs_show_atrim ( struct device * dev ,
struct device_attribute * attr ,
char * buf )
{
int atr ;
atr = isl1208_i2c_get_atr ( to_i2c_client ( dev ) ) ;
if ( atr < 0 )
return atr ;
return sprintf ( buf , " %d.%.2d pF \n " , atr > > 2 , ( atr & 0x3 ) * 25 ) ;
}
static DEVICE_ATTR ( atrim , S_IRUGO , isl1208_sysfs_show_atrim , NULL ) ;
static ssize_t isl1208_sysfs_show_dtrim ( struct device * dev ,
struct device_attribute * attr ,
char * buf )
{
int dtr ;
dtr = isl1208_i2c_get_dtr ( to_i2c_client ( dev ) ) ;
if ( dtr < 0 )
return dtr ;
return sprintf ( buf , " %d ppm \n " , dtr ) ;
}
static DEVICE_ATTR ( dtrim , S_IRUGO , isl1208_sysfs_show_dtrim , NULL ) ;
static ssize_t isl1208_sysfs_show_usr ( struct device * dev ,
struct device_attribute * attr ,
char * buf )
{
int usr ;
usr = isl1208_i2c_get_usr ( to_i2c_client ( dev ) ) ;
if ( usr < 0 )
return usr ;
return sprintf ( buf , " 0x%.4x \n " , usr ) ;
}
static ssize_t isl1208_sysfs_store_usr ( struct device * dev ,
struct device_attribute * attr ,
const char * buf , size_t count )
{
int usr = - 1 ;
if ( buf [ 0 ] = = ' 0 ' & & ( buf [ 1 ] = = ' x ' | | buf [ 1 ] = = ' X ' ) ) {
if ( sscanf ( buf , " %x " , & usr ) ! = 1 )
return - EINVAL ;
} else {
if ( sscanf ( buf , " %d " , & usr ) ! = 1 )
return - EINVAL ;
}
if ( usr < 0 | | usr > 0xffff )
return - EINVAL ;
return isl1208_i2c_set_usr ( to_i2c_client ( dev ) , usr ) ? - EIO : count ;
}
static DEVICE_ATTR ( usr , S_IRUGO | S_IWUSR , isl1208_sysfs_show_usr ,
isl1208_sysfs_store_usr ) ;
static int
isl1208_probe ( struct i2c_adapter * adapter , int addr , int kind )
{
int rc = 0 ;
struct i2c_client * new_client = NULL ;
struct rtc_device * rtc = NULL ;
if ( ! i2c_check_functionality ( adapter , I2C_FUNC_I2C ) ) {
rc = - ENODEV ;
goto failout ;
}
new_client = kzalloc ( sizeof ( struct i2c_client ) , GFP_KERNEL ) ;
if ( new_client = = NULL ) {
rc = - ENOMEM ;
goto failout ;
}
new_client - > addr = addr ;
new_client - > adapter = adapter ;
new_client - > driver = & isl1208_driver ;
new_client - > flags = 0 ;
strcpy ( new_client - > name , DRV_NAME ) ;
if ( kind < 0 ) {
rc = isl1208_i2c_validate_client ( new_client ) ;
if ( rc < 0 )
goto failout ;
}
rc = i2c_attach_client ( new_client ) ;
if ( rc < 0 )
goto failout ;
dev_info ( & new_client - > dev ,
" chip found, driver version " DRV_VERSION " \n " ) ;
rtc = rtc_device_register ( isl1208_driver . driver . name ,
& new_client - > dev ,
& isl1208_rtc_ops , THIS_MODULE ) ;
if ( IS_ERR ( rtc ) ) {
rc = PTR_ERR ( rtc ) ;
goto failout_detach ;
}
i2c_set_clientdata ( new_client , rtc ) ;
rc = isl1208_i2c_get_sr ( new_client ) ;
if ( rc < 0 ) {
dev_err ( & new_client - > dev , " reading status failed \n " ) ;
goto failout_unregister ;
}
if ( rc & ISL1208_REG_SR_RTCF )
dev_warn ( & new_client - > dev , " rtc power failure detected, "
" please set clock. \n " ) ;
rc = device_create_file ( & new_client - > dev , & dev_attr_atrim ) ;
if ( rc < 0 )
goto failout_unregister ;
rc = device_create_file ( & new_client - > dev , & dev_attr_dtrim ) ;
if ( rc < 0 )
goto failout_atrim ;
rc = device_create_file ( & new_client - > dev , & dev_attr_usr ) ;
if ( rc < 0 )
goto failout_dtrim ;
return 0 ;
failout_dtrim :
device_remove_file ( & new_client - > dev , & dev_attr_dtrim ) ;
failout_atrim :
device_remove_file ( & new_client - > dev , & dev_attr_atrim ) ;
failout_unregister :
rtc_device_unregister ( rtc ) ;
failout_detach :
i2c_detach_client ( new_client ) ;
failout :
kfree ( new_client ) ;
return rc ;
}
static int
isl1208_attach_adapter ( struct i2c_adapter * adapter )
{
return i2c_probe ( adapter , & addr_data , isl1208_probe ) ;
}
static int
isl1208_detach_client ( struct i2c_client * client )
{
int rc ;
struct rtc_device * const rtc = i2c_get_clientdata ( client ) ;
if ( rtc )
rtc_device_unregister ( rtc ) ; /* do we need to kfree? */
rc = i2c_detach_client ( client ) ;
if ( rc )
return rc ;
kfree ( client ) ;
return 0 ;
}
/* module management */
static int __init isl1208_init ( void )
{
return i2c_add_driver ( & isl1208_driver ) ;
}
static void __exit isl1208_exit ( void )
{
i2c_del_driver ( & isl1208_driver ) ;
}
MODULE_AUTHOR ( " Herbert Valerio Riedel <hvr@gnu.org> " ) ;
MODULE_DESCRIPTION ( " Intersil ISL1208 RTC driver " ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_VERSION ( DRV_VERSION ) ;
module_init ( isl1208_init ) ;
module_exit ( isl1208_exit ) ;