|
| 1 | +// This file is part of the CircuitPython project: https://circuitpython.org |
| 2 | +// |
| 3 | +// SPDX-FileCopyrightText: Copyright (c) 2026 Scott Shawcroft for Adafruit Industries |
| 4 | +// |
| 5 | +// SPDX-License-Identifier: MIT |
| 6 | + |
| 7 | +#include <errno.h> |
| 8 | + |
| 9 | +#include "py/obj.h" |
| 10 | +#include "py/runtime.h" |
| 11 | +#include "shared/timeutils/timeutils.h" |
| 12 | +#include "shared-bindings/rtc/RTC.h" |
| 13 | +#include "common-hal/rtc/RTC.h" |
| 14 | + |
| 15 | +#include "bindings/zephyr_kernel/__init__.h" |
| 16 | + |
| 17 | +#include <zephyr/devicetree.h> |
| 18 | +#include <zephyr/device.h> |
| 19 | +#include <zephyr/drivers/rtc.h> |
| 20 | + |
| 21 | +// The RTC driver already initializes its device at Zephyr boot time, so there |
| 22 | +// is nothing for the port to start up: time is simply read from and written |
| 23 | +// to the devicetree `rtc` device. |
| 24 | +static const struct device *const rtc_dev = DEVICE_DT_GET(DT_ALIAS(rtc)); |
| 25 | + |
| 26 | +// Zephyr stores calibration in parts per billion. CircuitPython's calibration |
| 27 | +// unit is approximately one part per million. |
| 28 | +#define CALIBRATION_PPM_TO_ZEPHYR (1000) |
| 29 | + |
| 30 | +void common_hal_rtc_get_time(timeutils_struct_time_t *tm) { |
| 31 | + struct rtc_time zephyr_tm; |
| 32 | + int ret = rtc_get_time(rtc_dev, &zephyr_tm); |
| 33 | + if (ret == -ENODATA) { |
| 34 | + // The RTC has never been set (no battery-backed hardware and no |
| 35 | + // seeded time). Report the start of the CircuitPython epoch, |
| 36 | + // 2000-01-01, instead of failing; users set it with |
| 37 | + // rtc.RTC().datetime. |
| 38 | + timeutils_seconds_since_2000_to_struct_time(0, tm); |
| 39 | + return; |
| 40 | + } |
| 41 | + CHECK_ZEPHYR_RESULT(ret); |
| 42 | + |
| 43 | + tm->tm_year = zephyr_tm.tm_year + 1900; |
| 44 | + tm->tm_mon = zephyr_tm.tm_mon + 1; |
| 45 | + tm->tm_mday = zephyr_tm.tm_mday; |
| 46 | + tm->tm_hour = zephyr_tm.tm_hour; |
| 47 | + tm->tm_min = zephyr_tm.tm_min; |
| 48 | + tm->tm_sec = zephyr_tm.tm_sec; |
| 49 | + // tm_wday and tm_yday are filled in by struct_time_from_tm. |
| 50 | +} |
| 51 | + |
| 52 | +void common_hal_rtc_set_time(timeutils_struct_time_t *tm) { |
| 53 | + struct rtc_time zephyr_tm = { |
| 54 | + .tm_year = tm->tm_year - 1900, |
| 55 | + .tm_mon = tm->tm_mon - 1, |
| 56 | + .tm_mday = tm->tm_mday, |
| 57 | + .tm_hour = tm->tm_hour, |
| 58 | + .tm_min = tm->tm_min, |
| 59 | + .tm_sec = tm->tm_sec, |
| 60 | + }; |
| 61 | + CHECK_ZEPHYR_RESULT(rtc_set_time(rtc_dev, &zephyr_tm)); |
| 62 | +} |
| 63 | + |
| 64 | +int common_hal_rtc_get_calibration(void) { |
| 65 | + #ifdef CONFIG_RTC_CALIBRATION |
| 66 | + int32_t zephyr_calibration; |
| 67 | + int ret = rtc_get_calibration(rtc_dev, &zephyr_calibration); |
| 68 | + if (ret == -ENOSYS || ret == -ENOTSUP) { |
| 69 | + mp_raise_NotImplementedError_varg(MP_ERROR_TEXT("%q"), MP_QSTR_calibration); |
| 70 | + } |
| 71 | + CHECK_ZEPHYR_RESULT(ret); |
| 72 | + return zephyr_calibration / CALIBRATION_PPM_TO_ZEPHYR; |
| 73 | + #else |
| 74 | + // The Zephyr RTC calibration syscall only exists with |
| 75 | + // CONFIG_RTC_CALIBRATION, which is off unless a driver needs it. |
| 76 | + mp_raise_NotImplementedError_varg(MP_ERROR_TEXT("%q"), MP_QSTR_calibration); |
| 77 | + #endif |
| 78 | +} |
| 79 | + |
| 80 | +void common_hal_rtc_set_calibration(int calibration) { |
| 81 | + #ifdef CONFIG_RTC_CALIBRATION |
| 82 | + int ret = rtc_set_calibration(rtc_dev, (int32_t)calibration * CALIBRATION_PPM_TO_ZEPHYR); |
| 83 | + if (ret == -ENOSYS || ret == -ENOTSUP) { |
| 84 | + mp_raise_NotImplementedError_varg(MP_ERROR_TEXT("%q"), MP_QSTR_calibration); |
| 85 | + } |
| 86 | + CHECK_ZEPHYR_RESULT(ret); |
| 87 | + #else |
| 88 | + mp_raise_NotImplementedError_varg(MP_ERROR_TEXT("%q"), MP_QSTR_calibration); |
| 89 | + #endif |
| 90 | +} |
0 commit comments