blob: b416babd0d59bbaa40e81a015743bce6cc272e40 [file] [log] [blame]
/*
* 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)