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