diff --git a/CODEOWNERS b/CODEOWNERS index 6c6600653a4..574b76ff3fc 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -190,7 +190,9 @@ /drivers/i2c/*litex* @mateusz-holenko @kgugala @pgielda /drivers/i2s/i2s_ll_stm32* @avisconti /drivers/i2c/i2c_common.c @sjg20 +/drivers/i2c/i2c_emul.c @sjg20 /drivers/i2c/i2c_shell.c @nashif +/drivers/i2c/Kconfig.i2c_emul @sjg20 /drivers/i2s/*litex* @mateusz-holenko @kgugala @pgielda /drivers/ieee802154/ @jukkar @tbursztyka /drivers/ieee802154/ieee802154_rf2xx* @jukkar @tbursztyka @nandojve @@ -302,6 +304,7 @@ /dts/xtensa/intel/ @dcpleung /dts/bindings/ @galak /dts/bindings/can/ @alexanderwachter +/dts/bindings/i2c/zephyr*i2c-emul.yaml @sjg20 /dts/bindings/iio/adc/st*stm32-adc.yaml @cybertale /dts/bindings/serial/ns16550.yaml @andrewboie /dts/bindings/*/nordic* @anangl @@ -324,6 +327,7 @@ /include/drivers/espi.h @albertofloyd @franciscomunoz @scottwcpg /include/drivers/bluetooth/ @joerchan @jhedberg @Vudentz /include/drivers/flash.h @nashif @carlescufi @galak @MaureenHelm @nvlsianpu +/include/drivers/i2c_emul.h @sjg20 /include/drivers/led/ht16k33.h @henrikbrixandersen /include/drivers/interrupt_controller/ @andrewboie /include/drivers/interrupt_controller/gic.h @stephanosio diff --git a/drivers/i2c/CMakeLists.txt b/drivers/i2c/CMakeLists.txt index a5596fe8907..8887ac09842 100644 --- a/drivers/i2c/CMakeLists.txt +++ b/drivers/i2c/CMakeLists.txt @@ -15,6 +15,7 @@ zephyr_library_sources_ifdef(CONFIG_I2C_XEC i2c_mchp_xec.c) zephyr_library_sources_ifdef(CONFIG_I2C_MCUX i2c_mcux.c) zephyr_library_sources_ifdef(CONFIG_I2C_MCUX_FLEXCOMM i2c_mcux_flexcomm.c) zephyr_library_sources_ifdef(CONFIG_I2C_MCUX_LPI2C i2c_mcux_lpi2c.c) +zephyr_library_sources_ifdef(CONFIG_I2C_EMUL i2c_emul.c) zephyr_library_sources_ifdef(CONFIG_NRFX_TWI i2c_nrfx_twi.c) zephyr_library_sources_ifdef(CONFIG_NRFX_TWIM i2c_nrfx_twim.c) zephyr_library_sources_ifdef(CONFIG_I2C_SAM_TWI i2c_sam_twi.c) diff --git a/drivers/i2c/Kconfig b/drivers/i2c/Kconfig index 3c51ee8e125..caf483e21fc 100644 --- a/drivers/i2c/Kconfig +++ b/drivers/i2c/Kconfig @@ -32,6 +32,7 @@ source "drivers/i2c/slave/Kconfig" source "drivers/i2c/Kconfig.gpio" source "drivers/i2c/Kconfig.xec" source "drivers/i2c/Kconfig.nrfx" +source "drivers/i2c/Kconfig.i2c_emul" source "drivers/i2c/Kconfig.sbcon" source "drivers/i2c/Kconfig.sifive" source "drivers/i2c/Kconfig.stm32" diff --git a/drivers/i2c/Kconfig.i2c_emul b/drivers/i2c/Kconfig.i2c_emul new file mode 100644 index 00000000000..2c9102fcdc3 --- /dev/null +++ b/drivers/i2c/Kconfig.i2c_emul @@ -0,0 +1,10 @@ +# Copyright 2020 Google LLC +# SPDX-License-Identifier: Apache-2.0 + +config I2C_EMUL + bool "I2C emulator" + help + Enable the I2C 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 I2C bus. It is + used for testing drivers for I2C devices. diff --git a/drivers/i2c/i2c_emul.c b/drivers/i2c/i2c_emul.c new file mode 100644 index 00000000000..c219c4b5083 --- /dev/null +++ b/drivers/i2c/i2c_emul.c @@ -0,0 +1,155 @@ +/* + * This driver creates fake I2C buses which can contain emulated devices, + * implemented by a separate emulation driver. The API between this driver and + * its emulators is defined by struct i2c_emul_driver_api. + * + * Copyright 2020 Google LLC + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT zephyr_i2c_emul_controller + +#define LOG_LEVEL CONFIG_I2C_LOG_LEVEL +#include +LOG_MODULE_REGISTER(i2c_emul_ctlr); + +#include +#include +#include +#include + +/** Working data for the device */ +struct i2c_emul_data { + /* List of struct i2c_emul associated with the device */ + sys_slist_t emuls; + /* I2C host configuration */ + uint32_t config; +}; + +uint32_t i2c_emul_get_config(const struct device *dev) +{ + struct i2c_emul_data *data = dev->data; + + return data->config; +} + +/** + * Find an emulator by its I2C address + * + * @param dev I2C emulation controller device + * @param addr I2C address of that device + * @return emulator ro use + * @return NULL if not found + */ +static struct i2c_emul *i2c_emul_find(struct device *dev, int addr) +{ + struct i2c_emul_data *data = dev->data; + sys_snode_t *node; + + SYS_SLIST_FOR_EACH_NODE(&data->emuls, node) { + struct i2c_emul *emul = NULL; + + emul = CONTAINER_OF(node, struct i2c_emul, node); + if (emul->addr == addr) { + return emul; + } + } + + return NULL; +} + +static int i2c_emul_configure(struct device *dev, uint32_t dev_config) +{ + struct i2c_emul_data *data = dev->data; + + data->config = dev_config; + + return 0; +} + +static int i2c_emul_transfer(struct device *dev, struct i2c_msg *msgs, + uint8_t num_msgs, uint16_t addr) +{ + struct i2c_emul *emul; + const struct i2c_emul_api *api; + int ret; + + emul = i2c_emul_find(dev, addr); + if (!emul) { + return -EIO; + } + + api = emul->api; + __ASSERT_NO_MSG(emul->api); + __ASSERT_NO_MSG(emul->api->transfer); + + ret = api->transfer(emul, msgs, num_msgs, addr); + if (ret) { + return ret; + } + + return 0; +} + +/** + * Set up a new emulator and add it to the list + * + * @param dev I2C emulation controller device + */ +static int i2c_emul_init(struct device *dev) +{ + struct i2c_emul_data *data = dev->data; + const struct emul_list_for_bus *list = dev->config; + int rc; + + sys_slist_init(&data->emuls); + + rc = emul_init_for_bus_from_list(dev, list); + + return rc; +} + +int i2c_emul_register(struct device *dev, const char *name, + struct i2c_emul *emul) +{ + struct i2c_emul_data *data = dev->data; + + sys_slist_append(&data->emuls, &emul->node); + + LOG_INF("Register emulator '%s' at I2C addr %02x\n", name, emul->addr); + + return 0; +} + +/* Device instantiation */ + +static struct i2c_driver_api i2c_emul_api = { + .configure = i2c_emul_configure, + .transfer = i2c_emul_transfer, +}; + +#define EMUL_LINK_AND_COMMA(node_id) { \ + .label = DT_LABEL(node_id), \ +}, + +#define I2C_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 i2c_emul_cfg_##n = { \ + .children = emuls_##n, \ + .num_children = ARRAY_SIZE(emuls_##n), \ + }; \ + static struct i2c_emul_data i2c_emul_data_##n; \ + DEVICE_AND_API_INIT(i2c_##n, \ + DT_INST_LABEL(n), \ + i2c_emul_init, \ + &i2c_emul_data_##n, \ + &i2c_emul_cfg_##n, \ + POST_KERNEL, \ + CONFIG_I2C_INIT_PRIORITY, \ + &i2c_emul_api); + +DT_INST_FOREACH_STATUS_OKAY(I2C_EMUL_INIT) diff --git a/dts/bindings/i2c/zephyr,i2c-emul.yaml b/dts/bindings/i2c/zephyr,i2c-emul.yaml new file mode 100644 index 00000000000..7bd4f58acbc --- /dev/null +++ b/dts/bindings/i2c/zephyr,i2c-emul.yaml @@ -0,0 +1,12 @@ +# Copyright 2020 Google LLC +# SPDX-License-Identifier: Apache-2.0 + +description: Zephyr I2C Emulation controller + +compatible: "zephyr,i2c-emul-controller" + +include: i2c-controller.yaml + +properties: + reg: + required: true diff --git a/include/drivers/i2c_emul.h b/include/drivers/i2c_emul.h new file mode 100644 index 00000000000..c464d9da2e9 --- /dev/null +++ b/include/drivers/i2c_emul.h @@ -0,0 +1,92 @@ +/** + * @file + * + * @brief Public APIs for the I2C emulation drivers. + */ + +/* + * Copyright 2020 Google LLC + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef ZEPHYR_INCLUDE_DRIVERS_I2C_I2C_EMUL_H_ +#define ZEPHYR_INCLUDE_DRIVERS_I2C_I2C_EMUL_H_ + +/** + * @brief I2C Emulation Interface + * @defgroup i2c_emul_interface I2C Emulation Interface + * @ingroup io_emulators + * @{ + */ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct i2c_msg; +struct i2c_emul_api; + +/** Node in a linked list of emulators for I2C devices */ +struct i2c_emul { + sys_snode_t node; + + /* API provided for this device */ + const struct i2c_emul_api *api; + + /* I2C address of the emulated device */ + uint16_t addr; +}; + +/** + * Passes I2C messages to the emulator. The emulator updates the data with what + * was read back. + * + * @param emul Emulator instance + * @param msgs Array of messages to transfer. For 'read' messages, this function + * updates the 'buf' member with the data that was read + * @param num_msgs Number of messages to transfer. + * @param addr Address of the I2C target device. + * + * @retval 0 If successful. + * @retval -EIO General input / output error. + */ +typedef int (*i2c_emul_transfer_t)(struct i2c_emul *emul, struct i2c_msg *msgs, + int num_msgs, int addr); + +/** + * 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 I2C emulator to use + * @return 0 indicating success (always) + */ +int i2c_emul_register(struct device *dev, const char *name, + struct i2c_emul *emul); + +/** Definition of the emulator API */ +struct i2c_emul_api { + i2c_emul_transfer_t transfer; +}; + +/** + * Back door to allow an emulator to retrieve the host configuration. + * + * @param dev I2C device associated with the emulator + * @return Bit-packed 32-bit value containing the device's runtime configuration + */ +uint32_t i2c_emul_get_config(const struct device *dev); + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* ZEPHYR_INCLUDE_DRIVERS_I2C_I2C_EMUL_H_ */