diff options
Diffstat (limited to 'zephyr/emul/emul_smart_battery.c')
-rw-r--r-- | zephyr/emul/emul_smart_battery.c | 995 |
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; + } +} |