summaryrefslogtreecommitdiff
path: root/zephyr/emul/emul_smart_battery.c
diff options
context:
space:
mode:
authorTomasz Michalec <tm@semihalf.com>2021-05-11 12:41:38 +0200
committerCommit Bot <commit-bot@chromium.org>2021-05-18 20:59:50 +0000
commit4698c8c20a44c5519d4ddabc3f409e0d9f9f5c68 (patch)
tree639a67053eb652b882190619a1996518504bbff5 /zephyr/emul/emul_smart_battery.c
parent59780263fcca42d1b411fddf48ebcba683d5f26b (diff)
downloadchrome-ec-4698c8c20a44c5519d4ddabc3f409e0d9f9f5c68.tar.gz
zephyr: Add Smart Battery emulator
Add Smart Battery emulator which is emulated device on i2c bus. Emulated battery properties are defined through device tree, but they can be changed in runtime through Smart Battery emulator API. It allows to set custom handlers for write and read messages to emulate more complex scenarios or malfunctioning device. BUG=b:184855975 BRANCH=none TEST=none Signed-off-by: Tomasz Michalec <tm@semihalf.com> Change-Id: Ia94a0a122123e3259882dfdc80d067c61c98379b Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/ec/+/2903206 Reviewed-by: Jeremy Bettis <jbettis@chromium.org> Reviewed-by: Simon Glass <sjg@chromium.org> Commit-Queue: Simon Glass <sjg@chromium.org> Tested-by: Simon Glass <sjg@chromium.org>
Diffstat (limited to 'zephyr/emul/emul_smart_battery.c')
-rw-r--r--zephyr/emul/emul_smart_battery.c995
1 files changed, 995 insertions, 0 deletions
diff --git a/zephyr/emul/emul_smart_battery.c b/zephyr/emul/emul_smart_battery.c
new file mode 100644
index 0000000000..6d23269d2a
--- /dev/null
+++ b/zephyr/emul/emul_smart_battery.c
@@ -0,0 +1,995 @@
+/* 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, 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;
+ }
+}