diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 96633aaa..0a4a6de4 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -47,13 +47,18 @@ include_directories(src/applications) include_directories(src/ui/watchfaces) if (CONFIG_DT_HAS_NORDIC_NPM1300_ENABLED) -add_subdirectory(src/fuel_gauge) + add_subdirectory(src/fuel_gauge) else() -add_subdirectory(src/basic_battery) + add_subdirectory(src/basic_battery) +endif() + +if (CONFIG_RTC) + target_sources(app PRIVATE src/zsw_rtc.c) +else() + target_sources(app PRIVATE src/zsw_clock.c) endif() target_sources(app PRIVATE src/main.c) -target_sources(app PRIVATE src/zsw_clock.c) target_sources(app PRIVATE src/zsw_cpu_freq.c) target_sources(app PRIVATE src/zsw_retained_ram_storage.c) target_sources(app PRIVATE src/zsw_coredump.c) diff --git a/app/boards/arm/zswatch_nrf5340/zswatch_nrf5340_cpuapp_3.overlay b/app/boards/arm/zswatch_nrf5340/zswatch_nrf5340_cpuapp_3.overlay index 4273d1fb..4266ef71 100644 --- a/app/boards/arm/zswatch_nrf5340/zswatch_nrf5340_cpuapp_3.overlay +++ b/app/boards/arm/zswatch_nrf5340/zswatch_nrf5340_cpuapp_3.overlay @@ -83,6 +83,10 @@ }; }; +&uart0 { + status = "disabled"; +}; + &i2c1 { bmi270: bmi270@68 { compatible = "bosch,bmi270-plus"; diff --git a/app/boards/arm/zswatch_nrf5340/zswatch_nrf5340_cpuapp_4.overlay b/app/boards/arm/zswatch_nrf5340/zswatch_nrf5340_cpuapp_4.overlay index 4df49748..3402482f 100644 --- a/app/boards/arm/zswatch_nrf5340/zswatch_nrf5340_cpuapp_4.overlay +++ b/app/boards/arm/zswatch_nrf5340/zswatch_nrf5340_cpuapp_4.overlay @@ -127,6 +127,10 @@ }; +&uart0 { + status = "disabled"; +}; + &i2c1 { bmi270: bmi270@68 { compatible = "bosch,bmi270-plus"; diff --git a/app/boards/arm/zswatch_nrf5340/zswatch_nrf5340_cpuapp_5.conf b/app/boards/arm/zswatch_nrf5340/zswatch_nrf5340_cpuapp_5.conf index 97ef031a..b9a724ce 100644 --- a/app/boards/arm/zswatch_nrf5340/zswatch_nrf5340_cpuapp_5.conf +++ b/app/boards/arm/zswatch_nrf5340/zswatch_nrf5340_cpuapp_5.conf @@ -48,8 +48,4 @@ CONFIG_NVS=y CONFIG_DEBUG_COREDUMP_BACKEND_OTHER=y CONFIG_RTC=y -CONFIG_RTC_ALARM=y -CONFIG_RTC_RV8263_ALARM_OWN_THREAD=y CONFIG_RTC_UPDATE=y -CONFIG_RTC_RV8263_UPDATE_OWN_THREAD=y -CONFIG_RTC_CALIBRATION=y \ No newline at end of file diff --git a/app/boards/arm/zswatch_nrf5340/zswatch_nrf5340_cpuapp_5.overlay b/app/boards/arm/zswatch_nrf5340/zswatch_nrf5340_cpuapp_5.overlay index ad548e8f..18ba65e0 100644 --- a/app/boards/arm/zswatch_nrf5340/zswatch_nrf5340_cpuapp_5.overlay +++ b/app/boards/arm/zswatch_nrf5340/zswatch_nrf5340_cpuapp_5.overlay @@ -185,13 +185,16 @@ pwms = <&pwm2 0 PWM_HZ(880) PWM_POLARITY_NORMAL>; }; }; +}; +&uart0 { + status = "disabled"; }; &i2c1 { - rv_8263_c8: rv-8263-c8@A2 { + rv_8263_c8: rv-8263-c8@51 { compatible = "microcrystal,rv-8263-c8"; - reg = <0xA2>; + reg = <0x51>; status = "okay"; clkout = <0>; int-gpios = <&gpio1 13 (GPIO_PULL_DOWN | GPIO_ACTIVE_HIGH)>; @@ -367,4 +370,4 @@ reg = <0x400000 0x100000 >; }; }; -}; \ No newline at end of file +}; diff --git a/app/drivers/CMakeLists.txt b/app/drivers/CMakeLists.txt index 5c438f54..cd6d4278 100644 --- a/app/drivers/CMakeLists.txt +++ b/app/drivers/CMakeLists.txt @@ -1,5 +1,4 @@ add_subdirectory(display) add_subdirectory(input) -add_subdirectory(rtc) -add_subdirectory_ifdef(CONFIG_SENSOR sensor) \ No newline at end of file +add_subdirectory_ifdef(CONFIG_SENSOR sensor) diff --git a/app/drivers/Kconfig b/app/drivers/Kconfig index 774a916e..1c492e03 100644 --- a/app/drivers/Kconfig +++ b/app/drivers/Kconfig @@ -1,4 +1,3 @@ rsource "display/Kconfig" rsource "input/Kconfig" -rsource "rtc/Kconfig" -rsource "sensor/Kconfig" \ No newline at end of file +rsource "sensor/Kconfig" diff --git a/app/drivers/rtc/CMakeLists.txt b/app/drivers/rtc/CMakeLists.txt deleted file mode 100644 index 720d23ca..00000000 --- a/app/drivers/rtc/CMakeLists.txt +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright (c) 2024, Daniel Kampert -# -# SPDX-License-Identifier: Apache-2.0 -# - -add_subdirectory_ifdef(CONFIG_RTC_RV8263 rv8263c8) \ No newline at end of file diff --git a/app/drivers/rtc/Kconfig b/app/drivers/rtc/Kconfig deleted file mode 100644 index 903dd8f1..00000000 --- a/app/drivers/rtc/Kconfig +++ /dev/null @@ -1,3 +0,0 @@ -if RTC - rsource "rv8263c8/Kconfig" -endif \ No newline at end of file diff --git a/app/drivers/rtc/rv8263c8/CMakeLists.txt b/app/drivers/rtc/rv8263c8/CMakeLists.txt deleted file mode 100644 index 6c34f57d..00000000 --- a/app/drivers/rtc/rv8263c8/CMakeLists.txt +++ /dev/null @@ -1,5 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# Copyright (c) 2024 Daniel Kampert -# Author: Daniel Kampert - -zephyr_sources(microcrystal_rv8263c8.c) \ No newline at end of file diff --git a/app/drivers/rtc/rv8263c8/Kconfig b/app/drivers/rtc/rv8263c8/Kconfig deleted file mode 100644 index 53cfe77f..00000000 --- a/app/drivers/rtc/rv8263c8/Kconfig +++ /dev/null @@ -1,83 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# Copyright (c) 2024 Daniel Kampert -# Author: Daniel Kampert - -config RTC_RV8263 - bool "Micro Crystal RV-8263-C8 RTC driver" - default y - depends on DT_HAS_MICROCRYSTAL_RV_8263_C8_ENABLED - select I2C - help - Micro Crystal RV-8263-C8 RTC driver. - -if RTC_RV8263 - menu "RV8263 Update" - depends on RTC_UPDATE - - choice - prompt "RV-8263-C8 update handling mode" - default RTC_RV8263_UPDATE_GLOBAL_THREAD - - config RTC_RV8263_UPDATE_GLOBAL_THREAD - depends on GPIO - bool "Use workqueue (Default)" - help - Use the global workqueue to process the updates from the RV-8263-C8. - - config RTC_RV8263_UPDATE_OWN_THREAD - depends on GPIO - bool "Use driver thread" - help - Use a separate thread to process the updates from the RV-8263-C8. - endchoice - - config RTC_RV8263_UPDATE_THREAD_STACK_SIZE - int "Stack size for the RV-8263-C8 update thread" - depends on RTC_RV8263_UPDATE_OWN_THREAD - default 512 - help - Size of the stack used for the thread handling RTC updates and dispatching callbacks. - - config RTC_RV8263_UPDATE_THREAD_PRIORITY - int "RV-8263-C8 update thread priority" - depends on RTC_RV8263_UPDATE_OWN_THREAD - default 0 - help - Priority level for the thread handling RTC updates and dispatching callbacks. - endmenu - - menu "RV8263 Alarm" - depends on RTC_ALARM - - choice - prompt "RV-8263-C8 alarm handling mode" - default RTC_RV8263_ALARM_GLOBAL_THREAD - - config RTC_RV8263_ALARM_GLOBAL_THREAD - depends on GPIO - bool "Use workqueue (Default)" - help - Use the global workqueue to process the interrupts from the RV-8263-C8. - - config RTC_RV8263_ALARM_OWN_THREAD - depends on GPIO - bool "Use driver thread" - help - Use a separate thread to process the alarms from the RV-8263-C8. - endchoice - - config RTC_RV8263_ALARM_THREAD_STACK_SIZE - int "Stack size for the RV-8263-C8 interrupt thread" - depends on RTC_RV8263_ALARM_OWN_THREAD - default 512 - help - Size of the stack used for the thread handling alarms and dispatching callbacks. - - config RTC_RV8263_ALARM_THREAD_PRIORITY - int "RV-8263-C8 alarm thread priority" - depends on RTC_RV8263_ALARM_OWN_THREAD - default 0 - help - Priority level for the thread handling alarms and dispatching callbacks. - endmenu -endif \ No newline at end of file diff --git a/app/drivers/rtc/rv8263c8/microcrystal_rv8263c8.c b/app/drivers/rtc/rv8263c8/microcrystal_rv8263c8.c deleted file mode 100644 index 476422a7..00000000 --- a/app/drivers/rtc/rv8263c8/microcrystal_rv8263c8.c +++ /dev/null @@ -1,945 +0,0 @@ -/* microcrystal_rv8263c8.c - Driver for Micro Crystal RV-8263-C8 RTC. - * - * Copyright (c) 2024 Daniel Kampert - * Author: Daniel Kampert - */ - -#include -#include -#include -#include -#include -#include -#include - -#define RV8263C8_REGISTER_CONTROL_1 0x00 -#define RV8263C8_REGISTER_CONTROL_2 0x01 -#define RV8263C8_REGISTER_OFFSET 0x02 -#define RV8263C8_REGISTER_RAM 0x03 -#define RV8263C8_REGISTER_SECONDS 0x04 -#define RV8263C8_REGISTER_MINUTES 0x05 -#define RV8263C8_REGISTER_HOURS 0x06 -#define RV8263C8_REGISTER_DATE 0x07 -#define RV8263C8_REGISTER_WEEKDAY 0x08 -#define RV8263C8_REGISTER_MONTH 0x09 -#define RV8263C8_REGISTER_YEAR 0x0A -#define RV8263C8_REGISTER_SECONDS_ALARM 0x0B -#define RV8263C8_REGISTER_MINUTES_ALARM 0x0C -#define RV8263C8_REGISTER_HOURS_ALARM 0x0D -#define RV8263C8_REGISTER_DATE_ALARM 0x0E -#define RV8263C8_REGISTER_WEEKDAY_ALARM 0x0F -#define RV8263C8_REGISTER_TIMER_VALUE 0x10 -#define RV8263C8_REGISTER_TIMER_MODE 0x11 - -#define RV8263C8_BM_24H_MODE_ENABLE (0x00 << 1) -#define RV8263C8_BM_24H_MODE_DISABLE (0x00 << 1) -#define RV8263C8_BM_CLOCK_ENABLE (0x00 << 5) -#define RV8263C8_BM_CLOCK_DISABLE (0x01 << 5) -#define RV8263C8_BM_ALARM_INT_ENABLE (0x01 << 7) -#define RV8263C8_BM_ALARM_INT_DISABLE (0x00 << 7) -#define RV8263C8_BM_MINUTE_INT_ENABLE (0x01 << 5) -#define RV8263C8_BM_MINUTE_INT_DISABLE (0x00 << 5) -#define RV8263C8_BM_HALF_MINUTE_INT_ENABLE (0x01 << 4) -#define RV8263C8_BM_HALF_MINUTE_INT_DISABLE (0x00 << 4) -#define RV8263C8_BM_ALARM_ENABLE (0x00 << 7) -#define RV8263C8_BM_ALARM_DISABLE (0x01 << 7) -#define RV8263C8_BM_AF (0x01 << 6) -#define RV8263C8_BM_TF (0x01 << 3) -#define RV8263_BM_MODE (0x01 << 7) -#define RV8263C8_BM_SOFTWARE_RESET (0x58) - -#define SECONDS_BITS GENMASK(6, 0) -#define MINUTES_BITS GENMASK(7, 0) -#define HOURS_BITS GENMASK(5, 0) -#define DATE_BITS GENMASK(5, 0) -#define MONTHS_BITS GENMASK(4, 0) -#define WEEKDAY_BITS GENMASK(2, 0) -#define YEAR_BITS GENMASK(7, 0) -#define VALIDATE_24HR BIT(6) - -#define MIN_SEC 0 -#define MAX_SEC 59 -#define MIN_MIN 0 -#define MAX_MIN 59 -#define MIN_HOUR 0 -#define MAX_HOUR 23 -#define MAX_WDAY 7 -#define MIN_WDAY 1 -#define MAX_MDAY 31 -#define MIN_MDAY 1 -#define MAX_MON 12 -#define MIN_MON 1 -#define MIN_YEAR_DIFF 0 // YEAR - 1900 */ -#define MAX_YEAR_DIFF 99 // YEAR - 1999 */ - -#define DT_DRV_COMPAT microcrystal_rv_8263_c8 - -#if defined(CONFIG_RTC_ALARM) || defined(CONFIG_RTC_UPDATE) -#define RV8263_USE_INT 1 -/** - * @brief RV-8263-C8 "int_gpios" property must be in the devicetree in order to use the RTC_ALARM or RTC_UPDATE feature. - */ -#if !DT_ANY_INST_HAS_PROP_STATUS_OKAY(int_gpios) -#error int-gpios property not available in devicetree! -#endif -#endif - -#ifdef CONFIG_RTC_CALIBRATION -/** - * @brief RV-8263-C8 "offset" property must be in the devicetree in order to use the RTC_CALIBRATION feature. - */ -#if !DT_ANY_INST_HAS_PROP_STATUS_OKAY(offset) -#error offset-property not available in devicetree! -#endif -#endif - -#ifdef CONFIG_RTC_CALIBRATION -/** - * @brief RV-8263-C8 "fast_mode" property must be in the devicetree in order to use the RTC_CALIBRATION feature. - */ -#if !DT_ANY_INST_HAS_PROP_STATUS_OKAY(fast_mode) -#error fast-mode-property not available in devicetree! -#endif -#endif - -LOG_MODULE_REGISTER(microcrystal_rv8263c8, CONFIG_RTC_LOG_LEVEL); - -struct rv8263c8_config { - struct i2c_dt_spec i2c_bus; - uint8_t clkout; - bool fast_mode; - int8_t offset; - -#if defined(CONFIG_RTC_ALARM) || defined(CONFIG_RTC_UPDATE) - struct gpio_dt_spec int_gpio; -#endif -}; - -struct rv8263c8_data { - struct k_spinlock lock; - -#if defined(CONFIG_RTC_RV8263_ALARM_GLOBAL_THREAD) || defined(CONFIG_RTC_RV8263_UPDATE_GLOBAL_THREAD) - const struct device *dev; -#endif - -#if defined(CONFIG_RTC_ALARM) || defined(CONFIG_RTC_UPDATE) - struct gpio_callback gpio_cb; -#endif - -#ifdef CONFIG_RTC_ALARM - rtc_alarm_callback alarm_cb; - void *alarm_cb_data; - bool is_alarm_pending; - -#if defined(CONFIG_RTC_RV8263_ALARM_OWN_THREAD) - struct k_sem alarm_sem; -#elif defined(CONFIG_RTC_RV8263_ALARM_GLOBAL_THREAD) - struct k_work alarm_work; -#endif -#endif - -#ifdef CONFIG_RTC_UPDATE - rtc_update_callback update_cb; - void *update_cb_data; - -#if defined(CONFIG_RTC_RV8263_UPDATE_OWN_THREAD) - struct k_sem update_sem; -#elif defined(CONFIG_RTC_RV8263_UPDATE_GLOBAL_THREAD) - struct k_work update_work; -#endif -#endif -}; - -#ifdef CONFIG_RTC_RV8263_ALARM_OWN_THREAD -static K_KERNEL_STACK_MEMBER(rv8263c8_alarm_stack, CONFIG_RTC_RV8263_ALARM_THREAD_STACK_SIZE); -static struct k_thread rv8263c8_alarm_thread; -#endif - -#ifdef CONFIG_RTC_RV8263_UPDATE_OWN_THREAD -static K_KERNEL_STACK_MEMBER(rv8263c8_update_stack, CONFIG_RTC_RV8263_UPDATE_THREAD_STACK_SIZE); -static struct k_thread rv8263c8_update_thread; -#endif - -#if defined(CONFIG_RTC_ALARM) || defined(CONFIG_RTC_UPDATE) -/** - * @brief - * - * @param p_port - * @param p_cb - * @param pins - */ -static void rv8263c8_gpio_callback_handler(const struct device *p_port, struct gpio_callback *p_cb, - gpio_port_pins_t pins) -{ - ARG_UNUSED(pins); - ARG_UNUSED(p_port); - - struct rv8263c8_data *data = CONTAINER_OF(p_cb, struct rv8263c8_data, gpio_cb); - -#if defined(CONFIG_RTC_RV8263_ALARM_OWN_THREAD) - k_sem_give(&data->alarm_sem); -#elif defined(CONFIG_RTC_RV8263_ALARM_GLOBAL_THREAD) - k_work_submit(&data->alarm_work); -#endif - -#if defined(CONFIG_RTC_RV8263_UPDATE_OWN_THREAD) - k_sem_give(&data->update_sem); -#elif defined(CONFIG_RTC_RV8263_UPDATE_GLOBAL_THREAD) - k_work_submit(&data->update_work); -#endif -} -#endif - -#ifdef CONFIG_RTC_ALARM -/** - * @brief - * - * @param p_dev - */ -static void rv8263_process_alarm(const struct device *p_dev) -{ - struct rv8263c8_data *data = p_dev->data; - const struct rv8263c8_config *config = p_dev->config; - - data->is_alarm_pending = true; - - if (data->alarm_cb != NULL) { - LOG_DBG("Calling alarm callback"); - data->alarm_cb(p_dev, 0, data->alarm_cb_data); - } - - i2c_reg_update_byte_dt(&config->i2c_bus, RV8263C8_REGISTER_CONTROL_2, RV8263C8_BM_AF, RV8263C8_BM_AF); -} - -#if defined(CONFIG_RTC_RV8263_ALARM_OWN_THREAD) -/** - * @brief - * - * @param p_arg1 - * @param p_arg2 - * @param p_arg3 - */ -static void rv8263c8_alarm_thread_func(void *p_arg1, void *p_arg2, void *p_arg3) -{ - const struct device *dev = p_arg1; - struct rv8263c8_data *data = dev->data; - - while (1) { - k_sem_take(&data->alarm_sem, K_FOREVER); - rv8263_process_alarm(dev); - } -} -#elif defined(CONFIG_RTC_RV8263_UPDATE_GLOBAL_THREAD) -/** - * @brief - * - * @param p_work - */ -static void rv8263c8_alarm_worker(struct k_work *p_work) -{ - struct rv8263c8_data *data = CONTAINER_OF(p_work, struct rv8263c8_data, alarm_work); - - LOG_DBG("Process alarm interrupt from worker"); - - rv8263_process_alarm(data->dev); -} -#endif -#endif - -/** - * @brief - * - * @param p_timeptr - * @param mask - * @return true - * @return false - */ -static bool rv8263c8_validate_alarm(const struct rtc_time *p_timeptr, uint32_t mask) -{ - if ((mask & RTC_ALARM_TIME_MASK_SECOND) && - (p_timeptr->tm_sec < MIN_SEC || p_timeptr->tm_sec > MAX_SEC)) { - return false; - } - - if ((mask & RTC_ALARM_TIME_MASK_MINUTE) && - (p_timeptr->tm_min < MIN_MIN || p_timeptr->tm_min > MAX_MIN)) { - return false; - } - - if ((mask & RTC_ALARM_TIME_MASK_HOUR) && - (p_timeptr->tm_hour < MIN_HOUR || p_timeptr->tm_hour > MAX_HOUR)) { - return false; - } - - return true; -} -#endif - -#ifdef CONFIG_RTC_UPDATE -/** - * @brief This function configures and enables the countdown timer. - * - * @param p_dev Pointer to device strucure - * @return int 0 when successful - */ -static int rv8263c8_update_enable_timer(const struct device *p_dev) -{ - int err; - uint8_t regs; - struct rv8263c8_data *data = p_dev->data; - const struct rv8263c8_config *config = p_dev->config; - - if (p_dev == NULL) { - return -EINVAL; - } - - regs = 0; - k_spinlock_key_t key = k_spin_lock(&data->lock); - err = i2c_burst_write_dt(&config->i2c_bus, RV8263C8_REGISTER_TIMER_VALUE, ®s, sizeof(regs)); - if (err < 0) { - goto rv8263c8_update_enable_timer_exit; - } - - // Configure the countdown timer for 1 Hz and enable pulse interrupts. - regs = (0x02 << 3) | (0x01 << 2) | (0x01 << 1) | (0x01 << 0); - err = i2c_burst_write_dt(&config->i2c_bus, RV8263C8_REGISTER_TIMER_MODE, ®s, sizeof(regs)); - -rv8263c8_update_enable_timer_exit: - k_spin_unlock(&data->lock, key); - - return err; -} - -/** - * @brief - * - * @param p_dev - */ -static void rv8263_process_update(const struct device *p_dev) -{ - struct rv8263c8_data *data = p_dev->data; - const struct rv8263c8_config *config = p_dev->config; - - if (data->update_cb != NULL) { - LOG_DBG("Calling update callback"); - data->update_cb(p_dev, data->update_cb_data); - } - - rv8263c8_update_enable_timer(p_dev); - i2c_reg_update_byte_dt(&config->i2c_bus, RV8263C8_REGISTER_CONTROL_2, RV8263C8_BM_TF, RV8263C8_BM_TF); -} - -#if defined(CONFIG_RTC_RV8263_UPDATE_OWN_THREAD) -/** - * @brief - * - * @param p_arg1 - * @param p_arg2 - * @param p_arg3 - */ -static void rv8263c8_update_thread_func(void *p_arg1, void *p_arg2, void *p_arg3) -{ - const struct device *dev = p_arg1; - struct rv8263c8_data *data = dev->data; - - while (1) { - k_sem_take(&data->update_sem, K_FOREVER); - rv8263_process_update(dev); - } -} -#elif defined(CONFIG_RTC_RV8263_UPDATE_GLOBAL_THREAD) -/** - * @brief - * - * @param p_work - */ -static void rv8263c8_update_worker(struct k_work *p_work) -{ - struct rv8263c8_data *data = CONTAINER_OF(p_work, struct rv8263c8_data, alarm_work); - - LOG_DBG("Process update interrupt from worker"); - - rv8263_process_update(data->dev); -} -#endif -#endif - -/** - * @brief Set time function for RTC API. - * - * @param p_dev Pointer to device strucure - * @param p_timeptr - * @return int 0 when successful - */ -static int rv8263c8_time_set(const struct device *p_dev, const struct rtc_time *p_timeptr) -{ - int err; - uint8_t regs[7]; - struct rv8263c8_data *data = p_dev->data; - const struct rv8263c8_config *config = p_dev->config; - - if ((p_dev == NULL) || (p_timeptr == NULL)) { - return -EINVAL; - } - - LOG_DBG("Set time: year = %u, mon = %u, mday = %u, wday = %u, hour = %u, min = %u, sec = %u", - p_timeptr->tm_year, p_timeptr->tm_mon, p_timeptr->tm_mday, p_timeptr->tm_wday, p_timeptr->tm_hour, p_timeptr->tm_min, - p_timeptr->tm_sec); - - regs[0] = bin2bcd(p_timeptr->tm_sec) & SECONDS_BITS; - regs[1] = bin2bcd(p_timeptr->tm_min); - regs[2] = bin2bcd(p_timeptr->tm_hour); - regs[3] = bin2bcd(p_timeptr->tm_wday); - regs[4] = bin2bcd(p_timeptr->tm_mday); - regs[5] = bin2bcd(p_timeptr->tm_mon); - regs[6] = bin2bcd((p_timeptr->tm_year % 100)); - - k_spinlock_key_t key = k_spin_lock(&data->lock); - err = i2c_burst_write_dt(&config->i2c_bus, RV8263C8_REGISTER_SECONDS, regs, sizeof(regs)); - k_spin_unlock(&data->lock, key); - - return err; -} - -/** - * @brief Get time function for RTC API. - * - * @param p_dev Pointer to device strucure - * @param p_timeptr - * @return int 0 when successful - */ -static int rv8263c8_time_get(const struct device *p_dev, struct rtc_time *p_timeptr) -{ - int err; - uint8_t regs[7]; - struct rv8263c8_data *data = p_dev->data; - const struct rv8263c8_config *config = p_dev->config; - - if ((p_dev == NULL) || (p_timeptr == NULL)) { - return -EINVAL; - } - - k_spinlock_key_t key = k_spin_lock(&data->lock); - err = i2c_burst_read_dt(&config->i2c_bus, RV8263C8_REGISTER_SECONDS, regs, sizeof(regs)); - k_spin_unlock(&data->lock, key); - if (err < 0) { - return err; - } - - p_timeptr->tm_sec = bcd2bin(regs[0] & SECONDS_BITS); - p_timeptr->tm_min = bcd2bin(regs[1] & MINUTES_BITS); - p_timeptr->tm_hour = bcd2bin(regs[2] & HOURS_BITS); - p_timeptr->tm_wday = bcd2bin(regs[3] & WEEKDAY_BITS); - p_timeptr->tm_mday = bcd2bin(regs[4] & DATE_BITS); - p_timeptr->tm_mon = bcd2bin(regs[5] & MONTHS_BITS); - p_timeptr->tm_year = bcd2bin(regs[6] & YEAR_BITS); - p_timeptr->tm_year = p_timeptr->tm_year + 100; - - // Unused - p_timeptr->tm_nsec = 0; - p_timeptr->tm_isdst = -1; - p_timeptr->tm_yday = -1; - - // Validate the chip in 24hr mode - if (regs[2] & VALIDATE_24HR) { - return -ENODATA; - } - - LOG_DBG("get time: year = %u, mon = %u, mday = %u, wday = %u, hour = %u, min = %u, sec = %u", - p_timeptr->tm_year, p_timeptr->tm_mon, p_timeptr->tm_mday, p_timeptr->tm_wday, p_timeptr->tm_hour, p_timeptr->tm_min, - p_timeptr->tm_sec); - - return 0; -} - -/** - * @brief Init function for RTC API. - * - * @param p_dev Pointer to device strucure - * @return int 0 when successful - */ -static int rv8263c8_init(const struct device *p_dev) -{ - int err; - int temp; - struct rv8263c8_data *data = p_dev->data; - const struct rv8263c8_config *config = p_dev->config; - - if (p_dev == NULL) { - return -EINVAL; - } - - if (!i2c_is_ready_dt(&config->i2c_bus)) { - LOG_ERR("I2C bus not ready!"); - return -ENODEV; - } - - LOG_DBG("Configure RV-8263-C8:"); - LOG_DBG(" ClkOut: %u", config->clkout); - LOG_DBG(" Fast Mode: %u", config->fast_mode); - LOG_DBG(" Offset: %i", config->offset); - - k_spinlock_key_t key = k_spin_lock(&data->lock); - - // Configure the first config register - err = i2c_reg_write_byte_dt(&config->i2c_bus, RV8263C8_REGISTER_CONTROL_1, - RV8263C8_BM_24H_MODE_DISABLE | RV8263C8_BM_CLOCK_ENABLE); - if (err < 0) { - LOG_ERR("Error while writing CONTROL_1! Error: %i", err); - goto rv8263c8_init_exit; - } - -#ifdef CONFIG_RTC_CALIBRATION - err = i2c_reg_update_byte_dt(&config->i2c_bus, RV8263C8_REGISTER_OFFSET, RV8263_BM_MODE, config->fast_mode << 7); - if (err < 0) { - LOG_ERR("Error while setting OFFSET mode! Error: %i", err); - goto rv8263c8_init_exit; - } -#endif - - // Configure the second config register - temp = RV8263C8_BM_MINUTE_INT_DISABLE | RV8263C8_BM_HALF_MINUTE_INT_DISABLE | (config->clkout << 0x00); - -#ifdef CONFIG_RTC_ALARM - temp |= RV8263C8_BM_ALARM_INT_ENABLE; -#endif - err = i2c_reg_write_byte_dt(&config->i2c_bus, RV8263C8_REGISTER_CONTROL_2, temp); - if (err < 0) { - LOG_ERR("Error while writing CONTROL_2! Error: %i", err); - goto rv8263c8_init_exit; - } - -#ifdef CONFIG_RTC_UPDATE - uint8_t regs = 0; - err = i2c_burst_write_dt(&config->i2c_bus, RV8263C8_REGISTER_TIMER_MODE, ®s, sizeof(regs)); - if (err < 0) { - LOG_ERR("Error while writing CONTROL2! Error: %i", err); - goto rv8263c8_init_exit; - } -#endif - -#if defined(CONFIG_RTC_ALARM) || defined(CONFIG_RTC_UPDATE) - if (!gpio_is_ready_dt(&config->int_gpio)) { - LOG_ERR("GPIO not ready!"); - goto rv8263c8_init_exit; - } - - err = gpio_pin_configure_dt(&config->int_gpio, GPIO_INPUT); - if (err < 0) { - LOG_ERR("Failed to configure GPIO! Error: %u", err); - goto rv8263c8_init_exit; - } - - err = gpio_pin_interrupt_configure_dt(&config->int_gpio, GPIO_INT_EDGE_TO_INACTIVE); - if (err < 0) { - LOG_ERR("Failed to configure interrupt! Error: %u", err); - goto rv8263c8_init_exit; - } - - gpio_init_callback(&data->gpio_cb, rv8263c8_gpio_callback_handler, BIT(config->int_gpio.pin)); - - err = gpio_add_callback_dt(&config->int_gpio, &data->gpio_cb); - if (err < 0) { - LOG_ERR("Failed to add GPIO callback! Error: %u", err); - goto rv8263c8_init_exit; - } -#endif - -#ifdef CONFIG_RTC_ALARM -#if defined(CONFIG_RTC_RV8263_ALARM_OWN_THREAD) - k_sem_init(&data->alarm_sem, 0, K_SEM_MAX_LIMIT); - k_thread_create(&rv8263c8_alarm_thread, rv8263c8_alarm_stack, - K_THREAD_STACK_SIZEOF(rv8263c8_alarm_stack), - rv8263c8_alarm_thread_func, (struct device *)p_dev, NULL, - NULL, CONFIG_RTC_RV8263_ALARM_THREAD_PRIORITY, 0, K_NO_WAIT); -#elif defined(CONFIG_RTC_RV8263_ALARM_GLOBAL_THREAD) - data->alarm_work.handler = rv8263c8_alarm_worker; -#endif -#endif - -#ifdef CONFIG_RTC_UPDATE -#if defined(CONFIG_RTC_RV8263_UPDATE_OWN_THREAD) - k_sem_init(&data->update_sem, 0, K_SEM_MAX_LIMIT); - k_thread_create(&rv8263c8_update_thread, rv8263c8_update_stack, - K_THREAD_STACK_SIZEOF(rv8263c8_update_stack), - rv8263c8_update_thread_func, (struct device *)p_dev, NULL, - NULL, CONFIG_RTC_RV8263_UPDATE_THREAD_PRIORITY, 0, K_NO_WAIT); -#elif defined(CONFIG_RTC_RV8263_UPDATE_GLOBAL_THREAD) - data->update_work.handler = rv8263c8_update_worker; -#endif -#endif - -#if defined(CONFIG_RTC_RV8263_ALARM_GLOBAL_THREAD) || defined(CONFIG_RTC_RV8263_UPDATE_GLOBAL_THREAD) - data->dev = p_dev; -#endif - -rv8263c8_init_exit: - k_spin_unlock(&data->lock, key); - return err; -} - -#ifdef CONFIG_RTC_ALARM -/** - * @brief - * - * @param p_dev Pointer to device strucure - * @param id - * @param p_mask - * @return int 0 when successful - */ -static int rv8263c8_alarm_get_supported_fields(const struct device *p_dev, uint16_t id, - uint16_t *p_mask) -{ - ARG_UNUSED(p_dev); - - if ((p_dev == NULL) || (id != 0)) { - return -EINVAL; - } - - (*p_mask) = (RTC_ALARM_TIME_MASK_SECOND | RTC_ALARM_TIME_MASK_MINUTE | RTC_ALARM_TIME_MASK_HOUR | - RTC_ALARM_TIME_MASK_MONTHDAY | RTC_ALARM_TIME_MASK_WEEKDAY); - - return 0; -} - -/** - * @brief - * - * @param p_dev Pointer to device strucure - * @param id - * @param mask - * @param p_timeptr - * @return int 0 when successful - */ -static int rv8263c8_alarm_set_time(const struct device *p_dev, uint16_t id, uint16_t mask, - const struct rtc_time *p_timeptr) -{ - int err; - struct rv8263c8_data *const data = p_dev->data; - const struct rv8263c8_config *config = p_dev->config; - - if ((p_dev == NULL) || (p_timeptr == NULL) || (id != 0) || ((mask > 0) && (p_timeptr == NULL))) { - return -EINVAL; - } - - k_spinlock_key_t key = k_spin_lock(&data->lock); - - // Check time valid - if (!rv8263c8_validate_alarm(p_timeptr, mask)) { - return -EINVAL; - } - - if (mask & RTC_ALARM_TIME_MASK_SECOND) { - err = i2c_reg_write_byte_dt(&config->i2c_bus, RV8263C8_REGISTER_SECONDS_ALARM, - RV8263C8_BM_ALARM_ENABLE | p_timeptr->tm_sec); - } else { - err = i2c_reg_write_byte_dt(&config->i2c_bus, RV8263C8_REGISTER_SECONDS_ALARM, RV8263C8_BM_ALARM_DISABLE); - } - - if (err < 0) { - LOG_ERR("Error while writing SECONDS alarm! Error: %i", err); - goto rv8263c8_alarm_set_time_exit; - } - - if (mask & RTC_ALARM_TIME_MASK_MINUTE) { - err = i2c_reg_write_byte_dt(&config->i2c_bus, RV8263C8_REGISTER_MINUTES_ALARM, - RV8263C8_BM_ALARM_ENABLE | p_timeptr->tm_min); - } else { - err = i2c_reg_write_byte_dt(&config->i2c_bus, RV8263C8_REGISTER_MINUTES_ALARM, RV8263C8_BM_ALARM_DISABLE); - } - - if (err < 0) { - LOG_ERR("Error while writing MINUTE alarm! Error: %i", err); - goto rv8263c8_alarm_set_time_exit; - } - - if (mask & RTC_ALARM_TIME_MASK_HOUR) { - err = i2c_reg_write_byte_dt(&config->i2c_bus, RV8263C8_REGISTER_HOURS_ALARM, - RV8263C8_BM_ALARM_ENABLE | p_timeptr->tm_hour); - } else { - err = i2c_reg_write_byte_dt(&config->i2c_bus, RV8263C8_REGISTER_HOURS_ALARM, RV8263C8_BM_ALARM_DISABLE); - } - - if (err < 0) { - LOG_ERR("Error while writing HOUR alarm! Error: %i", err); - goto rv8263c8_alarm_set_time_exit; - } - - if (mask & RTC_ALARM_TIME_MASK_MONTHDAY) { - err = i2c_reg_write_byte_dt(&config->i2c_bus, RV8263C8_REGISTER_DATE_ALARM, - RV8263C8_BM_ALARM_ENABLE | p_timeptr->tm_mday); - } else { - err = i2c_reg_write_byte_dt(&config->i2c_bus, RV8263C8_REGISTER_DATE_ALARM, RV8263C8_BM_ALARM_DISABLE); - } - - if (err < 0) { - LOG_ERR("Error while writing MONTHDAY alarm! Error: %i", err); - goto rv8263c8_alarm_set_time_exit; - } - - if (mask & RTC_ALARM_TIME_MASK_WEEKDAY) { - err = i2c_reg_write_byte_dt(&config->i2c_bus, RV8263C8_REGISTER_WEEKDAY_ALARM, - RV8263C8_BM_ALARM_ENABLE | p_timeptr->tm_wday); - } else { - err = i2c_reg_write_byte_dt(&config->i2c_bus, RV8263C8_REGISTER_WEEKDAY_ALARM, RV8263C8_BM_ALARM_DISABLE); - } - - if (err < 0) { - LOG_ERR("Error while writing WEEKDAY alarm! Error: %i", err); - goto rv8263c8_alarm_set_time_exit; - } - -rv8263c8_alarm_set_time_exit: - k_spin_unlock(&data->lock, key); - - return err; -} - -/** - * @brief - * - * @param p_dev Pointer to device strucure - * @param id - * @param p_mask - * @param p_timeptr - * @return int 0 when successful - */ -static int rv8263c8_alarm_get_time(const struct device *p_dev, uint16_t id, uint16_t *p_mask, - struct rtc_time *p_timeptr) -{ - struct rv8263c8_data *const data = p_dev->data; - const struct rv8263c8_config *config = p_dev->config; - uint8_t value[5]; - - if ((p_dev == NULL) || (p_timeptr == NULL) || (id != 0)) { - return -EINVAL; - } - - (*p_mask) = 0; - - k_spinlock_key_t key = k_spin_lock(&data->lock); - err = i2c_burst_read_dt(&config->i2c_bus, RV8263C8_REGISTER_SECONDS_ALARM, value, sizeof(value)); - k_spin_unlock(&data->lock, key); - - if (err < 0) { - LOG_ERR("Error while reading alarm! Error: %i", err); - return err; - } - - if (value[0] <= MAX_SEC) { - p_timeptr->tm_sec = value[0]; - (*p_mask) |= RTC_ALARM_TIME_MASK_SECOND; - } - - if (value[1] <= MAX_MIN) { - p_timeptr->tm_min = value[1]; - (*p_mask) |= RTC_ALARM_TIME_MASK_MINUTE; - } - - if (value[2] <= MAX_HOUR) { - p_timeptr->tm_hour = value[2]; - (*p_mask) |= RTC_ALARM_TIME_MASK_HOUR; - } - - if (value[3] <= MAX_MDAY) { - p_timeptr->tm_mday = value[3]; - (*p_mask) |= RTC_ALARM_TIME_MASK_MONTHDAY; - } - - if (value[4] <= MAX_WDAY) { - p_timeptr->tm_hour = value[4]; - (*p_mask) |= RTC_ALARM_TIME_MASK_WEEKDAY; - } - - return 0; -} - -/** - * @brief - * - * @param p_dev Pointer to device strucure - * @param id - * @param callback RTC Alarm callback - * @param p_user_data Pointer to user data for alarm callback - * @return int 0 when successful - */ -static int rv8263c8_alarm_set_callback(const struct device *p_dev, uint16_t id, - rtc_alarm_callback callback, void *p_user_data) -{ - int err; - struct rv8263c8_data *const data = p_dev->data; - const struct rv8263c8_config *config = p_dev->config; - - if ((p_dev == NULL) || (id != 0)) { - return -EINVAL; - } - - k_spinlock_key_t key = k_spin_lock(&data->lock); - - data->alarm_cb = callback; - data->alarm_cb_data = p_user_data; - - if (callback != NULL) { - err = i2c_reg_update_byte_dt(&config->i2c_bus, RV8263C8_REGISTER_CONTROL_2, RV8263C8_BM_ALARM_INT_ENABLE, - RV8263C8_BM_ALARM_INT_ENABLE); - } else { - err = i2c_reg_update_byte_dt(&config->i2c_bus, RV8263C8_REGISTER_CONTROL_2, RV8263C8_BM_ALARM_INT_ENABLE, - RV8263C8_BM_ALARM_INT_DISABLE); - } - - k_spin_unlock(&data->lock, key); - - if (err < 0) { - LOG_ERR("Error while writing CONTROL2! Error: %i", err); - return err; - } - - return 0; -} - -/** - * @brief - * - * @param p_dev Pointer to device strucure - * @param id - * @return int 0 when successful - */ -static int rv8263c8_alarm_is_pending(const struct device *p_dev, uint16_t id) -{ - int ret; - struct rv8263c8_data *const data = p_dev->data; - - if ((p_dev == NULL) || (id != 0)) { - return -EINVAL; - } - - k_spinlock_key_t key = k_spin_lock(&data->lock); - ret = data->is_alarm_pending ? 1 : 0; - data->is_alarm_pending = false; - k_spin_unlock(&data->lock, key); - - return ret; -} -#endif - -#ifdef CONFIG_RTC_UPDATE -/** - * @brief - * - * @param p_dev Pointer to device strucure - * @param callback RTC update callback - * @param p_user_data Pointer to user data for update callback - * @return int 0 when successful - */ -int rv8263_update_callback(const struct device *p_dev, rtc_update_callback callback, void *p_user_data) -{ - struct rv8263c8_data *const data = p_dev->data; - - if (p_dev == NULL) { - return -EINVAL; - } - - k_spinlock_key_t key = k_spin_lock(&data->lock); - data->update_cb = callback; - data->update_cb_data = p_user_data; - k_spin_unlock(&data->lock, key); - - return rv8263c8_update_enable_timer(p_dev); -} -#endif - -#ifdef CONFIG_RTC_CALIBRATION -/** - * @brief - * - * @param p_dev Pointer to device strucure - * @param calibration - * @return int 0 when successful - */ -int rv8263c8_calibration_set(const struct device *p_dev, int32_t calibration) -{ - int err; - int8_t reg; - struct rv8263c8_data *const data = p_dev->data; - const struct rv8263c8_config *config = p_dev->config; - - if (p_dev == NULL) { - return -EINVAL; - } - - reg = calibration & 0x7F; - reg = bin2bcd(reg); - - k_spinlock_key_t key = k_spin_lock(&data->lock); - err = i2c_reg_update_byte_dt(&config->i2c_bus, RV8263C8_REGISTER_OFFSET, 0x7F, reg); - k_spin_unlock(&data->lock, key); - - return err; -} - -/** - * @brief - * - * @param p_dev Pointer to device strucure - * @param p_calibration - * @return int 0 when successful - */ -int rv8263c8_calibration_get(const struct device *p_dev, int32_t *p_calibration) -{ - int err; - uint8_t value; - struct rv8263c8_data *const data = p_dev->data; - const struct rv8263c8_config *config = p_dev->config; - - if ((p_dev == NULL) || (p_calibration == NULL)) { - return -EINVAL; - } - - k_spinlock_key_t key = k_spin_lock(&data->lock); - err = i2c_reg_read_byte_dt(&config->i2c_bus, RV8263C8_REGISTER_OFFSET, &value); - k_spin_unlock(&data->lock, key); - if (err < 0) { - LOG_ERR("Error while reading OFFSET! Error: %i", err); - return err; - } - - *p_calibration = bcd2bin(value & 0x7F); - - return 0; -} -#endif - -static const struct rtc_driver_api rv8263c8_driver_api = { - .set_time = rv8263c8_time_set, - .get_time = rv8263c8_time_get, -#ifdef CONFIG_RTC_ALARM - .alarm_get_supported_fields = rv8263c8_alarm_get_supported_fields, - .alarm_set_time = rv8263c8_alarm_set_time, - .alarm_get_time = rv8263c8_alarm_get_time, - .alarm_is_pending = rv8263c8_alarm_is_pending, - .alarm_set_callback = rv8263c8_alarm_set_callback, -#endif -#ifdef CONFIG_RTC_UPDATE - .update_set_callback = rv8263_update_callback, -#endif -#ifdef CONFIG_RTC_CALIBRATION - .set_calibration = rv8263c8_calibration_set, - .get_calibration = rv8263c8_calibration_get, -#endif -}; - -#define RV8263_DEFINE(inst) \ - static struct rv8263c8_data rv8263c8_data_##inst; \ - static const struct rv8263c8_config rv8263c8_config_##inst = { \ - .i2c_bus = I2C_DT_SPEC_INST_GET(inst), \ - .clkout = DT_INST_PROP(inst, clkout), \ - .fast_mode = DT_INST_PROP(inst, fast_mode), \ - .offset = DT_INST_PROP(inst, offset), \ - IF_ENABLED(RV8263_USE_INT, \ - (.int_gpio = GPIO_DT_SPEC_INST_GET_OR(inst, int_gpios, { 0 }),)) \ - }; \ - DEVICE_DT_INST_DEFINE(inst, &rv8263c8_init, NULL, &rv8263c8_data_##inst, \ - &rv8263c8_config_##inst, POST_KERNEL, CONFIG_RTC_INIT_PRIORITY, \ - &rv8263c8_driver_api); - -DT_INST_FOREACH_STATUS_OKAY(RV8263_DEFINE) \ No newline at end of file diff --git a/app/dts/bindings/rtc/microcrystal,rv-8263-c8.yml b/app/dts/bindings/rtc/microcrystal,rv-8263-c8.yml deleted file mode 100644 index 1c998e3e..00000000 --- a/app/dts/bindings/rtc/microcrystal,rv-8263-c8.yml +++ /dev/null @@ -1,40 +0,0 @@ -description: | - The RV-8263-C8 is a CMOS real-time clock/calendar module optimized for low power consumption. An Offset - register makes it possible to compensate for the frequency deviation of the clock of 32.768 kHz - -compatible: "microcrystal,rv-8263-c8" - -include: [rtc-device.yaml, i2c-device.yaml] - -properties: - int-gpios: - type: phandle-array - required: false - - fast_mode: - type: boolean - required: false - description: | - Enable fast offset mode (Offset every 4 minutes). - - offset: - type: int - required: false - default: 0 - description: - Offset for timer compensation. - - clkout: - type: int - required: false - default: 0 - enum: - - 32768 - - 8192 - - 1024 - - 64 - - 32 - - 1 - - 0 - description: - CLKOUT frequency selection. Set to 0 to disable the CLKOUT signal (CLKOUT pin is LOW). \ No newline at end of file diff --git a/app/zephyr_patches/ntc_fix.patch b/app/patches/ntc_fix.patch similarity index 100% rename from app/zephyr_patches/ntc_fix.patch rename to app/patches/ntc_fix.patch diff --git a/app/patches/rv8263_rtc.patch b/app/patches/rv8263_rtc.patch new file mode 100644 index 00000000..9cacd8fe --- /dev/null +++ b/app/patches/rv8263_rtc.patch @@ -0,0 +1,950 @@ +diff --git a/drivers/rtc/rtc_utils.h b/drivers/rtc/rtc_utils.c +new file mode 100644 +index 0000000000..035034da56 +--- /dev/null ++++ b/drivers/rtc/rtc_utils.c +@@ -0,0 +1,58 @@ ++/* ++ * Copyright (c) 2023 Bjarki Arge Andreasen ++ * Copyright (c) 2024 Andrew Featherstone ++ * ++ * SPDX-License-Identifier: Apache-2.0 ++ */ ++ ++#include ++#include ++ ++#include ++ ++#include "rtc_utils.h" ++ ++bool rtc_utils_validate_rtc_time(const struct rtc_time *timeptr, uint16_t mask) ++{ ++ if ((mask & RTC_ALARM_TIME_MASK_SECOND) && (timeptr->tm_sec < 0 || timeptr->tm_sec > 59)) { ++ return false; ++ } ++ ++ if ((mask & RTC_ALARM_TIME_MASK_MINUTE) && (timeptr->tm_min < 0 || timeptr->tm_min > 59)) { ++ return false; ++ } ++ ++ if ((mask & RTC_ALARM_TIME_MASK_HOUR) && (timeptr->tm_hour < 0 || timeptr->tm_hour > 23)) { ++ return false; ++ } ++ ++ if ((mask & RTC_ALARM_TIME_MASK_MONTH) && (timeptr->tm_mon < 0 || timeptr->tm_mon > 11)) { ++ return false; ++ } ++ ++ if ((mask & RTC_ALARM_TIME_MASK_MONTHDAY) && ++ (timeptr->tm_mday < 1 || timeptr->tm_mday > 31)) { ++ return false; ++ } ++ ++ if ((mask & RTC_ALARM_TIME_MASK_YEAR) && (timeptr->tm_year < 0 || timeptr->tm_year > 199)) { ++ return false; ++ } ++ ++ if ((mask & RTC_ALARM_TIME_MASK_WEEKDAY) && ++ (timeptr->tm_wday < 0 || timeptr->tm_wday > 6)) { ++ return false; ++ } ++ ++ if ((mask & RTC_ALARM_TIME_MASK_YEARDAY) && ++ (timeptr->tm_yday < 0 || timeptr->tm_yday > 365)) { ++ return false; ++ } ++ ++ if ((mask & RTC_ALARM_TIME_MASK_NSEC) && ++ (timeptr->tm_nsec < 0 || timeptr->tm_nsec > 999999999)) { ++ return false; ++ } ++ ++ return true; ++} +diff --git a/drivers/rtc/rtc_utils.h b/drivers/rtc/rtc_utils.h +new file mode 100644 +index 0000000000..035034da56 +--- /dev/null ++++ b/drivers/rtc/rtc_utils.h +@@ -0,0 +1,28 @@ ++/* ++ * Copyright (c) 2023 Bjarki Arge Andreasen ++ * Copyright (c) 2024 Andrew Featherstone ++ * ++ * SPDX-License-Identifier: Apache-2.0 ++ */ ++ ++#ifndef ZEPHYR_DRIVERS_RTC_RTC_UTILS_H_ ++#define ZEPHYR_DRIVERS_RTC_RTC_UTILS_H_ ++ ++#include ++#include ++ ++#include ++ ++/** ++ * @brief Validate a datetime with a mask ++ * ++ * Ensure that any fields selected by mask contain a valid value. ++ * ++ * @param timeptr The time to set ++ * @param mask Mask of fields to validate ++ * ++ * @return true if the required fields are valid. ++ */ ++bool rtc_utils_validate_rtc_time(const struct rtc_time *timeptr, uint16_t mask); ++ ++#endif /* ZEPHYR_DRIVERS_RTC_RTC_UTILS_H_ */ +diff --git a/drivers/rtc/CMakeLists.txt b/drivers/rtc/CMakeLists.txt +index 65d822d..4236ad5 100644 +--- a/drivers/rtc/CMakeLists.txt ++++ b/drivers/rtc/CMakeLists.txt +@@ -5,6 +5,9 @@ zephyr_syscall_header(${ZEPHYR_BASE}/include/zephyr/drivers/rtc.h) + + zephyr_library() + ++zephyr_library_sources(rtc_utils.c) ++ ++zephyr_library_sources_ifdef(CONFIG_RTC_RV8263 rtc_rv8263.c) + zephyr_library_sources_ifdef(CONFIG_RTC_AM1805 rtc_am1805.c) + zephyr_library_sources_ifdef(CONFIG_RTC_DS1307 rtc_ds1307.c) + zephyr_library_sources_ifdef(CONFIG_USERSPACE rtc_handlers.c) +index 46bd2562dd..2a1d5fa05b 100644 +--- a/drivers/rtc/Kconfig ++++ b/drivers/rtc/Kconfig +@@ -53,1 +53,2 @@ source "drivers/rtc/Kconfig.sam" ++source "drivers/rtc/Kconfig.rv8263" + + endif # RTC +diff --git a/drivers/rtc/Kconfig.rv8263 b/drivers/rtc/Kconfig.rv8263 +new file mode 100644 +index 0000000000..035034da56 +--- /dev/null ++++ b/drivers/rtc/Kconfig.rv8263 +@@ -0,0 +1,11 @@ ++# SPDX-License-Identifier: Apache-2.0 ++# Copyright (c) 2024 Daniel Kampert ++# Author: Daniel Kampert ++ ++config RTC_RV8263 ++ bool "Micro Crystal RV-8263-C8 RTC driver" ++ default y ++ depends on DT_HAS_MICROCRYSTAL_RV_8263_C8_ENABLED ++ select I2C ++ help ++ Micro Crystal RV-8263-C8 RTC driver. +diff --git a/drivers/rtc/rtc_rv8263.c b/drivers/rtc/rtc_rv8263.c +new file mode 100644 +index 0000000000..bb82ed7d9b +--- /dev/null ++++ b/drivers/rtc/rtc_rv8263.c +@@ -0,0 +1,771 @@ ++/* Copyright (c) 2024 Daniel Kampert ++ * Author: Daniel Kampert ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include "rtc_utils.h" ++ ++#define RV8263C8_REGISTER_CONTROL_1 0x00 ++#define RV8263C8_REGISTER_CONTROL_2 0x01 ++#define RV8263C8_REGISTER_OFFSET 0x02 ++#define RV8263C8_REGISTER_RAM 0x03 ++#define RV8263C8_REGISTER_SECONDS 0x04 ++#define RV8263C8_REGISTER_MINUTES 0x05 ++#define RV8263C8_REGISTER_HOURS 0x06 ++#define RV8263C8_REGISTER_DATE 0x07 ++#define RV8263C8_REGISTER_WEEKDAY 0x08 ++#define RV8263C8_REGISTER_MONTH 0x09 ++#define RV8263C8_REGISTER_YEAR 0x0A ++#define RV8263C8_REGISTER_SECONDS_ALARM 0x0B ++#define RV8263C8_REGISTER_MINUTES_ALARM 0x0C ++#define RV8263C8_REGISTER_HOURS_ALARM 0x0D ++#define RV8263C8_REGISTER_DATE_ALARM 0x0E ++#define RV8263C8_REGISTER_WEEKDAY_ALARM 0x0F ++#define RV8263C8_REGISTER_TIMER_VALUE 0x10 ++#define RV8263C8_REGISTER_TIMER_MODE 0x11 ++ ++#define RV8263_BM_FAST_MODE (0x01 << 7) ++#define RV8263_BM_NORMAL_MODE (0x00 << 7) ++#define RV8263C8_BM_24H_MODE_ENABLE (0x00 << 1) ++#define RV8263C8_BM_24H_MODE_DISABLE (0x00 << 1) ++#define RV8263C8_BM_CLOCK_ENABLE (0x00 << 5) ++#define RV8263C8_BM_CLOCK_DISABLE (0x01 << 5) ++#define RV8263C8_BM_ALARM_INT_ENABLE (0x01 << 7) ++#define RV8263C8_BM_ALARM_INT_DISABLE (0x00 << 7) ++#define RV8263C8_BM_MINUTE_INT_ENABLE (0x01 << 5) ++#define RV8263C8_BM_MINUTE_INT_DISABLE (0x00 << 5) ++#define RV8263C8_BM_HALF_MINUTE_INT_ENABLE (0x01 << 4) ++#define RV8263C8_BM_HALF_MINUTE_INT_DISABLE (0x00 << 4) ++#define RV8263C8_BM_ALARM_ENABLE (0x00 << 7) ++#define RV8263C8_BM_ALARM_DISABLE (0x01 << 7) ++#define RV8263C8_BM_AF (0x01 << 6) ++#define RV8263C8_BM_TF (0x01 << 3) ++#define RV8263_BM_MODE (0x01 << 7) ++#define RV8263_BM_TD_1HZ (0x02 << 3) ++#define RV8263_BM_TE_ENABLE (0x01 << 2) ++#define RV8263_BM_TIE_ENABLE (0x01 << 1) ++#define RV8263_BM_TI_TP_PULSE (0x01 << 0) ++#define RV8263_BM_OS (0x01 << 7) ++#define RV8263C8_BM_SOFTWARE_RESET (0x58) ++#define RV8263C8_BM_REGISTER_OFFSET 0x7F ++#define RV8263_YEAR_OFFSET (2000 - 1900) ++ ++#define SECONDS_BITS GENMASK(6, 0) ++#define MINUTES_BITS GENMASK(7, 0) ++#define HOURS_BITS GENMASK(5, 0) ++#define DATE_BITS GENMASK(5, 0) ++#define MONTHS_BITS GENMASK(4, 0) ++#define WEEKDAY_BITS GENMASK(2, 0) ++#define YEAR_BITS GENMASK(7, 0) ++#define VALIDATE_24HR BIT(6) ++ ++#define MIN_SEC 0 ++#define MAX_SEC 59 ++#define MIN_MIN 0 ++#define MAX_MIN 59 ++#define MIN_HOUR 0 ++#define MAX_HOUR 23 ++#define MAX_WDAY 7 ++#define MIN_WDAY 1 ++#define MAX_MDAY 31 ++#define MIN_MDAY 1 ++#define MAX_MON 12 ++#define MIN_MON 1 ++#define MIN_YEAR_DIFF 0 ++#define MAX_YEAR_DIFF 99 ++ ++#define DT_DRV_COMPAT microcrystal_rv_8263_c8 ++ ++LOG_MODULE_REGISTER(microcrystal_rv8263c8, CONFIG_RTC_LOG_LEVEL); ++ ++struct rv8263c8_config { ++ struct i2c_dt_spec i2c_bus; ++ uint32_t clkout; ++ ++#if DT_ANY_INST_HAS_PROP_STATUS_OKAY(int_gpios) ++ struct gpio_dt_spec int_gpio; ++#endif ++}; ++ ++struct rv8263c8_data { ++ struct k_sem lock; ++ ++#if (CONFIG_RTC_ALARM || CONFIG_RTC_UPDATE) && DT_ANY_INST_HAS_PROP_STATUS_OKAY(int_gpios) ++ const struct device *dev; ++ struct gpio_callback gpio_cb; ++#endif ++ ++#if CONFIG_RTC_ALARM && DT_ANY_INST_HAS_PROP_STATUS_OKAY(int_gpios) ++ rtc_alarm_callback alarm_cb; ++ void *alarm_cb_data; ++ struct k_work alarm_work; ++#endif ++ ++#if CONFIG_RTC_UPDATE && DT_ANY_INST_HAS_PROP_STATUS_OKAY(int_gpios) ++ rtc_update_callback update_cb; ++ void *update_cb_data; ++ struct k_work update_work; ++#endif ++}; ++ ++static int rv8263c8_update_disable_timer(const struct device *dev) ++{ ++ int err; ++ uint8_t buf[2]; ++ const struct rv8263c8_config *config = dev->config; ++ ++ /* Value 0 disables the timer. */ ++ buf[0] = RV8263C8_REGISTER_TIMER_VALUE; ++ buf[1] = 0; ++ err = i2c_write_dt(&config->i2c_bus, buf, 2); ++ if (err < 0) { ++ return err; ++ } ++ ++ buf[0] = RV8263C8_REGISTER_TIMER_MODE; ++ return i2c_write_dt(&config->i2c_bus, buf, 2); ++} ++ ++#if (CONFIG_RTC_ALARM || CONFIG_RTC_UPDATE) && DT_ANY_INST_HAS_PROP_STATUS_OKAY(int_gpios) ++static void rv8263c8_gpio_callback_handler(const struct device *p_port, struct gpio_callback *p_cb, ++ gpio_port_pins_t pins) ++{ ++ ARG_UNUSED(pins); ++ ARG_UNUSED(p_port); ++ ++ struct rv8263c8_data *data = CONTAINER_OF(p_cb, struct rv8263c8_data, gpio_cb); ++ ++#if CONFIG_RTC_ALARM ++ k_work_submit(&data->alarm_work); ++#endif ++ ++#if CONFIG_RTC_UPDATE ++ k_work_submit(&data->update_work); ++#endif ++} ++#endif ++ ++#if CONFIG_RTC_ALARM && DT_ANY_INST_HAS_PROP_STATUS_OKAY(int_gpios) ++static void rv8263c8_alarm_worker(struct k_work *p_work) ++{ ++ struct rv8263c8_data *data = CONTAINER_OF(p_work, struct rv8263c8_data, alarm_work); ++ const struct rv8263c8_config *config = data->dev->config; ++ ++ LOG_DBG("Process alarm worker from interrupt"); ++ ++ if (data->alarm_cb != NULL) { ++ uint8_t reg; ++ ++ i2c_reg_read_byte_dt(&config->i2c_bus, RV8263C8_REGISTER_CONTROL_2, ®); ++ ++ if (reg & RV8263C8_BM_AF) { ++ reg &= ~RV8263C8_BM_AF; ++ ++ LOG_DBG("Calling alarm callback"); ++ data->alarm_cb(data->dev, 0, data->alarm_cb_data); ++ ++ i2c_reg_write_byte_dt(&config->i2c_bus, RV8263C8_REGISTER_CONTROL_2, reg); ++ } ++ } ++} ++#endif ++ ++#if CONFIG_RTC_UPDATE && DT_ANY_INST_HAS_PROP_STATUS_OKAY(int_gpios) ++static int rv8263c8_update_enable_timer(const struct device *dev) ++{ ++ int err; ++ const struct rv8263c8_config *config = dev->config; ++ uint8_t buf[2]; ++ ++ /* Set the timer preload value for 1 second. */ ++ buf[0] = RV8263C8_REGISTER_TIMER_VALUE; ++ buf[1] = 1; ++ err = i2c_write_dt(&config->i2c_bus, buf, 2); ++ if (err < 0) { ++ return err; ++ } ++ ++ buf[0] = RV8263C8_REGISTER_TIMER_MODE; ++ buf[1] = RV8263_BM_TD_1HZ | RV8263_BM_TE_ENABLE | RV8263_BM_TIE_ENABLE | ++ RV8263_BM_TI_TP_PULSE; ++ return i2c_write_dt(&config->i2c_bus, buf, 2); ++} ++ ++static void rv8263c8_update_worker(struct k_work *p_work) ++{ ++ uint8_t reg; ++ struct rv8263c8_data *data = CONTAINER_OF(p_work, struct rv8263c8_data, update_work); ++ const struct rv8263c8_config *config = data->dev->config; ++ ++ LOG_DBG("Process update worker from interrupt"); ++ ++ if (data->update_cb != NULL) { ++ i2c_reg_read_byte_dt(&config->i2c_bus, RV8263C8_REGISTER_CONTROL_2, ®); ++ ++ if (reg & RV8263C8_BM_TF) { ++ LOG_DBG("Calling update callback"); ++ data->update_cb(data->dev, data->update_cb_data); ++ } ++ } ++ ++ rv8263c8_update_enable_timer(data->dev); ++ i2c_reg_update_byte_dt(&config->i2c_bus, RV8263C8_REGISTER_CONTROL_2, RV8263C8_BM_TF, ++ RV8263C8_BM_TF); ++} ++#endif ++ ++static int rv8263c8_time_set(const struct device *dev, const struct rtc_time *timeptr) ++{ ++ uint8_t regs[8]; ++ const struct rv8263c8_config *config = dev->config; ++ ++ if (timeptr == NULL || (timeptr->tm_year < RV8263_YEAR_OFFSET)) { ++ LOG_ERR("invalid time"); ++ return -EINVAL; ++ } ++ ++ LOG_DBG("Set time: year = %u, mon = %u, mday = %u, wday = %u, hour = %u, min = %u, sec = " ++ "%u", ++ timeptr->tm_year, timeptr->tm_mon, timeptr->tm_mday, timeptr->tm_wday, ++ timeptr->tm_hour, timeptr->tm_min, timeptr->tm_sec); ++ ++ regs[0] = RV8263C8_REGISTER_SECONDS; ++ regs[1] = bin2bcd(timeptr->tm_sec) & SECONDS_BITS; ++ regs[2] = bin2bcd(timeptr->tm_min) & MINUTES_BITS; ++ regs[3] = bin2bcd(timeptr->tm_hour) & HOURS_BITS; ++ regs[4] = bin2bcd(timeptr->tm_mday) & DATE_BITS; ++ regs[5] = bin2bcd(timeptr->tm_wday) & WEEKDAY_BITS; ++ regs[6] = bin2bcd(timeptr->tm_mon) & MONTHS_BITS; ++ regs[7] = bin2bcd(timeptr->tm_year - RV8263_YEAR_OFFSET) & YEAR_BITS; ++ ++ return i2c_write_dt(&config->i2c_bus, regs, 8); ++} ++ ++static int rv8263c8_time_get(const struct device *dev, struct rtc_time *timeptr) ++{ ++ int err; ++ uint8_t regs[7]; ++ const struct rv8263c8_config *config = dev->config; ++ ++ if (timeptr == NULL) { ++ return -EINVAL; ++ } ++ ++ err = i2c_burst_read_dt(&config->i2c_bus, RV8263C8_REGISTER_SECONDS, regs, sizeof(regs)); ++ if (err < 0) { ++ return err; ++ } ++ ++ /* Return an error when the oscillator is stopped. */ ++ if (regs[0] & RV8263_BM_OS) { ++ return -ECANCELED; ++ } ++ ++ timeptr->tm_sec = bcd2bin(regs[0] & SECONDS_BITS); ++ timeptr->tm_min = bcd2bin(regs[1] & MINUTES_BITS); ++ timeptr->tm_hour = bcd2bin(regs[2] & HOURS_BITS); ++ timeptr->tm_mday = bcd2bin(regs[3] & DATE_BITS); ++ timeptr->tm_wday = bcd2bin(regs[4] & WEEKDAY_BITS); ++ timeptr->tm_mon = bcd2bin(regs[5] & MONTHS_BITS); ++ timeptr->tm_year = bcd2bin(regs[6] & YEAR_BITS) + RV8263_YEAR_OFFSET; ++ ++ /* Unused. */ ++ timeptr->tm_nsec = 0; ++ timeptr->tm_isdst = -1; ++ timeptr->tm_yday = -1; ++ ++ /* Validate the chip in 24hr mode. */ ++ if (regs[2] & VALIDATE_24HR) { ++ return -ENODATA; ++ } ++ ++ LOG_DBG("Get time: year = %u, mon = %u, mday = %u, wday = %u, hour = %u, min = %u, sec = " ++ "%u", ++ timeptr->tm_year, timeptr->tm_mon, timeptr->tm_mday, timeptr->tm_wday, ++ timeptr->tm_hour, timeptr->tm_min, timeptr->tm_sec); ++ ++ return 0; ++} ++ ++static int rv8263c8_init(const struct device *dev) ++{ ++ int err; ++ int temp; ++ struct rv8263c8_data *data = dev->data; ++ const struct rv8263c8_config *config = dev->config; ++ ++#if (CONFIG_RTC_ALARM || CONFIG_RTC_UPDATE) && DT_ANY_INST_HAS_PROP_STATUS_OKAY(int_gpios) ++ if (config->int_gpio.port == NULL) { ++ return -EINVAL; ++ } ++#endif ++ ++ if (!i2c_is_ready_dt(&config->i2c_bus)) { ++ LOG_ERR("I2C bus not ready!"); ++ return -ENODEV; ++ } ++ ++ k_sem_init(&data->lock, 1, 1); ++ ++ err = rv8263c8_update_disable_timer(dev); ++ if (err < 0) { ++ LOG_ERR("Error while disabling the timer! Error: %i", err); ++ return err; ++ } ++ ++ err = i2c_reg_write_byte_dt(&config->i2c_bus, RV8263C8_REGISTER_CONTROL_1, ++ RV8263C8_BM_24H_MODE_DISABLE | RV8263C8_BM_CLOCK_ENABLE); ++ if (err < 0) { ++ LOG_ERR("Error while writing CONTROL_1! Error: %i", err); ++ return err; ++ } ++ ++ temp = config->clkout; ++ LOG_DBG("Configure ClkOut: %u", temp); ++ ++ err = i2c_reg_write_byte_dt(&config->i2c_bus, RV8263C8_REGISTER_CONTROL_2, ++ RV8263C8_BM_AF | temp); ++ if (err < 0) { ++ LOG_ERR("Error while writing CONTROL_2! Error: %i", err); ++ return err; ++ } ++ ++ LOG_DBG("Configure ClkOut: %u", temp); ++ ++#if CONFIG_RTC_UPDATE && DT_ANY_INST_HAS_PROP_STATUS_OKAY(int_gpios) ++ uint8_t buf[2]; ++ ++ buf[0] = RV8263C8_REGISTER_TIMER_MODE; ++ buf[1] = 0; ++ err = i2c_write_dt(&config->i2c_bus, buf, 2); ++ if (err < 0) { ++ LOG_ERR("Error while writing CONTROL2! Error: %i", err); ++ return err; ++ } ++#endif ++ ++#if (CONFIG_RTC_ALARM || CONFIG_RTC_UPDATE) && DT_ANY_INST_HAS_PROP_STATUS_OKAY(int_gpios) ++ if (!gpio_is_ready_dt(&config->int_gpio)) { ++ LOG_ERR("GPIO not ready!"); ++ return err; ++ } ++ ++ err = gpio_pin_configure_dt(&config->int_gpio, GPIO_INPUT); ++ if (err < 0) { ++ LOG_ERR("Failed to configure GPIO! Error: %u", err); ++ return err; ++ } ++ ++ err = gpio_pin_interrupt_configure_dt(&config->int_gpio, GPIO_INT_EDGE_FALLING); ++ if (err < 0) { ++ LOG_ERR("Failed to configure interrupt! Error: %u", err); ++ return err; ++ } ++ ++ gpio_init_callback(&data->gpio_cb, rv8263c8_gpio_callback_handler, ++ BIT(config->int_gpio.pin)); ++ ++ err = gpio_add_callback_dt(&config->int_gpio, &data->gpio_cb); ++ if (err < 0) { ++ LOG_ERR("Failed to add GPIO callback! Error: %u", err); ++ return err; ++ } ++#endif ++ ++ (void)k_sem_take(&data->lock, K_FOREVER); ++#if CONFIG_RTC_ALARM && DT_ANY_INST_HAS_PROP_STATUS_OKAY(int_gpios) ++ data->alarm_work.handler = rv8263c8_alarm_worker; ++#endif ++ ++#if CONFIG_RTC_UPDATE && DT_ANY_INST_HAS_PROP_STATUS_OKAY(int_gpios) ++ data->update_work.handler = rv8263c8_update_worker; ++#endif ++ ++#if (CONFIG_RTC_ALARM || CONFIG_RTC_UPDATE) && DT_ANY_INST_HAS_PROP_STATUS_OKAY(int_gpios) ++ data->dev = dev; ++#endif ++ k_sem_give(&data->lock); ++ ++ return 0; ++} ++ ++#if CONFIG_RTC_ALARM ++static int rv8263c8_alarm_get_supported_fields(const struct device *dev, uint16_t id, ++ uint16_t *p_mask) ++{ ++ ARG_UNUSED(dev); ++ ARG_UNUSED(id); ++ ++ (*p_mask) = (RTC_ALARM_TIME_MASK_SECOND | RTC_ALARM_TIME_MASK_MINUTE | ++ RTC_ALARM_TIME_MASK_HOUR | RTC_ALARM_TIME_MASK_MONTHDAY | ++ RTC_ALARM_TIME_MASK_WEEKDAY); ++ ++ return 0; ++} ++ ++static int rv8263c8_alarm_set_time(const struct device *dev, uint16_t id, uint16_t mask, ++ const struct rtc_time *timeptr) ++{ ++ int err; ++ uint8_t regs[5]; ++ const struct rv8263c8_config *config = dev->config; ++ ++ ARG_UNUSED(id); ++ ++ if ((mask > 0) && (timeptr == NULL)) { ++ return -EINVAL; ++ } ++ ++ /* Disable the alarm when mask is zero. */ ++ if (mask == 0) { ++ return i2c_reg_update_byte_dt(&config->i2c_bus, RV8263C8_REGISTER_CONTROL_2, ++ RV8263C8_BM_ALARM_INT_ENABLE, ++ RV8263C8_BM_ALARM_INT_DISABLE); ++ } ++ ++ if (!rtc_utils_validate_rtc_time(timeptr, mask)) { ++ LOG_ERR("Invalid mask!"); ++ return -EINVAL; ++ } ++ ++ regs[0] = bin2bcd(timeptr->tm_sec) & SECONDS_BITS; ++ regs[1] = bin2bcd(timeptr->tm_min) & MINUTES_BITS; ++ regs[2] = bin2bcd(timeptr->tm_hour) & HOURS_BITS; ++ regs[3] = bin2bcd(timeptr->tm_mday) & DATE_BITS; ++ regs[4] = bin2bcd(timeptr->tm_wday) & WEEKDAY_BITS; ++ ++ if (mask & RTC_ALARM_TIME_MASK_SECOND) { ++ err = i2c_reg_write_byte_dt(&config->i2c_bus, RV8263C8_REGISTER_SECONDS_ALARM, ++ RV8263C8_BM_ALARM_ENABLE | regs[0]); ++ } else { ++ err = i2c_reg_write_byte_dt(&config->i2c_bus, RV8263C8_REGISTER_SECONDS_ALARM, ++ RV8263C8_BM_ALARM_DISABLE); ++ } ++ ++ if (err < 0) { ++ LOG_ERR("Error while writing SECONDS alarm! Error: %i", err); ++ return err; ++ } ++ ++ if (mask & RTC_ALARM_TIME_MASK_MINUTE) { ++ err = i2c_reg_write_byte_dt(&config->i2c_bus, RV8263C8_REGISTER_MINUTES_ALARM, ++ RV8263C8_BM_ALARM_ENABLE | regs[1]); ++ } else { ++ err = i2c_reg_write_byte_dt(&config->i2c_bus, RV8263C8_REGISTER_MINUTES_ALARM, ++ RV8263C8_BM_ALARM_DISABLE); ++ } ++ ++ if (err < 0) { ++ LOG_ERR("Error while writing MINUTE alarm! Error: %i", err); ++ return err; ++ } ++ ++ if (mask & RTC_ALARM_TIME_MASK_HOUR) { ++ err = i2c_reg_write_byte_dt(&config->i2c_bus, RV8263C8_REGISTER_HOURS_ALARM, ++ RV8263C8_BM_ALARM_ENABLE | regs[2]); ++ } else { ++ err = i2c_reg_write_byte_dt(&config->i2c_bus, RV8263C8_REGISTER_HOURS_ALARM, ++ RV8263C8_BM_ALARM_DISABLE); ++ } ++ ++ if (err < 0) { ++ LOG_ERR("Error while writing HOUR alarm! Error: %i", err); ++ return err; ++ } ++ ++ if (mask & RTC_ALARM_TIME_MASK_MONTHDAY) { ++ err = i2c_reg_write_byte_dt(&config->i2c_bus, RV8263C8_REGISTER_DATE_ALARM, ++ RV8263C8_BM_ALARM_ENABLE | regs[3]); ++ } else { ++ err = i2c_reg_write_byte_dt(&config->i2c_bus, RV8263C8_REGISTER_DATE_ALARM, ++ RV8263C8_BM_ALARM_DISABLE); ++ } ++ ++ if (err < 0) { ++ LOG_ERR("Error while writing MONTHDAY alarm! Error: %i", err); ++ return err; ++ } ++ ++ if (mask & RTC_ALARM_TIME_MASK_WEEKDAY) { ++ err = i2c_reg_write_byte_dt(&config->i2c_bus, RV8263C8_REGISTER_WEEKDAY_ALARM, ++ RV8263C8_BM_ALARM_ENABLE | regs[4]); ++ } else { ++ err = i2c_reg_write_byte_dt(&config->i2c_bus, RV8263C8_REGISTER_WEEKDAY_ALARM, ++ RV8263C8_BM_ALARM_DISABLE); ++ } ++ ++ if (err < 0) { ++ LOG_ERR("Error while writing WEEKDAY alarm! Error: %i", err); ++ } ++ ++ return err; ++} ++ ++static int rv8263c8_alarm_get_time(const struct device *dev, uint16_t id, uint16_t *p_mask, ++ struct rtc_time *timeptr) ++{ ++ int err; ++ const struct rv8263c8_config *config = dev->config; ++ uint8_t value[5]; ++ ++ ARG_UNUSED(id); ++ ++ if (timeptr == NULL) { ++ return -EINVAL; ++ } ++ ++ (*p_mask) = 0; ++ ++ err = i2c_burst_read_dt(&config->i2c_bus, RV8263C8_REGISTER_SECONDS_ALARM, value, ++ sizeof(value)); ++ if (err < 0) { ++ LOG_ERR("Error while reading alarm! Error: %i", err); ++ return err; ++ } ++ ++ if (value[0] <= MAX_SEC) { ++ timeptr->tm_sec = bcd2bin(value[0]) & SECONDS_BITS; ++ (*p_mask) |= RTC_ALARM_TIME_MASK_SECOND; ++ } ++ ++ if (value[1] <= MAX_MIN) { ++ timeptr->tm_min = bcd2bin(value[1]) & MINUTES_BITS; ++ (*p_mask) |= RTC_ALARM_TIME_MASK_MINUTE; ++ } ++ ++ if (value[2] <= MAX_HOUR) { ++ timeptr->tm_hour = bcd2bin(value[2]) & HOURS_BITS; ++ (*p_mask) |= RTC_ALARM_TIME_MASK_HOUR; ++ } ++ ++ if (value[3] <= MAX_MDAY) { ++ timeptr->tm_mday = bcd2bin(value[3]) & DATE_BITS; ++ (*p_mask) |= RTC_ALARM_TIME_MASK_MONTHDAY; ++ } ++ ++ if (value[4] <= MAX_WDAY) { ++ timeptr->tm_wday = bcd2bin(value[4]) & WEEKDAY_BITS; ++ (*p_mask) |= RTC_ALARM_TIME_MASK_WEEKDAY; ++ } ++ ++ return 0; ++} ++ ++static int rv8263c8_alarm_set_callback(const struct device *dev, uint16_t id, ++ rtc_alarm_callback callback, void *user_data) ++{ ++ int err; ++ const struct rv8263c8_config *config = dev->config; ++ ++#if DT_ANY_INST_HAS_PROP_STATUS_OKAY(int_gpios) ++ struct rv8263c8_data *data = dev->data; ++#endif ++ ++ ARG_UNUSED(id); ++ ++#if DT_ANY_INST_HAS_PROP_STATUS_OKAY(int_gpios) ++ if (config->int_gpio.port == NULL) { ++ return -ENOTSUP; ++ } ++ ++ (void)k_sem_take(&data->lock, K_FOREVER); ++ data->alarm_cb = callback; ++ data->alarm_cb_data = user_data; ++ k_sem_give(&data->lock); ++#else ++ return -ENOTSUP; ++#endif ++ ++ if ((callback == NULL) && (user_data == NULL)) { ++ LOG_DBG("Disable alarm function"); ++ ++ err = i2c_reg_update_byte_dt(&config->i2c_bus, RV8263C8_REGISTER_CONTROL_2, ++ RV8263C8_BM_ALARM_INT_ENABLE, ++ RV8263C8_BM_ALARM_INT_DISABLE); ++ } else { ++ LOG_DBG("Enable alarm function"); ++ ++ err = i2c_reg_update_byte_dt(&config->i2c_bus, RV8263C8_REGISTER_CONTROL_2, ++ RV8263C8_BM_ALARM_INT_ENABLE | RV8263C8_BM_AF, ++ RV8263C8_BM_ALARM_INT_ENABLE); ++ } ++ ++ if (err < 0) { ++ LOG_ERR("Error while writing CONTROL2! Error: %i", err); ++ } ++ ++ return err; ++} ++ ++static int rv8263c8_alarm_is_pending(const struct device *dev, uint16_t id) ++{ ++ int err; ++ uint8_t reg; ++ const struct rv8263c8_config *config = dev->config; ++ ++ ARG_UNUSED(id); ++ ++ err = i2c_reg_read_byte_dt(&config->i2c_bus, RV8263C8_REGISTER_CONTROL_2, ®); ++ if (err) { ++ return err; ++ } ++ ++ if (reg & RV8263C8_BM_AF) { ++ reg &= ~RV8263C8_BM_AF; ++ err = i2c_reg_write_byte_dt(&config->i2c_bus, RV8263C8_REGISTER_CONTROL_2, reg); ++ if (err) { ++ return err; ++ } ++ ++ return 1; ++ } ++ ++ return 0; ++} ++#endif ++ ++#if CONFIG_RTC_UPDATE && DT_ANY_INST_HAS_PROP_STATUS_OKAY(int_gpios) ++int rv8263_update_callback(const struct device *dev, rtc_update_callback callback, void *user_data) ++{ ++ struct rv8263c8_data *const data = dev->data; ++ ++ (void)k_sem_take(&data->lock, K_FOREVER); ++ data->update_cb = callback; ++ data->update_cb_data = user_data; ++ k_sem_give(&data->lock); ++ ++ /* Disable the update callback. */ ++ if ((callback == NULL) && (user_data == NULL)) { ++ return rv8263c8_update_disable_timer(dev); ++ } ++ ++ return rv8263c8_update_enable_timer(dev); ++} ++#endif ++ ++#ifdef CONFIG_RTC_CALIBRATION ++int rv8263c8_calibration_set(const struct device *dev, int32_t calibration) ++{ ++ int8_t offset; ++ int32_t test_mode0; ++ int32_t test_mode1; ++ int32_t offset_ppm_mode0; ++ int32_t offset_ppm_mode1; ++ const struct rv8263c8_config *config = dev->config; ++ ++ /* NOTE: The RTC API is using a PPB (Parts Per Billion) value. The RTC is using PPM. ++ * Here we calculate the offset when using MODE = 0. ++ * Formula from the application manual: ++ * Offset [ppm] = (calibration [ppb] / (4.34 [ppm] x 1000)) ++ */ ++ offset_ppm_mode0 = calibration / 4340; ++ ++ /* Here we calculate the offset when using MODE = 1. ++ * Formula from the application manual: ++ * Offset [ppm] = (calibration [ppb] / (4.069 [ppm] x 1000)) ++ */ ++ offset_ppm_mode1 = calibration / 4069; ++ ++ LOG_DBG("Offset Mode = 0: %i", offset_ppm_mode0); ++ LOG_DBG("Offset Mode = 1: %i", offset_ppm_mode1); ++ ++ test_mode0 = offset_ppm_mode0 * 4340; ++ test_mode0 = calibration - test_mode0; ++ test_mode1 = offset_ppm_mode1 * 4069; ++ test_mode1 = calibration - test_mode1; ++ ++ /* Compare the values and select the value with the smallest error. */ ++ test_mode0 = test_mode0 < 0 ? -test_mode0 : test_mode0; ++ test_mode1 = test_mode1 < 0 ? -test_mode1 : test_mode1; ++ if (test_mode0 > test_mode1) { ++ LOG_DBG("Use fast mode (Mode = 1)"); ++ ++ /* Error with MODE = 1 is smaller -> Use MODE = 1. */ ++ offset = RV8263_BM_FAST_MODE | (offset_ppm_mode1 & GENMASK(7, 0)); ++ } else { ++ LOG_DBG("Use normal mode (Mode = 0)"); ++ ++ /* Error with MODE = 0 is smaller -> Use MODE = 0. */ ++ offset = RV8263_BM_NORMAL_MODE | (offset_ppm_mode0 & GENMASK(7, 0)); ++ } ++ ++ LOG_DBG("Set offset value: %i", (offset & RV8263C8_BM_REGISTER_OFFSET)); ++ ++ return i2c_reg_write_byte_dt(&config->i2c_bus, RV8263C8_REGISTER_OFFSET, offset); ++} ++ ++int rv8263c8_calibration_get(const struct device *dev, int32_t *calibration) ++{ ++ int err; ++ int32_t temp; ++ int8_t offset; ++ const struct rv8263c8_config *config = dev->config; ++ ++ if (calibration == NULL) { ++ return -EINVAL; ++ } ++ ++ err = i2c_reg_read_byte_dt(&config->i2c_bus, RV8263C8_REGISTER_OFFSET, &offset); ++ if (err) { ++ return err; ++ } ++ ++ /* Convert the signed 7 bit into a signed 8 bit value. */ ++ if (offset & (0x01 << 6)) { ++ temp = offset | (0x01 << 7); ++ } else { ++ temp = offset & (0x3F); ++ temp &= ~(0x01 << 7); ++ } ++ ++ LOG_DBG("Read offset: %i", temp); ++ ++ if (offset & RV8263_BM_FAST_MODE) { ++ temp = temp * 4340L; ++ } else { ++ temp = temp * 4069L; ++ } ++ ++ *calibration = temp; ++ ++ return 0; ++} ++#endif ++ ++static const struct rtc_driver_api rv8263c8_driver_api = { ++ .set_time = rv8263c8_time_set, ++ .get_time = rv8263c8_time_get, ++#if CONFIG_RTC_ALARM ++ .alarm_get_supported_fields = rv8263c8_alarm_get_supported_fields, ++ .alarm_set_time = rv8263c8_alarm_set_time, ++ .alarm_get_time = rv8263c8_alarm_get_time, ++ .alarm_is_pending = rv8263c8_alarm_is_pending, ++ .alarm_set_callback = rv8263c8_alarm_set_callback, ++#endif ++#if CONFIG_RTC_UPDATE && DT_ANY_INST_HAS_PROP_STATUS_OKAY(int_gpios) ++ .update_set_callback = rv8263_update_callback, ++#endif ++#ifdef CONFIG_RTC_CALIBRATION ++ .set_calibration = rv8263c8_calibration_set, ++ .get_calibration = rv8263c8_calibration_get, ++#endif ++}; ++ ++#define RV8263_DEFINE(inst) \ ++ static struct rv8263c8_data rv8263c8_data_##inst; \ ++ static const struct rv8263c8_config rv8263c8_config_##inst = { \ ++ .i2c_bus = I2C_DT_SPEC_INST_GET(inst), \ ++ .clkout = DT_INST_ENUM_IDX(inst, clkout), \ ++ IF_ENABLED(DT_ANY_INST_HAS_PROP_STATUS_OKAY(int_gpios), \ ++ (.int_gpio = GPIO_DT_SPEC_INST_GET_OR(inst, int_gpios, {0})))}; \ ++ DEVICE_DT_INST_DEFINE(inst, &rv8263c8_init, NULL, &rv8263c8_data_##inst, \ ++ &rv8263c8_config_##inst, POST_KERNEL, CONFIG_RTC_INIT_PRIORITY, \ ++ &rv8263c8_driver_api); ++ ++DT_INST_FOREACH_STATUS_OKAY(RV8263_DEFINE) +diff --git a/dts/bindings/rtc/microcrystal,rv-8263-c8.yaml b/dts/bindings/rtc/microcrystal,rv-8263-c8.yaml +new file mode 100644 +index 0000000000..da6c246dbc +--- /dev/null ++++ b/dts/bindings/rtc/microcrystal,rv-8263-c8.yaml +@@ -0,0 +1,31 @@ ++# SPDX-License-Identifier: Apache-2.0 ++# Copyright (c) 2024 Daniel Kampert ++# Author: Daniel Kampert ++ ++description: Micro Crystal RV-8263-C8 RTC ++ ++compatible: "microcrystal,rv-8263-c8" ++ ++include: ++ - name: rtc-device.yaml ++ - name: i2c-device.yaml ++ ++properties: ++ int-gpios: ++ type: phandle-array ++ ++ clkout: ++ type: int ++ default: 0 ++ enum: ++ - 32768 ++ - 16384 ++ - 8192 ++ - 4096 ++ - 2048 ++ - 1024 ++ - 1 ++ - 0 ++ description: ++ Enable CLKOUT at given frequency. When disabled, CLKOUT pin is LOW. ++ The default is 0 and corresponds to the disable the CLKOUT signal. diff --git a/app/src/events/zsw_periodic_event.c b/app/src/events/zsw_periodic_event.c index 27209a96..d3981bb9 100644 --- a/app/src/events/zsw_periodic_event.c +++ b/app/src/events/zsw_periodic_event.c @@ -105,6 +105,7 @@ static int zsw_timer_init(void) k_work_init_delayable(work, handle_slow_timeout); zbus_chan_finish(&periodic_event_10s_chan); + // TODO: Use the RTC instead of the timer in rev 5 to generate this zbus_chan_claim(&periodic_event_1s_chan, K_FOREVER); work = (struct k_work_delayable *)zbus_chan_user_data(&periodic_event_1s_chan); k_work_init_delayable(work, handle_mid_timeout); diff --git a/app/src/main.c b/app/src/main.c index bf9c4513..7b7740b7 100644 --- a/app/src/main.c +++ b/app/src/main.c @@ -574,6 +574,7 @@ static void on_zbus_ble_data_callback(const struct zbus_channel *chan) switch (event->data.type) { case BLE_COMM_DATA_TYPE_SET_TIME: { if (event->data.data.time.seconds > 0) { + // TODO: Replace with set time struct timespec tspec; tspec.tv_sec = event->data.data.time.seconds; tspec.tv_nsec = 0; diff --git a/app/src/zsw_clock.c b/app/src/zsw_clock.c index ce187f74..fc46a423 100644 --- a/app/src/zsw_clock.c +++ b/app/src/zsw_clock.c @@ -15,22 +15,19 @@ * along with this program. If not, see . */ -#include -#include -#include -#include #include #include #include +#include +#include + #include #include -#include #include #include -#include -#include -#include +#include "zsw_clock.h" +#include "zsw_retained_ram_storage.h" #include "events/zsw_periodic_event.h" static void zbus_periodic_slow_callback(const struct zbus_channel *chan); @@ -38,6 +35,16 @@ static void zbus_periodic_slow_callback(const struct zbus_channel *chan); ZBUS_CHAN_DECLARE(periodic_event_1s_chan); ZBUS_LISTENER_DEFINE(zsw_clock_lis, zbus_periodic_slow_callback); +void zsw_clock_set_time(zsw_timeval_t *ztm) +{ + struct timespec tspec; + + tspec.tv_sec = ztm->tm.tm_sec; + tspec.tv_nsec = 0; + + clock_settime(CLOCK_REALTIME, &tspec); +} + void zsw_clock_get_time(zsw_timeval_t *ztm) { struct timeval tv; @@ -49,10 +56,8 @@ void zsw_clock_get_time(zsw_timeval_t *ztm) // Add one to the month because we want to count from December instead of January ztm->tm.tm_mon += 1; - // Add 1900 to the year because we want to count from 0 + // Add 1900 to the year because we want to count from 1900 ztm->tm.tm_year += 1900; - - ztm->tv_usec = tv.tv_usec; } time_t zsw_clock_get_time_unix(void) @@ -72,8 +77,10 @@ static void zbus_periodic_slow_callback(const struct zbus_channel *chan) static int zsw_clock_init(void) { struct timespec tspec; + tspec.tv_sec = retained.current_time_seconds; tspec.tv_nsec = 0; + clock_settime(CLOCK_REALTIME, &tspec); if (strlen(retained.timezone) > 0) { setenv("TZ", retained.timezone, 1); diff --git a/app/src/zsw_clock.h b/app/src/zsw_clock.h index 5290ceb9..cbfd188d 100644 --- a/app/src/zsw_clock.h +++ b/app/src/zsw_clock.h @@ -20,10 +20,17 @@ #include #include +typedef void (*zsw_clock_on_update)(void); + typedef struct { struct tm tm; /**< Modified time object with 1900 added to the year and the month increased by one. */ uint32_t tv_usec; } zsw_timeval_t; +void zsw_clock_add_update(zsw_clock_on_update callback); + +void zsw_clock_set_time(zsw_timeval_t *ztm); + void zsw_clock_get_time(zsw_timeval_t *ztm); + time_t zsw_clock_get_time_unix(void); \ No newline at end of file diff --git a/app/src/zsw_rtc.c b/app/src/zsw_rtc.c new file mode 100644 index 00000000..33977beb --- /dev/null +++ b/app/src/zsw_rtc.c @@ -0,0 +1,98 @@ +/* + * This file is part of ZSWatch project . + * Copyright (c) 2023 Jakob Krantz. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "zsw_clock.h" +#include "events/zsw_periodic_event.h" + +const struct device *const rtc = DEVICE_DT_GET(DT_NODELABEL(rv_8263_c8)); + +LOG_MODULE_REGISTER(zsw_clock, LOG_LEVEL_DBG); + +void zsw_clock_add_update(zsw_clock_on_update callback) +{ + +} + +void zsw_clock_set_time(zsw_timeval_t *ztm) +{ + struct rtc_time time; + + time.tm_sec = ztm->tm.tm_sec; + time.tm_min = ztm->tm.tm_min; + time.tm_hour = ztm->tm.tm_hour; + time.tm_wday = ztm->tm.tm_wday; + time.tm_mday = ztm->tm.tm_mday; + time.tm_mon = ztm->tm.tm_mon - 1; + time.tm_year = ztm->tm.tm_year - 1900; + + //rtc_set_time(rtc, &time); +} + +void zsw_clock_get_time(zsw_timeval_t *ztm) +{ + struct rtc_time time; + + //rtc_get_time(rtc, &time); + + ztm->tm.tm_sec = time.tm_sec; + ztm->tm.tm_min = time.tm_min; + ztm->tm.tm_hour = time.tm_hour; + ztm->tm.tm_wday = time.tm_wday; + ztm->tm.tm_mday = time.tm_mday; + + // Add one to the month because we want to count from December instead of January + ztm->tm.tm_mon = time.tm_mon + 1; + + // Add 1900 to the year because we want to count from 0 + ztm->tm.tm_year = time.tm_year + 1900; +} + +time_t zsw_clock_get_time_unix(void) +{ + // TODO: + return 0; +} + +void on_rtc_update(const struct device *dev, void *user_data) +{ +} + +static int zsw_clock_init(void) +{ + if (!device_is_ready(rtc)) { + LOG_ERR("Device not ready!"); + return -1; + } + + rtc_update_set_callback(rtc, on_rtc_update, NULL); + + return 0; +} + +SYS_INIT(zsw_clock_init, APPLICATION, 2); \ No newline at end of file