/*
 * Copyright (c) 2019-2020 Peter Bigot Consulting, LLC
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#ifdef CONFIG_SOC_POSIX
#undef _POSIX_C_SOURCE
#define _POSIX_C_SOURCE 200809L /* Required for gmtime_r */
#endif

#define DT_DRV_COMPAT maxim_ds3231

#include <zephyr/device.h>
#include <zephyr/drivers/rtc/maxim_ds3231.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/i2c.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/sys/timeutil.h>
#include <zephyr/sys/util.h>

LOG_MODULE_REGISTER(DS3231, CONFIG_COUNTER_LOG_LEVEL);

#define REG_MONCEN_CENTURY 0x80
#define REG_HOURS_12H 0x40
#define REG_HOURS_PM 0x20
#define REG_HOURS_20 0x20
#define REG_HOURS_10 0x20
#define REG_DAYDATE_DOW 0x40
#define REG_ALARM_IGN 0x80

/* Return lower 32-bits of time as counter value */
#define COUNTER_GET(t) ((uint32_t) (t & UINT32_MAX))

enum {
	SYNCSM_IDLE,
	SYNCSM_PREP_READ,
	SYNCSM_FINISH_READ,
	SYNCSM_PREP_WRITE,
	SYNCSM_FINISH_WRITE,
};

struct register_map {
	uint8_t sec;
	uint8_t min;
	uint8_t hour;
	uint8_t dow;
	uint8_t dom;
	uint8_t moncen;
	uint8_t year;

	struct {
		uint8_t sec;
		uint8_t min;
		uint8_t hour;
		uint8_t date;
	} __packed alarm1;

	struct {
		uint8_t min;
		uint8_t hour;
		uint8_t date;
	} __packed alarm2;

	uint8_t ctrl;
	uint8_t ctrl_stat;
	uint8_t aging;
	int8_t temp_units;
	uint8_t temp_frac256;
};

struct ds3231_config {
	/* Common structure first because generic API expects this here. */
	struct counter_config_info generic;
	struct i2c_dt_spec bus;
	struct gpio_dt_spec isw_gpios;
};

struct ds3231_data {
	const struct device *ds3231;
	struct register_map registers;

	struct k_sem lock;

	/* Timer structure used for synchronization */
	struct k_timer sync_timer;

	/* Work structures for the various cases of ISW interrupt. */
	struct k_work alarm_work;
	struct k_work sqw_work;
	struct k_work sync_work;

	/* Forward ISW interrupt to proper worker. */
	struct gpio_callback isw_callback;

	/* syncclock captured in the last ISW interrupt handler */
	uint32_t isw_syncclock;

	struct maxim_ds3231_syncpoint syncpoint;
	struct maxim_ds3231_syncpoint new_sp;

	uint32_t syncclock_base;

	/* Pointer to the structure used to notify when a synchronize
	 * or set operation completes.  Null when nobody's waiting for
	 * such an operation, or when doing a no-notify synchronize
	 * through the signal API.
	 */
	union {
		void *ptr;
		struct sys_notify *notify;
		struct k_poll_signal *signal;
	} sync;

	/* Handlers and state when using the counter alarm API. */
	counter_alarm_callback_t counter_handler[2];
	uint32_t counter_ticks[2];

	/* Handlers and state for DS3231 alarm API. */
	maxim_ds3231_alarm_callback_handler_t alarm_handler[2];
	void *alarm_user_data[2];
	uint8_t alarm_flags[2];

	/* Flags recording requests for ISW monitoring. */
	uint8_t isw_mon_req;
#define ISW_MON_REQ_Alarm 0x01
#define ISW_MON_REQ_Sync 0x02

	/* Status of synchronization operations. */
	uint8_t sync_state;
	bool sync_signal;
};

/*
 * Set and clear specific bits in the control register.
 *
 * This function assumes the device register cache is valid and will
 * update the device only if the value changes as a result of applying
 * the set and clear changes.
 *
 * Caches and returns the value with the changes applied.
 */
static int sc_ctrl(const struct device *dev,
		   uint8_t set,
		   uint8_t clear)
{
	struct ds3231_data *data = dev->data;
	const struct ds3231_config *cfg = dev->config;
	struct register_map *rp = &data->registers;
	uint8_t ctrl = (rp->ctrl & ~clear) | set;
	int rc = ctrl;

	if (rp->ctrl != ctrl) {
		uint8_t buf[2] = {
			offsetof(struct register_map, ctrl),
			ctrl,
		};
		rc = i2c_write_dt(&cfg->bus, buf, sizeof(buf));
		if (rc >= 0) {
			rp->ctrl = ctrl;
			rc = ctrl;
		}
	}
	return rc;
}

int maxim_ds3231_ctrl_update(const struct device *dev,
			     uint8_t set_bits,
			     uint8_t clear_bits)
{
	struct ds3231_data *data = dev->data;

	k_sem_take(&data->lock, K_FOREVER);

	int rc = sc_ctrl(dev, set_bits, clear_bits);

	k_sem_give(&data->lock);

	return rc;
}

/*
 * Read the ctrl_stat register then set and clear bits in it.
 *
 * OSF, A1F, and A2F will be written with 1s if the corresponding bits
 * do not appear in either set or clear.  This ensures that if any
 * flag becomes set between the read and the write that indicator will
 * not be cleared.
 *
 * Returns the value as originally read (disregarding the effect of
 * clears and sets).
 */
static inline int rsc_stat(const struct device *dev,
			   uint8_t set,
			   uint8_t clear)
{
	uint8_t const ign = MAXIM_DS3231_REG_STAT_OSF | MAXIM_DS3231_ALARM1
			 | MAXIM_DS3231_ALARM2;
	struct ds3231_data *data = dev->data;
	const struct ds3231_config *cfg = dev->config;
	struct register_map *rp = &data->registers;
	uint8_t addr = offsetof(struct register_map, ctrl_stat);
	int rc;

	rc = i2c_write_read_dt(&cfg->bus, &addr, sizeof(addr), &rp->ctrl_stat,
			       sizeof(rp->ctrl_stat));
	if (rc >= 0) {
		uint8_t stat = rp->ctrl_stat & ~clear;

		if (rp->ctrl_stat != stat) {
			uint8_t buf[2] = {
				addr,
				stat | (ign & ~(set | clear)),
			};
			rc = i2c_write_dt(&cfg->bus, buf, sizeof(buf));
		}
		if (rc >= 0) {
			rc = rp->ctrl_stat;
		}
	}
	return rc;
}

int maxim_ds3231_stat_update(const struct device *dev,
			     uint8_t set_bits,
			     uint8_t clear_bits)
{
	struct ds3231_data *data = dev->data;

	k_sem_take(&data->lock, K_FOREVER);

	int rv = rsc_stat(dev, set_bits, clear_bits);

	k_sem_give(&data->lock);

	return rv;
}

/*
 * Look for current users of the interrupt/square-wave signal and
 * enable monitoring if and only if at least one consumer is active.
 */
static void validate_isw_monitoring(const struct device *dev)
{
	struct ds3231_data *data = dev->data;
	const struct ds3231_config *cfg = dev->config;
	const struct register_map *rp = &data->registers;
	uint8_t isw_mon_req = 0;

	if (rp->ctrl & (MAXIM_DS3231_ALARM1 | MAXIM_DS3231_ALARM2)) {
		isw_mon_req |= ISW_MON_REQ_Alarm;
	}
	if (data->sync_state != SYNCSM_IDLE) {
		isw_mon_req |= ISW_MON_REQ_Sync;
	}
	LOG_DBG("ISW %p : %d ?= %d", cfg->isw_gpios.port, isw_mon_req,
		data->isw_mon_req);
	if ((cfg->isw_gpios.port != NULL)
	    && (isw_mon_req != data->isw_mon_req)) {
		int rc = 0;

		/* Disable before reconfigure */
		rc = gpio_pin_interrupt_configure_dt(&cfg->isw_gpios,
						     GPIO_INT_DISABLE);

		if ((rc >= 0)
		    && ((isw_mon_req & ISW_MON_REQ_Sync)
			!= (data->isw_mon_req & ISW_MON_REQ_Sync))) {
			if (isw_mon_req & ISW_MON_REQ_Sync) {
				rc = sc_ctrl(dev, 0,
					     MAXIM_DS3231_REG_CTRL_INTCN
					     | MAXIM_DS3231_REG_CTRL_RS_Msk);
			} else {
				rc = sc_ctrl(dev, MAXIM_DS3231_REG_CTRL_INTCN, 0);
			}
		}

		data->isw_mon_req = isw_mon_req;

		/* Enable if any requests active */
		if ((rc >= 0) && (isw_mon_req != 0)) {
			rc = gpio_pin_interrupt_configure_dt(
				&cfg->isw_gpios, GPIO_INT_EDGE_TO_ACTIVE);
		}

		LOG_INF("ISW reconfigure to %x: %d", isw_mon_req, rc);
	}
}

static const uint8_t *decode_time(struct tm *tp,
			       const uint8_t *rp,
			       bool with_sec)
{
	uint8_t reg;

	if (with_sec) {
		reg = *rp++;

		tp->tm_sec = bcd2bin(reg & 0x7F);
	}

	reg = *rp++;
	tp->tm_min = bcd2bin(reg & 0x7F);

	reg = *rp++;
	tp->tm_hour = (reg & 0x0F);
	if (REG_HOURS_12H & reg) {
		/* 12-hour */
		if (REG_HOURS_10 & reg) {
			tp->tm_hour += 10;
		}
		if (REG_HOURS_PM & reg) {
			tp->tm_hour += 12;
		}
	} else {
		/* 24 hour */
		tp->tm_hour += 10 * ((reg >> 4) & 0x03);
	}

	return rp;
}

static uint8_t decode_alarm(const uint8_t *ap,
			 bool with_sec,
			 time_t *tp)
{
	struct tm tm = {
		/* tm_year zero is 1900 with underflows a 32-bit counter
		 * representation.  Use 1978-01, the first January after the
		 * POSIX epoch where the first day of the month is the first
		 * day of the week.
		 */
		.tm_year = 78,
	};
	const uint8_t *dp = decode_time(&tm, ap, with_sec);
	uint8_t flags = 0;
	uint8_t amf = MAXIM_DS3231_ALARM_FLAGS_IGNDA;

	/* Done decoding time, now decode day/date. */
	if (REG_DAYDATE_DOW & *dp) {
		flags |= MAXIM_DS3231_ALARM_FLAGS_DOW;

		/* Because tm.tm_wday does not contribute to the UNIX
		 * time that the civil time translates into, we need
		 * to also record the tm_mday for our selected base
		 * 1978-01 that will produce the correct tm_wday.
		 */
		tm.tm_mday = (*dp & 0x07);
		tm.tm_wday = tm.tm_mday - 1;
	} else {
		tm.tm_mday = bcd2bin(*dp & 0x3F);
	}

	/* Walk backwards to extract the alarm mask flags. */
	while (true) {
		if (REG_ALARM_IGN & *dp) {
			flags |= amf;
		}
		amf >>= 1;
		if (dp-- == ap) {
			break;
		}
	}

	/* Convert to the reduced representation. */
	*tp = timeutil_timegm(&tm);
	return flags;
}

static int encode_alarm(uint8_t *ap,
			bool with_sec,
			time_t time,
			uint8_t flags)
{
	struct tm tm;
	uint8_t val;

	(void)gmtime_r(&time, &tm);

	/* For predictable behavior the low 4 bits of flags
	 * (corresponding to AxMy) must be 0b1111, 0b1110, 0b1100,
	 * 0b1000, or 0b0000.  This corresponds to the bitwise inverse
	 * being one less than a power of two.
	 */
	if (!is_power_of_two(1U + (0x0F & ~flags))) {
		LOG_DBG("invalid alarm mask in flags: %02x", flags);
		return -EINVAL;
	}

	if (with_sec) {
		if (flags & MAXIM_DS3231_ALARM_FLAGS_IGNSE) {
			val = REG_ALARM_IGN;
		} else {
			val = bin2bcd(tm.tm_sec);
		}
		*ap++ = val;
	}

	if (flags & MAXIM_DS3231_ALARM_FLAGS_IGNMN) {
		val = REG_ALARM_IGN;
	} else {
		val = bin2bcd(tm.tm_min);
	}
	*ap++ = val;

	if (flags & MAXIM_DS3231_ALARM_FLAGS_IGNHR) {
		val = REG_ALARM_IGN;
	} else {
		val = bin2bcd(tm.tm_hour);
	}
	*ap++ = val;

	if (flags & MAXIM_DS3231_ALARM_FLAGS_IGNDA) {
		val = REG_ALARM_IGN;
	} else if (flags & MAXIM_DS3231_ALARM_FLAGS_DOW) {
		val = REG_DAYDATE_DOW | (tm.tm_wday + 1);
	} else {
		val = bin2bcd(tm.tm_mday);
	}
	*ap++ = val;

	return 0;
}

static uint32_t decode_rtc(struct ds3231_data *data)
{
	struct tm tm = { 0 };
	const struct register_map *rp = &data->registers;
	time_t t;

	decode_time(&tm, &rp->sec, true);
	tm.tm_wday = (rp->dow & 0x07) - 1;
	tm.tm_mday = bcd2bin(rp->dom & 0x3F);
	tm.tm_mon = 10 * (((0xF0 & ~REG_MONCEN_CENTURY) & rp->moncen) >> 4)
		    + (rp->moncen & 0x0F) - 1;
	tm.tm_year = bcd2bin(rp->year);
	if (REG_MONCEN_CENTURY & rp->moncen) {
		tm.tm_year += 100;
	}

	t = timeutil_timegm(&tm);
	return COUNTER_GET(t);
}

static int update_registers(const struct device *dev)
{
	struct ds3231_data *data = dev->data;
	const struct ds3231_config *cfg = dev->config;
	uint32_t syncclock;
	int rc;
	uint8_t addr = 0;

	data->syncclock_base = maxim_ds3231_read_syncclock(dev);
	rc = i2c_write_read_dt(&cfg->bus, &addr, sizeof(addr), &data->registers,
			       sizeof(data->registers));
	syncclock = maxim_ds3231_read_syncclock(dev);
	if (rc < 0) {
		return rc;
	}

	return 0;
}

int maxim_ds3231_get_alarm(const struct device *dev,
			   uint8_t id,
			   struct maxim_ds3231_alarm *cp)
{
	struct ds3231_data *data = dev->data;
	const struct ds3231_config *cfg = dev->config;
	int rv = 0;
	uint8_t addr;
	uint8_t len;

	if (id == 0) {
		addr = offsetof(struct register_map, alarm1);
		len = sizeof(data->registers.alarm1);
	} else if (id < cfg->generic.channels) {
		addr = offsetof(struct register_map, alarm2);
		len = sizeof(data->registers.alarm2);
	} else {
		rv = -EINVAL;
		goto out;
	}

	k_sem_take(&data->lock, K_FOREVER);

	/* Update alarm structure */
	uint8_t *rbp = &data->registers.sec + addr;

	rv = i2c_write_read_dt(&cfg->bus, &addr, sizeof(addr), rbp, len);

	if (rv < 0) {
		LOG_DBG("get_config at %02x failed: %d\n", addr, rv);
		goto out_locked;
	}

	*cp = (struct maxim_ds3231_alarm){ 0 };
	cp->flags = decode_alarm(rbp, (id == 0), &cp->time);
	cp->handler = data->alarm_handler[id];
	cp->user_data = data->alarm_user_data[id];

out_locked:
	k_sem_give(&data->lock);

out:
	return rv;
}

static int cancel_alarm(const struct device *dev,
			uint8_t id)
{
	struct ds3231_data *data = dev->data;

	data->alarm_handler[id] = NULL;
	data->alarm_user_data[id] = NULL;

	return sc_ctrl(dev, 0, MAXIM_DS3231_ALARM1 << id);
}

static int ds3231_counter_cancel_alarm(const struct device *dev,
				       uint8_t id)
{
	struct ds3231_data *data = dev->data;
	const struct ds3231_config *cfg = dev->config;
	int rv = 0;

	if (id >= cfg->generic.channels) {
		rv = -EINVAL;
		goto out;
	}

	k_sem_take(&data->lock, K_FOREVER);

	rv = cancel_alarm(dev, id);

	k_sem_give(&data->lock);

out:
	/* Throw away information counter API disallows */
	if (rv >= 0) {
		rv = 0;
	}

	return rv;
}

static int set_alarm(const struct device *dev,
		     uint8_t id,
		     const struct maxim_ds3231_alarm *cp)
{
	struct ds3231_data *data = dev->data;
	const struct ds3231_config *cfg = dev->config;
	uint8_t addr;
	uint8_t len;

	if (id == 0) {
		addr = offsetof(struct register_map, alarm1);
		len = sizeof(data->registers.alarm1);
	} else if (id < cfg->generic.channels) {
		addr = offsetof(struct register_map, alarm2);
		len = sizeof(data->registers.alarm2);
	} else {
		return -EINVAL;
	}

	uint8_t buf[5] = { addr };
	int rc = encode_alarm(buf + 1, (id == 0), cp->time, cp->flags);

	if (rc < 0) {
		return rc;
	}

	/* @todo resolve race condition: a previously stored alarm may
	 * trigger between clear of AxF and the write of the new alarm
	 * control.
	 */
	rc = rsc_stat(dev, 0U, (MAXIM_DS3231_ALARM1 << id));
	if (rc >= 0) {
		rc = i2c_write_dt(&cfg->bus, buf, len + 1);
	}
	if ((rc >= 0)
	    && (cp->handler != NULL)) {
		rc = sc_ctrl(dev, MAXIM_DS3231_ALARM1 << id, 0);
	}
	if (rc >= 0) {
		memmove(&data->registers.sec + addr, buf + 1, len);
		data->alarm_handler[id] = cp->handler;
		data->alarm_user_data[id] = cp->user_data;
		data->alarm_flags[id] = cp->flags;
		validate_isw_monitoring(dev);
	}

	return rc;
}

int maxim_ds3231_set_alarm(const struct device *dev,
			   uint8_t id,
			   const struct maxim_ds3231_alarm *cp)
{
	struct ds3231_data *data = dev->data;

	k_sem_take(&data->lock, K_FOREVER);

	int rc = set_alarm(dev, id, cp);

	k_sem_give(&data->lock);

	return rc;
}

int maxim_ds3231_check_alarms(const struct device *dev)
{
	struct ds3231_data *data = dev->data;
	const struct register_map *rp = &data->registers;
	uint8_t mask = (MAXIM_DS3231_ALARM1 | MAXIM_DS3231_ALARM2);

	k_sem_take(&data->lock, K_FOREVER);

	/* Fetch and clear only the alarm flags that are not
	 * interrupt-enabled.
	 */
	int rv = rsc_stat(dev, 0U, (rp->ctrl & mask) ^ mask);

	if (rv >= 0) {
		rv &= mask;
	}

	k_sem_give(&data->lock);

	return rv;
}

static int check_handled_alarms(const struct device *dev)
{
	struct ds3231_data *data = dev->data;
	const struct register_map *rp = &data->registers;
	uint8_t mask = (MAXIM_DS3231_ALARM1 | MAXIM_DS3231_ALARM2);

	/* Fetch and clear only the alarm flags that are
	 * interrupt-enabled.  Leave any flags that are not enabled;
	 * it may be an alarm that triggered a wakeup.
	 */
	mask &= rp->ctrl;

	int rv = rsc_stat(dev, 0U, mask);

	if (rv > 0) {
		rv &= mask;
	}

	return rv;
}

static void counter_alarm_forwarder(const struct device *dev,
				    uint8_t id,
				    uint32_t syncclock,
				    void *ud)
{
	/* Dummy handler marking a counter callback. */
}

static void alarm_worker(struct k_work *work)
{
	struct ds3231_data *data = CONTAINER_OF(work, struct ds3231_data,
						alarm_work);
	const struct device *ds3231 = data->ds3231;
	const struct ds3231_config *cfg = ds3231->config;

	k_sem_take(&data->lock, K_FOREVER);

	int af = check_handled_alarms(ds3231);

	while (af > 0) {
		uint8_t id;

		for (id = 0; id < cfg->generic.channels; ++id) {
			if ((af & (MAXIM_DS3231_ALARM1 << id)) == 0) {
				continue;
			}


			maxim_ds3231_alarm_callback_handler_t handler
				= data->alarm_handler[id];
			void *ud = data->alarm_user_data[id];

			if (data->alarm_flags[id] & MAXIM_DS3231_ALARM_FLAGS_AUTODISABLE) {
				int rc = cancel_alarm(ds3231, id);

				LOG_DBG("autodisable %d: %d", id, rc);
				validate_isw_monitoring(ds3231);
			}

			if (handler == counter_alarm_forwarder) {
				counter_alarm_callback_t cb = data->counter_handler[id];
				uint32_t ticks = data->counter_ticks[id];

				data->counter_handler[id] = NULL;
				data->counter_ticks[id] = 0;

				if (cb) {
					k_sem_give(&data->lock);

					cb(ds3231, id, ticks, ud);

					k_sem_take(&data->lock, K_FOREVER);
				}

			} else if (handler != NULL) {
				k_sem_give(&data->lock);

				handler(ds3231, id, data->isw_syncclock, ud);

				k_sem_take(&data->lock, K_FOREVER);
			}
		}
		af = check_handled_alarms(ds3231);
	}

	k_sem_give(&data->lock);

	if (af < 0) {
		LOG_ERR("failed to read alarm flags");
		return;
	}

	LOG_DBG("ALARM %02x at %u latency %u", af, data->isw_syncclock,
		maxim_ds3231_read_syncclock(ds3231) - data->isw_syncclock);
}

static void sqw_worker(struct k_work *work)
{
	struct ds3231_data *data = CONTAINER_OF(work, struct ds3231_data, sqw_work);
	uint32_t syncclock = maxim_ds3231_read_syncclock(data->ds3231);

	/* This is a placeholder for potential application-controlled
	 * use of the square wave functionality.
	 */
	LOG_DBG("SQW %u latency %u", data->isw_syncclock,
		syncclock - data->isw_syncclock);
}

static int read_time(const struct device *dev,
		     time_t *time)
{
	struct ds3231_data *data = dev->data;
	const struct ds3231_config *cfg = dev->config;
	uint8_t addr = 0;

	int rc = i2c_write_read_dt(&cfg->bus, &addr, sizeof(addr),
				   &data->registers, 7);

	if (rc >= 0) {
		*time = decode_rtc(data);
	}

	return rc;
}

static int ds3231_counter_get_value(const struct device *dev,
				    uint32_t *ticks)
{
	struct ds3231_data *data = dev->data;
	time_t time = 0;

	k_sem_take(&data->lock, K_FOREVER);

	int rc = read_time(dev, &time);

	k_sem_give(&data->lock);

	if (rc >= 0) {
		*ticks = COUNTER_GET(time);
	}

	return rc;
}

static void sync_finish(const struct device *dev,
			int rc)
{
	struct ds3231_data *data = dev->data;
	struct sys_notify *notify = NULL;
	struct k_poll_signal *signal = NULL;

	if (data->sync_signal) {
		signal = data->sync.signal;
	} else {
		notify = data->sync.notify;
	}
	data->sync.ptr = NULL;
	data->sync_signal = false;
	data->sync_state = SYNCSM_IDLE;
	(void)validate_isw_monitoring(dev);

	LOG_DBG("sync complete, notify %d to %p or %p\n", rc, notify, signal);
	k_sem_give(&data->lock);

	if (notify != NULL) {
		maxim_ds3231_notify_callback cb =
			(maxim_ds3231_notify_callback)sys_notify_finalize(notify, rc);

		if (cb) {
			cb(dev, notify, rc);
		}
	} else if (signal != NULL) {
		k_poll_signal_raise(signal, rc);
	}
}

static void sync_prep_read(const struct device *dev)
{
	struct ds3231_data *data = dev->data;
	int rc = sc_ctrl(dev, 0U, MAXIM_DS3231_REG_CTRL_INTCN
			 | MAXIM_DS3231_REG_CTRL_RS_Msk);

	if (rc < 0) {
		sync_finish(dev, rc);
		return;
	}
	data->sync_state = SYNCSM_FINISH_READ;
	validate_isw_monitoring(dev);
}

static void sync_finish_read(const struct device *dev)
{
	struct ds3231_data *data = dev->data;
	time_t time = 0;

	(void)read_time(dev, &time);
	data->syncpoint.rtc.tv_sec = time;
	data->syncpoint.rtc.tv_nsec = 0;
	data->syncpoint.syncclock = data->isw_syncclock;
	sync_finish(dev, 0);
}

static void sync_timer_handler(struct k_timer *tmr)
{
	struct ds3231_data *data = CONTAINER_OF(tmr, struct ds3231_data,
						sync_timer);

	LOG_INF("sync_timer fired");
	k_work_submit(&data->sync_work);
}

static void sync_prep_write(const struct device *dev)
{
	struct ds3231_data *data = dev->data;
	uint32_t syncclock = maxim_ds3231_read_syncclock(dev);
	uint32_t offset = syncclock - data->new_sp.syncclock;
	uint32_t syncclock_Hz = maxim_ds3231_syncclock_frequency(dev);
	uint32_t offset_s = offset / syncclock_Hz;
	uint32_t offset_ms = (offset % syncclock_Hz) * 1000U / syncclock_Hz;
	time_t when = data->new_sp.rtc.tv_sec;

	when += offset_s;
	offset_ms += data->new_sp.rtc.tv_nsec / NSEC_PER_USEC / USEC_PER_MSEC;
	if (offset_ms >= MSEC_PER_SEC) {
		offset_ms -= MSEC_PER_SEC;
	} else {
		when += 1;
	}

	uint32_t rem_ms = MSEC_PER_SEC - offset_ms;

	if (rem_ms < 5) {
		when += 1;
		rem_ms += MSEC_PER_SEC;
	}
	data->new_sp.rtc.tv_sec = when;
	data->new_sp.rtc.tv_nsec = 0;

	data->sync_state = SYNCSM_FINISH_WRITE;
	k_timer_start(&data->sync_timer, K_MSEC(rem_ms), K_NO_WAIT);
	LOG_INF("sync %u in %u ms after %u", COUNTER_GET(when), rem_ms, syncclock);
}

static void sync_finish_write(const struct device *dev)
{
	struct ds3231_data *data = dev->data;
	const struct ds3231_config *cfg = dev->config;
	time_t when = data->new_sp.rtc.tv_sec;
	struct tm tm;
	uint8_t buf[8];
	uint8_t *bp = buf;
	uint8_t val;

	*bp++ = offsetof(struct register_map, sec);

	(void)gmtime_r(&when, &tm);
	val = bin2bcd(tm.tm_sec);
	*bp++ = val;

	val = bin2bcd(tm.tm_min);
	*bp++ = val;

	val = bin2bcd(tm.tm_hour);
	*bp++ = val;

	*bp++ = 1 + tm.tm_wday;

	val = bin2bcd(tm.tm_mday);
	*bp++ = val;

	tm.tm_mon += 1;
	val = bin2bcd(tm.tm_mon);
	if (tm.tm_year >= 100) {
		tm.tm_year -= 100;
		val |= REG_MONCEN_CENTURY;
	}
	*bp++ = val;

	val = bin2bcd(tm.tm_year);
	*bp++ = val;

	uint32_t syncclock = maxim_ds3231_read_syncclock(dev);
	int rc = i2c_write_dt(&cfg->bus, buf, bp - buf);

	if (rc >= 0) {
		data->syncpoint.rtc.tv_sec = when;
		data->syncpoint.rtc.tv_nsec = 0;
		data->syncpoint.syncclock = syncclock;
		LOG_INF("sync %u at %u", COUNTER_GET(when), syncclock);
	}
	sync_finish(dev, rc);
}

static void sync_worker(struct k_work *work)
{
	struct ds3231_data *data = CONTAINER_OF(work, struct ds3231_data, sync_work);
	uint32_t syncclock = maxim_ds3231_read_syncclock(data->ds3231);
	bool unlock = true;

	k_sem_take(&data->lock, K_FOREVER);

	LOG_DBG("SYNC.%u %u latency %u", data->sync_state, data->isw_syncclock,
		syncclock - data->isw_syncclock);
	switch (data->sync_state) {
	default:
	case SYNCSM_IDLE:
		break;
	case SYNCSM_PREP_READ:
		sync_prep_read(data->ds3231);
		break;
	case SYNCSM_FINISH_READ:
		sync_finish_read(data->ds3231);
		break;
	case SYNCSM_PREP_WRITE:
		sync_prep_write(data->ds3231);
		break;
	case SYNCSM_FINISH_WRITE:
		sync_finish_write(data->ds3231);
		unlock = false;
		break;
	}

	if (unlock) {
		k_sem_give(&data->lock);
	}
}

static void isw_gpio_callback(const struct device *port,
			      struct gpio_callback *cb,
			      uint32_t pins)
{
	struct ds3231_data *data = CONTAINER_OF(cb, struct ds3231_data,
						isw_callback);

	data->isw_syncclock = maxim_ds3231_read_syncclock(data->ds3231);
	if (data->registers.ctrl & MAXIM_DS3231_REG_CTRL_INTCN) {
		k_work_submit(&data->alarm_work);
	} else if (data->sync_state != SYNCSM_IDLE) {
		k_work_submit(&data->sync_work);
	} else {
		k_work_submit(&data->sqw_work);
	}
}

int z_impl_maxim_ds3231_get_syncpoint(const struct device *dev,
				      struct maxim_ds3231_syncpoint *syncpoint)
{
	struct ds3231_data *data = dev->data;
	int rv = 0;

	k_sem_take(&data->lock, K_FOREVER);

	if (data->syncpoint.rtc.tv_sec == 0) {
		rv = -ENOENT;
	} else {
		__ASSERT_NO_MSG(syncpoint != NULL);
		*syncpoint = data->syncpoint;
	}

	k_sem_give(&data->lock);

	return rv;
}

int maxim_ds3231_synchronize(const struct device *dev,
			     struct sys_notify *notify)
{
	const struct ds3231_config *cfg = dev->config;
	struct ds3231_data *data = dev->data;
	int rv = 0;

	if (notify == NULL) {
		rv = -EINVAL;
		goto out;
	}

	if (cfg->isw_gpios.port == NULL) {
		rv = -ENOTSUP;
		goto out;
	}

	k_sem_take(&data->lock, K_FOREVER);

	if (data->sync_state != SYNCSM_IDLE) {
		rv = -EBUSY;
		goto out_locked;
	}

	data->sync_signal = false;
	data->sync.notify = notify;
	data->sync_state = SYNCSM_PREP_READ;

out_locked:
	k_sem_give(&data->lock);

	if (rv >= 0) {
		k_work_submit(&data->sync_work);
	}

out:
	return rv;
}

int z_impl_maxim_ds3231_req_syncpoint(const struct device *dev,
				      struct k_poll_signal *sig)
{
	const struct ds3231_config *cfg = dev->config;
	struct ds3231_data *data = dev->data;
	int rv = 0;

	if (cfg->isw_gpios.port == NULL) {
		rv = -ENOTSUP;
		goto out;
	}

	k_sem_take(&data->lock, K_FOREVER);

	if (data->sync_state != SYNCSM_IDLE) {
		rv = -EBUSY;
		goto out_locked;
	}

	data->sync_signal = true;
	data->sync.signal = sig;
	data->sync_state = SYNCSM_PREP_READ;

out_locked:
	k_sem_give(&data->lock);

	if (rv >= 0) {
		k_work_submit(&data->sync_work);
	}

out:
	return rv;
}

int maxim_ds3231_set(const struct device *dev,
		     const struct maxim_ds3231_syncpoint *syncpoint,
		     struct sys_notify *notify)
{
	const struct ds3231_config *cfg = dev->config;
	struct ds3231_data *data = dev->data;
	int rv = 0;

	if ((syncpoint == NULL)
	    || (notify == NULL)) {
		rv = -EINVAL;
		goto out;
	}
	if (cfg->isw_gpios.port == NULL) {
		rv = -ENOTSUP;
		goto out;
	}

	k_sem_take(&data->lock, K_FOREVER);

	if (data->sync_state != SYNCSM_IDLE) {
		rv = -EBUSY;
		goto out_locked;
	}

	data->new_sp = *syncpoint;
	data->sync_signal = false;
	data->sync.notify = notify;
	data->sync_state = SYNCSM_PREP_WRITE;

out_locked:
	k_sem_give(&data->lock);

	if (rv >= 0) {
		k_work_submit(&data->sync_work);
	}

out:
	return rv;
}

static int ds3231_init(const struct device *dev)
{
	struct ds3231_data *data = dev->data;
	const struct ds3231_config *cfg = dev->config;
	int rc;

	/* Initialize and take the lock */
	k_sem_init(&data->lock, 0, 1);

	data->ds3231 = dev;
	if (!device_is_ready(cfg->bus.bus)) {
		LOG_ERR("I2C device not ready");
		rc = -ENODEV;
		goto out;
	}

	rc = update_registers(dev);
	if (rc < 0) {
		LOG_WRN("Failed to fetch registers: %d", rc);
		goto out;
	}

	/* INTCN and AxIE to power-up default, RS to 1 Hz */
	rc = sc_ctrl(dev,
		     MAXIM_DS3231_REG_CTRL_INTCN,
		     MAXIM_DS3231_REG_CTRL_RS_Msk
		     | MAXIM_DS3231_ALARM1 | MAXIM_DS3231_ALARM2);
	if (rc < 0) {
		LOG_WRN("Failed to reset config: %d", rc);
		goto out;
	}

	/* Do not clear pending flags in the status register.  This
	 * device may have been used for external wakeup, which can be
	 * detected using the extended API.
	 */

	if (cfg->isw_gpios.port != NULL) {
		if (!gpio_is_ready_dt(&cfg->isw_gpios)) {
			LOG_ERR("INTn/SQW GPIO device not ready");
			rc = -ENODEV;
			goto out;
		}

		k_timer_init(&data->sync_timer, sync_timer_handler, NULL);
		k_work_init(&data->alarm_work, alarm_worker);
		k_work_init(&data->sqw_work, sqw_worker);
		k_work_init(&data->sync_work, sync_worker);
		gpio_init_callback(&data->isw_callback,
				   isw_gpio_callback,
				   BIT(cfg->isw_gpios.pin));

		rc = gpio_pin_configure_dt(&cfg->isw_gpios, GPIO_INPUT);
		if (rc >= 0) {
			rc = gpio_pin_interrupt_configure_dt(&cfg->isw_gpios,
							     GPIO_INT_DISABLE);
		}
		if (rc >= 0) {
			rc = gpio_add_callback(cfg->isw_gpios.port,
					       &data->isw_callback);
			if (rc < 0) {
				LOG_ERR("Failed to configure ISW callback: %d",
					rc);
			}
		}
	}

out:
	k_sem_give(&data->lock);

	LOG_DBG("Initialized %d", rc);
	if (rc > 0) {
		rc = 0;
	}

	return rc;
}

static int ds3231_counter_start(const struct device *dev)
{
	return -EALREADY;
}

static int ds3231_counter_stop(const struct device *dev)
{
	return -ENOTSUP;
}

int ds3231_counter_set_alarm(const struct device *dev,
			     uint8_t id,
			     const struct counter_alarm_cfg *alarm_cfg)
{
	struct ds3231_data *data = dev->data;
	const struct register_map *rp = &data->registers;
	const struct ds3231_config *cfg = dev->config;
	time_t when;
	int rc = 0;

	if (id >= cfg->generic.channels) {
		rc = -ENOTSUP;
		goto out;
	}

	k_sem_take(&data->lock, K_FOREVER);

	if (rp->ctrl & (MAXIM_DS3231_ALARM1 << id)) {
		rc = -EBUSY;
		goto out_locked;
	}

	if ((alarm_cfg->flags & COUNTER_ALARM_CFG_ABSOLUTE) == 0) {
		rc = read_time(dev, &when);
		if (rc >= 0) {
			when += alarm_cfg->ticks;
		}
	} else {
		when = alarm_cfg->ticks;
	}

	struct maxim_ds3231_alarm alarm = {
		.time = COUNTER_GET(when),
		.handler = counter_alarm_forwarder,
		.user_data = alarm_cfg->user_data,
		.flags = MAXIM_DS3231_ALARM_FLAGS_AUTODISABLE,
	};

	if (rc >= 0) {
		data->counter_handler[id] = alarm_cfg->callback;
		data->counter_ticks[id] = COUNTER_GET(alarm.time);
		rc = set_alarm(dev, id, &alarm);
	}

out_locked:
	k_sem_give(&data->lock);

out:
	/* Throw away information counter API disallows */
	if (rc >= 0) {
		rc = 0;
	}

	return rc;
}

static uint32_t ds3231_counter_get_top_value(const struct device *dev)
{
	return UINT32_MAX;
}

static uint32_t ds3231_counter_get_pending_int(const struct device *dev)
{
	return 0;
}

static int ds3231_counter_set_top_value(const struct device *dev,
					const struct counter_top_cfg *cfg)
{
	return -ENOTSUP;
}

static DEVICE_API(counter, ds3231_api) = {
	.start = ds3231_counter_start,
	.stop = ds3231_counter_stop,
	.get_value = ds3231_counter_get_value,
	.set_alarm = ds3231_counter_set_alarm,
	.cancel_alarm = ds3231_counter_cancel_alarm,
	.set_top_value = ds3231_counter_set_top_value,
	.get_pending_int = ds3231_counter_get_pending_int,
	.get_top_value = ds3231_counter_get_top_value,
};

static const struct ds3231_config ds3231_0_config = {
	.generic = {
		.max_top_value = UINT32_MAX,
		.freq = 1,
		.flags = COUNTER_CONFIG_INFO_COUNT_UP,
		.channels = 2,
	},
	.bus = I2C_DT_SPEC_INST_GET(0),
	/* Driver does not currently use 32k GPIO. */
	.isw_gpios = GPIO_DT_SPEC_INST_GET_OR(0, isw_gpios, {}),
};

static struct ds3231_data ds3231_0_data;

#if CONFIG_COUNTER_INIT_PRIORITY <= CONFIG_I2C_INIT_PRIORITY
#error CONFIG_COUNTER_INIT_PRIORITY must be greater than I2C_INIT_PRIORITY
#endif

DEVICE_DT_INST_DEFINE(0, ds3231_init, NULL, &ds3231_0_data,
		    &ds3231_0_config,
		    POST_KERNEL, CONFIG_COUNTER_INIT_PRIORITY,
		    &ds3231_api);

#ifdef CONFIG_USERSPACE

#include <zephyr/internal/syscall_handler.h>

int z_vrfy_maxim_ds3231_get_syncpoint(const struct device *dev,
				      struct maxim_ds3231_syncpoint *syncpoint)
{
	struct maxim_ds3231_syncpoint value;
	int rv;

	K_OOPS(K_SYSCALL_SPECIFIC_DRIVER(dev, K_OBJ_DRIVER_COUNTER, &ds3231_api));
	K_OOPS(K_SYSCALL_MEMORY_WRITE(syncpoint, sizeof(*syncpoint)));

	rv = z_impl_maxim_ds3231_get_syncpoint(dev, &value);

	if (rv >= 0) {
		K_OOPS(k_usermode_to_copy(syncpoint, &value, sizeof(*syncpoint)));
	}

	return rv;
}

#include <zephyr/syscalls/maxim_ds3231_get_syncpoint_mrsh.c>

int z_vrfy_maxim_ds3231_req_syncpoint(const struct device *dev,
				      struct k_poll_signal *sig)
{
	K_OOPS(K_SYSCALL_SPECIFIC_DRIVER(dev, K_OBJ_DRIVER_COUNTER, &ds3231_api));
	if (sig != NULL) {
		K_OOPS(K_SYSCALL_OBJ(sig, K_OBJ_POLL_SIGNAL));
	}

	return z_impl_maxim_ds3231_req_syncpoint(dev, sig);
}

#include <zephyr/syscalls/maxim_ds3231_req_syncpoint_mrsh.c>

#endif /* CONFIG_USERSPACE */
