1 /*******************************************************************************
2 * File Name: cyhal_pdmpcm.c
3 *
4 * Description:
5 * Provides a high level interface for interacting with the Infineon I2C. 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 <math.h>
29 #include <stdlib.h>
30 #include <string.h> // For memset
31 #include "cyhal_clock.h"
32 #include "cyhal_dma.h"
33 #include "cyhal_gpio.h"
34 #include "cyhal_hwmgr.h"
35 #include "cyhal_pdmpcm.h"
36 #include "cyhal_syspm_impl.h"
37 #include "cyhal_system.h"
38 #include "cyhal_utils.h"
39 #include "cyhal_irq_impl.h"
40 #include "cy_device.h"
41 #include "cy_device_headers.h"
42 #include "cy_utils.h"
43 #if defined(CY_IP_MXAUDIOSS)
44 #include "cy_pdm_pcm.h"
45 #elif defined(CY_IP_MXPDM)
46 #include "cy_pdm_pcm_v2.h"
47 #endif
48 
49 /**
50 * \addtogroup group_hal_impl_pdmpcm PDM/PCM (Pulse Density Modulation to Pulse Code Modulation Converter)
51 * \ingroup group_hal_impl
52 * \{
53 * The CAT1A PDM/PCM Supports the following conversion parameters:<ul>
54 * <li>Mode: Mono Left, Mono Right, Stereo
55 * <li>Word Length: 16/18/20/24 bits</li>
56 * <li>Sampling Rate: up to 48kHz</li>
57 * <li>Left/Right Gain Amplifier: -12dB to +10.5dB in 1.5dB steps.</li>
58 * </ul>
59 *
60 * \note If the PDM/PCM block is initialized using the \ref cyhal_pdm_pcm_init function and
61 * the "channel recording swap" option is selected, the @ref CYHAL_PDM_PCM_MODE_LEFT and
62 * @ref CYHAL_PDM_PCM_MODE_RIGHT enum members refer to the origin left and right channels
63 * before the swap is applied.
64 *
65 * The CAT1B PDM/PCM Supports the following conversion parameters:<ul>
66 * <li>Mode: Mono Left, Mono Right, Stereo
67 * <li>Word Length: 8/10/12/14/16/18/20/24/32 bits</li>
68 * <li>Sampling Rate: up to 48kHz</li>
69 * <li>Decimation rates: 32, 64, 96 or 128
70 * <li>Left/Right Gain Amplifier: Supported gain values are given by the formula
71 *     `dB = 20 * log10(13921 / 2^x)` where `x` is an integer between 0 and 31 inclusive.
72 *     This provides a range of +82.5 to -103.5 dB, in approximately 5 dB steps.
73 *     The requested gain will rounded to the nearest supported value.
74 * </ul>
75 * \note On CAT1B devices, if stereo mode is selected, the length of all read operations
76 * should be a multiple of two so that the left and right channels are read together.
77 *
78 * \note On CAT1B devices, when multiple channels are configured in a configurator, each channel
79 * should be initialized via a separate call to \ref cyhal_pdm_pcm_init_cfg.
80 * \} group_hal_impl_pdmpcm
81 */
82 
83 #if (CYHAL_DRIVER_AVAILABLE_PDMPCM)
84 
85 #if defined(CY_IP_MXAUDIOSS_INSTANCES)
86 #define _CYHAL_PDM_INSTANCES (CY_IP_MXAUDIOSS_INSTANCES)
87 #if (CY_IP_MXAUDIOSS_INSTANCES >= 1)
88 #if (defined(AUDIOSS_PDM) && AUDIOSS_PDM) || (defined(AUDIOSS0_PDM) && AUDIOSS0_PDM) || (defined(AUDIOSS0_PDM_PDM) && AUDIOSS0_PDM_PDM)
89 #define _CYHAL_PDM_INST0 (1u)
90 #define _CYHAL_PDM_CHANNELS0 (1u)
91 #else
92 #define _CYHAL_PDM_INST0 (0u)
93 #endif
94 #endif
95 #if (CY_IP_MXAUDIOSS_INSTANCES >= 2)
96 #if (defined(AUDIOSS1_PDM) && AUDIOSS1_PDM) || (defined(AUDIOSS1_PDM_PDM) && AUDIOSS1_PDM_PDM)
97 #define _CYHAL_PDM_INST1 (1u)
98 #define _CYHAL_PDM_CHANNELS1 (1u)
99 #else
100 #define _CYHAL_PDM_INST1 (0u)
101 #endif
102 #endif
103 
104 #define _CYHAL_PDM_PCM_HALF_FIFO (0x80U)
105 #elif defined(CY_IP_MXPDM) /* defined(CY_IP_MXAUDIOSS_INSTANCES) */
106 #define _CYHAL_PDM_INSTANCES (CY_IP_MXPDM_INSTANCES)
107 #define _CYHAL_PDM_INST0 (_CYHAL_PDM_INSTANCES >= 1)
108 #define _CYHAL_PDM_INST1 (_CYHAL_PDM_INSTANCES >= 2)
109 #define _CYHAL_PDM_CHANNEL_PAIRING
110 
111 // TODO Remove this when DRIVERS-5627 is resolved
112 #define PDM0_NR (2u)
113 #if (_CYHAL_PDM_INST0)
114 #define _CYHAL_PDM_CHANNELS0 (PDM0_NR)
115 #endif
116 #if (_CYHAL_PDM_INST1)
117 #define _CYHAL_PDM_CHANNELS1 (PDM1_NR)
118 #endif
119 
120 #define _CYHAL_PDM_PCM_HALF_FIFO (0x20U)
121 #define _CYHAL_PDM_PCM_MAX_FIR1_SCALE (31)
122 
123 #else
124 #error "Unsupported PDM IP"
125 #endif
126 
127 #if defined(__cplusplus)
128 extern "C"
129 {
130 #endif
131 
132 #define _CYHAL_PDM_PCM_UNPAIRED (-1)
133 
134  // 35-45 PCM samples it takes for PCM to stabilize. Round to even so that same number of left and right samples are removed
135 #define _CYHAL_PDM_PCM_STABILIZATION_FS 46
136 #define _CYHAL_PDM_PCM_EVENT_NONE ((cyhal_pdm_pcm_event_t) 0x0)
137 #define _CYHAL_PDM_PCM_HFCLK (1)
138 
139 static PDM_Type *const _cyhal_pdm_pcm_base[] =
140 {
141 #if (_CYHAL_PDM_INST0)
142 #if (CY_IP_MXAUDIOSS_INSTANCES == 1)
143     PDM, /* Only MXAUDIOSS leaves off the trailing 0 for 1 instance. MXPDM keeps it. */
144 #else
145     PDM0,
146 #endif
147 #endif
148 #if (_CYHAL_PDM_INST1)
149     PDM1,
150 #endif
151 
152 #if (_CYHAL_PDM_INSTANCES > 2)
153     #warning Unhandled audioss instance count
154 #endif
155 };
156 
157 /* Note: We don't track channels in HWMGR because they are not completely independent. Instead we
158  * keep a pointer to the obj struct for each channel */
159 static bool _cyhal_pdm_pcm_arrays_initialized = false;
160 #if (_CYHAL_PDM_INST0)
161 CY_NOINIT static cyhal_pdm_pcm_t* _cyhal_pdm_pcm_config_structs_inst0[_CYHAL_PDM_CHANNELS0];
162 #endif
163 #if (_CYHAL_PDM_INST1)
164 CY_NOINIT static cyhal_pdm_pcm_t* _cyhal_pdm_pcm_config_structs_inst1[_CYHAL_PDM_CHANNELS1];
165 #endif
166 
167 static uint8_t _cyhal_pdm_num_channels[] =
168 {
169 #if (_CYHAL_PDM_INST0)
170     _CYHAL_PDM_CHANNELS0,
171 #endif
172 #if (_CYHAL_PDM_INST1)
173     _CYHAL_PDM_CHANNELS1,
174 #endif
175 };
176 
177 static cyhal_pdm_pcm_t** _cyhal_pdm_pcm_config_structs[_CYHAL_PDM_INSTANCES] =
178 {
179 #if (_CYHAL_PDM_INST0)
180     _cyhal_pdm_pcm_config_structs_inst0,
181 #endif
182 #if (_CYHAL_PDM_INST1)
183     _cyhal_pdm_pcm_config_structs_inst1,
184 #endif
185 };
186 
187 #if (_CYHAL_PDM_INST0)
188 static _cyhal_system_irq_t _cyhal_pdm_pcm_irq_n0[] =
189 {
190 #if (CY_IP_MXAUDIOSS_INSTANCES == 1)
191     audioss_interrupt_pdm_IRQn, /* Only MXAUDIOSS leaves off the trailing 0 for 1 instance. MXPDM keeps it. */
192 #elif defined(CY_IP_MXAUDIOSS)
193     audioss_0_interrupt_pdm_IRQn,
194 #elif defined(CY_IP_MXPDM)
195     pdm_0_interrupts_0_IRQn,
196     pdm_0_interrupts_1_IRQn,
197 #endif
198 };
199 #endif
200 
201 #if (_CYHAL_PDM_INST1)
202 static _cyhal_system_irq_t _cyhal_pdm_pcm_irq_n1[] =
203 {
204 #if defined(CY_IP_MXAUDIOSS)
205     audioss_1_interrupt_pdm_IRQn,
206 #endif
207 };
208 #endif
209 
210 static const _cyhal_system_irq_t* _cyhal_pdm_pcm_irq_n[] =
211 {
212 #if (_CYHAL_PDM_INST0)
213     _cyhal_pdm_pcm_irq_n0,
214 #endif
215 #if (_CYHAL_PDM_INST1)
216     _cyhal_pdm_pcm_irq_n1,
217 #endif
218 #if (_CYHAL_PDM_INSTANCES > 2)
219     #warning Unhandled audioss instance count
220 #endif
221 };
222 
_cyhal_pdm_pcm_get_block_from_irqn(_cyhal_system_irq_t irqn,uint8_t * block,uint8_t * channel)223 static void _cyhal_pdm_pcm_get_block_from_irqn(_cyhal_system_irq_t irqn, uint8_t* block, uint8_t* channel)
224 {
225     switch (irqn)
226     {
227 #if (_CYHAL_PDM_INST0)
228 #if (CY_IP_MXAUDIOSS_INSTANCES == 1)
229     case audioss_interrupt_pdm_IRQn: /* Only MXAUDIOSS leaves off the trailing 0 for 1 instance. MXPDM keeps it. */
230 #elif defined(CY_IP_MXAUDIOSS)
231     case audioss_0_interrupt_pdm_IRQn:
232 #elif defined(CY_IP_MXPDM)
233     case pdm_0_interrupts_1_IRQn:
234         *channel = 1;
235         *block = 0;
236         break;
237     case pdm_0_interrupts_0_IRQn:
238 #endif
239         *channel = 0;
240         *block = 0;
241         break;
242 #endif /* (_CYHAL_PDM_INST0) */
243 #if (_CYHAL_PDM_INST1 == 1)
244     case audioss_1_interrupt_pdm_IRQn:
245         *channel = 0;
246         *block = 1;
247         break;
248 #endif
249 #if (_CYHAL_PDM_INSTANCES > 2)
250     #warning Unhandled audioss instance count
251 #endif
252     default:
253         CY_ASSERT(false); // Should never be called with a non-PDM IRQn
254         *block = *channel = 0;
255         break;
256     }
257 }
258 
259 #if defined(CY_IP_MXPDM)
260 #define _CYHAL_PDM_USES_PCLK
261 static const en_clk_dst_t _cyhal_pdm_clock[] =
262 {
263 #if (CY_IP_MXPDM_INSTANCES >= 1)
264     PCLK_PDM0_CLK_IF_SRSS
265 #else
266     #warning Unhandled tdm instance count
267 #endif
268 };
269 #endif
270 
271 #if defined(CY_IP_MXAUDIOSS)
272 static const cy_stc_pdm_pcm_config_t _cyhal_pdm_pcm_default_config =
273 {
274     .clkDiv = CY_PDM_PCM_CLK_DIV_BYPASS, // Configured by cyhal_pdm_pcm_init
275     .mclkDiv = CY_PDM_PCM_CLK_DIV_BYPASS, // Configured by cyhal_pdm_pcm_init
276     .ckoDiv = 3U, // Configured by cyhal_pdm_pcm_init
277     .ckoDelay = 0U,
278     .sincDecRate = 32U, // Configured by cyhal_pdm_pcm_init
279     .chanSelect = CY_PDM_PCM_OUT_STEREO, // Configured by cyhal_pdm_pcm_init
280     .chanSwapEnable = false,
281     .highPassFilterGain = 8U,
282     .highPassDisable = false,
283     .softMuteCycles = CY_PDM_PCM_SOFT_MUTE_CYCLES_96,
284     .softMuteFineGain = 1UL,
285     .softMuteEnable = false,
286     .wordLen = CY_PDM_PCM_WLEN_16_BIT, // Configured by cyhal_pdm_pcm_init
287     .signExtension = true,
288     .gainLeft = CY_PDM_PCM_BYPASS, // Configured by cyhal_pdm_pcm_init and cyhal_pdm_pcm_set_gain
289     .gainRight = CY_PDM_PCM_BYPASS, // Configured by cyhal_pdm_pcm_init and cyhal_pdm_pcm_set_gain
290     .rxFifoTriggerLevel = _CYHAL_PDM_PCM_HALF_FIFO - 1,
291     .dmaTriggerEnable = false,
292     .interruptMask = 0UL,
293 };
294 #elif defined(CY_IP_MXPDM)
295 static const cy_stc_pdm_pcm_config_v2_t _cyhal_pdm_pcm_default_config =
296 {
297     /* clkDiv configured by pdm_pcm_init */
298     .clksel = CY_PDM_PCM_SEL_SRSS_CLOCK,
299     .halverate = CY_PDM_PCM_RATE_FULL,
300     .route = 0u, /* Will be updated as channels are enabled */
301     .fir0_coeff_user_value = 0u, /* Use default filter */
302     .fir1_coeff_user_value = 0u, /* Use default filter */
303 
304 };
305 
306 static const cy_stc_pdm_pcm_channel_config_t _cyhal_pdm_pcm_default_channel_config =
307 {
308     /* sampleDelay configured by init */
309     /* Word size configured by init */
310     .signExtension = true,
311     .rxFifoTriggerLevel = _CYHAL_PDM_PCM_HALF_FIFO - 1,
312     .fir0_enable = false, /* Default behavior is fir0 off and fir1 on */
313     .cic_decim_code = CY_PDM_PCM_CHAN_CIC_DECIM_32,
314     .fir0_decim_code = CY_PDM_PCM_CHAN_FIR0_DECIM_1, /* Ignored, FIR0 is disabled */
315     .fir0_scale = 0u, /* Ignored, FIR0 is disabled */
316     /* fir1 code and scale configured by init */
317     .dc_block_disable = false, /* DC disable is only for testing */
318     .dc_block_code = CY_PDM_PCM_CHAN_DCBLOCK_CODE_16
319 };
320 
321 #endif
322 
323 /* If check_used is set, return -1 if the paired channel is not used by this obj */
_cyhal_pdm_pcm_get_paired_channel(cyhal_pdm_pcm_t * obj,bool check_used)324 static inline int16_t _cyhal_pdm_pcm_get_paired_channel(cyhal_pdm_pcm_t* obj, bool check_used)
325 {
326 #if defined(_CYHAL_PDM_CHANNEL_PAIRING)
327     /* The first index of a pair is always even. So clear the lowest bit if we're odd, set if if we're even */
328     uint8_t channel_idx = obj->resource.channel_num;
329     int16_t paired_channel = channel_idx ^ 1;
330     if(paired_channel < _cyhal_pdm_num_channels[obj->resource.block_num])
331     {
332        if((false == check_used) || (obj == _cyhal_pdm_pcm_config_structs[obj->resource.block_num][paired_channel]))
333        {
334            return paired_channel;
335        }
336     }
337 #else
338     CY_UNUSED_PARAMETER(obj);
339     CY_UNUSED_PARAMETER(check_used);
340 #endif
341     return _CYHAL_PDM_PCM_UNPAIRED;
342 }
343 
_cyhal_pdm_pcm_find_existing_obj(uint8_t block_num)344 static cyhal_pdm_pcm_t* _cyhal_pdm_pcm_find_existing_obj(uint8_t block_num)
345 {
346     cyhal_pdm_pcm_t* existing_obj = NULL;
347     /* Reserve this instance if there are no existing channels */
348     for(int i = 0; NULL == existing_obj && i < _cyhal_pdm_num_channels[block_num]; ++i)
349     {
350         existing_obj = _cyhal_pdm_pcm_config_structs[block_num][i];
351     }
352     return existing_obj;
353 }
354 
_cyhal_pdm_pcm_get_pdl_event_mask(cyhal_pdm_pcm_event_t event)355 static inline uint32_t _cyhal_pdm_pcm_get_pdl_event_mask(cyhal_pdm_pcm_event_t event)
356 {
357     static const uint32_t status_map[] =
358     {
359         0u,                                     // Default, no value
360         (uint32_t)CY_PDM_PCM_INTR_RX_TRIGGER,   // CYHAL_PDM_PCM_RX_HALF_FULL
361 #if defined(CY_IP_MXAUDIOSS)
362         (uint32_t)CY_PDM_PCM_INTR_RX_NOT_EMPTY, // CYHAL_PDM_PCM_RX_NOT_EMPTY
363 #elif defined(CY_IP_MXPDM)
364         0u,
365 #endif
366         (uint32_t)CY_PDM_PCM_INTR_RX_OVERFLOW,  // CYHAL_PDM_PCM_RX_OVERFLOW
367         (uint32_t)CY_PDM_PCM_INTR_RX_UNDERFLOW, // CYHAL_PDM_PCM_RX_UNDERFLOW
368     };
369     return _cyhal_utils_convert_flags(status_map, sizeof(status_map) / sizeof(uint32_t), (uint32_t)event);
370 }
371 
_cyhal_pdm_pcm_get_hal_event(uint32_t pdl_event)372 static inline cyhal_pdm_pcm_event_t _cyhal_pdm_pcm_get_hal_event(uint32_t pdl_event)
373 {
374     cyhal_pdm_pcm_event_t hal_event = (cyhal_pdm_pcm_event_t)0u;
375     if(0u != (pdl_event & CY_PDM_PCM_INTR_RX_TRIGGER))
376     {
377         hal_event |= CYHAL_PDM_PCM_RX_HALF_FULL;
378     }
379 #if defined(CY_IP_MXAUDIOSS)
380     if(0u != (pdl_event & CY_PDM_PCM_INTR_RX_NOT_EMPTY))
381     {
382         hal_event |= CYHAL_PDM_PCM_RX_NOT_EMPTY;
383     }
384 #endif
385     if(0u != (pdl_event & CY_PDM_PCM_INTR_RX_OVERFLOW))
386     {
387         hal_event |= CYHAL_PDM_PCM_RX_OVERFLOW;
388     }
389     if(0u != (pdl_event & CY_PDM_PCM_INTR_RX_UNDERFLOW))
390     {
391         hal_event |= CYHAL_PDM_PCM_RX_UNDERFLOW;
392     }
393     return hal_event;
394 }
395 
396 
_cyhal_pdm_pcm_clear_interrupt(cyhal_pdm_pcm_t * obj,uint32_t interrupt)397 static inline void _cyhal_pdm_pcm_clear_interrupt(cyhal_pdm_pcm_t* obj, uint32_t interrupt)
398 {
399 #if defined(CY_IP_MXAUDIOSS)
400     Cy_PDM_PCM_ClearInterrupt(obj->base, interrupt);
401 #elif defined(CY_IP_MXPDM)
402     Cy_PDM_PCM_Channel_ClearInterrupt(obj->base, obj->resource.channel_num, interrupt);
403 #endif
404 }
405 
_cyhal_pdm_pcm_get_interrupt_mask(cyhal_pdm_pcm_t * obj)406 static inline uint32_t _cyhal_pdm_pcm_get_interrupt_mask(cyhal_pdm_pcm_t* obj)
407 {
408 #if defined(CY_IP_MXAUDIOSS)
409     return Cy_PDM_PCM_GetInterruptMask(obj->base);
410 #elif defined(CY_IP_MXPDM)
411     return Cy_PDM_PCM_Channel_GetInterruptMask(obj->base, obj->resource.channel_num);
412 #endif
413 }
414 
_cyhal_pdm_pcm_set_interrupt_mask(cyhal_pdm_pcm_t * obj,uint32_t mask)415 static inline void _cyhal_pdm_pcm_set_interrupt_mask(cyhal_pdm_pcm_t* obj, uint32_t mask)
416 {
417 #if defined(CY_IP_MXAUDIOSS)
418     Cy_PDM_PCM_SetInterruptMask(obj->base, mask);
419 #elif defined(CY_IP_MXPDM)
420     Cy_PDM_PCM_Channel_SetInterruptMask(obj->base, obj->resource.channel_num, mask);
421 #endif
422 }
423 
_cyhal_pdm_pcm_set_rx_fifo_level(cyhal_pdm_pcm_t * obj,uint8_t fifo_level)424 static inline void _cyhal_pdm_pcm_set_rx_fifo_level(cyhal_pdm_pcm_t *obj, uint8_t fifo_level)
425 {
426 #if defined(CY_IP_MXAUDIOSS)
427     PDM_PCM_RX_FIFO_CTL(obj->base) = _VAL2FLD(PDM_RX_FIFO_CTL_TRIGGER_LEVEL, fifo_level - 1);
428 #elif defined(CY_IP_MXPDM)
429     PDM_PCM_RX_FIFO_CTL(obj->base, obj->resource.channel_num) = _VAL2FLD(PDM_CH_RX_FIFO_CTL_TRIGGER_LEVEL, fifo_level - 1);
430     int16_t paired_channel = _cyhal_pdm_pcm_get_paired_channel(obj, true);
431     if(paired_channel >= 0)
432     {
433         PDM_PCM_RX_FIFO_CTL(obj->base, obj->resource.channel_num) = _VAL2FLD(PDM_CH_RX_FIFO_CTL_TRIGGER_LEVEL, fifo_level - 1);
434     }
435 #endif
436     _cyhal_pdm_pcm_clear_interrupt(obj, CY_PDM_PCM_INTR_RX_TRIGGER);
437 }
438 
_cyhal_pdm_pcm_pm_callback(cyhal_syspm_callback_state_t state,cyhal_syspm_callback_mode_t mode,void * callback_arg)439 static bool _cyhal_pdm_pcm_pm_callback(cyhal_syspm_callback_state_t state, cyhal_syspm_callback_mode_t mode, void* callback_arg)
440 {
441     CY_UNUSED_PARAMETER(state);
442     cyhal_pdm_pcm_t *obj = (cyhal_pdm_pcm_t *)callback_arg;
443 
444     switch (mode)
445     {
446         case CYHAL_SYSPM_CHECK_READY:
447         {
448             bool enabled = cyhal_pdm_pcm_is_enabled(obj);
449             obj->pm_transition_ready = !(enabled || cyhal_pdm_pcm_is_pending(obj));
450             break;
451         }
452         case CYHAL_SYSPM_CHECK_FAIL:
453         case CYHAL_SYSPM_AFTER_TRANSITION:
454             obj->pm_transition_ready = false;
455             break;
456         default:
457             CY_ASSERT(false);
458             break;
459     }
460     return obj->pm_transition_ready;
461 }
462 
_cyhal_pdm_pcm_increment_async_buffer(cyhal_pdm_pcm_t * obj,size_t increment)463 static inline void _cyhal_pdm_pcm_increment_async_buffer(cyhal_pdm_pcm_t *obj, size_t increment)
464 {
465     CY_ASSERT(obj->async_read_remaining >= increment);
466     uint32_t saved_intr = cyhal_system_critical_section_enter();
467     obj->async_read_remaining -= increment;
468     obj->async_buffer = (obj->async_read_remaining == 0)
469         ? NULL
470         : (void*)(((uint8_t*) obj->async_buffer) + increment * obj->word_size);
471     cyhal_system_critical_section_exit(saved_intr);
472 }
473 
_cyhal_pdm_pcm_try_read_async(cyhal_pdm_pcm_t * obj)474 static inline void _cyhal_pdm_pcm_try_read_async(cyhal_pdm_pcm_t *obj)
475 {
476     size_t read_remaining = obj->async_read_remaining;
477     cyhal_pdm_pcm_read(obj, obj->async_buffer, &read_remaining);
478     _cyhal_pdm_pcm_increment_async_buffer(obj, read_remaining);
479 }
480 
_cyhal_pdm_pcm_dma_start(cyhal_pdm_pcm_t * obj)481 static inline cy_rslt_t _cyhal_pdm_pcm_dma_start(cyhal_pdm_pcm_t *obj)
482 {
483     cy_rslt_t rslt;
484     size_t transfer_size = obj->user_trigger_level;
485     if (obj->async_read_remaining <= obj->user_trigger_level)
486     {
487         transfer_size = obj->async_read_remaining;
488         // Only want the user callback to be called on the last dma transfer.
489         cyhal_dma_enable_event(&(obj->dma), CYHAL_DMA_TRANSFER_COMPLETE, CYHAL_ISR_PRIORITY_DEFAULT, true);
490     }
491 
492     int16_t paired_channel = _cyhal_pdm_pcm_get_paired_channel(obj, true);
493 
494     cyhal_dma_cfg_t dma_cfg =
495     {
496 #if defined(CY_IP_MXAUDIOSS)
497         .src_addr = (uint32_t)(&(obj->base->RX_FIFO_RD)),
498 #elif defined(CY_IP_MXPDM)
499         .src_addr = (uint32_t)(&(obj->base->CH[obj->resource.channel_num].RX_FIFO_RD)),
500 #endif
501         .src_increment = 0,
502         .dst_addr = (uint32_t)obj->async_buffer,
503         .dst_increment = (paired_channel < 0) ? 1 : 2,
504         .transfer_width = 8 * obj->word_size,
505         .length = (paired_channel < 0) ? transfer_size : transfer_size / 2,
506         .burst_size = 0,
507         .action = CYHAL_DMA_TRANSFER_BURST,
508     };
509 
510     rslt = cyhal_dma_configure(&(obj->dma), &dma_cfg);
511 
512     #if defined (__DCACHE_PRESENT) && (__DCACHE_PRESENT == 1U)
513     SCB_InvalidateDCache_by_Addr((void *)obj->async_buffer, (transfer_size * obj->word_size));
514     #endif /* defined (__DCACHE_PRESENT) && (__DCACHE_PRESENT == 1U) */
515 
516     if (CY_RSLT_SUCCESS == rslt)
517     {
518         rslt = cyhal_dma_enable(&(obj->dma));
519     }
520 
521 #if defined(CY_IP_MXPDM)
522     if(paired_channel >= 0 && CY_RSLT_SUCCESS == rslt)
523     {
524         /* The first DMA was for the left samples. The second DMA will be for the right ones
525          * Start the transfer at the first element of async_buffer */
526         void* dst_addr = (((uint8_t*) obj->async_buffer) + obj->word_size);
527         cyhal_dma_cfg_t dma_cfg_paired =
528         {
529             .src_addr = (uint32_t)(&(obj->base->CH[paired_channel].RX_FIFO_RD)),
530             .src_increment = 0,
531             .dst_addr = (uint32_t)dst_addr,
532             .dst_increment = 2,
533             .transfer_width = 8 * obj->word_size,
534             .length = transfer_size / 2,
535             .burst_size = 0,
536             .action = CYHAL_DMA_TRANSFER_BURST,
537         };
538 
539         rslt = cyhal_dma_configure(&(obj->dma_paired), &dma_cfg_paired);
540 
541         if (CY_RSLT_SUCCESS == rslt)
542         {
543             rslt = cyhal_dma_enable(&(obj->dma_paired));
544         }
545     }
546 #endif
547 
548     if (CY_RSLT_SUCCESS == rslt)
549     {
550         _cyhal_pdm_pcm_increment_async_buffer(obj, transfer_size);
551         rslt = cyhal_dma_start_transfer(&(obj->dma));
552     }
553 #if defined(CY_IP_MXPDM)
554     if (paired_channel >= 0 && CY_RSLT_SUCCESS == rslt)
555     {
556         rslt = cyhal_dma_start_transfer(&(obj->dma_paired));
557     }
558 #endif
559     return rslt;
560 }
561 
_cyhal_pdm_pcm_hw_irq_handler(void)562 static void _cyhal_pdm_pcm_hw_irq_handler(void)
563 {
564     _cyhal_system_irq_t irqn = _cyhal_irq_get_active();
565     uint8_t block, channel;
566     _cyhal_pdm_pcm_get_block_from_irqn(irqn, &block, &channel);
567     cyhal_pdm_pcm_t *obj = _cyhal_pdm_pcm_config_structs[block][channel];
568 
569     if (obj != NULL)
570     {
571 #if defined(CY_IP_MXAUDIOSS)
572         uint32_t irq_status = Cy_PDM_PCM_GetInterruptStatus(obj->base);
573 #elif defined(CY_IP_MXPDM)
574         uint32_t irq_status = Cy_PDM_PCM_Channel_GetInterruptStatus(obj->base, obj->resource.channel_num);
575 #endif
576         cyhal_pdm_pcm_event_t event = _cyhal_pdm_pcm_get_hal_event(irq_status);
577 
578         if((CYHAL_PDM_PCM_RX_HALF_FULL & event) || (CYHAL_PDM_PCM_RX_OVERFLOW & event))
579         {
580             if (obj->stabilized)
581             {
582                 if (NULL != obj->async_buffer)
583                 {
584                     if (obj->dma.resource.type == CYHAL_RSC_INVALID)
585                     {
586                         if (obj->async_read_remaining > 0)
587                         {
588                             _cyhal_pdm_pcm_try_read_async(obj);
589                         }
590                         if (obj->async_read_remaining == 0)
591                         {
592                             event |= CYHAL_PDM_PCM_ASYNC_COMPLETE;
593                         }
594                     }
595                     else
596                     {
597                         if (obj->async_read_remaining > 0 && !cyhal_dma_is_busy(&(obj->dma)))
598                         {
599                             cy_rslt_t rslt = _cyhal_pdm_pcm_dma_start(obj);
600                             CY_UNUSED_PARAMETER(rslt);
601                             CY_ASSERT(CY_RSLT_SUCCESS == rslt);
602                         }
603                     }
604 
605                     if (obj->async_read_remaining == 0)
606                     {
607                         obj->async_buffer = NULL;
608                         if (!(obj->irq_cause & CYHAL_PDM_PCM_RX_HALF_FULL))
609                         {
610                             // Only disable the interrupt mask if the user did not explicitly enable the mask
611                             _cyhal_pdm_pcm_set_interrupt_mask(obj, _cyhal_pdm_pcm_get_interrupt_mask(obj) & ~CY_PDM_PCM_INTR_RX_TRIGGER);
612                         }
613                     }
614                 }
615             }
616             else
617             {
618                 /* Filter out the events that aren't applicable until we've stabilized */
619                 event &= ~(CYHAL_PDM_PCM_RX_HALF_FULL | CYHAL_PDM_PCM_RX_OVERFLOW);
620                 // The PDM/PCM block alternates between left and right in stereo.
621                 // To preserve oddness and eveness of left and right, removes an even number of elements.
622                 for (int i = 0; i < _CYHAL_PDM_PCM_STABILIZATION_FS; i++)
623                 {
624 #if defined(CY_IP_MXAUDIOSS)
625                     (void)Cy_PDM_PCM_ReadFifo(obj->base);
626 #elif defined(CY_IP_MXPDM)
627                     Cy_PDM_PCM_Channel_ReadFifo(obj->base, obj->resource.channel_num);
628                     int16_t paired_channel = _cyhal_pdm_pcm_get_paired_channel(obj, true);
629                     if(paired_channel >= 0)
630                     {
631                         (void)Cy_PDM_PCM_Channel_ReadFifo(obj->base, paired_channel);
632                     }
633 #endif
634                 }
635                 _cyhal_pdm_pcm_set_rx_fifo_level(obj, obj->user_trigger_level);
636                 if (!cyhal_pdm_pcm_is_pending(obj) && !(CYHAL_PDM_PCM_RX_HALF_FULL & obj->irq_cause))
637                 {
638                     _cyhal_pdm_pcm_set_interrupt_mask(obj, _cyhal_pdm_pcm_get_interrupt_mask(obj) & ~CY_PDM_PCM_INTR_RX_TRIGGER);
639                 }
640                 obj->stabilized = true;
641             }
642         }
643 
644         event &= obj->irq_cause;
645 
646         if (event != _CYHAL_PDM_PCM_EVENT_NONE)
647         {
648             cyhal_pdm_pcm_event_callback_t callback = (cyhal_pdm_pcm_event_callback_t) obj->callback_data.callback;
649             if (callback != NULL)
650             {
651                 callback(obj->callback_data.callback_arg, event);
652             }
653         }
654         _cyhal_pdm_pcm_clear_interrupt(obj, irq_status);
655     }
656 }
657 
658 #if defined(CY_IP_MXAUDIOSS)
_cyhal_pdm_pcm_invalid_gain_range(int16_t gain_value)659 static inline bool _cyhal_pdm_pcm_invalid_gain_range(int16_t gain_value)
660 {
661     return gain_value < CYHAL_PDM_PCM_MIN_GAIN || gain_value > CYHAL_PDM_PCM_MAX_GAIN;
662 }
663 
_cyhal_pdm_pcm_scale_gain_value(int8_t gain_value)664 static inline cy_en_pdm_pcm_gain_t _cyhal_pdm_pcm_scale_gain_value(int8_t gain_value)
665 {
666     // The hardware use gain rate of 1.5 dB per register increment,
667     // ranging from -12dB (register value 0x0) to 10.5dB (register value 0xF).
668     // Need to scale dB range [-24, 21] to register range [0x0, 0xF]
669     return (cy_en_pdm_pcm_gain_t) ((gain_value + 25) / 3);
670 }
671 
_cyhal_pdm_pcm_word_length_from_pdl(cy_en_pdm_pcm_word_len_t pdl_length)672 static inline uint8_t _cyhal_pdm_pcm_word_length_from_pdl(cy_en_pdm_pcm_word_len_t pdl_length)
673 {
674     switch(pdl_length)
675     {
676         case CY_PDM_PCM_WLEN_16_BIT:
677             return 16u;
678         case CY_PDM_PCM_WLEN_18_BIT:
679             return 18u;
680         case CY_PDM_PCM_WLEN_20_BIT:
681             return 20u;
682         case CY_PDM_PCM_WLEN_24_BIT:
683             return 24u;
684         default:
685             CY_ASSERT(false);
686             return 24u;
687     }
688 }
689 
690 #elif defined(CY_IP_MXPDM)
691 
_cyhal_pdm_pcm_word_length_from_pdl(cy_en_pdm_pcm_word_size_t pdl_length)692 static inline uint8_t _cyhal_pdm_pcm_word_length_from_pdl(cy_en_pdm_pcm_word_size_t pdl_length)
693 {
694     switch(pdl_length)
695     {
696         case CY_PDM_PCM_WSIZE_8_BIT:
697             return 8u;
698         case CY_PDM_PCM_WSIZE_10_BIT:
699             return 10u;
700         case CY_PDM_PCM_WSIZE_12_BIT:
701             return 12u;
702         case CY_PDM_PCM_WSIZE_14_BIT:
703             return 14u;
704         case CY_PDM_PCM_WSIZE_16_BIT:
705             return 16u;
706         case CY_PDM_PCM_WSIZE_18_BIT:
707             return 18u;
708         case CY_PDM_PCM_WSIZE_20_BIT:
709             return 20u;
710         case CY_PDM_PCM_WSIZE_24_BIT:
711             return 24u;
712         case CY_PDM_PCM_WSIZE_32_BIT:
713             return 32u;
714         default:
715             CY_ASSERT(false);
716             return 32u;
717     }
718 
719 }
_cyhal_pdm_pcm_word_length_to_pdl(uint8_t word_length,cy_en_pdm_pcm_word_size_t * pdl_length)720 static inline cy_rslt_t _cyhal_pdm_pcm_word_length_to_pdl(uint8_t word_length, cy_en_pdm_pcm_word_size_t *pdl_length)
721 {
722     switch(word_length)
723     {
724         case 8:
725             *pdl_length = CY_PDM_PCM_WSIZE_8_BIT;
726             break;
727         case 10:
728             *pdl_length = CY_PDM_PCM_WSIZE_10_BIT;
729             break;
730         case 12:
731             *pdl_length = CY_PDM_PCM_WSIZE_12_BIT;
732             break;
733         case 14:
734             *pdl_length = CY_PDM_PCM_WSIZE_14_BIT;
735             break;
736         case 16:
737             *pdl_length = CY_PDM_PCM_WSIZE_16_BIT;
738             break;
739         case 18:
740             *pdl_length = CY_PDM_PCM_WSIZE_18_BIT;
741             break;
742         case 20:
743             *pdl_length = CY_PDM_PCM_WSIZE_20_BIT;
744             break;
745         case 24:
746             *pdl_length = CY_PDM_PCM_WSIZE_24_BIT;
747             break;
748         case 32:
749             *pdl_length = CY_PDM_PCM_WSIZE_32_BIT;
750             break;
751         default:
752             return CYHAL_PDM_PCM_RSLT_ERR_INVALID_CONFIG_PARAM;
753     }
754 
755     return CY_RSLT_SUCCESS;
756 }
757 
_cyhal_pdm_pcm_convert_decim_rate(uint8_t decim_rate,cy_en_pdm_pcm_ch_fir1_decimcode_t * pdl_code)758 static inline cy_rslt_t _cyhal_pdm_pcm_convert_decim_rate(uint8_t decim_rate, cy_en_pdm_pcm_ch_fir1_decimcode_t* pdl_code)
759 {
760     switch(decim_rate)
761     {
762         case 32:
763             *pdl_code = CY_PDM_PCM_CHAN_FIR1_DECIM_1;
764             break;
765         case 64:
766             *pdl_code = CY_PDM_PCM_CHAN_FIR1_DECIM_2;
767             break;
768         case 96:
769             *pdl_code = CY_PDM_PCM_CHAN_FIR1_DECIM_3;
770             break;
771         case 128:
772             *pdl_code = CY_PDM_PCM_CHAN_FIR1_DECIM_4;
773             break;
774         default:
775             return CYHAL_PDM_PCM_RSLT_ERR_INVALID_CONFIG_PARAM;
776     }
777 
778     return CY_RSLT_SUCCESS;
779 }
780 
_cyhal_pdm_pcm_gain_to_scale(int16_t gain_val)781 static inline int _cyhal_pdm_pcm_gain_to_scale(int16_t gain_val)
782 {
783     /* This IP version doesn't support directly configuring the gain, but we can manipulate the FIR scale
784      * The formula for gain in db is db = 20 * log_10(FIR1_GAIN / 2^scale), where FIR1_GAIN = 13921
785      * Solving for scale, we get: scale = log_2(FIR1_GAIN / 10^(db / 20))
786      */
787 
788      /* Cmath only provides ln and log10, need to compute log_2 in terms of those */
789      const int FIR1_GAIN = 13921;
790      /* Gain is specified in 0.5 db steps in the interface */
791      float desired_gain = ((float)gain_val) * 0.5f;
792      float inner_value = FIR1_GAIN / (powf(10, (desired_gain / 20)));
793      float scale = logf(inner_value) / logf(2);
794      int scale_rounded = (uint8_t)(scale + 0.5f);
795      return scale_rounded;
796 }
797 
798 #endif
799 #if defined(CY_IP_MXAUDIOSS)
_cyhal_pdm_pcm_set_pdl_config_struct(cyhal_pdm_pcm_t * obj,const cyhal_pdm_pcm_cfg_t * cfg,cy_stc_pdm_pcm_config_t * pdl_config)800 static inline cy_rslt_t _cyhal_pdm_pcm_set_pdl_config_struct(cyhal_pdm_pcm_t* obj, const cyhal_pdm_pcm_cfg_t *cfg, cy_stc_pdm_pcm_config_t *pdl_config)
801 {
802     // PDM_CKO = sample_rate * decimation_rate
803     if (cfg->sample_rate > CYHAL_PDM_PCM_MAX_SAMPLE_RATE)
804     {
805         return CYHAL_PDM_PCM_RSLT_ERR_INVALID_CONFIG_PARAM;
806     }
807     uint32_t pdm_cko = cfg->sample_rate * cfg->decimation_rate;
808     uint32_t hf1_freq = cyhal_clock_get_frequency(&obj->clock);
809     // need to use 3 clock dividers to divied hf1_freq to pdm_cko
810     // divider 0 and 1 have values 1 to 4, divider 2 has values 2 to 16
811     uint8_t best_div0 = 1, best_div1 = 1, best_div2 = 2;
812     uint32_t min_error = UINT32_MAX;
813     for (uint8_t div1 = 1; div1 <= 4; div1++)
814     {
815         // start divider 0 at divider 1 's current value
816         // (div0, div1) = (2,3) is equivalent to (3,2)
817         for (uint8_t div0 = div1; div0 <= 4; div0++)
818         {
819             uint32_t div01_freq = div0 * div1 * pdm_cko;
820             for (uint8_t div2 = 2; div2 <= 16; div2++)
821             {
822                 uint32_t computed_hfclk1_freq = div01_freq * div2;
823                 uint32_t error = computed_hfclk1_freq < hf1_freq ? hf1_freq - computed_hfclk1_freq : computed_hfclk1_freq - hf1_freq;
824                 if (error < min_error)
825                 {
826                     best_div0 = div0;
827                     best_div1 = div1;
828                     best_div2 = div2;
829                     min_error = error;
830                 }
831             }
832         }
833     }
834     pdl_config->clkDiv = (cy_en_pdm_pcm_clk_div_t)(best_div0 - 1);
835     pdl_config->mclkDiv = (cy_en_pdm_pcm_clk_div_t)(best_div1 - 1);
836     pdl_config->ckoDiv = best_div2 - 1;
837 
838     // sinc_rate = decimation_rate / 2
839     // decimation rate is always valid. The max value for sync rate is 0x7F
840     pdl_config->sincDecRate = (cfg->decimation_rate) / 2;
841 
842     switch(cfg->mode)
843     {
844         case CYHAL_PDM_PCM_MODE_LEFT:
845             pdl_config->chanSelect = CY_PDM_PCM_OUT_CHAN_LEFT;
846             break;
847         case CYHAL_PDM_PCM_MODE_RIGHT:
848             pdl_config->chanSelect = CY_PDM_PCM_OUT_CHAN_RIGHT;
849             break;
850         case CYHAL_PDM_PCM_MODE_STEREO:
851             pdl_config->chanSelect = CY_PDM_PCM_OUT_STEREO;
852             break;
853         default:
854             return CYHAL_PDM_PCM_RSLT_ERR_INVALID_CONFIG_PARAM;
855     }
856 
857     switch(cfg->word_length)
858     {
859         case 16:
860             pdl_config->wordLen = CY_PDM_PCM_WLEN_16_BIT;
861             break;
862         case 18:
863             pdl_config->wordLen = CY_PDM_PCM_WLEN_18_BIT;
864             break;
865         case 20:
866             pdl_config->wordLen = CY_PDM_PCM_WLEN_20_BIT;
867             break;
868         case 24:
869             pdl_config->wordLen = CY_PDM_PCM_WLEN_24_BIT;
870             break;
871         default:
872             return CYHAL_PDM_PCM_RSLT_ERR_INVALID_CONFIG_PARAM;
873     }
874 
875     if (_cyhal_pdm_pcm_invalid_gain_range(cfg->left_gain) || _cyhal_pdm_pcm_invalid_gain_range(cfg->right_gain))
876     {
877         return CYHAL_PDM_PCM_RSLT_ERR_INVALID_CONFIG_PARAM;
878     }
879     pdl_config->gainLeft = _cyhal_pdm_pcm_scale_gain_value(cfg->left_gain);
880     pdl_config->gainRight = _cyhal_pdm_pcm_scale_gain_value(cfg->right_gain);
881 
882     return CY_RSLT_SUCCESS;
883 }
884 
885 #elif defined(CY_IP_MXPDM)
886 
_cyhal_pdm_pcm_set_pdl_config_struct(cyhal_pdm_pcm_t * obj,const cyhal_pdm_pcm_cfg_t * cfg,bool found_existing,cy_stc_pdm_pcm_config_v2_t * pdl_config,cy_stc_pdm_pcm_channel_config_t * pdl_chan_config,cy_stc_pdm_pcm_channel_config_t * pdl_chan_config_paired)887 static inline cy_rslt_t _cyhal_pdm_pcm_set_pdl_config_struct(cyhal_pdm_pcm_t* obj, const cyhal_pdm_pcm_cfg_t *cfg,
888                                                              bool found_existing,
889                                                              cy_stc_pdm_pcm_config_v2_t  *pdl_config,
890                                                              cy_stc_pdm_pcm_channel_config_t* pdl_chan_config,
891                                                              cy_stc_pdm_pcm_channel_config_t* pdl_chan_config_paired)
892 {
893     const uint8_t MIN_CLK_DIV = 4u; /* PDM.CLOCK_CTL.CLOCK_DIV defines 0-2 (corresponding to divide by 1-3) as illegal */
894     const uint16_t MAX_CLK_DIV = 256;
895     const uint16_t CLK_STEP = 2u; /* Use even divide divides to ensure a 50/50 duty cycle */
896     const cyhal_clock_tolerance_t CLK_TOLERANCE = { .type = CYHAL_TOLERANCE_PERCENT, .value = 1 };
897 
898     cy_rslt_t result = CY_RSLT_SUCCESS;
899     if (cfg->sample_rate > CYHAL_PDM_PCM_MAX_SAMPLE_RATE)
900     {
901         result = CYHAL_PDM_PCM_RSLT_ERR_INVALID_CONFIG_PARAM;
902     }
903     int fir1_scale = _cyhal_pdm_pcm_gain_to_scale(cfg->left_gain);
904     if(CY_RSLT_SUCCESS == result && (fir1_scale < 0 || fir1_scale > _CYHAL_PDM_PCM_MAX_FIR1_SCALE))
905     {
906         result = CYHAL_PDM_PCM_RSLT_ERR_INVALID_CONFIG_PARAM;
907     }
908 
909     uint8_t clkDiv = 0u; /* Per hw definition, this should be 1 less than the actual desired divide */
910     if(CY_RSLT_SUCCESS == result)
911     {
912         uint32_t target_freq = cfg->sample_rate * cfg->decimation_rate;
913 
914         if(obj->is_clock_owned && false == found_existing)
915         {
916             /* For power consumption purposes we want the input clock divided down as far as it will
917              * go. So start out at our smallest supported divider and increase until we find something that
918              * will get us within our tolerance. */
919              for(uint16_t i = MIN_CLK_DIV; i <= MAX_CLK_DIV; i += CLK_STEP)
920              {
921                  uint32_t desired_source_freq = target_freq * i;
922                  cy_rslt_t freq_result = _cyhal_utils_set_clock_frequency(&(obj->clock), desired_source_freq, &CLK_TOLERANCE);
923                  if(CY_RSLT_SUCCESS == freq_result)
924                  {
925                      clkDiv = i - 1;
926                      break;
927                  }
928              }
929         }
930         else // User-provided clock or clock already initialized by another channel, all we can do is adjust/check our internal divider
931         {
932             uint32_t actual_source_freq = cyhal_clock_get_frequency(&obj->clock);
933             uint32_t best_divider = (actual_source_freq + (target_freq / 2)) / target_freq; // Round to nearest divider
934             uint32_t desired_source_freq = target_freq * best_divider;
935             uint32_t diff = (uint32_t)abs(_cyhal_utils_calculate_tolerance(CLK_TOLERANCE.type, desired_source_freq, actual_source_freq));
936             if(diff <= CLK_TOLERANCE.value && best_divider <= MAX_CLK_DIV)
937             {
938                 clkDiv = (uint8_t)(best_divider - 1);
939             }
940         }
941 
942         if(0u == clkDiv)
943         {
944             result = CYHAL_PDM_PCM_RSLT_ERR_INVALID_CONFIG_PARAM;
945         }
946 
947         if(found_existing) /* Check that the required clock divider is the same as what is already there */
948         {
949             uint8_t existingDiv = _FLD2VAL(PDM_CLOCK_CTL_CLOCK_DIV, PDM_PCM_CLOCK_CTL(obj->base));
950             if(existingDiv != clkDiv)
951             {
952                 result = CYHAL_PDM_PCM_RSLT_ERR_INVALID_CONFIG_PARAM;
953             }
954         }
955     }
956 
957     if(CY_RSLT_SUCCESS == result)
958     {
959         pdl_config->clkDiv = clkDiv;
960         /* Left samples on rising edge, right samples on falling edge. Per PDL documentation, rising edge corresponds to
961          * a sampledelay of clockDiv, and falling edge corresponds to sampledelay of clockDiv / 2.
962          * When we're in stereo, we sample left on the "primary" channel and right on the paired one */
963         pdl_chan_config->sampledelay = (CYHAL_PDM_PCM_MODE_RIGHT == cfg->mode) ? clkDiv / 2 : clkDiv;
964         pdl_chan_config->fir1_scale = fir1_scale;
965         result = _cyhal_pdm_pcm_word_length_to_pdl(cfg->word_length, &pdl_chan_config->wordSize);
966     }
967 
968     if(CY_RSLT_SUCCESS == result)
969     {
970         result = _cyhal_pdm_pcm_convert_decim_rate(cfg->decimation_rate, &pdl_chan_config->fir1_decim_code);
971     }
972 
973     if(CY_RSLT_SUCCESS == result && NULL != pdl_chan_config_paired)
974     {
975         int fir1_scale_paired = _cyhal_pdm_pcm_gain_to_scale(cfg->right_gain);
976         if(CY_RSLT_SUCCESS == result && (fir1_scale_paired < 0 || fir1_scale_paired > _CYHAL_PDM_PCM_MAX_FIR1_SCALE))
977         {
978             result = CYHAL_PDM_PCM_RSLT_ERR_INVALID_CONFIG_PARAM;
979         }
980 
981         if(CY_RSLT_SUCCESS == result)
982         {
983             *pdl_chan_config_paired = *pdl_chan_config;
984             pdl_chan_config_paired->sampledelay = clkDiv / 2;
985             pdl_chan_config_paired->fir1_scale = fir1_scale_paired;
986         }
987     }
988 
989     return result;
990 }
991 
992 #endif
993 
994 // - Update clocking confguration
995 // - Handle word length
996 
_cyhal_pdm_pcm_dma_callback(void * callback_arg,cyhal_dma_event_t event)997 static void _cyhal_pdm_pcm_dma_callback(void *callback_arg, cyhal_dma_event_t event)
998 {
999     CY_UNUSED_PARAMETER(event);
1000     cyhal_pdm_pcm_t *obj = (cyhal_pdm_pcm_t *)callback_arg;
1001     if (obj != NULL)
1002     {
1003 #if defined(CY_IP_MXPDM)
1004         bool has_pair = CYHAL_RSC_INVALID != obj->dma_paired.resource.type;
1005         if(has_pair && ((cyhal_dma_is_busy(&obj->dma) || cyhal_dma_is_busy(&obj->dma_paired))))
1006         {
1007             return; /* This callback was for the first DMA. Wait for the second one */
1008         }
1009 #endif
1010         // DMA finished trigger callback
1011         cyhal_pdm_pcm_event_callback_t callback = (cyhal_pdm_pcm_event_callback_t) obj->callback_data.callback;
1012         if (callback != NULL)
1013         {
1014             callback(obj->callback_data.callback_arg, CYHAL_PDM_PCM_ASYNC_COMPLETE);
1015         }
1016     }
1017 }
1018 
1019 
_cyhal_pdm_pcm_init_clock(cyhal_pdm_pcm_t * obj,const cyhal_clock_t * clk_source,cyhal_pdm_pcm_t * existing_obj)1020 static cy_rslt_t _cyhal_pdm_pcm_init_clock(cyhal_pdm_pcm_t *obj, const cyhal_clock_t* clk_source, cyhal_pdm_pcm_t* existing_obj)
1021 {
1022     cy_rslt_t result = CY_RSLT_SUCCESS;
1023     if(NULL != existing_obj)
1024     {
1025         /* Either both use auto-allocated clock source or they both use the same manually specified source */
1026         if((true == existing_obj->is_clock_owned) != (clk_source == NULL))
1027         {
1028             result = CYHAL_PDM_PCM_RSLT_ERR_INVALID_CONFIG_PARAM;
1029         }
1030         else
1031         {
1032             /* Copy over the existing clock settings */
1033             obj->clock = existing_obj->clock;
1034             obj->is_clock_owned = existing_obj->is_clock_owned;
1035             if(false == existing_obj->is_clock_owned)
1036             {
1037                 /* Custom clocks must be the same */
1038                 if((clk_source->block != existing_obj->clock.block) ||
1039                    (clk_source->channel != existing_obj->clock.channel))
1040                {
1041                     result = CYHAL_PDM_PCM_RSLT_ERR_INVALID_CONFIG_PARAM;
1042                }
1043             }
1044         }
1045     }
1046     else if (clk_source != NULL)
1047     {
1048         obj->clock = *clk_source;
1049     }
1050     else
1051     {
1052         // On CAT1A, we're hard-wired to an HFCLK so the divider type doesn't apply. On all current CAT1B hardware,
1053         // we happen to be wired to a PERI group with a 16.5 bit divider, but we're not terribly picky so just ask
1054         // for a 16-bit divider.
1055         result = _cyhal_utils_allocate_clock(&(obj->clock), &(obj->resource), CYHAL_CLOCK_BLOCK_PERIPHERAL_16BIT, true);
1056         if(CY_RSLT_SUCCESS == result)
1057         {
1058             obj->is_clock_owned = true;
1059             result = cyhal_clock_set_enabled(&(obj->clock), true, true);
1060         }
1061     }
1062 
1063 #if defined(_CYHAL_PDM_USES_PCLK)
1064     // If we're using a peri divider, hook it up to ourself
1065     if (CY_RSLT_SUCCESS == result && obj->clock.block != CYHAL_CLOCK_BLOCK_HF && false == obj->owned_by_configurator)
1066     {
1067         en_clk_dst_t pclk = _cyhal_pdm_clock[obj->resource.block_num];
1068         result = _cyhal_utils_peri_pclk_assign_divider(pclk, &(obj->clock));
1069     }
1070 #endif
1071 
1072     return result;
1073 }
1074 
_cyhal_pdm_pcm_arrays_init(void)1075 static void _cyhal_pdm_pcm_arrays_init( void )
1076 {
1077     if (!_cyhal_pdm_pcm_arrays_initialized)
1078     {
1079 #if (_CYHAL_PDM_INST0)
1080         for (uint8_t i = 0; i < _CYHAL_PDM_CHANNELS0; i++)
1081         {
1082             _cyhal_pdm_pcm_config_structs_inst0[i] = NULL;
1083         }
1084 #endif
1085 #if (_CYHAL_PDM_INST1)
1086         for (uint8_t i = 0; i < _CYHAL_PDM_CHANNELS1; i++)
1087         {
1088             _cyhal_pdm_pcm_config_structs_inst1[i] = NULL;
1089         }
1090 #endif
1091         _cyhal_pdm_pcm_arrays_initialized = true;
1092     }
1093 
1094 }
1095 
_cyhal_pdm_pcm_init_hw(cyhal_pdm_pcm_t * obj,int paired_channel,cyhal_pdm_pcm_t * existing_obj,const cy_stc_pdm_pcm_config_t * pdl_struct)1096 static cy_rslt_t _cyhal_pdm_pcm_init_hw(cyhal_pdm_pcm_t *obj, int paired_channel, cyhal_pdm_pcm_t* existing_obj,
1097 #if defined(CY_IP_MXAUDIOSS_INSTANCES)
1098                                         const cy_stc_pdm_pcm_config_t* pdl_struct)
1099 #elif defined(CY_IP_MXTDM_INSTANCES)
1100                                         const cy_stc_pdm_pcm_config_v2_t* pdl_struct,
1101                                         const cy_stc_pdm_pcm_channel_config_t* pdl_chan_struct,
1102                                         const cy_stc_pdm_pcm_channel_config_t* pdl_chan_struct_paired)
1103 #endif
1104 {
1105     cy_rslt_t result = CY_RSLT_SUCCESS;
1106     obj->base = _cyhal_pdm_pcm_base[obj->resource.block_num];
1107     if(NULL == existing_obj)
1108     {
1109         result = (cy_rslt_t)Cy_PDM_PCM_Init(obj->base, pdl_struct);
1110     }
1111 
1112 #if defined(CY_IP_MXPDM)
1113     if (CY_RSLT_SUCCESS == result)
1114     {
1115         result = (cy_rslt_t)Cy_PDM_PCM_Channel_Init(obj->base, pdl_chan_struct, obj->resource.channel_num);
1116     }
1117     if(CY_RSLT_SUCCESS == result && paired_channel >= 0)
1118     {
1119         result = (cy_rslt_t)Cy_PDM_PCM_Channel_Init(obj->base, pdl_chan_struct_paired, paired_channel);
1120     }
1121     if (CY_RSLT_SUCCESS == result)
1122     {
1123         Cy_PDM_PCM_Channel_Enable(obj->base, obj->resource.channel_num);
1124         if(paired_channel >= 0)
1125         {
1126             Cy_PDM_PCM_Channel_Enable(obj->base, paired_channel);
1127         }
1128     }
1129 #elif defined(CY_IP_MXAUDIOSS)
1130     CY_UNUSED_PARAMETER(paired_channel);
1131 #endif
1132 
1133     if (CY_RSLT_SUCCESS == result)
1134     {
1135         _cyhal_pdm_pcm_config_structs[obj->resource.block_num][obj->resource.channel_num] = obj;
1136 #if defined(CY_IP_MXAUDIOSS)
1137         uint8_t word_length = _cyhal_pdm_pcm_word_length_from_pdl(pdl_struct->wordLen);
1138 #elif defined(CY_IP_MXPDM)
1139         if(paired_channel >= 0)
1140         {
1141             _cyhal_pdm_pcm_config_structs[obj->resource.block_num][paired_channel] = obj;
1142         }
1143         uint8_t word_length = _cyhal_pdm_pcm_word_length_from_pdl(pdl_chan_struct->wordSize);
1144 #endif
1145         if(word_length <= 8)
1146         {
1147             obj->word_size = 1;
1148         }
1149         else if(word_length <= 16)
1150         {
1151             obj->word_size = 2;
1152         }
1153         else
1154         {
1155             obj->word_size = 4;
1156         }
1157         obj->callback_data.callback = NULL;
1158         obj->callback_data.callback_arg = NULL;
1159 #if defined(CY_IP_MXAUDIOSS)
1160         /* No irq mask in the config struct for MXTDM */
1161         obj->irq_cause = (uint32_t)_cyhal_pdm_pcm_get_hal_event(pdl_struct->interruptMask);
1162         obj->stabilized = false;
1163 #elif defined(CY_IP_MXTDM)
1164         obj->stabilized = true;
1165         _cyhal_pdm_pcm_set_rx_fifo_level(obj, obj->user_trigger_level);
1166 #endif
1167         obj->pm_transition_ready = false;
1168 
1169         obj->pm_callback.callback = &_cyhal_pdm_pcm_pm_callback;
1170         obj->pm_callback.states = (cyhal_syspm_callback_state_t)(CYHAL_SYSPM_CB_CPU_DEEPSLEEP | CYHAL_SYSPM_CB_CPU_DEEPSLEEP_RAM | CYHAL_SYSPM_CB_SYSTEM_HIBERNATE);
1171         obj->pm_callback.next = NULL;
1172         obj->pm_callback.args = (void*)obj;
1173         obj->pm_callback.ignore_modes = (cyhal_syspm_callback_mode_t)(CYHAL_SYSPM_BEFORE_TRANSITION | CYHAL_SYSPM_AFTER_DS_WFI_TRANSITION);
1174 
1175         _cyhal_syspm_register_peripheral_callback(&(obj->pm_callback));
1176 
1177         /* If we're using two channels in a stereo pair, both will generate interrupts in sync with each other, so
1178          * only listen on the primary channel (i.e. the one corresponding to the pins that were passed in) */
1179         _cyhal_system_irq_t irqn = _cyhal_pdm_pcm_irq_n[obj->resource.block_num][obj->resource.channel_num];
1180         _cyhal_irq_register(irqn, CYHAL_ISR_PRIORITY_DEFAULT, _cyhal_pdm_pcm_hw_irq_handler);
1181         _cyhal_irq_enable(irqn);
1182         cyhal_pdm_pcm_clear(obj);
1183     }
1184 
1185     return result;
1186 }
1187 
cyhal_pdm_pcm_init(cyhal_pdm_pcm_t * obj,cyhal_gpio_t pin_data,cyhal_gpio_t pin_clk,const cyhal_clock_t * clk_source,const cyhal_pdm_pcm_cfg_t * cfg)1188 cy_rslt_t cyhal_pdm_pcm_init(cyhal_pdm_pcm_t *obj, cyhal_gpio_t pin_data, cyhal_gpio_t pin_clk,
1189                 const cyhal_clock_t *clk_source, const cyhal_pdm_pcm_cfg_t *cfg)
1190 {
1191     CY_ASSERT(NULL != obj);
1192     memset(obj, 0, sizeof(cyhal_pdm_pcm_t));
1193     cy_rslt_t result = CY_RSLT_SUCCESS;
1194 
1195     _cyhal_pdm_pcm_arrays_init();
1196 
1197     /* Explicitly marked not allocated resources as invalid to prevent freeing them. */
1198     obj->resource.type = CYHAL_RSC_INVALID;
1199     obj->pin_data = CYHAL_NC_PIN_VALUE;
1200     obj->pin_clk = CYHAL_NC_PIN_VALUE;
1201     obj->dma.resource.type = CYHAL_RSC_INVALID;
1202 #if defined(CY_IP_MXPDM)
1203     obj->dma_paired.resource.type = CYHAL_RSC_INVALID;
1204 #endif
1205 
1206     obj->user_trigger_level = _CYHAL_PDM_PCM_HALF_FIFO;
1207 
1208     /* Reserve the PDM-PCM */
1209 #if defined(CY_IP_MXAUDIOSS)
1210     const cyhal_resource_pin_mapping_t *data_map = _CYHAL_UTILS_GET_RESOURCE(pin_data, cyhal_pin_map_audioss_pdm_data);
1211     const cyhal_resource_pin_mapping_t *clk_map = _CYHAL_UTILS_GET_RESOURCE(pin_clk, cyhal_pin_map_audioss_pdm_clk);
1212     uint8_t data_dm = CYHAL_PIN_MAP_DRIVE_MODE_AUDIOSS_PDM_DATA;
1213     uint8_t clk_dm = CYHAL_PIN_MAP_DRIVE_MODE_AUDIOSS_PDM_CLK;
1214 #elif defined(CY_IP_MXTDM)
1215     const cyhal_resource_pin_mapping_t *data_map = _CYHAL_UTILS_GET_RESOURCE(pin_data, cyhal_pin_map_pdm_pdm_data);
1216     const cyhal_resource_pin_mapping_t *clk_map = _CYHAL_UTILS_GET_RESOURCE(pin_clk, cyhal_pin_map_pdm_pdm_clk);
1217     uint8_t data_dm = CYHAL_PIN_MAP_DRIVE_MODE_PDM_PDM_DATA;
1218     uint8_t clk_dm = CYHAL_PIN_MAP_DRIVE_MODE_PDM_PDM_CLK;
1219 #endif
1220 
1221     if ((NULL == data_map) || (NULL == clk_map) || !_cyhal_utils_map_resources_equal(data_map, clk_map))
1222     {
1223         result = CYHAL_PDM_PCM_RSLT_ERR_INVALID_PIN;
1224     }
1225 
1226     cyhal_pdm_pcm_t* existing_obj = NULL;
1227     int paired_channel = _CYHAL_PDM_PCM_UNPAIRED;
1228     if(CY_RSLT_SUCCESS == result)
1229     {
1230         _CYHAL_UTILS_ASSIGN_RESOURCE(obj->resource, CYHAL_RSC_PDM, data_map);
1231         obj->base = _cyhal_pdm_pcm_base[obj->resource.block_num];
1232         if(CYHAL_PDM_PCM_MODE_STEREO == cfg->mode)
1233         {
1234             /* Need to also reserve the adjacent receiver */
1235             paired_channel = _cyhal_pdm_pcm_get_paired_channel(obj, false);
1236         }
1237 
1238         if((NULL != _cyhal_pdm_pcm_config_structs[obj->resource.block_num][obj->resource.channel_num]) ||
1239            (paired_channel >= 0 && (NULL != _cyhal_pdm_pcm_config_structs[obj->resource.block_num][paired_channel])))
1240         {
1241             /* This channel is already consumed */
1242             result = CYHAL_HWMGR_RSLT_ERR_INUSE;
1243         }
1244         else
1245         {
1246             existing_obj = _cyhal_pdm_pcm_find_existing_obj(obj->resource.block_num);
1247             if(NULL == existing_obj)
1248             {
1249                 result = cyhal_hwmgr_reserve(&(obj->resource));
1250             }
1251         }
1252     }
1253 
1254     /* Reserve the pdm in pin */
1255     if (CY_RSLT_SUCCESS == result)
1256     {
1257         result = _cyhal_utils_reserve_and_connect(data_map, data_dm);
1258         if (CY_RSLT_SUCCESS == result)
1259             obj->pin_data = pin_data;
1260     }
1261 
1262     /* Reserve the clk pin */
1263     if (CY_RSLT_SUCCESS == result)
1264     {
1265         result = _cyhal_utils_reserve_and_connect(clk_map, clk_dm);
1266         if (CY_RSLT_SUCCESS == result)
1267             obj->pin_clk = pin_clk;
1268     }
1269 
1270     /* Clock init needs to happen before we perform the PDL config so that we know our source frequency */
1271     if (CY_RSLT_SUCCESS == result)
1272     {
1273         result = _cyhal_pdm_pcm_init_clock(obj, clk_source, existing_obj);
1274     }
1275 
1276     if (CY_RSLT_SUCCESS == result)
1277     {
1278 #if defined(CY_IP_MXAUDIOSS)
1279         cy_stc_pdm_pcm_config_t pdl_struct = _cyhal_pdm_pcm_default_config;
1280         result = _cyhal_pdm_pcm_set_pdl_config_struct(obj, cfg, &pdl_struct);
1281 #elif defined(CY_IP_MXPDM)
1282         cy_stc_pdm_pcm_config_v2_t pdl_struct = _cyhal_pdm_pcm_default_config;
1283         cy_stc_pdm_pcm_channel_config_t pdl_chan_struct = _cyhal_pdm_pcm_default_channel_config;
1284         cy_stc_pdm_pcm_channel_config_t pdl_chan_struct_paired = _cyhal_pdm_pcm_default_channel_config;
1285         result = _cyhal_pdm_pcm_set_pdl_config_struct(obj, cfg, (NULL != existing_obj), &pdl_struct,
1286                     &pdl_chan_struct, paired_channel >= 0 ? &pdl_chan_struct_paired : NULL);
1287 #endif
1288         if (CY_RSLT_SUCCESS == result)
1289         {
1290 #if defined(CY_IP_MXAUDIOSS)
1291             result = _cyhal_pdm_pcm_init_hw(obj, paired_channel, existing_obj, &pdl_struct);
1292 #elif defined(CY_IP_MXTDM)
1293             result = _cyhal_pdm_pcm_init_hw(obj, paired_channel, existing_obj, &pdl_struct,
1294                         &pdl_chan_struct, &pdl_chan_struct_paired);
1295 
1296         if(NULL != existing_obj && paired_channel >= 0)
1297         {
1298             /* Update the route register to point the paired channel to the primary channel's pins */
1299 
1300             PDM_PCM_ROUTE_CTL(obj->base) |= (1 << paired_channel);
1301         }
1302 #endif
1303         }
1304     }
1305 
1306     if (CY_RSLT_SUCCESS != result)
1307     {
1308         cyhal_pdm_pcm_free(obj);
1309     }
1310     return result;
1311 }
1312 
cyhal_pdm_pcm_init_cfg(cyhal_pdm_pcm_t * obj,const cyhal_pdm_pcm_configurator_t * cfg)1313 cy_rslt_t cyhal_pdm_pcm_init_cfg(cyhal_pdm_pcm_t *obj, const cyhal_pdm_pcm_configurator_t* cfg)
1314 {
1315     CY_ASSERT(NULL != obj);
1316     memset(obj, 0, sizeof(cyhal_pdm_pcm_t));
1317     obj->resource = *cfg->resource;
1318     obj->pin_data = CYHAL_NC_PIN_VALUE;
1319     obj->pin_clk = CYHAL_NC_PIN_VALUE;
1320     obj->dma.resource.type = CYHAL_RSC_INVALID;
1321 #if defined(CY_IP_MXPDM)
1322     obj->dma_paired.resource.type = CYHAL_RSC_INVALID;
1323 #endif
1324     obj->owned_by_configurator = true;
1325 
1326     /* Trigger fires when the FIFO has one more entry than the configured level */
1327 #if defined(CY_IP_MXAUDIOSS)
1328     obj->user_trigger_level = cfg->config->rxFifoTriggerLevel + 1;
1329 #elif defined(CY_IP_MXTDM)
1330     obj->user_trigger_level = cfg->chan_config->rxFifoTriggerLevel + 1;
1331 #endif
1332 
1333     _cyhal_pdm_pcm_arrays_init();
1334 
1335     cyhal_pdm_pcm_t* existing_obj = _cyhal_pdm_pcm_find_existing_obj(obj->resource.block_num);
1336     cy_rslt_t result = _cyhal_pdm_pcm_init_clock(obj, cfg->clock, existing_obj);
1337 
1338     if(CY_RSLT_SUCCESS == result)
1339     {
1340         /* When initializing from configurator, separate channels are always configured independently */
1341 #if defined(CY_IP_MXAUDIOSS)
1342         result = _cyhal_pdm_pcm_init_hw(obj, _CYHAL_PDM_PCM_UNPAIRED, existing_obj, cfg->config);
1343 #elif defined(CY_IP_MXTDM)
1344         result = _cyhal_pdm_pcm_init_hw(obj, _CYHAL_PDM_PCM_UNPAIRED, existing_obj, cfg->config, cfg->chan_config, NULL);
1345 #endif
1346     }
1347 
1348     if(CY_RSLT_SUCCESS != result)
1349     {
1350         cyhal_pdm_pcm_free(obj);
1351     }
1352 
1353     return result;
1354 }
1355 
cyhal_pdm_pcm_free(cyhal_pdm_pcm_t * obj)1356 void cyhal_pdm_pcm_free(cyhal_pdm_pcm_t *obj)
1357 {
1358     CY_ASSERT(NULL != obj);
1359     CY_ASSERT(_cyhal_pdm_pcm_arrays_initialized); /* Should not be freeing if we never initialized anything */
1360 
1361     if (CYHAL_RSC_INVALID != obj->resource.type)
1362     {
1363 #if defined(CY_IP_MXPDM)
1364         int16_t paired_channel = _cyhal_pdm_pcm_get_paired_channel(obj, true);
1365 #endif
1366         bool last_remaining = true;
1367         for(int i = 0; last_remaining && i < _cyhal_pdm_num_channels[obj->resource.block_num]; ++i)
1368         {
1369             cyhal_pdm_pcm_t* existing = _cyhal_pdm_pcm_config_structs[obj->resource.block_num][i];
1370             last_remaining &= (NULL == existing || obj == existing);
1371         }
1372         _cyhal_syspm_unregister_peripheral_callback(&(obj->pm_callback));
1373         if(NULL != obj->base)
1374         {
1375 #if defined(CY_IP_MXPDM)
1376             Cy_PDM_PCM_Channel_Disable(obj->base, obj->resource.channel_num);
1377             Cy_PDM_PCM_Channel_DeInit(obj->base, obj->resource.channel_num);
1378             if(paired_channel >= 0)
1379             {
1380                 Cy_PDM_PCM_Channel_Disable(obj->base, paired_channel);
1381                 Cy_PDM_PCM_Channel_DeInit(obj->base, paired_channel);
1382 
1383                 /* Update the route register to point the paired channel to the primary channel's pins. */
1384                 PDM_PCM_ROUTE_CTL(obj->base) &= ~(1 << paired_channel);
1385             }
1386 #endif
1387             if(last_remaining)
1388             {
1389                 Cy_PDM_PCM_DeInit(obj->base);
1390             }
1391 
1392             _cyhal_irq_free(_cyhal_pdm_pcm_irq_n[obj->resource.block_num][obj->resource.channel_num]);
1393         }
1394 
1395         if(obj == _cyhal_pdm_pcm_config_structs[obj->resource.block_num][obj->resource.channel_num])
1396         {
1397             _cyhal_pdm_pcm_config_structs[obj->resource.block_num][obj->resource.channel_num] = NULL;
1398         }
1399 #if defined(CY_IP_MXPDM)
1400         if(paired_channel >= 0)
1401         {
1402             /* Clear out the paired channel if stereo */
1403             _cyhal_pdm_pcm_config_structs[obj->resource.block_num][paired_channel] = NULL;
1404         }
1405 #endif
1406 
1407         if(last_remaining && false == obj->owned_by_configurator)
1408         {
1409             cyhal_hwmgr_free(&(obj->resource));
1410         }
1411         obj->base = NULL;
1412         obj->resource.type = CYHAL_RSC_INVALID;
1413 
1414         if(false == obj->owned_by_configurator)
1415         {
1416             _cyhal_utils_release_if_used(&(obj->pin_data));
1417             _cyhal_utils_release_if_used(&(obj->pin_clk));
1418         }
1419 
1420         if(last_remaining && obj->is_clock_owned)
1421         {
1422             cyhal_clock_free(&(obj->clock));
1423         }
1424 
1425         if (CYHAL_RSC_INVALID != obj->dma.resource.type)
1426         {
1427             cyhal_dma_free(&(obj->dma));
1428         }
1429 
1430 #if defined(CY_IP_MXPDM)
1431         if (CYHAL_RSC_INVALID != obj->dma_paired.resource.type)
1432         {
1433             cyhal_dma_free(&(obj->dma_paired));
1434         }
1435 #endif
1436     }
1437 }
1438 
cyhal_pdm_pcm_start(cyhal_pdm_pcm_t * obj)1439 cy_rslt_t cyhal_pdm_pcm_start(cyhal_pdm_pcm_t *obj)
1440 {
1441     CY_ASSERT(NULL != obj);
1442     if (obj->pm_transition_ready)
1443     {
1444         return CYHAL_SYSPM_RSLT_ERR_PM_PENDING;
1445     }
1446 #if defined(CY_IP_MXAUDIOSS) /* MXPDM clears the FIFO whenever it is disabled */
1447     obj->stabilized = false;
1448     // Remove any element currently in the FIFO. This ensure the correct stabilization time delay.
1449     Cy_PDM_PCM_ClearFifo(obj->base);
1450     Cy_PDM_PCM_Enable(obj->base);
1451     // After Enable is asserted, there is a transition period of about 35-45 sample cycles on MXAUDIOSS
1452     _cyhal_pdm_pcm_set_rx_fifo_level(obj, _CYHAL_PDM_PCM_STABILIZATION_FS);
1453     _cyhal_pdm_pcm_set_interrupt_mask(obj, _cyhal_pdm_pcm_get_interrupt_mask(obj) | CY_PDM_PCM_INTR_RX_TRIGGER);
1454 #elif defined(CY_IP_MXPDM)
1455     /* Enable/disable is more aggressive than we want, e.g. it will clear the FIFOs. Just activate/deactivate */
1456     Cy_PDM_PCM_Activate_Channel(obj->base, obj->resource.channel_num);
1457     int16_t paired_channel = _cyhal_pdm_pcm_get_paired_channel(obj, true);
1458     if(paired_channel >= 0)
1459     {
1460         Cy_PDM_PCM_Activate_Channel(obj->base, paired_channel);
1461     }
1462 #endif
1463     return CY_RSLT_SUCCESS;
1464 }
1465 
cyhal_pdm_pcm_stop(cyhal_pdm_pcm_t * obj)1466 cy_rslt_t cyhal_pdm_pcm_stop(cyhal_pdm_pcm_t *obj)
1467 {
1468     CY_ASSERT(NULL != obj);
1469 #if defined(CY_IP_MXAUDIOSS)
1470     Cy_PDM_PCM_Disable(obj->base);
1471 #elif defined(CY_IP_MXPDM)
1472     /* Enable/disable is more aggressive than we want, e.g. it will clear the FIFOs. Just activate/deactivate */
1473     Cy_PDM_PCM_DeActivate_Channel(obj->base, obj->resource.channel_num);
1474     int16_t paired_channel = _cyhal_pdm_pcm_get_paired_channel(obj, true);
1475     if(paired_channel >= 0)
1476     {
1477         Cy_PDM_PCM_DeActivate_Channel(obj->base, paired_channel);
1478     }
1479 #endif
1480     return CY_RSLT_SUCCESS;
1481 }
1482 
cyhal_pdm_pcm_is_enabled(cyhal_pdm_pcm_t * obj)1483 bool cyhal_pdm_pcm_is_enabled(cyhal_pdm_pcm_t *obj)
1484 {
1485     CY_ASSERT(NULL != obj);
1486 #if defined(CY_IP_MXAUDIOSS)
1487     return (0 != Cy_PDM_PCM_GetCurrentState(obj->base));
1488 #elif defined(CY_IP_MXPDM)
1489     return (0 != Cy_PDM_PCM_Channel_GetCurrentState(obj->base, obj->resource.channel_num));
1490 #endif
1491 }
1492 
cyhal_pdm_pcm_set_gain(cyhal_pdm_pcm_t * obj,int16_t gain_left,int16_t gain_right)1493 cy_rslt_t cyhal_pdm_pcm_set_gain(cyhal_pdm_pcm_t *obj, int16_t gain_left, int16_t gain_right)
1494 {
1495     CY_ASSERT(NULL != obj);
1496     cy_rslt_t result = CY_RSLT_SUCCESS;
1497 
1498 #if defined(CY_IP_MXAUDIOSS)
1499     if (_cyhal_pdm_pcm_invalid_gain_range(gain_left) || _cyhal_pdm_pcm_invalid_gain_range(gain_right))
1500     {
1501         result = CYHAL_PDM_PCM_RSLT_ERR_INVALID_CONFIG_PARAM;
1502     }
1503     Cy_PDM_PCM_SetGain(obj->base, CY_PDM_PCM_CHAN_LEFT, _cyhal_pdm_pcm_scale_gain_value(gain_left));
1504     Cy_PDM_PCM_SetGain(obj->base, CY_PDM_PCM_CHAN_RIGHT, _cyhal_pdm_pcm_scale_gain_value(gain_right));
1505 #elif defined(CY_IP_MXPDM)
1506     int16_t paired_channel = _cyhal_pdm_pcm_get_paired_channel(obj, true);
1507 
1508     int fir1_scale_left = _cyhal_pdm_pcm_gain_to_scale(gain_left);
1509     if(CY_RSLT_SUCCESS == result && (fir1_scale_left < 0 || fir1_scale_left > _CYHAL_PDM_PCM_MAX_FIR1_SCALE))
1510     {
1511         result = CYHAL_PDM_PCM_RSLT_ERR_INVALID_CONFIG_PARAM;
1512     }
1513 
1514     int fir1_scale_right = _cyhal_pdm_pcm_gain_to_scale(gain_right);
1515     if(CY_RSLT_SUCCESS == result && (fir1_scale_right < 0 || fir1_scale_right > _CYHAL_PDM_PCM_MAX_FIR1_SCALE))
1516     {
1517         result = CYHAL_PDM_PCM_RSLT_ERR_INVALID_CONFIG_PARAM;
1518     }
1519 
1520     bool mono_and_right = false;
1521     if(paired_channel < 0) /* Unpaired, figure out if we were set up for left or right. */
1522     {
1523         /* Left = rising edge = (sampleDelay == clockDiv). Right = falling edge = (sampleDelay == clockDiv / 2) */
1524         uint8_t clkDiv = _FLD2VAL(PDM_CLOCK_CTL_CLOCK_DIV, PDM_PCM_CLOCK_CTL(obj->base));
1525         uint8_t sampleDelay = _FLD2VAL(PDM_CH_IF_CTL_SAMPLE_DELAY, PDM_PCM_CH_IF_CTL(obj->base, obj->resource.channel_num));
1526         mono_and_right = (sampleDelay != clkDiv);
1527     }
1528 
1529     if(CY_RSLT_SUCCESS == result)
1530     {
1531         /* The primary channel index is left, and the paired one is right */
1532         cy_en_pdm_pcm_ch_fir1_decimcode_t decim_code = (cy_en_pdm_pcm_ch_fir1_decimcode_t)
1533                                                         _FLD2VAL(PDM_CH_FIR1_CTL_DECIM2, PDM_PCM_CH_FIR1_CTL(obj->base, 0u));
1534         Cy_PDM_PCM_Channel_Set_Fir1(obj->base, obj->resource.channel_num, decim_code, mono_and_right ? fir1_scale_right : fir1_scale_left);
1535 
1536         if(paired_channel >= 0)
1537         {
1538             /* Decim code is the same between left and right because we set them up from the same user selection */
1539             Cy_PDM_PCM_Channel_Set_Fir1(obj->base, paired_channel, decim_code, fir1_scale_right);
1540         }
1541 
1542     }
1543 #endif
1544     return result;
1545 }
1546 
cyhal_pdm_pcm_clear(cyhal_pdm_pcm_t * obj)1547 cy_rslt_t cyhal_pdm_pcm_clear(cyhal_pdm_pcm_t *obj)
1548 {
1549     CY_ASSERT(NULL != obj);
1550 #if defined(CY_IP_MXAUDIOSS)
1551     // Remove any element currently in the FIFO. This ensure the correct stabilization time delay.
1552     Cy_PDM_PCM_ClearFifo(obj->base);
1553 #elif defined(CY_IP_MXPDM)
1554     /* No explicit clear operation in the HW, just read everything in the FIFO. */
1555     while(Cy_PDM_PCM_Channel_GetNumInFifo(obj->base, obj->resource.channel_num) > 0)
1556     {
1557         (void)Cy_PDM_PCM_Channel_ReadFifo(obj->base, obj->resource.channel_num);
1558     }
1559 
1560     int16_t paired_channel = _cyhal_pdm_pcm_get_paired_channel(obj, true);
1561     if(paired_channel >= 0)
1562     {
1563         while(Cy_PDM_PCM_Channel_GetNumInFifo(obj->base, paired_channel) > 0)
1564         {
1565             (void)Cy_PDM_PCM_Channel_ReadFifo(obj->base, paired_channel);
1566         }
1567     }
1568 #endif
1569     // clear data-related interrupts
1570     _cyhal_pdm_pcm_clear_interrupt(obj, CY_PDM_PCM_INTR_RX_TRIGGER | CY_PDM_PCM_INTR_RX_OVERFLOW
1571             | CY_PDM_PCM_INTR_RX_UNDERFLOW);
1572     return CY_RSLT_SUCCESS;
1573 }
1574 
cyhal_pdm_pcm_read(cyhal_pdm_pcm_t * obj,void * data,size_t * length)1575 cy_rslt_t cyhal_pdm_pcm_read(cyhal_pdm_pcm_t *obj, void *data, size_t *length)
1576 {
1577     CY_ASSERT(NULL != obj);
1578     if (!(obj->stabilized))
1579     {
1580         *length = 0;
1581     }
1582 
1583 #if defined(CY_IP_MXAUDIOSS)
1584     uint8_t fifo_count = Cy_PDM_PCM_GetNumInFifo(obj->base);
1585 #elif defined(CY_IP_MXPDM)
1586     int16_t paired_channel = _cyhal_pdm_pcm_get_paired_channel(obj, true);
1587     uint8_t fifo_count = Cy_PDM_PCM_Channel_GetNumInFifo(obj->base, obj->resource.channel_num);
1588     if(paired_channel >= 0)
1589     {
1590         fifo_count += Cy_PDM_PCM_Channel_GetNumInFifo(obj->base, paired_channel);
1591         /* We'll be reading one entry from each FIFO, so round down if neceessary to get an even number */
1592         fifo_count &= ~(1u);
1593     }
1594 #endif
1595 
1596     if (*length > fifo_count)
1597     {
1598         *length = fifo_count;
1599     }
1600     size_t i;
1601 
1602     if (obj->word_size == 1)
1603     {
1604         uint8_t *buffer = (uint8_t *)data;
1605         for (i = 0; i < *length; i++)
1606         {
1607 #if defined(CY_IP_MXAUDIOSS)
1608             buffer[i] = (Cy_PDM_PCM_ReadFifo(obj->base) & 0xFF);
1609 #elif defined(CY_IP_MXPDM)
1610             buffer[i] = (Cy_PDM_PCM_Channel_ReadFifo(obj->base, obj->resource.channel_num)) & 0xFF;
1611             if(paired_channel >= 0)
1612             {
1613                 ++i;
1614                 buffer[i] = (Cy_PDM_PCM_Channel_ReadFifo(obj->base, paired_channel)) & 0xFF;
1615             }
1616 #endif
1617         }
1618     }
1619     if (obj->word_size == 2)
1620     {
1621         uint16_t *buffer = (uint16_t *)data;
1622         for (i = 0; i < *length; i++)
1623         {
1624 #if defined(CY_IP_MXAUDIOSS)
1625             buffer[i] = (Cy_PDM_PCM_ReadFifo(obj->base) & 0xFFFF);
1626 #elif defined(CY_IP_MXPDM)
1627             buffer[i] = (Cy_PDM_PCM_Channel_ReadFifo(obj->base, obj->resource.channel_num)) & 0xFFFF;
1628             if(paired_channel >= 0)
1629             {
1630                 ++i;
1631                 buffer[i] = (Cy_PDM_PCM_Channel_ReadFifo(obj->base, paired_channel)) & 0xFFFF;
1632             }
1633 #endif
1634         }
1635     }
1636     else
1637     {
1638         uint32_t *buffer = (uint32_t *)data;
1639         for (i = 0; i < *length; i++)
1640         {
1641 #if defined(CY_IP_MXAUDIOSS)
1642             buffer[i] = Cy_PDM_PCM_ReadFifo(obj->base);
1643 #elif defined(CY_IP_MXPDM)
1644             buffer[i] = Cy_PDM_PCM_Channel_ReadFifo(obj->base, obj->resource.channel_num);
1645             if(paired_channel >= 0)
1646             {
1647                 ++i;
1648                 buffer[i] = Cy_PDM_PCM_Channel_ReadFifo(obj->base, paired_channel);
1649             }
1650 #endif
1651         }
1652     }
1653     return CY_RSLT_SUCCESS;
1654 }
1655 
cyhal_pdm_pcm_read_async(cyhal_pdm_pcm_t * obj,void * data,size_t length)1656 cy_rslt_t cyhal_pdm_pcm_read_async(cyhal_pdm_pcm_t *obj, void *data, size_t length)
1657 {
1658     CY_ASSERT(NULL != obj);
1659     if (obj->pm_transition_ready)
1660     {
1661         return CYHAL_SYSPM_RSLT_ERR_PM_PENDING;
1662     }
1663     if (cyhal_pdm_pcm_is_pending(obj))
1664     {
1665         return CYHAL_PDM_PCM_RSLT_ERR_ASYNC_IN_PROGRESS;
1666     }
1667 
1668     // Disable PDM interrupts temporarily.
1669     _cyhal_irq_disable(_cyhal_pdm_pcm_irq_n[obj->resource.block_num][obj->resource.channel_num]);
1670 
1671     obj->async_buffer = data;
1672     obj->async_read_remaining = length;
1673 
1674     if (obj->stabilized)
1675     {
1676         if (obj->dma.resource.type == CYHAL_RSC_INVALID)
1677         {
1678             // read as much as we can, if there are left overs, then set interrupt flags
1679             _cyhal_pdm_pcm_try_read_async(obj);
1680 
1681             if (0 == obj->async_read_remaining)
1682             {
1683                 cyhal_pdm_pcm_event_callback_t callback = (cyhal_pdm_pcm_event_callback_t) obj->callback_data.callback;
1684                 if (callback != NULL)
1685                 {
1686                     obj->async_buffer = NULL;
1687                     callback(obj->callback_data.callback_arg, CYHAL_PDM_PCM_ASYNC_COMPLETE);
1688                 }
1689             }
1690         }
1691     }
1692     // Setup interrupt for FIFO half full.
1693     if (0 != obj->async_read_remaining)
1694     {
1695         _cyhal_pdm_pcm_set_interrupt_mask(obj, _cyhal_pdm_pcm_get_interrupt_mask(obj) | CY_PDM_PCM_INTR_RX_TRIGGER);
1696     }
1697     _cyhal_irq_enable(_cyhal_pdm_pcm_irq_n[obj->resource.block_num][obj->resource.channel_num]);
1698     return CY_RSLT_SUCCESS;
1699 }
1700 
cyhal_pdm_pcm_is_pending(cyhal_pdm_pcm_t * obj)1701 bool cyhal_pdm_pcm_is_pending(cyhal_pdm_pcm_t *obj)
1702 {
1703     CY_ASSERT(NULL != obj);
1704     return obj->async_read_remaining != 0 && obj->async_buffer != NULL;
1705 }
1706 
cyhal_pdm_pcm_abort_async(cyhal_pdm_pcm_t * obj)1707 cy_rslt_t cyhal_pdm_pcm_abort_async(cyhal_pdm_pcm_t *obj)
1708 {
1709     CY_ASSERT(NULL != obj);
1710     uint32_t savedIntrStatus = cyhal_system_critical_section_enter();
1711     obj->async_read_remaining = 0;
1712     obj->async_buffer = NULL;
1713     // Only disable the interrupt mask if the user did not explicitly enable the mask
1714     if (!(obj->irq_cause & CYHAL_PDM_PCM_RX_HALF_FULL))
1715     {
1716         _cyhal_pdm_pcm_set_interrupt_mask(obj, _cyhal_pdm_pcm_get_interrupt_mask(obj) & ~CY_PDM_PCM_INTR_RX_TRIGGER);
1717     }
1718     cyhal_system_critical_section_exit(savedIntrStatus);
1719     return CY_RSLT_SUCCESS;
1720 }
1721 
cyhal_pdm_pcm_register_callback(cyhal_pdm_pcm_t * obj,cyhal_pdm_pcm_event_callback_t callback,void * callback_arg)1722 void cyhal_pdm_pcm_register_callback(cyhal_pdm_pcm_t *obj, cyhal_pdm_pcm_event_callback_t callback, void *callback_arg)
1723 {
1724     uint32_t savedIntrStatus = cyhal_system_critical_section_enter();
1725     obj->callback_data.callback = (cy_israddress) callback;
1726     obj->callback_data.callback_arg = callback_arg;
1727     cyhal_system_critical_section_exit(savedIntrStatus);
1728 }
1729 
cyhal_pdm_pcm_enable_event(cyhal_pdm_pcm_t * obj,cyhal_pdm_pcm_event_t event,uint8_t intr_priority,bool enable)1730 void cyhal_pdm_pcm_enable_event(cyhal_pdm_pcm_t *obj, cyhal_pdm_pcm_event_t event, uint8_t intr_priority, bool enable)
1731 {
1732     uint32_t mask = _cyhal_pdm_pcm_get_pdl_event_mask(event);
1733     if (enable)
1734     {
1735         obj->irq_cause |= event;
1736         _cyhal_pdm_pcm_clear_interrupt(obj, mask);
1737         _cyhal_pdm_pcm_set_interrupt_mask(obj, _cyhal_pdm_pcm_get_interrupt_mask(obj) | mask);
1738     }
1739     else
1740     {
1741         obj->irq_cause &= ~event;
1742         uint32_t intr_status = cyhal_system_critical_section_enter();
1743         if (!obj->stabilized && cyhal_pdm_pcm_is_pending(obj))
1744         {
1745             // The half full event is used internally by the stabilization code.
1746             // The start() API clear the FIFO, if we have more data than the half FIFO, then PDM/PCM has stabilized.
1747             // This half interrupt mask will automatically cleared by the stabilization code.
1748 
1749             // Is an async operation is pending the mask will also be cleared automatically when the async operation finishes
1750             mask &= ~CY_PDM_PCM_INTR_RX_TRIGGER;
1751         }
1752         _cyhal_pdm_pcm_set_interrupt_mask(obj, _cyhal_pdm_pcm_get_interrupt_mask(obj) & ~mask);
1753         cyhal_system_critical_section_exit(intr_status);
1754     }
1755 
1756     _cyhal_irq_set_priority(_cyhal_pdm_pcm_irq_n[obj->resource.block_num][obj->resource.channel_num], intr_priority);
1757 }
1758 
cyhal_pdm_pcm_set_async_mode(cyhal_pdm_pcm_t * obj,cyhal_async_mode_t mode,uint8_t dma_priority)1759 cy_rslt_t cyhal_pdm_pcm_set_async_mode(cyhal_pdm_pcm_t *obj, cyhal_async_mode_t mode, uint8_t dma_priority)
1760 {
1761     CY_ASSERT(NULL != obj);
1762     cy_rslt_t rslt = CY_RSLT_SUCCESS;
1763 
1764     if (CYHAL_ASYNC_SW == mode)
1765     {
1766         if (CYHAL_DMA_PRIORITY_DEFAULT != dma_priority)
1767         {
1768             rslt = CYHAL_PDM_PCM_RSLT_ERR_INVALID_CONFIG_PARAM;
1769         }
1770         else if (CYHAL_RSC_INVALID != obj->dma.resource.type)
1771         {
1772             cyhal_dma_free(&(obj->dma));
1773             obj->dma.resource.type = CYHAL_RSC_INVALID;
1774 #if defined(CY_IP_MXPDM)
1775             if(CYHAL_RSC_INVALID != obj->dma_paired.resource.type)
1776             {
1777                 cyhal_dma_free(&(obj->dma_paired));
1778                 obj->dma_paired.resource.type = CYHAL_RSC_INVALID;
1779             }
1780 #endif
1781         }
1782     }
1783     else if (CYHAL_ASYNC_DMA == mode && CYHAL_RSC_INVALID == obj->dma.resource.type)
1784     {
1785         rslt = cyhal_dma_init(&(obj->dma), dma_priority, CYHAL_DMA_DIRECTION_PERIPH2MEM);
1786 #if defined(CY_IP_MXPDM)
1787         int16_t paired_channel = _cyhal_pdm_pcm_get_paired_channel(obj, true);
1788         if(paired_channel >= 0)
1789         {
1790             rslt = cyhal_dma_init(&(obj->dma_paired), dma_priority, CYHAL_DMA_DIRECTION_PERIPH2MEM);
1791             if(CY_RSLT_SUCCESS == rslt)
1792             {
1793                 cyhal_dma_register_callback(&(obj->dma_paired), &_cyhal_pdm_pcm_dma_callback, obj);
1794             }
1795         }
1796 #endif
1797         if(CY_RSLT_SUCCESS == rslt)
1798         {
1799             cyhal_dma_register_callback(&(obj->dma), &_cyhal_pdm_pcm_dma_callback, obj);
1800         }
1801     }
1802     return rslt;
1803 }
1804 
1805 #if defined(__cplusplus)
1806 }
1807 #endif
1808 
1809 #endif /* CYHAL_DRIVER_AVAILABLE_PDMPCM */
1810