diff --git a/drivers/i2c/CMakeLists.txt b/drivers/i2c/CMakeLists.txt index 59fbb73bc7fd..a3f5ee05823f 100644 --- a/drivers/i2c/CMakeLists.txt +++ b/drivers/i2c/CMakeLists.txt @@ -26,3 +26,5 @@ zephyr_library_sources_ifdef(CONFIG_I2C_STM32_V2 ) zephyr_library_sources_ifdef(CONFIG_USERSPACE i2c_handlers.c) + +add_subdirectory_ifdef(CONFIG_I2C_SLAVE slave) diff --git a/drivers/i2c/Kconfig b/drivers/i2c/Kconfig index 4aff5a8b5f51..baa08e66d563 100644 --- a/drivers/i2c/Kconfig +++ b/drivers/i2c/Kconfig @@ -213,6 +213,7 @@ config I2C_NIOS2 source "drivers/i2c/Kconfig.dw" source "drivers/i2c/Kconfig.esp32" +source "drivers/i2c/slave/Kconfig" source "drivers/i2c/Kconfig.gpio" diff --git a/drivers/i2c/Kconfig.stm32 b/drivers/i2c/Kconfig.stm32 index 8821a7e3ed13..f7e7346c9910 100644 --- a/drivers/i2c/Kconfig.stm32 +++ b/drivers/i2c/Kconfig.stm32 @@ -27,9 +27,13 @@ config I2C_STM32_V2 select HAS_DTS_I2C select USE_STM32_LL_I2C select USE_STM32_LL_RCC if SOC_SERIES_STM32F0X || SOC_SERIES_STM32F3X + select I2C_STM32_INTERRUPT if I2C_SLAVE + default n help Enable I2C support on the STM32 F0, F3 and L4X family of processors. This driver also supports the F7 and L0 series. + If I2C_SLAVE is enabled it selects I2C_STM32_INTERRUPT, since slave mode + is only supported by this driver with interrupts enabled. config I2C_STM32_INTERRUPT bool "STM32 MCU I2C Interrupt Support" diff --git a/drivers/i2c/i2c_handlers.c b/drivers/i2c/i2c_handlers.c index 967192260efc..dc0f27740a48 100644 --- a/drivers/i2c/i2c_handlers.c +++ b/drivers/i2c/i2c_handlers.c @@ -57,3 +57,9 @@ Z_SYSCALL_HANDLER(i2c_transfer, dev, msgs, num_msgs, addr) (u8_t)num_msgs, (u16_t)addr, ssf); } + +Z_SYSCALL_HANDLER1_SIMPLE(i2c_slave_driver_register, K_OBJ_DRIVER_I2C, + struct device *); + +Z_SYSCALL_HANDLER1_SIMPLE(i2c_slave_driver_unregister, K_OBJ_DRIVER_I2C, + struct device *); diff --git a/drivers/i2c/i2c_ll_stm32.c b/drivers/i2c/i2c_ll_stm32.c index 5b2e7f174875..48195747a84e 100644 --- a/drivers/i2c/i2c_ll_stm32.c +++ b/drivers/i2c/i2c_ll_stm32.c @@ -18,7 +18,7 @@ #define SYS_LOG_LEVEL CONFIG_SYS_LOG_I2C_LEVEL #include -static int i2c_stm32_runtime_configure(struct device *dev, u32_t config) +int i2c_stm32_runtime_configure(struct device *dev, u32_t config) { const struct i2c_stm32_config *cfg = DEV_CFG(dev); struct i2c_stm32_data *data = DEV_DATA(dev); @@ -53,28 +53,36 @@ static int i2c_stm32_runtime_configure(struct device *dev, u32_t config) static int i2c_stm32_transfer(struct device *dev, struct i2c_msg *msg, u8_t num_msgs, u16_t slave) { +#if defined(CONFIG_I2C_STM32_V1) const struct i2c_stm32_config *cfg = DEV_CFG(dev); - struct i2c_msg *current, *next; I2C_TypeDef *i2c = cfg->i2c; +#endif + struct i2c_msg *current, *next; int ret = 0; - LL_I2C_Enable(i2c); - + /* Check for validity of all messages, to prevent having to abort + * in the middle of a transfer + */ current = msg; + /* * Set I2C_MSG_RESTART flag on first message in order to send start * condition */ current->flags |= I2C_MSG_RESTART; - while (num_msgs > 0) { - u8_t *next_msg_flags = NULL; - if (num_msgs > 1) { + for (u8_t i = 1; i <= num_msgs; i++) { + /* Maximum length of a single message is 255 Bytes */ + if (current->len > 255) { + ret = -EINVAL; + break; + } + + if (i < num_msgs) { next = current + 1; - next_msg_flags = &(next->flags); /* - * Stop or restart condition between messages + * Restart condition between messages * of different directions is required */ if (OPERATION(current) != OPERATION(next)) { @@ -83,17 +91,37 @@ static int i2c_stm32_transfer(struct device *dev, struct i2c_msg *msg, break; } } - } - if (current->len > 255) { - ret = -EINVAL; - break; + /* Stop condition is only allowed on last message */ + if (current->flags & I2C_MSG_STOP) { + ret = -EINVAL; + break; + } + } else { + /* Stop condition is required for the last message */ + current->flags |= I2C_MSG_STOP; } - /* Stop condition is required for the last message */ - if ((num_msgs == 1) && !(current->flags & I2C_MSG_STOP)) { - ret = -EINVAL; - break; + current++; + } + + if (ret) { + return ret; + } + + /* Send out messages */ +#if defined(CONFIG_I2C_STM32_V1) + LL_I2C_Enable(i2c); +#endif + + current = msg; + + while (num_msgs > 0) { + u8_t *next_msg_flags = NULL; + + if (num_msgs > 1) { + next = current + 1; + next_msg_flags = &(next->flags); } if ((current->flags & I2C_MSG_RW_MASK) == I2C_MSG_WRITE) { @@ -111,15 +139,19 @@ static int i2c_stm32_transfer(struct device *dev, struct i2c_msg *msg, current++; num_msgs--; }; - +#if defined(CONFIG_I2C_STM32_V1) LL_I2C_Disable(i2c); - +#endif return ret; } static const struct i2c_driver_api api_funcs = { .configure = i2c_stm32_runtime_configure, .transfer = i2c_stm32_transfer, +#if defined(CONFIG_I2C_SLAVE) && defined(CONFIG_I2C_STM32_V2) + .slave_register = i2c_stm32_slave_register, + .slave_unregister = i2c_stm32_slave_unregister, +#endif }; static int i2c_stm32_init(struct device *dev) diff --git a/drivers/i2c/i2c_ll_stm32.h b/drivers/i2c/i2c_ll_stm32.h index cde627800e95..2a184d9ab55e 100644 --- a/drivers/i2c/i2c_ll_stm32.h +++ b/drivers/i2c/i2c_ll_stm32.h @@ -34,12 +34,18 @@ struct i2c_stm32_data { unsigned int flags; #endif unsigned int is_write; + unsigned int is_arlo; unsigned int is_nack; unsigned int is_err; struct i2c_msg *msg; unsigned int len; u8_t *buf; } current; +#ifdef CONFIG_I2C_SLAVE + bool master_active; + struct i2c_slave_config *slave_cfg; + bool slave_attached; +#endif }; s32_t stm32_i2c_msg_write(struct device *dev, struct i2c_msg *msg, u8_t *flg, @@ -47,6 +53,7 @@ s32_t stm32_i2c_msg_write(struct device *dev, struct i2c_msg *msg, u8_t *flg, s32_t stm32_i2c_msg_read(struct device *dev, struct i2c_msg *msg, u8_t *flg, u16_t sadr); s32_t stm32_i2c_configure_timing(struct device *dev, u32_t clk); +int i2c_stm32_runtime_configure(struct device *dev, u32_t config); void stm32_i2c_event_isr(void *arg); void stm32_i2c_error_isr(void *arg); @@ -54,6 +61,13 @@ void stm32_i2c_error_isr(void *arg); void stm32_i2c_combined_isr(void *arg); #endif +#ifdef CONFIG_I2C_SLAVE +int i2c_stm32_slave_register(struct device *dev, + struct i2c_slave_config *config); +int i2c_stm32_slave_unregister(struct device *dev, + struct i2c_slave_config *config); +#endif + #define DEV_DATA(dev) ((struct i2c_stm32_data * const)(dev)->driver_data) #define DEV_CFG(dev) \ ((const struct i2c_stm32_config * const)(dev)->config->config_info) diff --git a/drivers/i2c/i2c_ll_stm32_v2.c b/drivers/i2c/i2c_ll_stm32_v2.c index f80b768137b4..43a8a9113340 100644 --- a/drivers/i2c/i2c_ll_stm32_v2.c +++ b/drivers/i2c/i2c_ll_stm32_v2.c @@ -15,6 +15,7 @@ #include #include #include +#include "i2c-priv.h" #include "i2c_ll_stm32.h" #define SYS_LOG_LEVEL CONFIG_SYS_LOG_I2C_LEVEL @@ -50,110 +51,319 @@ static inline void msg_init(struct device *dev, struct i2c_msg *msg, LL_I2C_DisableAutoEndMode(i2c); LL_I2C_SetTransferRequest(i2c, transfer); LL_I2C_SetTransferSize(i2c, msg->len); + +#if defined(CONFIG_I2C_SLAVE) + data->master_active = true; +#endif + LL_I2C_Enable(i2c); + LL_I2C_GenerateStartCondition(i2c); } } #ifdef CONFIG_I2C_STM32_INTERRUPT -void stm32_i2c_event_isr(void *arg) + +static void stm32_i2c_disable_transfer_interrupts(struct device *dev) { - const struct i2c_stm32_config *cfg = DEV_CFG((struct device *)arg); - struct i2c_stm32_data *data = DEV_DATA((struct device *)arg); + const struct i2c_stm32_config *cfg = DEV_CFG(dev); I2C_TypeDef *i2c = cfg->i2c; - if (data->current.len) { - /* Interrupts for sending/receiving next byte */ + LL_I2C_DisableIT_TX(i2c); + LL_I2C_DisableIT_RX(i2c); + LL_I2C_DisableIT_STOP(i2c); + LL_I2C_DisableIT_NACK(i2c); + LL_I2C_DisableIT_TC(i2c); + LL_I2C_DisableIT_ERR(i2c); +} + +static void stm32_i2c_enable_transfer_interrupts(struct device *dev) +{ + const struct i2c_stm32_config *cfg = DEV_CFG(dev); + I2C_TypeDef *i2c = cfg->i2c; + + LL_I2C_EnableIT_STOP(i2c); + LL_I2C_EnableIT_NACK(i2c); + LL_I2C_EnableIT_TC(i2c); + LL_I2C_EnableIT_ERR(i2c); +} + +static void stm32_i2c_master_mode_end(struct device *dev) +{ + const struct i2c_stm32_config *cfg = DEV_CFG(dev); + struct i2c_stm32_data *data = DEV_DATA(dev); + I2C_TypeDef *i2c = cfg->i2c; + + stm32_i2c_disable_transfer_interrupts(dev); + +#if defined(CONFIG_I2C_SLAVE) + data->master_active = false; + if (!data->slave_attached) { + LL_I2C_Disable(i2c); + } +#else + LL_I2C_Disable(i2c); +#endif + k_sem_give(&data->device_sync_sem); +} + +#if defined(CONFIG_I2C_SLAVE) +static void stm32_i2c_slave_event(struct device *dev) +{ + const struct i2c_stm32_config *cfg = DEV_CFG(dev); + struct i2c_stm32_data *data = DEV_DATA(dev); + I2C_TypeDef *i2c = cfg->i2c; + const struct i2c_slave_callbacks *slave_cb = + data->slave_cfg->callbacks; + + if (LL_I2C_IsActiveFlag_TXIS(i2c)) { + u8_t val; + + slave_cb->read_processed(data->slave_cfg, &val); + LL_I2C_TransmitData8(i2c, val); + return; + } + + if (LL_I2C_IsActiveFlag_RXNE(i2c)) { + u8_t val = LL_I2C_ReceiveData8(i2c); + + if (slave_cb->write_received(data->slave_cfg, val)) { + LL_I2C_AcknowledgeNextData(i2c, LL_I2C_NACK); + } + return; + } + + if (LL_I2C_IsActiveFlag_NACK(i2c)) { + LL_I2C_ClearFlag_NACK(i2c); + } + + if (LL_I2C_IsActiveFlag_STOP(i2c)) { + stm32_i2c_disable_transfer_interrupts(dev); + + /* Flush remaining TX byte before clearing Stop Flag */ + LL_I2C_ClearFlag_TXE(i2c); + + LL_I2C_ClearFlag_STOP(i2c); + + slave_cb->stop(data->slave_cfg); + + /* Prepare to ACK next transmissions address byte */ + LL_I2C_AcknowledgeNextData(i2c, LL_I2C_ACK); + } + + if (LL_I2C_IsActiveFlag_ADDR(i2c)) { + u32_t dir; + + LL_I2C_ClearFlag_ADDR(i2c); + + dir = LL_I2C_GetTransferDirection(i2c); + if (dir == LL_I2C_DIRECTION_WRITE) { + slave_cb->write_requested(data->slave_cfg); + LL_I2C_EnableIT_RX(i2c); + } else { + u8_t val; + + slave_cb->read_requested(data->slave_cfg, &val); + LL_I2C_TransmitData8(i2c, val); + LL_I2C_EnableIT_TX(i2c); + } + + stm32_i2c_enable_transfer_interrupts(dev); + } +} + +/* Attach and start I2C as slave */ +int i2c_stm32_slave_register(struct device *dev, + struct i2c_slave_config *config) +{ + const struct i2c_stm32_config *cfg = DEV_CFG(dev); + struct i2c_stm32_data *data = DEV_DATA(dev); + I2C_TypeDef *i2c = cfg->i2c; + u32_t bitrate_cfg; + int ret; + + if (!config) { + return -EINVAL; + } + + if (data->slave_attached) { + return -EBUSY; + } + + if (data->master_active) { + return -EBUSY; + } + + bitrate_cfg = _i2c_map_dt_bitrate(cfg->bitrate); + + ret = i2c_stm32_runtime_configure(dev, bitrate_cfg); + if (ret < 0) { + SYS_LOG_ERR("i2c: failure initializing"); + return ret; + } + + data->slave_cfg = config; + + LL_I2C_Enable(i2c); + + LL_I2C_SetOwnAddress1(i2c, config->address << 1, + LL_I2C_OWNADDRESS1_7BIT); + LL_I2C_EnableOwnAddress1(i2c); + + data->slave_attached = true; + + SYS_LOG_DBG("i2c: slave registered"); + + LL_I2C_EnableIT_ADDR(i2c); + + return 0; +} + +int i2c_stm32_slave_unregister(struct device *dev, + struct i2c_slave_config *config) +{ + const struct i2c_stm32_config *cfg = DEV_CFG(dev); + struct i2c_stm32_data *data = DEV_DATA(dev); + I2C_TypeDef *i2c = cfg->i2c; + + if (!data->slave_attached) { + return -EINVAL; + } + + if (data->master_active) { + return -EBUSY; + } - if (data->current.is_write && LL_I2C_IsActiveFlag_TXIS(i2c)) { - /* Send next byte */ + LL_I2C_DisableOwnAddress1(i2c); + + LL_I2C_DisableIT_ADDR(i2c); + stm32_i2c_disable_transfer_interrupts(dev); + + LL_I2C_ClearFlag_NACK(i2c); + LL_I2C_ClearFlag_STOP(i2c); + LL_I2C_ClearFlag_ADDR(i2c); + + LL_I2C_Disable(i2c); + + SYS_LOG_DBG("i2c: slave unregistered"); + + return 0; +} + +#endif /* defined(CONFIG_I2C_SLAVE) */ + +static void stm32_i2c_event(struct device *dev) +{ + const struct i2c_stm32_config *cfg = DEV_CFG(dev); + struct i2c_stm32_data *data = DEV_DATA(dev); + I2C_TypeDef *i2c = cfg->i2c; + +#if defined(CONFIG_I2C_SLAVE) + if (data->slave_attached && !data->master_active) { + stm32_i2c_slave_event(dev); + return; + } +#endif + if (data->current.len) { + /* Send next byte */ + if (LL_I2C_IsActiveFlag_TXIS(i2c)) { LL_I2C_TransmitData8(i2c, *data->current.buf); - } else if (!(data->current.is_write) && - LL_I2C_IsActiveFlag_RXNE(i2c)) { - /* Receive next byte */ + } + + /* Receive next byte */ + if (LL_I2C_IsActiveFlag_RXNE(i2c)) { *data->current.buf = LL_I2C_ReceiveData8(i2c); - } else { - goto error; } data->current.buf++; data->current.len--; - if (!data->current.len) { - LL_I2C_EnableIT_TC(i2c); - } + } - } else { - /* Interrupts to process end of message */ - - if (LL_I2C_IsActiveFlag_TC(i2c) || - LL_I2C_IsActiveFlag_TCR(i2c)) { - /* Interrupt after last byte of message - * was transferred - */ - LL_I2C_DisableIT_TC(i2c); - LL_I2C_DisableIT_RX(i2c); - - /* Issue stop condition if necessary */ - if (data->current.msg->flags & I2C_MSG_STOP) { - LL_I2C_GenerateStopCondition(i2c); - LL_I2C_EnableIT_STOP(i2c); - } else { - k_sem_give(&data->device_sync_sem); - } + /* NACK received */ + if (LL_I2C_IsActiveFlag_NACK(i2c)) { + LL_I2C_ClearFlag_NACK(i2c); + data->current.is_nack = 1; + goto end; + } - } else if (LL_I2C_IsActiveFlag_STOP(i2c)) { - /* Interrupt after stop condition was - * successfully issued - */ - LL_I2C_DisableIT_STOP(i2c); - LL_I2C_ClearFlag_STOP(i2c); - LL_I2C_DisableReloadMode(i2c); - k_sem_give(&data->device_sync_sem); + /* STOP received */ + if (LL_I2C_IsActiveFlag_STOP(i2c)) { + LL_I2C_ClearFlag_STOP(i2c); + LL_I2C_DisableReloadMode(i2c); + goto end; + } + + /* Transfer Complete or Transfer Complete Reload */ + if (LL_I2C_IsActiveFlag_TC(i2c) || + LL_I2C_IsActiveFlag_TCR(i2c)) { + /* Issue stop condition if necessary */ + if (data->current.msg->flags & I2C_MSG_STOP) { + LL_I2C_GenerateStopCondition(i2c); } else { - goto error; + stm32_i2c_disable_transfer_interrupts(dev); + k_sem_give(&data->device_sync_sem); } } return; -error: - LL_I2C_DisableIT_TX(i2c); - LL_I2C_DisableIT_RX(i2c); - data->current.is_err = 1; - k_sem_give(&data->device_sync_sem); +end: + stm32_i2c_master_mode_end(dev); } -#ifndef CONFIG_I2C_STM32_COMBINED_INTERRUPT -void stm32_i2c_error_isr(void *arg) +static int stm32_i2c_error(struct device *dev) { - const struct i2c_stm32_config *cfg = DEV_CFG((struct device *)arg); - struct i2c_stm32_data *data = DEV_DATA((struct device *)arg); + const struct i2c_stm32_config *cfg = DEV_CFG(dev); + struct i2c_stm32_data *data = DEV_DATA(dev); I2C_TypeDef *i2c = cfg->i2c; - if (LL_I2C_IsActiveFlag_NACK(i2c)) { - LL_I2C_ClearFlag_NACK(i2c); - data->current.is_nack = 1; - } else { +#if defined(CONFIG_I2C_SLAVE) + if (data->slave_attached && !data->master_active) { + /* No need for a slave error function right now. */ + return 0; + } +#endif + + if (LL_I2C_IsActiveFlag_ARLO(i2c)) { + LL_I2C_ClearFlag_ARLO(i2c); + data->current.is_arlo = 1; + goto end; + } + + if (LL_I2C_IsActiveFlag_BERR(i2c)) { + LL_I2C_ClearFlag_BERR(i2c); data->current.is_err = 1; + goto end; } - k_sem_give(&data->device_sync_sem); + return 0; +end: + stm32_i2c_master_mode_end(dev); + return -EIO; } -#endif #ifdef CONFIG_I2C_STM32_COMBINED_INTERRUPT void stm32_i2c_combined_isr(void *arg) { - const struct i2c_stm32_config *cfg = DEV_CFG((struct device *)arg); - struct i2c_stm32_data *data = DEV_DATA((struct device *)arg); - I2C_TypeDef *i2c = cfg->i2c; - - if (LL_I2C_IsActiveFlag_NACK(i2c)) { - LL_I2C_ClearFlag_NACK(i2c); - data->current.is_nack = 1; + struct device *dev = (struct device *) arg; - k_sem_give(&data->device_sync_sem); - } else { - stm32_i2c_event_isr(arg); + if (stm32_i2c_error(dev)) { + return; } + stm32_i2c_event(dev); +} +#else + +void stm32_i2c_event_isr(void *arg) +{ + struct device *dev = (struct device *) arg; + + stm32_i2c_event(dev); +} + +void stm32_i2c_error_isr(void *arg) +{ + struct device *dev = (struct device *) arg; + + stm32_i2c_error(dev); } #endif @@ -172,19 +382,25 @@ int stm32_i2c_msg_write(struct device *dev, struct i2c_msg *msg, data->current.msg = msg; msg_init(dev, msg, next_msg_flags, slave, LL_I2C_REQUEST_WRITE); + + stm32_i2c_enable_transfer_interrupts(dev); LL_I2C_EnableIT_TX(i2c); - LL_I2C_EnableIT_NACK(i2c); k_sem_take(&data->device_sync_sem, K_FOREVER); - LL_I2C_DisableIT_TX(i2c); - LL_I2C_DisableIT_NACK(i2c); - if (data->current.is_nack || data->current.is_err) { + if (data->current.is_nack || data->current.is_err || + data->current.is_arlo) { goto error; } return 0; error: + if (data->current.is_arlo) { + SYS_LOG_DBG("%s: ARLO %d", __func__, + data->current.is_arlo); + data->current.is_arlo = 0; + } + if (data->current.is_nack) { SYS_LOG_DBG("%s: NACK", __func__); data->current.is_nack = 0; @@ -209,23 +425,41 @@ int stm32_i2c_msg_read(struct device *dev, struct i2c_msg *msg, data->current.len = msg->len; data->current.buf = msg->buf; data->current.is_write = 0; + data->current.is_arlo = 0; data->current.is_err = 0; + data->current.is_nack = 0; data->current.msg = msg; msg_init(dev, msg, next_msg_flags, slave, LL_I2C_REQUEST_READ); + + stm32_i2c_enable_transfer_interrupts(dev); LL_I2C_EnableIT_RX(i2c); k_sem_take(&data->device_sync_sem, K_FOREVER); - LL_I2C_DisableIT_RX(i2c); - if (data->current.is_err) { + if (data->current.is_nack || data->current.is_err || + data->current.is_arlo) { goto error; } return 0; error: - SYS_LOG_DBG("%s: ERR %d", __func__, data->current.is_err); - data->current.is_err = 0; + if (data->current.is_arlo) { + SYS_LOG_DBG("%s: ARLO %d", __func__, + data->current.is_arlo); + data->current.is_arlo = 0; + } + + if (data->current.is_nack) { + SYS_LOG_DBG("%s: NACK", __func__); + data->current.is_nack = 0; + } + + if (data->current.is_err) { + SYS_LOG_DBG("%s: ERR %d", __func__, + data->current.is_err); + data->current.is_err = 0; + } return -EIO; } diff --git a/drivers/i2c/slave/CMakeLists.txt b/drivers/i2c/slave/CMakeLists.txt new file mode 100644 index 000000000000..2d3f386379b5 --- /dev/null +++ b/drivers/i2c/slave/CMakeLists.txt @@ -0,0 +1 @@ +zephyr_sources_ifdef(CONFIG_I2C_EEPROM_SLAVE eeprom_slave.c) diff --git a/drivers/i2c/slave/Kconfig b/drivers/i2c/slave/Kconfig new file mode 100644 index 000000000000..dd9cdeb20285 --- /dev/null +++ b/drivers/i2c/slave/Kconfig @@ -0,0 +1,42 @@ +# Kconfig - I2C Slave configuration options + +# +# Copyright (c) 2017 BayLibre, SAS +# +# SPDX-License-Identifier: Apache-2.0 +# + +# +# I2C options +# +menuconfig I2C_SLAVE + bool "I2C Slave Drivers" + depends on I2C + default n + help + Enable I2C Slave Driver Configuration + +if I2C_SLAVE + +config I2C_SLAVE_INIT_PRIORITY + int "Init priority" + default 60 + help + I2C Slave device driver initialization priority. + +config SYS_LOG_I2C_SLAVE_LEVEL + int "I2C Slave log level" + depends on SYS_LOG + default 0 + help + Sets log level for I2C drivers. + Levels are: + - 0 OFF, do not write + - 1 ERROR, only write SYS_LOG_ERR + - 2 WARNING, write SYS_LOG_WRN in addition to previous level + - 3 INFO, write SYS_LOG_INF in addition to previous levels + - 4 DEBUG, write SYS_LOG_DBG in addition to previous levels + +source "drivers/i2c/slave/Kconfig.eeprom" + +endif # I2C_SLAVE diff --git a/drivers/i2c/slave/Kconfig.eeprom b/drivers/i2c/slave/Kconfig.eeprom new file mode 100644 index 000000000000..c6329a1121d1 --- /dev/null +++ b/drivers/i2c/slave/Kconfig.eeprom @@ -0,0 +1,64 @@ +# Kconfig - I2C EEPROM Slave configuration options + +# +# Copyright (c) 2017 BayLibre, SAS +# +# SPDX-License-Identifier: Apache-2.0 +# + +config I2C_EEPROM_SLAVE + bool "I2C Slave EEPROM driver" + depends on I2C_SLAVE + default n + help + Enable virtual I2C Slave EEPROM driver + +config I2C_EEPROM_SLAVE_0 + bool "Enable I2C Slave EEPROM driver instance 0" + depends on I2C_EEPROM_SLAVE + default y + +config I2C_EEPROM_SLAVE_0_SIZE + int "I2C Slave EEPROM 0 Size in KiB" + depends on I2C_EEPROM_SLAVE_0 + default 1 + +config I2C_EEPROM_SLAVE_0_NAME + string "I2C Slave EEPROM 0 device name" + depends on I2C_EEPROM_SLAVE_0 + default "EEPROM_SLAVE_0" + +config I2C_EEPROM_SLAVE_0_CONTROLLER_DEV_NAME + string "I2C Slave EEPROM 0 Controller device name" + depends on I2C_EEPROM_SLAVE_0 + default "I2C_0" + +config I2C_EEPROM_SLAVE_0_ADDRESS + hex "I2C Slave EEPROM 0 address" + depends on I2C_EEPROM_SLAVE_0 + default 0x54 + +config I2C_EEPROM_SLAVE_1 + bool "Enable I2C Slave EEPROM driver instance 1" + depends on I2C_EEPROM_SLAVE + default n + +config I2C_EEPROM_SLAVE_1_SIZE + int "I2C Slave EEPROM 1 Size in KiB" + depends on I2C_EEPROM_SLAVE_1 + default 1 + +config I2C_EEPROM_SLAVE_1_NAME + string "I2C Slave EEPROM 1 device name" + depends on I2C_EEPROM_SLAVE_1 + default "EEPROM_SLAVE_1" + +config I2C_EEPROM_SLAVE_1_CONTROLLER_DEV_NAME + string "I2C Slave EEPROM 1 Controller device name" + depends on I2C_EEPROM_SLAVE_1 + default "I2C_1" + +config I2C_EEPROM_SLAVE_1_ADDRESS + hex "I2C Slave EEPROM 1 address" + depends on I2C_EEPROM_SLAVE_1 + default 0x56 diff --git a/drivers/i2c/slave/eeprom_slave.c b/drivers/i2c/slave/eeprom_slave.c new file mode 100644 index 000000000000..9c06f6563578 --- /dev/null +++ b/drivers/i2c/slave/eeprom_slave.c @@ -0,0 +1,245 @@ +/* + * Copyright (c) 2017 BayLibre, SAS + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include + +#define SYS_LOG_LEVEL CONFIG_SYS_LOG_I2C_SLAVE_LEVEL +#include + +struct i2c_eeprom_slave_data { + struct device *i2c_controller; + struct i2c_slave_config config; + u32_t buffer_size; + u8_t *buffer; + u32_t buffer_idx; + bool first_write; +}; + +struct i2c_eeprom_slave_config { + char *controller_dev_name; + u8_t address; + u32_t buffer_size; + u8_t *buffer; +}; + +/* convenience defines */ +#define DEV_CFG(dev) \ + ((const struct i2c_eeprom_slave_config * const) \ + (dev)->config->config_info) +#define DEV_DATA(dev) \ + ((struct i2c_eeprom_slave_data * const)(dev)->driver_data) + +int eeprom_slave_program(struct device *dev, u8_t *eeprom_data, + unsigned int length) +{ + struct i2c_eeprom_slave_data *data = dev->driver_data; + + if (length > data->buffer_size) { + return -EINVAL; + } + + memcpy(data->buffer, eeprom_data, length); + + return 0; +} + +int eeprom_slave_read(struct device *dev, u8_t *eeprom_data, + unsigned int offset) +{ + struct i2c_eeprom_slave_data *data = dev->driver_data; + + if (!data || offset >= data->buffer_size) { + return -EINVAL; + } + + *eeprom_data = data->buffer[offset]; + + return 0; +} + +static int eeprom_slave_write_requested(struct i2c_slave_config *config) +{ + struct i2c_eeprom_slave_data *data = CONTAINER_OF(config, + struct i2c_eeprom_slave_data, + config); + + SYS_LOG_DBG("eeprom: write req"); + + data->first_write = true; + + return 0; +} + +static int eeprom_slave_read_requested(struct i2c_slave_config *config, + u8_t *val) +{ + struct i2c_eeprom_slave_data *data = CONTAINER_OF(config, + struct i2c_eeprom_slave_data, + config); + + *val = data->buffer[data->buffer_idx]; + + SYS_LOG_DBG("eeprom: read req, val=0x%x", *val); + + /* Increment will be done in the read_processed callback */ + + return 0; +} + +static int eeprom_slave_write_received(struct i2c_slave_config *config, + u8_t val) +{ + struct i2c_eeprom_slave_data *data = CONTAINER_OF(config, + struct i2c_eeprom_slave_data, + config); + + SYS_LOG_DBG("eeprom: write done, val=0x%x", val); + + /* In case EEPROM wants to be R/O, return !0 here could trigger + * a NACK to the I2C controller, support depends on the + * I2C controller support + */ + + if (data->first_write) { + data->buffer_idx = val; + data->first_write = false; + } else { + data->buffer[data->buffer_idx++] = val; + } + + data->buffer_idx = data->buffer_idx % data->buffer_size; + + return 0; +} + +static int eeprom_slave_read_processed(struct i2c_slave_config *config, + u8_t *val) +{ + struct i2c_eeprom_slave_data *data = CONTAINER_OF(config, + struct i2c_eeprom_slave_data, + config); + + /* Increment here */ + data->buffer_idx = (data->buffer_idx + 1) % data->buffer_size; + + *val = data->buffer[data->buffer_idx]; + + SYS_LOG_DBG("eeprom: read done, val=0x%x", *val); + + /* Increment will be done in the next read_processed callback + * In case of STOP, the byte won't be taken in account + */ + + return 0; +} + +static int eeprom_slave_stop(struct i2c_slave_config *config) +{ + struct i2c_eeprom_slave_data *data = CONTAINER_OF(config, + struct i2c_eeprom_slave_data, + config); + + SYS_LOG_DBG("eeprom: stop"); + + data->first_write = true; + + return 0; +} + +static int eeprom_slave_register(struct device *dev) +{ + struct i2c_eeprom_slave_data *data = dev->driver_data; + + return i2c_slave_register(data->i2c_controller, &data->config); +} + +static int eeprom_slave_unregister(struct device *dev) +{ + struct i2c_eeprom_slave_data *data = dev->driver_data; + + return i2c_slave_unregister(data->i2c_controller, &data->config); +} + +static const struct i2c_slave_driver_api api_funcs = { + .driver_register = eeprom_slave_register, + .driver_unregister = eeprom_slave_unregister, +}; + +static const struct i2c_slave_callbacks eeprom_callbacks = { + .write_requested = eeprom_slave_write_requested, + .read_requested = eeprom_slave_read_requested, + .write_received = eeprom_slave_write_received, + .read_processed = eeprom_slave_read_processed, + .stop = eeprom_slave_stop, +}; + +static int i2c_eeprom_slave_init(struct device *dev) +{ + struct i2c_eeprom_slave_data *data = DEV_DATA(dev); + const struct i2c_eeprom_slave_config *cfg = DEV_CFG(dev); + + data->i2c_controller = + device_get_binding(cfg->controller_dev_name); + if (!data->i2c_controller) { + SYS_LOG_ERR("i2c controller not found: %s", + cfg->controller_dev_name); + return -EINVAL; + } + + data->buffer_size = cfg->buffer_size; + data->buffer = cfg->buffer; + data->config.address = cfg->address; + data->config.callbacks = &eeprom_callbacks; + + return 0; +} + +#ifdef CONFIG_I2C_EEPROM_SLAVE_0 + +static struct i2c_eeprom_slave_data i2c_eeprom_slave_0_dev_data; + +static u8_t i2c_eeprom_slave_0_buffer[(CONFIG_I2C_EEPROM_SLAVE_0_SIZE * 1024)]; + +static const struct i2c_eeprom_slave_config i2c_eeprom_slave_0_cfg = { + .controller_dev_name = CONFIG_I2C_EEPROM_SLAVE_0_CONTROLLER_DEV_NAME, + .address = CONFIG_I2C_EEPROM_SLAVE_0_ADDRESS, + .buffer_size = (CONFIG_I2C_EEPROM_SLAVE_0_SIZE * 1024), + .buffer = i2c_eeprom_slave_0_buffer +}; + +DEVICE_AND_API_INIT(i2c_eeprom_slave_0, CONFIG_I2C_EEPROM_SLAVE_0_NAME, + &i2c_eeprom_slave_init, + &i2c_eeprom_slave_0_dev_data, &i2c_eeprom_slave_0_cfg, + POST_KERNEL, CONFIG_I2C_SLAVE_INIT_PRIORITY, + &api_funcs); + +#endif /* CONFIG_I2C_EEPROM_SLAVE_0 */ + +#ifdef CONFIG_I2C_EEPROM_SLAVE_1 + +static struct i2c_eeprom_slave_data i2c_eeprom_slave_1_dev_data; + +static u8_t i2c_eeprom_slave_1_buffer[(CONFIG_I2C_EEPROM_SLAVE_1_SIZE * 1024)]; + +static const struct i2c_eeprom_slave_config i2c_eeprom_slave_1_cfg = { + .controller_dev_name = CONFIG_I2C_EEPROM_SLAVE_1_CONTROLLER_DEV_NAME, + .address = CONFIG_I2C_EEPROM_SLAVE_1_ADDRESS, + .buffer_size = (CONFIG_I2C_EEPROM_SLAVE_1_SIZE * 1024), + .buffer = i2c_eeprom_slave_1_buffer +}; + +DEVICE_AND_API_INIT(i2c_eeprom_slave_1, CONFIG_I2C_EEPROM_SLAVE_1_NAME, + &i2c_eeprom_slave_init, + &i2c_eeprom_slave_1_dev_data, &i2c_eeprom_slave_1_cfg, + POST_KERNEL, CONFIG_I2C_SLAVE_INIT_PRIORITY, + &api_funcs); + +#endif /* CONFIG_I2C_EEPROM_SLAVE_1 */ diff --git a/include/drivers/i2c/slave/eeprom.h b/include/drivers/i2c/slave/eeprom.h new file mode 100644 index 000000000000..b64693a6c021 --- /dev/null +++ b/include/drivers/i2c/slave/eeprom.h @@ -0,0 +1,52 @@ +/** + * @file + * + * @brief Public APIs for the I2C EEPROM Slave driver. + */ + +/* + * Copyright (c) 2017 BayLibre, SAS + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef I2C_EEPROM_SLAVE_H +#define I2C_EEPROM_SLAVE_H + +/** + * @brief I2C EEPROM Slave Driver API + * @defgroup i2c_eeprom_slave_api I2C EEPROM Slave Driver API + * @ingroup io_interfaces + * @{ + */ + +/** + * @brief Program memory of the virtual EEPROM + * + * @param dev Pointer to the device structure for the driver instance. + * @param eeprom_data Pointer of data to program into the virtual eeprom memory + * @param length Length of data to program into the virtual eeprom memory + * + * @retval 0 If successful. + * @retval -EINVAL Invalid data size + */ +int eeprom_slave_program(struct device *dev, u8_t *eeprom_data, + unsigned int length); + +/** + * @brief Read single byte of virtual EEPROM memory + * + * @param dev Pointer to the device structure for the driver instance. + * @param eeprom_data Pointer of byte where to store the virtual eeprom memory + * @param offset Offset into EEPROM memory where to read the byte + * + * @retval 0 If successful. + * @retval -EINVAL Invalid data pointer or offset + */ +int eeprom_slave_read(struct device *dev, u8_t *eeprom_data, + unsigned int offset); + +/** + * @} + */ + +#endif /* I2C_EEPROM_SLAVE_H */ diff --git a/include/i2c.h b/include/i2c.h index d41a94adf1b1..2332eceed84a 100644 --- a/include/i2c.h +++ b/include/i2c.h @@ -58,6 +58,13 @@ extern "C" { /** Controller to act as Master. */ #define I2C_MODE_MASTER (1 << 4) +/* + * The following #defines are used to configure the I2C slave device + */ + +/** Slave device responds to 10-bit addressing. */ +#define I2C_SLAVE_FLAGS_ADDR_10_BITS I2C_ADDR_10_BITS + /* * I2C_MSG_* are I2C Message flags. */ @@ -110,16 +117,66 @@ union __deprecated dev_config { * These are for internal use only, so skip these in * public documentation. */ +struct i2c_slave_config; + +typedef int (*i2c_slave_write_requested_cb_t)( + struct i2c_slave_config *config); +typedef int (*i2c_slave_read_requested_cb_t)( + struct i2c_slave_config *config, u8_t *val); +typedef int (*i2c_slave_write_received_cb_t)( + struct i2c_slave_config *config, u8_t val); +typedef int (*i2c_slave_read_processed_cb_t)( + struct i2c_slave_config *config, u8_t *val); +typedef int (*i2c_slave_stop_cb_t)(struct i2c_slave_config *config); + +struct i2c_slave_callbacks { + /** callback function being called when write is requested */ + i2c_slave_write_requested_cb_t write_requested; + /** callback function being called when read is requested */ + i2c_slave_read_requested_cb_t read_requested; + /** callback function being called when byte has been received */ + i2c_slave_write_received_cb_t write_received; + /** callback function being called when byte has been sent */ + i2c_slave_read_processed_cb_t read_processed; + /** callback function being called when stop occurs on the bus */ + i2c_slave_stop_cb_t stop; +}; + +struct i2c_slave_config { + /** Private, do not modify */ + sys_snode_t node; + /** Flags for the slave device defined by I2C_SLAVE_FLAGS_* constants */ + u8_t flags; + /** Address for this slave device */ + u16_t address; + /** Callback functions */ + const struct i2c_slave_callbacks *callbacks; +}; + typedef int (*i2c_api_configure_t)(struct device *dev, u32_t dev_config); typedef int (*i2c_api_full_io_t)(struct device *dev, struct i2c_msg *msgs, u8_t num_msgs, u16_t addr); +typedef int (*i2c_api_slave_register_t)(struct device *dev, + struct i2c_slave_config *cfg); +typedef int (*i2c_api_slave_unregister_t)(struct device *dev, + struct i2c_slave_config *cfg); struct i2c_driver_api { i2c_api_configure_t configure; i2c_api_full_io_t transfer; + i2c_api_slave_register_t slave_register; + i2c_api_slave_unregister_t slave_unregister; +}; + +typedef int (*i2c_slave_api_register_t)(struct device *dev); +typedef int (*i2c_slave_api_unregister_t)(struct device *dev); + +struct i2c_slave_driver_api { + i2c_slave_api_register_t driver_register; + i2c_slave_api_unregister_t driver_unregister; }; /** * @endcond @@ -175,6 +232,116 @@ static inline int _impl_i2c_transfer(struct device *dev, return api->transfer(dev, msgs, num_msgs, addr); } +/** + * @brief Registers the provided config as Slave device + * + * Enable I2C slave mode for the 'dev' I2C bus driver using the provided + * 'config' struct containing the functions and parameters to send bus + * events. The I2C slave will be registered at the address provided as 'address' + * struct member. Addressing mode - 7 or 10 bit - depends on the 'flags' + * struct member. Any I2C bus events related to the slave mode will be passed + * onto I2C slave device driver via a set of callback functions provided in + * the 'callbacks' struct member. + * + * Most of the existing hardware allows simultaneous support for master + * and slave mode. This is however not guaranteed. + * + * @param dev Pointer to the device structure for the driver instance. + * @param cfg Config struct with functions and parameters used by the I2C driver + * to send bus events + * + * @retval 0 Is successful + * @retval -EINVAL If parameters are invalid + * @retval -EIO General input / output error. + * @retval -ENOTSUP If slave mode is not supported + */ +__syscall int i2c_slave_register(struct device *dev, + struct i2c_slave_config *cfg); + +static inline int _impl_i2c_slave_register(struct device *dev, + struct i2c_slave_config *cfg) +{ + const struct i2c_driver_api *api = dev->driver_api; + + if (!api->slave_register) { + return -ENOTSUP; + } + + return api->slave_register(dev, cfg); +} + +/** + * @brief Unregisters the provided config as Slave device + * + * This routine disables I2C slave mode for the 'dev' I2C bus driver using + * the provided 'config' struct containing the functions and parameters + * to send bus events. + * + * @param dev Pointer to the device structure for the driver instance. + * @param cfg Config struct with functions and parameters used by the I2C driver + * to send bus events + * + * @retval 0 Is successful + * @retval -EINVAL If parameters are invalid + * @retval -ENOTSUP If slave mode is not supported + */ +__syscall int i2c_slave_unregister(struct device *dev, + struct i2c_slave_config *cfg); + +static inline int _impl_i2c_slave_unregister(struct device *dev, + struct i2c_slave_config *cfg) +{ + const struct i2c_driver_api *api = dev->driver_api; + + if (!api->slave_unregister) { + return -ENOTSUP; + } + + return api->slave_unregister(dev, cfg); +} + +/** + * @brief Intructs the I2C Slave device to register itself to the I2C Controller + * + * This routine instructs the I2C Slave device to register itself to the I2C + * Controller. + * + * @param dev Pointer to the device structure for the driver instance. + * + * @retval 0 Is successful + * @retval -EINVAL If parameters are invalid + * @retval -EIO General input / output error. + */ +__syscall int i2c_slave_driver_register(struct device *dev); + +static inline int _impl_i2c_slave_driver_register(struct device *dev) +{ + const struct i2c_slave_driver_api *api = dev->driver_api; + + return api->driver_register(dev); +} + +/** + * @brief Intructs the I2C Slave device to unregister itself from the I2C + * Controller + * + * This routine instructs the I2C Slave device to unregister itself from the I2C + * Controller. + * + * @param dev Pointer to the device structure for the driver instance. + * + * @retval 0 Is successful + * @retval -EINVAL If parameters are invalid + */ +__syscall int i2c_slave_driver_unregister(struct device *dev); + +static inline int _impl_i2c_slave_driver_unregister(struct device *dev) +{ + const struct i2c_slave_driver_api *api = dev->driver_api; + + return api->driver_unregister(dev); +} + /* * Derived i2c APIs -- all implemented in terms of i2c_transfer() */ diff --git a/tests/drivers/i2c/i2c_slave_api/CMakeLists.txt b/tests/drivers/i2c/i2c_slave_api/CMakeLists.txt new file mode 100644 index 000000000000..31aa7233b60a --- /dev/null +++ b/tests/drivers/i2c/i2c_slave_api/CMakeLists.txt @@ -0,0 +1,18 @@ +set(KCONFIG_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/Kconfig) + +macro(set_conf_file) + if(EXISTS ${APPLICATION_SOURCE_DIR}/boards/${BOARD}.conf) + set(CONF_FILE "prj_base.conf ${APPLICATION_SOURCE_DIR}/boards/${BOARD}.conf") + else() + set(CONF_FILE "prj_virtual.conf") + endif() +endmacro() + +include($ENV{ZEPHYR_BASE}/cmake/app/boilerplate.cmake NO_POLICY_SCOPE) +project(NONE) + +add_subdirectory(common) + +FILE(GLOB app_sources src/*.c) + +target_sources(app PRIVATE ${app_sources}) diff --git a/tests/drivers/i2c/i2c_slave_api/Kconfig b/tests/drivers/i2c/i2c_slave_api/Kconfig new file mode 100644 index 000000000000..d2896445986d --- /dev/null +++ b/tests/drivers/i2c/i2c_slave_api/Kconfig @@ -0,0 +1,9 @@ +mainmenu "I2C Slave API Test" + +config ZEPHYR_BASE + string + option env="ZEPHYR_BASE" + +source "$ZEPHYR_BASE/Kconfig.zephyr" + +source "$ZEPHYR_BASE/tests/drivers/i2c/i2c_slave_api/common/Kconfig" diff --git a/tests/drivers/i2c/i2c_slave_api/boards/nucleo_f091rc.conf b/tests/drivers/i2c/i2c_slave_api/boards/nucleo_f091rc.conf new file mode 100644 index 000000000000..c47b7943c2db --- /dev/null +++ b/tests/drivers/i2c/i2c_slave_api/boards/nucleo_f091rc.conf @@ -0,0 +1,6 @@ +CONFIG_I2C_STM32_V2=y +CONFIG_I2C_STM32_INTERRUPT=y +CONFIG_I2C_1=y +CONFIG_I2C_EEPROM_SLAVE_0_CONTROLLER_DEV_NAME="I2C_1" +CONFIG_I2C_2=y +CONFIG_I2C_EEPROM_SLAVE_1_CONTROLLER_DEV_NAME="I2C_2" diff --git a/tests/drivers/i2c/i2c_slave_api/boards/stm32f072b_disco.conf b/tests/drivers/i2c/i2c_slave_api/boards/stm32f072b_disco.conf new file mode 100644 index 000000000000..c47b7943c2db --- /dev/null +++ b/tests/drivers/i2c/i2c_slave_api/boards/stm32f072b_disco.conf @@ -0,0 +1,6 @@ +CONFIG_I2C_STM32_V2=y +CONFIG_I2C_STM32_INTERRUPT=y +CONFIG_I2C_1=y +CONFIG_I2C_EEPROM_SLAVE_0_CONTROLLER_DEV_NAME="I2C_1" +CONFIG_I2C_2=y +CONFIG_I2C_EEPROM_SLAVE_1_CONTROLLER_DEV_NAME="I2C_2" diff --git a/tests/drivers/i2c/i2c_slave_api/common/CMakeLists.txt b/tests/drivers/i2c/i2c_slave_api/common/CMakeLists.txt new file mode 100644 index 000000000000..90b131c50c4c --- /dev/null +++ b/tests/drivers/i2c/i2c_slave_api/common/CMakeLists.txt @@ -0,0 +1 @@ +zephyr_sources_ifdef(CONFIG_I2C_VIRTUAL i2c_virtual.c) diff --git a/tests/drivers/i2c/i2c_slave_api/common/Kconfig b/tests/drivers/i2c/i2c_slave_api/common/Kconfig new file mode 100644 index 000000000000..9fbf80578928 --- /dev/null +++ b/tests/drivers/i2c/i2c_slave_api/common/Kconfig @@ -0,0 +1,20 @@ +# Kconfig - I2C slave tests common configuration options + +# +# Copyright (c) 2017 BayLibre, SAS +# +# SPDX-License-Identifier: Apache-2.0 +# + +config I2C_VIRTUAL + bool "Virtual I2C Driver for Slave development" + depends on I2C + default n + help + This driver exposes a virtual I2C driver used to debug virtual + I2C slave drivers. + +config I2C_VIRTUAL_NAME + string "Virtual Port device name" + depends on I2C_VIRTUAL + default "I2C_VIRTUAL" diff --git a/tests/drivers/i2c/i2c_slave_api/common/i2c_virtual.c b/tests/drivers/i2c/i2c_slave_api/common/i2c_virtual.c new file mode 100644 index 000000000000..524120edb47a --- /dev/null +++ b/tests/drivers/i2c/i2c_slave_api/common/i2c_virtual.c @@ -0,0 +1,229 @@ +/* + * Copyright (c) 2017 BayLibre, SAS + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include +#include + +#define SYS_LOG_LEVEL CONFIG_SYS_LOG_I2C_LEVEL +#include + +#define DEV_DATA(dev) ((struct i2c_virtual_data * const)(dev)->driver_data) + +struct i2c_virtual_data { + sys_slist_t slaves; +}; + +int i2c_virtual_runtime_configure(struct device *dev, u32_t config) +{ + return 0; +} + +static struct i2c_slave_config *find_address(struct i2c_virtual_data *data, + u16_t address, bool is_10bit) +{ + struct i2c_slave_config *cfg = NULL; + sys_snode_t *node; + bool search_10bit; + + SYS_SLIST_FOR_EACH_NODE(&data->slaves, node) { + cfg = CONTAINER_OF(node, struct i2c_slave_config, node); + + search_10bit = (cfg->flags & I2C_ADDR_10_BITS); + + if (cfg->address == address && search_10bit == is_10bit) { + return cfg; + } + } + + return NULL; +} + +/* Attach I2C slaves */ +int i2c_virtual_slave_register(struct device *dev, + struct i2c_slave_config *config) +{ + struct i2c_virtual_data *data = DEV_DATA(dev); + + if (!config) { + return -EINVAL; + } + + /* Check the address is unique */ + if (find_address(data, config->address, + (config->flags & I2C_ADDR_10_BITS))) { + return -EINVAL; + } + + sys_slist_append(&data->slaves, &config->node); + + return 0; +} + + +int i2c_virtual_slave_unregister(struct device *dev, + struct i2c_slave_config *config) +{ + struct i2c_virtual_data *data = DEV_DATA(dev); + + if (!config) { + return -EINVAL; + } + + if (!sys_slist_find_and_remove(&data->slaves, &config->node)) { + return -EINVAL; + } + + return 0; +} + +static int i2c_virtual_msg_write(struct device *dev, struct i2c_msg *msg, + struct i2c_slave_config *config, + bool prev_write) +{ + unsigned int len = 0; + u8_t *buf = msg->buf; + int ret; + + if (!prev_write) { + config->callbacks->write_requested(config); + } + + len = msg->len; + while (len) { + + ret = config->callbacks->write_received(config, *buf); + if (ret) { + goto error; + } + buf++; + len--; + } + + if (!(msg->flags & I2C_MSG_RESTART) && msg->flags & I2C_MSG_STOP) { + config->callbacks->stop(config); + } + + return 0; +error: + SYS_LOG_DBG("%s: NACK", __func__); + + return -EIO; +} + +static int i2c_virtual_msg_read(struct device *dev, struct i2c_msg *msg, + struct i2c_slave_config *config) +{ + unsigned int len = msg->len; + u8_t *buf = msg->buf; + + if (!msg->len) { + return 0; + } + + config->callbacks->read_requested(config, buf); + buf++; + len--; + + while (len) { + config->callbacks->read_processed(config, buf); + buf++; + len--; + } + + if (!(msg->flags & I2C_MSG_RESTART) && msg->flags & I2C_MSG_STOP) { + config->callbacks->stop(config); + } + + return 0; +} + +#define OPERATION(msg) (((struct i2c_msg *) msg)->flags & I2C_MSG_RW_MASK) + +static int i2c_virtual_transfer(struct device *dev, struct i2c_msg *msg, + u8_t num_msgs, u16_t slave) +{ + struct i2c_virtual_data *data = DEV_DATA(dev); + struct i2c_msg *current, *next; + struct i2c_slave_config *cfg; + bool is_write = false; + int ret = 0; + + cfg = find_address(data, slave, (msg->flags & I2C_ADDR_10_BITS)); + if (!cfg) { + return -EIO; + } + + current = msg; + current->flags |= I2C_MSG_RESTART; + while (num_msgs > 0) { + if (num_msgs > 1) { + next = current + 1; + + /* + * Stop or restart condition between messages + * of different directions is required + */ + if (OPERATION(current) != OPERATION(next)) { + if (!(next->flags & I2C_MSG_RESTART)) { + ret = -EINVAL; + break; + } + } + } + + /* Stop condition is required for the last message */ + if ((num_msgs == 1) && !(current->flags & I2C_MSG_STOP)) { + ret = -EINVAL; + break; + } + + if ((current->flags & I2C_MSG_RW_MASK) == I2C_MSG_WRITE) { + ret = i2c_virtual_msg_write(dev, current, + cfg, is_write); + is_write = true; + } else { + ret = i2c_virtual_msg_read(dev, current, cfg); + is_write = false; + } + + if (ret < 0) { + break; + } + + current++; + num_msgs--; + }; + + return ret; +} + +static const struct i2c_driver_api api_funcs = { + .configure = i2c_virtual_runtime_configure, + .transfer = i2c_virtual_transfer, + .slave_register = i2c_virtual_slave_register, + .slave_unregister = i2c_virtual_slave_unregister, +}; + +static int i2c_virtual_init(struct device *dev) +{ + struct i2c_virtual_data *data = DEV_DATA(dev); + + sys_slist_init(&data->slaves); + + return 0; +} + +static struct i2c_virtual_data i2c_virtual_dev_data_0; + +DEVICE_AND_API_INIT(i2c_virtual_0, CONFIG_I2C_VIRTUAL_NAME, &i2c_virtual_init, + &i2c_virtual_dev_data_0, NULL, + POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, + &api_funcs); diff --git a/tests/drivers/i2c/i2c_slave_api/prj_base.conf b/tests/drivers/i2c/i2c_slave_api/prj_base.conf new file mode 100644 index 000000000000..7a4b726945f9 --- /dev/null +++ b/tests/drivers/i2c/i2c_slave_api/prj_base.conf @@ -0,0 +1,9 @@ +CONFIG_SYS_LOG=y +CONFIG_SYS_LOG_I2C_LEVEL=4 +CONFIG_BOOT_BANNER=y +CONFIG_ZTEST=y +CONFIG_I2C=y +CONFIG_I2C_SLAVE=y +CONFIG_I2C_EEPROM_SLAVE=y +CONFIG_I2C_EEPROM_SLAVE_0=y +CONFIG_I2C_EEPROM_SLAVE_1=y diff --git a/tests/drivers/i2c/i2c_slave_api/prj_virtual.conf b/tests/drivers/i2c/i2c_slave_api/prj_virtual.conf new file mode 100644 index 000000000000..c9d7355d1fd7 --- /dev/null +++ b/tests/drivers/i2c/i2c_slave_api/prj_virtual.conf @@ -0,0 +1,12 @@ +CONFIG_SYS_LOG=y +CONFIG_SYS_LOG_I2C_LEVEL=4 +CONFIG_BOOT_BANNER=y +CONFIG_ZTEST=y +CONFIG_I2C=y +CONFIG_I2C_SLAVE=y +CONFIG_I2C_EEPROM_SLAVE=y +CONFIG_I2C_EEPROM_SLAVE_0=y +CONFIG_I2C_EEPROM_SLAVE_1=y +CONFIG_I2C_VIRTUAL=y +CONFIG_I2C_EEPROM_SLAVE_0_CONTROLLER_DEV_NAME="I2C_VIRTUAL" +CONFIG_I2C_EEPROM_SLAVE_1_CONTROLLER_DEV_NAME="I2C_VIRTUAL" diff --git a/tests/drivers/i2c/i2c_slave_api/src/main.c b/tests/drivers/i2c/i2c_slave_api/src/main.c new file mode 100644 index 000000000000..a0a911eef768 --- /dev/null +++ b/tests/drivers/i2c/i2c_slave_api/src/main.c @@ -0,0 +1,235 @@ +/* + * Copyright (c) 2017 BayLibre, SAS + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#define SYS_LOG_LEVEL SYS_LOG_LEVEL_DEBUG +#include + +#include +#include +#include +#include + +#include +#include + +#include + +#define TEST_DATA_SIZE 20 + +static u8_t eeprom_0_data[TEST_DATA_SIZE] = "0123456789abcdefghij"; +static u8_t eeprom_1_data[TEST_DATA_SIZE] = "jihgfedcba9876543210"; +static u8_t i2c_buffer[TEST_DATA_SIZE]; + +/* + * We need 5x(buffer size) + 1 to print a comma-separated list of each + * byte in hex, plus a null. + */ +u8_t buffer_print_eeprom[TEST_DATA_SIZE * 5 + 1]; +u8_t buffer_print_i2c[TEST_DATA_SIZE * 5 + 1]; + +static void to_display_format(const u8_t *src, size_t size, char *dst) +{ + size_t i; + + for (i = 0; i < size; i++) { + sprintf(dst + 5 * i, "0x%02x,", src[i]); + } +} + +static void run_full_read(struct device *i2c, u8_t addr, u8_t *comp_buffer) +{ + int ret; + + SYS_LOG_INF("Start full read. Master: %s, address: 0x%x", + i2c->config->name, addr); + + /* Read EEPROM from I2C Master requests, then compare */ + ret = i2c_burst_read(i2c, addr, + 0, i2c_buffer, TEST_DATA_SIZE); + zassert_equal(ret, 0, "Failed to read EEPROM"); + + if (memcmp(i2c_buffer, comp_buffer, TEST_DATA_SIZE)) { + to_display_format(i2c_buffer, TEST_DATA_SIZE, + buffer_print_i2c); + to_display_format(comp_buffer, TEST_DATA_SIZE, + buffer_print_eeprom); + SYS_LOG_ERR("Buffer contents are different: %s", + buffer_print_i2c); + SYS_LOG_ERR(" vs: %s", + buffer_print_eeprom); + + ztest_test_fail(); + } +} + +static void run_partial_read(struct device *i2c, u8_t addr, u8_t *comp_buffer, + unsigned int offset) +{ + int ret; + + SYS_LOG_INF("Start partial read. Master: %s, address: 0x%x, off=%d", + i2c->config->name, addr, offset); + + ret = i2c_burst_read(i2c, addr, + offset, i2c_buffer, TEST_DATA_SIZE-offset); + zassert_equal(ret, 0, "Failed to read EEPROM"); + + if (memcmp(i2c_buffer, &comp_buffer[offset], TEST_DATA_SIZE-offset)) { + to_display_format(i2c_buffer, TEST_DATA_SIZE-offset, + buffer_print_i2c); + to_display_format(&comp_buffer[offset], TEST_DATA_SIZE-offset, + buffer_print_eeprom); + SYS_LOG_ERR("Buffer contents are different: %s", + buffer_print_i2c); + SYS_LOG_ERR(" vs: %s", + buffer_print_eeprom); + + ztest_test_fail(); + } +} + +static void run_program_read(struct device *i2c, u8_t addr, unsigned int offset) +{ + int ret, i; + + SYS_LOG_INF("Start program. Master: %s, address: 0x%x, off=%d", + i2c->config->name, addr, offset); + + for (i = 0 ; i < TEST_DATA_SIZE-offset ; ++i) { + i2c_buffer[i] = i; + } + + ret = i2c_burst_write(i2c, addr, + offset, i2c_buffer, TEST_DATA_SIZE-offset); + zassert_equal(ret, 0, "Failed to write EEPROM"); + + memset(i2c_buffer, 0xFF, TEST_DATA_SIZE); + + /* Read back EEPROM from I2C Master requests, then compare */ + ret = i2c_burst_read(i2c, addr, + offset, i2c_buffer, TEST_DATA_SIZE-offset); + zassert_equal(ret, 0, "Failed to read EEPROM"); + + for (i = 0 ; i < TEST_DATA_SIZE-offset ; ++i) { + if (i2c_buffer[i] != i) { + to_display_format(i2c_buffer, TEST_DATA_SIZE-offset, + buffer_print_i2c); + SYS_LOG_ERR("Invalid Buffer content: %s", + buffer_print_i2c); + + ztest_test_fail(); + } + } +} + +void test_eeprom_slave(void) +{ + struct device *eeprom_0; + struct device *eeprom_1; + struct device *i2c_0; + struct device *i2c_1; + int ret, offset; + + i2c_0 = device_get_binding( + CONFIG_I2C_EEPROM_SLAVE_0_CONTROLLER_DEV_NAME); + zassert_not_null(i2c_0, "I2C device %s not found", + CONFIG_I2C_EEPROM_SLAVE_0_CONTROLLER_DEV_NAME); + + SYS_LOG_INF("Found I2C Master device %s", + CONFIG_I2C_EEPROM_SLAVE_0_CONTROLLER_DEV_NAME); + + i2c_1 = device_get_binding( + CONFIG_I2C_EEPROM_SLAVE_1_CONTROLLER_DEV_NAME); + zassert_not_null(i2c_1, "I2C device %s not found", + CONFIG_I2C_EEPROM_SLAVE_1_CONTROLLER_DEV_NAME); + + SYS_LOG_INF("Found I2C Master device %s", + CONFIG_I2C_EEPROM_SLAVE_1_CONTROLLER_DEV_NAME); + + eeprom_0 = device_get_binding(CONFIG_I2C_EEPROM_SLAVE_0_NAME); + zassert_not_null(eeprom_0, "EEPROM device %s not found", + CONFIG_I2C_EEPROM_SLAVE_0_NAME); + + SYS_LOG_INF("Found EEPROM device %s", CONFIG_I2C_EEPROM_SLAVE_0_NAME); + + eeprom_1 = device_get_binding(CONFIG_I2C_EEPROM_SLAVE_1_NAME); + zassert_not_null(eeprom_1, "EEPROM device %s not found", + CONFIG_I2C_EEPROM_SLAVE_1_NAME); + + SYS_LOG_INF("Found EEPROM device %s", CONFIG_I2C_EEPROM_SLAVE_1_NAME); + + /* Program dummy bytes */ + ret = eeprom_slave_program(eeprom_0, eeprom_0_data, TEST_DATA_SIZE); + zassert_equal(ret, 0, "Failed to program EEPROM %s", + CONFIG_I2C_EEPROM_SLAVE_0_NAME); + + ret = eeprom_slave_program(eeprom_1, eeprom_1_data, TEST_DATA_SIZE); + zassert_equal(ret, 0, "Failed to program EEPROM %s", + CONFIG_I2C_EEPROM_SLAVE_1_NAME); + + /* Attach EEPROM */ + ret = i2c_slave_driver_register(eeprom_0); + zassert_equal(ret, 0, "Failed to register EEPROM %s", + CONFIG_I2C_EEPROM_SLAVE_0_NAME); + + SYS_LOG_INF("EEPROM %s Attached !", CONFIG_I2C_EEPROM_SLAVE_0_NAME); + + ret = i2c_slave_driver_register(eeprom_1); + zassert_equal(ret, 0, "Failed to register EEPROM %s", + CONFIG_I2C_EEPROM_SLAVE_1_NAME); + + SYS_LOG_INF("EEPROM %s Attached !", CONFIG_I2C_EEPROM_SLAVE_1_NAME); + + /* Run Tests without bus access conflicts */ + run_full_read(i2c_0, CONFIG_I2C_EEPROM_SLAVE_1_ADDRESS, eeprom_1_data); + run_full_read(i2c_1, CONFIG_I2C_EEPROM_SLAVE_0_ADDRESS, eeprom_0_data); + + for (offset = 0 ; offset < TEST_DATA_SIZE-1 ; ++offset) { + run_partial_read(i2c_0, CONFIG_I2C_EEPROM_SLAVE_1_ADDRESS, + eeprom_1_data, offset); + } + + for (offset = 0 ; offset < TEST_DATA_SIZE-1 ; ++offset) { + run_partial_read(i2c_1, CONFIG_I2C_EEPROM_SLAVE_0_ADDRESS, + eeprom_0_data, offset); + } + + for (offset = 0 ; offset < TEST_DATA_SIZE-1 ; ++offset) { + run_program_read(i2c_0, CONFIG_I2C_EEPROM_SLAVE_1_ADDRESS, + offset); + } + + for (offset = 0 ; offset < TEST_DATA_SIZE-1 ; ++offset) { + run_program_read(i2c_1, CONFIG_I2C_EEPROM_SLAVE_0_ADDRESS, + offset); + } + + SYS_LOG_INF("Success !"); + + /* Detach EEPROM */ + ret = i2c_slave_driver_unregister(eeprom_0); + zassert_equal(ret, 0, "Failed to unregister EEPROM %s", + CONFIG_I2C_EEPROM_SLAVE_0_NAME); + + SYS_LOG_INF("EEPROM %s Detached !", + CONFIG_I2C_EEPROM_SLAVE_0_NAME); + + ret = i2c_slave_driver_unregister(eeprom_1); + zassert_equal(ret, 0, "Failed to unregister EEPROM %s", + CONFIG_I2C_EEPROM_SLAVE_1_NAME); + + SYS_LOG_INF("EEPROM %s Detached !", + CONFIG_I2C_EEPROM_SLAVE_1_NAME); +} + +void test_main(void) +{ + ztest_test_suite(test_eeprom_slave, ztest_unit_test(test_eeprom_slave)); + ztest_run_test_suite(test_eeprom_slave); +}