1 /*
2 * Copyright (c) 2019-2020 Peter Bigot Consulting, LLC
3 * Copyright (c) 2021 Laird Connectivity
4 *
5 * SPDX-License-Identifier: Apache-2.0
6 */
7
8 #ifdef CONFIG_SOC_POSIX
9 #undef _POSIX_C_SOURCE
10 #define _POSIX_C_SOURCE 200809L /* Required for gmtime_r */
11 #endif
12
13 #define DT_DRV_COMPAT microchip_mcp7940n
14
15 #include <zephyr/device.h>
16 #include <zephyr/drivers/counter.h>
17 #include <zephyr/drivers/gpio.h>
18 #include <zephyr/drivers/i2c.h>
19 #include <zephyr/drivers/rtc/mcp7940n.h>
20 #include <zephyr/kernel.h>
21 #include <zephyr/logging/log.h>
22 #include <zephyr/sys/timeutil.h>
23 #include <zephyr/sys/util.h>
24 #include <time.h>
25
26 LOG_MODULE_REGISTER(MCP7940N, CONFIG_COUNTER_LOG_LEVEL);
27
28 /* Alarm channels */
29 #define ALARM0_ID 0
30 #define ALARM1_ID 1
31
32 /* Size of block when writing whole struct */
33 #define RTC_TIME_REGISTERS_SIZE sizeof(struct mcp7940n_time_registers)
34 #define RTC_ALARM_REGISTERS_SIZE sizeof(struct mcp7940n_alarm_registers)
35
36 /* Largest block size */
37 #define MAX_WRITE_SIZE (RTC_TIME_REGISTERS_SIZE)
38
39 /* tm struct uses years since 1900 but unix time uses years since
40 * 1970. MCP7940N default year is '1' so the offset is 69
41 */
42 #define UNIX_YEAR_OFFSET 69
43
44 /* Macro used to decode BCD to UNIX time to avoid potential copy and paste
45 * errors.
46 */
47 #define RTC_BCD_DECODE(reg_prefix) (reg_prefix##_one + reg_prefix##_ten * 10)
48
49 struct mcp7940n_config {
50 struct counter_config_info generic;
51 struct i2c_dt_spec i2c;
52 const struct gpio_dt_spec int_gpios;
53 };
54
55 struct mcp7940n_data {
56 const struct device *mcp7940n;
57 struct k_sem lock;
58 struct mcp7940n_time_registers registers;
59 struct mcp7940n_alarm_registers alm0_registers;
60 struct mcp7940n_alarm_registers alm1_registers;
61
62 struct k_work alarm_work;
63 struct gpio_callback int_callback;
64
65 counter_alarm_callback_t counter_handler[2];
66 uint32_t counter_ticks[2];
67 void *alarm_user_data[2];
68
69 bool int_active_high;
70 };
71
72 /** @brief Convert bcd time in device registers to UNIX time
73 *
74 * @param dev the MCP7940N device pointer.
75 *
76 * @retval returns unix time.
77 */
decode_rtc(const struct device * dev)78 static time_t decode_rtc(const struct device *dev)
79 {
80 struct mcp7940n_data *data = dev->data;
81 time_t time_unix = 0;
82 struct tm time = { 0 };
83
84 time.tm_sec = RTC_BCD_DECODE(data->registers.rtc_sec.sec);
85 time.tm_min = RTC_BCD_DECODE(data->registers.rtc_min.min);
86 time.tm_hour = RTC_BCD_DECODE(data->registers.rtc_hours.hr);
87 time.tm_mday = RTC_BCD_DECODE(data->registers.rtc_date.date);
88 time.tm_wday = data->registers.rtc_weekday.weekday;
89 /* tm struct starts months at 0, mcp7940n starts at 1 */
90 time.tm_mon = RTC_BCD_DECODE(data->registers.rtc_month.month) - 1;
91 /* tm struct uses years since 1900 but unix time uses years since 1970 */
92 time.tm_year = RTC_BCD_DECODE(data->registers.rtc_year.year) +
93 UNIX_YEAR_OFFSET;
94
95 time_unix = timeutil_timegm(&time);
96
97 LOG_DBG("Unix time is %d\n", (uint32_t)time_unix);
98
99 return time_unix;
100 }
101
102 /** @brief Encode time struct tm into mcp7940n rtc registers
103 *
104 * @param dev the MCP7940N device pointer.
105 * @param time_buffer tm struct containing time to be encoded into mcp7940n
106 * registers.
107 *
108 * @retval return 0 on success, or a negative error code from invalid
109 * parameter.
110 */
encode_rtc(const struct device * dev,struct tm * time_buffer)111 static int encode_rtc(const struct device *dev, struct tm *time_buffer)
112 {
113 struct mcp7940n_data *data = dev->data;
114 uint8_t month;
115 uint8_t year_since_epoch;
116
117 /* In a tm struct, months start at 0, mcp7940n starts with 1 */
118 month = time_buffer->tm_mon + 1;
119
120 if (time_buffer->tm_year < UNIX_YEAR_OFFSET) {
121 return -EINVAL;
122 }
123 year_since_epoch = time_buffer->tm_year - UNIX_YEAR_OFFSET;
124
125 /* Set external oscillator configuration bit */
126 data->registers.rtc_sec.start_osc = 1;
127
128 data->registers.rtc_sec.sec_one = time_buffer->tm_sec % 10;
129 data->registers.rtc_sec.sec_ten = time_buffer->tm_sec / 10;
130 data->registers.rtc_min.min_one = time_buffer->tm_min % 10;
131 data->registers.rtc_min.min_ten = time_buffer->tm_min / 10;
132 data->registers.rtc_hours.hr_one = time_buffer->tm_hour % 10;
133 data->registers.rtc_hours.hr_ten = time_buffer->tm_hour / 10;
134 data->registers.rtc_weekday.weekday = time_buffer->tm_wday;
135 data->registers.rtc_date.date_one = time_buffer->tm_mday % 10;
136 data->registers.rtc_date.date_ten = time_buffer->tm_mday / 10;
137 data->registers.rtc_month.month_one = month % 10;
138 data->registers.rtc_month.month_ten = month / 10;
139 data->registers.rtc_year.year_one = year_since_epoch % 10;
140 data->registers.rtc_year.year_ten = year_since_epoch / 10;
141
142 return 0;
143 }
144
145 /** @brief Encode time struct tm into mcp7940n alarm registers
146 *
147 * @param dev the MCP7940N device pointer.
148 * @param time_buffer tm struct containing time to be encoded into mcp7940n
149 * registers.
150 * @param alarm_id alarm ID, can be 0 or 1 for MCP7940N.
151 *
152 * @retval return 0 on success, or a negative error code from invalid
153 * parameter.
154 */
encode_alarm(const struct device * dev,struct tm * time_buffer,uint8_t alarm_id)155 static int encode_alarm(const struct device *dev, struct tm *time_buffer, uint8_t alarm_id)
156 {
157 struct mcp7940n_data *data = dev->data;
158 uint8_t month;
159 struct mcp7940n_alarm_registers *alm_regs;
160
161 if (alarm_id == ALARM0_ID) {
162 alm_regs = &data->alm0_registers;
163 } else if (alarm_id == ALARM1_ID) {
164 alm_regs = &data->alm1_registers;
165 } else {
166 return -EINVAL;
167 }
168 /* In a tm struct, months start at 0 */
169 month = time_buffer->tm_mon + 1;
170
171 alm_regs->alm_sec.sec_one = time_buffer->tm_sec % 10;
172 alm_regs->alm_sec.sec_ten = time_buffer->tm_sec / 10;
173 alm_regs->alm_min.min_one = time_buffer->tm_min % 10;
174 alm_regs->alm_min.min_ten = time_buffer->tm_min / 10;
175 alm_regs->alm_hours.hr_one = time_buffer->tm_hour % 10;
176 alm_regs->alm_hours.hr_ten = time_buffer->tm_hour / 10;
177 alm_regs->alm_weekday.weekday = time_buffer->tm_wday;
178 alm_regs->alm_date.date_one = time_buffer->tm_mday % 10;
179 alm_regs->alm_date.date_ten = time_buffer->tm_mday / 10;
180 alm_regs->alm_month.month_one = month % 10;
181 alm_regs->alm_month.month_ten = month / 10;
182
183 return 0;
184 }
185
186 /** @brief Reads single register from MCP7940N
187 *
188 * @param dev the MCP7940N device pointer.
189 * @param addr register address.
190 * @param val pointer to uint8_t that will contain register value if
191 * successful.
192 *
193 * @retval return 0 on success, or a negative error code from an I2C
194 * transaction.
195 */
read_register(const struct device * dev,uint8_t addr,uint8_t * val)196 static int read_register(const struct device *dev, uint8_t addr, uint8_t *val)
197 {
198 const struct mcp7940n_config *cfg = dev->config;
199
200 int rc = i2c_write_read_dt(&cfg->i2c, &addr, sizeof(addr), val, 1);
201
202 return rc;
203 }
204
205 /** @brief Read registers from device and populate mcp7940n_registers struct
206 *
207 * @param dev the MCP7940N device pointer.
208 * @param unix_time pointer to time_t value that will contain unix time if
209 * successful.
210 *
211 * @retval return 0 on success, or a negative error code from an I2C
212 * transaction.
213 */
read_time(const struct device * dev,time_t * unix_time)214 static int read_time(const struct device *dev, time_t *unix_time)
215 {
216 struct mcp7940n_data *data = dev->data;
217 const struct mcp7940n_config *cfg = dev->config;
218 uint8_t addr = REG_RTC_SEC;
219
220 int rc = i2c_write_read_dt(&cfg->i2c, &addr, sizeof(addr), &data->registers,
221 RTC_TIME_REGISTERS_SIZE);
222
223 if (rc >= 0) {
224 *unix_time = decode_rtc(dev);
225 }
226
227 return rc;
228 }
229
230 /** @brief Write a single register to MCP7940N
231 *
232 * @param dev the MCP7940N device pointer.
233 * @param addr register address.
234 * @param value Value that will be written to the register.
235 *
236 * @retval return 0 on success, or a negative error code from an I2C
237 * transaction or invalid parameter.
238 */
write_register(const struct device * dev,enum mcp7940n_register addr,uint8_t value)239 static int write_register(const struct device *dev, enum mcp7940n_register addr, uint8_t value)
240 {
241 const struct mcp7940n_config *cfg = dev->config;
242 int rc = 0;
243
244 uint8_t time_data[2] = {addr, value};
245
246 rc = i2c_write_dt(&cfg->i2c, time_data, sizeof(time_data));
247
248 return rc;
249 }
250
251 /** @brief Write a full time struct to MCP7940N registers.
252 *
253 * @param dev the MCP7940N device pointer.
254 * @param addr first register address to write to, should be REG_RTC_SEC,
255 * REG_ALM0_SEC or REG_ALM0_SEC.
256 * @param size size of data struct that will be written.
257 *
258 * @retval return 0 on success, or a negative error code from an I2C
259 * transaction or invalid parameter.
260 */
write_data_block(const struct device * dev,enum mcp7940n_register addr,uint8_t size)261 static int write_data_block(const struct device *dev, enum mcp7940n_register addr, uint8_t size)
262 {
263 struct mcp7940n_data *data = dev->data;
264 const struct mcp7940n_config *cfg = dev->config;
265 int rc = 0;
266 uint8_t time_data[MAX_WRITE_SIZE + 1];
267 uint8_t *write_block_start;
268
269 if (size > MAX_WRITE_SIZE) {
270 return -EINVAL;
271 }
272
273 if (addr >= REG_INVAL) {
274 return -EINVAL;
275 }
276
277 if (addr == REG_RTC_SEC) {
278 write_block_start = (uint8_t *)&data->registers;
279 } else if (addr == REG_ALM0_SEC) {
280 write_block_start = (uint8_t *)&data->alm0_registers;
281 } else if (addr == REG_ALM1_SEC) {
282 write_block_start = (uint8_t *)&data->alm1_registers;
283 } else {
284 return -EINVAL;
285 }
286
287 /* Load register address into first byte then fill in data values */
288 time_data[0] = addr;
289 memcpy(&time_data[1], write_block_start, size);
290
291 rc = i2c_write_dt(&cfg->i2c, time_data, size + 1);
292
293 return rc;
294 }
295
296 /** @brief Sets the correct weekday.
297 *
298 * If the time is never set then the device defaults to 1st January 1970
299 * but with the wrong weekday set. This function ensures the weekday is
300 * correct in this case.
301 *
302 * @param dev the MCP7940N device pointer.
303 * @param unix_time pointer to unix time that will be used to work out the weekday
304 *
305 * @retval return 0 on success, or a negative error code from an I2C
306 * transaction or invalid parameter.
307 */
set_day_of_week(const struct device * dev,time_t * unix_time)308 static int set_day_of_week(const struct device *dev, time_t *unix_time)
309 {
310 struct mcp7940n_data *data = dev->data;
311 struct tm time_buffer = { 0 };
312 int rc = 0;
313
314 if (gmtime_r(unix_time, &time_buffer) != NULL) {
315 data->registers.rtc_weekday.weekday = time_buffer.tm_wday;
316 rc = write_register(dev, REG_RTC_WDAY,
317 *((uint8_t *)(&data->registers.rtc_weekday)));
318 } else {
319 rc = -EINVAL;
320 }
321
322 return rc;
323 }
324
325 /** @brief Checks the interrupt pending flag (IF) of a given alarm.
326 *
327 * A callback is fired if an IRQ is pending.
328 *
329 * @param dev the MCP7940N device pointer.
330 * @param alarm_id ID of alarm, can be 0 or 1 for MCP7940N.
331 */
mcp7940n_handle_interrupt(const struct device * dev,uint8_t alarm_id)332 static void mcp7940n_handle_interrupt(const struct device *dev, uint8_t alarm_id)
333 {
334 struct mcp7940n_data *data = dev->data;
335 uint8_t alarm_reg_address;
336 struct mcp7940n_alarm_registers *alm_regs;
337 counter_alarm_callback_t cb;
338 uint32_t ticks = 0;
339 bool fire_callback = false;
340
341 if (alarm_id == ALARM0_ID) {
342 alarm_reg_address = REG_ALM0_WDAY;
343 alm_regs = &data->alm0_registers;
344 } else if (alarm_id == ALARM1_ID) {
345 alarm_reg_address = REG_ALM1_WDAY;
346 alm_regs = &data->alm1_registers;
347 } else {
348 return;
349 }
350
351 k_sem_take(&data->lock, K_FOREVER);
352
353 /* Check if this alarm has a pending interrupt */
354 read_register(dev, alarm_reg_address, (uint8_t *)&alm_regs->alm_weekday);
355
356 if (alm_regs->alm_weekday.alm_if) {
357 /* Clear interrupt */
358 alm_regs->alm_weekday.alm_if = 0;
359 write_register(dev, alarm_reg_address,
360 *((uint8_t *)(&alm_regs->alm_weekday)));
361
362 /* Fire callback */
363 if (data->counter_handler[alarm_id]) {
364 cb = data->counter_handler[alarm_id];
365 ticks = data->counter_ticks[alarm_id];
366 fire_callback = true;
367 }
368 }
369
370 k_sem_give(&data->lock);
371
372 if (fire_callback) {
373 cb(data->mcp7940n, 0, ticks, data->alarm_user_data[alarm_id]);
374 }
375 }
376
mcp7940n_work_handler(struct k_work * work)377 static void mcp7940n_work_handler(struct k_work *work)
378 {
379 struct mcp7940n_data *data =
380 CONTAINER_OF(work, struct mcp7940n_data, alarm_work);
381
382 /* Check interrupt flags for both alarms */
383 mcp7940n_handle_interrupt(data->mcp7940n, ALARM0_ID);
384 mcp7940n_handle_interrupt(data->mcp7940n, ALARM1_ID);
385 }
386
mcp7940n_init_cb(const struct device * dev,struct gpio_callback * gpio_cb,uint32_t pins)387 static void mcp7940n_init_cb(const struct device *dev,
388 struct gpio_callback *gpio_cb, uint32_t pins)
389 {
390 struct mcp7940n_data *data =
391 CONTAINER_OF(gpio_cb, struct mcp7940n_data, int_callback);
392
393 ARG_UNUSED(pins);
394
395 k_work_submit(&data->alarm_work);
396 }
397
mcp7940n_rtc_set_time(const struct device * dev,time_t unix_time)398 int mcp7940n_rtc_set_time(const struct device *dev, time_t unix_time)
399 {
400 struct mcp7940n_data *data = dev->data;
401 struct tm time_buffer = { 0 };
402 int rc = 0;
403
404 if (unix_time > UINT32_MAX) {
405 LOG_ERR("Unix time must be 32-bit");
406 return -EINVAL;
407 }
408
409 k_sem_take(&data->lock, K_FOREVER);
410
411 /* Convert unix_time to civil time */
412 gmtime_r(&unix_time, &time_buffer);
413 LOG_DBG("Desired time is %d-%d-%d %d:%d:%d\n", (time_buffer.tm_year + 1900),
414 (time_buffer.tm_mon + 1), time_buffer.tm_mday, time_buffer.tm_hour,
415 time_buffer.tm_min, time_buffer.tm_sec);
416
417 /* Encode time */
418 rc = encode_rtc(dev, &time_buffer);
419 if (rc < 0) {
420 goto out;
421 }
422
423 /* Write to device */
424 rc = write_data_block(dev, REG_RTC_SEC, RTC_TIME_REGISTERS_SIZE);
425
426 out:
427 k_sem_give(&data->lock);
428
429 return rc;
430 }
431
mcp7940n_counter_start(const struct device * dev)432 static int mcp7940n_counter_start(const struct device *dev)
433 {
434 struct mcp7940n_data *data = dev->data;
435 int rc = 0;
436
437 k_sem_take(&data->lock, K_FOREVER);
438
439 /* Set start oscillator configuration bit */
440 data->registers.rtc_sec.start_osc = 1;
441 rc = write_register(dev, REG_RTC_SEC,
442 *((uint8_t *)(&data->registers.rtc_sec)));
443
444 k_sem_give(&data->lock);
445
446 return rc;
447 }
448
mcp7940n_counter_stop(const struct device * dev)449 static int mcp7940n_counter_stop(const struct device *dev)
450 {
451 struct mcp7940n_data *data = dev->data;
452 int rc = 0;
453
454 k_sem_take(&data->lock, K_FOREVER);
455
456 /* Clear start oscillator configuration bit */
457 data->registers.rtc_sec.start_osc = 0;
458 rc = write_register(dev, REG_RTC_SEC,
459 *((uint8_t *)(&data->registers.rtc_sec)));
460
461 k_sem_give(&data->lock);
462
463 return rc;
464 }
465
mcp7940n_counter_get_value(const struct device * dev,uint32_t * ticks)466 static int mcp7940n_counter_get_value(const struct device *dev,
467 uint32_t *ticks)
468 {
469 struct mcp7940n_data *data = dev->data;
470 time_t unix_time;
471 int rc;
472
473 k_sem_take(&data->lock, K_FOREVER);
474
475 /* Get time */
476 rc = read_time(dev, &unix_time);
477
478 /* Convert time to ticks */
479 if (rc >= 0) {
480 *ticks = unix_time;
481 }
482
483 k_sem_give(&data->lock);
484
485 return rc;
486 }
487
mcp7940n_counter_set_alarm(const struct device * dev,uint8_t alarm_id,const struct counter_alarm_cfg * alarm_cfg)488 static int mcp7940n_counter_set_alarm(const struct device *dev, uint8_t alarm_id,
489 const struct counter_alarm_cfg *alarm_cfg)
490 {
491 struct mcp7940n_data *data = dev->data;
492 uint32_t seconds_until_alarm;
493 time_t current_time;
494 time_t alarm_time;
495 struct tm time_buffer = { 0 };
496 uint8_t alarm_base_address;
497 struct mcp7940n_alarm_registers *alm_regs;
498 int rc = 0;
499
500 k_sem_take(&data->lock, K_FOREVER);
501
502 if (alarm_id == ALARM0_ID) {
503 alarm_base_address = REG_ALM0_SEC;
504 alm_regs = &data->alm0_registers;
505 } else if (alarm_id == ALARM1_ID) {
506 alarm_base_address = REG_ALM1_SEC;
507 alm_regs = &data->alm1_registers;
508 } else {
509 rc = -EINVAL;
510 goto out;
511 }
512
513 /* Convert ticks to time */
514 seconds_until_alarm = alarm_cfg->ticks;
515
516 /* Get current time and add alarm offset */
517 rc = read_time(dev, ¤t_time);
518 if (rc < 0) {
519 goto out;
520 }
521
522 alarm_time = current_time + seconds_until_alarm;
523 gmtime_r(&alarm_time, &time_buffer);
524
525 /* Set alarm trigger mask and alarm enable flag */
526 if (alarm_id == ALARM0_ID) {
527 data->registers.rtc_control.alm0_en = 1;
528 } else if (alarm_id == ALARM1_ID) {
529 data->registers.rtc_control.alm1_en = 1;
530 }
531
532 /* Set alarm to match with second, minute, hour, day of week, day of
533 * month and month
534 */
535 alm_regs->alm_weekday.alm_msk = MCP7940N_ALARM_TRIGGER_ALL;
536
537 /* Write time to alarm registers */
538 encode_alarm(dev, &time_buffer, alarm_id);
539 rc = write_data_block(dev, alarm_base_address, RTC_ALARM_REGISTERS_SIZE);
540 if (rc < 0) {
541 goto out;
542 }
543
544 /* Enable alarm */
545 rc = write_register(dev, REG_RTC_CONTROL,
546 *((uint8_t *)(&data->registers.rtc_control)));
547 if (rc < 0) {
548 goto out;
549 }
550
551 /* Config user data and callback */
552 data->counter_handler[alarm_id] = alarm_cfg->callback;
553 data->counter_ticks[alarm_id] = current_time;
554 data->alarm_user_data[alarm_id] = alarm_cfg->user_data;
555
556 out:
557 k_sem_give(&data->lock);
558
559 return rc;
560 }
561
mcp7940n_counter_cancel_alarm(const struct device * dev,uint8_t alarm_id)562 static int mcp7940n_counter_cancel_alarm(const struct device *dev, uint8_t alarm_id)
563 {
564 struct mcp7940n_data *data = dev->data;
565 int rc = 0;
566
567 k_sem_take(&data->lock, K_FOREVER);
568
569 /* Clear alarm enable bit */
570 if (alarm_id == ALARM0_ID) {
571 data->registers.rtc_control.alm0_en = 0;
572 } else if (alarm_id == ALARM1_ID) {
573 data->registers.rtc_control.alm1_en = 0;
574 } else {
575 rc = -EINVAL;
576 goto out;
577 }
578
579 rc = write_register(dev, REG_RTC_CONTROL,
580 *((uint8_t *)(&data->registers.rtc_control)));
581
582 out:
583 k_sem_give(&data->lock);
584
585 return rc;
586 }
587
mcp7940n_counter_set_top_value(const struct device * dev,const struct counter_top_cfg * cfg)588 static int mcp7940n_counter_set_top_value(const struct device *dev,
589 const struct counter_top_cfg *cfg)
590 {
591 return -ENOTSUP;
592 }
593
594 /* This function can be used to poll the alarm interrupt flags if the MCU is
595 * not connected to the MC7940N MFP pin. It can also be used to check if an
596 * alarm was triggered while the MCU was in reset. This function will clear
597 * the interrupt flag
598 *
599 * Return bitmask of alarm interrupt flag (IF) where each IF is shifted by
600 * the alarm ID.
601 */
mcp7940n_counter_get_pending_int(const struct device * dev)602 static uint32_t mcp7940n_counter_get_pending_int(const struct device *dev)
603 {
604 struct mcp7940n_data *data = dev->data;
605 uint32_t interrupt_pending = 0;
606 int rc;
607
608 k_sem_take(&data->lock, K_FOREVER);
609
610 /* Check interrupt flag for alarm 0 */
611 rc = read_register(dev, REG_ALM0_WDAY,
612 (uint8_t *)&data->alm0_registers.alm_weekday);
613 if (rc < 0) {
614 goto out;
615 }
616
617 if (data->alm0_registers.alm_weekday.alm_if) {
618 /* Clear interrupt */
619 data->alm0_registers.alm_weekday.alm_if = 0;
620 rc = write_register(dev, REG_ALM0_WDAY,
621 *((uint8_t *)(&data->alm0_registers.alm_weekday)));
622 if (rc < 0) {
623 goto out;
624 }
625 interrupt_pending |= (1 << ALARM0_ID);
626 }
627
628 /* Check interrupt flag for alarm 1 */
629 rc = read_register(dev, REG_ALM1_WDAY,
630 (uint8_t *)&data->alm1_registers.alm_weekday);
631 if (rc < 0) {
632 goto out;
633 }
634
635 if (data->alm1_registers.alm_weekday.alm_if) {
636 /* Clear interrupt */
637 data->alm1_registers.alm_weekday.alm_if = 0;
638 rc = write_register(dev, REG_ALM1_WDAY,
639 *((uint8_t *)(&data->alm1_registers.alm_weekday)));
640 if (rc < 0) {
641 goto out;
642 }
643 interrupt_pending |= (1 << ALARM1_ID);
644 }
645
646 out:
647 k_sem_give(&data->lock);
648
649 if (rc) {
650 interrupt_pending = 0;
651 }
652 return (interrupt_pending);
653 }
654
mcp7940n_counter_get_top_value(const struct device * dev)655 static uint32_t mcp7940n_counter_get_top_value(const struct device *dev)
656 {
657 return UINT32_MAX;
658 }
659
mcp7940n_init(const struct device * dev)660 static int mcp7940n_init(const struct device *dev)
661 {
662 struct mcp7940n_data *data = dev->data;
663 const struct mcp7940n_config *cfg = dev->config;
664 int rc = 0;
665 time_t unix_time = 0;
666
667 /* Initialize and take the lock */
668 k_sem_init(&data->lock, 0, 1);
669
670 if (!device_is_ready(cfg->i2c.bus)) {
671 LOG_ERR("I2C device %s is not ready", cfg->i2c.bus->name);
672 rc = -ENODEV;
673 goto out;
674 }
675
676 rc = read_time(dev, &unix_time);
677 if (rc < 0) {
678 goto out;
679 }
680
681 rc = set_day_of_week(dev, &unix_time);
682 if (rc < 0) {
683 goto out;
684 }
685
686 /* Set 24-hour time */
687 data->registers.rtc_hours.twelve_hr = false;
688 rc = write_register(dev, REG_RTC_HOUR,
689 *((uint8_t *)(&data->registers.rtc_hours)));
690 if (rc < 0) {
691 goto out;
692 }
693
694 /* Configure alarm interrupt gpio */
695 if (cfg->int_gpios.port != NULL) {
696
697 if (!gpio_is_ready_dt(&cfg->int_gpios)) {
698 LOG_ERR("Port device %s is not ready",
699 cfg->int_gpios.port->name);
700 rc = -ENODEV;
701 goto out;
702 }
703
704 data->mcp7940n = dev;
705 k_work_init(&data->alarm_work, mcp7940n_work_handler);
706
707 gpio_pin_configure_dt(&cfg->int_gpios, GPIO_INPUT);
708
709 gpio_pin_interrupt_configure_dt(&cfg->int_gpios,
710 GPIO_INT_EDGE_TO_ACTIVE);
711
712 gpio_init_callback(&data->int_callback, mcp7940n_init_cb,
713 BIT(cfg->int_gpios.pin));
714
715 (void)gpio_add_callback(cfg->int_gpios.port, &data->int_callback);
716
717 /* Configure interrupt polarity */
718 if ((cfg->int_gpios.dt_flags & GPIO_ACTIVE_LOW) == GPIO_ACTIVE_LOW) {
719 data->int_active_high = false;
720 } else {
721 data->int_active_high = true;
722 }
723 data->alm0_registers.alm_weekday.alm_pol = data->int_active_high;
724 data->alm1_registers.alm_weekday.alm_pol = data->int_active_high;
725 rc = write_register(dev, REG_ALM0_WDAY,
726 *((uint8_t *)(&data->alm0_registers.alm_weekday)));
727 rc = write_register(dev, REG_ALM1_WDAY,
728 *((uint8_t *)(&data->alm1_registers.alm_weekday)));
729 }
730 out:
731 k_sem_give(&data->lock);
732
733 return rc;
734 }
735
736 static DEVICE_API(counter, mcp7940n_api) = {
737 .start = mcp7940n_counter_start,
738 .stop = mcp7940n_counter_stop,
739 .get_value = mcp7940n_counter_get_value,
740 .set_alarm = mcp7940n_counter_set_alarm,
741 .cancel_alarm = mcp7940n_counter_cancel_alarm,
742 .set_top_value = mcp7940n_counter_set_top_value,
743 .get_pending_int = mcp7940n_counter_get_pending_int,
744 .get_top_value = mcp7940n_counter_get_top_value,
745 };
746
747 #define INST_DT_MCP7904N(index) \
748 \
749 static struct mcp7940n_data mcp7940n_data_##index; \
750 \
751 static const struct mcp7940n_config mcp7940n_config_##index = { \
752 .generic = { \
753 .max_top_value = UINT32_MAX, \
754 .freq = 1, \
755 .flags = COUNTER_CONFIG_INFO_COUNT_UP, \
756 .channels = 2, \
757 }, \
758 .i2c = I2C_DT_SPEC_INST_GET(index), \
759 .int_gpios = GPIO_DT_SPEC_INST_GET_OR(index, int_gpios, {0}), \
760 }; \
761 \
762 DEVICE_DT_INST_DEFINE(index, mcp7940n_init, NULL, \
763 &mcp7940n_data_##index, \
764 &mcp7940n_config_##index, \
765 POST_KERNEL, \
766 CONFIG_COUNTER_INIT_PRIORITY, \
767 &mcp7940n_api);
768
769 DT_INST_FOREACH_STATUS_OKAY(INST_DT_MCP7904N);
770