From 442f38d61034b7207d624dc502956685219330a6 Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Fri, 18 Sep 2020 16:33:09 -0600 Subject: [PATCH] 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 --- drivers/sensor/bmi160/bmi160.h | 6 + subsys/emul/CMakeLists.txt | 1 + subsys/emul/Kconfig | 1 + subsys/emul/spi/CMakeLists.txt | 6 + subsys/emul/spi/Kconfig | 12 ++ subsys/emul/spi/emul_bmi160.c | 268 +++++++++++++++++++++++++++++++++ 6 files changed, 294 insertions(+) create mode 100644 subsys/emul/spi/CMakeLists.txt create mode 100644 subsys/emul/spi/Kconfig create mode 100644 subsys/emul/spi/emul_bmi160.c diff --git a/drivers/sensor/bmi160/bmi160.h b/drivers/sensor/bmi160/bmi160.h index b59d5c7651c..7ba0bb03384 100644 --- a/drivers/sensor/bmi160/bmi160.h +++ b/drivers/sensor/bmi160/bmi160.h @@ -9,6 +9,7 @@ #define ZEPHYR_DRIVERS_SENSOR_BMI160_BMI160_H_ #include +#include #include #include @@ -204,6 +205,11 @@ #define BMI160_CMD_PMU_MAG 0x18 #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 */ #define BMI160_FOC_ACC_Z_POS 0 #define BMI160_FOC_ACC_Y_POS 2 diff --git a/subsys/emul/CMakeLists.txt b/subsys/emul/CMakeLists.txt index f5857ac8c29..93bd566828e 100644 --- a/subsys/emul/CMakeLists.txt +++ b/subsys/emul/CMakeLists.txt @@ -4,3 +4,4 @@ zephyr_library() zephyr_library_sources_ifdef(CONFIG_EMUL emul.c) add_subdirectory(i2c) +add_subdirectory(spi) diff --git a/subsys/emul/Kconfig b/subsys/emul/Kconfig index 6860e99173c..5ede4b16f2e 100644 --- a/subsys/emul/Kconfig +++ b/subsys/emul/Kconfig @@ -35,5 +35,6 @@ module-str = emul source "subsys/logging/Kconfig.template.log_config" source "subsys/emul/i2c/Kconfig" +source "subsys/emul/spi/Kconfig" endif diff --git a/subsys/emul/spi/CMakeLists.txt b/subsys/emul/spi/CMakeLists.txt new file mode 100644 index 00000000000..bdbc3590eb1 --- /dev/null +++ b/subsys/emul/spi/CMakeLists.txt @@ -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) diff --git a/subsys/emul/spi/Kconfig b/subsys/emul/spi/Kconfig new file mode 100644 index 00000000000..ca524cceae8 --- /dev/null +++ b/subsys/emul/spi/Kconfig @@ -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. diff --git a/subsys/emul/spi/emul_bmi160.c b/subsys/emul/spi/emul_bmi160.c new file mode 100644 index 00000000000..3e47beb6881 --- /dev/null +++ b/subsys/emul/spi/emul_bmi160.c @@ -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 +LOG_MODULE_REGISTER(bosch_bmi160); + +#include +#include +#include +#include +#include +#include + +/** 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)