2006-06-25 16:48:29 +04:00
/*
* An rtc driver for the Dallas DS1742
*
* Copyright ( C ) 2006 Atsushi Nemoto < anemo @ mba . ocn . ne . jp >
*
* 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 .
2006-12-07 07:39:41 +03:00
*
* Copyright ( C ) 2006 Torsten Ertbjerg Rasmussen < tr @ newtec . dk >
* - nvram size determined from resource
* - this ds1742 driver now supports ds1743 .
2006-06-25 16:48:29 +04:00
*/
# include <linux/bcd.h>
# include <linux/init.h>
# include <linux/kernel.h>
# include <linux/delay.h>
# include <linux/jiffies.h>
# include <linux/rtc.h>
# include <linux/platform_device.h>
# include <linux/io.h>
2006-12-07 07:39:41 +03:00
# define DRV_VERSION "0.3"
2006-06-25 16:48:29 +04:00
2006-12-07 07:39:41 +03:00
# define RTC_SIZE 8
2006-06-25 16:48:29 +04:00
2006-12-07 07:39:41 +03:00
# define RTC_CONTROL 0
# define RTC_CENTURY 0
# define RTC_SECONDS 1
# define RTC_MINUTES 2
# define RTC_HOURS 3
# define RTC_DAY 4
# define RTC_DATE 5
# define RTC_MONTH 6
# define RTC_YEAR 7
2006-06-25 16:48:29 +04:00
# define RTC_CENTURY_MASK 0x3f
# define RTC_SECONDS_MASK 0x7f
# define RTC_DAY_MASK 0x07
/* Bits in the Control/Century register */
# define RTC_WRITE 0x80
# define RTC_READ 0x40
/* Bits in the Seconds register */
# define RTC_STOP 0x80
/* Bits in the Day register */
# define RTC_BATT_FLAG 0x80
struct rtc_plat_data {
struct rtc_device * rtc ;
2006-12-07 07:39:41 +03:00
void __iomem * ioaddr_nvram ;
void __iomem * ioaddr_rtc ;
size_t size_nvram ;
size_t size ;
2006-06-25 16:48:29 +04:00
unsigned long baseaddr ;
unsigned long last_jiffies ;
} ;
static int ds1742_rtc_set_time ( struct device * dev , struct rtc_time * tm )
{
struct platform_device * pdev = to_platform_device ( dev ) ;
struct rtc_plat_data * pdata = platform_get_drvdata ( pdev ) ;
2006-12-07 07:39:41 +03:00
void __iomem * ioaddr = pdata - > ioaddr_rtc ;
2006-06-25 16:48:29 +04:00
u8 century ;
century = BIN2BCD ( ( tm - > tm_year + 1900 ) / 100 ) ;
writeb ( RTC_WRITE , ioaddr + RTC_CONTROL ) ;
writeb ( BIN2BCD ( tm - > tm_year % 100 ) , ioaddr + RTC_YEAR ) ;
writeb ( BIN2BCD ( tm - > tm_mon + 1 ) , ioaddr + RTC_MONTH ) ;
writeb ( BIN2BCD ( tm - > tm_wday ) & RTC_DAY_MASK , ioaddr + RTC_DAY ) ;
writeb ( BIN2BCD ( tm - > tm_mday ) , ioaddr + RTC_DATE ) ;
writeb ( BIN2BCD ( tm - > tm_hour ) , ioaddr + RTC_HOURS ) ;
writeb ( BIN2BCD ( tm - > tm_min ) , ioaddr + RTC_MINUTES ) ;
writeb ( BIN2BCD ( tm - > tm_sec ) & RTC_SECONDS_MASK , ioaddr + RTC_SECONDS ) ;
/* RTC_CENTURY and RTC_CONTROL share same register */
writeb ( RTC_WRITE | ( century & RTC_CENTURY_MASK ) , ioaddr + RTC_CENTURY ) ;
writeb ( century & RTC_CENTURY_MASK , ioaddr + RTC_CONTROL ) ;
return 0 ;
}
static int ds1742_rtc_read_time ( struct device * dev , struct rtc_time * tm )
{
struct platform_device * pdev = to_platform_device ( dev ) ;
struct rtc_plat_data * pdata = platform_get_drvdata ( pdev ) ;
2006-12-07 07:39:41 +03:00
void __iomem * ioaddr = pdata - > ioaddr_rtc ;
2006-06-25 16:48:29 +04:00
unsigned int year , month , day , hour , minute , second , week ;
unsigned int century ;
/* give enough time to update RTC in case of continuous read */
if ( pdata - > last_jiffies = = jiffies )
msleep ( 1 ) ;
pdata - > last_jiffies = jiffies ;
writeb ( RTC_READ , ioaddr + RTC_CONTROL ) ;
second = readb ( ioaddr + RTC_SECONDS ) & RTC_SECONDS_MASK ;
minute = readb ( ioaddr + RTC_MINUTES ) ;
hour = readb ( ioaddr + RTC_HOURS ) ;
day = readb ( ioaddr + RTC_DATE ) ;
week = readb ( ioaddr + RTC_DAY ) & RTC_DAY_MASK ;
month = readb ( ioaddr + RTC_MONTH ) ;
year = readb ( ioaddr + RTC_YEAR ) ;
century = readb ( ioaddr + RTC_CENTURY ) & RTC_CENTURY_MASK ;
writeb ( 0 , ioaddr + RTC_CONTROL ) ;
tm - > tm_sec = BCD2BIN ( second ) ;
tm - > tm_min = BCD2BIN ( minute ) ;
tm - > tm_hour = BCD2BIN ( hour ) ;
tm - > tm_mday = BCD2BIN ( day ) ;
tm - > tm_wday = BCD2BIN ( week ) ;
tm - > tm_mon = BCD2BIN ( month ) - 1 ;
/* year is 1900 + tm->tm_year */
tm - > tm_year = BCD2BIN ( year ) + BCD2BIN ( century ) * 100 - 1900 ;
if ( rtc_valid_tm ( tm ) < 0 ) {
dev_err ( dev , " retrieved date/time is not valid. \n " ) ;
rtc_time_to_tm ( 0 , tm ) ;
}
return 0 ;
}
2006-10-01 10:28:17 +04:00
static const struct rtc_class_ops ds1742_rtc_ops = {
2006-06-25 16:48:29 +04:00
. read_time = ds1742_rtc_read_time ,
. set_time = ds1742_rtc_set_time ,
} ;
static ssize_t ds1742_nvram_read ( struct kobject * kobj , char * buf ,
loff_t pos , size_t size )
{
struct platform_device * pdev =
to_platform_device ( container_of ( kobj , struct device , kobj ) ) ;
struct rtc_plat_data * pdata = platform_get_drvdata ( pdev ) ;
2006-12-07 07:39:41 +03:00
void __iomem * ioaddr = pdata - > ioaddr_nvram ;
2006-06-25 16:48:29 +04:00
ssize_t count ;
2006-12-07 07:39:41 +03:00
for ( count = 0 ; size > 0 & & pos < pdata - > size_nvram ; count + + , size - - )
2006-06-25 16:48:29 +04:00
* buf + + = readb ( ioaddr + pos + + ) ;
return count ;
}
static ssize_t ds1742_nvram_write ( struct kobject * kobj , char * buf ,
loff_t pos , size_t size )
{
struct platform_device * pdev =
to_platform_device ( container_of ( kobj , struct device , kobj ) ) ;
struct rtc_plat_data * pdata = platform_get_drvdata ( pdev ) ;
2006-12-07 07:39:41 +03:00
void __iomem * ioaddr = pdata - > ioaddr_nvram ;
2006-06-25 16:48:29 +04:00
ssize_t count ;
2006-12-07 07:39:41 +03:00
for ( count = 0 ; size > 0 & & pos < pdata - > size_nvram ; count + + , size - - )
2006-06-25 16:48:29 +04:00
writeb ( * buf + + , ioaddr + pos + + ) ;
return count ;
}
static struct bin_attribute ds1742_nvram_attr = {
. attr = {
. name = " nvram " ,
. mode = S_IRUGO | S_IWUGO ,
. owner = THIS_MODULE ,
} ,
. read = ds1742_nvram_read ,
. write = ds1742_nvram_write ,
} ;
static int __init ds1742_rtc_probe ( struct platform_device * pdev )
{
struct rtc_device * rtc ;
struct resource * res ;
unsigned int cen , sec ;
struct rtc_plat_data * pdata = NULL ;
void __iomem * ioaddr = 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 ;
2006-12-07 07:39:41 +03:00
pdata - > size = res - > end - res - > start + 1 ;
if ( ! request_mem_region ( res - > start , pdata - > size , pdev - > name ) ) {
2006-06-25 16:48:29 +04:00
ret = - EBUSY ;
goto out ;
}
pdata - > baseaddr = res - > start ;
2006-12-07 07:39:41 +03:00
ioaddr = ioremap ( pdata - > baseaddr , pdata - > size ) ;
2006-06-25 16:48:29 +04:00
if ( ! ioaddr ) {
ret = - ENOMEM ;
goto out ;
}
2006-12-07 07:39:41 +03:00
pdata - > ioaddr_nvram = ioaddr ;
pdata - > size_nvram = pdata - > size - RTC_SIZE ;
pdata - > ioaddr_rtc = ioaddr + pdata - > size_nvram ;
2006-06-25 16:48:29 +04:00
/* turn RTC on if it was not on */
2006-12-07 07:39:41 +03:00
ioaddr = pdata - > ioaddr_rtc ;
2006-06-25 16:48:29 +04:00
sec = readb ( ioaddr + RTC_SECONDS ) ;
if ( sec & RTC_STOP ) {
sec & = RTC_SECONDS_MASK ;
cen = readb ( ioaddr + RTC_CENTURY ) & RTC_CENTURY_MASK ;
writeb ( RTC_WRITE , ioaddr + RTC_CONTROL ) ;
writeb ( sec , ioaddr + RTC_SECONDS ) ;
writeb ( cen & RTC_CENTURY_MASK , ioaddr + RTC_CONTROL ) ;
}
2006-10-01 10:28:18 +04:00
if ( ! ( readb ( ioaddr + RTC_DAY ) & RTC_BATT_FLAG ) )
2006-06-25 16:48:29 +04:00
dev_warn ( & pdev - > dev , " voltage-low detected. \n " ) ;
rtc = rtc_device_register ( pdev - > name , & pdev - > dev ,
& ds1742_rtc_ops , THIS_MODULE ) ;
if ( IS_ERR ( rtc ) ) {
ret = PTR_ERR ( rtc ) ;
goto out ;
}
pdata - > rtc = rtc ;
pdata - > last_jiffies = jiffies ;
platform_set_drvdata ( pdev , pdata ) ;
2006-12-07 07:39:41 +03:00
ds1742_nvram_attr . size = max ( ds1742_nvram_attr . size ,
pdata - > size_nvram ) ;
2006-10-01 10:28:18 +04:00
ret = sysfs_create_bin_file ( & pdev - > dev . kobj , & ds1742_nvram_attr ) ;
if ( ret )
goto out ;
2006-06-25 16:48:29 +04:00
return 0 ;
out :
2006-10-01 10:28:18 +04:00
if ( pdata - > rtc )
rtc_device_unregister ( pdata - > rtc ) ;
2006-12-07 07:39:41 +03:00
if ( pdata - > ioaddr_nvram )
iounmap ( pdata - > ioaddr_nvram ) ;
2006-06-25 16:48:29 +04:00
if ( pdata - > baseaddr )
2006-12-07 07:39:41 +03:00
release_mem_region ( pdata - > baseaddr , pdata - > size ) ;
2006-06-25 16:48:29 +04:00
kfree ( pdata ) ;
return ret ;
}
static int __devexit ds1742_rtc_remove ( struct platform_device * pdev )
{
struct rtc_plat_data * pdata = platform_get_drvdata ( pdev ) ;
sysfs_remove_bin_file ( & pdev - > dev . kobj , & ds1742_nvram_attr ) ;
rtc_device_unregister ( pdata - > rtc ) ;
2006-12-07 07:39:41 +03:00
iounmap ( pdata - > ioaddr_nvram ) ;
release_mem_region ( pdata - > baseaddr , pdata - > size ) ;
2006-06-25 16:48:29 +04:00
kfree ( pdata ) ;
return 0 ;
}
static struct platform_driver ds1742_rtc_driver = {
. probe = ds1742_rtc_probe ,
. remove = __devexit_p ( ds1742_rtc_remove ) ,
. driver = {
. name = " ds1742 " ,
. owner = THIS_MODULE ,
} ,
} ;
static __init int ds1742_init ( void )
{
return platform_driver_register ( & ds1742_rtc_driver ) ;
}
static __exit void ds1742_exit ( void )
{
return platform_driver_unregister ( & ds1742_rtc_driver ) ;
}
module_init ( ds1742_init ) ;
module_exit ( ds1742_exit ) ;
MODULE_AUTHOR ( " Atsushi Nemoto <anemo@mba.ocn.ne.jp> " ) ;
MODULE_DESCRIPTION ( " Dallas DS1742 RTC driver " ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_VERSION ( DRV_VERSION ) ;