/* Copyright 2021 The Chromium OS Authors. All rights reserved.
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#define DT_DRV_COMPAT zephyr_smart_battery

#define LOG_LEVEL CONFIG_I2C_LOG_LEVEL
#include <logging/log.h>
LOG_MODULE_REGISTER(smart_battery);

#include <device.h>
#include <emul.h>
#include <drivers/i2c.h>
#include <drivers/i2c_emul.h>

#include "emul/emul_smart_battery.h"

#include "crc8.h"
#include "battery_smart.h"

/** Run-time data used by the emulator */
struct sbat_emul_data {
	/** I2C emulator detail */
	struct i2c_emul emul;
	/** Smart battery device being emulated */
	const struct device *i2c;
	/** Configuration information */
	const struct sbat_emul_cfg *cfg;
	/** Data required to simulate battery */
	struct sbat_emul_bat_data bat;
	/** Command that should be handled next */
	int cur_cmd;
	/** Message buffer which is used to handle smb transactions */
	uint8_t msg_buf[MSG_BUF_LEN];
	/** Position in msg_buf if there is on going smb write opperation */
	int ong_write;
	/** Position in msg_buf if there is on going smb read opperation */
	int ong_read;
	/** Total bytes that were generated in response to smb read operation */
	int num_to_read;
	/** Custom write function called on smb write opperation */
	sbat_emul_custom_func write_custom_func;
	/** Data passed to custom write function */
	void *write_custom_func_data;
	/** Custom read function called on smb read opperation */
	sbat_emul_custom_func read_custom_func;
	/** Data passed to custom read function */
	void *read_custom_func_data;

	/** Mutex used to control access to battery data */
	struct k_mutex bat_mtx;
};

/** Static configuration for the emulator */
struct sbat_emul_cfg {
	/** Label of the I2C bus this emulator connects to */
	const char *i2c_label;
	/** Pointer to run-time data */
	struct sbat_emul_data *data;
	/** Address of smart battery on i2c bus */
	uint16_t addr;
};

/** Check description in emul_smart_battery.h */
struct sbat_emul_bat_data *sbat_emul_get_bat_data(struct i2c_emul *emul)
{
	struct sbat_emul_data *data;

	data = CONTAINER_OF(emul, struct sbat_emul_data, emul);

	return &data->bat;
}

/** Check description in emul_smart_battery.h */
int sbat_emul_lock_bat_data(struct i2c_emul *emul, k_timeout_t timeout)
{
	struct sbat_emul_data *data;

	data = CONTAINER_OF(emul, struct sbat_emul_data, emul);

	return k_mutex_lock(&data->bat_mtx, timeout);
}

/** Check description in emul_smart_battery.h */
int sbat_emul_unlock_bat_dat(struct i2c_emul *emul)
{
	struct sbat_emul_data *data;

	data = CONTAINER_OF(emul, struct sbat_emul_data, emul);

	return k_mutex_unlock(&data->bat_mtx);
}

/** Check description in emul_smart_battery.h */
void sbat_emul_set_custom_write_func(struct i2c_emul *emul,
				     sbat_emul_custom_func func, void *data)
{
	struct sbat_emul_data *emul_data;

	emul_data = CONTAINER_OF(emul, struct sbat_emul_data, emul);
	emul_data->write_custom_func = func;
	emul_data->write_custom_func_data = data;
}

/** Check description in emul_smart_battery.h */
void sbat_emul_set_custom_read_func(struct i2c_emul *emul,
				    sbat_emul_custom_func func, void *data)
{
	struct sbat_emul_data *emul_data;

	emul_data = CONTAINER_OF(emul, struct sbat_emul_data, emul);
	emul_data->read_custom_func = func;
	emul_data->read_custom_func_data = data;
}

/** Check description in emul_smart_battery.h */
uint16_t sbat_emul_date_to_word(unsigned int day, unsigned int month,
				unsigned int year)
{
	year   -= MANUFACTURE_DATE_YEAR_OFFSET;
	year  <<= MANUFACTURE_DATE_YEAR_SHIFT;
	year   &= MANUFACTURE_DATE_YEAR_MASK;
	month <<= MANUFACTURE_DATE_MONTH_SHIFT;
	month  &= MANUFACTURE_DATE_MONTH_MASK;
	day   <<= MANUFACTURE_DATE_DAY_SHIFT;
	day    &= MANUFACTURE_DATE_DAY_MASK;

	return day | month | year;
}

/**
 * @brief Compute CRC from the beginning of the message
 *
 * @param addr Smart battery address on SMBus
 * @param read If message for which CRC is computed is read. For read message
 *             byte command and repeated address is added to CRC
 * @param cmd Command used in read message
 *
 * @return pec CRC from first bytes of message
 */
static uint8_t sbat_emul_pec_head(uint8_t addr, int read, uint8_t cmd)
{
	uint8_t pec;

	addr <<= 1;

	pec = cros_crc8(&addr, 1);
	if (!read) {
		return pec;
	}

	pec = cros_crc8_arg(&cmd, 1, pec);
	addr |= I2C_MSG_READ;
	pec = cros_crc8_arg(&addr, 1, pec);

	return pec;
}

/**
 * @brief Convert from 10mW power units to mA current under given mV voltage
 *
 * @param mw Power in 10mW units
 * @param mv Voltage in mV units
 *
 * @return Current in mA units
 */
static uint16_t sbat_emul_10mw_to_ma(int mw, int mv)
{
	/* Smart battery use 10mW units, convert to mW */
	mw *= 10;
	/* Multiple by 1000 to get mA instead of A */
	return 1000 * mw/mv;
}

/**
 * @brief Convert from mA current to 10mW power under given mV voltage
 *
 * @param ma Current in mA units
 * @param mv Voltage in mV units
 *
 * @return Power in 10mW units
 */
static uint16_t sbat_emul_ma_to_10mw(int ma, int mv)
{
	int mw;
	/* Divide by 1000 to get mW instead of uW */
	mw = ma * mv / 1000;
	/* Smart battery use 10mW units, convert to 10mW */
	return mw / 10;
}

/**
 * @brief Get time in minutes how long it will take to get given amount of
 *        charge at given current flow
 *
 * @param bat Pointer to battery data to set error code in case of
 *            over/under flow in time calculation
 * @param rate Rate of current in mAh
 * @param cap Required amount of charge in mA
 * @param time Pointer to memory where calculated time will be stored
 *
 * @return 0 on success
 * @return -EINVAL when over or under flow occurred
 */
static int sbat_emul_get_time_to_complete(struct sbat_emul_bat_data *bat,
					  int rate, int cap, uint16_t *ret_time)
{
	int time;

	/* At negative rate process never ends, return maximum value */
	if (rate <= 0) {
		*ret_time = UINT16_MAX;

		return 0;
	}
	/* Convert capacity from mAh to mAmin */
	time = cap * 60 / rate;
	/* Check overflow */
	if (time >= UINT16_MAX) {
		*ret_time = UINT16_MAX;
		bat->error_code = STATUS_CODE_OVERUNDERFLOW;

		return -EINVAL;
	}
	/* Check underflow */
	if (time < 0) {
		*ret_time = 0;
		bat->error_code = STATUS_CODE_OVERUNDERFLOW;

		return -EINVAL;
	}

	*ret_time = time;

	return 0;
}

/**
 * @brief Get time in minutes how long it will take to charge battery
 *
 * @param bat Pointer to battery data
 * @param rate Rate of charging current in mAh
 * @param time Pointer to memory where calculated time will be stored
 *
 * @return 0 on success
 * @return -EINVAL when over or under flow occurred
 */
static int sbat_emul_time_to_full(struct sbat_emul_bat_data *bat, int rate,
				  uint16_t *time)
{
	int cap;

	cap = bat->full_cap - bat->cap;
	return sbat_emul_get_time_to_complete(bat, rate, cap, time);
}

/**
 * @brief Get time in minutes how long it will take to discharge battery. Note,
 *        that rate should be negative to indicate discharging.
 *
 * @param bat Pointer to battery data
 * @param rate Rate of charging current in mAh
 * @param time Pointer to memory where calculated time will be stored
 *
 * @return 0 on success
 * @return -EINVAL when over or under flow occurred
 */
static int sbat_emul_time_to_empty(struct sbat_emul_bat_data *bat, int rate,
				   uint16_t *time)
{
	int cap;

	/* Reverse to have discharging rate instead of charging rate */
	rate = -rate;
	cap = bat->cap;
	return sbat_emul_get_time_to_complete(bat, rate, cap, time);
}

/**
 * @brief Check if battery can supply for 10 seconds additional power/current
 *        set in at_rate register.
 *
 * @param bat Pointer to battery data
 * @param rate Rate of charging current in mAh
 * @param ok Pointer to memory where 0 is written if battery is able to supply
 *           additional power/curent or 1 is written if battery is unable
 *           to do so.
 *
 * @return 0 on success
 */
static int sbat_emul_read_at_rate_ok(struct sbat_emul_bat_data *bat,
				     uint16_t *ok)
{
	int rem_time_s;
	int rate;
	int cap;

	rate = bat->at_rate;
	if (bat->mode & MODE_CAPACITY) {
		rate = sbat_emul_10mw_to_ma(rate, bat->design_mv);
	}

	/* Add current battery usage */
	rate += bat->cur;
	if (rate >= 0) {
		/* Battery will be charged */
		*ok = 1;

		return 0;
	}
	/* Reverse to have discharging rate instead of charging rate */
	rate = -rate;

	rem_time_s = bat->cap * 3600 / rate;
	if (rem_time_s > 10) {
		/*
		 * Battery can support 10 seconds of additional at_rate
		 * current/power
		 */
		*ok = 1;
	} else {
		*ok = 0;
	}

	return 0;
}

/**
 * @brief Get battery status. This function use emulated status register and
 *        set or clear some of the flags based on other properties of emulated
 *        smart battery. Discharge bit, capacity alarm, time alarm, fully
 *        discharged bit and error code are controlled by battery properties.
 *        Terminate charge/discharge/overcharge alarms are set only if they are
 *        set in emulated status register and battery is charging/discharging,
 *        so they are partialy controlled by emulated status register.
 *        Other bits are controlled by emulated status register
 *
 * @param emul Pointer to smart battery emulator
 *
 * @return value which equals to computed status register
 */
static uint16_t sbat_emul_read_status(struct i2c_emul *emul)
{
	uint16_t status, cap, rem_time, charge_percent;
	struct sbat_emul_bat_data *bat;
	struct sbat_emul_data *data;

	data = CONTAINER_OF(emul, struct sbat_emul_data, emul);
	bat = &data->bat;

	status = bat->status;

	/*
	 * Over charged and terminate charger alarm cannot appear when battery
	 * is not charged
	 */
	if (bat->cur <= 0) {
		status &= ~(STATUS_TERMINATE_CHARGE_ALARM |
			    STATUS_OVERCHARGED_ALARM);
		status |= STATUS_DISCHARGING;
	}
	/* Terminate discharge alarm cannot appear when battery is charged */
	if (bat->cur >= 0) {
		status &= ~(STATUS_TERMINATE_DISCHARGE_ALARM |
			    STATUS_DISCHARGING);
	}

	sbat_emul_get_word_val(emul, SB_REMAINING_CAPACITY, &cap);
	if (bat->cap_alarm && cap < bat->cap_alarm) {
		status |= STATUS_REMAINING_CAPACITY_ALARM;
	} else {
		status &= ~STATUS_REMAINING_CAPACITY_ALARM;
	}

	sbat_emul_get_word_val(emul, SB_AVERAGE_TIME_TO_EMPTY, &rem_time);
	if (bat->time_alarm && rem_time < bat->time_alarm) {
		status |= STATUS_REMAINING_TIME_ALARM;
	} else {
		status &= ~STATUS_REMAINING_TIME_ALARM;
	}

	/* Unset fully discharged bit when charge is grater than 20% */
	sbat_emul_get_word_val(emul, SB_RELATIVE_STATE_OF_CHARGE,
			       &charge_percent);
	if (charge_percent > 20) {
		status &= ~STATUS_FULLY_DISCHARGED;
	} else {
		status |= STATUS_FULLY_DISCHARGED;
	}

	status |= bat->error_code & STATUS_ERR_CODE_MASK;

	return status;
}

/** Check description in emul_smart_battery.h */
int sbat_emul_get_word_val(struct i2c_emul *emul, int cmd, uint16_t *val)
{
	struct sbat_emul_bat_data *bat;
	struct sbat_emul_data *data;
	int mode_mw;
	int rate;

	data = CONTAINER_OF(emul, struct sbat_emul_data, emul);
	bat = &data->bat;
	mode_mw = bat->mode & MODE_CAPACITY;

	switch (cmd) {
	case SB_MANUFACTURER_ACCESS:
		*val = bat->mf_access;
		return 0;
	case SB_REMAINING_CAPACITY_ALARM:
		*val = bat->cap_alarm;
		return 0;
	case SB_REMAINING_TIME_ALARM:
		*val = bat->time_alarm;
		return 0;
	case SB_BATTERY_MODE:
		*val = bat->mode;
		return 0;
	case SB_AT_RATE:
		*val = bat->at_rate;
		return 0;
	case SB_AT_RATE_TIME_TO_FULL:
		/* Support for reporting time to full in mW mode is optional */
		if (mode_mw && !bat->at_rate_full_mw_support) {
			bat->error_code = STATUS_CODE_OVERUNDERFLOW;
			*val = UINT16_MAX;

			return -EINVAL;
		}

		rate = bat->at_rate;
		if (mode_mw) {
			rate = sbat_emul_10mw_to_ma(rate, bat->design_mv);
		}
		return sbat_emul_time_to_full(bat, rate, val);

	case SB_AT_RATE_TIME_TO_EMPTY:
		rate = bat->at_rate;
		if (mode_mw) {
			rate = sbat_emul_10mw_to_ma(rate, bat->design_mv);
		}
		return sbat_emul_time_to_empty(bat, rate, val);

	case SB_AT_RATE_OK:
		return sbat_emul_read_at_rate_ok(bat, val);
	case SB_TEMPERATURE:
		*val = bat->temp;
		return 0;
	case SB_VOLTAGE:
		*val = bat->volt;
		return 0;
	case SB_CURRENT:
		*val = bat->cur;
		return 0;
	case SB_AVERAGE_CURRENT:
		*val = bat->avg_cur;
		return 0;
	case SB_MAX_ERROR:
		*val = bat->max_error;
		return 0;
	case SB_RELATIVE_STATE_OF_CHARGE:
		/* Percent of charge according to full capacity */
		*val = 100 * bat->cap / bat->full_cap;
		return 0;
	case SB_ABSOLUTE_STATE_OF_CHARGE:
		/* Percent of charge according to design capacity */
		*val = 100 * bat->cap / bat->design_cap;
		return 0;
	case SB_REMAINING_CAPACITY:
		if (mode_mw) {
			*val = sbat_emul_ma_to_10mw(bat->cap, bat->design_mv);
		} else {
			*val = bat->cap;
		}
		return 0;
	case SB_FULL_CHARGE_CAPACITY:
		if (mode_mw) {
			*val = sbat_emul_ma_to_10mw(bat->full_cap,
						    bat->design_mv);
		} else {
			*val = bat->full_cap;
		}
		return 0;
	case SB_RUN_TIME_TO_EMPTY:
		rate = bat->cur;
		return sbat_emul_time_to_empty(bat, rate, val);
	case SB_AVERAGE_TIME_TO_EMPTY:
		rate = bat->avg_cur;
		return sbat_emul_time_to_empty(bat, rate, val);
	case SB_AVERAGE_TIME_TO_FULL:
		rate = bat->avg_cur;
		return sbat_emul_time_to_full(bat, rate, val);
	case SB_CHARGING_CURRENT:
		*val = bat->desired_charg_cur;
		return 0;
	case SB_CHARGING_VOLTAGE:
		*val = bat->desired_charg_volt;
		return 0;
	case SB_BATTERY_STATUS:
		*val = sbat_emul_read_status(emul);
		return 0;
	case SB_CYCLE_COUNT:
		*val = bat->cycle_count;
		return 0;
	case SB_DESIGN_CAPACITY:
		if (mode_mw) {
			*val = sbat_emul_ma_to_10mw(bat->design_cap,
						    bat->design_mv);
		} else {
			*val = bat->design_cap;
		}
		return 0;
	case SB_DESIGN_VOLTAGE:
		*val = bat->design_mv;
		return 0;
	case SB_SPECIFICATION_INFO:
		*val = bat->spec_info;
		return 0;
	case SB_MANUFACTURE_DATE:
		*val = bat->mf_date;
		return 0;
	case SB_SERIAL_NUMBER:
		*val = bat->sn;
		return 0;
	default:
		/* Unknown command or return value is not word */
		return 1;
	}
}

/** Check description in emul_smart_battery.h */
int sbat_emul_get_block_data(struct i2c_emul *emul, int cmd, uint8_t **blk,
			     int *len)
{
	struct sbat_emul_bat_data *bat;
	struct sbat_emul_data *data;

	data = CONTAINER_OF(emul, struct sbat_emul_data, emul);
	bat = &data->bat;

	switch (cmd) {
	case SB_MANUFACTURER_NAME:
		*blk = bat->mf_name;
		*len = bat->mf_name_len;
		return 0;
	case SB_DEVICE_NAME:
		*blk = bat->dev_name;
		*len = bat->dev_name_len;
		return 0;
	case SB_DEVICE_CHEMISTRY:
		*blk = bat->dev_chem;
		*len = bat->dev_chem_len;
		return 0;
	case SB_MANUFACTURER_DATA:
		*blk = bat->mf_data;
		*len = bat->mf_data_len;
		return 0;
	default:
		/* Unknown command or return value is not word */
		return 1;
	}
}

/**
 * @brief Append PEC to read command response if battery support it
 *
 * @param data Pointer to smart battery emulator data
 */
static void sbat_emul_append_pec(struct sbat_emul_data *data)
{
	uint8_t pec;

	if (BATTERY_SPEC_VERSION(data->bat.spec_info) ==
	    BATTERY_SPEC_VER_1_1_WITH_PEC) {
		pec = sbat_emul_pec_head(data->cfg->addr, 1, data->cur_cmd);
		pec = cros_crc8_arg(data->msg_buf, data->num_to_read, pec);
		data->msg_buf[data->num_to_read] = pec;
		data->num_to_read++;
	}
}

/**
 * @brief Function which handles read messages. It expects that data->cur_cmd
 *        is set to command number which should be handled. It guarantee that
 *        data->num_to_read is set to number of bytes in data->msg_buf on
 *        successful handling read request. On error, data->num_to_read is
 *        always set to 0.
 *
 * @param emul Pointer to smart battery emulator
 *
 * @return 0 on success
 * @return -EIO on error
 */
static int sbat_emul_handle_read_msg(struct i2c_emul *emul)
{
	struct sbat_emul_data *data;
	uint16_t word;
	uint8_t *blk;
	int ret, len;

	data = CONTAINER_OF(emul, struct sbat_emul_data, emul);

	data->num_to_read = 0;
	if (data->read_custom_func != NULL) {
		ret = data->read_custom_func(emul, data->msg_buf,
					     &data->num_to_read, data->cur_cmd,
					     data->read_custom_func_data);
		if (ret < 0) {
			data->bat.error_code = STATUS_CODE_UNKNOWN_ERROR;
			data->num_to_read = 0;

			return -EIO;
		} else if (ret == 0) {
			data->bat.error_code = STATUS_CODE_OK;
			sbat_emul_append_pec(data);

			return 0;
		}
	}

	if (data->cur_cmd == SBAT_EMUL_NO_CMD) {
		/* Unexpected read message without preceding command select */
		data->bat.error_code = STATUS_CODE_UNKNOWN_ERROR;
		return -EIO;
	}

	/* Handle commands which return word */
	ret = sbat_emul_get_word_val(emul, data->cur_cmd, &word);
	if (ret < 0) {
		return -EIO;
	}
	if (ret == 0) {
		data->num_to_read = 2;
		data->msg_buf[0] = word & 0xff;
		data->msg_buf[1] = (word >> 8) & 0xff;
		data->bat.error_code = STATUS_CODE_OK;
		sbat_emul_append_pec(data);

		return 0;
	}

	/* Handle commands which return block */
	ret = sbat_emul_get_block_data(emul, data->cur_cmd, &blk, &len);
	if (ret != 0) {
		if (ret == 1) {
			data->bat.error_code = STATUS_CODE_UNSUPPORTED;
			LOG_ERR("Unknown read command (0x%x)", data->cur_cmd);
		}

		return -EIO;
	}

	data->num_to_read = len + 1;
	data->msg_buf[0] = len;
	memcpy(&data->msg_buf[1], blk, len);
	data->bat.error_code = STATUS_CODE_OK;
	sbat_emul_append_pec(data);

	return 0;
}

/**
 * @brief Function which finalize write messages. It expects that
 *        data->ong_write is set to number of bytes received in data->msg_buf.
 *        It guarantee that data->ong_write is set to 0.
 *
 * @param emul Pointer to smart battery emulator
 *
 * @return 0 on success
 * @return -EIO on error
 */
static int sbat_emul_finalize_write_msg(struct i2c_emul *emul)
{
	struct sbat_emul_bat_data *bat;
	struct sbat_emul_data *data;
	uint16_t word;
	uint8_t pec;
	int ret;

	data = CONTAINER_OF(emul, struct sbat_emul_data, emul);
	bat = &data->bat;

	if (data->write_custom_func != NULL) {
		ret = data->write_custom_func(emul, data->msg_buf,
					      &data->ong_write,
					      data->msg_buf[0],
					      data->write_custom_func_data);
		if (ret < 0) {
			data->bat.error_code = STATUS_CODE_UNKNOWN_ERROR;
			data->ong_write = 0;

			return -EIO;
		} else if (ret == 0) {
			data->bat.error_code = STATUS_CODE_OK;
			data->ong_write = 0;

			return 0;
		}
	}

	/*
	 * Fail if:
	 *  - there are no bytes to handle
	 *  - there are too many bytes
	 *  - there is command byte and only one data byte
	 */
	if (data->ong_write <= 0 ||
	    data->ong_write > 4 ||
	    data->ong_write == 2) {
		data->bat.error_code = STATUS_CODE_BADSIZE;
		data->ong_write = 0;
		LOG_ERR("wrong write message size (%d)", data->ong_write);

		return -EIO;
	}

	/* There is only command for read */
	if (data->ong_write == 1) {
		data->cur_cmd = data->msg_buf[0];
		data->ong_write = 0;
		return 0;
	}

	/* Handle PEC */
	if (data->ong_write == 4) {
		if (BATTERY_SPEC_VERSION(data->bat.spec_info) !=
		    BATTERY_SPEC_VER_1_1_WITH_PEC) {
			data->bat.error_code = STATUS_CODE_BADSIZE;
			data->ong_write = 0;
			LOG_ERR("Unexpected PEC; No support in this version");

			return -EIO;
		}
		pec = sbat_emul_pec_head(data->cfg->addr, 0, 0);
		pec = cros_crc8_arg(data->msg_buf, 3, pec);
		if (pec != data->msg_buf[3]) {
			data->bat.error_code = STATUS_CODE_UNKNOWN_ERROR;
			data->ong_write = 0;
			LOG_ERR("Wrong PEC 0x%x != 0x%x",
				pec, data->msg_buf[3]);

			return -EIO;
		}
	}

	word = ((int)data->msg_buf[2] << 8) | data->msg_buf[1];

	switch (data->msg_buf[0]) {
	case SB_MANUFACTURER_ACCESS:
		bat->mf_access = word;
		break;
	case SB_REMAINING_CAPACITY_ALARM:
		bat->cap_alarm = word;
		break;
	case SB_REMAINING_TIME_ALARM:
		bat->time_alarm = word;
		break;
	case SB_BATTERY_MODE:
		/* Allow to set only upper byte */
		bat->mode &= 0xff;
		bat->mode |= word & 0xff00;
		break;
	case SB_AT_RATE:
		bat->at_rate = word;
		break;
	default:
		data->ong_write = 0;
		data->bat.error_code = STATUS_CODE_ACCESS_DENIED;
		LOG_ERR("Unknown write command (0x%x)", data->msg_buf[0]);

		return -EIO;
	}

	data->ong_write = 0;
	data->bat.error_code = STATUS_CODE_OK;

	return 0;
}

/**
 * Emulator an I2C transfer to an smart battery
 *
 * This handles simple reads and writes
 *
 * @param emul I2C emulation information
 * @param msgs List of messages to process. For 'read' messages, this function
 *             updates the 'buf' member with the data that was read
 * @param num_msgs Number of messages to process
 * @param addr Address of the I2C target device.
 *
 * @retval 0 If successful
 * @retval -EIO General input / output error
 */
static int sbat_emul_transfer(struct i2c_emul *emul, struct i2c_msg *msgs,
			      int num_msgs, int addr)
{
	const struct sbat_emul_cfg *cfg;
	struct sbat_emul_data *data;
	unsigned int len;
	int ret = 0, i;

	data = CONTAINER_OF(emul, struct sbat_emul_data, emul);
	cfg = data->cfg;

	if (cfg->addr != addr) {
		LOG_ERR("Address mismatch, expected %02x, got %02x", cfg->addr,
			addr);
		return -EIO;
	}

	i2c_dump_msgs("emul", msgs, num_msgs, addr);

	for (; num_msgs > 0; num_msgs--, msgs++) {
		if (!(msgs->flags & I2C_MSG_READ)) {
			/* Disscard any ongoing read */
			data->num_to_read = 0;

			/* Save incoming data to buffer */
			for (i = 0; i < msgs->len; i++, data->ong_write++) {
				if (data->ong_write < MSG_BUF_LEN) {
					data->msg_buf[data->ong_write] =
								msgs->buf[i];
				}
			}

			/* Handle write message when we receive stop signal */
			if (msgs->flags & I2C_MSG_STOP) {
				k_mutex_lock(&data->bat_mtx, K_FOREVER);
				ret = sbat_emul_finalize_write_msg(emul);
				k_mutex_unlock(&data->bat_mtx);
			}
		} else {
			/* Finalize any ongoing write message before read */
			if (data->ong_write) {
				k_mutex_lock(&data->bat_mtx, K_FOREVER);
				ret = sbat_emul_finalize_write_msg(emul);
				k_mutex_unlock(&data->bat_mtx);
				if (ret) {
					return -EIO;
				}
			}

			/* Prepare read message */
			if (!data->num_to_read) {
				k_mutex_lock(&data->bat_mtx, K_FOREVER);
				ret = sbat_emul_handle_read_msg(emul);
				k_mutex_unlock(&data->bat_mtx);
				data->cur_cmd = SBAT_EMUL_NO_CMD;
				data->ong_read = 0;
			}

			for (i = 0; i < msgs->len; i++, data->ong_read++) {
				if (data->ong_read >= data->num_to_read) {
					/* We wrote everything */
					data->num_to_read = 0;
					break;
				}
				msgs->buf[i] = data->msg_buf[data->ong_read];
			}

			if (msgs->flags & I2C_MSG_STOP) {
				/* Disscard any data that wern't read */
				data->num_to_read = 0;
			}
		}

		if (ret) {
			return -EIO;
		}
	}

	return 0;
}

/* Device instantiation */

static struct i2c_emul_api sbat_emul_api = {
	.transfer = sbat_emul_transfer,
};

/**
 * @brief Set up a new Smart Battery emulator
 *
 * This should be called for each Smart Battery device that needs to be
 * emulated. It registers it with the I2C emulation controller.
 *
 * @param emul Emulation information
 * @param parent Device to emulate
 *
 * @return 0 indicating success (always)
 */
static int sbat_emul_init(const struct emul *emul,
			  const struct device *parent)
{
	const struct sbat_emul_cfg *cfg = emul->cfg;
	struct sbat_emul_data *data = cfg->data;
	int ret;

	data->emul.api = &sbat_emul_api;
	data->emul.addr = cfg->addr;
	data->i2c = parent;
	data->cfg = cfg;
	k_mutex_init(&data->bat_mtx);

	ret = i2c_emul_register(parent, emul->dev_label, &data->emul);

	return ret;
}

#define SMART_BATTERY_EMUL(n)						\
	static struct sbat_emul_data sbat_emul_data_##n = {		\
		.bat = {						\
			.mf_access = DT_INST_PROP(n, mf_access),	\
			.at_rate_full_mw_support = DT_INST_PROP(n,	\
					at_rate_full_mw_support),	\
			.spec_info = ((DT_ENUM_TOKEN(DT_DRV_INST(n),	\
						     version) <<	\
				       BATTERY_SPEC_VERSION_SHIFT) &	\
				      BATTERY_SPEC_VERSION_MASK) |	\
				     ((DT_INST_PROP(n, vscale) <<	\
				       BATTERY_SPEC_VSCALE_SHIFT) &	\
				      BATTERY_SPEC_VSCALE_MASK) |	\
				     ((DT_INST_PROP(n, ipscale) <<	\
				       BATTERY_SPEC_IPSCALE_SHIFT) &	\
				      BATTERY_SPEC_IPSCALE_MASK) |	\
				     BATTERY_SPEC_REVISION_1,		\
			.mode = (DT_INST_PROP(n,			\
					      int_charge_controller) *	\
				 MODE_INTERNAL_CHARGE_CONTROLLER) |	\
				(DT_INST_PROP(n, primary_battery) *	\
				 MODE_PRIMARY_BATTERY_SUPPORT),		\
			.design_mv = DT_INST_PROP(n, design_mv),	\
			.design_cap = DT_INST_PROP(n, design_cap),	\
			.temp = DT_INST_PROP(n, temperature),		\
			.volt = DT_INST_PROP(n, volt),			\
			.cur = DT_INST_PROP(n, cur),			\
			.avg_cur = DT_INST_PROP(n, avg_cur),		\
			.max_error = DT_INST_PROP(n, max_error),	\
			.cap = DT_INST_PROP(n, cap),			\
			.full_cap = DT_INST_PROP(n, full_cap),		\
			.desired_charg_cur = DT_INST_PROP(n,		\
						desired_charg_cur),	\
			.desired_charg_volt = DT_INST_PROP(n,		\
						desired_charg_volt),	\
			.cycle_count = DT_INST_PROP(n, cycle_count),	\
			.sn = DT_INST_PROP(n, serial_number),		\
			.mf_name = DT_INST_PROP(n, mf_name),		\
			.mf_name_len = sizeof(				\
					DT_INST_PROP(n, mf_name)) - 1,	\
			.mf_data = DT_INST_PROP(n, mf_data),		\
			.mf_data_len = sizeof(				\
					DT_INST_PROP(n, mf_data)) - 1,	\
			.dev_name = DT_INST_PROP(n, dev_name),		\
			.dev_name_len = sizeof(				\
					DT_INST_PROP(n, dev_name)) - 1,	\
			.dev_chem = DT_INST_PROP(n, dev_chem),		\
			.dev_chem_len = sizeof(				\
					DT_INST_PROP(n, dev_chem)) - 1,	\
			.mf_date = 0,					\
			.cap_alarm = 0,					\
			.time_alarm = 0,				\
			.at_rate = 0,					\
			.status = STATUS_INITIALIZED,			\
			.error_code = STATUS_CODE_OK,			\
		},							\
		.cur_cmd = SBAT_EMUL_NO_CMD,				\
		.write_custom_func = NULL,				\
		.read_custom_func = NULL,				\
	};								\
									\
	static const struct sbat_emul_cfg sbat_emul_cfg_##n = {		\
		.i2c_label = DT_INST_BUS_LABEL(n),			\
		.data = &sbat_emul_data_##n,				\
		.addr = DT_INST_REG_ADDR(n),				\
	};								\
	EMUL_DEFINE(sbat_emul_init, DT_DRV_INST(n), &sbat_emul_cfg_##n)

DT_INST_FOREACH_STATUS_OKAY(SMART_BATTERY_EMUL)

#define SMART_BATTERY_EMUL_CASE(n)					\
	case DT_INST_DEP_ORD(n): return &sbat_emul_data_##n.emul;

/** Check description in emul_smart_battery.h */
struct i2c_emul *sbat_emul_get_ptr(int ord)
{
	switch (ord) {
	DT_INST_FOREACH_STATUS_OKAY(SMART_BATTERY_EMUL_CASE)

	default:
		return NULL;
	}
}
