diff --git a/core/embed/projects/prodtest/README.md b/core/embed/projects/prodtest/README.md index 9d0416a11d..6ab1e787ca 100644 --- a/core/embed/projects/prodtest/README.md +++ b/core/embed/projects/prodtest/README.md @@ -1066,7 +1066,30 @@ rtc-timestamp OK 1886533 ``` - +### rtc-set +Sets the current RTC date and time. + +`rtc-set ` + +Example: +``` +rtc-set 2025 07 03 14 23 00 +OK +``` + +### rtc-get +Reads the current RTC date and time. + +Response format: +`OK `, where `day-of-week` is a number from 1 (Monday) to 7 (Sunday). + +Where: + +Example: +``` +rtc-get +OK 2025 07 03 14 23 00 4 +``` diff --git a/core/embed/projects/prodtest/cmd/prodtest_rtc.c b/core/embed/projects/prodtest/cmd/prodtest_rtc.c index bd1bac5ad5..41d081897b 100644 --- a/core/embed/projects/prodtest/cmd/prodtest_rtc.c +++ b/core/embed/projects/prodtest/cmd/prodtest_rtc.c @@ -38,6 +38,47 @@ static void prodtest_rtc_timestamp(cli_t* cli) { cli_ok(cli, "%u", timestamp); } +static void prodtest_rtc_set(cli_t* cli) { + if (cli_arg_count(cli) != 6) { + cli_error_arg_count(cli); + return; + } + + uint32_t year, month, day, hour, minute, second; + if (!cli_arg_uint32(cli, "year", &year) || + !cli_arg_uint32(cli, "month", &month) || + !cli_arg_uint32(cli, "day", &day) || + !cli_arg_uint32(cli, "hour", &hour) || + !cli_arg_uint32(cli, "minute", &minute) || + !cli_arg_uint32(cli, "second", &second)) { + cli_error_arg(cli, "Invalid date/time values"); + return; + } + + if (!rtc_set(year, month, day, hour, minute, second)) { + cli_error(cli, CLI_ERROR, "Failed to set RTC time"); + return; + } + cli_ok(cli, ""); +} + +static void prodtest_rtc_get(cli_t* cli) { + if (cli_arg_count(cli) > 0) { + cli_error_arg_count(cli); + return; + } + + rtc_datetime_t datetime; + if (!rtc_get(&datetime)) { + cli_error(cli, CLI_ERROR, "Failed to get RTC time"); + return; + } + + cli_ok(cli, "%04u %02u %02u %02u %02u %02u %02u", datetime.year, + datetime.month, datetime.day, datetime.hour, datetime.minute, + datetime.second, datetime.weekday); +} + // clang-format off PRODTEST_CLI_CMD( .name = "rtc-timestamp", @@ -46,4 +87,20 @@ PRODTEST_CLI_CMD( .args = "" ); +PRODTEST_CLI_CMD( + .name = "rtc-set", + .func = prodtest_rtc_set, + .info = "Set RTC date/time", + .args = " ", +); + + +PRODTEST_CLI_CMD( + .name = "rtc-get", + .func = prodtest_rtc_get, + .info = "Get RTC date/time", + .args = "", +); + + #endif diff --git a/core/embed/sys/time/inc/sys/rtc.h b/core/embed/sys/time/inc/sys/rtc.h index eb5876a6da..07d48bd116 100644 --- a/core/embed/sys/time/inc/sys/rtc.h +++ b/core/embed/sys/time/inc/sys/rtc.h @@ -19,6 +19,18 @@ #pragma once +#include + +typedef struct { + uint16_t year; /**< Full year (e.g., 2025) */ + uint8_t month; /**< Month (1–12) */ + uint8_t day; /**< Day of the month (1–31) */ + uint8_t hour; /**< Hour (0–23) */ + uint8_t minute; /**< Minute (0–59) */ + uint8_t second; /**< Second (0–59) */ + uint8_t weekday; /**< Weekday (1=Monday to 7=Sunday) */ +} rtc_datetime_t; + /** * @brief Initialize the RTC driver * @@ -67,3 +79,32 @@ bool rtc_wakeup_timer_start(uint32_t seconds, rtc_wakeup_callback_t callback, * @brief Stop the RTC wakeup timer */ void rtc_wakeup_timer_stop(void); + +/** + * @brief Set the RTC using discrete time values + * + * Sets the RTC date and time using individual components: year, month, day, + * hour, minute, and second. The weekday is automatically calculated based on + * the given date. + * + * @param year Full year (e.g., 2025). Must be between 2000 and 2099. + * @param month Month (1–12). + * @param day Day of the month (1–31). + * @param hour Hour (0–23). + * @param minute Minute (0–59). + * @param second Second (0–59). + * @return true if the time was successfully set, false otherwise + */ +bool rtc_set(uint16_t year, uint8_t month, uint8_t day, uint8_t hour, + uint8_t minute, uint8_t second); + +/** + * @brief Get the current RTC time as a structured date and time + * + * Reads the RTC date and time registers and returns the result in a + * structured format. + * + * @param datetime Pointer to an rtc_datetime_t struct to hold the result. + * @return true if the time was successfully retrieved, false otherwise + */ +bool rtc_get(rtc_datetime_t* datetime); diff --git a/core/embed/sys/time/stm32u5/rtc.c b/core/embed/sys/time/stm32u5/rtc.c index c48a7200a9..3c578431b2 100644 --- a/core/embed/sys/time/stm32u5/rtc.c +++ b/core/embed/sys/time/stm32u5/rtc.c @@ -87,7 +87,7 @@ bool rtc_get_timestamp(uint32_t* timestamp) { // Get current time and date, // Important: GetTime has to be called before the GetDate in order to unlock - // the values in higher-order callendar. + // the values in higher-order calendar. if (HAL_OK != HAL_RTC_GetTime(&drv->hrtc, &time, RTC_FORMAT_BCD)) { return false; } @@ -221,4 +221,82 @@ static uint32_t rtc_calendar_to_timestamp(const RTC_DateTypeDef* date, return seconds + 946684800; } +bool rtc_set(uint16_t year, uint8_t month, uint8_t day, uint8_t hour, + uint8_t minute, uint8_t second) { + rtc_driver_t* drv = &g_rtc_driver; + if (!drv->initialized) { + return false; + } + + // Validate inputs + if (year < 2000 || year > 2099 || month < 1 || month > 12 || day < 1 || + day > 31 || hour > 23 || minute > 59 || second > 59) { + return false; + } + + // --- Weekday calculation using Zeller's Congruence --- + // Adjust month/year for Zeller's algorithm + int y = (month <= 2) ? year - 1 : year; + int m = (month <= 2) ? month + 12 : month; + int d = day; + + int K = y % 100; + int J = y / 100; + int h = (d + 13 * (m + 1) / 5 + K + K / 4 + J / 4 + 5 * J) % 7; + + // Convert Zeller's output to RTC weekday (1 = Monday, ..., 7 = Sunday) + // Zeller: 0 = Saturday, 1 = Sunday, 2 = Monday, ..., 6 = Friday + uint8_t weekday = ((h + 5) % 7) + 1; + + RTC_TimeTypeDef time = {.Hours = hour, + .Minutes = minute, + .Seconds = second, + .TimeFormat = RTC_HOURFORMAT_24, + .DayLightSaving = RTC_DAYLIGHTSAVING_NONE, + .StoreOperation = RTC_STOREOPERATION_RESET}; + + if (HAL_OK != HAL_RTC_SetTime(&drv->hrtc, &time, RTC_FORMAT_BIN)) { + return false; + } + + RTC_DateTypeDef date = { + .Year = year - 2000, .Month = month, .Date = day, .WeekDay = weekday}; + + if (HAL_OK != HAL_RTC_SetDate(&drv->hrtc, &date, RTC_FORMAT_BIN)) { + return false; + } + + return true; +} + +bool rtc_get(rtc_datetime_t* datetime) { + rtc_driver_t* drv = &g_rtc_driver; + + if (!drv->initialized || datetime == NULL) { + return false; + } + + RTC_DateTypeDef date; + RTC_TimeTypeDef time; + + // Get current time before date (important for consistency) + if (HAL_OK != HAL_RTC_GetTime(&drv->hrtc, &time, RTC_FORMAT_BIN)) { + return false; + } + + if (HAL_OK != HAL_RTC_GetDate(&drv->hrtc, &date, RTC_FORMAT_BIN)) { + return false; + } + + datetime->year = 2000 + date.Year; + datetime->month = date.Month; + datetime->day = date.Date; + datetime->hour = time.Hours; + datetime->minute = time.Minutes; + datetime->second = time.Seconds; + datetime->weekday = date.WeekDay; + + return true; +} + #endif // KERNEL_MODE