1 /*
2 * Copyright (c) 2021-2023 HPMicro
3 *
4 * SPDX-License-Identifier: BSD-3-Clause
5 *
6 */
7
8 #include "hpm_i2s_drv.h"
9
10 #define HPM_I2S_DRV_DEFAULT_RETRY_COUNT 5000U
11
12 #ifndef HPM_I2S_BCLK_TOLERANCE
13 #define HPM_I2S_BCLK_TOLERANCE (4U)
14 #endif
15
16 #define HPM_I2S_SLOT_MASK I2S_TXDSLOT_EN_MASK /* TX/RX has same SLOT MASK */
17
18
i2s_audio_depth_is_valid(uint8_t bits)19 static bool i2s_audio_depth_is_valid(uint8_t bits)
20 {
21 /* i2s audio depth only support 16bits, 24bits, 32bits */
22 if (bits == i2s_audio_depth_16_bits || bits == i2s_audio_depth_24_bits || bits == i2s_audio_depth_32_bits) {
23 return true;
24 }
25 return false;
26 }
27
i2s_channel_length_is_valid(uint8_t bits)28 static bool i2s_channel_length_is_valid(uint8_t bits)
29 {
30 /* i2s channel length only support 16bits or 32bits */
31 if (bits == i2s_channel_length_16_bits || bits == i2s_channel_length_32_bits) {
32 return true;
33 }
34 return false;
35 }
36
37 /* work around: fill dummy data into TX fifo to avoid TX underflow during tx start */
i2s_fill_tx_dummy_data(I2S_Type * ptr,uint8_t data_line,uint8_t data_count)38 hpm_stat_t i2s_fill_tx_dummy_data(I2S_Type *ptr, uint8_t data_line, uint8_t data_count)
39 {
40 uint32_t retry = 0;
41
42 if (data_count > I2S_SOC_MAX_TX_FIFO_DEPTH) {
43 return status_invalid_argument;
44 }
45
46 /* check dummy data count in TX FIFO */
47 while (i2s_get_tx_line_fifo_level(ptr, data_line) < data_count) {
48 ptr->TXD[data_line] = 0;
49 if (retry > HPM_I2S_DRV_DEFAULT_RETRY_COUNT * data_count) {
50 return status_timeout;
51 }
52 retry++;
53 }
54
55 return status_success;
56 }
57
58 /* The I2S software reset function relies on a working BCLK */
i2s_reset_all(I2S_Type * ptr)59 void i2s_reset_all(I2S_Type *ptr)
60 {
61 uint32_t cfgr_temp, misc_cfgr_temp;
62
63 /* disable I2S */
64 ptr->CTRL &= ~I2S_CTRL_I2S_EN_MASK;
65
66 /* enable internal clock for software reset function */
67 cfgr_temp = ptr->CFGR;
68 ptr->CFGR |= I2S_CFGR_BCLK_DIV_SET(1);
69 ptr->CFGR &= ~(I2S_CFGR_MCK_SEL_OP_MASK | I2S_CFGR_BCLK_SEL_OP_MASK | I2S_CFGR_FCLK_SEL_OP_MASK | I2S_CFGR_BCLK_GATEOFF_MASK);
70 misc_cfgr_temp = ptr->MISC_CFGR;
71 ptr->MISC_CFGR &= ~I2S_MISC_CFGR_MCLK_GATEOFF_MASK;
72
73 /* reset function block and clear fifo */
74 ptr->CTRL |= (I2S_CTRL_TXFIFOCLR_MASK | I2S_CTRL_RXFIFOCLR_MASK | I2S_CTRL_SFTRST_CLKGEN_MASK | I2S_CTRL_SFTRST_TX_MASK | I2S_CTRL_SFTRST_RX_MASK);
75 ptr->CTRL &= ~(I2S_CTRL_TXFIFOCLR_MASK | I2S_CTRL_RXFIFOCLR_MASK | I2S_CTRL_SFTRST_CLKGEN_MASK | I2S_CTRL_SFTRST_TX_MASK | I2S_CTRL_SFTRST_RX_MASK);
76
77 /* Restore the value of the register */
78 ptr->CFGR = cfgr_temp;
79 ptr->MISC_CFGR = misc_cfgr_temp;
80 }
81
i2s_get_default_config(I2S_Type * ptr,i2s_config_t * config)82 void i2s_get_default_config(I2S_Type *ptr, i2s_config_t *config)
83 {
84 (void) ptr;
85 config->invert_mclk_out = false;
86 config->invert_mclk_in = false;
87 config->use_external_mclk = false;
88 config->invert_bclk_out = false;
89 config->invert_bclk_in = false;
90 config->use_external_bclk = false;
91 config->invert_fclk_out = false;
92 config->invert_fclk_in = false;
93 config->use_external_fclk = false;
94 config->enable_mclk_out = false;
95 config->frame_start_at_rising_edge = false;
96 config->tx_fifo_threshold = 4;
97 config->rx_fifo_threshold = 4;
98 }
99
i2s_init(I2S_Type * ptr,i2s_config_t * config)100 void i2s_init(I2S_Type *ptr, i2s_config_t *config)
101 {
102 i2s_reset_all(ptr);
103
104 ptr->CFGR = I2S_CFGR_INV_MCLK_OUT_SET(config->invert_mclk_out)
105 | I2S_CFGR_INV_MCLK_IN_SET(config->invert_mclk_in)
106 | I2S_CFGR_MCK_SEL_OP_SET(config->use_external_mclk)
107 | I2S_CFGR_INV_BCLK_OUT_SET(config->invert_bclk_out)
108 | I2S_CFGR_INV_BCLK_IN_SET(config->invert_bclk_in)
109 | I2S_CFGR_BCLK_SEL_OP_SET(config->use_external_bclk)
110 | I2S_CFGR_INV_FCLK_OUT_SET(config->invert_fclk_out)
111 | I2S_CFGR_INV_FCLK_IN_SET(config->invert_fclk_in)
112 | I2S_CFGR_FCLK_SEL_OP_SET(config->use_external_fclk)
113 | I2S_CFGR_FRAME_EDGE_SET(config->frame_start_at_rising_edge);
114 ptr->MISC_CFGR = (ptr->MISC_CFGR
115 & ~(I2S_MISC_CFGR_MCLKOE_MASK
116 | I2S_MISC_CFGR_MCLK_GATEOFF_MASK))
117 | I2S_MISC_CFGR_MCLKOE_SET(config->enable_mclk_out);
118 ptr->FIFO_THRESH = I2S_FIFO_THRESH_TX_SET(config->tx_fifo_threshold)
119 | I2S_FIFO_THRESH_RX_SET(config->rx_fifo_threshold);
120 }
121
i2s_config_cfgr(I2S_Type * ptr,uint32_t bclk_div,i2s_transfer_config_t * config)122 static void i2s_config_cfgr(I2S_Type *ptr,
123 uint32_t bclk_div,
124 i2s_transfer_config_t *config)
125 {
126 i2s_gate_bclk(ptr);
127 ptr->CFGR = (ptr->CFGR & ~(I2S_CFGR_BCLK_DIV_MASK | I2S_CFGR_TDM_EN_MASK | I2S_CFGR_CH_MAX_MASK | I2S_CFGR_STD_MASK | I2S_CFGR_DATSIZ_MASK | I2S_CFGR_CHSIZ_MASK))
128 | I2S_CFGR_BCLK_DIV_SET(bclk_div)
129 | I2S_CFGR_TDM_EN_SET(config->enable_tdm_mode)
130 | I2S_CFGR_CH_MAX_SET(config->channel_num_per_frame)
131 | I2S_CFGR_STD_SET(config->protocol)
132 | I2S_CFGR_DATSIZ_SET(I2S_CFGR_DATASIZ(config->audio_depth))
133 | I2S_CFGR_CHSIZ_SET(I2S_CFGR_CHSIZ(config->channel_length));
134 i2s_ungate_bclk(ptr);
135 }
136
i2s_config_cfgr_slave(I2S_Type * ptr,i2s_transfer_config_t * config)137 static void i2s_config_cfgr_slave(I2S_Type *ptr,
138 i2s_transfer_config_t *config)
139 {
140 ptr->CFGR = (ptr->CFGR & ~(I2S_CFGR_TDM_EN_MASK | I2S_CFGR_CH_MAX_MASK | I2S_CFGR_STD_MASK | I2S_CFGR_DATSIZ_MASK | I2S_CFGR_CHSIZ_MASK))
141 | I2S_CFGR_TDM_EN_SET(config->enable_tdm_mode)
142 | I2S_CFGR_CH_MAX_SET(config->channel_num_per_frame)
143 | I2S_CFGR_STD_SET(config->protocol)
144 | I2S_CFGR_DATSIZ_SET(I2S_CFGR_DATASIZ(config->audio_depth))
145 | I2S_CFGR_CHSIZ_SET(I2S_CFGR_CHSIZ(config->channel_length));
146 }
147
i2s_calculate_bclk_divider(uint32_t mclk_in_hz,uint32_t bclk_in_hz,uint32_t * div_out)148 static bool i2s_calculate_bclk_divider(uint32_t mclk_in_hz, uint32_t bclk_in_hz, uint32_t *div_out)
149 {
150 uint32_t bclk_div;
151 uint32_t delta1, delta2;
152
153 bclk_div = mclk_in_hz / bclk_in_hz;
154
155 if ((bclk_div > (I2S_CFGR_BCLK_DIV_MASK >> I2S_CFGR_BCLK_DIV_SHIFT))) {
156 return false;
157 }
158
159 delta1 = mclk_in_hz - bclk_in_hz * bclk_div;
160 delta2 = bclk_in_hz * (bclk_div + 1) - mclk_in_hz;
161 if (delta2 < delta1) {
162 bclk_div++;
163 if ((bclk_div > (I2S_CFGR_BCLK_DIV_MASK >> I2S_CFGR_BCLK_DIV_SHIFT))) {
164 return false;
165 }
166 }
167
168 if (MIN(delta1, delta2) && ((MIN(delta1, delta2) * 100 / bclk_in_hz) > HPM_I2S_BCLK_TOLERANCE)) {
169 return false;
170 }
171
172 *div_out = bclk_div;
173 return true;
174 }
175
_i2s_config_tx(I2S_Type * ptr,i2s_transfer_config_t * config)176 static hpm_stat_t _i2s_config_tx(I2S_Type *ptr, i2s_transfer_config_t *config)
177 {
178 /* channel_num_per_frame has to even. non TDM mode, it has be 2 */
179 uint8_t channel_num_per_frame = HPM_NUM_TO_EVEN_CEILING(config->channel_num_per_frame);
180 if (!i2s_audio_depth_is_valid(config->audio_depth)
181 || !i2s_channel_length_is_valid(config->channel_length)
182 || !config->sample_rate
183 || !channel_num_per_frame
184 || (channel_num_per_frame > I2S_SOC_MAX_CHANNEL_NUM)
185 || ((!config->enable_tdm_mode) && (channel_num_per_frame > 2))
186 || ((config->channel_slot_mask & HPM_I2S_SLOT_MASK) == 0)) {
187 return status_invalid_argument;
188 }
189
190 ptr->TXDSLOT[config->data_line] = config->channel_slot_mask;
191
192 /* work around: fill dummy data into TX fifo to avoid TX underflow during tx start */
193 if (i2s_fill_tx_dummy_data(ptr, config->data_line, config->channel_num_per_frame) != status_success) {
194 return status_invalid_argument;
195 }
196
197 ptr->CTRL = (ptr->CTRL & ~(I2S_CTRL_TX_EN_MASK))
198 | I2S_CTRL_TX_EN_SET(1 << config->data_line);
199
200 return status_success;
201 }
202
_i2s_config_rx(I2S_Type * ptr,i2s_transfer_config_t * config)203 static hpm_stat_t _i2s_config_rx(I2S_Type *ptr, i2s_transfer_config_t *config)
204 {
205 /* channel_num_per_frame has to even. non TDM mode, it has be 2 */
206 uint8_t channel_num_per_frame = HPM_NUM_TO_EVEN_CEILING(config->channel_num_per_frame);
207 if (!i2s_audio_depth_is_valid(config->audio_depth)
208 || !i2s_channel_length_is_valid(config->channel_length)
209 || !config->sample_rate
210 || !channel_num_per_frame
211 || (channel_num_per_frame > I2S_SOC_MAX_CHANNEL_NUM)
212 || ((!config->enable_tdm_mode) && (channel_num_per_frame > 2))
213 || ((config->channel_slot_mask & HPM_I2S_SLOT_MASK) == 0)) {
214 return status_invalid_argument;
215 }
216
217 ptr->RXDSLOT[config->data_line] = config->channel_slot_mask;
218 ptr->CTRL = (ptr->CTRL & ~(I2S_CTRL_RX_EN_MASK))
219 | I2S_CTRL_RX_EN_SET(1 << config->data_line);
220
221 return status_success;
222 }
223
_i2s_config_transfer(I2S_Type * ptr,i2s_transfer_config_t * config)224 static hpm_stat_t _i2s_config_transfer(I2S_Type *ptr, i2s_transfer_config_t *config)
225 {
226 /* channel_num_per_frame has to even. non TDM mode, it has be 2 */
227 uint8_t channel_num_per_frame = HPM_NUM_TO_EVEN_CEILING(config->channel_num_per_frame);
228 if (!i2s_audio_depth_is_valid(config->audio_depth)
229 || !i2s_channel_length_is_valid(config->channel_length)
230 || !config->sample_rate
231 || !channel_num_per_frame
232 || (channel_num_per_frame > I2S_SOC_MAX_CHANNEL_NUM)
233 || ((!config->enable_tdm_mode) && (channel_num_per_frame > 2))
234 || ((config->channel_slot_mask & HPM_I2S_SLOT_MASK) == 0)) {
235 return status_invalid_argument;
236 }
237
238 /* Suppose RX and TX use same channel */
239 ptr->RXDSLOT[config->data_line] = config->channel_slot_mask;
240 ptr->TXDSLOT[config->data_line] = config->channel_slot_mask;
241
242 /* work around: fill dummy data into TX fifo to avoid TX underflow during tx start */
243 if (i2s_fill_tx_dummy_data(ptr, config->data_line, config->channel_num_per_frame) != status_success) {
244 return status_invalid_argument;
245 }
246
247 ptr->CTRL = (ptr->CTRL & ~(I2S_CTRL_RX_EN_MASK | I2S_CTRL_TX_EN_MASK))
248 | I2S_CTRL_RX_EN_SET(1 << config->data_line)
249 | I2S_CTRL_TX_EN_SET(1 << config->data_line);
250
251 return status_success;
252 }
253
i2s_config_tx(I2S_Type * ptr,uint32_t mclk_in_hz,i2s_transfer_config_t * config)254 hpm_stat_t i2s_config_tx(I2S_Type *ptr, uint32_t mclk_in_hz, i2s_transfer_config_t *config)
255 {
256 uint32_t bclk_in_hz;
257 uint32_t bclk_div;
258 uint8_t channel_num_per_frame = HPM_NUM_TO_EVEN_CEILING(config->channel_num_per_frame);
259
260 bclk_in_hz = config->sample_rate * config->channel_length * channel_num_per_frame;
261 if (!i2s_calculate_bclk_divider(mclk_in_hz, bclk_in_hz, &bclk_div)) {
262 return status_invalid_argument;
263 }
264
265 i2s_disable(ptr);
266 i2s_config_cfgr(ptr, bclk_div, config);
267
268 return _i2s_config_tx(ptr, config);
269 }
270
i2s_config_tx_slave(I2S_Type * ptr,i2s_transfer_config_t * config)271 hpm_stat_t i2s_config_tx_slave(I2S_Type *ptr, i2s_transfer_config_t *config)
272 {
273 i2s_disable(ptr);
274 i2s_config_cfgr_slave(ptr, config);
275
276 return _i2s_config_tx(ptr, config);
277 }
278
i2s_config_rx(I2S_Type * ptr,uint32_t mclk_in_hz,i2s_transfer_config_t * config)279 hpm_stat_t i2s_config_rx(I2S_Type *ptr, uint32_t mclk_in_hz, i2s_transfer_config_t *config)
280 {
281 uint32_t bclk_in_hz;
282 uint32_t bclk_div;
283
284 uint8_t channel_num_per_frame = HPM_NUM_TO_EVEN_CEILING(config->channel_num_per_frame);
285 bclk_in_hz = config->sample_rate * config->channel_length * channel_num_per_frame;
286 if (!i2s_calculate_bclk_divider(mclk_in_hz, bclk_in_hz, &bclk_div)) {
287 return status_invalid_argument;
288 }
289
290 i2s_disable(ptr);
291 i2s_config_cfgr(ptr, bclk_div, config);
292
293 return _i2s_config_rx(ptr, config);
294 }
295
i2s_config_rx_slave(I2S_Type * ptr,i2s_transfer_config_t * config)296 hpm_stat_t i2s_config_rx_slave(I2S_Type *ptr, i2s_transfer_config_t *config)
297 {
298 i2s_disable(ptr);
299 i2s_config_cfgr_slave(ptr, config);
300
301 return _i2s_config_rx(ptr, config);
302 }
303
i2s_config_transfer(I2S_Type * ptr,uint32_t mclk_in_hz,i2s_transfer_config_t * config)304 hpm_stat_t i2s_config_transfer(I2S_Type *ptr, uint32_t mclk_in_hz, i2s_transfer_config_t *config)
305 {
306 uint32_t bclk_in_hz;
307 uint32_t bclk_div;
308
309 uint8_t channel_num_per_frame = HPM_NUM_TO_EVEN_CEILING(config->channel_num_per_frame);
310 bclk_in_hz = config->sample_rate * config->channel_length * channel_num_per_frame;
311 if (!i2s_calculate_bclk_divider(mclk_in_hz, bclk_in_hz, &bclk_div)) {
312 return status_invalid_argument;
313 }
314
315 i2s_disable(ptr);
316 i2s_config_cfgr(ptr, bclk_div, config);
317
318 return _i2s_config_transfer(ptr, config);
319 }
320
i2s_config_transfer_slave(I2S_Type * ptr,i2s_transfer_config_t * config)321 hpm_stat_t i2s_config_transfer_slave(I2S_Type *ptr, i2s_transfer_config_t *config)
322 {
323 i2s_disable(ptr);
324 i2s_config_cfgr_slave(ptr, config);
325
326 return _i2s_config_transfer(ptr, config);
327 }
328
i2s_send_buff(I2S_Type * ptr,uint8_t tx_line_index,uint8_t samplebits,uint8_t * src,uint32_t size)329 uint32_t i2s_send_buff(I2S_Type *ptr, uint8_t tx_line_index, uint8_t samplebits, uint8_t *src, uint32_t size)
330 {
331 uint32_t data;
332 uint32_t retry = 0;
333 uint8_t bytes = samplebits / 8U;
334 uint32_t left;
335
336 if (!i2s_audio_depth_is_valid(samplebits)) {
337 return 0;
338 }
339
340 if ((size % bytes) != 0) {
341 return 0;
342 }
343
344 left = size;
345 while (left) {
346 /* check fifo status */
347 if (i2s_get_tx_line_fifo_level(ptr, tx_line_index) < I2S_FIFO_THRESH_TX_GET(ptr->FIFO_THRESH)) {
348 /* Move valid data to high position */
349 data = *((uint32_t *)(src)) << (32 - samplebits);
350 ptr->TXD[tx_line_index] = data;
351 src += bytes;
352 left -= bytes;
353 retry = 0;
354 } else {
355 if (retry > HPM_I2S_DRV_DEFAULT_RETRY_COUNT) {
356 break;
357 }
358 retry++;
359 }
360 }
361
362 return size - left;
363 }
364
i2s_receive_buff(I2S_Type * ptr,uint8_t rx_line_index,uint8_t samplebits,uint8_t * dst,uint32_t size)365 uint32_t i2s_receive_buff(I2S_Type *ptr, uint8_t rx_line_index, uint8_t samplebits, uint8_t *dst, uint32_t size)
366 {
367 uint32_t data;
368 uint32_t left;
369 uint32_t retry = 0;
370 uint8_t bytes = samplebits / 8U;
371
372 if (!i2s_audio_depth_is_valid(samplebits)) {
373 return 0;
374 }
375
376 if ((size % bytes) != 0) {
377 return 0;
378 }
379
380 left = size;
381 while (left) {
382 /* check fifo status */
383 if (i2s_get_rx_line_fifo_level(ptr, rx_line_index) < I2S_FIFO_THRESH_RX_GET(ptr->FIFO_THRESH)) {
384 /* valid data on high position */
385 data = ptr->RXD[rx_line_index] >> (32 - samplebits);
386 for (uint8_t n = 0; n < bytes; n++) {
387 *dst = (uint8_t)(data >> (8U * n)) & 0xFFU;
388 dst++;
389 left--;
390 retry = 0;
391 }
392 } else {
393 if (retry > HPM_I2S_DRV_DEFAULT_RETRY_COUNT) {
394 break;
395 }
396 retry++;
397 }
398 }
399
400 return size - left;
401 }
402
i2s_get_default_transfer_config_for_pdm(i2s_transfer_config_t * transfer)403 void i2s_get_default_transfer_config_for_pdm(i2s_transfer_config_t *transfer)
404 {
405 transfer->sample_rate = PDM_SOC_SAMPLE_RATE_IN_HZ;
406 transfer->channel_num_per_frame = 8;
407 transfer->channel_length = i2s_channel_length_32_bits;
408 transfer->audio_depth = i2s_audio_depth_32_bits;
409 transfer->enable_tdm_mode = true;
410 transfer->protocol = I2S_PROTOCOL_MSB_JUSTIFIED;
411 }
412
i2s_get_default_transfer_config_for_dao(i2s_transfer_config_t * transfer)413 void i2s_get_default_transfer_config_for_dao(i2s_transfer_config_t *transfer)
414 {
415 transfer->sample_rate = DAO_SOC_SAMPLE_RATE_IN_HZ;
416 transfer->channel_num_per_frame = 2;
417 transfer->channel_length = i2s_channel_length_32_bits;
418 transfer->audio_depth = i2s_audio_depth_32_bits;
419 transfer->enable_tdm_mode = false;
420 transfer->protocol = I2S_PROTOCOL_MSB_JUSTIFIED;
421 transfer->data_line = I2S_DATA_LINE_0;
422 transfer->channel_slot_mask = 0x3;
423 }
424
i2s_get_default_transfer_config(i2s_transfer_config_t * transfer)425 void i2s_get_default_transfer_config(i2s_transfer_config_t *transfer)
426 {
427 transfer->sample_rate = 48000U;
428 transfer->channel_num_per_frame = 2;
429 transfer->channel_length = i2s_channel_length_32_bits;
430 transfer->audio_depth = i2s_audio_depth_32_bits;
431 transfer->enable_tdm_mode = false;
432 transfer->protocol = I2S_PROTOCOL_MSB_JUSTIFIED;
433 transfer->data_line = I2S_DATA_LINE_0;
434 transfer->channel_slot_mask = 0x3;
435 }
436