| /* |
| * Copyright (c) 2023 Intel Corporation. |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define DT_DRV_COMPAT motorola_mc146818 |
| |
| #include <errno.h> |
| #include <zephyr/device.h> |
| #include <zephyr/kernel.h> |
| #include <zephyr/init.h> |
| #include <zephyr/sys/util.h> |
| #include <zephyr/spinlock.h> |
| #include <zephyr/devicetree.h> |
| #include <zephyr/drivers/rtc.h> |
| #include <zephyr/sys/sys_io.h> |
| |
| #define RTC_STD_INDEX (DT_INST_REG_ADDR_BY_IDX(0, 0)) |
| #define RTC_STD_TARGET (DT_INST_REG_ADDR_BY_IDX(0, 1)) |
| |
| /* Time indices in RTC RAM */ |
| #define RTC_SEC 0x00 |
| #define RTC_MIN 0x02 |
| #define RTC_HOUR 0x04 |
| |
| /* Day of week index in RTC RAM */ |
| #define RTC_WDAY 0x06 |
| |
| /* Day of month index in RTC RAM */ |
| #define RTC_MDAY 0x07 |
| |
| /* Month and year index in RTC RAM */ |
| #define RTC_MONTH 0x08 |
| #define RTC_YEAR 0x09 |
| |
| /* Y2K Bugfix */ |
| #define RTC_CENTURY 0x32 |
| |
| /* Alarm time indices in RTC RAM */ |
| #define RTC_ALARM_SEC 0x01 |
| #define RTC_ALARM_MIN 0x03 |
| #define RTC_ALARM_HOUR 0x05 |
| |
| /* Registers A-D indeces in RTC RAM */ |
| #define RTC_REG_A 0x0A |
| #define RTC_REG_B 0x0B |
| #define RTC_REG_C 0x0C |
| #define RTC_REG_D 0x0D |
| |
| #define RTC_UIP RTC_REG_A |
| #define RTC_DATA RTC_REG_B |
| #define RTC_FLAG RTC_REG_C |
| |
| /* Alarm don't case state */ |
| #define RTC_ALARM_DC 0xFF |
| |
| /* Update In Progress bit in REG_A */ |
| #define RTC_UIP_BIT BIT(7) |
| |
| /* Update Cycle Inhibit bit in REG_B */ |
| #define RTC_UCI_BIT BIT(7) |
| |
| /* Periodic Interrupt Enable bit in REG_B */ |
| #define RTC_PIE_BIT BIT(6) |
| |
| /* Alarm Interrupt Enable bit in REG_B */ |
| #define RTC_AIE_BIT BIT(5) |
| |
| /* Update-ended Interrupt Enable bit in REG_B */ |
| #define RTC_UIE_BIT BIT(4) |
| |
| /* Data mode bit in REG_B */ |
| #define RTC_DMODE_BIT BIT(2) |
| |
| /* Hour Format bit in REG_B */ |
| #define RTC_HFORMAT_BIT BIT(1) |
| |
| /* Daylight Savings Enable Format bit in REG_B */ |
| #define RTC_DSE_BIT BIT(0) |
| |
| /* Interrupt Request Flag bit in REG_C */ |
| #define RTC_IRF_BIT BIT(7) |
| |
| /* Periodic Flag bit in REG_C */ |
| #define RTC_PF_BIT BIT(6) |
| |
| /* Alarm Flag bit in REG_C */ |
| #define RTC_AF_BIT BIT(5) |
| |
| /* Update-end Flag bit in REG_C */ |
| #define RTC_UEF_BIT BIT(4) |
| |
| /* VRT bit in REG_D */ |
| #define RTC_VRT_BIT BIT(7) |
| |
| /* Month day Alarm bits in REG_D */ |
| #define RTC_MDAY_ALARM BIT_MASK(5) |
| |
| /* Maximum and Minimum values of time */ |
| #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 */ |
| |
| /* Input clock frequency mapped to divider bits */ |
| #define RTC_IN_CLK_DIV_BITS_4194304 (0) |
| #define RTC_IN_CLK_DIV_BITS_1048576 (1 << 4) |
| #define RTC_IN_CLK_DIV_BITS_32768 (2 << 4) |
| |
| struct rtc_mc146818_data { |
| struct k_spinlock lock; |
| bool alarm_pending; |
| rtc_alarm_callback cb; |
| void *cb_data; |
| rtc_update_callback update_cb; |
| void *update_cb_data; |
| }; |
| |
| static uint8_t rtc_read(int reg) |
| { |
| uint8_t value; |
| |
| sys_out8(reg, RTC_STD_INDEX); |
| value = sys_in8(RTC_STD_TARGET); |
| |
| return value; |
| } |
| |
| static void rtc_write(int reg, uint8_t value) |
| { |
| sys_out8(reg, RTC_STD_INDEX); |
| sys_out8(value, RTC_STD_TARGET); |
| } |
| |
| static bool rtc_mc146818_validate_time(const struct rtc_time *timeptr) |
| { |
| if (timeptr->tm_sec < MIN_SEC || timeptr->tm_sec > MAX_SEC) { |
| return false; |
| } |
| if (timeptr->tm_min < MIN_MIN || timeptr->tm_min > MAX_MIN) { |
| return false; |
| } |
| if (timeptr->tm_hour < MIN_HOUR || timeptr->tm_hour > MAX_HOUR) { |
| return false; |
| } |
| if (timeptr->tm_wday + 1 < MIN_WDAY || timeptr->tm_wday + 1 > MAX_WDAY) { |
| return false; |
| } |
| if (timeptr->tm_mday < MIN_MDAY || timeptr->tm_mday > MAX_MDAY) { |
| return false; |
| } |
| if (timeptr->tm_mon + 1 < MIN_MON || timeptr->tm_mon + 1 > MAX_MON) { |
| return false; |
| } |
| if (timeptr->tm_year - 70 < MIN_YEAR_DIFF || timeptr->tm_year - 70 > MAX_YEAR_DIFF) { |
| return false; |
| } |
| return true; |
| } |
| |
| static int rtc_mc146818_set_time(const struct device *dev, const struct rtc_time *timeptr) |
| { |
| struct rtc_mc146818_data * const dev_data = dev->data; |
| uint8_t value; |
| int year; |
| int cent; |
| int ret; |
| |
| k_spinlock_key_t key = k_spin_lock(&dev_data->lock); |
| |
| if (timeptr == NULL) { |
| ret = -EINVAL; |
| goto out; |
| } |
| |
| /* Check time valid */ |
| if (!rtc_mc146818_validate_time(timeptr)) { |
| ret = -EINVAL; |
| goto out; |
| } |
| |
| value = rtc_read(RTC_DATA); |
| rtc_write(RTC_DATA, value | RTC_UCI_BIT); |
| |
| year = (1900 + timeptr->tm_year) % 100; |
| cent = (1900 + timeptr->tm_year) / 100; |
| |
| rtc_write(RTC_SEC, (uint8_t)timeptr->tm_sec); |
| rtc_write(RTC_MIN, (uint8_t)timeptr->tm_min); |
| rtc_write(RTC_HOUR, (uint8_t)timeptr->tm_hour); |
| rtc_write(RTC_WDAY, (uint8_t)timeptr->tm_wday); |
| rtc_write(RTC_MDAY, (uint8_t)timeptr->tm_mday); |
| rtc_write(RTC_MONTH, (uint8_t)timeptr->tm_mon + 1); |
| rtc_write(RTC_YEAR, year); |
| rtc_write(RTC_CENTURY, cent); |
| |
| value &= (~RTC_UCI_BIT); |
| rtc_write(RTC_DATA, value); |
| ret = 0; |
| out: |
| k_spin_unlock(&dev_data->lock, key); |
| return ret; |
| } |
| |
| static int rtc_mc146818_get_time(const struct device *dev, struct rtc_time *timeptr) |
| { |
| struct rtc_mc146818_data * const dev_data = dev->data; |
| int ret; |
| uint8_t cent; |
| uint8_t year; |
| uint8_t value; |
| |
| k_spinlock_key_t key = k_spin_lock(&dev_data->lock); |
| |
| /* Validate arguments */ |
| if (timeptr == NULL) { |
| ret = -EINVAL; |
| goto out; |
| } |
| |
| if (!(rtc_read(RTC_REG_D) & RTC_VRT_BIT)) { |
| ret = -ENODATA; |
| goto out; |
| } |
| |
| while (rtc_read(RTC_UIP) & RTC_UIP_BIT) { |
| continue; |
| } |
| |
| cent = rtc_read(RTC_CENTURY); |
| year = rtc_read(RTC_YEAR); |
| timeptr->tm_mon = rtc_read(RTC_MONTH) - 1; |
| timeptr->tm_mday = rtc_read(RTC_MDAY); |
| timeptr->tm_wday = rtc_read(RTC_WDAY) - 1; |
| timeptr->tm_hour = rtc_read(RTC_HOUR); |
| timeptr->tm_min = rtc_read(RTC_MIN); |
| timeptr->tm_sec = rtc_read(RTC_SEC); |
| |
| timeptr->tm_year = 100 * (int)cent + year - 1900; |
| |
| timeptr->tm_nsec = 0; |
| timeptr->tm_yday = 0; |
| value = rtc_read(RTC_DATA); |
| |
| /* Check time valid */ |
| if (!rtc_mc146818_validate_time(timeptr)) { |
| ret = -ENODATA; |
| goto out; |
| } |
| ret = 0; |
| out: |
| k_spin_unlock(&dev_data->lock, key); |
| return ret; |
| } |
| |
| #if defined(CONFIG_RTC_ALARM) |
| static bool rtc_mc146818_validate_alarm(const struct rtc_time *timeptr, uint32_t mask) |
| { |
| if ((mask & RTC_ALARM_TIME_MASK_SECOND) && |
| (timeptr->tm_sec < MIN_SEC || timeptr->tm_sec > MAX_SEC)) { |
| return false; |
| } |
| |
| if ((mask & RTC_ALARM_TIME_MASK_MINUTE) && |
| (timeptr->tm_min < MIN_MIN || timeptr->tm_min > MAX_MIN)) { |
| return false; |
| } |
| |
| if ((mask & RTC_ALARM_TIME_MASK_HOUR) && |
| (timeptr->tm_hour < MIN_HOUR || timeptr->tm_hour > MAX_HOUR)) { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| static int rtc_mc146818_alarm_get_supported_fields(const struct device *dev, uint16_t id, |
| uint16_t *mask) |
| { |
| ARG_UNUSED(dev); |
| |
| if (id != 0) { |
| return -EINVAL; |
| } |
| |
| (*mask) = (RTC_ALARM_TIME_MASK_SECOND |
| | RTC_ALARM_TIME_MASK_MINUTE |
| | RTC_ALARM_TIME_MASK_HOUR); |
| |
| return 0; |
| } |
| |
| static int rtc_mc146818_alarm_set_time(const struct device *dev, uint16_t id, uint16_t mask, |
| const struct rtc_time *timeptr) |
| { |
| struct rtc_mc146818_data * const dev_data = dev->data; |
| int ret; |
| |
| k_spinlock_key_t key = k_spin_lock(&dev_data->lock); |
| |
| if (id != 0) { |
| ret = -EINVAL; |
| goto out; |
| } |
| |
| if ((mask > 0) && (timeptr == NULL)) { |
| ret = -EINVAL; |
| goto out; |
| } |
| |
| /* Check time valid */ |
| if (!rtc_mc146818_validate_alarm(timeptr, mask)) { |
| ret = -EINVAL; |
| goto out; |
| } |
| |
| if (mask & RTC_ALARM_TIME_MASK_SECOND) { |
| rtc_write(RTC_ALARM_SEC, timeptr->tm_sec); |
| } else { |
| rtc_write(RTC_ALARM_SEC, RTC_ALARM_DC); |
| } |
| |
| if (mask & RTC_ALARM_TIME_MASK_MINUTE) { |
| rtc_write(RTC_ALARM_MIN, timeptr->tm_min); |
| } else { |
| rtc_write(RTC_ALARM_SEC, RTC_ALARM_DC); |
| } |
| if (mask & RTC_ALARM_TIME_MASK_HOUR) { |
| rtc_write(RTC_ALARM_HOUR, timeptr->tm_hour); |
| } else { |
| rtc_write(RTC_ALARM_SEC, RTC_ALARM_DC); |
| } |
| |
| rtc_write(RTC_DATA, rtc_read(RTC_DATA) | RTC_AIE_BIT); |
| ret = 0; |
| out: |
| k_spin_unlock(&dev_data->lock, key); |
| return ret; |
| } |
| |
| static int rtc_mc146818_alarm_get_time(const struct device *dev, uint16_t id, uint16_t *mask, |
| struct rtc_time *timeptr) |
| { |
| struct rtc_mc146818_data * const dev_data = dev->data; |
| uint8_t value; |
| int ret; |
| |
| k_spinlock_key_t key = k_spin_lock(&dev_data->lock); |
| |
| if (id != 0) { |
| ret = -EINVAL; |
| goto out; |
| } |
| |
| if (timeptr == NULL) { |
| ret = -EINVAL; |
| goto out; |
| } |
| |
| (*mask) = 0; |
| |
| value = rtc_read(RTC_ALARM_SEC); |
| if (value <= MAX_SEC) { |
| timeptr->tm_sec = value; |
| (*mask) |= RTC_ALARM_TIME_MASK_SECOND; |
| } |
| |
| value = rtc_read(RTC_ALARM_MIN); |
| if (value <= MAX_MIN) { |
| timeptr->tm_min = value; |
| (*mask) |= RTC_ALARM_TIME_MASK_MINUTE; |
| } |
| |
| value = rtc_read(RTC_ALARM_HOUR); |
| if (value <= MAX_HOUR) { |
| timeptr->tm_hour = value; |
| (*mask) |= RTC_ALARM_TIME_MASK_HOUR; |
| } |
| |
| ret = 0; |
| out: |
| k_spin_unlock(&dev_data->lock, key); |
| return ret; |
| } |
| |
| static int rtc_mc146818_alarm_set_callback(const struct device *dev, uint16_t id, |
| rtc_alarm_callback callback, void *user_data) |
| { |
| struct rtc_mc146818_data * const dev_data = dev->data; |
| |
| if (id != 0) { |
| return -EINVAL; |
| } |
| |
| k_spinlock_key_t key = k_spin_lock(&dev_data->lock); |
| |
| dev_data->cb = callback; |
| dev_data->cb_data = user_data; |
| |
| if (callback != NULL) { |
| /* Enable Alarm callback */ |
| rtc_write(RTC_DATA, (rtc_read(RTC_DATA) | RTC_AIE_BIT)); |
| } else { |
| /* Disable Alarm callback */ |
| rtc_write(RTC_DATA, (rtc_read(RTC_DATA) & (~RTC_AIE_BIT))); |
| } |
| |
| k_spin_unlock(&dev_data->lock, key); |
| return 0; |
| } |
| |
| static int rtc_mc146818_alarm_is_pending(const struct device *dev, uint16_t id) |
| { |
| struct rtc_mc146818_data * const dev_data = dev->data; |
| int ret; |
| |
| if (id != 0) { |
| return -EINVAL; |
| } |
| |
| k_spinlock_key_t key = k_spin_lock(&dev_data->lock); |
| |
| ret = dev_data->alarm_pending ? 1 : 0; |
| dev_data->alarm_pending = false; |
| |
| k_spin_unlock(&dev_data->lock, key); |
| return ret; |
| } |
| #endif /* CONFIG_RTC_ALARM */ |
| |
| #if defined(CONFIG_RTC_UPDATE) |
| static int rtc_mc146818_update_set_callback(const struct device *dev, |
| rtc_update_callback callback, void *user_data) |
| { |
| struct rtc_mc146818_data * const dev_data = dev->data; |
| |
| k_spinlock_key_t key = k_spin_lock(&dev_data->lock); |
| |
| dev_data->update_cb = callback; |
| dev_data->update_cb_data = user_data; |
| |
| if (callback != NULL) { |
| /* Enable update callback */ |
| rtc_write(RTC_DATA, (rtc_read(RTC_DATA) | RTC_UIE_BIT)); |
| } else { |
| /* Disable update callback */ |
| rtc_write(RTC_DATA, (rtc_read(RTC_DATA) & (~RTC_UIE_BIT))); |
| } |
| |
| |
| k_spin_unlock(&dev_data->lock, key); |
| return 0; |
| } |
| |
| #endif /* CONFIG_RTC_UPDATE */ |
| |
| static void rtc_mc146818_isr(const struct device *dev) |
| { |
| struct rtc_mc146818_data * const dev_data = dev->data; |
| uint8_t regc; |
| |
| ARG_UNUSED(dev_data); |
| |
| /* Read register, which clears the register */ |
| regc = rtc_read(RTC_FLAG); |
| |
| #if defined(CONFIG_RTC_ALARM) |
| if (regc & RTC_AF_BIT) { |
| if (dev_data->cb) { |
| dev_data->cb(dev, 0, dev_data->cb_data); |
| dev_data->alarm_pending = false; |
| } else { |
| dev_data->alarm_pending = true; |
| } |
| } |
| #endif |
| |
| #if defined(CONFIG_RTC_UPDATE) |
| if (regc & RTC_UEF_BIT) { |
| if (dev_data->update_cb) { |
| dev_data->update_cb(dev, dev_data->update_cb_data); |
| } |
| } |
| #endif |
| } |
| |
| static const struct rtc_driver_api rtc_mc146818_driver_api = { |
| .set_time = rtc_mc146818_set_time, |
| .get_time = rtc_mc146818_get_time, |
| #if defined(CONFIG_RTC_ALARM) |
| .alarm_get_supported_fields = rtc_mc146818_alarm_get_supported_fields, |
| .alarm_set_time = rtc_mc146818_alarm_set_time, |
| .alarm_get_time = rtc_mc146818_alarm_get_time, |
| .alarm_is_pending = rtc_mc146818_alarm_is_pending, |
| .alarm_set_callback = rtc_mc146818_alarm_set_callback, |
| #endif /* CONFIG_RTC_ALARM */ |
| |
| #if defined(CONFIG_RTC_UPDATE) |
| .update_set_callback = rtc_mc146818_update_set_callback, |
| #endif /* CONFIG_RTC_UPDATE */ |
| }; |
| |
| #define RTC_MC146818_INIT_FN_DEFINE(n) \ |
| static int rtc_mc146818_init##n(const struct device *dev) \ |
| { \ |
| rtc_write(RTC_REG_A, \ |
| _CONCAT(RTC_IN_CLK_DIV_BITS_, \ |
| DT_INST_PROP(n, clock_frequency))); \ |
| \ |
| rtc_write(RTC_REG_B, RTC_DMODE_BIT | RTC_HFORMAT_BIT); \ |
| \ |
| IRQ_CONNECT(DT_INST_IRQN(0), \ |
| DT_INST_IRQ(0, priority), \ |
| rtc_mc146818_isr, DEVICE_DT_INST_GET(n), \ |
| DT_INST_IRQ(0, sense)); \ |
| \ |
| irq_enable(DT_INST_IRQN(0)); \ |
| \ |
| return 0; \ |
| } |
| |
| #define RTC_MC146818_DEV_CFG(inst) \ |
| struct rtc_mc146818_data rtc_mc146818_data##inst; \ |
| \ |
| RTC_MC146818_INIT_FN_DEFINE(inst) \ |
| \ |
| DEVICE_DT_INST_DEFINE(inst, &rtc_mc146818_init##inst, NULL, \ |
| &rtc_mc146818_data##inst, NULL, POST_KERNEL, \ |
| CONFIG_RTC_INIT_PRIORITY, \ |
| &rtc_mc146818_driver_api); \ |
| |
| DT_INST_FOREACH_STATUS_OKAY(RTC_MC146818_DEV_CFG) |