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