1 /*******************************************************************************
2 * File Name: cyhal_i2s.c
3 *
4 * Description:
5 * Provides a high level interface for interacting with the Infineon I2S. This is
6 * a wrapper around the lower level PDL API.
7 *
8 ********************************************************************************
9 * \copyright
10 * Copyright 2018-2022 Cypress Semiconductor Corporation (an Infineon company) or
11 * an affiliate of Cypress Semiconductor Corporation
12 *
13 * SPDX-License-Identifier: Apache-2.0
14 *
15 * Licensed under the Apache License, Version 2.0 (the "License");
16 * you may not use this file except in compliance with the License.
17 * You may obtain a copy of the License at
18 *
19 * http://www.apache.org/licenses/LICENSE-2.0
20 *
21 * Unless required by applicable law or agreed to in writing, software
22 * distributed under the License is distributed on an "AS IS" BASIS,
23 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
24 * See the License for the specific language governing permissions and
25 * limitations under the License.
26 *******************************************************************************/
27
28 #include "cyhal_i2s.h"
29 #include "cyhal_audio_common.h"
30 #include "cyhal_system.h"
31
32 #if defined(COMPONENT_CAT1A) || defined(COMPONENT_CAT1B)
33 /**
34 * \addtogroup group_hal_impl_i2s I2S (Inter-IC Sound)
35 * \ingroup group_hal_impl
36 * \{
37 * The CAT1 (PSoC™ 6) I2S Supports the following values for word lengths:
38 * - 8 bits
39 * - 10 bits (CAT1B only)
40 * - 12 bits (CAT1B only)
41 * - 14 bits (CAT1B only)
42 * - 16 bits
43 * - 18 bits
44 * - 20 bits
45 * - 24 bits
46 * - 32 bits
47 *
48 * The channel length must be greater than or equal to the word length. On CAT1A devices, the
49 * set of supported channel lengths is the same as the set of supported word lengths. On CAT1B
50 * devices, the channel length may be any value between 8 and 32 bits.
51 *
52 * The sclk signal is formed by integer division of the input clock source (either internally
53 * provided or from the mclk pin). The CAT1A I2S supports sclk divider values from 1 to 64. On
54 * CAT1B devices, the I2S supports sclk divider values from 2 to 256.
55 *
56 * On CAT1A devices, if both RX and TX are used, the same GPIO must be specified for
57 * mclk in both directions. See the device datasheet for more details on valid pin selections.
58 *
59 * The following events are not supported on CAT1B:
60 * - \ref CYHAL_I2S_TX_EMPTY
61 * - \ref CYHAL_I2S_TX_NOT_FULL
62 * - \ref CYHAL_I2S_RX_FULL
63 * - \ref CYHAL_I2S_RX_NOT_EMPTY
64 *
65 * \note If the I2S hardware is initialized with a configurator-generated configuration via the
66 * @ref cyhal_i2s_init_cfg function, the @ref CYHAL_I2S_TX_HALF_EMPTY and @ref CYHAL_I2S_RX_HALF_FULL
67 * events will be raised at the configurator defined TX and RX FIFO trigger levels, respectively,
68 * instead of their usual trigger level of half the FIFO depth.
69 *
70 * \section section_psoc6_i2s_output_signals_behavior MXTDM SCLK/WS output signals behavior
71 * On devices with MXTDM IP block (e.g. CAT1B devices), in master role, I2S' SCK and WS signals starts toggling upon
72 * cyhal_i2s_init function call, while data is being transmitted / received after corresponding cyhal_i2s_start_*
73 * functions call. This is different to the behavior of MXAUDIOSS-based I2S (e.g. CAT1A, CAT1C devices), where SCK
74 * and WS signals starts toggling along with data receive / transmit process is being started with corresponding
75 * cyhal_i2s_start_* function call. This is important for power efficient applications. For them, it is recommended
76 * to init i2s master using @ref cyhal_i2s_init right before data transfers are performed and deinit it using
77 * @ref cyhal_i2s_free when no i2s transmission is expected in nearest time, to save power.
78 *
79 * \} group_hal_impl_i2s
80 */
81 #elif defined(COMPONENT_CAT2)
82 /**
83 * \addtogroup group_hal_impl_i2s I2S (Inter-IC Sound)
84 * \ingroup group_hal_impl
85 * \{
86 *
87 * The CAT2 (PSoC™ 4) I2S only supports TX in master mode.
88 *
89 * There are no trigger connections available from the I2S peripheral to other peripherals on
90 * the CAT2 platform. Hence, the \ref cyhal_i2s_enable_output and \ref cyhal_i2s_disable_output
91 * are not supported on this platform.
92 *
93 * It supports the following values for word lengths:
94 * - 8 bits
95 * - 16 bits
96 * - 18 bits
97 * - 20 bits
98 * - 24 bits
99 * - 32 bits
100 *
101 * The channel length must be greater than or equal to the word length. On CAT2 devices, the
102 * set of supported channel lengths is the same as the set of supported word lengths.
103 *
104 * The sclk signal is formed by integer division of the input clock source (either internally
105 * provided or from the mclk pin). The CAT2 I2S supports sclk divider values from 1 to 64.
106 *
107 * \note If the I2S hardware is initialized with a configurator-generated configuration via the
108 * @ref cyhal_i2s_init_cfg function, the @ref CYHAL_I2S_TX_HALF_EMPTY event will be raised at the
109 * configurator defined TX FIFO trigger level instead of the usual trigger level of half the FIFO depth.
110 *
111 * \} group_hal_impl_i2s
112 */
113 #endif
114
115 #if (CYHAL_DRIVER_AVAILABLE_I2S)
116
117 #if defined(__cplusplus)
118 extern "C"
119 {
120 #endif
121
122 #if defined(CY_IP_MXAUDIOSS)
123 static uint32_t _cyhal_i2s_convert_interrupt_cause(uint32_t pdl_cause);
124 static uint32_t _cyhal_i2s_convert_event(uint32_t event);
125 #elif defined(CY_IP_MXTDM)
126 static uint32_t _cyhal_i2s_convert_interrupt_cause(uint32_t pdl_cause, bool is_tx);
127 static uint32_t _cyhal_i2s_convert_event(uint32_t event, bool is_tx);
128 #endif
129 static void _cyhal_i2s_invoke_callback(_cyhal_audioss_t* obj, uint32_t event);
130
131 static const _cyhal_audioss_interface_t _cyhal_i2s_interface =
132 {
133 .convert_interrupt_cause = _cyhal_i2s_convert_interrupt_cause,
134 .convert_to_pdl = _cyhal_i2s_convert_event,
135 .invoke_user_callback = _cyhal_i2s_invoke_callback,
136 .event_mask_empty = CYHAL_I2S_TX_EMPTY,
137 .event_mask_half_empty = CYHAL_I2S_TX_HALF_EMPTY,
138 #if (CYHAL_DRIVER_AVAILABLE_I2S_RX)
139 .event_mask_full = CYHAL_I2S_RX_FULL,
140 .event_mask_half_full = CYHAL_I2S_RX_HALF_FULL,
141 .event_rx_complete = CYHAL_I2S_ASYNC_RX_COMPLETE,
142 #endif
143 .event_tx_complete = CYHAL_I2S_ASYNC_TX_COMPLETE,
144 .err_invalid_pin = CYHAL_I2S_RSLT_ERR_INVALID_PIN,
145 .err_invalid_arg = CYHAL_I2S_RSLT_ERR_INVALID_ARG,
146 .err_clock = CYHAL_I2S_RSLT_ERR_CLOCK,
147 .err_not_supported = CYHAL_I2S_RSLT_NOT_SUPPORTED,
148 };
149
cyhal_i2s_init(cyhal_i2s_t * obj,const cyhal_i2s_pins_t * tx_pins,const cyhal_i2s_pins_t * rx_pins,const cyhal_i2s_config_t * config,cyhal_clock_t * clk)150 cy_rslt_t cyhal_i2s_init(cyhal_i2s_t *obj, const cyhal_i2s_pins_t* tx_pins, const cyhal_i2s_pins_t* rx_pins,
151 const cyhal_i2s_config_t* config, cyhal_clock_t* clk)
152 {
153 _cyhal_audioss_pins_t rx_converted, tx_converted;
154 _cyhal_audioss_pins_t* rx_pin_ptr = NULL;
155 _cyhal_audioss_pins_t* tx_pin_ptr = NULL;
156 if(NULL != rx_pins)
157 {
158 rx_converted.sck = rx_pins->sck;
159 rx_converted.ws = rx_pins->ws;
160 rx_converted.data = rx_pins->data;
161 rx_converted.mclk = rx_pins->mclk;
162 rx_pin_ptr = &rx_converted;
163 }
164
165 if(NULL != tx_pins)
166 {
167 tx_converted.sck = tx_pins->sck;
168 tx_converted.ws = tx_pins->ws;
169 tx_converted.data = tx_pins->data;
170 tx_converted.mclk = tx_pins->mclk;
171 tx_pin_ptr = &tx_converted;
172 }
173
174 _cyhal_audioss_config_t converted_config =
175 {
176 .is_tx_slave = config->is_tx_slave,
177 .is_rx_slave = config->is_rx_slave,
178 .mclk_hz = config->mclk_hz,
179 .channel_length = config->channel_length,
180 .word_length = config->word_length,
181 .sample_rate_hz = config->sample_rate_hz,
182 /* The following values are fixed for the I2S format */
183 .num_channels = 2u,
184 .channel_mask = 0x3u, /* Both channels enabled */
185 .tx_ws_full = true,
186 #if (CYHAL_DRIVER_AVAILABLE_I2S_RX)
187 .rx_ws_full = true,
188 #endif
189 .is_i2s = true,
190 };
191
192 return _cyhal_audioss_init((_cyhal_audioss_t *)obj, tx_pin_ptr, rx_pin_ptr, &converted_config, clk, &_cyhal_i2s_interface);
193 }
194
cyhal_i2s_init_cfg(cyhal_i2s_t * obj,const cyhal_i2s_configurator_t * cfg)195 cy_rslt_t cyhal_i2s_init_cfg(cyhal_i2s_t *obj, const cyhal_i2s_configurator_t *cfg)
196 {
197 return _cyhal_audioss_init_cfg((_cyhal_audioss_t *)obj, cfg, &_cyhal_i2s_interface);
198 }
199
cyhal_i2s_register_callback(cyhal_i2s_t * obj,cyhal_i2s_event_callback_t callback,void * callback_arg)200 void cyhal_i2s_register_callback(cyhal_i2s_t *obj, cyhal_i2s_event_callback_t callback, void *callback_arg)
201 {
202 CY_ASSERT(NULL != obj);
203
204 uint32_t savedIntrStatus = cyhal_system_critical_section_enter();
205 obj->callback_data.callback = (cy_israddress) callback;
206 obj->callback_data.callback_arg = callback_arg;
207 cyhal_system_critical_section_exit(savedIntrStatus);
208 }
209
210 #if defined(CY_IP_MXAUDIOSS)
211
_cyhal_i2s_convert_interrupt_cause(uint32_t pdl_cause)212 static uint32_t _cyhal_i2s_convert_interrupt_cause(uint32_t pdl_cause)
213 {
214 cyhal_i2s_event_t result = (cyhal_i2s_event_t)0u;
215 if(0 != (pdl_cause & CY_I2S_INTR_TX_NOT_FULL))
216 {
217 result |= CYHAL_I2S_TX_NOT_FULL;
218 }
219 if(0 != (pdl_cause & CY_I2S_INTR_TX_TRIGGER))
220 {
221 result |= CYHAL_I2S_TX_HALF_EMPTY;
222 }
223 if(0 != (pdl_cause & CY_I2S_INTR_TX_EMPTY))
224 {
225 result |= CYHAL_I2S_TX_EMPTY;
226 }
227 if(0 != (pdl_cause & CY_I2S_INTR_TX_OVERFLOW))
228 {
229 result |= CYHAL_I2S_TX_OVERFLOW;
230 }
231 if(0 != (pdl_cause & CY_I2S_INTR_TX_UNDERFLOW))
232 {
233 result |= CYHAL_I2S_TX_UNDERFLOW ;
234 }
235 #if (CYHAL_DRIVER_AVAILABLE_I2S_RX)
236 if(0 != (pdl_cause & CY_I2S_INTR_RX_NOT_EMPTY))
237 {
238 result |= CYHAL_I2S_RX_NOT_EMPTY;
239 }
240 if(0 != (pdl_cause & CY_I2S_INTR_RX_TRIGGER))
241 {
242 result |= CYHAL_I2S_RX_HALF_FULL;
243 }
244 if(0 != (pdl_cause & CY_I2S_INTR_RX_FULL))
245 {
246 result |= CYHAL_I2S_RX_FULL;
247 }
248 if(0 != (pdl_cause & CY_I2S_INTR_RX_OVERFLOW))
249 {
250 result |= CYHAL_I2S_RX_OVERFLOW;
251 }
252 if(0 != (pdl_cause & CY_I2S_INTR_RX_UNDERFLOW))
253 {
254 result |= CYHAL_I2S_RX_UNDERFLOW;
255 }
256 #endif
257
258 return (uint32_t)result;
259 }
260
_cyhal_i2s_convert_event(uint32_t event)261 static uint32_t _cyhal_i2s_convert_event(uint32_t event)
262 {
263 cyhal_i2s_event_t hal_event = (cyhal_i2s_event_t)event;
264 uint32_t pdl_event = 0u;
265 if(0 != (hal_event & CYHAL_I2S_TX_NOT_FULL))
266 {
267 pdl_event |= CY_I2S_INTR_TX_NOT_FULL;
268 }
269 if(0 != (hal_event & CYHAL_I2S_TX_HALF_EMPTY))
270 {
271 pdl_event |= CY_I2S_INTR_TX_TRIGGER;
272 }
273 if(0 != (hal_event & CYHAL_I2S_TX_EMPTY))
274 {
275 pdl_event |= CY_I2S_INTR_TX_EMPTY;
276 }
277 if(0 != (hal_event & CYHAL_I2S_TX_OVERFLOW))
278 {
279 pdl_event |= CY_I2S_INTR_TX_OVERFLOW;
280 }
281 if(0 != (hal_event & CYHAL_I2S_TX_UNDERFLOW ))
282 {
283 pdl_event |= CY_I2S_INTR_TX_UNDERFLOW;
284 }
285 #if (CYHAL_DRIVER_AVAILABLE_I2S_RX)
286 if(0 != (hal_event & CYHAL_I2S_RX_NOT_EMPTY))
287 {
288 pdl_event |= CY_I2S_INTR_RX_NOT_EMPTY;
289 }
290 if(0 != (hal_event & CYHAL_I2S_RX_HALF_FULL))
291 {
292 pdl_event |= CY_I2S_INTR_RX_TRIGGER;
293 }
294 if(0 != (hal_event & CYHAL_I2S_RX_FULL))
295 {
296 pdl_event |= CY_I2S_INTR_RX_FULL;
297 }
298 if(0 != (hal_event & CYHAL_I2S_RX_OVERFLOW))
299 {
300 pdl_event |= CY_I2S_INTR_RX_OVERFLOW;
301 }
302 if(0 != (hal_event & CYHAL_I2S_RX_UNDERFLOW))
303 {
304 pdl_event |= CY_I2S_INTR_RX_UNDERFLOW;
305 }
306 #endif
307 return pdl_event;
308 }
309 #elif defined(CY_IP_MXTDM)
_cyhal_i2s_convert_event(uint32_t event,bool is_tx)310 static uint32_t _cyhal_i2s_convert_event(uint32_t event, bool is_tx)
311 {
312 cyhal_i2s_event_t hal_event = (cyhal_i2s_event_t)event;
313 uint32_t pdl_event = 0u;
314 if(is_tx)
315 {
316 /* Full/empty related interrupts not supported by this IP:
317 * * CYHAL_I2S_TX_NOT_FULL
318 * * CYHAL_I2S_TX_EMPTY
319 */
320 if(0 != (hal_event & CYHAL_I2S_TX_HALF_EMPTY))
321 {
322 pdl_event |= CY_TDM_INTR_TX_FIFO_TRIGGER;
323 }
324 if(0 != (hal_event & CYHAL_I2S_TX_OVERFLOW))
325 {
326 pdl_event |= CY_TDM_INTR_TX_FIFO_OVERFLOW;
327 }
328 if(0 != (hal_event & CYHAL_I2S_TX_UNDERFLOW ))
329 {
330 pdl_event |= CY_TDM_INTR_TX_FIFO_UNDERFLOW;
331 }
332 }
333 else
334 {
335 /* Full/empty related interrupts not supported by this IP:
336 * * CYHAL_I2S_RX_NOT_FULL
337 * * CYHAL_I2S_RX_EMPTY
338 */
339 if(0 != (hal_event & CYHAL_I2S_RX_HALF_FULL))
340 {
341 pdl_event |= CY_TDM_INTR_RX_FIFO_TRIGGER;
342 }
343 if(0 != (hal_event & CYHAL_I2S_RX_OVERFLOW))
344 {
345 pdl_event |= CY_TDM_INTR_RX_FIFO_OVERFLOW;
346 }
347 if(0 != (hal_event & CYHAL_I2S_RX_UNDERFLOW ))
348 {
349 pdl_event |= CY_TDM_INTR_RX_FIFO_UNDERFLOW;
350 }
351 }
352
353 return pdl_event;
354 }
355
_cyhal_i2s_convert_interrupt_cause(uint32_t pdl_cause,bool is_tx)356 static uint32_t _cyhal_i2s_convert_interrupt_cause(uint32_t pdl_cause, bool is_tx)
357 {
358 cyhal_i2s_event_t result = (cyhal_i2s_event_t)0u;
359 if(is_tx)
360 {
361 /* Full/empty related interrupts not supported by this IP:
362 * * CYHAL_I2S_TX_NOT_FULL
363 * * CYHAL_I2S_TX_EMPTY
364 */
365 if(0 != (pdl_cause & CY_TDM_INTR_TX_FIFO_TRIGGER))
366 {
367 result |= CYHAL_I2S_TX_HALF_EMPTY;
368 }
369 if(0 != (pdl_cause & CY_TDM_INTR_TX_FIFO_OVERFLOW))
370 {
371 result |= CYHAL_I2S_TX_OVERFLOW;
372 }
373 if(0 != (pdl_cause & CY_TDM_INTR_TX_FIFO_UNDERFLOW))
374 {
375 result |= CYHAL_I2S_TX_UNDERFLOW;
376 }
377 }
378 else
379 {
380 /* Full/empty related interrupts not supported by this IP:
381 * * CYHAL_I2S_RX_NOT_FULL
382 * * CYHAL_I2S_RX_EMPTY
383 */
384 if(0 != (pdl_cause & CY_TDM_INTR_RX_FIFO_TRIGGER))
385 {
386 result |= CYHAL_I2S_RX_HALF_FULL;
387 }
388 if(0 != (pdl_cause & CY_TDM_INTR_RX_FIFO_OVERFLOW))
389 {
390 result |= CYHAL_I2S_RX_OVERFLOW;
391 }
392 if(0 != (pdl_cause & CY_TDM_INTR_RX_FIFO_UNDERFLOW))
393 {
394 result |= CYHAL_I2S_RX_UNDERFLOW;
395 }
396 }
397
398 return (uint32_t)result;
399 }
400 #endif
401
_cyhal_i2s_invoke_callback(_cyhal_audioss_t * obj,uint32_t event)402 static void _cyhal_i2s_invoke_callback(_cyhal_audioss_t* obj, uint32_t event)
403 {
404 cyhal_i2s_event_callback_t callback = (cyhal_i2s_event_callback_t)obj->callback_data.callback;
405 if(NULL != callback)
406 {
407 callback(obj->callback_data.callback_arg, (cyhal_i2s_event_t)event);
408 }
409 }
410
cyhal_i2s_enable_output(cyhal_i2s_t * obj,cyhal_i2s_output_t output,cyhal_source_t * source)411 cy_rslt_t cyhal_i2s_enable_output(cyhal_i2s_t *obj, cyhal_i2s_output_t output, cyhal_source_t *source)
412 {
413 CY_ASSERT(CYHAL_I2S_TRIGGER_RX_HALF_FULL == output || CYHAL_I2S_TRIGGER_TX_HALF_EMPTY == output);
414 bool is_rx = CYHAL_I2S_TRIGGER_RX_HALF_FULL == output;
415 return _cyhal_audioss_enable_output(obj, is_rx, source);
416 }
417
cyhal_i2s_disable_output(cyhal_i2s_t * obj,cyhal_i2s_output_t output)418 cy_rslt_t cyhal_i2s_disable_output(cyhal_i2s_t *obj, cyhal_i2s_output_t output)
419 {
420 CY_ASSERT(CYHAL_I2S_TRIGGER_RX_HALF_FULL == output || CYHAL_I2S_TRIGGER_TX_HALF_EMPTY == output);
421 bool is_rx = CYHAL_I2S_TRIGGER_RX_HALF_FULL == output;
422 return _cyhal_audioss_disable_output(obj, is_rx);
423 }
424
425 #if defined(__cplusplus)
426 }
427 #endif
428
429 #endif /* CYHAL_DRIVER_AVAILABLE_I2S */
430