|
| 1 | +/* |
| 2 | + * Copyright (c) 2025 Espressif Systems (Shanghai) Co., Ltd. |
| 3 | + * |
| 4 | + * SPDX-License-Identifier: Apache-2.0 |
| 5 | + */ |
| 6 | + |
| 7 | +#include <zephyr/kernel.h> |
| 8 | +#include <zephyr/ztest.h> |
| 9 | +#include <zephyr/device.h> |
| 10 | +#include <zephyr/drivers/i2s.h> |
| 11 | + |
| 12 | +#define SAMPLE_NO 32 |
| 13 | +#define TIMEOUT 2000 |
| 14 | +#define FRAME_CLK_FREQ 8000 |
| 15 | + |
| 16 | +#define NUM_RX_BLOCKS 4 |
| 17 | +#define NUM_TX_BLOCKS 4 |
| 18 | + |
| 19 | +#define VAL_L 11 |
| 20 | +#define VAL_R 22 |
| 21 | + |
| 22 | +#define TRANSFER_REPEAT_COUNT 100 |
| 23 | + |
| 24 | +#define I2S_DEV_NODE_RX DT_ALIAS(i2s_node0) |
| 25 | +#ifdef CONFIG_I2S_TEST_SEPARATE_DEVICES |
| 26 | +#define I2S_DEV_NODE_TX DT_ALIAS(i2s_node1) |
| 27 | +#else |
| 28 | +#define I2S_DEV_NODE_TX DT_ALIAS(i2s_node0) |
| 29 | +#endif |
| 30 | + |
| 31 | +ZTEST_DMEM const struct device *dev_i2s_rx = DEVICE_DT_GET_OR_NULL(I2S_DEV_NODE_RX); |
| 32 | +ZTEST_DMEM const struct device *dev_i2s_tx = DEVICE_DT_GET_OR_NULL(I2S_DEV_NODE_TX); |
| 33 | +ZTEST_DMEM const struct device *dev_i2s = DEVICE_DT_GET_OR_NULL(I2S_DEV_NODE_RX); |
| 34 | + |
| 35 | +#define BLOCK_SIZE (2 * SAMPLE_NO * sizeof(int16_t)) |
| 36 | + |
| 37 | +K_MEM_SLAB_DEFINE(rx_mem_slab, BLOCK_SIZE, NUM_RX_BLOCKS, 32); |
| 38 | +K_MEM_SLAB_DEFINE(tx_mem_slab, BLOCK_SIZE, NUM_TX_BLOCKS, 32); |
| 39 | + |
| 40 | +void fill_buf(int16_t *tx_block, int16_t val_l, int16_t val_r) |
| 41 | +{ |
| 42 | + for (int16_t i = 0; i < SAMPLE_NO; i++) { |
| 43 | + tx_block[2 * i] = val_l + i; |
| 44 | + tx_block[2 * i + 1] = val_r + i; |
| 45 | + } |
| 46 | +} |
| 47 | + |
| 48 | +int verify_buf(int16_t *rx_block, int16_t val_l, int16_t val_r) |
| 49 | +{ |
| 50 | + int sample_no = SAMPLE_NO; |
| 51 | + |
| 52 | +#if (CONFIG_I2S_TEST_ALLOWED_DATA_DISCARD > 0) |
| 53 | + static ZTEST_DMEM int offset = -1; |
| 54 | + |
| 55 | + if (offset < 0) { |
| 56 | + do { |
| 57 | + ++offset; |
| 58 | + if (offset > CONFIG_I2S_TEST_ALLOWED_DATA_DISCARD) { |
| 59 | + TC_PRINT("Allowed data discard exceeded\n"); |
| 60 | + return -TC_FAIL; |
| 61 | + } |
| 62 | + } while ((rx_block[0] != val_l + offset) || (rx_block[1] != val_r + offset)); |
| 63 | + } |
| 64 | + |
| 65 | + val_l += offset; |
| 66 | + val_r += offset; |
| 67 | + sample_no -= offset; |
| 68 | +#endif |
| 69 | + |
| 70 | + for (int16_t i = 0; i < sample_no; i++) { |
| 71 | + if (rx_block[2 * i] != (val_l + i)) { |
| 72 | + TC_PRINT("Error: data_l mismatch at position " |
| 73 | + "%d, expected %d, actual %d\n", |
| 74 | + i, (int)(val_l + i), (int)rx_block[2 * i]); |
| 75 | + return -TC_FAIL; |
| 76 | + } |
| 77 | + if (rx_block[2 * i + 1] != (val_r + i)) { |
| 78 | + TC_PRINT("Error: data_r mismatch at position " |
| 79 | + "%d, expected %d, actual %d\n", |
| 80 | + i, (int)(val_r + i), (int)rx_block[2 * i + 1]); |
| 81 | + return -TC_FAIL; |
| 82 | + } |
| 83 | + } |
| 84 | + |
| 85 | + return TC_PASS; |
| 86 | +} |
| 87 | + |
| 88 | +static int tx_block_write_slab(const struct device *i2s_dev, int16_t val_l, int16_t val_r, int err, |
| 89 | + struct k_mem_slab *slab) |
| 90 | +{ |
| 91 | + char tx_block[BLOCK_SIZE]; |
| 92 | + int ret; |
| 93 | + |
| 94 | + fill_buf((uint16_t *)tx_block, val_l, val_r); |
| 95 | + ret = i2s_buf_write(i2s_dev, tx_block, BLOCK_SIZE); |
| 96 | + if (ret != err) { |
| 97 | + TC_PRINT("Error: i2s_write failed expected %d, actual %d\n", err, ret); |
| 98 | + return -TC_FAIL; |
| 99 | + } |
| 100 | + |
| 101 | + return TC_PASS; |
| 102 | +} |
| 103 | + |
| 104 | +int tx_block_write(const struct device *i2s_dev, int16_t val_l, int16_t val_r, int err) |
| 105 | +{ |
| 106 | + return tx_block_write_slab(i2s_dev, val_l, val_r, err, &tx_mem_slab); |
| 107 | +} |
| 108 | + |
| 109 | +static int rx_block_read_slab(const struct device *i2s_dev, int16_t val_l, int16_t val_r, |
| 110 | + struct k_mem_slab *slab) |
| 111 | +{ |
| 112 | + char rx_block[BLOCK_SIZE]; |
| 113 | + size_t rx_size; |
| 114 | + int ret; |
| 115 | + |
| 116 | + ret = i2s_buf_read(i2s_dev, rx_block, &rx_size); |
| 117 | + if (ret < 0 || rx_size != BLOCK_SIZE) { |
| 118 | + TC_PRINT("Error: Read failed\n"); |
| 119 | + return -TC_FAIL; |
| 120 | + } |
| 121 | + ret = verify_buf((uint16_t *)rx_block, val_l, val_r); |
| 122 | + if (ret < 0) { |
| 123 | + TC_PRINT("Error: Verify failed\n"); |
| 124 | + return -TC_FAIL; |
| 125 | + } |
| 126 | + |
| 127 | + return TC_PASS; |
| 128 | +} |
| 129 | + |
| 130 | +int rx_block_read(const struct device *i2s_dev, int16_t val_l, int16_t val_r) |
| 131 | +{ |
| 132 | + return rx_block_read_slab(i2s_dev, val_l, val_r, &rx_mem_slab); |
| 133 | +} |
| 134 | + |
| 135 | +int configure_stream(const struct device *i2s_dev, enum i2s_dir dir) |
| 136 | +{ |
| 137 | + int ret; |
| 138 | + struct i2s_config i2s_cfg = {0}; |
| 139 | + |
| 140 | + i2s_cfg.word_size = 16U; |
| 141 | + i2s_cfg.channels = 2U; |
| 142 | + i2s_cfg.format = I2S_FMT_DATA_FORMAT_I2S; |
| 143 | + i2s_cfg.frame_clk_freq = FRAME_CLK_FREQ; |
| 144 | + i2s_cfg.block_size = BLOCK_SIZE; |
| 145 | + i2s_cfg.timeout = TIMEOUT; |
| 146 | + |
| 147 | + if (dir == I2S_DIR_TX) { |
| 148 | + /* Configure the Transmit port as Master */ |
| 149 | + i2s_cfg.options = I2S_OPT_FRAME_CLK_MASTER | I2S_OPT_BIT_CLK_MASTER; |
| 150 | + } else if (dir == I2S_DIR_RX) { |
| 151 | + /* Configure the Receive port as Slave */ |
| 152 | + i2s_cfg.options = I2S_OPT_FRAME_CLK_SLAVE | I2S_OPT_BIT_CLK_SLAVE; |
| 153 | + } else { /* dir == I2S_DIR_BOTH */ |
| 154 | + i2s_cfg.options = I2S_OPT_FRAME_CLK_MASTER | I2S_OPT_BIT_CLK_MASTER; |
| 155 | + } |
| 156 | + |
| 157 | + if (!IS_ENABLED(CONFIG_I2S_TEST_USE_GPIO_LOOPBACK)) { |
| 158 | + i2s_cfg.options |= I2S_OPT_LOOPBACK; |
| 159 | + } |
| 160 | + |
| 161 | + if (dir == I2S_DIR_TX || dir == I2S_DIR_BOTH) { |
| 162 | + i2s_cfg.mem_slab = &tx_mem_slab; |
| 163 | + ret = i2s_configure(i2s_dev, I2S_DIR_TX, &i2s_cfg); |
| 164 | + if (ret < 0) { |
| 165 | + TC_PRINT("Failed to configure I2S TX stream (%d)\n", ret); |
| 166 | + return -TC_FAIL; |
| 167 | + } |
| 168 | + } |
| 169 | + |
| 170 | + if (dir == I2S_DIR_RX || dir == I2S_DIR_BOTH) { |
| 171 | + i2s_cfg.mem_slab = &rx_mem_slab; |
| 172 | + ret = i2s_configure(i2s_dev, I2S_DIR_RX, &i2s_cfg); |
| 173 | + if (ret < 0) { |
| 174 | + TC_PRINT("Failed to configure I2S RX stream (%d)\n", ret); |
| 175 | + return -TC_FAIL; |
| 176 | + } |
| 177 | + } |
| 178 | + |
| 179 | + return TC_PASS; |
| 180 | +} |
| 181 | + |
| 182 | +static void *setup(void) |
| 183 | +{ |
| 184 | + k_thread_access_grant(k_current_get(), &rx_mem_slab, &tx_mem_slab); |
| 185 | + k_object_access_grant(dev_i2s_rx, k_current_get()); |
| 186 | + k_object_access_grant(dev_i2s_tx, k_current_get()); |
| 187 | + |
| 188 | + return NULL; |
| 189 | +} |
| 190 | + |
| 191 | +static void before(void *fixture) |
| 192 | +{ |
| 193 | + ARG_UNUSED(fixture); |
| 194 | + |
| 195 | + int ret; |
| 196 | + |
| 197 | + zassert_not_null(dev_i2s_rx, "RX device not found"); |
| 198 | + zassert_true(device_is_ready(dev_i2s_rx), "device %s is not ready", dev_i2s_rx->name); |
| 199 | + |
| 200 | + zassert_not_null(dev_i2s_tx, "TX device not found"); |
| 201 | + zassert_true(device_is_ready(dev_i2s_tx), "device %s is not ready", dev_i2s_tx->name); |
| 202 | + |
| 203 | + ret = configure_stream(dev_i2s_rx, I2S_DIR_RX); |
| 204 | + zassert_equal(ret, TC_PASS); |
| 205 | + |
| 206 | + ret = configure_stream(dev_i2s_tx, I2S_DIR_TX); |
| 207 | + zassert_equal(ret, TC_PASS); |
| 208 | +} |
| 209 | + |
| 210 | +/** @brief I2S transfer. |
| 211 | + * |
| 212 | + * - START trigger starts both the transmission and reception. |
| 213 | + * - sending / receiving a sequence of data returns success. |
| 214 | + * - DRAIN trigger empties the transmit queue and stops both streams. |
| 215 | + */ |
| 216 | +ZTEST_USER(i2s_loopback, test_i2s_transfer) |
| 217 | +{ |
| 218 | + int ret; |
| 219 | + |
| 220 | + /* Prefill TX queue */ |
| 221 | + ret = tx_block_write(dev_i2s, VAL_L, VAL_R, 0); |
| 222 | + zassert_equal(ret, TC_PASS); |
| 223 | + |
| 224 | + ret = i2s_trigger(dev_i2s, I2S_DIR_BOTH, I2S_TRIGGER_START); |
| 225 | + zassert_equal(ret, 0, "RX/TX START trigger failed\n"); |
| 226 | + |
| 227 | + for (int i = 0; i < TRANSFER_REPEAT_COUNT; i++) { |
| 228 | + ret = tx_block_write(dev_i2s_tx, VAL_L, VAL_R, 0); |
| 229 | + zassert_equal(ret, TC_PASS); |
| 230 | + |
| 231 | + ret = rx_block_read(dev_i2s_rx, VAL_L, VAL_R); |
| 232 | + zassert_equal(ret, TC_PASS); |
| 233 | + } |
| 234 | + |
| 235 | + /* All data written, all but one data block read, flush TX queue |
| 236 | + * and stop both streams. |
| 237 | + */ |
| 238 | + ret = i2s_trigger(dev_i2s, I2S_DIR_BOTH, I2S_TRIGGER_DRAIN); |
| 239 | + zassert_equal(ret, 0, "RX/TX DRAIN trigger failed"); |
| 240 | +} |
| 241 | + |
| 242 | +ZTEST_SUITE(i2s_loopback, NULL, setup, before, NULL, NULL); |
0 commit comments