diff --git a/drivers/i2c/i2c_nrfx_twim.c b/drivers/i2c/i2c_nrfx_twim.c index 6be4b1c1050..953a03a7b73 100644 --- a/drivers/i2c/i2c_nrfx_twim.c +++ b/drivers/i2c/i2c_nrfx_twim.c @@ -5,6 +5,7 @@ */ #include +#include #include #include #include @@ -33,6 +34,29 @@ struct i2c_nrfx_twim_data { volatile nrfx_err_t res; }; +int i2c_nrfx_twim_exclusive_access_acquire(const struct device *dev, k_timeout_t timeout) +{ + struct i2c_nrfx_twim_data *dev_data = dev->data; + int ret; + + ret = k_sem_take(&dev_data->transfer_sync, timeout); + + if (ret == 0) { + (void)pm_device_runtime_get(dev); + } + + return ret; +} + +void i2c_nrfx_twim_exclusive_access_release(const struct device *dev) +{ + struct i2c_nrfx_twim_data *dev_data = dev->data; + + (void)pm_device_runtime_put(dev); + + k_sem_give(&dev_data->transfer_sync); +} + static int i2c_nrfx_twim_transfer(const struct device *dev, struct i2c_msg *msgs, uint8_t num_msgs, uint16_t addr) @@ -46,13 +70,11 @@ static int i2c_nrfx_twim_transfer(const struct device *dev, uint8_t *buf; uint16_t buf_len; - k_sem_take(&dev_data->transfer_sync, K_FOREVER); + (void)i2c_nrfx_twim_exclusive_access_acquire(dev, K_FOREVER); /* Dummy take on completion_sync sem to be sure that it is empty */ k_sem_take(&dev_data->completion_sync, K_NO_WAIT); - (void)pm_device_runtime_get(dev); - for (size_t i = 0; i < num_msgs; i++) { if (I2C_MSG_ADDR_10_BITS & msgs[i].flags) { ret = -ENOTSUP; @@ -164,9 +186,7 @@ static int i2c_nrfx_twim_transfer(const struct device *dev, msg_buf_used = 0; } - (void)pm_device_runtime_put(dev); - - k_sem_give(&dev_data->transfer_sync); + i2c_nrfx_twim_exclusive_access_release(dev); return ret; } diff --git a/include/zephyr/drivers/i2c/i2c_nrfx_twim.h b/include/zephyr/drivers/i2c/i2c_nrfx_twim.h new file mode 100644 index 00000000000..37b4a65a84b --- /dev/null +++ b/include/zephyr/drivers/i2c/i2c_nrfx_twim.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_DRIVERS_I2C_NRFX_TWIM_H +#define ZEPHYR_INCLUDE_DRIVERS_I2C_NRFX_TWIM_H + +#include +#include + +/** @brief Acquires exclusive access to the i2c bus controller. + * + * @param dev Pointer to the device structure for an I2C controller + * driver configured in controller mode. + * @param timeout Timeout for waiting to acquire exclusive access. + * + * @retval 0 If successful. + * @retval -EBUSY Returned without waiting. + * @retval -EAGAIN Waiting period timed out, + * or the underlying semaphore was reset during the waiting period. + */ +int i2c_nrfx_twim_exclusive_access_acquire(const struct device *dev, k_timeout_t timeout); + +/** @brief Releases exclusive access to the i2c bus controller. + * + * @param dev Pointer to the device structure for an I2C controller + * driver on which @ref i2c_nrfx_twim_exclusive_access_acquire + * has been successfully called. + */ +void i2c_nrfx_twim_exclusive_access_release(const struct device *dev); + +#endif /* ZEPHYR_INCLUDE_DRIVERS_I2C_NRFX_TWIM_H */ diff --git a/tests/drivers/i2c/i2c_nrfx_twim/CMakeLists.txt b/tests/drivers/i2c/i2c_nrfx_twim/CMakeLists.txt new file mode 100644 index 00000000000..65eaa7adcb1 --- /dev/null +++ b/tests/drivers/i2c/i2c_nrfx_twim/CMakeLists.txt @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(i2c_api) + +FILE(GLOB app_sources src/*.c) +target_sources(app PRIVATE ${app_sources}) diff --git a/tests/drivers/i2c/i2c_nrfx_twim/boards/nrf5340dk_nrf5340_cpuapp.overlay b/tests/drivers/i2c/i2c_nrfx_twim/boards/nrf5340dk_nrf5340_cpuapp.overlay new file mode 100644 index 00000000000..99a22ba075f --- /dev/null +++ b/tests/drivers/i2c/i2c_nrfx_twim/boards/nrf5340dk_nrf5340_cpuapp.overlay @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * SDA = P0.26 and P1.2 + * SCL = P0.25 and P1.3 + */ + +/ { + aliases { + i2c-controller = &i2c1; + i2c-controller-target = &i2c2; + }; +}; + +&pinctrl { + i2c2_default: i2c2_default { + group1 { + psels = , + ; + bias-pull-up; + }; + }; + + i2c2_sleep: i2c2_sleep { + group1 { + psels = , + ; + low-power-enable; + }; + }; +}; + +&i2c2 { + compatible = "nordic,nrf-twis"; + pinctrl-0 = <&i2c2_default>; + pinctrl-1 = <&i2c2_sleep>; + pinctrl-names = "default", "sleep"; + status = "okay"; +}; + +&i2c1 { + compatible = "nordic,nrf-twim"; + zephyr,concat-buf-size = <256>; + status = "okay"; +}; diff --git a/tests/drivers/i2c/i2c_nrfx_twim/boards/nrf54h20dk_nrf54h20_cpuapp.overlay b/tests/drivers/i2c/i2c_nrfx_twim/boards/nrf54h20dk_nrf54h20_cpuapp.overlay new file mode 100644 index 00000000000..cbe5db62983 --- /dev/null +++ b/tests/drivers/i2c/i2c_nrfx_twim/boards/nrf54h20dk_nrf54h20_cpuapp.overlay @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * SDA = P2.8 and P2.9 + * SCL = P1.2 and P1.3 + */ + +/ { + aliases { + i2c-controller = &i2c130; + i2c-controller-target = &i2c131; + }; +}; + +&pinctrl { + i2c130_default: i2c130_default { + group1 { + psels = , + ; + bias-pull-up; + }; + }; + + i2c130_sleep: i2c130_sleep { + group1 { + psels = , + ; + low-power-enable; + }; + }; + + i2c131_default: i2c131_default { + group1 { + psels = , + ; + bias-pull-up; + }; + }; + + i2c131_sleep: i2c131_sleep { + group1 { + psels = , + ; + low-power-enable; + }; + }; +}; + +&i2c130 { + compatible = "nordic,nrf-twim"; + clock-frequency = ; + pinctrl-0 = <&i2c130_default>; + pinctrl-1 = <&i2c130_sleep>; + pinctrl-names = "default", "sleep"; + zephyr,concat-buf-size = <256>; + memory-regions = <&cpuapp_dma_region>; + status = "okay"; +}; + +&i2c131 { + compatible = "nordic,nrf-twis"; + clock-frequency = ; + pinctrl-0 = <&i2c131_default>; + pinctrl-1 = <&i2c131_sleep>; + pinctrl-names = "default", "sleep"; + memory-regions = <&cpuapp_dma_region>; + status = "okay"; +}; diff --git a/tests/drivers/i2c/i2c_nrfx_twim/boards/nrf54l15dk_nrf54l15_cpuapp.overlay b/tests/drivers/i2c/i2c_nrfx_twim/boards/nrf54l15dk_nrf54l15_cpuapp.overlay new file mode 100644 index 00000000000..f24477f4102 --- /dev/null +++ b/tests/drivers/i2c/i2c_nrfx_twim/boards/nrf54l15dk_nrf54l15_cpuapp.overlay @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * SDA = P1.8 and P1.9 + * SCL = P1.10 and P1.11 + */ + +/ { + aliases { + i2c-controller = &i2c21; + i2c-controller-target = &i2c22; + }; +}; + +&pinctrl { + i2c21_default: i2c21_default { + group1 { + psels = , + ; + bias-pull-up; + }; + }; + + i2c21_sleep: i2c21_sleep { + group1 { + psels = , + ; + low-power-enable; + }; + }; + + i2c22_default: i2c22_default { + group1 { + psels = , + ; + bias-pull-up; + }; + }; + + i2c22_sleep: i2c22_sleep { + group1 { + psels = , + ; + low-power-enable; + }; + }; +}; + +&i2c21 { + compatible = "nordic,nrf-twim"; + pinctrl-0 = <&i2c21_default>; + pinctrl-1 = <&i2c21_sleep>; + pinctrl-names = "default", "sleep"; + zephyr,concat-buf-size = <256>; + status = "okay"; +}; + +&i2c22 { + compatible = "nordic,nrf-twis"; + pinctrl-0 = <&i2c22_default>; + pinctrl-1 = <&i2c22_sleep>; + pinctrl-names = "default", "sleep"; + status = "okay"; +}; diff --git a/tests/drivers/i2c/i2c_nrfx_twim/prj.conf b/tests/drivers/i2c/i2c_nrfx_twim/prj.conf new file mode 100644 index 00000000000..f14696bf38b --- /dev/null +++ b/tests/drivers/i2c/i2c_nrfx_twim/prj.conf @@ -0,0 +1,11 @@ +CONFIG_ZTEST=y +CONFIG_BOOT_BANNER=n + +CONFIG_LOG=y +CONFIG_I2C_LOG_LEVEL_INF=y +CONFIG_I2C=y +CONFIG_I2C_TARGET=y +CONFIG_I2C_TARGET_BUFFER_MODE=y +CONFIG_I2C_NRFX_TWIS_BUF_SIZE=256 + +CONFIG_ZERO_LATENCY_IRQS=y diff --git a/tests/drivers/i2c/i2c_nrfx_twim/src/test_i2c_nrfx_twim.c b/tests/drivers/i2c/i2c_nrfx_twim/src/test_i2c_nrfx_twim.c new file mode 100644 index 00000000000..f6256609fc0 --- /dev/null +++ b/tests/drivers/i2c/i2c_nrfx_twim/src/test_i2c_nrfx_twim.c @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include + +#define I2C_CONTROLLER_NODE DT_ALIAS(i2c_controller) +#define I2C_CONTROLLER_TARGET_NODE DT_ALIAS(i2c_controller_target) +#define I2C_CONTROLLER_DEVICE_GET DEVICE_DT_GET(I2C_CONTROLLER_NODE) +#define I2C_CONTROLLER_TARGET_DEVICE_GET DEVICE_DT_GET(I2C_CONTROLLER_TARGET_NODE) +#define I2C_TARGET_ADDR 0x0A + +static const struct device *sample_i2c_controller = I2C_CONTROLLER_DEVICE_GET; +static const struct device *sample_i2c_controller_target = I2C_CONTROLLER_TARGET_DEVICE_GET; + +#define TEST_TRANSFER_BUF_SIZE 16 +#define TARGET_RECEIVED_BUFFERS_CAPACITY 4 + +typedef struct { + uint32_t len; + uint8_t buf[TEST_TRANSFER_BUF_SIZE]; +} test_transfer_buf_t; + +static uint32_t target_received_buffers_count; +static test_transfer_buf_t target_received_buffers[TARGET_RECEIVED_BUFFERS_CAPACITY]; + +static void test_target_received_buffers_reset(void) +{ + target_received_buffers_count = 0; + memset(target_received_buffers, 0, sizeof(target_received_buffers)); +} + +BUILD_ASSERT(IS_ENABLED(CONFIG_I2C_TARGET_BUFFER_MODE), + "CONFIG_I2C_TARGET_BUFFER_MODE must be enabled"); + +#ifdef CONFIG_I2C_TARGET_BUFFER_MODE +static void sample_i2c_controller_target_buf_write_received_cb(struct i2c_target_config *config, + uint8_t *ptr, uint32_t len) +{ + if (target_received_buffers_count >= TARGET_RECEIVED_BUFFERS_CAPACITY) { + return; + } + + test_transfer_buf_t *transfer_buf = &target_received_buffers[target_received_buffers_count]; + + transfer_buf->len = len; + memcpy(transfer_buf->buf, ptr, MIN(len, TEST_TRANSFER_BUF_SIZE)); + target_received_buffers_count++; +} + +static int sample_i2c_controller_target_buf_read_requested_cb(struct i2c_target_config *config, + uint8_t **ptr, uint32_t *len) +{ + zassert_true(false, "Call to target_buf_read_requested_cb was unexpected"); + return -EIO; +} +#endif /* CONFIG_I2C_TARGET_BUFFER_MODE */ + +static const struct i2c_target_callbacks sample_i2c_controller_target_callbacks = { +#ifdef CONFIG_I2C_TARGET_BUFFER_MODE + .buf_write_received = sample_i2c_controller_target_buf_write_received_cb, + .buf_read_requested = sample_i2c_controller_target_buf_read_requested_cb, +#endif +}; + +static struct i2c_target_config sample_i2c_controller_target_config = { + .address = I2C_TARGET_ADDR, .callbacks = &sample_i2c_controller_target_callbacks}; + +static void test_prepare(void) +{ + int ret; + bool ret_bool; + + ret_bool = device_is_ready(sample_i2c_controller_target); + zassert_true(ret_bool, "sample_i2c_controller_target device is not ready"); + + ret = i2c_target_register(sample_i2c_controller_target, + &sample_i2c_controller_target_config); + zassert_equal(ret, 0, "sample_i2c_controller_target can't register target"); + + ret_bool = device_is_ready(sample_i2c_controller); + zassert_true(ret_bool, "sample_i2c_controller device is not ready"); + + test_target_received_buffers_reset(); +} + +#define SOME_OTHER_I2C_ACCESSING_THREAD_STACK_SIZE 1024 +static K_THREAD_STACK_DEFINE(some_other_i2c_accessing_thread_stack, + SOME_OTHER_I2C_ACCESSING_THREAD_STACK_SIZE); +static struct k_thread some_other_i2c_accessing_thread_data; +static K_SEM_DEFINE(some_other_i2c_accessing_thread_execute_sem, 0, 1); + +static void some_other_i2c_accessing_thread(void *param1, void *dummy2, void *dummy3) +{ + ARG_UNUSED(dummy2); + ARG_UNUSED(dummy3); + + while (true) { + if (k_sem_take(&some_other_i2c_accessing_thread_execute_sem, K_FOREVER) == 0) { + int ret; + test_transfer_buf_t *tx_buf = (test_transfer_buf_t *)param1; + + ret = i2c_write(sample_i2c_controller, tx_buf->buf, tx_buf->len, + I2C_TARGET_ADDR); + zassert_equal(ret, 0, "i2c_write failed"); + } + } +} + +static void some_other_i2c_accessing_thread_start(test_transfer_buf_t *tx_buf) +{ + (void)k_thread_create(&some_other_i2c_accessing_thread_data, + some_other_i2c_accessing_thread_stack, + K_THREAD_STACK_SIZEOF(some_other_i2c_accessing_thread_stack), + some_other_i2c_accessing_thread, tx_buf, NULL, NULL, + CONFIG_ZTEST_THREAD_PRIORITY, 0, K_NO_WAIT); +} + +ZTEST(i2c_nrfx_twim_async, test_01_i2c_nrfx_twim_exclusive_access) +{ + int ret; + + test_prepare(); + + static test_transfer_buf_t some_other_thread_tx_buf = { + .len = 3, + .buf = {0xE1, 0xE2, 0xE3}, + }; + + some_other_i2c_accessing_thread_start(&some_other_thread_tx_buf); + + ret = i2c_nrfx_twim_exclusive_access_acquire(sample_i2c_controller, K_FOREVER); + zassert_equal(ret, 0, "i2c_nrfx_twim_exclusive_access_acquire failed"); + + /* While we are holding exclusive access to the sample_i2c_controller, + * let the some_other_i2c_accessing_thread attempt to perform an i2c_write. + */ + + k_sem_give(&some_other_i2c_accessing_thread_execute_sem); + + /* Let the some_other_i2c_accessing_thread run for a while. */ + k_sleep(K_MSEC(100)); + + /* We are still holding the exclusive access so the some_other_i2c_accessing_thread + * waits on semaphore. No i2c transfer should occur. + */ + zassert_equal(target_received_buffers_count, 0); + + i2c_nrfx_twim_exclusive_access_release(sample_i2c_controller); + + /* Let the some_other_i2c_accessing_thread finally perform the i2c_write. */ + k_sleep(K_MSEC(100)); + + zassert_equal(target_received_buffers_count, 1); + zassert_equal(target_received_buffers[0].len, some_other_thread_tx_buf.len); + zassert_mem_equal(some_other_thread_tx_buf.buf, target_received_buffers[0].buf, + some_other_thread_tx_buf.len); +} + +ZTEST_SUITE(i2c_nrfx_twim_async, NULL, NULL, NULL, NULL, NULL); diff --git a/tests/drivers/i2c/i2c_nrfx_twim/testcase.yaml b/tests/drivers/i2c/i2c_nrfx_twim/testcase.yaml new file mode 100644 index 00000000000..e35dd49dbb7 --- /dev/null +++ b/tests/drivers/i2c/i2c_nrfx_twim/testcase.yaml @@ -0,0 +1,18 @@ +common: + depends_on: i2c + tags: + - drivers + - i2c + harness: ztest + harness_config: + fixture: i2c_bus_short + +tests: + drivers.i2c.nrfx_twim_async_api: + platform_allow: + - nrf5340dk/nrf5340/cpuapp + - nrf54h20dk/nrf54h20/cpuapp + - nrf54l15dk/nrf54l15/cpuapp + integration_platforms: + - nrf5340dk/nrf5340/cpuapp + - nrf54l15dk/nrf54l15/cpuapp