diff --git a/drivers/spi/CMakeLists.txt b/drivers/spi/CMakeLists.txt index af27a2433bb..00c85fbcbb4 100644 --- a/drivers/spi/CMakeLists.txt +++ b/drivers/spi/CMakeLists.txt @@ -4,6 +4,7 @@ zephyr_library() zephyr_library_sources_ifdef(CONFIG_SPI_CC13XX_CC26XX spi_cc13xx_cc26xx.c) zephyr_library_sources_ifdef(CONFIG_SPI_DW spi_dw.c) +zephyr_library_sources_ifdef(CONFIG_SPI_EMUL spi_emul.c) zephyr_library_sources_ifdef(CONFIG_SPI_STM32 spi_ll_stm32.c) zephyr_library_sources_ifdef(CONFIG_SPI_MCUX_DSPI spi_mcux_dspi.c) zephyr_library_sources_ifdef(CONFIG_SPI_MCUX_FLEXCOMM spi_mcux_flexcomm.c) diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index 4935d4d7e3e..fe259520be4 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -207,6 +207,8 @@ source "drivers/spi/Kconfig.sam0" source "drivers/spi/Kconfig.sifive" +source "drivers/spi/Kconfig.spi_emul" + source "drivers/spi/Kconfig.nrfx" source "drivers/spi/Kconfig.cc13xx_cc26xx" diff --git a/drivers/spi/Kconfig.spi_emul b/drivers/spi/Kconfig.spi_emul new file mode 100644 index 00000000000..309a553ddf5 --- /dev/null +++ b/drivers/spi/Kconfig.spi_emul @@ -0,0 +1,10 @@ +# Copyright 2020 Google LLC +# SPDX-License-Identifier: Apache-2.0 + +config SPI_EMUL + bool "SPI emulator" + help + Enable the SPI emulator driver. This is a fake driver in that it + does not talk to real hardware. Instead it talks to emulation + drivers that pretend to be devices on the emulated SPI bus. It is + used for testing drivers for SPI devices. diff --git a/drivers/spi/spi_emul.c b/drivers/spi/spi_emul.c new file mode 100644 index 00000000000..bf58289d8c8 --- /dev/null +++ b/drivers/spi/spi_emul.c @@ -0,0 +1,142 @@ +/* + * Copyright 2020 Google LLC + * + * SPDX-License-Identifier: Apache-2.0 + * + * This driver creates fake SPI buses which can contain emulated devices, + * implemented by a separate emulation driver. The API between this driver and + * its emulators is defined by struct spi_emul_driver_api. + */ + +#define DT_DRV_COMPAT zephyr_spi_emul_controller + +#define LOG_LEVEL CONFIG_SPI_LOG_LEVEL +#include +LOG_MODULE_REGISTER(spi_emul_ctlr); + +#include +#include +#include +#include + +/** Working data for the device */ +struct spi_emul_data { + /* List of struct spi_emul associated with the device */ + sys_slist_t emuls; + /* SPI host configuration */ + uint32_t config; +}; + +uint32_t spi_emul_get_config(const struct device *dev) +{ + struct spi_emul_data *data = dev->data; + + return data->config; +} + +/** + * Find an emulator for a SPI bus + * + * At present only a single emulator is supported on the bus, since we do not + * support chip selects, despite there being a chipsel field. It cannot be + * implemented until we have a GPIO emulator. + * + * @param dev SPI emulation controller device + * @param chipsel Chip-select value + * @return emulator to use + * @return NULL if not found + */ +static struct spi_emul *spi_emul_find(const struct device *dev, + unsigned int chipsel) +{ + struct spi_emul_data *data = dev->data; + sys_snode_t *node; + + SYS_SLIST_FOR_EACH_NODE(&data->emuls, node) { + struct spi_emul *emul; + + emul = CONTAINER_OF(node, struct spi_emul, node); + if (emul->chipsel == chipsel) { + return emul; + } + } + + return NULL; +} + +static int spi_emul_io(const struct device *dev, + const struct spi_config *config, + const struct spi_buf_set *tx_bufs, + const struct spi_buf_set *rx_bufs) +{ + struct spi_emul *emul; + const struct spi_emul_api *api; + + emul = spi_emul_find(dev, config->slave); + if (!emul) { + return -EIO; + } + + api = emul->api; + __ASSERT_NO_MSG(emul->api); + __ASSERT_NO_MSG(emul->api->io); + + return api->io(emul, config, tx_bufs, rx_bufs); +} + +/** + * Set up a new emulator and add it to the list + * + * @param dev SPI emulation controller device + */ +static int spi_emul_init(const struct device *dev) +{ + struct spi_emul_data *data = dev->data; + const struct emul_list_for_bus *list = dev->config; + + sys_slist_init(&data->emuls); + + return emul_init_for_bus_from_list(dev, list); +} + +int spi_emul_register(const struct device *dev, const char *name, + struct spi_emul *emul) +{ + struct spi_emul_data *data = dev->data; + + sys_slist_append(&data->emuls, &emul->node); + + LOG_INF("Register emulator '%s' at cs %u\n", name, emul->chipsel); + + return 0; +} + +/* Device instantiation */ + +static struct spi_driver_api spi_emul_api = { + .transceive = spi_emul_io, +}; + +#define EMUL_LINK_AND_COMMA(node_id) { \ + .label = DT_LABEL(node_id), \ +}, + +#define SPI_EMUL_INIT(n) \ + static const struct emul_link_for_bus emuls_##n[] = { \ + DT_FOREACH_CHILD(DT_DRV_INST(0), EMUL_LINK_AND_COMMA) \ + }; \ + static struct emul_list_for_bus spi_emul_cfg_##n = { \ + .children = emuls_##n, \ + .num_children = ARRAY_SIZE(emuls_##n), \ + }; \ + static struct spi_emul_data spi_emul_data_##n; \ + DEVICE_AND_API_INIT(spi_##n, \ + DT_INST_LABEL(n), \ + spi_emul_init, \ + &spi_emul_data_##n, \ + &spi_emul_cfg_##n, \ + POST_KERNEL, \ + CONFIG_SPI_INIT_PRIORITY, \ + &spi_emul_api); + +DT_INST_FOREACH_STATUS_OKAY(SPI_EMUL_INIT) diff --git a/dts/bindings/spi/zephyr,spi-emul.yaml b/dts/bindings/spi/zephyr,spi-emul.yaml new file mode 100644 index 00000000000..df457424507 --- /dev/null +++ b/dts/bindings/spi/zephyr,spi-emul.yaml @@ -0,0 +1,12 @@ +# Copyright 2020 Google LLC +# SPDX-License-Identifier: Apache-2.0 + +description: Zephyr SPI Emulation controller + +compatible: "zephyr,spi-emul-controller" + +include: spi-controller.yaml + +properties: + reg: + required: true diff --git a/include/drivers/spi_emul.h b/include/drivers/spi_emul.h new file mode 100644 index 00000000000..ce1fa61c23e --- /dev/null +++ b/include/drivers/spi_emul.h @@ -0,0 +1,97 @@ +/* + * Copyright 2020 Google LLC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_DRIVERS_SPI_SPI_EMUL_H_ +#define ZEPHYR_INCLUDE_DRIVERS_SPI_SPI_EMUL_H_ + +/** + * @file + * + * @brief Public APIs for the SPI emulation drivers. + */ + +#include +#include + +/** + * @brief SPI Emulation Interface + * @defgroup spi_emul_interface SPI Emulation Interface + * @ingroup io_emulators + * @{ + */ + +#ifdef __cplusplus +extern "C" { +#endif + +struct spi_msg; +struct spi_emul_api; + +/** Node in a linked list of emulators for SPI devices */ +struct spi_emul { + sys_snode_t node; + + /* API provided for this device */ + const struct spi_emul_api *api; + + /* SPI chip-slect of the emulated device */ + uint16_t chipsel; +}; + +/** + * Passes SPI messages to the emulator. The emulator updates the data with what + * was read back. + * + * @param emul Emulator instance + * @param config Pointer to a valid spi_config structure instance. + * Pointer-comparison may be used to detect changes from + * previous operations. + * @param tx_bufs Buffer array where data to be sent originates from, + * or NULL if none. + * @param rx_bufs Buffer array where data to be read will be written to, + * or NULL if none. + * + * @retval 0 If successful. + * @retval -EIO General input / output error. + */ +typedef int (*spi_emul_io_t)(struct spi_emul *emul, + const struct spi_config *config, + const struct spi_buf_set *tx_bufs, + const struct spi_buf_set *rx_bufs); + +/** + * Register an emulated device on the controller + * + * @param dev Device that will use the emulator + * @param name User-friendly name for this emulator + * @param emul SPI emulator to use + * @return 0 indicating success (always) + */ +int spi_emul_register(const struct device *dev, const char *name, + struct spi_emul *emul); + +/** Definition of the emulator API */ +struct spi_emul_api { + spi_emul_io_t io; +}; + +/** + * Back door to allow an emulator to retrieve the host configuration. + * + * @param dev SPI device associated with the emulator + * @return Bit-packed 32-bit value containing the device's runtime configuration + */ +uint32_t spi_emul_get_config(const struct device *dev); + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* ZEPHYR_INCLUDE_DRIVERS_SPI_SPI_EMUL_H_ */