1 /*
2  * Copyright (c) 2022 HPMicro
3  *
4  * SPDX-License-Identifier: BSD-3-Clause
5  *
6  */
7 
8 #include <rtthread.h>
9 #include <rtdevice.h>
10 
11 #define DBG_TAG              "pdm"
12 #define DBG_LVL              DBG_INFO
13 #include <rtdbg.h>
14 
15 #ifdef BSP_USING_PDM
16 #include "board.h"
17 #include "drivers/dev_audio.h"
18 #include "hpm_i2s_drv.h"
19 #include "hpm_pdm_drv.h"
20 #include "drv_pdm.h"
21 #ifdef HPMSOC_HAS_HPMSDK_DMAV2
22 #include "hpm_dmav2_drv.h"
23 #else
24 #include "hpm_dma_drv.h"
25 #endif
26 #include "hpm_dmamux_drv.h"
27 #include "hpm_l1c_drv.h"
28 #include "hpm_dma_mgr.h"
29 
30 
31 /* PDM connect to I2S0 RX */
32 #define PDM_DMA_REQ          HPM_DMA_SRC_I2S0_RX
33 #define PDM_I2S_DATA_LINE    0
34 
35 struct hpm_pdm
36 {
37     struct rt_audio_device audio;
38     struct rt_audio_configure record_config;
39     rt_uint8_t* rx_fifo;
40 };
41 
42 struct hpm_pdm hpm_pdm_dev = { 0 };
43 static dma_resource_t dma_resource = { 0 };
44 static rt_err_t hpm_pdm_dma_transmit();
45 
46 
pdm_dma_tc_callback(DMA_Type * ptr,uint32_t channel,void * user_data)47 void pdm_dma_tc_callback(DMA_Type *ptr, uint32_t channel, void *user_data)
48 {
49     rt_audio_rx_done(&hpm_pdm_dev.audio, hpm_pdm_dev.rx_fifo, PDM_FIFO_SIZE);
50     hpm_pdm_dma_transmit();
51 }
52 
hpm_pdm_getcaps(struct rt_audio_device * audio,struct rt_audio_caps * caps)53 static rt_err_t hpm_pdm_getcaps(struct rt_audio_device* audio, struct rt_audio_caps* caps)
54 {
55     rt_err_t result = RT_EOK;
56 
57     RT_ASSERT(audio != RT_NULL);
58     struct hpm_pdm* hpm_audio = (struct hpm_pdm*)audio->parent.user_data;
59 
60     switch(caps->main_type)
61     {
62         case AUDIO_TYPE_INPUT:
63         {
64             switch(caps->sub_type)
65             {
66             case AUDIO_DSP_PARAM:
67                 caps->udata.config.samplerate   = hpm_audio->record_config.samplerate;
68                 caps->udata.config.channels     = hpm_audio->record_config.channels;
69                 caps->udata.config.samplebits   = hpm_audio->record_config.samplebits;
70                 break;
71 
72             case AUDIO_DSP_SAMPLERATE:
73                 caps->udata.config.samplerate   = hpm_audio->record_config.samplerate;
74                 break;
75 
76             case AUDIO_DSP_CHANNELS:
77                 caps->udata.config.channels     = hpm_audio->record_config.channels;
78                 break;
79 
80             case AUDIO_DSP_SAMPLEBITS:
81                 caps->udata.config.samplebits   = hpm_audio->record_config.samplebits;
82                 break;
83 
84             default:
85                 result = -RT_ERROR;
86                 break;
87             }
88 
89             break;
90         }
91 
92         default:
93             result = -RT_ERROR;
94             break;
95     }
96 
97     return result;
98 }
99 
i2s_is_enabled(I2S_Type * ptr)100 static bool i2s_is_enabled(I2S_Type *ptr)
101 {
102     return ((ptr->CTRL & I2S_CTRL_I2S_EN_MASK) != 0);
103 }
104 
hpm_pdm_set_channels(uint32_t channel)105 static rt_err_t hpm_pdm_set_channels(uint32_t channel)
106 {
107     uint32_t mclk_hz;
108     i2s_transfer_config_t transfer;
109 
110     mclk_hz = clock_get_frequency(clock_i2s0);
111     i2s_get_default_transfer_config_for_pdm(&transfer);
112     transfer.data_line = PDM_I2S_DATA_LINE;
113     if (channel == 1) {
114         transfer.channel_slot_mask = BOARD_PDM_SINGLE_CHANNEL_MASK;
115     } else if(channel == 2) {
116         transfer.channel_slot_mask = BOARD_PDM_DUAL_CHANNEL_MASK;
117     } else {
118         LOG_E("PDM not support channels number %d.\n", channel);
119         return -RT_ERROR;
120     }
121 
122     bool is_enabled = i2s_is_enabled(PDM_I2S);
123     if (is_enabled) {
124         dma_abort_channel(dma_resource.base, dma_resource.channel);
125     }
126     if (status_success != i2s_config_rx(PDM_I2S, mclk_hz, &transfer))
127     {
128         LOG_E("pdm_i2s configure transfer failed\n");
129         return -RT_ERROR;
130     }
131     if (is_enabled)
132     {
133         i2s_enable(PDM_I2S);
134     }
135 
136     return RT_EOK;
137 }
138 
hpm_pdm_configure(struct rt_audio_device * audio,struct rt_audio_caps * caps)139 static rt_err_t hpm_pdm_configure(struct rt_audio_device* audio, struct rt_audio_caps* caps)
140 {
141 
142     rt_err_t result = RT_EOK;
143     RT_ASSERT(audio != RT_NULL);
144 
145     struct hpm_pdm* hpm_audio = (struct hpm_pdm*)audio->parent.user_data;
146 
147     switch(caps->main_type)
148     {
149         case AUDIO_TYPE_INPUT:
150         {
151             switch(caps->sub_type)
152             {
153                 case AUDIO_DSP_CHANNELS:
154                 {
155                     hpm_audio->record_config.channels = caps->udata.config.channels;
156                     hpm_pdm_set_channels(caps->udata.config.channels);
157                     break;
158                 }
159 
160                 default:
161                     result = -RT_ERROR;
162                     break;
163             }
164         }
165 
166         default:
167             result = -RT_ERROR;
168             break;
169     }
170     return result;
171 }
172 
173 
hpm_pdm_init(struct rt_audio_device * audio)174 static rt_err_t hpm_pdm_init(struct rt_audio_device* audio)
175 {
176     RT_ASSERT(audio != RT_NULL);
177 
178     i2s_config_t i2s_config;
179     i2s_transfer_config_t transfer;
180     pdm_config_t pdm_config;
181 
182     init_pdm_pins();
183     board_init_pdm_clock();
184 
185     i2s_get_default_config(PDM_I2S, &i2s_config);
186     i2s_enable_rx_dma_request(PDM_I2S);
187     i2s_init(PDM_I2S, &i2s_config);
188 
189     i2s_get_default_transfer_config_for_pdm(&transfer);
190     transfer.data_line = PDM_I2S_DATA_LINE;
191     transfer.channel_slot_mask = BOARD_PDM_SINGLE_CHANNEL_MASK;
192     if (status_success != i2s_config_rx(PDM_I2S, clock_get_frequency(clock_i2s0), &transfer))
193     {
194         LOG_E("pdm_i2s configure receive failed\n");
195         return -RT_ERROR;
196     }
197 
198     /* init audio configure */
199     hpm_pdm_dev.record_config.channels = 1U;
200     hpm_pdm_dev.record_config.samplebits = 32U; /* the actual significant bit is 24 bits, and the low bit is 0  */
201     hpm_pdm_dev.record_config.samplerate = PDM_SOC_SAMPLE_RATE_IN_HZ; /* fix 16KHz */
202 
203     pdm_get_default_config(HPM_PDM, &pdm_config);
204     if (status_success != pdm_init(HPM_PDM, &pdm_config)) {
205         LOG_E("pdm init failed\n");
206         return -RT_ERROR;
207     }
208 
209     return RT_EOK;
210 }
211 
hpm_pdm_start(struct rt_audio_device * audio,int stream)212 static rt_err_t hpm_pdm_start(struct rt_audio_device* audio, int stream)
213 {
214     RT_ASSERT(audio != RT_NULL);
215     struct hpm_pdm* hpm_audio = (struct hpm_pdm*)audio->parent.user_data;
216 
217     if (stream == AUDIO_STREAM_RECORD)
218     {
219         i2s_disable(PDM_I2S);
220         i2s_disable_rx_dma_request(PDM_I2S);
221         pdm_stop(HPM_PDM);
222         pdm_software_reset(HPM_PDM);
223 
224         if (dma_mgr_request_resource(&dma_resource) == status_success) {
225             uint8_t dmamux_ch;
226             dma_mgr_install_chn_tc_callback(&dma_resource, pdm_dma_tc_callback, NULL);
227             dma_mgr_enable_dma_irq_with_priority(&dma_resource, 1);
228             dmamux_ch = DMA_SOC_CHN_TO_DMAMUX_CHN(dma_resource.base, dma_resource.channel);
229             dmamux_config(HPM_DMAMUX, dmamux_ch, PDM_DMA_REQ, true);
230         } else {
231             LOG_E("no dma resource available for PDM transfer.\n");
232             return -RT_ERROR;
233         }
234 
235         i2s_reset_rx(PDM_I2S);
236         if (RT_EOK != hpm_pdm_dma_transmit()) {
237             return -RT_ERROR;
238         }
239 
240         pdm_start(HPM_PDM);
241         i2s_enable_rx_dma_request(PDM_I2S);
242         i2s_start(PDM_I2S);
243     }
244 
245     return RT_EOK;
246 }
247 
hpm_pdm_stop(struct rt_audio_device * audio,int stream)248 static rt_err_t hpm_pdm_stop(struct rt_audio_device* audio, int stream)
249 {
250     RT_ASSERT(audio != RT_NULL);
251 
252     if (stream == AUDIO_STREAM_RECORD) {
253         pdm_stop(HPM_PDM);
254         i2s_stop(PDM_I2S);
255 
256         dma_abort_channel(dma_resource.base, dma_resource.channel);
257         dma_mgr_release_resource(&dma_resource);
258     }
259 
260     return RT_EOK;
261 }
262 
hpm_pdm_dma_transmit()263 static rt_err_t hpm_pdm_dma_transmit()
264 {
265     dma_channel_config_t ch_config = {0};
266     dma_default_channel_config(dma_resource.base, &ch_config);
267     ch_config.src_addr = (uint32_t)&PDM_I2S->RXD[PDM_I2S_DATA_LINE];
268     ch_config.dst_addr = core_local_mem_to_sys_address(HPM_CORE0, (uint32_t)hpm_pdm_dev.rx_fifo);
269     ch_config.src_width = DMA_TRANSFER_WIDTH_WORD;
270     ch_config.dst_width = DMA_TRANSFER_WIDTH_WORD;
271     ch_config.src_addr_ctrl = DMA_ADDRESS_CONTROL_FIXED;
272     ch_config.dst_addr_ctrl = DMA_ADDRESS_CONTROL_INCREMENT;
273     ch_config.size_in_byte = PDM_FIFO_SIZE;
274     ch_config.src_mode = DMA_HANDSHAKE_MODE_HANDSHAKE;
275     ch_config.src_burst_size = DMA_NUM_TRANSFER_PER_BURST_1T;
276 
277     if (status_success != dma_setup_channel(dma_resource.base, dma_resource.channel, &ch_config, true)) {
278         LOG_E("dma setup channel failed\n");
279         return -RT_ERROR;
280     }
281 
282     if (l1c_dc_is_enabled()) {
283         /* cache invalidate for receive buff */
284         l1c_dc_invalidate((uint32_t)hpm_pdm_dev.rx_fifo, PDM_FIFO_SIZE);
285     }
286 
287     return RT_EOK;
288 }
289 
290 
291 static struct rt_audio_ops hpm_pdm_ops =
292 {
293     .getcaps     = hpm_pdm_getcaps,
294     .configure   = hpm_pdm_configure,
295     .init        = hpm_pdm_init,
296     .start       = hpm_pdm_start,
297     .stop        = hpm_pdm_stop,
298     .transmit    = NULL,
299     .buffer_info = NULL,
300 };
301 
ATTR_ALIGN(HPM_L1C_CACHELINE_SIZE)302 ATTR_ALIGN(HPM_L1C_CACHELINE_SIZE) uint8_t pdm_rx_fifo[PDM_FIFO_SIZE];
303 
304 int rt_hw_pdm_init(void)
305 {
306     hpm_pdm_dev.rx_fifo = pdm_rx_fifo;
307 
308     hpm_pdm_dev.audio.ops = &hpm_pdm_ops;
309 
310     LOG_I("audio pdm registered.\n");
311     LOG_I("!!!Note: pdm depends on i2s0, they share clock.\n");
312     rt_audio_register(&hpm_pdm_dev.audio, "pdm", RT_DEVICE_FLAG_RDONLY, &hpm_pdm_dev);
313 
314     return RT_EOK;
315 }
316 INIT_DEVICE_EXPORT(rt_hw_pdm_init);
317 
318 #endif /* BSP_USING_PDM */
319