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