| /* |
| * Copyright (c) 2019-2020 Peter Bigot Consulting, LLC |
| * Copyright (c) 2021 Laird Connectivity |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define DT_DRV_COMPAT microchip_mcp7940n |
| |
| #include <zephyr/device.h> |
| #include <zephyr/drivers/counter.h> |
| #include <zephyr/drivers/gpio.h> |
| #include <zephyr/drivers/i2c.h> |
| #include <zephyr/drivers/rtc/mcp7940n.h> |
| #include <zephyr/kernel.h> |
| #include <zephyr/logging/log.h> |
| #include <zephyr/sys/timeutil.h> |
| #include <zephyr/sys/util.h> |
| #include <time.h> |
| |
| LOG_MODULE_REGISTER(MCP7940N, CONFIG_COUNTER_LOG_LEVEL); |
| |
| /* Alarm channels */ |
| #define ALARM0_ID 0 |
| #define ALARM1_ID 1 |
| |
| /* Size of block when writing whole struct */ |
| #define RTC_TIME_REGISTERS_SIZE sizeof(struct mcp7940n_time_registers) |
| #define RTC_ALARM_REGISTERS_SIZE sizeof(struct mcp7940n_alarm_registers) |
| |
| /* Largest block size */ |
| #define MAX_WRITE_SIZE (RTC_TIME_REGISTERS_SIZE) |
| |
| /* tm struct uses years since 1900 but unix time uses years since |
| * 1970. MCP7940N default year is '1' so the offset is 69 |
| */ |
| #define UNIX_YEAR_OFFSET 69 |
| |
| /* Macro used to decode BCD to UNIX time to avoid potential copy and paste |
| * errors. |
| */ |
| #define RTC_BCD_DECODE(reg_prefix) (reg_prefix##_one + reg_prefix##_ten * 10) |
| |
| struct mcp7940n_config { |
| struct counter_config_info generic; |
| struct i2c_dt_spec i2c; |
| const struct gpio_dt_spec int_gpios; |
| }; |
| |
| struct mcp7940n_data { |
| const struct device *mcp7940n; |
| struct k_sem lock; |
| struct mcp7940n_time_registers registers; |
| struct mcp7940n_alarm_registers alm0_registers; |
| struct mcp7940n_alarm_registers alm1_registers; |
| |
| struct k_work alarm_work; |
| struct gpio_callback int_callback; |
| |
| counter_alarm_callback_t counter_handler[2]; |
| uint32_t counter_ticks[2]; |
| void *alarm_user_data[2]; |
| |
| bool int_active_high; |
| }; |
| |
| /** @brief Convert bcd time in device registers to UNIX time |
| * |
| * @param dev the MCP7940N device pointer. |
| * |
| * @retval returns unix time. |
| */ |
| static time_t decode_rtc(const struct device *dev) |
| { |
| struct mcp7940n_data *data = dev->data; |
| time_t time_unix = 0; |
| struct tm time = { 0 }; |
| |
| time.tm_sec = RTC_BCD_DECODE(data->registers.rtc_sec.sec); |
| time.tm_min = RTC_BCD_DECODE(data->registers.rtc_min.min); |
| time.tm_hour = RTC_BCD_DECODE(data->registers.rtc_hours.hr); |
| time.tm_mday = RTC_BCD_DECODE(data->registers.rtc_date.date); |
| time.tm_wday = data->registers.rtc_weekday.weekday; |
| /* tm struct starts months at 0, mcp7940n starts at 1 */ |
| time.tm_mon = RTC_BCD_DECODE(data->registers.rtc_month.month) - 1; |
| /* tm struct uses years since 1900 but unix time uses years since 1970 */ |
| time.tm_year = RTC_BCD_DECODE(data->registers.rtc_year.year) + |
| UNIX_YEAR_OFFSET; |
| |
| time_unix = timeutil_timegm(&time); |
| |
| LOG_DBG("Unix time is %d\n", (uint32_t)time_unix); |
| |
| return time_unix; |
| } |
| |
| /** @brief Encode time struct tm into mcp7940n rtc registers |
| * |
| * @param dev the MCP7940N device pointer. |
| * @param time_buffer tm struct containing time to be encoded into mcp7940n |
| * registers. |
| * |
| * @retval return 0 on success, or a negative error code from invalid |
| * parameter. |
| */ |
| static int encode_rtc(const struct device *dev, struct tm *time_buffer) |
| { |
| struct mcp7940n_data *data = dev->data; |
| uint8_t month; |
| uint8_t year_since_epoch; |
| |
| /* In a tm struct, months start at 0, mcp7940n starts with 1 */ |
| month = time_buffer->tm_mon + 1; |
| |
| if (time_buffer->tm_year < UNIX_YEAR_OFFSET) { |
| return -EINVAL; |
| } |
| year_since_epoch = time_buffer->tm_year - UNIX_YEAR_OFFSET; |
| |
| /* Set external oscillator configuration bit */ |
| data->registers.rtc_sec.start_osc = 1; |
| |
| data->registers.rtc_sec.sec_one = time_buffer->tm_sec % 10; |
| data->registers.rtc_sec.sec_ten = time_buffer->tm_sec / 10; |
| data->registers.rtc_min.min_one = time_buffer->tm_min % 10; |
| data->registers.rtc_min.min_ten = time_buffer->tm_min / 10; |
| data->registers.rtc_hours.hr_one = time_buffer->tm_hour % 10; |
| data->registers.rtc_hours.hr_ten = time_buffer->tm_hour / 10; |
| data->registers.rtc_weekday.weekday = time_buffer->tm_wday; |
| data->registers.rtc_date.date_one = time_buffer->tm_mday % 10; |
| data->registers.rtc_date.date_ten = time_buffer->tm_mday / 10; |
| data->registers.rtc_month.month_one = month % 10; |
| data->registers.rtc_month.month_ten = month / 10; |
| data->registers.rtc_year.year_one = year_since_epoch % 10; |
| data->registers.rtc_year.year_ten = year_since_epoch / 10; |
| |
| return 0; |
| } |
| |
| /** @brief Encode time struct tm into mcp7940n alarm registers |
| * |
| * @param dev the MCP7940N device pointer. |
| * @param time_buffer tm struct containing time to be encoded into mcp7940n |
| * registers. |
| * @param alarm_id alarm ID, can be 0 or 1 for MCP7940N. |
| * |
| * @retval return 0 on success, or a negative error code from invalid |
| * parameter. |
| */ |
| static int encode_alarm(const struct device *dev, struct tm *time_buffer, uint8_t alarm_id) |
| { |
| struct mcp7940n_data *data = dev->data; |
| uint8_t month; |
| struct mcp7940n_alarm_registers *alm_regs; |
| |
| if (alarm_id == ALARM0_ID) { |
| alm_regs = &data->alm0_registers; |
| } else if (alarm_id == ALARM1_ID) { |
| alm_regs = &data->alm1_registers; |
| } else { |
| return -EINVAL; |
| } |
| /* In a tm struct, months start at 0 */ |
| month = time_buffer->tm_mon + 1; |
| |
| alm_regs->alm_sec.sec_one = time_buffer->tm_sec % 10; |
| alm_regs->alm_sec.sec_ten = time_buffer->tm_sec / 10; |
| alm_regs->alm_min.min_one = time_buffer->tm_min % 10; |
| alm_regs->alm_min.min_ten = time_buffer->tm_min / 10; |
| alm_regs->alm_hours.hr_one = time_buffer->tm_hour % 10; |
| alm_regs->alm_hours.hr_ten = time_buffer->tm_hour / 10; |
| alm_regs->alm_weekday.weekday = time_buffer->tm_wday; |
| alm_regs->alm_date.date_one = time_buffer->tm_mday % 10; |
| alm_regs->alm_date.date_ten = time_buffer->tm_mday / 10; |
| alm_regs->alm_month.month_one = month % 10; |
| alm_regs->alm_month.month_ten = month / 10; |
| |
| return 0; |
| } |
| |
| /** @brief Reads single register from MCP7940N |
| * |
| * @param dev the MCP7940N device pointer. |
| * @param addr register address. |
| * @param val pointer to uint8_t that will contain register value if |
| * successful. |
| * |
| * @retval return 0 on success, or a negative error code from an I2C |
| * transaction. |
| */ |
| static int read_register(const struct device *dev, uint8_t addr, uint8_t *val) |
| { |
| const struct mcp7940n_config *cfg = dev->config; |
| |
| int rc = i2c_write_read_dt(&cfg->i2c, &addr, sizeof(addr), val, 1); |
| |
| return rc; |
| } |
| |
| /** @brief Read registers from device and populate mcp7940n_registers struct |
| * |
| * @param dev the MCP7940N device pointer. |
| * @param unix_time pointer to time_t value that will contain unix time if |
| * successful. |
| * |
| * @retval return 0 on success, or a negative error code from an I2C |
| * transaction. |
| */ |
| static int read_time(const struct device *dev, time_t *unix_time) |
| { |
| struct mcp7940n_data *data = dev->data; |
| const struct mcp7940n_config *cfg = dev->config; |
| uint8_t addr = REG_RTC_SEC; |
| |
| int rc = i2c_write_read_dt(&cfg->i2c, &addr, sizeof(addr), &data->registers, |
| RTC_TIME_REGISTERS_SIZE); |
| |
| if (rc >= 0) { |
| *unix_time = decode_rtc(dev); |
| } |
| |
| return rc; |
| } |
| |
| /** @brief Write a single register to MCP7940N |
| * |
| * @param dev the MCP7940N device pointer. |
| * @param addr register address. |
| * @param value Value that will be written to the register. |
| * |
| * @retval return 0 on success, or a negative error code from an I2C |
| * transaction or invalid parameter. |
| */ |
| static int write_register(const struct device *dev, enum mcp7940n_register addr, uint8_t value) |
| { |
| const struct mcp7940n_config *cfg = dev->config; |
| int rc = 0; |
| |
| uint8_t time_data[2] = {addr, value}; |
| |
| rc = i2c_write_dt(&cfg->i2c, time_data, sizeof(time_data)); |
| |
| return rc; |
| } |
| |
| /** @brief Write a full time struct to MCP7940N registers. |
| * |
| * @param dev the MCP7940N device pointer. |
| * @param addr first register address to write to, should be REG_RTC_SEC, |
| * REG_ALM0_SEC or REG_ALM0_SEC. |
| * @param size size of data struct that will be written. |
| * |
| * @retval return 0 on success, or a negative error code from an I2C |
| * transaction or invalid parameter. |
| */ |
| static int write_data_block(const struct device *dev, enum mcp7940n_register addr, uint8_t size) |
| { |
| struct mcp7940n_data *data = dev->data; |
| const struct mcp7940n_config *cfg = dev->config; |
| int rc = 0; |
| uint8_t time_data[MAX_WRITE_SIZE + 1]; |
| uint8_t *write_block_start; |
| |
| if (size > MAX_WRITE_SIZE) { |
| return -EINVAL; |
| } |
| |
| if (addr >= REG_INVAL) { |
| return -EINVAL; |
| } |
| |
| if (addr == REG_RTC_SEC) { |
| write_block_start = (uint8_t *)&data->registers; |
| } else if (addr == REG_ALM0_SEC) { |
| write_block_start = (uint8_t *)&data->alm0_registers; |
| } else if (addr == REG_ALM1_SEC) { |
| write_block_start = (uint8_t *)&data->alm1_registers; |
| } else { |
| return -EINVAL; |
| } |
| |
| /* Load register address into first byte then fill in data values */ |
| time_data[0] = addr; |
| memcpy(&time_data[1], write_block_start, size); |
| |
| rc = i2c_write_dt(&cfg->i2c, time_data, size + 1); |
| |
| return rc; |
| } |
| |
| /** @brief Sets the correct weekday. |
| * |
| * If the time is never set then the device defaults to 1st January 1970 |
| * but with the wrong weekday set. This function ensures the weekday is |
| * correct in this case. |
| * |
| * @param dev the MCP7940N device pointer. |
| * @param unix_time pointer to unix time that will be used to work out the weekday |
| * |
| * @retval return 0 on success, or a negative error code from an I2C |
| * transaction or invalid parameter. |
| */ |
| static int set_day_of_week(const struct device *dev, time_t *unix_time) |
| { |
| struct mcp7940n_data *data = dev->data; |
| struct tm time_buffer = { 0 }; |
| int rc = 0; |
| |
| if (gmtime_r(unix_time, &time_buffer) != NULL) { |
| data->registers.rtc_weekday.weekday = time_buffer.tm_wday; |
| rc = write_register(dev, REG_RTC_WDAY, |
| *((uint8_t *)(&data->registers.rtc_weekday))); |
| } else { |
| rc = -EINVAL; |
| } |
| |
| return rc; |
| } |
| |
| /** @brief Checks the interrupt pending flag (IF) of a given alarm. |
| * |
| * A callback is fired if an IRQ is pending. |
| * |
| * @param dev the MCP7940N device pointer. |
| * @param alarm_id ID of alarm, can be 0 or 1 for MCP7940N. |
| */ |
| static void mcp7940n_handle_interrupt(const struct device *dev, uint8_t alarm_id) |
| { |
| struct mcp7940n_data *data = dev->data; |
| uint8_t alarm_reg_address; |
| struct mcp7940n_alarm_registers *alm_regs; |
| counter_alarm_callback_t cb; |
| uint32_t ticks = 0; |
| bool fire_callback = false; |
| |
| if (alarm_id == ALARM0_ID) { |
| alarm_reg_address = REG_ALM0_WDAY; |
| alm_regs = &data->alm0_registers; |
| } else if (alarm_id == ALARM1_ID) { |
| alarm_reg_address = REG_ALM1_WDAY; |
| alm_regs = &data->alm1_registers; |
| } else { |
| return; |
| } |
| |
| k_sem_take(&data->lock, K_FOREVER); |
| |
| /* Check if this alarm has a pending interrupt */ |
| read_register(dev, alarm_reg_address, (uint8_t *)&alm_regs->alm_weekday); |
| |
| if (alm_regs->alm_weekday.alm_if) { |
| /* Clear interrupt */ |
| alm_regs->alm_weekday.alm_if = 0; |
| write_register(dev, alarm_reg_address, |
| *((uint8_t *)(&alm_regs->alm_weekday))); |
| |
| /* Fire callback */ |
| if (data->counter_handler[alarm_id]) { |
| cb = data->counter_handler[alarm_id]; |
| ticks = data->counter_ticks[alarm_id]; |
| fire_callback = true; |
| } |
| } |
| |
| k_sem_give(&data->lock); |
| |
| if (fire_callback) { |
| cb(data->mcp7940n, 0, ticks, data->alarm_user_data[alarm_id]); |
| } |
| } |
| |
| static void mcp7940n_work_handler(struct k_work *work) |
| { |
| struct mcp7940n_data *data = |
| CONTAINER_OF(work, struct mcp7940n_data, alarm_work); |
| |
| /* Check interrupt flags for both alarms */ |
| mcp7940n_handle_interrupt(data->mcp7940n, ALARM0_ID); |
| mcp7940n_handle_interrupt(data->mcp7940n, ALARM1_ID); |
| } |
| |
| static void mcp7940n_init_cb(const struct device *dev, |
| struct gpio_callback *gpio_cb, uint32_t pins) |
| { |
| struct mcp7940n_data *data = |
| CONTAINER_OF(gpio_cb, struct mcp7940n_data, int_callback); |
| |
| ARG_UNUSED(pins); |
| |
| k_work_submit(&data->alarm_work); |
| } |
| |
| int mcp7940n_rtc_set_time(const struct device *dev, time_t unix_time) |
| { |
| struct mcp7940n_data *data = dev->data; |
| struct tm time_buffer = { 0 }; |
| int rc = 0; |
| |
| if (unix_time > UINT32_MAX) { |
| LOG_ERR("Unix time must be 32-bit"); |
| return -EINVAL; |
| } |
| |
| k_sem_take(&data->lock, K_FOREVER); |
| |
| /* Convert unix_time to civil time */ |
| gmtime_r(&unix_time, &time_buffer); |
| LOG_DBG("Desired time is %d-%d-%d %d:%d:%d\n", (time_buffer.tm_year + 1900), |
| (time_buffer.tm_mon + 1), time_buffer.tm_mday, time_buffer.tm_hour, |
| time_buffer.tm_min, time_buffer.tm_sec); |
| |
| /* Encode time */ |
| rc = encode_rtc(dev, &time_buffer); |
| if (rc < 0) { |
| goto out; |
| } |
| |
| /* Write to device */ |
| rc = write_data_block(dev, REG_RTC_SEC, RTC_TIME_REGISTERS_SIZE); |
| |
| out: |
| k_sem_give(&data->lock); |
| |
| return rc; |
| } |
| |
| static int mcp7940n_counter_start(const struct device *dev) |
| { |
| struct mcp7940n_data *data = dev->data; |
| int rc = 0; |
| |
| k_sem_take(&data->lock, K_FOREVER); |
| |
| /* Set start oscillator configuration bit */ |
| data->registers.rtc_sec.start_osc = 1; |
| rc = write_register(dev, REG_RTC_SEC, |
| *((uint8_t *)(&data->registers.rtc_sec))); |
| |
| k_sem_give(&data->lock); |
| |
| return rc; |
| } |
| |
| static int mcp7940n_counter_stop(const struct device *dev) |
| { |
| struct mcp7940n_data *data = dev->data; |
| int rc = 0; |
| |
| k_sem_take(&data->lock, K_FOREVER); |
| |
| /* Clear start oscillator configuration bit */ |
| data->registers.rtc_sec.start_osc = 0; |
| rc = write_register(dev, REG_RTC_SEC, |
| *((uint8_t *)(&data->registers.rtc_sec))); |
| |
| k_sem_give(&data->lock); |
| |
| return rc; |
| } |
| |
| static int mcp7940n_counter_get_value(const struct device *dev, |
| uint32_t *ticks) |
| { |
| struct mcp7940n_data *data = dev->data; |
| time_t unix_time; |
| int rc; |
| |
| k_sem_take(&data->lock, K_FOREVER); |
| |
| /* Get time */ |
| rc = read_time(dev, &unix_time); |
| |
| /* Convert time to ticks */ |
| if (rc >= 0) { |
| *ticks = unix_time; |
| } |
| |
| k_sem_give(&data->lock); |
| |
| return rc; |
| } |
| |
| static int mcp7940n_counter_set_alarm(const struct device *dev, uint8_t alarm_id, |
| const struct counter_alarm_cfg *alarm_cfg) |
| { |
| struct mcp7940n_data *data = dev->data; |
| uint32_t seconds_until_alarm; |
| time_t current_time; |
| time_t alarm_time; |
| struct tm time_buffer = { 0 }; |
| uint8_t alarm_base_address; |
| struct mcp7940n_alarm_registers *alm_regs; |
| int rc = 0; |
| |
| k_sem_take(&data->lock, K_FOREVER); |
| |
| if (alarm_id == ALARM0_ID) { |
| alarm_base_address = REG_ALM0_SEC; |
| alm_regs = &data->alm0_registers; |
| } else if (alarm_id == ALARM1_ID) { |
| alarm_base_address = REG_ALM1_SEC; |
| alm_regs = &data->alm1_registers; |
| } else { |
| rc = -EINVAL; |
| goto out; |
| } |
| |
| /* Convert ticks to time */ |
| seconds_until_alarm = alarm_cfg->ticks; |
| |
| /* Get current time and add alarm offset */ |
| rc = read_time(dev, ¤t_time); |
| if (rc < 0) { |
| goto out; |
| } |
| |
| alarm_time = current_time + seconds_until_alarm; |
| gmtime_r(&alarm_time, &time_buffer); |
| |
| /* Set alarm trigger mask and alarm enable flag */ |
| if (alarm_id == ALARM0_ID) { |
| data->registers.rtc_control.alm0_en = 1; |
| } else if (alarm_id == ALARM1_ID) { |
| data->registers.rtc_control.alm1_en = 1; |
| } |
| |
| /* Set alarm to match with second, minute, hour, day of week, day of |
| * month and month |
| */ |
| alm_regs->alm_weekday.alm_msk = MCP7940N_ALARM_TRIGGER_ALL; |
| |
| /* Write time to alarm registers */ |
| encode_alarm(dev, &time_buffer, alarm_id); |
| rc = write_data_block(dev, alarm_base_address, RTC_ALARM_REGISTERS_SIZE); |
| if (rc < 0) { |
| goto out; |
| } |
| |
| /* Enable alarm */ |
| rc = write_register(dev, REG_RTC_CONTROL, |
| *((uint8_t *)(&data->registers.rtc_control))); |
| if (rc < 0) { |
| goto out; |
| } |
| |
| /* Config user data and callback */ |
| data->counter_handler[alarm_id] = alarm_cfg->callback; |
| data->counter_ticks[alarm_id] = current_time; |
| data->alarm_user_data[alarm_id] = alarm_cfg->user_data; |
| |
| out: |
| k_sem_give(&data->lock); |
| |
| return rc; |
| } |
| |
| static int mcp7940n_counter_cancel_alarm(const struct device *dev, uint8_t alarm_id) |
| { |
| struct mcp7940n_data *data = dev->data; |
| int rc = 0; |
| |
| k_sem_take(&data->lock, K_FOREVER); |
| |
| /* Clear alarm enable bit */ |
| if (alarm_id == ALARM0_ID) { |
| data->registers.rtc_control.alm0_en = 0; |
| } else if (alarm_id == ALARM1_ID) { |
| data->registers.rtc_control.alm1_en = 0; |
| } else { |
| rc = -EINVAL; |
| goto out; |
| } |
| |
| rc = write_register(dev, REG_RTC_CONTROL, |
| *((uint8_t *)(&data->registers.rtc_control))); |
| |
| out: |
| k_sem_give(&data->lock); |
| |
| return rc; |
| } |
| |
| static int mcp7940n_counter_set_top_value(const struct device *dev, |
| const struct counter_top_cfg *cfg) |
| { |
| return -ENOTSUP; |
| } |
| |
| /* This function can be used to poll the alarm interrupt flags if the MCU is |
| * not connected to the MC7940N MFP pin. It can also be used to check if an |
| * alarm was triggered while the MCU was in reset. This function will clear |
| * the interrupt flag |
| * |
| * Return bitmask of alarm interrupt flag (IF) where each IF is shifted by |
| * the alarm ID. |
| */ |
| static uint32_t mcp7940n_counter_get_pending_int(const struct device *dev) |
| { |
| struct mcp7940n_data *data = dev->data; |
| uint32_t interrupt_pending = 0; |
| int rc; |
| |
| k_sem_take(&data->lock, K_FOREVER); |
| |
| /* Check interrupt flag for alarm 0 */ |
| rc = read_register(dev, REG_ALM0_WDAY, |
| (uint8_t *)&data->alm0_registers.alm_weekday); |
| if (rc < 0) { |
| goto out; |
| } |
| |
| if (data->alm0_registers.alm_weekday.alm_if) { |
| /* Clear interrupt */ |
| data->alm0_registers.alm_weekday.alm_if = 0; |
| rc = write_register(dev, REG_ALM0_WDAY, |
| *((uint8_t *)(&data->alm0_registers.alm_weekday))); |
| if (rc < 0) { |
| goto out; |
| } |
| interrupt_pending |= (1 << ALARM0_ID); |
| } |
| |
| /* Check interrupt flag for alarm 1 */ |
| rc = read_register(dev, REG_ALM1_WDAY, |
| (uint8_t *)&data->alm1_registers.alm_weekday); |
| if (rc < 0) { |
| goto out; |
| } |
| |
| if (data->alm1_registers.alm_weekday.alm_if) { |
| /* Clear interrupt */ |
| data->alm1_registers.alm_weekday.alm_if = 0; |
| rc = write_register(dev, REG_ALM1_WDAY, |
| *((uint8_t *)(&data->alm1_registers.alm_weekday))); |
| if (rc < 0) { |
| goto out; |
| } |
| interrupt_pending |= (1 << ALARM1_ID); |
| } |
| |
| out: |
| k_sem_give(&data->lock); |
| |
| if (rc) { |
| interrupt_pending = 0; |
| } |
| return (interrupt_pending); |
| } |
| |
| static uint32_t mcp7940n_counter_get_top_value(const struct device *dev) |
| { |
| return UINT32_MAX; |
| } |
| |
| static int mcp7940n_init(const struct device *dev) |
| { |
| struct mcp7940n_data *data = dev->data; |
| const struct mcp7940n_config *cfg = dev->config; |
| int rc = 0; |
| time_t unix_time = 0; |
| |
| /* Initialize and take the lock */ |
| k_sem_init(&data->lock, 0, 1); |
| |
| if (!device_is_ready(cfg->i2c.bus)) { |
| LOG_ERR("I2C device %s is not ready", cfg->i2c.bus->name); |
| rc = -ENODEV; |
| goto out; |
| } |
| |
| rc = read_time(dev, &unix_time); |
| if (rc < 0) { |
| goto out; |
| } |
| |
| rc = set_day_of_week(dev, &unix_time); |
| if (rc < 0) { |
| goto out; |
| } |
| |
| /* Set 24-hour time */ |
| data->registers.rtc_hours.twelve_hr = false; |
| rc = write_register(dev, REG_RTC_HOUR, |
| *((uint8_t *)(&data->registers.rtc_hours))); |
| if (rc < 0) { |
| goto out; |
| } |
| |
| /* Configure alarm interrupt gpio */ |
| if (cfg->int_gpios.port != NULL) { |
| |
| if (!gpio_is_ready_dt(&cfg->int_gpios)) { |
| LOG_ERR("Port device %s is not ready", |
| cfg->int_gpios.port->name); |
| rc = -ENODEV; |
| goto out; |
| } |
| |
| data->mcp7940n = dev; |
| k_work_init(&data->alarm_work, mcp7940n_work_handler); |
| |
| gpio_pin_configure_dt(&cfg->int_gpios, GPIO_INPUT); |
| |
| gpio_pin_interrupt_configure_dt(&cfg->int_gpios, |
| GPIO_INT_EDGE_TO_ACTIVE); |
| |
| gpio_init_callback(&data->int_callback, mcp7940n_init_cb, |
| BIT(cfg->int_gpios.pin)); |
| |
| gpio_add_callback(cfg->int_gpios.port, &data->int_callback); |
| |
| /* Configure interrupt polarity */ |
| if ((cfg->int_gpios.dt_flags & GPIO_ACTIVE_LOW) == GPIO_ACTIVE_LOW) { |
| data->int_active_high = false; |
| } else { |
| data->int_active_high = true; |
| } |
| data->alm0_registers.alm_weekday.alm_pol = data->int_active_high; |
| data->alm1_registers.alm_weekday.alm_pol = data->int_active_high; |
| rc = write_register(dev, REG_ALM0_WDAY, |
| *((uint8_t *)(&data->alm0_registers.alm_weekday))); |
| rc = write_register(dev, REG_ALM1_WDAY, |
| *((uint8_t *)(&data->alm1_registers.alm_weekday))); |
| } |
| out: |
| k_sem_give(&data->lock); |
| |
| return rc; |
| } |
| |
| static const struct counter_driver_api mcp7940n_api = { |
| .start = mcp7940n_counter_start, |
| .stop = mcp7940n_counter_stop, |
| .get_value = mcp7940n_counter_get_value, |
| .set_alarm = mcp7940n_counter_set_alarm, |
| .cancel_alarm = mcp7940n_counter_cancel_alarm, |
| .set_top_value = mcp7940n_counter_set_top_value, |
| .get_pending_int = mcp7940n_counter_get_pending_int, |
| .get_top_value = mcp7940n_counter_get_top_value, |
| }; |
| |
| #define INST_DT_MCP7904N(index) \ |
| \ |
| static struct mcp7940n_data mcp7940n_data_##index; \ |
| \ |
| static const struct mcp7940n_config mcp7940n_config_##index = { \ |
| .generic = { \ |
| .max_top_value = UINT32_MAX, \ |
| .freq = 1, \ |
| .flags = COUNTER_CONFIG_INFO_COUNT_UP, \ |
| .channels = 2, \ |
| }, \ |
| .i2c = I2C_DT_SPEC_INST_GET(index), \ |
| .int_gpios = GPIO_DT_SPEC_INST_GET_OR(index, int_gpios, {0}), \ |
| }; \ |
| \ |
| DEVICE_DT_INST_DEFINE(index, mcp7940n_init, NULL, \ |
| &mcp7940n_data_##index, \ |
| &mcp7940n_config_##index, \ |
| POST_KERNEL, \ |
| CONFIG_COUNTER_INIT_PRIORITY, \ |
| &mcp7940n_api); |
| |
| DT_INST_FOREACH_STATUS_OKAY(INST_DT_MCP7904N); |