drivers: counter: add Maxim DS3231 support

The DS3231 is an I2C real-time clock with internal temperature
compensated oscillator, maintaining civil time to 1 s precision with
nominal 2 ppm accuracy from 0-40 Cel.

The basic functionality is exposed as a counter that is always running
at 1 Hz.  Much more functionality is exposed as driver-specific API,
including the ability to translate between the time scale of the DS3231
and the time scale of the Zephyr uptime clock.  This allows correlation
of events in the system clock to UTC, TAI, or whatever time scale is
used to maintain the DS3231.

Signed-off-by: Peter A. Bigot <pab@pabigot.com>
This commit is contained in:
Peter A. Bigot 2019-06-23 20:03:52 -05:00 committed by Carles Cufí
parent 967f7a8bcb
commit e444274e95
22 changed files with 2681 additions and 0 deletions

View file

@ -140,6 +140,7 @@
/drivers/clock_control/*nrf* @nordic-krch /drivers/clock_control/*nrf* @nordic-krch
/drivers/counter/ @nordic-krch /drivers/counter/ @nordic-krch
/drivers/counter/counter_cmos.c @andrewboie /drivers/counter/counter_cmos.c @andrewboie
/drivers/counter/maxim_ds3231.c @pabigot
/drivers/crypto/*nrf_ecb* @maciekfabia @anangl /drivers/crypto/*nrf_ecb* @maciekfabia @anangl
/drivers/console/*mux* @jukkar /drivers/console/*mux* @jukkar
/drivers/display/ @vanwinkeljan /drivers/display/ @vanwinkeljan
@ -376,6 +377,7 @@
/samples/drivers/display/ @vanwinkeljan /samples/drivers/display/ @vanwinkeljan
/samples/drivers/ht16k33/ @henrikbrixandersen /samples/drivers/ht16k33/ @henrikbrixandersen
/samples/drivers/lora/ @Mani-Sadhasivam /samples/drivers/lora/ @Mani-Sadhasivam
/samples/drivers/counter/maxim_ds3231/ @pabigot
/samples/net/ @jukkar @tbursztyka @pfalcon /samples/net/ @jukkar @tbursztyka @pfalcon
/samples/net/dns_resolve/ @jukkar @tbursztyka @pfalcon /samples/net/dns_resolve/ @jukkar @tbursztyka @pfalcon
/samples/net/lwm2m_client/ @rlubos /samples/net/lwm2m_client/ @rlubos

View file

@ -27,6 +27,7 @@ Peripherals
pwm.rst pwm.rst
ps2.rst ps2.rst
peci.rst peci.rst
rtc.rst
sensor.rst sensor.rst
spi.rst spi.rst
uart.rst uart.rst

View file

@ -0,0 +1,17 @@
.. _rtc_api:
RTC
###
Overview
********
This is a placeholder for API specific to real-time clocks. Currently
all RTC peripherals are implemented through :ref:`counter_api` with
device-specific API for counters with real-time support.
API Reference
*************
.. doxygengroup:: rtc_interface
:project: Zephyr

View file

@ -15,4 +15,5 @@ zephyr_library_sources_ifdef(CONFIG_COUNTER_CMOS counter_cmos.c)
zephyr_library_sources_ifdef(CONFIG_COUNTER_MCUX_GPT counter_mcux_gpt.c) zephyr_library_sources_ifdef(CONFIG_COUNTER_MCUX_GPT counter_mcux_gpt.c)
zephyr_library_sources_ifdef(CONFIG_COUNTER_XEC counter_mchp_xec.c) zephyr_library_sources_ifdef(CONFIG_COUNTER_XEC counter_mchp_xec.c)
zephyr_library_sources_ifdef(CONFIG_COUNTER_MCUX_LPTMR counter_mcux_lptmr.c) zephyr_library_sources_ifdef(CONFIG_COUNTER_MCUX_LPTMR counter_mcux_lptmr.c)
zephyr_library_sources_ifdef(CONFIG_COUNTER_MAXIM_DS3231 maxim_ds3231.c)
zephyr_library_sources_ifdef(CONFIG_USERSPACE counter_handlers.c) zephyr_library_sources_ifdef(CONFIG_USERSPACE counter_handlers.c)

View file

@ -38,4 +38,6 @@ source "drivers/counter/Kconfig.xec"
source "drivers/counter/Kconfig.mcux_lptmr" source "drivers/counter/Kconfig.mcux_lptmr"
source "drivers/counter/Kconfig.maxim_ds3231"
endif # COUNTER endif # COUNTER

View file

@ -0,0 +1,18 @@
# Copyright (c) 2019 Peter Bigot Consulting, LLC
#
# SPDX-License-Identifier: Apache-2.0
#
config COUNTER_MAXIM_DS3231
bool "Maxim DS3231 RTC/TCXO"
select I2C
select POLL
help
Enable counter driver based on Maxim DS3231 I2C device.
config COUNTER_MAXIM_DS3231_INIT_PRIORITY
int "Init priority"
depends on COUNTER_MAXIM_DS3231
default 65
help
DS3231 device driver initialization priority.

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,39 @@
#
# Copyright (c) 2019 Peter Bigot Consulting, LLC
#
# SPDX-License-Identifier: Apache-2.0
#
description: Maxim DS3231 I2C RTC/TCXO
compatible: "maxim,ds3231"
include: i2c-device.yaml
properties:
reg:
required: true
32k-gpios:
type: phandle-array
required: false
description: |
32 KiHz open drain output
The DS3231 defaults to providing a 32 KiHz square wave on this
signal. The driver does not make use of this, but applications
may want access.
isw-gpios:
type: phandle-array
required: false
description: |
interrupt/square wave open drain output
The DS3231 uses this signal to notify when an alarm has triggered,
and also to produce a square wave aligned to the countdown chain.
Both capabilities are used within the driver. This signal must be
present to support time set and read operations that preserve
sub-second accuracy.

View file

@ -0,0 +1,602 @@
/*
* Copyright (c) 2019 Peter Bigot Consulting, LLC
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file
* @brief Real-time clock control based on the DS3231 counter API.
*
* The [Maxim
* DS3231](https://www.maximintegrated.com/en/products/analog/real-time-clocks/DS3231.html)
* is a high-precision real-time clock with temperature-compensated
* crystal oscillator and support for configurable alarms.
*
* The core Zephyr API to this device is as a counter, with the
* following limitations:
* * counter_read() and counter_*_alarm() cannot be invoked from
* interrupt context, as they require communication with the device
* over an I2C bus.
* * many other counter APIs, such as start/stop/set_top_value are not
* supported as the clock is always running.
* * two alarm channels are supported but are not equally capable:
* channel 0 supports alarms at 1 s resolution, while channel 1
* supports alarms at 1 minute resolution.
*
* Most applications for this device will need to use the extended
* functionality exposed by this header to access the real-time-clock
* features. The majority of these functions must be invoked from
* supervisor mode.
*/
#ifndef ZEPHYR_INCLUDE_DRIVERS_RTC_DS3231_H_
#define ZEPHYR_INCLUDE_DRIVERS_RTC_DS3231_H_
#include <time.h>
#include <drivers/counter.h>
#include <kernel.h>
#include <zephyr/types.h>
#include <sys/notify.h>
#ifdef __cplusplus
extern "C" {
#endif
/** @brief Bit in ctrl or ctrl_stat associated with alarm 1. */
#define MAXIM_DS3231_ALARM1 BIT(0)
/** @brief Bit in ctrl or ctrl_stat associated with alarm 2. */
#define MAXIM_DS3231_ALARM2 BIT(1)
/* Constants corresponding to bits in the DS3231 control register at
* 0x0E.
*
* See the datasheet for interpretation of these bits.
*/
/** @brief ctrl bit for alarm 1 interrupt enable. */
#define MAXIM_DS3231_REG_CTRL_A1IE MAXIM_DS3231_ALARM1
/** @brief ctrl bit for alarm 2 interrupt enable. */
#define MAXIM_DS3231_REG_CTRL_A2IE MAXIM_DS3231_ALARM2
/** @brief ctrl bit for ISQ functionality.
*
* When clear the ISW signal provides a square wave. When set the ISW
* signal indicates alarm events.
*
* @note The driver expects to be able to control this bit.
*/
#define MAXIM_DS3231_REG_CTRL_INTCN BIT(2)
/** @brief ctrl bit offset for square wave output frequency.
*
* @note The driver will control the content of this field.
*/
#define MAXIM_DS3231_REG_CTRL_RS_Pos 3
/** @brief ctrl mask to isolate RS bits. */
#define MAXIM_DS3231_REG_CTRL_RS_Msk (0x03 << MAXIM_DS3231_REG_CTRL_RS_Pos)
/** @brief ctrl RS field value for 1 Hz square wave. */
#define MAXIM_DS3231_REG_CTRL_RS_1Hz 0x00
/** @brief ctrl RS field value for 1024 Hz square wave. */
#define MAXIM_DS3231_REG_CTRL_RS_1KiHz 0x01
/** @brief ctrl RS field value for 4096 Hz square wave. */
#define MAXIM_DS3231_REG_CTRL_RS_4KiHz 0x02
/** @brief ctrl RS field value for 8192 Hz square wave. */
#define MAXIM_DS3231_REG_CTRL_RS_8KiHz 0x03
/** @brief ctrl bit to write to trigger temperature conversion. */
#define MAXIM_DS3231_REG_CTRL_CONV BIT(5)
/** @brief ctrl bit to write to enable square wave output in battery mode. */
#define MAXIM_DS3231_REG_CTRL_BBSQW BIT(6)
/** @brief ctrl bit to write to disable the oscillator. */
#define MAXIM_DS3231_REG_CTRL_EOSCn BIT(7),
/** @brief ctrl_stat bit indicating alarm1 has triggered.
*
* If an alarm callback handler is registered this bit is
* cleared prior to invoking the callback with the flags
* indicating which alarms are ready.
*/
#define MAXIM_DS3231_REG_STAT_A1F MAXIM_DS3231_ALARM1
/** @brief ctrl_stat bit indicating alarm2 has triggered.
*
* If an alarm callback handler is registered this bit is
* cleared prior to invoking the callback with the flags
* indicating which alarms are ready.
*/
#define MAXIM_DS3231_REG_STAT_A2F MAXIM_DS3231_ALARM2
/** @brief Flag indicating a temperature conversion is in progress. */
#define MAXIM_DS3231_REG_STAT_BSY BIT(2)
/** @brief Set to enable 32 KiHz open drain signal.
*
* @note This is a control bit, though it is positioned within the
* ctrl_stat register which otherwise contains status bits.
*/
#define MAXIM_DS3231_REG_STAT_EN32kHz BIT(3)
/** @brief Flag indicating the oscillator has been off since last cleared. */
#define MAXIM_DS3231_REG_STAT_OSF BIT(7)
/** @brief Control alarm behavior on match in seconds field.
*
* If clear the alarm fires only when the RTC seconds matches the
* alarm seconds.
*
* If set the alarm seconds field is ignored and an alarm will be
* triggered every second. The bits for IGNMN, IGNHR, and IGNDA must
* all be set.
*
* This bit must be clear for the second alarm instance.
*
* Bit maps to A1M1 and is used in
* maxim_ds3231_alarm_configuration::alarm_flags.
*/
#define MAXIM_DS3231_ALARM_FLAGS_IGNSE BIT(0)
/** @brief Control alarm behavior on match in minutes field.
*
* If clear the alarm fires only when the RTC minutes matches the
* alarm minutes. The bit for IGNSE must be clear.
*
* If set the alarm minutes field is ignored and alarms will be
* triggered based on IGNSE. The bits for IGNHR and IGNDA must both be
* set.
*
* Bit maps to A1M2 or A2M2 and is used in
* maxim_ds3231_alarm_configuration::alarm_flags.
*/
#define MAXIM_DS3231_ALARM_FLAGS_IGNMN BIT(1)
/** @brief Control alarm behavior on match in hours field.
*
* If clear the alarm fires only when the RTC hours matches the
* alarm hours. The bits for IGNMN and IGNSE must be clear.
*
* If set the alarm hours field is ignored and alarms will be
* triggered based on IGNMN and IGNSE. The bit for IGNDA must be set.
*
* Bit maps to A1M3 or A2M3 and is used in
* maxim_ds3231_alarm_configuration::alarm_flags.
*/
#define MAXIM_DS3231_ALARM_FLAGS_IGNHR BIT(2)
/** @brief Control alarm behavior on match in day/date field.
*
* If clear the alarm fires only when the RTC day/date matches the
* alarm day/date, mediated by MAXIM_DS3231_ALARM_FLAGS_DAY. The bits
* for IGNHR, IGNMN, and IGNSE must be clear
*
* If set the alarm day/date field is ignored and an alarm will be
* triggered based on IGNHR, IGNMN, and IGNSE.
*
* Bit maps to A1M4 or A2M4 and is used in
* maxim_ds3231_alarm_configuration::alarm_flags.
*/
#define MAXIM_DS3231_ALARM_FLAGS_IGNDA BIT(3)
/** @brief Control match on day of week versus day of month
*
* Set the flag to match on day of week; clear it to match on day of
* month.
*
* Bit maps to DY/DTn in corresponding
* maxim_ds3231_alarm_configuration::alarm_flags.
*/
#define MAXIM_DS3231_ALARM_FLAGS_DOW BIT(4)
/** @brief Indicates that the alarm should be disabled once it fires.
*
* Set the flag in the maxim_ds3231_alarm_configuration::alarm_flags
* field to cause the alarm to be disabled when the interrupt fires,
* prior to invoking the corresponding handler.
*
* Leave false to allow the alarm to remain enabled so it will fire
* again on the next match.
*/
#define MAXIM_DS3231_ALARM_FLAGS_AUTODISABLE BIT(7)
/**
* @brief RTC DS3231 Driver-Specific API
* @defgroup rtc_interface Real Time Clock interfaces
* @ingroup io_interfaces
* @{
*/
/** @brief Signature for DS3231 alarm callbacks.
*
* The alarm callback is invoked from the system work queue thread.
* At the point the callback is invoked the corresponding alarm flags
* will have been cleared from the device status register. The
* callback is permitted to invoke operations on the device.
*
* @param dev the device from which the callback originated
*
* @param syncclock the value from maxim_ds3231_read_syncclock() at the
* time the alarm interrupt was processed.
*
* @param user_data the corresponding parameter from
* maxim_ds3231_alarm::user_data.
*/
typedef void (*maxim_ds3231_alarm_callback_handler_t)(struct device *dev,
u8_t id,
u32_t syncclock,
void *user_data);
/** @brief Signature used to notify a user of the DS3231 that an
* asynchronous operation has completed.
*
* Functions compatible with this type are subject to all the
* constraints of #sys_notify_generic_callback.
*
* @param dev the DS3231 device pointer
*
* @param notify the notification structure provided in the call
*
* @param res the result of the operation.
*/
typedef void (*maxim_ds3231_notify_callback)(struct device *dev,
struct sys_notify *notify,
int res);
/** @brief Information defining the alarm configuration.
*
* DS3231 alarms can be set to fire at specific times or at the
* rollover of minute, hour, day, or day of week.
*
* When an alarm is configured with a handler an interrupt will be
* generated and the handler called from the system work queue.
*
* When an alarm is configured without a handler, or a persisted alarm
* is present, alarms can be read using maxim_ds3231_check_alarms().
*/
struct maxim_ds3231_alarm {
/** @brief Time specification for an RTC alarm.
*
* Though specified as a UNIX time, the alarm parameters are
* determined by converting to civil time and interpreting the
* component hours, minutes, seconds, day-of-week, and
* day-of-month fields, mediated by the corresponding #flags.
*
* The year and month are ignored, but be aware that gmtime()
* determines day-of-week based on calendar date. Decoded
* alarm times will fall within 1978-01 since 1978-01-01
* (first of month) was a Sunday (first of week).
*/
time_t time;
/** @brief Handler to be invoked when alarms are signalled.
*
* If this is null the alarm will not be triggered by the
* INTn/SQW GPIO. This is a "persisted" alarm from its role
* in using the DS3231 to trigger a wake from deep sleep. The
* application should use maxim_ds3231_check_alarms() to
* determine whether such an alarm has been triggered.
*
* If this is not null the driver will monitor the ISW GPIO
* for alarm signals and will invoke the handler with a
* parameter carrying the value returned by
* maxim_ds3231_check_alarms(). The corresponding status flags
* will be cleared in the device before the handler is
* invoked.
*
* The handler will be invoked from the system work queue.
*/
maxim_ds3231_alarm_callback_handler_t handler;
/** @brief User-provided pointer passed to alarm callback. */
void *user_data;
/** @brief Flags controlling configuration of the alarm alarm.
*
* See MAXIM_DS3231_ALARM_FLAGS_IGNSE and related constants.
*
* Note that as described the alarm mask fields require that
* if a unit is not ignored, higher-precision units must also
* not be ignored. For example, if match on hours is enabled,
* match on minutes and seconds must also be enabled. Failure
* to comply with this requirement will cause
* maxim_ds3231_set_alarm() to return an error, leaving the
* alarm configuration unchanged.
*/
u8_t flags;
};
/** @brief Register the RTC clock against system clocks.
*
* This captures the same instant in both the RTC time scale and a
* stable system clock scale, allowing conversion between those
* scales.
*/
struct maxim_ds3231_syncpoint {
/** @brief Time from the DS3231.
*
* This maybe in UTC, TAI, or local offset depending on how
* the RTC is maintained.
*/
struct timespec rtc;
/** @brief Value of a local clock at the same instant as #rtc.
*
* This is captured from a stable monotonic system clock
* running at between 1 kHz and 1 MHz, allowing for
* microsecond to millisecond accuracy in synchronization.
*/
u32_t syncclock;
};
/** @brief Read the local synchronization clock.
*
* Synchronization aligns the DS3231 real-time clock with a stable
* monotonic local clock which should have a frequency between 1 kHz
* and 1 MHz and be itself synchronized with the primary system time
* clock. The accuracy of the alignment and the maximum time between
* synchronization updates is affected by the resolution of this
* clock.
*
* On some systems the hardware clock from k_cycles_get_32() is
* suitable, but on others that clock advances too quickly. The
* frequency of the target-specific clock is provided by
* maxim_ds3231_syncclock_frequency().
*
* At this time the value is captured from `k_uptime_get_32()`; future
* kernel extensions may make a higher-resolution clock available.
*
* @note This function is *isr-ok*.
*
* @param dev the DS3231 device pointer
*
* @return the current value of the synchronization clock.
*/
static inline u32_t maxim_ds3231_read_syncclock(struct device *dev)
{
return k_uptime_get_32();
}
/** @brief Get the frequency of the synchronization clock.
*
* Provides the frequency of the clock used in maxim_ds3231_read_syncclock().
*
* @param dev the DS3231 device pointer
*
* @return the frequency of the selected synchronization clock.
*/
static inline u32_t maxim_ds3231_syncclock_frequency(struct device *dev)
{
return 1000U;
}
/**
* @brief Set and clear specific bits in the control register.
*
* @note This function assumes the device register cache is valid. It
* will not read the register value, and it will write to the device
* only if the value changes as a result of applying the set and clear
* changes.
*
* @note Unlike maxim_ds3231_stat_update() the return value from this
* function indicates the register value after changes were made.
* That return value is cached for use in subsequent operations.
*
* @note This function is *supervisor*.
*
* @return the non-negative updated value of the register, or a
* negative error code from an I2C transaction.
*/
int maxim_ds3231_ctrl_update(struct device *dev,
u8_t set_bits,
u8_t clear_bits);
/**
* @brief Read the ctrl_stat register then set and clear bits in it.
*
* The content of the ctrl_stat register will be read, then the set
* and clear bits applied and the result written back to the device
* (regardless of whether there appears to be a change in value).
*
* OSF, A1F, and A2F will be written with 1s if the corresponding bits
* do not appear in either @p set_bits or @p clear_bits. This ensures
* that if any flag becomes set between the read and the write that
* indicator will not be cleared.
*
* @note Unlike maxim_ds3231_ctrl_update() the return value from this
* function indicates the register value before any changes were made.
*
* @note This function is *supervisor*.
*
* @param dev the DS3231 device pointer
*
* @param set_bits bits to be set when writing back. Setting bits
* other than @ref MAXIM_DS3231_REG_STAT_EN32kHz will have no effect.
*
* @param clear_bits bits to be cleared when writing back. Include
* the bits for the status flags you want to clear.
*
* @return the non-negative register value as originally read
* (disregarding the effect of clears and sets), or a negative error
* code from an I2C transaction.
*/
int maxim_ds3231_stat_update(struct device *dev,
u8_t set_bits,
u8_t clear_bits);
/** @brief Read a DS3231 alarm configuration.
*
* The alarm configuration data is read from the device and
* reconstructed into the output parameter.
*
* @note This function is *supervisor*.
*
* @param dev the DS3231 device pointer.
*
* @param id the alarm index, which must be 0 (for the 1 s resolution
* alarm) or 1 (for the 1 min resolution alarm).
*
* @param cfg a pointer to a structure into which the configured alarm
* data will be stored.
*
* @return a non-negative value indicating successful conversion, or a
* negative error code from an I2C transaction or invalid parameter.
*/
int maxim_ds3231_get_alarm(struct device *dev,
u8_t id,
struct maxim_ds3231_alarm *cfg);
/** @brief Configure a DS3231 alarm.
*
* The alarm configuration is validated and stored into the device.
*
* To cancel an alarm use counter_cancel_channel_alarm().
*
* @note This function is *supervisor*.
*
* @param dev the DS3231 device pointer.
*
* @param id 0 Analog to counter index. @c ALARM1 is 0 and has 1 s
* resolution, @c ALARM2 is 1 and has 1 minute resolution.
*
* @param cfg a pointer to the desired alarm configuration. Both
* alarms are configured; if only one is to change the application
* must supply the existing configuration for the other.
*
* @return a non-negative value on success, or a negative error code
* from an I2C transaction or an invalid parameter.
*/
int maxim_ds3231_set_alarm(struct device *dev,
u8_t id,
const struct maxim_ds3231_alarm *cfg);
/** @brief Synchronize the RTC against the local clock.
*
* The RTC advances one tick per second with no access to sub-second
* precision. Synchronizing clocks at sub-second resolution requires
* enabling a 1pps signal then capturing the system clocks in a GPIO
* callback. This function provides that operation.
*
* Synchronization is performed in asynchronously, and may take as
* long as 1 s to complete; notification of completion is provided
* through the @p notify parameter.
*
* Applications should use maxim_ds3231_get_syncpoint() to retrieve the
* synchronization data collected by this operation.
*
* @note This function is *supervisor*.
*
* @param dev the DS3231 device pointer.
*
* @param notify pointer to the object used to specify asynchronous
* function behavior and store completion information.
*
* @retval non-negative on success
* @retval -EBUSY if a synchronization or set is currently in progress
* @retval -EINVAL if notify is not provided
* @retval -ENOTSUP if the required interrupt is not configured
*/
int maxim_ds3231_synchronize(struct device *dev,
struct sys_notify *notify);
/** @brief Request to update the synchronization point.
*
* This is a variant of maxim_ds3231_synchronize() for use from user
* threads.
*
* @param dev the DS3231 device pointer.
*
* @param signal pointer to a valid and ready-to-be-signalled
* k_poll_signal. May be NULL to request a synchronization point be
* collected without notifying when it has been updated.
*
* @retval non-negative on success
* @retval -EBUSY if a synchronization or set is currently in progress
* @retval -ENOTSUP if the required interrupt is not configured
*/
__syscall int maxim_ds3231_req_syncpoint(struct device *dev,
struct k_poll_signal *signal);
/** @brief Retrieve the most recent synchronization point.
*
* This function returns the synchronization data last captured using
* maxim_ds3231_synchronize().
*
* @param dev the DS3231 device pointer.
*
* @param syncpoint where to store the synchronization data.
*
* @retval non-negative on success
* @retval -ENOENT if no syncpoint has been captured
*/
__syscall int maxim_ds3231_get_syncpoint(struct device *dev,
struct maxim_ds3231_syncpoint *syncpoint);
/** @brief Set the RTC to a time consistent with the provided
* synchronization.
*
* The RTC advances one tick per second with no access to sub-second
* precision, and setting the clock resets the internal countdown
* chain. This function implements the magic necessary to set the
* clock while retaining as much sub-second accuracy as possible. It
* requires a synchronization point that pairs sub-second resolution
* civil time with a local synchronization clock captured at the same
* instant. The set operation may take as long as 1 second to
* complete; notification of completion is provided through the @p
* notify parameter.
*
* @note This function is *supervisor*.
*
* @param dev the DS3231 device pointer.
*
* @param syncpoint the structure providing the synchronization point.
*
* @param notify pointer to the object used to specify asynchronous
* function behavior and store completion information.
*
* @retval non-negative on success
* @retval -EINVAL if syncpoint or notify are null
* @retval -ENOTSUP if the required interrupt signal is not configured
* @retval -EBUSY if a synchronization or set is currently in progress
*/
int maxim_ds3231_set(struct device *dev,
const struct maxim_ds3231_syncpoint *syncpoint,
struct sys_notify *notify);
/** @brief Check for and clear flags indicating that an alarm has
* fired.
*
* Returns a mask indicating alarms that are marked as having fired,
* and clears from stat the flags that it found set. Alarms that have
* been configured with a callback are not represented in the return
* value.
*
* This API may be used when a persistent alarm has been programmed.
*
* @note This function is *supervisor*.
*
* @param dev the DS3231 device pointer.
*
* @return a non-negative value that may have MAXIM_DS3231_ALARM1 and/or
* MAXIM_DS3231_ALARM2 set, or a negative error code.
*/
int maxim_ds3231_check_alarms(struct device *dev);
/**
* @}
*/
#ifdef __cplusplus
}
#endif
/* @todo this should be syscalls/drivers/rtc/maxim_ds3231.h */
#include <syscalls/maxim_ds3231.h>
#endif /* ZEPHYR_INCLUDE_DRIVERS_RTC_DS3231_H_ */

View file

@ -0,0 +1,8 @@
# SPDX-License-Identifier: Apache-2.0
cmake_minimum_required(VERSION 3.13.1)
find_package(Zephyr HINTS $ENV{ZEPHYR_BASE})
project(maxim_ds3231)
FILE(GLOB app_sources src/*.c)
target_sources(app PRIVATE ${app_sources})

View file

@ -0,0 +1,12 @@
# Copyright (c) 2019 Peter Bigot Consulting, LLC
#
# SPDX-License-Identifier: Apache-2.0
#
config APP_SET_ALIGNED_CLOCK
bool "Option to align RTC with time-since-boot"
help
If enabled this reads the RTC then sets it so that uptime
shows as being relative to the start of the next hour.
source "Kconfig"

View file

@ -0,0 +1,154 @@
.. _maxim-ds3231-sample:
Maxim DS3231 TCXO RTC Sample Application
########################################
Overview
********
The `DS3231`_ temperature-compensated real-time clock is a
high-precision (2 ppm) battery backed clock that maintains civil time
and supports alarms. The `Chronodot`_ breakout board can be used to
test it.
Annotated Example Output
************************
The sample first displays the boot banner, board identifier and
frequency of the local clock used for synchronization, and whether the
DS3231 has recorded a loss-of-oscillator::
***** Booting Zephyr OS build zephyr-v1.14.0-2409-g322d53aedaa0 *****
DS3231 on particle_xenon syncclock 1000 Hz
.
DS3231 has not experienced an oscillator fault
Next, information about the device as a counter is displayed. The
counter value is read, and its value formatted as the date, time, day of
week, and day of year (19 July 2019 is a Friday, and is the 200th day of
2019)::
Counter at 0x20001a58
Max top value: 4294967295 (ffffffff)
2 channels
1 Hz
Top counter value: 4294967295 (ffffffff)
Now 1563512509: 2019-07-19 05:01:49 Fri 200
The DS3231 control and status register values are displayed::
DS3231 ctrl 04 ; ctrl_stat 08
Next, if the sample application option ``CONFIG_APP_SET_ALIGNED_CLOCK``
is set, the civil time will be advanced to the start of the next hour,
and the clock will be set to align that time with the time of the boot,
which in the output below is 34 ms in the past. The time required to
synchronize the clock is 967 ms, and the whole second value of one
second past the hour is written at 1000 ms local uptime::
Set 2019-07-19 06:00:00.034000000 Fri 200 at 34 ms past: 0
Synchronize final: 0 0 in 967 ms
wrote sync 0: 1563516001 0 at 1000
Then a synchronization point is read. This takes 894 ms (it must align
to an RTC one-second rollover)::
Synchronize init: 0
Synchronize complete in 894 ms: 0 0
.
read sync 0: 1563516002 0 at 2000
The alarm configuration is read from non-volatile memory and displayed.
See the ``maxim_ds3231.h`` for interpretation of the integer value and
flags::
Alarm 1 flags 0 at 254034017: 0
Alarm 2 flags e at 252374400: 0
Five seconds is added to the current time and the civil time
representation displayed. The second-resolution alarm is configured to
fire at that time on the current day-of-week. The minute-resolution
alarm is configured to fire once per minute::
Min Sec base time: 2019-07-19 06:00:07 Fri 200
Set sec alarm 90 at 1563516007 ~ 2019-07-19 06:00:07 Fri 200: 5
Set min alarm flags f at 1563516007 ~ 2019-07-19 06:00:07 Fri 200: 7
We're now 2.131 ms into the run, at which point the alarms are read back
and displayed. Alarms do not include date but can include day-of-week
or day-of-month; the date is selected to preserve that information::
2131 ms in: get alarms: 0 0
Sec alarm flags 10 at 252914407 ~ 1978-01-06 06:00:07 Fri 006
Min alarm flags e at 252374400 ~ 1977-12-31 00:00:00 Sat 365
The second-resolution alarm was signalled, and processed by the
application at 7.002 s into the run, as scheduled (plus callback
latency). The callback uses the counter alarm API to schedule a second
alarm in 10 seconds::
Sec signaled at 7002 ms, param 0x20000048, delay 1; set 7
The counter API callback is called at the correct time::
Counter callback at 17001 ms, id 0, ticks 1563516017, ud 0x20000048
From here on the sample sleeps except when the minute-resolution alarm
fires, at which point it displays the RTC time; the
nanosecond-resolution offset in seconds between the RTC time and the
local time; the local time from ``k_uptime_get()``; and the aggregate
error between local and RTC time measured in parts-per-million::
2019-07-19 06:01:00 Fri 200: adj 0.002000000, uptime 0:01:00.002, clk err 34 ppm
2019-07-19 06:02:00 Fri 200: adj 0.003000000, uptime 0:02:00.004, clk err 25 ppm
2019-07-19 06:03:00 Fri 200: adj 0.005000000, uptime 0:03:00.005, clk err 28 ppm
2019-07-19 06:04:00 Fri 200: adj 0.006000000, uptime 0:04:00.007, clk err 25 ppm
2019-07-19 06:05:00 Fri 200: adj 0.008000000, uptime 0:05:00.008, clk err 26 ppm
The output shows that the Zephyr system clock is running about 25 ppm
faster than civil time on this device. This amount of error is expected
for this target as the system time derives from a crystal oscillator
with a similar accuracy.
Building and Running
********************
Wire a Chronodot to one of the supported boards as specified in the
corresponding devicetree overlay.
* Particle Xenon
.. zephyr-app-commands::
:zephyr-app: samples/drivers/counter/maxim_ds3231
:board: particle-xenon
:goals: build
:compact:
* NXP Freedom K64F
.. zephyr-app-commands::
:zephyr-app: samples/drivers/counter/maxim_ds3231
:board: frdm_k64f
:goals: build
:compact:
* ST Nucleo L476RG
.. zephyr-app-commands::
:zephyr-app: samples/drivers/counter/maxim_ds3231
:board: nucleo_l476rg
:goals: build
:compact:
* EFR32 Mighty Gecko Thunderboard Sense 2
.. zephyr-app-commands::
:zephyr-app: samples/drivers/counter/maxim_ds3231
:board: efr32mg_sltb004a
:goals: build
:compact:
.. _DS3231:
https://www.maximintegrated.com/en/products/analog/real-time-clocks/DS3231.html
.. _Chronodot:
http://macetech.com/store/index.php?main_page=product_info&products_id=8

View file

@ -0,0 +1,15 @@
/*
* Copyright (c) 2019 Peter Bigot Consulting, LLC
*
* SPDX-License-Identifier: Apache-2.0
*/
&i2c0 { /* SDA H16=PC10, SCL H15=PC11, ISW H13=PF6 */
status = "okay";
ds3231: ds3231@68 {
compatible = "maxim,ds3231";
reg = <0x68>;
label = "DS3231";
isw-gpios = <&gpiof 6 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>;
};
};

View file

@ -0,0 +1,15 @@
/*
* Copyright (c) 2019 Peter Bigot Consulting, LLC
*
* SPDX-License-Identifier: Apache-2.0
*/
&i2c0 { /* SDA PTE25, SCL PTE24, ISW PTE26 */
status = "okay";
ds3231: ds3231@68 {
compatible = "maxim,ds3231";
reg = <0x68>;
label = "DS3231";
isw-gpios = <&gpioe 26 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>;
};
};

View file

@ -0,0 +1,15 @@
/*
* Copyright (c) 2019 Peter Bigot Consulting, LLC
*
* SPDX-License-Identifier: Apache-2.0
*/
&i2c0 { /* SDA 30, SCL 7 */
status = "okay";
ds3231: ds3231@68 {
compatible = "maxim,ds3231";
reg = <0x68>;
label = "DS3231";
isw-gpios = <&gpio0 0 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>;
};
};

View file

@ -0,0 +1,15 @@
/*
* Copyright (c) 2019 Peter Bigot Consulting, LLC
*
* SPDX-License-Identifier: Apache-2.0
*/
&i2c0 { /* SDA P0.26, SCL P0.27 */
status = "okay";
ds3231: ds3231@68 {
compatible = "maxim,ds3231";
reg = <0x68>;
label = "DS3231";
isw-gpios = <&gpio0 0 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>;
};
};

View file

@ -0,0 +1,15 @@
/*
* Copyright (c) 2019 Peter Bigot Consulting, LLC
*
* SPDX-License-Identifier: Apache-2.0
*/
&i2c1 { /* SDA CN5.9=PB9, SCL CN5.10=PB8, ISW CN5.1=D8=PA9 */
status = "okay";
ds3231: ds3231@68 {
compatible = "maxim,ds3231";
reg = <0x68>;
label = "DS3231";
isw-gpios = <&gpioa 9 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>;
};
};

View file

@ -0,0 +1,16 @@
/*
* Copyright (c) 2019 Peter Bigot Consulting, LLC
*
* SPDX-License-Identifier: Apache-2.0
*/
&i2c0 { /* SDA P0.26, SCL P0.27, ISW P1.1, 32K P1.2 */
status = "okay";
ds3231: ds3231@68 {
compatible = "maxim,ds3231";
reg = <0x68>;
label = "DS3231";
isw-gpios = <&gpio1 1 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>;
32k-gpios = <&gpio1 2 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>;
};
};

View file

@ -0,0 +1,16 @@
CONFIG_PRINTK=y
CONFIG_I2C=y
CONFIG_COUNTER=y
CONFIG_COUNTER_MAXIM_DS3231=y
# Minimal libc doesn't have strftime()
CONFIG_NEWLIB_LIBC=y
# Optional step that syncs RTC and local clock. Don't enable this if
# your RTC has already been synchronized and you want to keep its
# setting.
CONFIG_APP_SET_ALIGNED_CLOCK=y
#CONFIG_LOG=y
#CONFIG_COUNTER_LOG_LEVEL_DBG=y

View file

@ -0,0 +1,6 @@
sample:
name: Maxim DS3231 RTC
tests:
sample.basic.maxim_ds3231:
build_only: true
platform_whitelist: efr32mg_sltb004a frdm_k64f nrf51_pca10028 nucleo_l476rg particle_xenon

View file

@ -0,0 +1,342 @@
/*
* Copyright (c) 2019-2020 Peter Bigot Consulting, LLC
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <zephyr.h>
#include <device.h>
#include <drivers/counter.h>
#include <sys/printk.h>
#include <drivers/rtc/maxim_ds3231.h>
/* Format times as: YYYY-MM-DD HH:MM:SS DOW DOY */
static const char *format_time(time_t time,
long nsec)
{
static char buf[64];
char *bp = buf;
char *const bpe = bp + sizeof(buf);
struct tm tv;
struct tm *tp = gmtime_r(&time, &tv);
bp += strftime(bp, bpe - bp, "%Y-%m-%d %H:%M:%S", tp);
if (nsec >= 0) {
bp += snprintf(bp, bpe - bp, ".%09lu", nsec);
}
bp += strftime(bp, bpe - bp, " %a %j", tp);
return buf;
}
static void sec_counter_callback(struct device *dev,
u8_t id,
u32_t ticks,
void *ud)
{
printk("Counter callback at %u ms, id %d, ticks %u, ud %p\n",
k_uptime_get_32(), id, ticks, ud);
}
static void sec_alarm_handler(struct device *dev,
u8_t id,
u32_t syncclock,
void *ud)
{
u32_t now = maxim_ds3231_read_syncclock(dev);
struct counter_alarm_cfg alarm = {
.callback = sec_counter_callback,
.ticks = 10,
.user_data = ud,
};
printk("setting channel alarm\n");
int rc = counter_set_channel_alarm(dev, id, &alarm);
printk("Sec signaled at %u ms, param %p, delay %u; set %d\n",
k_uptime_get_32(), ud, now - syncclock, rc);
}
/** Calculate the normalized result of a - b.
*
* For both inputs and outputs tv_nsec must be in the range [0,
* NSEC_PER_SEC). tv_sec may be negative, zero, or positive.
*/
void timespec_subtract(struct timespec *amb,
const struct timespec *a,
const struct timespec *b)
{
if (a->tv_nsec >= b->tv_nsec) {
amb->tv_nsec = a->tv_nsec - b->tv_nsec;
amb->tv_sec = a->tv_sec - b->tv_sec;
} else {
amb->tv_nsec = NSEC_PER_SEC + a->tv_nsec - b->tv_nsec;
amb->tv_sec = a->tv_sec - b->tv_sec - 1;
}
}
/** Calculate the normalized result of a + b.
*
* For both inputs and outputs tv_nsec must be in the range [0,
* NSEC_PER_SEC). tv_sec may be negative, zero, or positive.
*/
void timespec_add(struct timespec *apb,
const struct timespec *a,
const struct timespec *b)
{
apb->tv_nsec = a->tv_nsec + b->tv_nsec;
apb->tv_sec = a->tv_sec + b->tv_sec;
if (apb->tv_nsec >= NSEC_PER_SEC) {
apb->tv_sec += 1;
apb->tv_nsec -= NSEC_PER_SEC;
}
}
static void min_alarm_handler(struct device *dev,
u8_t id,
u32_t syncclock,
void *ud)
{
u32_t time = 0;
struct maxim_ds3231_syncpoint sp = { 0 };
(void)counter_get_value(dev, &time);
u32_t uptime = k_uptime_get_32();
u8_t us = uptime % 1000U;
uptime /= 1000U;
u8_t se = uptime % 60U;
uptime /= 60U;
u8_t mn = uptime % 60U;
uptime /= 60U;
u8_t hr = uptime;
(void)maxim_ds3231_get_syncpoint(dev, &sp);
u32_t offset_syncclock = syncclock - sp.syncclock;
u32_t offset_s = time - (u32_t)sp.rtc.tv_sec;
u32_t syncclock_Hz = maxim_ds3231_syncclock_frequency(dev);
struct timespec adj;
adj.tv_sec = offset_syncclock / syncclock_Hz;
adj.tv_nsec = (offset_syncclock % syncclock_Hz)
* (u64_t)NSEC_PER_SEC / syncclock_Hz;
s32_t err_ppm = (s32_t)(offset_syncclock
- offset_s * syncclock_Hz)
* (s64_t)1000000
/ (s32_t)syncclock_Hz / (s32_t)offset_s;
struct timespec *ts = &sp.rtc;
ts->tv_sec += adj.tv_sec;
ts->tv_nsec += adj.tv_nsec;
if (ts->tv_nsec >= NSEC_PER_SEC) {
ts->tv_sec += 1;
ts->tv_nsec -= NSEC_PER_SEC;
}
printk("%s: adj %d.%09lu, uptime %u:%02u:%02u.%03u, clk err %d ppm\n",
format_time(time, -1),
(u32_t)(ts->tv_sec - time), ts->tv_nsec,
hr, mn, se, us, err_ppm);
}
struct maxim_ds3231_alarm sec_alarm;
struct maxim_ds3231_alarm min_alarm;
static void show_counter(struct device *ds3231)
{
u32_t now = 0;
printk("\nCounter at %p\n", ds3231);
printk("\tMax top value: %u (%08x)\n",
counter_get_max_top_value(ds3231),
counter_get_max_top_value(ds3231));
printk("\t%u channels\n", counter_get_num_of_channels(ds3231));
printk("\t%u Hz\n", counter_get_frequency(ds3231));
printk("Top counter value: %u (%08x)\n",
counter_get_top_value(ds3231),
counter_get_top_value(ds3231));
(void)counter_get_value(ds3231, &now);
printk("Now %u: %s\n", now, format_time(now, -1));
}
/* Take the currently stored RTC time and round it up to the next
* hour. Program the RTC as though this time had occurred at the
* moment the application booted.
*
* Subsequent reads of the RTC time adjusted based on a syncpoint
* should match the uptime relative to the programmed hour.
*/
static void set_aligned_clock(struct device *ds3231)
{
if (!IS_ENABLED(CONFIG_APP_SET_ALIGNED_CLOCK)) {
return;
}
u32_t syncclock_Hz = maxim_ds3231_syncclock_frequency(ds3231);
u32_t syncclock = maxim_ds3231_read_syncclock(ds3231);
u32_t now = 0;
int rc = counter_get_value(ds3231, &now);
u32_t align_hour = now + 3600 - (now % 3600);
struct maxim_ds3231_syncpoint sp = {
.rtc = {
.tv_sec = align_hour,
.tv_nsec = (u64_t)NSEC_PER_SEC * syncclock / syncclock_Hz,
},
.syncclock = syncclock,
};
struct k_poll_signal ss;
struct sys_notify notify;
struct k_poll_event sevt = K_POLL_EVENT_INITIALIZER(K_POLL_TYPE_SIGNAL,
K_POLL_MODE_NOTIFY_ONLY,
&ss);
k_poll_signal_init(&ss);
sys_notify_init_signal(&notify, &ss);
u32_t t0 = k_uptime_get_32();
rc = maxim_ds3231_set(ds3231, &sp, &notify);
printk("\nSet %s at %u ms past: %d\n", format_time(sp.rtc.tv_sec, sp.rtc.tv_nsec),
syncclock, rc);
/* Wait for the set to complete */
rc = k_poll(&sevt, 1, K_FOREVER);
u32_t t1 = k_uptime_get_32();
/* Delay so log messages from sync can complete */
k_sleep(K_MSEC(100));
printk("Synchronize final: %d %d in %u ms\n", rc, ss.result, t1 - t0);
rc = maxim_ds3231_get_syncpoint(ds3231, &sp);
printk("wrote sync %d: %u %u at %u\n", rc,
(u32_t)sp.rtc.tv_sec, (u32_t)sp.rtc.tv_nsec,
sp.syncclock);
}
void main(void)
{
struct device *ds3231;
const char *const dev_id = DT_LABEL(DT_INST(0, maxim_ds3231));
ds3231 = device_get_binding(dev_id);
if (!ds3231) {
printk("No device %s available\n", dev_id);
return;
}
u32_t syncclock_Hz = maxim_ds3231_syncclock_frequency(ds3231);
printk("DS3231 on %s syncclock %u Hz\n\n", CONFIG_BOARD, syncclock_Hz);
int rc = maxim_ds3231_stat_update(ds3231, 0, MAXIM_DS3231_REG_STAT_OSF);
if (rc >= 0) {
printk("DS3231 has%s experienced an oscillator fault\n",
(rc & MAXIM_DS3231_REG_STAT_OSF) ? "" : " not");
} else {
printk("DS3231 stat fetch failed: %d\n", rc);
return;
}
/* Show the DS3231 counter properties */
show_counter(ds3231);
/* Show the DS3231 ctrl and ctrl_stat register values */
printk("\nDS3231 ctrl %02x ; ctrl_stat %02x\n",
maxim_ds3231_ctrl_update(ds3231, 0, 0),
maxim_ds3231_stat_update(ds3231, 0, 0));
/* Test maxim_ds3231_set, if enabled */
set_aligned_clock(ds3231);
struct k_poll_signal ss;
struct sys_notify notify;
struct maxim_ds3231_syncpoint sp = { 0 };
struct k_poll_event sevt = K_POLL_EVENT_INITIALIZER(K_POLL_TYPE_SIGNAL,
K_POLL_MODE_NOTIFY_ONLY,
&ss);
k_poll_signal_init(&ss);
sys_notify_init_signal(&notify, &ss);
u32_t t0 = k_uptime_get_32();
rc = maxim_ds3231_synchronize(ds3231, &notify);
printk("\nSynchronize init: %d\n", rc);
rc = k_poll(&sevt, 1, K_FOREVER);
u32_t t1 = k_uptime_get_32();
k_sleep(K_MSEC(100)); /* wait for log messages */
printk("Synchronize complete in %u ms: %d %d\n", t1 - t0, rc, ss.result);
rc = maxim_ds3231_get_syncpoint(ds3231, &sp);
printk("\nread sync %d: %u %u at %u\n", rc,
(u32_t)sp.rtc.tv_sec, (u32_t)sp.rtc.tv_nsec,
sp.syncclock);
rc = maxim_ds3231_get_alarm(ds3231, 0, &sec_alarm);
printk("\nAlarm 1 flags %x at %u: %d\n", sec_alarm.flags,
(u32_t)sec_alarm.time, rc);
rc = maxim_ds3231_get_alarm(ds3231, 1, &min_alarm);
printk("Alarm 2 flags %x at %u: %d\n", min_alarm.flags,
(u32_t)min_alarm.time, rc);
/* One-shot auto-disable callback in 5 s. The handler will
* then use the base device counter API to schedule a second
* alarm 10 s later.
*/
sec_alarm.time = sp.rtc.tv_sec + 5;
sec_alarm.flags = MAXIM_DS3231_ALARM_FLAGS_AUTODISABLE
| MAXIM_DS3231_ALARM_FLAGS_DOW;
sec_alarm.handler = sec_alarm_handler;
sec_alarm.user_data = &sec_alarm;
printk("Min Sec base time: %s\n", format_time(sec_alarm.time, -1));
/* Repeating callback at rollover to a new minute. */
min_alarm.time = sec_alarm.time;
min_alarm.flags = 0
| MAXIM_DS3231_ALARM_FLAGS_IGNDA
| MAXIM_DS3231_ALARM_FLAGS_IGNHR
| MAXIM_DS3231_ALARM_FLAGS_IGNMN
| MAXIM_DS3231_ALARM_FLAGS_IGNSE;
min_alarm.handler = min_alarm_handler;
rc = maxim_ds3231_set_alarm(ds3231, 0, &sec_alarm);
printk("Set sec alarm %x at %u ~ %s: %d\n", sec_alarm.flags,
(u32_t)sec_alarm.time, format_time(sec_alarm.time, -1), rc);
rc = maxim_ds3231_set_alarm(ds3231, 1, &min_alarm);
printk("Set min alarm flags %x at %u ~ %s: %d\n", min_alarm.flags,
(u32_t)min_alarm.time, format_time(min_alarm.time, -1), rc);
printk("%u ms in: get alarms: %d %d\n", k_uptime_get_32(),
maxim_ds3231_get_alarm(ds3231, 0, &sec_alarm),
maxim_ds3231_get_alarm(ds3231, 1, &min_alarm));
if (rc >= 0) {
printk("Sec alarm flags %x at %u ~ %s\n", sec_alarm.flags,
(u32_t)sec_alarm.time, format_time(sec_alarm.time, -1));
printk("Min alarm flags %x at %u ~ %s\n", min_alarm.flags,
(u32_t)min_alarm.time, format_time(min_alarm.time, -1));
}
k_sleep(K_FOREVER);
}

View file

@ -12,3 +12,4 @@ by Zephyr.
*/* */*
counter/alarm/README.rst counter/alarm/README.rst
counter/maxim_ds3231/README.rst