diff --git a/drivers/timers/CMakeLists.txt b/drivers/timers/CMakeLists.txt index da67a9cabd1..f819a283336 100644 --- a/drivers/timers/CMakeLists.txt +++ b/drivers/timers/CMakeLists.txt @@ -54,6 +54,10 @@ if(CONFIG_RTC_PCF85263) list(APPEND SRCS pcf85263.c) endif() +if(CONFIG_RTC_PCF8563) + list(APPEND SRCS pcf8563.c) +endif() + if(CONFIG_RTC_PL031) list(APPEND SRCS pl031.c) endif() diff --git a/drivers/timers/Kconfig b/drivers/timers/Kconfig index 143446ceb8e..f1c518d9313 100644 --- a/drivers/timers/Kconfig +++ b/drivers/timers/Kconfig @@ -371,6 +371,39 @@ config PCF85263_I2C_FREQUENCY endif # RTC_PCF85263 +config RTC_PCF8563 + bool "PCF8563 RTC Driver" + default n + select I2C + select RTC_DATETIME + depends on RTC_EXTERNAL + ---help--- + Enables support for the NXP PCF8563 I2C RTC timer, a battery + backed clock at the fixed address 0x51. + + The part counts two digits of year and a century rollover bit + whose meaning is not settled between manufacturers, so this + driver treats it as a clock for the years 2000 to 2099 and + writes that bit back exactly as it found it. Setting a date + outside that span is refused rather than stored as a year that + would read back as a different one. + + A read fails while the part says its oscillator has stopped, + which is what a flat backup cell or a board that has never had + its clock set looks like. Being told the time is not known is + more useful than being told a wrong one. + + The alarm and the countdown timer are not implemented. + +if RTC_PCF8563 + +config PCF8563_I2C_FREQUENCY + int "PCF8563 I2C frequency" + default 100000 + range 1 400000 + +endif # RTC_PCF8563 + config RTC_PL031 bool "PL031 RTC Support" default n diff --git a/drivers/timers/Make.defs b/drivers/timers/Make.defs index 51debd2db42..fa14e5311bc 100644 --- a/drivers/timers/Make.defs +++ b/drivers/timers/Make.defs @@ -70,6 +70,12 @@ ifeq ($(CONFIG_RTC_PCF85263),y) TMRVPATH = :timers endif +ifeq ($(CONFIG_RTC_PCF8563),y) + CSRCS += pcf8563.c + TMRDEPPATH = --dep-path timers + TMRVPATH = :timers +endif + ifeq ($(CONFIG_RTC_PL031),y) CSRCS += pl031.c TMRDEPPATH = --dep-path timers diff --git a/drivers/timers/pcf8563.c b/drivers/timers/pcf8563.c new file mode 100644 index 00000000000..ba0dbd75d1f --- /dev/null +++ b/drivers/timers/pcf8563.c @@ -0,0 +1,554 @@ +/**************************************************************************** + * drivers/timers/pcf8563.c + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#ifdef CONFIG_RTC_PCF8563 + +#ifndef CONFIG_RTC_DATETIME +# error CONFIG_RTC_DATETIME must be set to use this driver +#endif + +#ifdef CONFIG_RTC_HIRES +# error CONFIG_RTC_HIRES must NOT be set with this driver +#endif + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* Register map. Everything that carries a time is BCD. */ + +#define PCF8563_CTRL1 0x00 +#define PCF8563_CTRL2 0x01 +#define PCF8563_SECONDS 0x02 +#define PCF8563_MINUTES 0x03 +#define PCF8563_HOURS 0x04 +#define PCF8563_DAY 0x05 +#define PCF8563_WEEKDAY 0x06 +#define PCF8563_MONTH 0x07 +#define PCF8563_YEAR 0x08 + +/* Alarm at 0x09 to 0x0c, clock output at 0x0d, and a countdown timer at + * 0x0e and 0x0f. None is used here. They are written down because + * knowing where they are is most of the work of adding them, and because + * a reader wondering whether an alarm exists deserves an answer. + */ + +#define PCF8563_ALARM_MIN 0x09 +#define PCF8563_ALARM_HOUR 0x0a +#define PCF8563_ALARM_DAY 0x0b +#define PCF8563_ALARM_WEEK 0x0c +#define PCF8563_CLKOUT 0x0d +#define PCF8563_TIMER_CTRL 0x0e +#define PCF8563_TIMER 0x0f + +/* The seconds register carries a flag rather than a tenth digit: the + * oscillator has stopped at some point since the time was last set, so + * what the rest of the register file says is not to be believed. + */ + +#define PCF8563_SEC_VL 0x80 +#define PCF8563_SEC_MASK 0x7f + +#define PCF8563_MIN_MASK 0x7f +#define PCF8563_HOUR_MASK 0x3f +#define PCF8563_DAY_MASK 0x3f +#define PCF8563_WEEKDAY_MASK 0x07 +#define PCF8563_MONTH_MASK 0x1f +#define PCF8563_YEAR_MASK 0xff + +/* The month register's top bit marks a century rollover. Which value + * stands for which century is not settled between parts, so this driver + * does not try to read one out of it. See pcf8563_getdatetime(). + */ + +#define PCF8563_MONTH_C 0x80 + +/* Stopping the counters while the registers are written keeps a carry + * from landing in the middle of the write. + */ + +#define PCF8563_CTRL1_STOP 0x20 + +#ifndef CONFIG_PCF8563_I2C_FREQUENCY +# define CONFIG_PCF8563_I2C_FREQUENCY 100000 +#endif + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +struct pcf8563_dev_s +{ + FAR struct i2c_master_s *i2c; + + /* The century bit exactly as it was last seen, so that it can be + * written back the same way round. See pcf8563_getdatetime(). + */ + + bool c_polarity; +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +static int pcf8563_getdatetime(FAR struct pcf8563_dev_s *priv, + FAR struct tm *tp); +static int pcf8563_setdatetime(FAR struct pcf8563_dev_s *priv, + FAR const struct tm *tp); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +/* Only one of these is supported, because up_rtc_getdatetime() takes no + * argument saying which. + */ + +static struct pcf8563_dev_s g_pcf8563; + +/**************************************************************************** + * Public Data + ****************************************************************************/ + +/* Set once the chip is bound to a bus. Read by up_rtc_getdatetime(), which + * the system may call before that has happened. + */ + +volatile bool g_rtc_enabled = false; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: rtc_bin2bcd + * + * Description: + * Convert a binary value to the two BCD digits the chip stores. + * + * Input Parameters: + * value - The value to convert, 0 to 99 + * + * Returned Value: + * The value in BCD. + * + ****************************************************************************/ + +static uint8_t rtc_bin2bcd(int value) +{ + int msbcd = 0; + + while (value >= 10) + { + msbcd++; + value -= 10; + } + + return (msbcd << 4) | value; +} + +/**************************************************************************** + * Name: rtc_bcd2bin + * + * Description: + * Convert two BCD digits read from the chip to binary. + * + * Input Parameters: + * value - The BCD value to convert + * + * Returned Value: + * The value in binary. + * + ****************************************************************************/ + +static int rtc_bcd2bin(uint8_t value) +{ + int tens = ((int)value >> 4) * 10; + return tens + (value & 0x0f); +} + +/**************************************************************************** + * Name: pcf8563_getreg + * + * Description: + * Read consecutive registers, addressing the first and letting the + * chip's address pointer run on through the rest. + * + * Input Parameters: + * priv - The driver state + * regaddr - The first register to read + * buffer - Where to return the register contents + * buflen - How many registers to read + * + * Returned Value: + * Zero on success, or a negated errno on failure. + * + ****************************************************************************/ + +static int pcf8563_getreg(FAR struct pcf8563_dev_s *priv, uint8_t regaddr, + FAR uint8_t *buffer, size_t buflen) +{ + struct i2c_msg_s msg[2]; + + msg[0].frequency = CONFIG_PCF8563_I2C_FREQUENCY; + msg[0].addr = PCF8563_I2C_ADDRESS; + msg[0].flags = 0; + msg[0].buffer = ®addr; + msg[0].length = 1; + + msg[1].frequency = CONFIG_PCF8563_I2C_FREQUENCY; + msg[1].addr = PCF8563_I2C_ADDRESS; + msg[1].flags = I2C_M_READ; + msg[1].buffer = buffer; + msg[1].length = buflen; + + return I2C_TRANSFER(priv->i2c, msg, 2); +} + +/**************************************************************************** + * Name: pcf8563_putreg + * + * Description: + * Write consecutive registers as a single bus transaction, so the + * chip sees one write rather than one per register. + * + * Input Parameters: + * priv - The driver state + * regaddr - The first register to write + * buffer - The values to write + * buflen - How many registers to write + * + * Returned Value: + * Zero on success, or a negated errno on failure. + * + ****************************************************************************/ + +static int pcf8563_putreg(FAR struct pcf8563_dev_s *priv, uint8_t regaddr, + FAR const uint8_t *buffer, size_t buflen) +{ + struct i2c_msg_s msg[2]; + + msg[0].frequency = CONFIG_PCF8563_I2C_FREQUENCY; + msg[0].addr = PCF8563_I2C_ADDRESS; + msg[0].flags = 0; + msg[0].buffer = ®addr; + msg[0].length = 1; + + /* The second message runs on from the first without a stop, which is + * what makes the pair one write rather than two. + */ + + msg[1].frequency = CONFIG_PCF8563_I2C_FREQUENCY; + msg[1].addr = PCF8563_I2C_ADDRESS; + msg[1].flags = I2C_M_NOSTART; + msg[1].buffer = (FAR uint8_t *)buffer; + msg[1].length = buflen; + + return I2C_TRANSFER(priv->i2c, msg, 2); +} + +/**************************************************************************** + * Name: pcf8563_getdatetime + * + * Description: + * Read the time, refusing to hand back one that the chip says is not + * trustworthy. + * + * The century bit is recorded here so that a later write can put it back + * unchanged. A read that fails leaves it as it was. + * + * Input Parameters: + * priv - The driver state + * tp - Where to return the time + * + * Returned Value: + * Zero on success, -ENODATA if the oscillator has stopped since the time + * was last set, or a negated errno on a bus failure. + * + ****************************************************************************/ + +static int pcf8563_getdatetime(FAR struct pcf8563_dev_s *priv, + FAR struct tm *tp) +{ + uint8_t buffer[7]; + int year; + int ret; + + ret = pcf8563_getreg(priv, PCF8563_SECONDS, buffer, sizeof(buffer)); + if (ret < 0) + { + rtcerr("ERROR: I2C_TRANSFER failed: %d\n", ret); + return ret; + } + + /* The oscillator has stopped since the time was last set, so every + * register after this one holds whatever it happened to stop on. A + * flat backup cell and a board that has never had its clock set both + * arrive here, and in each case an answer would be worse than an + * error: the caller can do something sensible about not knowing the + * time, and nothing sensible about being told the wrong one. + */ + + if ((buffer[0] & PCF8563_SEC_VL) != 0) + { + rtcwarn("WARNING: oscillator stopped, the time is not known\n"); + return -ENODATA; + } + + tp->tm_sec = rtc_bcd2bin(buffer[0] & PCF8563_SEC_MASK); + tp->tm_min = rtc_bcd2bin(buffer[1] & PCF8563_MIN_MASK); + tp->tm_hour = rtc_bcd2bin(buffer[2] & PCF8563_HOUR_MASK); + tp->tm_mday = rtc_bcd2bin(buffer[3] & PCF8563_DAY_MASK); + tp->tm_wday = buffer[4] & PCF8563_WEEKDAY_MASK; + tp->tm_mon = rtc_bcd2bin(buffer[5] & PCF8563_MONTH_MASK) - 1; + + /* The part holds two digits of year and one bit saying a century has + * rolled over. Which value of that bit stands for which century is a + * convention rather than a rule, and parts disagree, so no century is + * read out of it: this is treated as a clock for the years 2000 to + * 2099, which is the same thing the mainline Linux driver settled on. + * + * The bit is remembered exactly as found so that a write puts it back + * the way this part expects it. Interpreting it would mean guessing; + * preserving it cannot be wrong. + */ + + year = rtc_bcd2bin(buffer[6] & PCF8563_YEAR_MASK); + + priv->c_polarity = (buffer[5] & PCF8563_MONTH_C) != 0; + + tp->tm_year = year + 100; + tp->tm_isdst = 0; + return OK; +} + +/**************************************************************************** + * Name: pcf8563_setdatetime + * + * Description: + * Set the time, stopping the counters across the write so that a carry + * cannot land between the seconds and the minutes. + * + * Input Parameters: + * priv - The driver state + * tp - The time to set + * + * Returned Value: + * Zero on success, -EINVAL for a year outside 2000 to 2099, or a + * negated errno on a bus failure. The counters are restarted either + * way. + * + ****************************************************************************/ + +static int pcf8563_setdatetime(FAR struct pcf8563_dev_s *priv, + FAR const struct tm *tp) +{ + uint8_t buffer[7]; + uint8_t ctrl1; + int year; + int ret; + + /* Two digits of year and no century that can be trusted means this part + * covers 2000 to 2099 and nothing else. Refuse anything outside rather + * than store a year that reads back as a different one. + */ + + year = tp->tm_year; + if (year < 100 || year > 199) + { + rtcerr("ERROR: year %d is outside 2000 to 2099\n", year + 1900); + return -EINVAL; + } + + /* Stop the counters, so that a carry cannot land between the seconds + * being written and the minutes. + */ + + ctrl1 = PCF8563_CTRL1_STOP; + ret = pcf8563_putreg(priv, PCF8563_CTRL1, &ctrl1, 1); + if (ret < 0) + { + rtcerr("ERROR: cannot stop the clock: %d\n", ret); + return ret; + } + + /* Writing the seconds clears the flag that says the time is unknown, + * because after this it is known. + */ + + buffer[0] = rtc_bin2bcd(tp->tm_sec) & PCF8563_SEC_MASK; + buffer[1] = rtc_bin2bcd(tp->tm_min); + buffer[2] = rtc_bin2bcd(tp->tm_hour); + buffer[3] = rtc_bin2bcd(tp->tm_mday); + buffer[4] = tp->tm_wday & PCF8563_WEEKDAY_MASK; + buffer[5] = rtc_bin2bcd(tp->tm_mon + 1); + buffer[6] = rtc_bin2bcd(year - 100); + + /* Restore the century bit as last read, so whatever convention this part + * follows survives being set. Nothing is interpreted from it, so a chip + * whose time has never been read back writes it as zero. + */ + + if (priv->c_polarity) + { + buffer[5] |= PCF8563_MONTH_C; + } + + ret = pcf8563_putreg(priv, PCF8563_SECONDS, buffer, sizeof(buffer)); + if (ret < 0) + { + rtcerr("ERROR: cannot write the time: %d\n", ret); + } + + /* Start the counters again whatever happened above, or a failed write + * leaves a stopped clock. + */ + + ctrl1 = 0; + pcf8563_putreg(priv, PCF8563_CTRL1, &ctrl1, 1); + + return ret; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: pcf8563_rtc_initialize + * + * Description: + * Bind the chip to an I2C bus and make it the system's clock. Board + * logic calls this once the bus exists, since an I2C bus is not available + * as early as clock_initialize() runs. + * + * Input Parameters: + * i2c - The bus the chip is on + * + * Returned Value: + * Zero on success, or a negated errno on failure. + * + ****************************************************************************/ + +int pcf8563_rtc_initialize(FAR struct i2c_master_s *i2c) +{ + FAR struct pcf8563_dev_s *priv = &g_pcf8563; + + DEBUGASSERT(i2c != NULL); + + priv->i2c = i2c; + priv->c_polarity = false; + + g_rtc_enabled = true; + return OK; +} + +/**************************************************************************** + * Name: up_rtc_getdatetime + * + * Description: + * Get the current date and time from the RTC. This is the interface the + * system uses to start its own clock when the only clock is external. + * + * Input Parameters: + * tp - Where to return the time + * + * Returned Value: + * Zero on success, or a negated errno on failure. -EAGAIN means no bus + * has been bound yet, and tp holds the epoch. + * + ****************************************************************************/ + +int up_rtc_getdatetime(FAR struct tm *tp) +{ + /* The system asks for the time before any board has had a chance to + * bind a bus, and has to be given something. The epoch is the + * conventional answer, along with the error that says so. + */ + + if (!g_rtc_enabled) + { + tp->tm_sec = 0; + tp->tm_min = 0; + tp->tm_hour = 0; + + /* The 1st of January 1970 was a Thursday */ + + tp->tm_wday = 4; + tp->tm_mday = 1; + tp->tm_mon = 0; + tp->tm_year = 70; + return -EAGAIN; + } + + return pcf8563_getdatetime(&g_pcf8563, tp); +} + +/**************************************************************************** + * Name: up_rtc_settime + * + * Description: + * Set the RTC from a timespec. Sub-second accuracy is dropped: this + * chip counts in whole seconds. + * + * Input Parameters: + * tp - The time to set + * + * Returned Value: + * Zero on success, or a negated errno on failure. -EAGAIN means no bus + * has been bound yet. + * + ****************************************************************************/ + +int up_rtc_settime(FAR const struct timespec *tp) +{ + struct tm newtime; + + DEBUGASSERT(tp != NULL); + + if (!g_rtc_enabled) + { + return -EAGAIN; + } + + gmtime_r(&tp->tv_sec, &newtime); + return pcf8563_setdatetime(&g_pcf8563, &newtime); +} + +#endif /* CONFIG_RTC_PCF8563 */ diff --git a/include/nuttx/timers/pcf8563.h b/include/nuttx/timers/pcf8563.h new file mode 100644 index 00000000000..55bc35a24a3 --- /dev/null +++ b/include/nuttx/timers/pcf8563.h @@ -0,0 +1,85 @@ +/**************************************************************************** + * include/nuttx/timers/pcf8563.h + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +#ifndef __INCLUDE_NUTTX_TIMERS_PCF8563_H +#define __INCLUDE_NUTTX_TIMERS_PCF8563_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#ifdef CONFIG_RTC_PCF8563 + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* The address is fixed in the part and cannot be strapped. */ + +#define PCF8563_I2C_ADDRESS 0x51 + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +#undef EXTERN +#if defined(__cplusplus) +#define EXTERN extern "C" +extern "C" +{ +#else +#define EXTERN extern +#endif + +/**************************************************************************** + * Name: pcf8563_rtc_initialize + * + * Description: + * Bind the driver to a bus and take over as the system's clock. Called + * once from board logic during start up. + * + * Call clock_synchronize() afterwards. The system timer is started from + * whatever the architecture could tell it before this driver existed, + * which for a board whose only clock is on a bus is nothing at all, so + * the time does not become right until something asks for it to be + * copied across. + * + * Input Parameters: + * i2c - The bus the chip is on + * + * Returned Value: + * Zero (OK) on success; a negated errno on failure. + * + ****************************************************************************/ + +int pcf8563_rtc_initialize(FAR struct i2c_master_s *i2c); + +#undef EXTERN +#if defined(__cplusplus) +} +#endif + +#endif /* CONFIG_RTC_PCF8563 */ +#endif /* __INCLUDE_NUTTX_TIMERS_PCF8563_H */