emul: Add an emulator for the Bosch BMI160 accelerometer

This emulator supports enable functionality to start up the device and
read a few samples. It connects itself to any BMI160 device it finds in
the device tree. The SPI emulation controller driver is used to direct
SPI messages from the BMI160 driver to the BMI160 emulator.

Add a few more definitions to the header file, as needed.

Signed-off-by: Simon Glass <sjg@chromium.org>
This commit is contained in:
Simon Glass 2020-09-18 16:33:09 -06:00 committed by Anas Nashif
parent 4d77edd6ba
commit 442f38d610
6 changed files with 294 additions and 0 deletions

View file

@ -9,6 +9,7 @@
#define ZEPHYR_DRIVERS_SENSOR_BMI160_BMI160_H_ #define ZEPHYR_DRIVERS_SENSOR_BMI160_BMI160_H_
#include <drivers/gpio.h> #include <drivers/gpio.h>
#include <drivers/sensor.h>
#include <drivers/spi.h> #include <drivers/spi.h>
#include <sys/util.h> #include <sys/util.h>
@ -204,6 +205,11 @@
#define BMI160_CMD_PMU_MAG 0x18 #define BMI160_CMD_PMU_MAG 0x18
#define BMI160_CMD_SOFT_RESET 0xB6 #define BMI160_CMD_SOFT_RESET 0xB6
#define BMI160_CMD_PMU_BIT 0x10
#define BMI160_CMD_PMU_MASK 0x0c
#define BMI160_CMD_PMU_SHIFT 2
#define BMI160_CMD_PMU_VAL_MASK 0x3
/* BMI160_REG_FOC_CONF */ /* BMI160_REG_FOC_CONF */
#define BMI160_FOC_ACC_Z_POS 0 #define BMI160_FOC_ACC_Z_POS 0
#define BMI160_FOC_ACC_Y_POS 2 #define BMI160_FOC_ACC_Y_POS 2

View file

@ -4,3 +4,4 @@ zephyr_library()
zephyr_library_sources_ifdef(CONFIG_EMUL emul.c) zephyr_library_sources_ifdef(CONFIG_EMUL emul.c)
add_subdirectory(i2c) add_subdirectory(i2c)
add_subdirectory(spi)

View file

@ -35,5 +35,6 @@ module-str = emul
source "subsys/logging/Kconfig.template.log_config" source "subsys/logging/Kconfig.template.log_config"
source "subsys/emul/i2c/Kconfig" source "subsys/emul/i2c/Kconfig"
source "subsys/emul/spi/Kconfig"
endif endif

View file

@ -0,0 +1,6 @@
# SPDX-License-Identifier: Apache-2.0
# Once we have more than 10 devices we should consider splitting them into
# subdirectories to match the drivers/ structure.
zephyr_include_directories_ifdef(CONFIG_EMUL_BMI160 ${ZEPHYR_BASE}/drivers/sensor/bmi160)
zephyr_library_sources_ifdef(CONFIG_EMUL_BMI160 emul_bmi160.c)

12
subsys/emul/spi/Kconfig Normal file
View file

@ -0,0 +1,12 @@
# Configuration options for I2C emulators
# Copyright 2020 Google LLC
# SPDX-License-Identifier: Apache-2.0
config EMUL_BMI160
bool "Emulate a Bosch BMI160 accelerometer"
help
This is an emulator for the Bosch BMI160 accelerometer.
It provides readings which follow a simple sequence, thus allowing
test code to check that things are working as expected.

View file

@ -0,0 +1,268 @@
/*
* Copyright 2020 Google LLC
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT bosch_bmi160
#define LOG_LEVEL CONFIG_SPI_LOG_LEVEL
#include <logging/log.h>
LOG_MODULE_REGISTER(bosch_bmi160);
#include <sys/byteorder.h>
#include <bmi160.h>
#include <device.h>
#include <emul.h>
#include <drivers/spi.h>
#include <drivers/spi_emul.h>
/** Run-time data used by the emulator */
struct bmi160_emul_data {
/** SPI emulator detail */
struct spi_emul emul;
/** BMI160 device being emulated */
const struct device *dev;
/** Configuration information */
const struct bmi160_emul_cfg *cfg;
uint8_t pmu_status;
};
/** Static configuration for the emulator */
struct bmi160_emul_cfg {
/** Label of the SPI bus this emulator connects to */
const char *spi_label;
/** Pointer to run-time data */
struct bmi160_emul_data *data;
/** Chip registers */
uint8_t *reg;
/** Unit address (chip select ordinal) of emulator */
uint16_t chipsel;
};
/* Names for the PMU components */
static const char *const pmu_name[] = {"acc", "gyr", "mag", "INV"};
static void sample_read(struct bmi160_emul_data *data, union bmi160_sample *buf)
{
int i;
buf->dummy_byte = 0;
for (i = 0; i < BMI160_AXES; i++) {
/*
* Use hard-coded scales to get values just above 0, 1, 2 and
* 3, 4, 5.
*/
buf->acc[i] = sys_cpu_to_le16(i * 1000000 / 598 + 1);
buf->gyr[i] = sys_cpu_to_le16((i + 3) * 1000000 / 1065 + 1);
}
}
static void reg_write(const struct bmi160_emul_cfg *cfg, int regn, int val)
{
struct bmi160_emul_data *data = cfg->data;
cfg->reg[regn] = val;
switch (regn) {
case BMI160_REG_ACC_CONF:
LOG_INF(" * acc conf");
break;
case BMI160_REG_ACC_RANGE:
LOG_INF(" * acc range");
break;
case BMI160_REG_GYR_CONF:
LOG_INF(" * gyr conf");
break;
case BMI160_REG_GYR_RANGE:
LOG_INF(" * gyr range");
break;
case BMI160_REG_CMD:
switch (val) {
case BMI160_CMD_SOFT_RESET:
LOG_INF(" * soft reset");
break;
default:
if ((val & BMI160_CMD_PMU_BIT) == BMI160_CMD_PMU_BIT) {
int which = (val & BMI160_CMD_PMU_MASK) >>
BMI160_CMD_PMU_SHIFT;
int shift;
int pmu_val = val & BMI160_CMD_PMU_VAL_MASK;
switch (which) {
case 0:
shift = BMI160_PMU_STATUS_ACC_POS;
break;
case 1:
shift = BMI160_PMU_STATUS_GYR_POS;
break;
case 2:
default:
shift = BMI160_PMU_STATUS_MAG_POS;
break;
}
data->pmu_status &= 3 << shift;
data->pmu_status |= pmu_val << shift;
LOG_INF(" * pmu %s = %x, new status %x",
pmu_name[which], pmu_val,
data->pmu_status);
} else {
LOG_INF("Unknown command %x", val);
}
break;
}
break;
default:
LOG_INF("Unknown write %x", regn);
}
}
static int reg_read(const struct bmi160_emul_cfg *cfg, int regn)
{
struct bmi160_emul_data *data = cfg->data;
int val;
val = cfg->reg[regn];
switch (regn) {
case BMI160_REG_CHIPID:
LOG_INF(" * get chipid");
break;
case BMI160_REG_PMU_STATUS:
LOG_INF(" * get pmu");
val = data->pmu_status;
break;
case BMI160_REG_STATUS:
LOG_INF(" * status");
val |= BMI160_DATA_READY_BIT_MASK;
break;
case BMI160_REG_ACC_CONF:
LOG_INF(" * acc conf");
break;
case BMI160_REG_GYR_CONF:
LOG_INF(" * gyr conf");
break;
case BMI160_SPI_START:
LOG_INF(" * SPI start");
break;
default:
LOG_INF("Unknown read %x", regn);
}
return val;
}
static int bmi160_emul_io(struct spi_emul *emul,
const struct spi_config *config,
const struct spi_buf_set *tx_bufs,
const struct spi_buf_set *rx_bufs)
{
struct bmi160_emul_data *data;
const struct bmi160_emul_cfg *cfg;
const struct spi_buf *tx, *txd, *rxd;
unsigned int regn, val;
int count;
data = CONTAINER_OF(emul, struct bmi160_emul_data, emul);
cfg = data->cfg;
__ASSERT_NO_MSG(tx_bufs || rx_bufs);
__ASSERT_NO_MSG(!tx_bufs || !rx_bufs ||
tx_bufs->count == rx_bufs->count);
count = tx_bufs ? tx_bufs->count : rx_bufs->count;
switch (count) {
case 2:
tx = tx_bufs->buffers;
txd = &tx_bufs->buffers[1];
rxd = rx_bufs ? &rx_bufs->buffers[1] : NULL;
switch (tx->len) {
case 1:
regn = *(uint8_t *)tx->buf;
switch (txd->len) {
case 1:
if (regn & BMI160_REG_READ) {
regn &= BMI160_REG_MASK;
LOG_INF("read %x =", regn);
val = reg_read(cfg, regn);
*(uint8_t *)rxd->buf = val;
LOG_INF(" = %x", val);
} else {
val = *(uint8_t *)txd->buf;
LOG_INF("write %x = %x", regn, val);
reg_write(cfg, regn, val);
}
break;
case BMI160_SAMPLE_SIZE:
if (regn & BMI160_REG_READ) {
LOG_INF("Sample read");
sample_read(data, rxd->buf);
} else {
LOG_INF("Unknown sample write");
}
break;
default:
LOG_INF("Unknown A txd->len %d", txd->len);
break;
}
break;
default:
LOG_INF("Unknown tx->len %d", tx->len);
break;
}
break;
default:
LOG_INF("Unknown tx_bufs->count %d", count);
break;
}
return 0;
}
/* Device instantiation */
static struct spi_emul_api bmi160_emul_api = {
.io = bmi160_emul_io,
};
/**
* Set up a new BMI160 emulator
*
* This should be called for each BMI160 device that needs to be emulated. It
* registers it with the SPI emulation controller.
*
* @param emul Emulation information
* @param parent Device to emulate (must use BMI160 driver)
* @return 0 indicating success (always)
*/
static int emul_bosch_bmi160_init(const struct emul *emul,
const struct device *parent)
{
const struct bmi160_emul_cfg *cfg = emul->cfg;
struct bmi160_emul_data *data = cfg->data;
uint8_t *reg = cfg->reg;
data->emul.api = &bmi160_emul_api;
data->emul.chipsel = cfg->chipsel;
data->dev = parent;
data->cfg = cfg;
data->pmu_status = 0;
reg[BMI160_REG_CHIPID] = BMI160_CHIP_ID;
int rc = spi_emul_register(parent, emul->dev_label, &data->emul);
return rc;
}
#define BMI160_EMUL(n) \
static uint8_t bmi160_emul_reg_##n[BMI160_REG_COUNT]; \
static struct bmi160_emul_data bmi160_emul_data_##n; \
static const struct bmi160_emul_cfg bmi160_emul_cfg_##n = { \
.spi_label = DT_INST_BUS_LABEL(n), \
.data = &bmi160_emul_data_##n, \
.reg = bmi160_emul_reg_##n, \
.chipsel = DT_INST_REG_ADDR(n) \
}; \
EMUL_DEFINE(emul_bosch_bmi160_init, DT_DRV_INST(n), \
&bmi160_emul_cfg_##n)
DT_INST_FOREACH_STATUS_OKAY(BMI160_EMUL)