2010-08-06 06:28:08 +04:00
/*
* RTC driver for Maxim MAX8998
*
* Copyright ( C ) 2010 Samsung Electronics Co . Ltd
* Author : Minkyu Kang < mk7 . kang @ samsung . com >
* Author : Joonyoung Shim < jy0922 . shim @ samsung . com >
*
* 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/slab.h>
# include <linux/bcd.h>
# include <linux/rtc.h>
# include <linux/platform_device.h>
# include <linux/mfd/max8998.h>
# include <linux/mfd/max8998-private.h>
2011-01-04 08:17:39 +03:00
# include <linux/delay.h>
2010-08-06 06:28:08 +04:00
# define MAX8998_RTC_SEC 0x00
# define MAX8998_RTC_MIN 0x01
# define MAX8998_RTC_HOUR 0x02
# define MAX8998_RTC_WEEKDAY 0x03
# define MAX8998_RTC_DATE 0x04
# define MAX8998_RTC_MONTH 0x05
# define MAX8998_RTC_YEAR1 0x06
# define MAX8998_RTC_YEAR2 0x07
# define MAX8998_ALARM0_SEC 0x08
# define MAX8998_ALARM0_MIN 0x09
# define MAX8998_ALARM0_HOUR 0x0a
# define MAX8998_ALARM0_WEEKDAY 0x0b
# define MAX8998_ALARM0_DATE 0x0c
# define MAX8998_ALARM0_MONTH 0x0d
# define MAX8998_ALARM0_YEAR1 0x0e
# define MAX8998_ALARM0_YEAR2 0x0f
# define MAX8998_ALARM1_SEC 0x10
# define MAX8998_ALARM1_MIN 0x11
# define MAX8998_ALARM1_HOUR 0x12
# define MAX8998_ALARM1_WEEKDAY 0x13
# define MAX8998_ALARM1_DATE 0x14
# define MAX8998_ALARM1_MONTH 0x15
# define MAX8998_ALARM1_YEAR1 0x16
# define MAX8998_ALARM1_YEAR2 0x17
# define MAX8998_ALARM0_CONF 0x18
# define MAX8998_ALARM1_CONF 0x19
# define MAX8998_RTC_STATUS 0x1a
# define MAX8998_WTSR_SMPL_CNTL 0x1b
# define MAX8998_TEST 0x1f
# define HOUR_12 (1 << 7)
# define HOUR_PM (1 << 5)
# define ALARM0_STATUS (1 << 1)
# define ALARM1_STATUS (1 << 2)
enum {
RTC_SEC = 0 ,
RTC_MIN ,
RTC_HOUR ,
RTC_WEEKDAY ,
RTC_DATE ,
RTC_MONTH ,
RTC_YEAR1 ,
RTC_YEAR2 ,
} ;
struct max8998_rtc_info {
struct device * dev ;
struct max8998_dev * max8998 ;
struct i2c_client * rtc ;
struct rtc_device * rtc_dev ;
int irq ;
2011-01-04 08:17:39 +03:00
bool lp3974_bug_workaround ;
2010-08-06 06:28:08 +04:00
} ;
static void max8998_data_to_tm ( u8 * data , struct rtc_time * tm )
{
tm - > tm_sec = bcd2bin ( data [ RTC_SEC ] ) ;
tm - > tm_min = bcd2bin ( data [ RTC_MIN ] ) ;
if ( data [ RTC_HOUR ] & HOUR_12 ) {
tm - > tm_hour = bcd2bin ( data [ RTC_HOUR ] & 0x1f ) ;
if ( data [ RTC_HOUR ] & HOUR_PM )
tm - > tm_hour + = 12 ;
} else
tm - > tm_hour = bcd2bin ( data [ RTC_HOUR ] & 0x3f ) ;
tm - > tm_wday = data [ RTC_WEEKDAY ] & 0x07 ;
tm - > tm_mday = bcd2bin ( data [ RTC_DATE ] ) ;
tm - > tm_mon = bcd2bin ( data [ RTC_MONTH ] ) ;
tm - > tm_year = bcd2bin ( data [ RTC_YEAR1 ] ) + bcd2bin ( data [ RTC_YEAR2 ] ) * 100 ;
tm - > tm_year - = 1900 ;
}
static void max8998_tm_to_data ( struct rtc_time * tm , u8 * data )
{
data [ RTC_SEC ] = bin2bcd ( tm - > tm_sec ) ;
data [ RTC_MIN ] = bin2bcd ( tm - > tm_min ) ;
data [ RTC_HOUR ] = bin2bcd ( tm - > tm_hour ) ;
data [ RTC_WEEKDAY ] = tm - > tm_wday ;
data [ RTC_DATE ] = bin2bcd ( tm - > tm_mday ) ;
data [ RTC_MONTH ] = bin2bcd ( tm - > tm_mon ) ;
data [ RTC_YEAR1 ] = bin2bcd ( tm - > tm_year % 100 ) ;
data [ RTC_YEAR2 ] = bin2bcd ( ( tm - > tm_year + 1900 ) / 100 ) ;
}
static int max8998_rtc_read_time ( struct device * dev , struct rtc_time * tm )
{
struct max8998_rtc_info * info = dev_get_drvdata ( dev ) ;
u8 data [ 8 ] ;
int ret ;
ret = max8998_bulk_read ( info - > rtc , MAX8998_RTC_SEC , 8 , data ) ;
if ( ret < 0 )
return ret ;
max8998_data_to_tm ( data , tm ) ;
return rtc_valid_tm ( tm ) ;
}
static int max8998_rtc_set_time ( struct device * dev , struct rtc_time * tm )
{
struct max8998_rtc_info * info = dev_get_drvdata ( dev ) ;
u8 data [ 8 ] ;
2011-01-04 08:17:39 +03:00
int ret ;
2010-08-06 06:28:08 +04:00
max8998_tm_to_data ( tm , data ) ;
2011-01-04 08:17:39 +03:00
ret = max8998_bulk_write ( info - > rtc , MAX8998_RTC_SEC , 8 , data ) ;
if ( info - > lp3974_bug_workaround )
msleep ( 2000 ) ;
return ret ;
2010-08-06 06:28:08 +04:00
}
static int max8998_rtc_read_alarm ( struct device * dev , struct rtc_wkalrm * alrm )
{
struct max8998_rtc_info * info = dev_get_drvdata ( dev ) ;
u8 data [ 8 ] ;
u8 val ;
int ret ;
ret = max8998_bulk_read ( info - > rtc , MAX8998_ALARM0_SEC , 8 , data ) ;
if ( ret < 0 )
return ret ;
max8998_data_to_tm ( data , & alrm - > time ) ;
ret = max8998_read_reg ( info - > rtc , MAX8998_ALARM0_CONF , & val ) ;
if ( ret < 0 )
return ret ;
alrm - > enabled = ! ! val ;
ret = max8998_read_reg ( info - > rtc , MAX8998_RTC_STATUS , & val ) ;
if ( ret < 0 )
return ret ;
if ( val & ALARM0_STATUS )
alrm - > pending = 1 ;
else
alrm - > pending = 0 ;
return 0 ;
}
static int max8998_rtc_stop_alarm ( struct max8998_rtc_info * info )
{
2011-01-04 08:17:39 +03:00
int ret = max8998_write_reg ( info - > rtc , MAX8998_ALARM0_CONF , 0 ) ;
if ( info - > lp3974_bug_workaround )
msleep ( 2000 ) ;
return ret ;
2010-08-06 06:28:08 +04:00
}
static int max8998_rtc_start_alarm ( struct max8998_rtc_info * info )
{
2011-01-04 08:17:39 +03:00
int ret ;
u8 alarm0_conf = 0x77 ;
/* LP3974 with delay bug chips has rtc alarm bugs with "MONTH" field */
if ( info - > lp3974_bug_workaround )
alarm0_conf = 0x57 ;
ret = max8998_write_reg ( info - > rtc , MAX8998_ALARM0_CONF , alarm0_conf ) ;
if ( info - > lp3974_bug_workaround )
msleep ( 2000 ) ;
return ret ;
2010-08-06 06:28:08 +04:00
}
static int max8998_rtc_set_alarm ( struct device * dev , struct rtc_wkalrm * alrm )
{
struct max8998_rtc_info * info = dev_get_drvdata ( dev ) ;
u8 data [ 8 ] ;
int ret ;
max8998_tm_to_data ( & alrm - > time , data ) ;
ret = max8998_rtc_stop_alarm ( info ) ;
if ( ret < 0 )
return ret ;
ret = max8998_bulk_write ( info - > rtc , MAX8998_ALARM0_SEC , 8 , data ) ;
if ( ret < 0 )
return ret ;
2011-01-04 08:17:39 +03:00
if ( info - > lp3974_bug_workaround )
msleep ( 2000 ) ;
2010-08-06 06:28:08 +04:00
if ( alrm - > enabled )
2011-01-04 08:17:39 +03:00
ret = max8998_rtc_start_alarm ( info ) ;
2010-08-06 06:28:08 +04:00
2011-01-04 08:17:39 +03:00
return ret ;
2010-08-06 06:28:08 +04:00
}
static int max8998_rtc_alarm_irq_enable ( struct device * dev ,
unsigned int enabled )
{
struct max8998_rtc_info * info = dev_get_drvdata ( dev ) ;
if ( enabled )
return max8998_rtc_start_alarm ( info ) ;
else
return max8998_rtc_stop_alarm ( info ) ;
}
static irqreturn_t max8998_rtc_alarm_irq ( int irq , void * data )
{
struct max8998_rtc_info * info = data ;
rtc_update_irq ( info - > rtc_dev , 1 , RTC_IRQF | RTC_AF ) ;
return IRQ_HANDLED ;
}
static const struct rtc_class_ops max8998_rtc_ops = {
. read_time = max8998_rtc_read_time ,
. set_time = max8998_rtc_set_time ,
. read_alarm = max8998_rtc_read_alarm ,
. set_alarm = max8998_rtc_set_alarm ,
. alarm_irq_enable = max8998_rtc_alarm_irq_enable ,
} ;
static int __devinit max8998_rtc_probe ( struct platform_device * pdev )
{
struct max8998_dev * max8998 = dev_get_drvdata ( pdev - > dev . parent ) ;
2011-01-04 08:17:39 +03:00
struct max8998_platform_data * pdata = dev_get_platdata ( max8998 - > dev ) ;
2010-08-06 06:28:08 +04:00
struct max8998_rtc_info * info ;
int ret ;
info = kzalloc ( sizeof ( struct max8998_rtc_info ) , GFP_KERNEL ) ;
if ( ! info )
return - ENOMEM ;
info - > dev = & pdev - > dev ;
info - > max8998 = max8998 ;
info - > rtc = max8998 - > rtc ;
info - > irq = max8998 - > irq_base + MAX8998_IRQ_ALARM0 ;
2011-05-07 04:27:07 +04:00
platform_set_drvdata ( pdev , info ) ;
2010-08-06 06:28:08 +04:00
info - > rtc_dev = rtc_device_register ( " max8998-rtc " , & pdev - > dev ,
& max8998_rtc_ops , THIS_MODULE ) ;
if ( IS_ERR ( info - > rtc_dev ) ) {
ret = PTR_ERR ( info - > rtc_dev ) ;
dev_err ( & pdev - > dev , " Failed to register RTC device: %d \n " , ret ) ;
goto out_rtc ;
}
ret = request_threaded_irq ( info - > irq , NULL , max8998_rtc_alarm_irq , 0 ,
" rtc-alarm0 " , info ) ;
2011-01-04 08:17:39 +03:00
2010-08-06 06:28:08 +04:00
if ( ret < 0 )
dev_err ( & pdev - > dev , " Failed to request alarm IRQ: %d: %d \n " ,
info - > irq , ret ) ;
2011-01-04 08:17:39 +03:00
dev_info ( & pdev - > dev , " RTC CHIP NAME: %s \n " , pdev - > id_entry - > name ) ;
if ( pdata - > rtc_delay ) {
info - > lp3974_bug_workaround = true ;
dev_warn ( & pdev - > dev , " LP3974 with RTC REGERR option. "
" RTC updates will be extremely slow. \n " ) ;
}
2010-08-06 06:28:08 +04:00
return 0 ;
out_rtc :
2011-05-07 04:27:07 +04:00
platform_set_drvdata ( pdev , NULL ) ;
2010-08-06 06:28:08 +04:00
kfree ( info ) ;
return ret ;
}
static int __devexit max8998_rtc_remove ( struct platform_device * pdev )
{
struct max8998_rtc_info * info = platform_get_drvdata ( pdev ) ;
if ( info ) {
free_irq ( info - > irq , info ) ;
rtc_device_unregister ( info - > rtc_dev ) ;
kfree ( info ) ;
}
return 0 ;
}
2011-01-04 08:17:39 +03:00
static const struct platform_device_id max8998_rtc_id [ ] = {
{ " max8998-rtc " , TYPE_MAX8998 } ,
{ " lp3974-rtc " , TYPE_LP3974 } ,
{ }
} ;
2010-08-06 06:28:08 +04:00
static struct platform_driver max8998_rtc_driver = {
. driver = {
. name = " max8998-rtc " ,
. owner = THIS_MODULE ,
} ,
. probe = max8998_rtc_probe ,
. remove = __devexit_p ( max8998_rtc_remove ) ,
2011-01-04 08:17:39 +03:00
. id_table = max8998_rtc_id ,
2010-08-06 06:28:08 +04:00
} ;
static int __init max8998_rtc_init ( void )
{
return platform_driver_register ( & max8998_rtc_driver ) ;
}
module_init ( max8998_rtc_init ) ;
static void __exit max8998_rtc_exit ( void )
{
platform_driver_unregister ( & max8998_rtc_driver ) ;
}
module_exit ( max8998_rtc_exit ) ;
MODULE_AUTHOR ( " Minkyu Kang <mk7.kang@samsung.com> " ) ;
MODULE_AUTHOR ( " Joonyoung Shim <jy0922.shim@samsung.com> " ) ;
MODULE_DESCRIPTION ( " Maxim MAX8998 RTC driver " ) ;
MODULE_LICENSE ( " GPL " ) ;