353 lines
8.0 KiB
C
353 lines
8.0 KiB
C
/*
|
|
* Copyright (c) 2023, ithinx GmbH
|
|
* Copyright (c) 2023, Tonies GmbH
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*
|
|
* Emulator for bq27z746 fuel gauge
|
|
*/
|
|
|
|
#include <string.h>
|
|
#define DT_DRV_COMPAT ti_bq27z746
|
|
|
|
#include <zephyr/logging/log.h>
|
|
LOG_MODULE_REGISTER(EMUL_BQ27Z746);
|
|
|
|
#include <zephyr/device.h>
|
|
#include <zephyr/drivers/emul.h>
|
|
#include <zephyr/drivers/i2c.h>
|
|
#include <zephyr/drivers/i2c_emul.h>
|
|
#include <zephyr/sys/byteorder.h>
|
|
|
|
#include "bq27z746.h"
|
|
|
|
#define BQ27Z746_MAC_DATA_LEN 32
|
|
#define BQ27Z746_MAC_OVERHEAD_LEN 4 /* 2 cmd bytes, 1 length byte, 1 checksum byte */
|
|
#define BQ27Z746_MAC_COMPLETE_LEN (BQ27Z746_MAC_DATA_LEN + BQ27Z746_MAC_OVERHEAD_LEN)
|
|
|
|
struct bq27z746_emul_data {
|
|
uint16_t mac_cmd;
|
|
};
|
|
|
|
/** Static configuration for the emulator */
|
|
struct bq27z746_emul_cfg {
|
|
/** I2C address of emulator */
|
|
uint16_t addr;
|
|
};
|
|
|
|
static int emul_bq27z746_read_altmac(const struct emul *target, uint8_t *buf, size_t len)
|
|
{
|
|
const uint8_t manufacturer_name[] = "Texas Instruments";
|
|
const uint8_t device_name[] = "BQ27Z746";
|
|
const uint8_t device_chemistry[] = "LION";
|
|
const struct bq27z746_emul_data *data = target->data;
|
|
|
|
if (len < BQ27Z746_MAC_COMPLETE_LEN) {
|
|
LOG_ERR("When reading the ALTMAC, one must read the full %u byte",
|
|
BQ27Z746_MAC_COMPLETE_LEN);
|
|
return -EIO;
|
|
}
|
|
|
|
memset(buf, 0, len);
|
|
|
|
/*
|
|
* The data read from BQ27Z746_ALTMANUFACTURERACCESS is:
|
|
* 0..1: The command (for verification)
|
|
* 2..33: The data
|
|
* 34: Checksum calculated as (uint8_t)(0xFF - (sum of all command and data bytes))
|
|
* 35: Length including command, checksum and length (e.g. data length + 4)
|
|
*/
|
|
|
|
/* Put the command in the first two byte */
|
|
sys_put_le16(data->mac_cmd, buf);
|
|
|
|
/* Based on the command, put some data and the length into the buffer. */
|
|
/* In all of the operations, don't consider the zero-terminator. */
|
|
switch (data->mac_cmd) {
|
|
case BQ27Z746_MAC_CMD_MANUFACTURER_NAME:
|
|
memcpy(&buf[2], manufacturer_name, sizeof(manufacturer_name) - 1);
|
|
buf[35] = sizeof(manufacturer_name) - 1 + BQ27Z746_MAC_OVERHEAD_LEN;
|
|
break;
|
|
case BQ27Z746_MAC_CMD_DEVICE_NAME:
|
|
memcpy(&buf[2], device_name, sizeof(device_name) - 1);
|
|
buf[35] = sizeof(device_name) - 1 + BQ27Z746_MAC_OVERHEAD_LEN;
|
|
break;
|
|
case BQ27Z746_MAC_CMD_DEVICE_CHEM:
|
|
memcpy(&buf[2], device_chemistry, sizeof(device_chemistry) - 1);
|
|
buf[35] = sizeof(device_chemistry) - 1 + BQ27Z746_MAC_OVERHEAD_LEN;
|
|
break;
|
|
default:
|
|
LOG_ERR("ALTMAC command 0x%x is not supported", data->mac_cmd);
|
|
return -EIO;
|
|
}
|
|
|
|
/* Calculate the checksum */
|
|
uint8_t sum = 0; /* Intentionally 8 bit wide and overflowing */
|
|
|
|
for (int i = 0; i < BQ27Z746_MAC_COMPLETE_LEN - 2; i++) {
|
|
sum += buf[i];
|
|
}
|
|
buf[34] = 0xFF - sum;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int emul_bq27z746_write(const struct emul *target, uint8_t *buf, size_t len)
|
|
{
|
|
struct bq27z746_emul_data *data = target->data;
|
|
const uint8_t reg = buf[0];
|
|
|
|
switch (reg) {
|
|
case BQ27Z746_ALTMANUFACTURERACCESS:
|
|
data->mac_cmd = sys_get_le16(&buf[1]);
|
|
return 0;
|
|
default:
|
|
LOG_ERR("Writing is only supported to ALTMAC currently");
|
|
return -EIO;
|
|
}
|
|
}
|
|
|
|
static int emul_bq27z746_reg_read(const struct emul *target, int reg, int *val)
|
|
{
|
|
switch (reg) {
|
|
case BQ27Z746_MANUFACTURERACCESS:
|
|
*val = 1;
|
|
break;
|
|
case BQ27Z746_ATRATE:
|
|
*val = -2;
|
|
break;
|
|
case BQ27Z746_ATRATETIMETOEMPTY:
|
|
*val = 1;
|
|
break;
|
|
case BQ27Z746_TEMPERATURE:
|
|
*val = 1;
|
|
break;
|
|
case BQ27Z746_VOLTAGE:
|
|
*val = 1;
|
|
break;
|
|
case BQ27Z746_BATTERYSTATUS:
|
|
*val = 1;
|
|
break;
|
|
case BQ27Z746_CURRENT:
|
|
*val = -2;
|
|
break;
|
|
case BQ27Z746_REMAININGCAPACITY:
|
|
*val = 1;
|
|
break;
|
|
case BQ27Z746_FULLCHARGECAPACITY:
|
|
*val = 1;
|
|
break;
|
|
case BQ27Z746_AVERAGECURRENT:
|
|
*val = -2;
|
|
break;
|
|
case BQ27Z746_AVERAGETIMETOEMPTY:
|
|
*val = 1;
|
|
break;
|
|
case BQ27Z746_AVERAGETIMETOFULL:
|
|
*val = 1;
|
|
break;
|
|
case BQ27Z746_MAXLOADCURRENT:
|
|
*val = 1;
|
|
break;
|
|
case BQ27Z746_MAXLOADTIMETOEMPTY:
|
|
*val = 1;
|
|
break;
|
|
case BQ27Z746_AVERAGEPOWER:
|
|
*val = 1;
|
|
break;
|
|
case BQ27Z746_BTPDISCHARGESET:
|
|
*val = 1;
|
|
break;
|
|
case BQ27Z746_BTPCHARGESET:
|
|
*val = 1;
|
|
break;
|
|
case BQ27Z746_INTERNALTEMPERATURE:
|
|
*val = 1;
|
|
break;
|
|
case BQ27Z746_CYCLECOUNT:
|
|
*val = 1;
|
|
break;
|
|
case BQ27Z746_RELATIVESTATEOFCHARGE:
|
|
*val = 1;
|
|
break;
|
|
case BQ27Z746_STATEOFHEALTH:
|
|
*val = 1;
|
|
break;
|
|
case BQ27Z746_CHARGINGVOLTAGE:
|
|
*val = 1;
|
|
break;
|
|
case BQ27Z746_CHARGINGCURRENT:
|
|
*val = 1;
|
|
break;
|
|
case BQ27Z746_TERMINATEVOLTAGE:
|
|
*val = 1;
|
|
break;
|
|
case BQ27Z746_TIMESTAMPUPPER:
|
|
*val = 1;
|
|
break;
|
|
case BQ27Z746_TIMESTAMPLOWER:
|
|
*val = 1;
|
|
break;
|
|
case BQ27Z746_QMAXCYCLES:
|
|
*val = 1;
|
|
break;
|
|
case BQ27Z746_DESIGNCAPACITY:
|
|
*val = 1;
|
|
break;
|
|
case BQ27Z746_ALTMANUFACTURERACCESS:
|
|
*val = 1;
|
|
break;
|
|
case BQ27Z746_MACDATA:
|
|
*val = 1;
|
|
break;
|
|
case BQ27Z746_MACDATASUM:
|
|
*val = 1;
|
|
break;
|
|
case BQ27Z746_MACDATALEN:
|
|
*val = 1;
|
|
break;
|
|
case BQ27Z746_VOLTHISETTHRESHOLD:
|
|
*val = 1;
|
|
break;
|
|
case BQ27Z746_VOLTHICLEARTHRESHOLD:
|
|
*val = 1;
|
|
break;
|
|
case BQ27Z746_VOLTLOSETTHRESHOLD:
|
|
*val = 1;
|
|
break;
|
|
case BQ27Z746_VOLTLOCLEARTHRESHOLD:
|
|
*val = 1;
|
|
break;
|
|
case BQ27Z746_TEMPHISETTHRESHOLD:
|
|
*val = 1;
|
|
break;
|
|
case BQ27Z746_TEMPHICLEARTHRESHOLD:
|
|
*val = 1;
|
|
break;
|
|
case BQ27Z746_TEMPLOSETTHRESHOLD:
|
|
*val = 1;
|
|
break;
|
|
case BQ27Z746_TEMPLOCLEARTHRESHOLD:
|
|
*val = 1;
|
|
break;
|
|
case BQ27Z746_INTERRUPTSTATUS:
|
|
*val = 1;
|
|
break;
|
|
case BQ27Z746_SOCDELTASETTHRESHOLD:
|
|
*val = 1;
|
|
break;
|
|
default:
|
|
LOG_ERR("Unknown register 0x%x read", reg);
|
|
return -EIO;
|
|
}
|
|
LOG_INF("read 0x%x = 0x%x", reg, *val);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int emul_bq27z746_read(const struct emul *target, int reg, uint8_t *buf, size_t len)
|
|
{
|
|
if (len == 2) {
|
|
unsigned int val;
|
|
int rc = emul_bq27z746_reg_read(target, reg, &val);
|
|
|
|
if (rc) {
|
|
return rc;
|
|
}
|
|
|
|
sys_put_le16(val, buf);
|
|
} else {
|
|
switch (reg) {
|
|
case BQ27Z746_ALTMANUFACTURERACCESS:
|
|
LOG_DBG("Reading %u byte from ALTMAC", len);
|
|
emul_bq27z746_read_altmac(target, buf, len);
|
|
break;
|
|
default:
|
|
LOG_ERR("Reading is only supported from ALTMAC currently");
|
|
return -EIO;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int bq27z746_emul_transfer_i2c(const struct emul *target, struct i2c_msg *msgs, int num_msgs,
|
|
int addr)
|
|
{
|
|
int reg;
|
|
int rc;
|
|
|
|
__ASSERT_NO_MSG(msgs && num_msgs);
|
|
|
|
i2c_dump_msgs_rw(target->dev, msgs, num_msgs, addr, false);
|
|
switch (num_msgs) {
|
|
case 1:
|
|
if (msgs->flags & I2C_MSG_READ) {
|
|
LOG_ERR("Unexpected read");
|
|
return -EIO;
|
|
}
|
|
|
|
return emul_bq27z746_write(target, msgs->buf, msgs->len);
|
|
case 2:
|
|
if (msgs->flags & I2C_MSG_READ) {
|
|
LOG_ERR("Unexpected read");
|
|
return -EIO;
|
|
}
|
|
if (msgs->len != 1) {
|
|
LOG_ERR("Unexpected msg0 length %d", msgs->len);
|
|
return -EIO;
|
|
}
|
|
reg = msgs->buf[0];
|
|
|
|
/* Now process the 'read' part of the message */
|
|
msgs++;
|
|
if (msgs->flags & I2C_MSG_READ) {
|
|
rc = emul_bq27z746_read(target, reg, msgs->buf, msgs->len);
|
|
if (rc) {
|
|
return rc;
|
|
}
|
|
} else {
|
|
LOG_ERR("Second message must be an I2C write");
|
|
return -EIO;
|
|
}
|
|
return rc;
|
|
default:
|
|
LOG_ERR("Invalid number of messages: %d", num_msgs);
|
|
return -EIO;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct i2c_emul_api bq27z746_emul_api_i2c = {
|
|
.transfer = bq27z746_emul_transfer_i2c,
|
|
};
|
|
|
|
/**
|
|
* Set up a new emulator (I2C)
|
|
*
|
|
* @param emul Emulation information
|
|
* @param parent Device to emulate
|
|
* @return 0 indicating success (always)
|
|
*/
|
|
static int emul_bq27z746_init(const struct emul *target, const struct device *parent)
|
|
{
|
|
ARG_UNUSED(target);
|
|
ARG_UNUSED(parent);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Main instantiation macro.
|
|
*/
|
|
#define BQ27Z746_EMUL(n) \
|
|
static struct bq27z746_emul_data bq27z746_emul_data_##n; \
|
|
static const struct bq27z746_emul_cfg bq27z746_emul_cfg_##n = { \
|
|
.addr = DT_INST_REG_ADDR(n), \
|
|
}; \
|
|
EMUL_DT_INST_DEFINE(n, emul_bq27z746_init, &bq27z746_emul_data_##n, \
|
|
&bq27z746_emul_cfg_##n, &bq27z746_emul_api_i2c, NULL)
|
|
|
|
DT_INST_FOREACH_STATUS_OKAY(BQ27Z746_EMUL)
|