2017-11-01 18:01:20 +03:00
/*
* drivers / rtc / rtc - pcf85363 . c
*
* Driver for NXP PCF85363 real - time clock .
*
* Copyright ( C ) 2017 Eric Nelson
*
* 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 .
*
* Based loosely on rtc - 8583 by Russell King , Wolfram Sang and Juergen Beisert
*/
# include <linux/module.h>
# include <linux/i2c.h>
# include <linux/slab.h>
# include <linux/rtc.h>
# include <linux/init.h>
# include <linux/err.h>
# include <linux/errno.h>
# include <linux/bcd.h>
# include <linux/of.h>
# include <linux/of_device.h>
# include <linux/regmap.h>
/*
* Date / Time registers
*/
# define DT_100THS 0x00
# define DT_SECS 0x01
# define DT_MINUTES 0x02
# define DT_HOURS 0x03
# define DT_DAYS 0x04
# define DT_WEEKDAYS 0x05
# define DT_MONTHS 0x06
# define DT_YEARS 0x07
/*
* Alarm registers
*/
# define DT_SECOND_ALM1 0x08
# define DT_MINUTE_ALM1 0x09
# define DT_HOUR_ALM1 0x0a
# define DT_DAY_ALM1 0x0b
# define DT_MONTH_ALM1 0x0c
# define DT_MINUTE_ALM2 0x0d
# define DT_HOUR_ALM2 0x0e
# define DT_WEEKDAY_ALM2 0x0f
# define DT_ALARM_EN 0x10
/*
* Time stamp registers
*/
# define DT_TIMESTAMP1 0x11
# define DT_TIMESTAMP2 0x17
# define DT_TIMESTAMP3 0x1d
# define DT_TS_MODE 0x23
/*
* control registers
*/
# define CTRL_OFFSET 0x24
# define CTRL_OSCILLATOR 0x25
# define CTRL_BATTERY 0x26
# define CTRL_PIN_IO 0x27
# define CTRL_FUNCTION 0x28
# define CTRL_INTA_EN 0x29
# define CTRL_INTB_EN 0x2a
# define CTRL_FLAGS 0x2b
# define CTRL_RAMBYTE 0x2c
# define CTRL_WDOG 0x2d
# define CTRL_STOP_EN 0x2e
# define CTRL_RESETS 0x2f
# define CTRL_RAM 0x40
2018-03-01 03:42:19 +03:00
# define ALRM_SEC_A1E BIT(0)
# define ALRM_MIN_A1E BIT(1)
# define ALRM_HR_A1E BIT(2)
# define ALRM_DAY_A1E BIT(3)
# define ALRM_MON_A1E BIT(4)
# define ALRM_MIN_A2E BIT(5)
# define ALRM_HR_A2E BIT(6)
# define ALRM_DAY_A2E BIT(7)
# define INT_WDIE BIT(0)
# define INT_BSIE BIT(1)
# define INT_TSRIE BIT(2)
# define INT_A2IE BIT(3)
# define INT_A1IE BIT(4)
# define INT_OIE BIT(5)
# define INT_PIE BIT(6)
# define INT_ILP BIT(7)
# define FLAGS_TSR1F BIT(0)
# define FLAGS_TSR2F BIT(1)
# define FLAGS_TSR3F BIT(2)
# define FLAGS_BSF BIT(3)
# define FLAGS_WDF BIT(4)
# define FLAGS_A1F BIT(5)
# define FLAGS_A2F BIT(6)
# define FLAGS_PIF BIT(7)
# define PIN_IO_INTAPM GENMASK(1, 0)
# define PIN_IO_INTA_CLK 0
# define PIN_IO_INTA_BAT 1
# define PIN_IO_INTA_OUT 2
# define PIN_IO_INTA_HIZ 3
2018-02-24 03:08:53 +03:00
# define STOP_EN_STOP BIT(0)
# define RESET_CPR 0xa4
2017-11-01 18:01:20 +03:00
# define NVRAM_SIZE 0x40
static struct i2c_driver pcf85363_driver ;
struct pcf85363 {
struct device * dev ;
struct rtc_device * rtc ;
struct regmap * regmap ;
} ;
static int pcf85363_rtc_read_time ( struct device * dev , struct rtc_time * tm )
{
struct pcf85363 * pcf85363 = dev_get_drvdata ( dev ) ;
unsigned char buf [ DT_YEARS + 1 ] ;
int ret , len = sizeof ( buf ) ;
/* read the RTC date and time registers all at once */
ret = regmap_bulk_read ( pcf85363 - > regmap , DT_100THS , buf , len ) ;
if ( ret ) {
dev_err ( dev , " %s: error %d \n " , __func__ , ret ) ;
return ret ;
}
tm - > tm_year = bcd2bin ( buf [ DT_YEARS ] ) ;
/* adjust for 1900 base of rtc_time */
tm - > tm_year + = 100 ;
tm - > tm_wday = buf [ DT_WEEKDAYS ] & 7 ;
buf [ DT_SECS ] & = 0x7F ;
tm - > tm_sec = bcd2bin ( buf [ DT_SECS ] ) ;
buf [ DT_MINUTES ] & = 0x7F ;
tm - > tm_min = bcd2bin ( buf [ DT_MINUTES ] ) ;
tm - > tm_hour = bcd2bin ( buf [ DT_HOURS ] ) ;
tm - > tm_mday = bcd2bin ( buf [ DT_DAYS ] ) ;
tm - > tm_mon = bcd2bin ( buf [ DT_MONTHS ] ) - 1 ;
return 0 ;
}
static int pcf85363_rtc_set_time ( struct device * dev , struct rtc_time * tm )
{
struct pcf85363 * pcf85363 = dev_get_drvdata ( dev ) ;
2018-02-24 03:08:53 +03:00
unsigned char tmp [ 11 ] ;
unsigned char * buf = & tmp [ 2 ] ;
int ret ;
tmp [ 0 ] = STOP_EN_STOP ;
tmp [ 1 ] = RESET_CPR ;
2017-11-01 18:01:20 +03:00
buf [ DT_100THS ] = 0 ;
buf [ DT_SECS ] = bin2bcd ( tm - > tm_sec ) ;
buf [ DT_MINUTES ] = bin2bcd ( tm - > tm_min ) ;
buf [ DT_HOURS ] = bin2bcd ( tm - > tm_hour ) ;
buf [ DT_DAYS ] = bin2bcd ( tm - > tm_mday ) ;
buf [ DT_WEEKDAYS ] = tm - > tm_wday ;
buf [ DT_MONTHS ] = bin2bcd ( tm - > tm_mon + 1 ) ;
buf [ DT_YEARS ] = bin2bcd ( tm - > tm_year % 100 ) ;
2018-02-24 03:08:53 +03:00
ret = regmap_bulk_write ( pcf85363 - > regmap , CTRL_STOP_EN ,
tmp , sizeof ( tmp ) ) ;
if ( ret )
return ret ;
return regmap_write ( pcf85363 - > regmap , CTRL_STOP_EN , 0 ) ;
2017-11-01 18:01:20 +03:00
}
2018-03-01 03:42:19 +03:00
static int pcf85363_rtc_read_alarm ( struct device * dev , struct rtc_wkalrm * alrm )
{
struct pcf85363 * pcf85363 = dev_get_drvdata ( dev ) ;
unsigned char buf [ DT_MONTH_ALM1 - DT_SECOND_ALM1 + 1 ] ;
unsigned int val ;
int ret ;
ret = regmap_bulk_read ( pcf85363 - > regmap , DT_SECOND_ALM1 , buf ,
sizeof ( buf ) ) ;
if ( ret )
return ret ;
alrm - > time . tm_sec = bcd2bin ( buf [ 0 ] ) ;
alrm - > time . tm_min = bcd2bin ( buf [ 1 ] ) ;
alrm - > time . tm_hour = bcd2bin ( buf [ 2 ] ) ;
alrm - > time . tm_mday = bcd2bin ( buf [ 3 ] ) ;
alrm - > time . tm_mon = bcd2bin ( buf [ 4 ] ) - 1 ;
ret = regmap_read ( pcf85363 - > regmap , CTRL_INTA_EN , & val ) ;
if ( ret )
return ret ;
alrm - > enabled = ! ! ( val & INT_A1IE ) ;
return 0 ;
}
static int _pcf85363_rtc_alarm_irq_enable ( struct pcf85363 * pcf85363 , unsigned
int enabled )
{
unsigned int alarm_flags = ALRM_SEC_A1E | ALRM_MIN_A1E | ALRM_HR_A1E |
ALRM_DAY_A1E | ALRM_MON_A1E ;
int ret ;
ret = regmap_update_bits ( pcf85363 - > regmap , DT_ALARM_EN , alarm_flags ,
enabled ? alarm_flags : 0 ) ;
if ( ret )
return ret ;
ret = regmap_update_bits ( pcf85363 - > regmap , CTRL_INTA_EN ,
INT_A1IE , enabled ? INT_A1IE : 0 ) ;
if ( ret | | enabled )
return ret ;
/* clear current flags */
return regmap_update_bits ( pcf85363 - > regmap , CTRL_FLAGS , FLAGS_A1F , 0 ) ;
}
static int pcf85363_rtc_alarm_irq_enable ( struct device * dev ,
unsigned int enabled )
{
struct pcf85363 * pcf85363 = dev_get_drvdata ( dev ) ;
return _pcf85363_rtc_alarm_irq_enable ( pcf85363 , enabled ) ;
}
static int pcf85363_rtc_set_alarm ( struct device * dev , struct rtc_wkalrm * alrm )
{
struct pcf85363 * pcf85363 = dev_get_drvdata ( dev ) ;
unsigned char buf [ DT_MONTH_ALM1 - DT_SECOND_ALM1 + 1 ] ;
int ret ;
buf [ 0 ] = bin2bcd ( alrm - > time . tm_sec ) ;
buf [ 1 ] = bin2bcd ( alrm - > time . tm_min ) ;
buf [ 2 ] = bin2bcd ( alrm - > time . tm_hour ) ;
buf [ 3 ] = bin2bcd ( alrm - > time . tm_mday ) ;
buf [ 4 ] = bin2bcd ( alrm - > time . tm_mon + 1 ) ;
/*
* Disable the alarm interrupt before changing the value to avoid
* spurious interrupts
*/
ret = _pcf85363_rtc_alarm_irq_enable ( pcf85363 , 0 ) ;
if ( ret )
return ret ;
ret = regmap_bulk_write ( pcf85363 - > regmap , DT_SECOND_ALM1 , buf ,
sizeof ( buf ) ) ;
if ( ret )
return ret ;
return _pcf85363_rtc_alarm_irq_enable ( pcf85363 , alrm - > enabled ) ;
}
static irqreturn_t pcf85363_rtc_handle_irq ( int irq , void * dev_id )
{
struct pcf85363 * pcf85363 = i2c_get_clientdata ( dev_id ) ;
unsigned int flags ;
int err ;
err = regmap_read ( pcf85363 - > regmap , CTRL_FLAGS , & flags ) ;
if ( err )
return IRQ_NONE ;
if ( flags & FLAGS_A1F ) {
rtc_update_irq ( pcf85363 - > rtc , 1 , RTC_IRQF | RTC_AF ) ;
regmap_update_bits ( pcf85363 - > regmap , CTRL_FLAGS , FLAGS_A1F , 0 ) ;
return IRQ_HANDLED ;
}
return IRQ_NONE ;
}
2017-11-01 18:01:20 +03:00
static const struct rtc_class_ops rtc_ops = {
. read_time = pcf85363_rtc_read_time ,
. set_time = pcf85363_rtc_set_time ,
} ;
2018-03-01 03:42:19 +03:00
static const struct rtc_class_ops rtc_ops_alarm = {
. read_time = pcf85363_rtc_read_time ,
. set_time = pcf85363_rtc_set_time ,
. read_alarm = pcf85363_rtc_read_alarm ,
. set_alarm = pcf85363_rtc_set_alarm ,
. alarm_irq_enable = pcf85363_rtc_alarm_irq_enable ,
} ;
2017-11-01 18:01:20 +03:00
static int pcf85363_nvram_read ( void * priv , unsigned int offset , void * val ,
size_t bytes )
{
struct pcf85363 * pcf85363 = priv ;
return regmap_bulk_read ( pcf85363 - > regmap , CTRL_RAM + offset ,
val , bytes ) ;
}
static int pcf85363_nvram_write ( void * priv , unsigned int offset , void * val ,
size_t bytes )
{
struct pcf85363 * pcf85363 = priv ;
return regmap_bulk_write ( pcf85363 - > regmap , CTRL_RAM + offset ,
val , bytes ) ;
}
static const struct regmap_config regmap_config = {
. reg_bits = 8 ,
. val_bits = 8 ,
2018-02-24 03:08:50 +03:00
. max_register = 0x7f ,
2017-11-01 18:01:20 +03:00
} ;
static int pcf85363_probe ( struct i2c_client * client ,
const struct i2c_device_id * id )
{
struct pcf85363 * pcf85363 ;
2018-02-13 01:47:30 +03:00
struct nvmem_config nvmem_cfg = {
. name = " pcf85363- " ,
. word_size = 1 ,
. stride = 1 ,
. size = NVRAM_SIZE ,
. reg_read = pcf85363_nvram_read ,
. reg_write = pcf85363_nvram_write ,
} ;
2018-02-13 01:47:29 +03:00
int ret ;
2017-11-01 18:01:20 +03:00
if ( ! i2c_check_functionality ( client - > adapter , I2C_FUNC_I2C ) )
return - ENODEV ;
pcf85363 = devm_kzalloc ( & client - > dev , sizeof ( struct pcf85363 ) ,
GFP_KERNEL ) ;
if ( ! pcf85363 )
return - ENOMEM ;
pcf85363 - > regmap = devm_regmap_init_i2c ( client , & regmap_config ) ;
if ( IS_ERR ( pcf85363 - > regmap ) ) {
dev_err ( & client - > dev , " regmap allocation failed \n " ) ;
return PTR_ERR ( pcf85363 - > regmap ) ;
}
pcf85363 - > dev = & client - > dev ;
i2c_set_clientdata ( client , pcf85363 ) ;
pcf85363 - > rtc = devm_rtc_allocate_device ( pcf85363 - > dev ) ;
if ( IS_ERR ( pcf85363 - > rtc ) )
return PTR_ERR ( pcf85363 - > rtc ) ;
pcf85363 - > rtc - > ops = & rtc_ops ;
2018-03-01 03:42:19 +03:00
if ( client - > irq > 0 ) {
regmap_write ( pcf85363 - > regmap , CTRL_FLAGS , 0 ) ;
regmap_update_bits ( pcf85363 - > regmap , CTRL_PIN_IO ,
PIN_IO_INTA_OUT , PIN_IO_INTAPM ) ;
ret = devm_request_threaded_irq ( pcf85363 - > dev , client - > irq ,
NULL , pcf85363_rtc_handle_irq ,
IRQF_TRIGGER_LOW | IRQF_ONESHOT ,
" pcf85363 " , client ) ;
if ( ret )
dev_warn ( & client - > dev , " unable to request IRQ, alarms disabled \n " ) ;
else
pcf85363 - > rtc - > ops = & rtc_ops_alarm ;
}
2018-02-13 01:47:29 +03:00
ret = rtc_register_device ( pcf85363 - > rtc ) ;
2018-02-13 01:47:30 +03:00
nvmem_cfg . priv = pcf85363 ;
rtc_nvmem_register ( pcf85363 - > rtc , & nvmem_cfg ) ;
2018-02-13 01:47:29 +03:00
return ret ;
2017-11-01 18:01:20 +03:00
}
static const struct of_device_id dev_ids [ ] = {
{ . compatible = " nxp,pcf85363 " } ,
{ }
} ;
MODULE_DEVICE_TABLE ( of , dev_ids ) ;
static struct i2c_driver pcf85363_driver = {
. driver = {
. name = " pcf85363 " ,
. of_match_table = of_match_ptr ( dev_ids ) ,
} ,
. probe = pcf85363_probe ,
} ;
module_i2c_driver ( pcf85363_driver ) ;
MODULE_AUTHOR ( " Eric Nelson " ) ;
MODULE_DESCRIPTION ( " pcf85363 I2C RTC driver " ) ;
MODULE_LICENSE ( " GPL " ) ;