diff options
-rw-r--r-- | driver/battery/smart.c | 9 | ||||
-rw-r--r-- | include/battery_smart.h | 23 | ||||
-rw-r--r-- | zephyr/CMakeLists.txt | 1 | ||||
-rw-r--r-- | zephyr/Kconfig | 1 | ||||
-rw-r--r-- | zephyr/dts/bindings/emul/zephyr,smart-battery.yaml | 155 | ||||
-rw-r--r-- | zephyr/emul/CMakeLists.txt | 5 | ||||
-rw-r--r-- | zephyr/emul/Kconfig | 8 | ||||
-rw-r--r-- | zephyr/emul/emul_smart_battery.c | 995 | ||||
-rw-r--r-- | zephyr/include/emul/emul_smart_battery.h | 243 |
9 files changed, 1436 insertions, 4 deletions
diff --git a/driver/battery/smart.c b/driver/battery/smart.c index 1c2dcaf4c9..fd7dbcafb9 100644 --- a/driver/battery/smart.c +++ b/driver/battery/smart.c @@ -294,9 +294,12 @@ test_mockable int battery_manufacture_date(int *year, int *month, int *day) /* battery date format: * ymd = day + month * 32 + (year - 1980) * 512 */ - *year = (ymd >> 9) + 1980; - *month = (ymd >> 5) & 0xf; - *day = ymd & 0x1f; + *year = ((ymd & MANUFACTURE_DATE_YEAR_MASK) >> + MANUFACTURE_DATE_YEAR_SHIFT) + MANUFACTURE_DATE_YEAR_OFFSET; + *month = (ymd & MANUFACTURE_DATE_MONTH_MASK) >> + MANUFACTURE_DATE_MONTH_SHIFT; + *day = (ymd & MANUFACTURE_DATE_DAY_MASK) >> + MANUFACTURE_DATE_DAY_SHIFT; return EC_SUCCESS; } diff --git a/include/battery_smart.h b/include/battery_smart.h index 635ac8558a..a610092d31 100644 --- a/include/battery_smart.h +++ b/include/battery_smart.h @@ -92,11 +92,23 @@ #define STATUS_OVERCHARGED_ALARM BIT(15) /* Battery Spec Info */ -#define BATTERY_SPEC_VERSION(INFO) ((INFO >> 4) & 0xF) +#define BATTERY_SPEC_REVISION_MASK 0x000F +#define BATTERY_SPEC_REVISION_SHIFT 0 +#define BATTERY_SPEC_VERSION_MASK 0x00F0 +#define BATTERY_SPEC_VERSION_SHIFT 4 +#define BATTERY_SPEC_VSCALE_MASK 0x0F00 +#define BATTERY_SPEC_VSCALE_SHIFT 8 +#define BATTERY_SPEC_IPSCALE_MASK 0xF000 +#define BATTERY_SPEC_IPSCALE_SHIFT 12 + +#define BATTERY_SPEC_VERSION(INFO) ((INFO & BATTERY_SPEC_VERSION_MASK) >> \ + BATTERY_SPEC_VERSION_SHIFT) /* Smart battery version info */ #define BATTERY_SPEC_VER_1_0 1 #define BATTERY_SPEC_VER_1_1 2 #define BATTERY_SPEC_VER_1_1_WITH_PEC 3 +/* Smart battery revision info */ +#define BATTERY_SPEC_REVISION_1 1 /* Charger alarm warning */ #define ALARM_OVER_CHARGED 0x8000 @@ -145,6 +157,15 @@ #define BATTERY_DISCHARGING_DISABLED 0x20 #define BATTERY_CHARGING_DISABLED 0x40 +/* Battery manufacture date */ +#define MANUFACTURE_DATE_DAY_MASK 0x001F +#define MANUFACTURE_DATE_DAY_SHIFT 0 +#define MANUFACTURE_DATE_MONTH_MASK 0x01E0 +#define MANUFACTURE_DATE_MONTH_SHIFT 5 +#define MANUFACTURE_DATE_YEAR_MASK 0xFE00 +#define MANUFACTURE_DATE_YEAR_SHIFT 9 +#define MANUFACTURE_DATE_YEAR_OFFSET 1980 + /* Read from battery */ int sb_read(int cmd, int *param); diff --git a/zephyr/CMakeLists.txt b/zephyr/CMakeLists.txt index b4e1b0475d..954a691a0b 100644 --- a/zephyr/CMakeLists.txt +++ b/zephyr/CMakeLists.txt @@ -173,6 +173,7 @@ endif() add_subdirectory("app") add_subdirectory("drivers") +add_subdirectory("emul") add_subdirectory_ifdef(CONFIG_PLATFORM_EC "shim") # Creates a phony target all.libraries in case you only want to build the diff --git a/zephyr/Kconfig b/zephyr/Kconfig index 31df8475e8..356eaf262d 100644 --- a/zephyr/Kconfig +++ b/zephyr/Kconfig @@ -4,6 +4,7 @@ rsource "app/Kconfig" rsource "drivers/Kconfig" +rsource "emul/Kconfig" if ZTEST diff --git a/zephyr/dts/bindings/emul/zephyr,smart-battery.yaml b/zephyr/dts/bindings/emul/zephyr,smart-battery.yaml new file mode 100644 index 0000000000..cc1d2f368d --- /dev/null +++ b/zephyr/dts/bindings/emul/zephyr,smart-battery.yaml @@ -0,0 +1,155 @@ +# 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. + +description: Zephyr Smart Battery Emulator + +compatible: "zephyr,smart-battery" + +include: base.yaml + +properties: + mf-access: + type: int + required: false + default: 0 + description: Word returned on manufacturer access command. + + at-rate-full-mw-support: + type: boolean + description: + Flag indicating if AT_RATE_TIME_TO_FULL command supports mW capacity + mode. + + version: + type: string + required: false + enum: + - BATTERY_SPEC_VER_1_0 + - BATTERY_SPEC_VER_1_1 + - BATTERY_SPEC_VER_1_1_WITH_PEC + default: BATTERY_SPEC_VER_1_1_WITH_PEC + description: Version of Smart Battery. + + vscale: + type: int + required: false + default: 0 + description: Scaling of voltage. + + ipscale: + type: int + required: false + default: 0 + description: Scaling of current. + + int-charge-controller: + type: boolean + description: Flag indicating if internal charge controller is supported. + + primary-battery: + type: boolean + description: + Flag indicating if primary battery role selection is supported. + + design-mv: + type: int + required: false + default: 5000 + description: Design battery voltage in mV. + + design-cap: + type: int + required: false + default: 5000 + description: Design battery capacity in mAh. + + temperature: + type: int + required: false + default: 2930 + description: Battery temperature in 0.1 Kelvins. + + volt: + type: int + required: false + default: 5000 + description: Battery voltage in mV. + + cur: + type: int + required: false + default: 1000 + description: Current charging (> 0) or discharging (< 0) battery in mA. + + avg-cur: + type: int + required: false + default: 1000 + description: Average current from 1 minute. + + max-error: + type: int + required: false + default: 0 + description: Maximum error of commands return value in percent. + + cap: + type: int + required: false + default: 2000 + description: Capacity of the battery in mAh. + + full-cap: + type: int + required: false + default: 4000 + description: Full capacity of the battery in mAh. + + desired-charg-cur: + type: int + required: false + default: 2000 + description: Charging current requested by battery. + + desired-charg-volt: + type: int + required: false + default: 7000 + description: Charging voltage requested by battery. + + cycle-count: + type: int + required: false + default: 125 + description: Number of cycles. + + serial-number: + type: int + required: false + default: 7 + description: Serial number of battery. + + mf-name: + type: string + required: false + default: "zephyr" + description: Manufacturer name. Length has to be smaller than 32 bytes. + + dev-name: + type: string + required: false + default: "smartbat" + description: Device name. Length has to be smaller than 32 bytes. + + dev-chem: + type: string + required: false + default: "LION" + description: Device chemistry. Length has to be smaller than 32 bytes. + + mf-data: + type: string + required: false + default: "LION" + description: Manufacturer data. Length has to be smaller than 32 bytes. diff --git a/zephyr/emul/CMakeLists.txt b/zephyr/emul/CMakeLists.txt new file mode 100644 index 0000000000..d16dd068cc --- /dev/null +++ b/zephyr/emul/CMakeLists.txt @@ -0,0 +1,5 @@ +# 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. + +zephyr_library_sources_ifdef(CONFIG_EMUL_SMART_BATTERY emul_smart_battery.c) diff --git a/zephyr/emul/Kconfig b/zephyr/emul/Kconfig new file mode 100644 index 0000000000..c7e64b9b14 --- /dev/null +++ b/zephyr/emul/Kconfig @@ -0,0 +1,8 @@ +# 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. + +config EMUL_SMART_BATTERY + bool "Smart Battery emulator" + help + Enable the Smart Battery emulator. This driver use emulated I2C bus. 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; + } +} diff --git a/zephyr/include/emul/emul_smart_battery.h b/zephyr/include/emul/emul_smart_battery.h new file mode 100644 index 0000000000..be5e7e4b16 --- /dev/null +++ b/zephyr/include/emul/emul_smart_battery.h @@ -0,0 +1,243 @@ +/* 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. + */ + +/** + * @file + * + * @brief Backend API for Smart Battery emulator + */ + +#ifndef __EMUL_SMART_BATTERY_H +#define __EMUL_SMART_BATTERY_H + +#include <emul.h> +#include <drivers/i2c.h> +#include <drivers/i2c_emul.h> + +/** + * @brief Smart Battery emulator backend API + * @defgroup sbat_emul Smart Battery emulator + * @{ + * + * Smart Battery emulator handle static state of device. E.g. setting charging + * current will not charge battery over time. Sending periodic status messages + * and alarms to SMBus Host or charging voltage/current to Smart Battery Charger + * is not supported. Behaviour of Smart Battery emulator is application-defined. + * As-such, each application may + * + * - define a Device Tree overlay file to set the most of battery properties + * - call @ref sbat_emul_set_custom_write_func and + * @ref sbat_emul_set_custom_read_func to setup custom handlers for SMBus + * messages. + * - get battery properties calling @ref sbat_emul_get_bat_data Battery + * properties can be changed through obtained pointer. In multithread + * environment access to battery can be guarded by calling + * @ref sbat_emul_lock_bat_data and @ref sbat_emul_unlock_bat_data + */ + +/* Value used to indicate that no command is selected */ +#define SBAT_EMUL_NO_CMD -1 +/* Maximum size of data that can be returned in SMBus block transaction */ +#define MAX_BLOCK_SIZE 32 +/* Maximum length of command to send is maximum size of data + len byte + PEC */ +#define MSG_BUF_LEN (MAX_BLOCK_SIZE + 2) + +/** @brief Emulated smart battery properties */ +struct sbat_emul_bat_data { + /** Battery mode - bit field configuring some battery behaviours */ + uint16_t mode; + /** Word returned on manufacturer access command */ + uint16_t mf_access; + /** Capacity alarm value */ + uint16_t cap_alarm; + /** Remaing time alarm value */ + uint16_t time_alarm; + /** Rate of charge used in some commands */ + int16_t at_rate; + /** + * Flag indicating if AT_RATE_TIME_TO_FULL command supports mW + * capacity mode + */ + int at_rate_full_mw_support; + /** Error code returned by last command */ + uint16_t error_code; + /** Design battery voltage in mV */ + uint16_t design_mv; + /** Battery temperature at the moment in Kelvins */ + uint16_t temp; + /** Battery voltage at the moment in mV */ + uint16_t volt; + /** Current charging (> 0) or discharging (< 0) battery in mA */ + int16_t cur; + /** Average current from 1 minute */ + int16_t avg_cur; + /** Maximum error of returned values in percent */ + uint16_t max_error; + /** Capacity of the battery at the moment in mAh */ + uint16_t cap; + /** Full capacity of the battery in mAh */ + uint16_t full_cap; + /** Design battery capacity in mAh */ + uint16_t design_cap; + /** Charging current requested by battery */ + uint16_t desired_charg_cur; + /** Charging voltage requested by battery */ + uint16_t desired_charg_volt; + /** Number of cycles */ + uint16_t cycle_count; + /** Specification of battery */ + uint16_t spec_info; + /** Status of battery */ + uint16_t status; + /** Date of manufacturing */ + uint16_t mf_date; + /** Serial number */ + uint16_t sn; + /** Manufacturer name */ + uint8_t mf_name[MAX_BLOCK_SIZE]; + /** Manufacturer name length */ + int mf_name_len; + /** Device name */ + uint8_t dev_name[MAX_BLOCK_SIZE]; + /** Device name length */ + int dev_name_len; + /** Device chemistry */ + uint8_t dev_chem[MAX_BLOCK_SIZE]; + /** Device chemistry length */ + int dev_chem_len; + /** Manufacturer data */ + uint8_t mf_data[MAX_BLOCK_SIZE]; + /** Manufacturer data length */ + int mf_data_len; +}; + +/** + * @brief Get pointer to smart battery emulator using device tree order number. + * + * @param ord Device tree order number obtained from DT_DEP_ORD macro + * + * @return Pointer to smart battery emulator + */ +struct i2c_emul *sbat_emul_get_ptr(int ord); + +/** + * @brief Custom function type that is used as user defined callbacks in read + * and write SMBus messages handling. + * + * @param emul Pointer to smart battery emulator + * @param buf Pointer to data from write command or to be filed by read command + * @param len Pointer to number of bytes used for write command buffer. It may + * exceed MSG_BUF_LEN, indicating that some bytes from write command + * are not saved in @p buf. If read command is handled, than + * function should set how many bytes are written to @p buf + * @param cmd Command that was recognized + * @param data Pointer to custom user data + * + * @return 0 on success + * @return 1 continue with normal smart battery emulator handler + * @return negative on error + */ +typedef int (*sbat_emul_custom_func)(struct i2c_emul *emul, uint8_t *buf, + int *len, int cmd, void *data); + +/** + * @brief Function which allows to get properties of emulated smart battery + * + * @param emul Pointer to smart battery emulator + * + * @return Pointer to smart battery properties + */ +struct sbat_emul_bat_data *sbat_emul_get_bat_data(struct i2c_emul *emul); + +/** + * @brief Lock access to smart battery properties. After acquiring lock, user + * may change emulator behaviour in multi-thread setup. + * + * @param emul Pointer to smart battery emulator + * @param timeout Timeout in getting lock + * + * @return k_mutex_lock return code + */ +int sbat_emul_lock_bat_data(struct i2c_emul *emul, k_timeout_t timeout); + +/** + * @brief Unlock access to smart battery properties. + * + * @param emul Pointer to smart battery emulator + * + * @return k_mutex_unlock return code + */ +int sbat_emul_unlock_bat_dat(struct i2c_emul *emul); + +/** + * @brief Set custom write SMBus message handler. This function is called before + * generic handler. + * + * @param emul Pointer to smart battery emulator + * @param func Pointer to custom function + * @param data User data passed on call of custom function + */ +void sbat_emul_set_custom_write_func(struct i2c_emul *emul, + sbat_emul_custom_func func, void *data); + +/** + * @brief Set custom read SMBus message handler. This function is called before + * generic handler. + * + * @param emul Pointer to smart battery emulator + * @param func Pointer to custom function + * @param data User data passed on call of custom function + */ +void sbat_emul_set_custom_read_func(struct i2c_emul *emul, + sbat_emul_custom_func func, void *data); + +/** + * @brief Convert date to format used by smart battery + * + * @param day Day + * @param month Month + * @param year Year + * + * @return Converted date + */ +uint16_t sbat_emul_date_to_word(unsigned int day, unsigned int month, + unsigned int year); + +/** + * @brief Function which gets return value for read commands that returns word. + * This function may be used to obtain battery properties that are + * calculated e.g. time to empty/full. + * + * @param emul Pointer to smart battery emulator + * @param cmd Read command + * @param val Pointer to where word should be stored + * + * @return 0 on success + * @return 1 if command is unknown or return type different then word + * @return negative on error while reading value + */ +int sbat_emul_get_word_val(struct i2c_emul *emul, int cmd, uint16_t *val); + +/** + * @brief Function which gets return value for read commands that returns block + * data + * + * @param emul Pointer to smart battery emulator + * @param cmd Read command + * @param blk Pointer to where data pointer should be stored + * @param len Pointer to where data length should be stored + * + * @return 0 on success + * @return 1 if command is unknown or return type different then word + * @return negative on error while reading value + */ +int sbat_emul_get_block_data(struct i2c_emul *emul, int cmd, uint8_t **blk, + int *len); + +/** + * @} + */ + +#endif /* __EMUL_SMART_BATTERY_H */ |