/*
 * Copyright 2023 Cirrus Logic, Inc.
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Emulator for SBS 1.1 compliant smart battery charger.
 */

#define DT_DRV_COMPAT sbs_sbs_charger

#include <zephyr/device.h>
#include <zephyr/drivers/emul.h>
#include <zephyr/drivers/i2c.h>
#include <zephyr/drivers/i2c_emul.h>
#include <zephyr/logging/log.h>
#include <zephyr/sys/byteorder.h>

#include "sbs_charger.h"

LOG_MODULE_REGISTER(sbs_sbs_charger, CONFIG_CHARGER_LOG_LEVEL);

/** Static configuration for the emulator */
struct sbs_charger_emul_cfg {
	/** I2C address of emulator */
	uint16_t addr;
};

/** Run-time data used by the emulator */
struct sbs_charger_emul_data {
	uint16_t reg_charger_mode;
};

static int emul_sbs_charger_reg_write(const struct emul *target, int reg, int val)
{
	struct sbs_charger_emul_data *data = target->data;

	LOG_INF("write %x = %x", reg, val);
	switch (reg) {
	case SBS_CHARGER_REG_CHARGER_MODE:
		data->reg_charger_mode = val;
		break;
	default:
		LOG_ERR("Unknown write %x", reg);
		return -EIO;
	}

	return 0;
}

static int emul_sbs_charger_reg_read(const struct emul *target, int reg, int *val)
{
	switch (reg) {
	case SBS_CHARGER_REG_SPEC_INFO:
	case SBS_CHARGER_REG_CHARGER_MODE:
	case SBS_CHARGER_REG_STATUS:
	case SBS_CHARGER_REG_ALARM_WARNING:
		/* Arbitrary stub value. */
		*val = 1;
		break;
	default:
		LOG_ERR("Unknown register 0x%x read", reg);
		return -EIO;
	}
	LOG_INF("read 0x%x = 0x%x", reg, *val);

	return 0;
}

static int sbs_charger_emul_transfer_i2c(const struct emul *target, struct i2c_msg *msgs,
					 int num_msgs, int addr)
{
	/* Largely copied from emul_sbs_gauge.c */
	struct sbs_charger_emul_data *data;
	unsigned int val;
	int reg;
	int rc;

	data = target->data;

	i2c_dump_msgs_rw(target->dev, msgs, num_msgs, addr, false);
	switch (num_msgs) {
	case 2:
		if (msgs->flags & I2C_MSG_READ) {
			LOG_ERR("Unexpected read");
			return -EIO;
		}
		if (msgs->len != 1) {
			LOG_ERR("Unexpected msg0 length %d", msgs->len);
			return -EIO;
		}
		reg = msgs->buf[0];

		/* Now process the 'read' part of the message */
		msgs++;
		if (msgs->flags & I2C_MSG_READ) {
			switch (msgs->len - 1) {
			case 1:
				rc = emul_sbs_charger_reg_read(target, reg, &val);
				if (rc) {
					/* Return before writing bad value to message buffer */
					return rc;
				}

				/* SBS uses SMBus, which sends data in little-endian format. */
				sys_put_le16(val, msgs->buf);
				break;
			default:
				LOG_ERR("Unexpected msg1 length %d", msgs->len);
				return -EIO;
			}
		} else {
			/* We write a word (2 bytes by the SBS spec) */
			if (msgs->len != 2) {
				LOG_ERR("Unexpected msg1 length %d", msgs->len);
			}
			uint16_t value = sys_get_le16(msgs->buf);

			rc = emul_sbs_charger_reg_write(target, reg, value);
		}
		break;
	default:
		LOG_ERR("Invalid number of messages: %d", num_msgs);
		return -EIO;
	}

	return rc;
}

static const struct i2c_emul_api sbs_charger_emul_api_i2c = {
	.transfer = sbs_charger_emul_transfer_i2c,
};

static int emul_sbs_sbs_charger_init(const struct emul *target, const struct device *parent)
{
	ARG_UNUSED(target);
	ARG_UNUSED(parent);

	return 0;
}

/*
 * Main instantiation macro. SBS Charger Emulator only implemented for I2C
 */
#define SBS_CHARGER_EMUL(n)                                                                        \
	static struct sbs_charger_emul_data sbs_charger_emul_data_##n;                             \
                                                                                                   \
	static const struct sbs_charger_emul_cfg sbs_charger_emul_cfg_##n = {                      \
		.addr = DT_INST_REG_ADDR(n),                                                       \
	};                                                                                         \
	EMUL_DT_INST_DEFINE(n, emul_sbs_sbs_charger_init, &sbs_charger_emul_data_##n,              \
			    &sbs_charger_emul_cfg_##n, &sbs_charger_emul_api_i2c, NULL)

DT_INST_FOREACH_STATUS_OKAY(SBS_CHARGER_EMUL)
