2008-02-06 12:38:46 +03:00
/*
* An rtc driver for the Dallas DS1511
*
* Copyright ( C ) 2006 Atsushi Nemoto < anemo @ mba . ocn . ne . jp >
* Copyright ( C ) 2007 Andrew Sharp < andy . sharp @ onstor . com >
*
* 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 .
*
* Real time clock driver for the Dallas 1511 chip , which also
* contains a watchdog timer . There is a tiny amount of code that
* platform code could use to mess with the watchdog device a little
* bit , but not a full watchdog driver .
*/
# include <linux/bcd.h>
# include <linux/init.h>
# include <linux/kernel.h>
# include <linux/delay.h>
# include <linux/interrupt.h>
# include <linux/rtc.h>
# include <linux/platform_device.h>
# include <linux/io.h>
# define DRV_VERSION "0.6"
enum ds1511reg {
DS1511_SEC = 0x0 ,
DS1511_MIN = 0x1 ,
DS1511_HOUR = 0x2 ,
DS1511_DOW = 0x3 ,
DS1511_DOM = 0x4 ,
DS1511_MONTH = 0x5 ,
DS1511_YEAR = 0x6 ,
DS1511_CENTURY = 0x7 ,
DS1511_AM1_SEC = 0x8 ,
DS1511_AM2_MIN = 0x9 ,
DS1511_AM3_HOUR = 0xa ,
DS1511_AM4_DATE = 0xb ,
DS1511_WD_MSEC = 0xc ,
DS1511_WD_SEC = 0xd ,
DS1511_CONTROL_A = 0xe ,
DS1511_CONTROL_B = 0xf ,
DS1511_RAMADDR_LSB = 0x10 ,
DS1511_RAMDATA = 0x13
} ;
# define DS1511_BLF1 0x80
# define DS1511_BLF2 0x40
# define DS1511_PRS 0x20
# define DS1511_PAB 0x10
# define DS1511_TDF 0x08
# define DS1511_KSF 0x04
# define DS1511_WDF 0x02
# define DS1511_IRQF 0x01
# define DS1511_TE 0x80
# define DS1511_CS 0x40
# define DS1511_BME 0x20
# define DS1511_TPE 0x10
# define DS1511_TIE 0x08
# define DS1511_KIE 0x04
# define DS1511_WDE 0x02
# define DS1511_WDS 0x01
# define DS1511_RAM_MAX 0xff
# define RTC_CMD DS1511_CONTROL_B
# define RTC_CMD1 DS1511_CONTROL_A
# define RTC_ALARM_SEC DS1511_AM1_SEC
# define RTC_ALARM_MIN DS1511_AM2_MIN
# define RTC_ALARM_HOUR DS1511_AM3_HOUR
# define RTC_ALARM_DATE DS1511_AM4_DATE
# define RTC_SEC DS1511_SEC
# define RTC_MIN DS1511_MIN
# define RTC_HOUR DS1511_HOUR
# define RTC_DOW DS1511_DOW
# define RTC_DOM DS1511_DOM
# define RTC_MON DS1511_MONTH
# define RTC_YEAR DS1511_YEAR
# define RTC_CENTURY DS1511_CENTURY
# define RTC_TIE DS1511_TIE
# define RTC_TE DS1511_TE
struct rtc_plat_data {
struct rtc_device * rtc ;
void __iomem * ioaddr ; /* virtual base address */
unsigned long baseaddr ; /* physical base address */
int size ; /* amount of memory mapped */
int irq ;
unsigned int irqen ;
int alrm_sec ;
int alrm_min ;
int alrm_hour ;
int alrm_mday ;
} ;
static DEFINE_SPINLOCK ( ds1511_lock ) ;
static __iomem char * ds1511_base ;
static u32 reg_spacing = 1 ;
static noinline void
rtc_write ( uint8_t val , uint32_t reg )
{
writeb ( val , ds1511_base + ( reg * reg_spacing ) ) ;
}
static inline void
rtc_write_alarm ( uint8_t val , enum ds1511reg reg )
{
rtc_write ( ( val | 0x80 ) , reg ) ;
}
static noinline uint8_t
rtc_read ( enum ds1511reg reg )
{
return readb ( ds1511_base + ( reg * reg_spacing ) ) ;
}
static inline void
rtc_disable_update ( void )
{
rtc_write ( ( rtc_read ( RTC_CMD ) & ~ RTC_TE ) , RTC_CMD ) ;
}
static void
rtc_enable_update ( void )
{
rtc_write ( ( rtc_read ( RTC_CMD ) | RTC_TE ) , RTC_CMD ) ;
}
/*
* # define DS1511_WDOG_RESET_SUPPORT
*
* Uncomment this if you want to use these routines in
* some platform code .
*/
# ifdef DS1511_WDOG_RESET_SUPPORT
/*
* just enough code to set the watchdog timer so that it
* will reboot the system
*/
void
ds1511_wdog_set ( unsigned long deciseconds )
{
/*
* the wdog timer can take 99.99 seconds
*/
deciseconds % = 10000 ;
/*
* set the wdog values in the wdog registers
*/
rtc_write ( BIN2BCD ( deciseconds % 100 ) , DS1511_WD_MSEC ) ;
rtc_write ( BIN2BCD ( deciseconds / 100 ) , DS1511_WD_SEC ) ;
/*
* set wdog enable and wdog ' steering ' bit to issue a reset
*/
rtc_write ( DS1511_WDE | DS1511_WDS , RTC_CMD ) ;
}
void
ds1511_wdog_disable ( void )
{
/*
* clear wdog enable and wdog ' steering ' bits
*/
rtc_write ( rtc_read ( RTC_CMD ) & ~ ( DS1511_WDE | DS1511_WDS ) , RTC_CMD ) ;
/*
* clear the wdog counter
*/
rtc_write ( 0 , DS1511_WD_MSEC ) ;
rtc_write ( 0 , DS1511_WD_SEC ) ;
}
# endif
/*
* set the rtc chip ' s idea of the time .
* stupidly , some callers call with year unmolested ;
* and some call with year = year - 1900. thanks .
*/
2008-04-28 13:11:55 +04:00
static int ds1511_rtc_set_time ( struct device * dev , struct rtc_time * rtc_tm )
2008-02-06 12:38:46 +03:00
{
u8 mon , day , dow , hrs , min , sec , yrs , cen ;
2008-05-07 07:42:30 +04:00
unsigned long flags ;
2008-02-06 12:38:46 +03:00
/*
* won ' t have to change this for a while
*/
if ( rtc_tm - > tm_year < 1900 ) {
rtc_tm - > tm_year + = 1900 ;
}
if ( rtc_tm - > tm_year < 1970 ) {
return - EINVAL ;
}
yrs = rtc_tm - > tm_year % 100 ;
cen = rtc_tm - > tm_year / 100 ;
mon = rtc_tm - > tm_mon + 1 ; /* tm_mon starts at zero */
day = rtc_tm - > tm_mday ;
dow = rtc_tm - > tm_wday & 0x7 ; /* automatic BCD */
hrs = rtc_tm - > tm_hour ;
min = rtc_tm - > tm_min ;
sec = rtc_tm - > tm_sec ;
if ( ( mon > 12 ) | | ( day = = 0 ) ) {
return - EINVAL ;
}
if ( day > rtc_month_days ( rtc_tm - > tm_mon , rtc_tm - > tm_year ) ) {
return - EINVAL ;
}
if ( ( hrs > = 24 ) | | ( min > = 60 ) | | ( sec > = 60 ) ) {
return - EINVAL ;
}
/*
* each register is a different number of valid bits
*/
sec = BIN2BCD ( sec ) & 0x7f ;
min = BIN2BCD ( min ) & 0x7f ;
hrs = BIN2BCD ( hrs ) & 0x3f ;
day = BIN2BCD ( day ) & 0x3f ;
mon = BIN2BCD ( mon ) & 0x1f ;
yrs = BIN2BCD ( yrs ) & 0xff ;
cen = BIN2BCD ( cen ) & 0xff ;
spin_lock_irqsave ( & ds1511_lock , flags ) ;
rtc_disable_update ( ) ;
rtc_write ( cen , RTC_CENTURY ) ;
rtc_write ( yrs , RTC_YEAR ) ;
rtc_write ( ( rtc_read ( RTC_MON ) & 0xe0 ) | mon , RTC_MON ) ;
rtc_write ( day , RTC_DOM ) ;
rtc_write ( hrs , RTC_HOUR ) ;
rtc_write ( min , RTC_MIN ) ;
rtc_write ( sec , RTC_SEC ) ;
rtc_write ( dow , RTC_DOW ) ;
rtc_enable_update ( ) ;
spin_unlock_irqrestore ( & ds1511_lock , flags ) ;
return 0 ;
}
2008-04-28 13:11:55 +04:00
static int ds1511_rtc_read_time ( struct device * dev , struct rtc_time * rtc_tm )
2008-02-06 12:38:46 +03:00
{
unsigned int century ;
2008-05-07 07:42:30 +04:00
unsigned long flags ;
2008-02-06 12:38:46 +03:00
spin_lock_irqsave ( & ds1511_lock , flags ) ;
rtc_disable_update ( ) ;
rtc_tm - > tm_sec = rtc_read ( RTC_SEC ) & 0x7f ;
rtc_tm - > tm_min = rtc_read ( RTC_MIN ) & 0x7f ;
rtc_tm - > tm_hour = rtc_read ( RTC_HOUR ) & 0x3f ;
rtc_tm - > tm_mday = rtc_read ( RTC_DOM ) & 0x3f ;
rtc_tm - > tm_wday = rtc_read ( RTC_DOW ) & 0x7 ;
rtc_tm - > tm_mon = rtc_read ( RTC_MON ) & 0x1f ;
rtc_tm - > tm_year = rtc_read ( RTC_YEAR ) & 0x7f ;
century = rtc_read ( RTC_CENTURY ) ;
rtc_enable_update ( ) ;
spin_unlock_irqrestore ( & ds1511_lock , flags ) ;
rtc_tm - > tm_sec = BCD2BIN ( rtc_tm - > tm_sec ) ;
rtc_tm - > tm_min = BCD2BIN ( rtc_tm - > tm_min ) ;
rtc_tm - > tm_hour = BCD2BIN ( rtc_tm - > tm_hour ) ;
rtc_tm - > tm_mday = BCD2BIN ( rtc_tm - > tm_mday ) ;
rtc_tm - > tm_wday = BCD2BIN ( rtc_tm - > tm_wday ) ;
rtc_tm - > tm_mon = BCD2BIN ( rtc_tm - > tm_mon ) ;
rtc_tm - > tm_year = BCD2BIN ( rtc_tm - > tm_year ) ;
century = BCD2BIN ( century ) * 100 ;
/*
* Account for differences between how the RTC uses the values
* and how they are defined in a struct rtc_time ;
*/
century + = rtc_tm - > tm_year ;
rtc_tm - > tm_year = century - 1900 ;
rtc_tm - > tm_mon - - ;
if ( rtc_valid_tm ( rtc_tm ) < 0 ) {
dev_err ( dev , " retrieved date/time is not valid. \n " ) ;
rtc_time_to_tm ( 0 , rtc_tm ) ;
}
return 0 ;
}
/*
* write the alarm register settings
*
* we only have the use to interrupt every second , otherwise
* known as the update interrupt , or the interrupt if the whole
* date / hours / mins / secs matches . the ds1511 has many more
* permutations , but the kernel doesn ' t .
*/
static void
ds1511_rtc_update_alarm ( struct rtc_plat_data * pdata )
{
unsigned long flags ;
spin_lock_irqsave ( & pdata - > rtc - > irq_lock , flags ) ;
rtc_write ( pdata - > alrm_mday < 0 | | ( pdata - > irqen & RTC_UF ) ?
0x80 : BIN2BCD ( pdata - > alrm_mday ) & 0x3f ,
RTC_ALARM_DATE ) ;
rtc_write ( pdata - > alrm_hour < 0 | | ( pdata - > irqen & RTC_UF ) ?
0x80 : BIN2BCD ( pdata - > alrm_hour ) & 0x3f ,
RTC_ALARM_HOUR ) ;
rtc_write ( pdata - > alrm_min < 0 | | ( pdata - > irqen & RTC_UF ) ?
0x80 : BIN2BCD ( pdata - > alrm_min ) & 0x7f ,
RTC_ALARM_MIN ) ;
rtc_write ( pdata - > alrm_sec < 0 | | ( pdata - > irqen & RTC_UF ) ?
0x80 : BIN2BCD ( pdata - > alrm_sec ) & 0x7f ,
RTC_ALARM_SEC ) ;
rtc_write ( rtc_read ( RTC_CMD ) | ( pdata - > irqen ? RTC_TIE : 0 ) , RTC_CMD ) ;
rtc_read ( RTC_CMD1 ) ; /* clear interrupts */
spin_unlock_irqrestore ( & pdata - > rtc - > irq_lock , flags ) ;
}
static int
ds1511_rtc_set_alarm ( struct device * dev , struct rtc_wkalrm * alrm )
{
struct platform_device * pdev = to_platform_device ( dev ) ;
struct rtc_plat_data * pdata = platform_get_drvdata ( pdev ) ;
if ( pdata - > irq < 0 ) {
return - EINVAL ;
}
pdata - > alrm_mday = alrm - > time . tm_mday ;
pdata - > alrm_hour = alrm - > time . tm_hour ;
pdata - > alrm_min = alrm - > time . tm_min ;
pdata - > alrm_sec = alrm - > time . tm_sec ;
if ( alrm - > enabled ) {
pdata - > irqen | = RTC_AF ;
}
ds1511_rtc_update_alarm ( pdata ) ;
return 0 ;
}
static int
ds1511_rtc_read_alarm ( struct device * dev , struct rtc_wkalrm * alrm )
{
struct platform_device * pdev = to_platform_device ( dev ) ;
struct rtc_plat_data * pdata = platform_get_drvdata ( pdev ) ;
if ( pdata - > irq < 0 ) {
return - EINVAL ;
}
alrm - > time . tm_mday = pdata - > alrm_mday < 0 ? 0 : pdata - > alrm_mday ;
alrm - > time . tm_hour = pdata - > alrm_hour < 0 ? 0 : pdata - > alrm_hour ;
alrm - > time . tm_min = pdata - > alrm_min < 0 ? 0 : pdata - > alrm_min ;
alrm - > time . tm_sec = pdata - > alrm_sec < 0 ? 0 : pdata - > alrm_sec ;
alrm - > enabled = ( pdata - > irqen & RTC_AF ) ? 1 : 0 ;
return 0 ;
}
static irqreturn_t
ds1511_interrupt ( int irq , void * dev_id )
{
struct platform_device * pdev = dev_id ;
struct rtc_plat_data * pdata = platform_get_drvdata ( pdev ) ;
unsigned long events = RTC_IRQF ;
/*
* read and clear interrupt
*/
if ( ! ( rtc_read ( RTC_CMD1 ) & DS1511_IRQF ) ) {
return IRQ_NONE ;
}
if ( rtc_read ( RTC_ALARM_SEC ) & 0x80 ) {
events | = RTC_UF ;
} else {
events | = RTC_AF ;
}
rtc_update_irq ( pdata - > rtc , 1 , events ) ;
return IRQ_HANDLED ;
}
static void
ds1511_rtc_release ( struct device * dev )
{
struct platform_device * pdev = to_platform_device ( dev ) ;
struct rtc_plat_data * pdata = platform_get_drvdata ( pdev ) ;
if ( pdata - > irq > = 0 ) {
pdata - > irqen = 0 ;
ds1511_rtc_update_alarm ( pdata ) ;
}
}
static int
ds1511_rtc_ioctl ( struct device * dev , unsigned int cmd , unsigned long arg )
{
struct platform_device * pdev = to_platform_device ( dev ) ;
struct rtc_plat_data * pdata = platform_get_drvdata ( pdev ) ;
if ( pdata - > irq < 0 ) {
return - ENOIOCTLCMD ; /* fall back into rtc-dev's emulation */
}
switch ( cmd ) {
case RTC_AIE_OFF :
pdata - > irqen & = ~ RTC_AF ;
ds1511_rtc_update_alarm ( pdata ) ;
break ;
case RTC_AIE_ON :
pdata - > irqen | = RTC_AF ;
ds1511_rtc_update_alarm ( pdata ) ;
break ;
case RTC_UIE_OFF :
pdata - > irqen & = ~ RTC_UF ;
ds1511_rtc_update_alarm ( pdata ) ;
break ;
case RTC_UIE_ON :
pdata - > irqen | = RTC_UF ;
ds1511_rtc_update_alarm ( pdata ) ;
break ;
default :
return - ENOIOCTLCMD ;
}
return 0 ;
}
static const struct rtc_class_ops ds1511_rtc_ops = {
. read_time = ds1511_rtc_read_time ,
. set_time = ds1511_rtc_set_time ,
. read_alarm = ds1511_rtc_read_alarm ,
. set_alarm = ds1511_rtc_set_alarm ,
. release = ds1511_rtc_release ,
. ioctl = ds1511_rtc_ioctl ,
} ;
static ssize_t
ds1511_nvram_read ( struct kobject * kobj , struct bin_attribute * ba ,
char * buf , loff_t pos , size_t size )
{
ssize_t count ;
/*
* if count is more than one , turn on " burst " mode
* turn it off when you ' re done
*/
if ( size > 1 ) {
rtc_write ( ( rtc_read ( RTC_CMD ) | DS1511_BME ) , RTC_CMD ) ;
}
if ( pos > DS1511_RAM_MAX ) {
pos = DS1511_RAM_MAX ;
}
if ( size + pos > DS1511_RAM_MAX + 1 ) {
size = DS1511_RAM_MAX - pos + 1 ;
}
rtc_write ( pos , DS1511_RAMADDR_LSB ) ;
for ( count = 0 ; size > 0 ; count + + , size - - ) {
* buf + + = rtc_read ( DS1511_RAMDATA ) ;
}
if ( count > 1 ) {
rtc_write ( ( rtc_read ( RTC_CMD ) & ~ DS1511_BME ) , RTC_CMD ) ;
}
return count ;
}
static ssize_t
ds1511_nvram_write ( struct kobject * kobj , struct bin_attribute * bin_attr ,
char * buf , loff_t pos , size_t size )
{
ssize_t count ;
/*
* if count is more than one , turn on " burst " mode
* turn it off when you ' re done
*/
if ( size > 1 ) {
rtc_write ( ( rtc_read ( RTC_CMD ) | DS1511_BME ) , RTC_CMD ) ;
}
if ( pos > DS1511_RAM_MAX ) {
pos = DS1511_RAM_MAX ;
}
if ( size + pos > DS1511_RAM_MAX + 1 ) {
size = DS1511_RAM_MAX - pos + 1 ;
}
rtc_write ( pos , DS1511_RAMADDR_LSB ) ;
for ( count = 0 ; size > 0 ; count + + , size - - ) {
rtc_write ( * buf + + , DS1511_RAMDATA ) ;
}
if ( count > 1 ) {
rtc_write ( ( rtc_read ( RTC_CMD ) & ~ DS1511_BME ) , RTC_CMD ) ;
}
return count ;
}
static struct bin_attribute ds1511_nvram_attr = {
. attr = {
. name = " nvram " ,
. mode = S_IRUGO | S_IWUGO ,
. owner = THIS_MODULE ,
} ,
. size = DS1511_RAM_MAX ,
. read = ds1511_nvram_read ,
. write = ds1511_nvram_write ,
} ;
static int __devinit
ds1511_rtc_probe ( struct platform_device * pdev )
{
struct rtc_device * rtc ;
struct resource * res ;
struct rtc_plat_data * pdata = NULL ;
int ret = 0 ;
res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
if ( ! res ) {
return - ENODEV ;
}
pdata = kzalloc ( sizeof ( * pdata ) , GFP_KERNEL ) ;
if ( ! pdata ) {
return - ENOMEM ;
}
pdata - > irq = - 1 ;
pdata - > size = res - > end - res - > start + 1 ;
if ( ! request_mem_region ( res - > start , pdata - > size , pdev - > name ) ) {
ret = - EBUSY ;
goto out ;
}
pdata - > baseaddr = res - > start ;
pdata - > size = pdata - > size ;
ds1511_base = ioremap ( pdata - > baseaddr , pdata - > size ) ;
if ( ! ds1511_base ) {
ret = - ENOMEM ;
goto out ;
}
pdata - > ioaddr = ds1511_base ;
pdata - > irq = platform_get_irq ( pdev , 0 ) ;
/*
* turn on the clock and the crystal , etc .
*/
rtc_write ( 0 , RTC_CMD ) ;
rtc_write ( 0 , RTC_CMD1 ) ;
/*
* clear the wdog counter
*/
rtc_write ( 0 , DS1511_WD_MSEC ) ;
rtc_write ( 0 , DS1511_WD_SEC ) ;
/*
* start the clock
*/
rtc_enable_update ( ) ;
/*
* check for a dying bat - tree
*/
if ( rtc_read ( RTC_CMD1 ) & DS1511_BLF1 ) {
dev_warn ( & pdev - > dev , " voltage-low detected. \n " ) ;
}
/*
* if the platform has an interrupt in mind for this device ,
* then by all means , set it
*/
if ( pdata - > irq > = 0 ) {
rtc_read ( RTC_CMD1 ) ;
if ( request_irq ( pdata - > irq , ds1511_interrupt ,
IRQF_DISABLED | IRQF_SHARED , pdev - > name , pdev ) < 0 ) {
dev_warn ( & pdev - > dev , " interrupt not available. \n " ) ;
pdata - > irq = - 1 ;
}
}
rtc = rtc_device_register ( pdev - > name , & pdev - > dev , & ds1511_rtc_ops ,
THIS_MODULE ) ;
if ( IS_ERR ( rtc ) ) {
ret = PTR_ERR ( rtc ) ;
goto out ;
}
pdata - > rtc = rtc ;
platform_set_drvdata ( pdev , pdata ) ;
ret = sysfs_create_bin_file ( & pdev - > dev . kobj , & ds1511_nvram_attr ) ;
if ( ret ) {
goto out ;
}
return 0 ;
out :
if ( pdata - > rtc ) {
rtc_device_unregister ( pdata - > rtc ) ;
}
if ( pdata - > irq > = 0 ) {
free_irq ( pdata - > irq , pdev ) ;
}
if ( ds1511_base ) {
iounmap ( ds1511_base ) ;
ds1511_base = NULL ;
}
if ( pdata - > baseaddr ) {
release_mem_region ( pdata - > baseaddr , pdata - > size ) ;
}
kfree ( pdata ) ;
return ret ;
}
static int __devexit
ds1511_rtc_remove ( struct platform_device * pdev )
{
struct rtc_plat_data * pdata = platform_get_drvdata ( pdev ) ;
sysfs_remove_bin_file ( & pdev - > dev . kobj , & ds1511_nvram_attr ) ;
rtc_device_unregister ( pdata - > rtc ) ;
pdata - > rtc = NULL ;
if ( pdata - > irq > = 0 ) {
/*
* disable the alarm interrupt
*/
rtc_write ( rtc_read ( RTC_CMD ) & ~ RTC_TIE , RTC_CMD ) ;
rtc_read ( RTC_CMD1 ) ;
free_irq ( pdata - > irq , pdev ) ;
}
iounmap ( pdata - > ioaddr ) ;
ds1511_base = NULL ;
release_mem_region ( pdata - > baseaddr , pdata - > size ) ;
kfree ( pdata ) ;
return 0 ;
}
2008-04-11 08:29:25 +04:00
/* work with hotplug and coldplug */
MODULE_ALIAS ( " platform:ds1511 " ) ;
2008-02-06 12:38:46 +03:00
static struct platform_driver ds1511_rtc_driver = {
. probe = ds1511_rtc_probe ,
. remove = __devexit_p ( ds1511_rtc_remove ) ,
. driver = {
. name = " ds1511 " ,
. owner = THIS_MODULE ,
} ,
} ;
static int __init
ds1511_rtc_init ( void )
{
return platform_driver_register ( & ds1511_rtc_driver ) ;
}
static void __exit
ds1511_rtc_exit ( void )
{
return platform_driver_unregister ( & ds1511_rtc_driver ) ;
}
module_init ( ds1511_rtc_init ) ;
module_exit ( ds1511_rtc_exit ) ;
MODULE_AUTHOR ( " Andrew Sharp <andy.sharp@onstor.com> " ) ;
MODULE_DESCRIPTION ( " Dallas DS1511 RTC driver " ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_VERSION ( DRV_VERSION ) ;