1 /*
2 * Copyright (c) 2019-2025 Allwinner Technology Co., Ltd. ALL rights reserved.
3 *
4 * Allwinner is a trademark of Allwinner Technology Co.,Ltd., registered in
5 * the the people's Republic of China and other countries.
6 * All Allwinner Technology Co.,Ltd. trademarks are used with permission.
7 *
8 * DISCLAIMER
9 * THIRD PARTY LICENCES MAY BE REQUIRED TO IMPLEMENT THE SOLUTION/PRODUCT.
10 * IF YOU NEED TO INTEGRATE THIRD PARTY’S TECHNOLOGY (SONY, DTS, DOLBY, AVS OR MPEGLA, ETC.)
11 * IN ALLWINNERS’SDK OR PRODUCTS, YOU SHALL BE SOLELY RESPONSIBLE TO OBTAIN
12 * ALL APPROPRIATELY REQUIRED THIRD PARTY LICENCES.
13 * ALLWINNER SHALL HAVE NO WARRANTY, INDEMNITY OR OTHER OBLIGATIONS WITH RESPECT TO MATTERS
14 * COVERED UNDER ANY REQUIRED THIRD PARTY LICENSE.
15 * YOU ARE SOLELY RESPONSIBLE FOR YOUR USAGE OF THIRD PARTY’S TECHNOLOGY.
16 *
17 *
18 * THIS SOFTWARE IS PROVIDED BY ALLWINNER"AS IS" AND TO THE MAXIMUM EXTENT
19 * PERMITTED BY LAW, ALLWINNER EXPRESSLY DISCLAIMS ALL WARRANTIES OF ANY KIND,
20 * WHETHER EXPRESS, IMPLIED OR STATUTORY, INCLUDING WITHOUT LIMITATION REGARDING
21 * THE TITLE, NON-INFRINGEMENT, ACCURACY, CONDITION, COMPLETENESS, PERFORMANCE
22 * OR MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
23 * IN NO EVENT SHALL ALLWINNER BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
25 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
26 * LOSS OF USE, DATA, OR PROFITS, OR BUSINESS INTERRUPTION)
27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
28 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
30 * OF THE POSSIBILITY OF SUCH DAMAGE.
31 */
32 #include <stdio.h>
33 #include <sound/snd_core.h>
34 #include <sound/snd_pcm.h>
35 #include <sound/snd_dma.h>
36 #include <sound/dma_wrap.h>
37 #include "sunxi-pcm.h"
38
39 #ifndef sunxi_slave_id
40 #define sunxi_slave_id(d, s) (((d)<<16) | (s))
41 #endif
42
43 /* playback: period_size=2048*(16*2/8)=8K buffer_size=8K*8=64K */
44 /* capture: period_size=2048*(16*4/8)=16K buffer_size=16K*8=128K */
45 static const struct snd_pcm_hardware sunxi_pcm_hardware[2] = {
46 { /* SNDRV_PCM_STREAM_PLAYBACK */
47 .info = SNDRV_PCM_INFO_INTERLEAVED
48 | SNDRV_PCM_INFO_BLOCK_TRANSFER
49 | SNDRV_PCM_INFO_MMAP
50 | SNDRV_PCM_INFO_MMAP_VALID
51 | SNDRV_PCM_INFO_PAUSE
52 | SNDRV_PCM_INFO_RESUME,
53 .buffer_bytes_max = 1024 * 128,
54 .period_bytes_min = 256,
55 .period_bytes_max = 1024 * 64,
56 .periods_min = 2,
57 .periods_max = 16,
58 },
59 { /* SNDRV_PCM_STREAM_CAPTURE */
60 .info = SNDRV_PCM_INFO_INTERLEAVED
61 | SNDRV_PCM_INFO_BLOCK_TRANSFER
62 | SNDRV_PCM_INFO_MMAP
63 | SNDRV_PCM_INFO_MMAP_VALID
64 | SNDRV_PCM_INFO_PAUSE
65 | SNDRV_PCM_INFO_RESUME,
66 .buffer_bytes_max = 1024 * 128,
67 .period_bytes_min = 256,
68 .period_bytes_max = 1024 * 64,
69 .periods_min = 2,
70 .periods_max = 16,
71 },
72 };
73
sunxi_pcm_preallocate_dma_buffer(struct snd_pcm * pcm,int stream)74 static int sunxi_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream)
75 {
76 const struct snd_pcm_hardware *hw = NULL;
77 struct snd_codec *codec = pcm->card->codec;
78 struct snd_dma_buffer *buf = &pcm->streams[stream]->dma_buffer;
79 size_t size = 0;
80
81 snd_print("prealloc dma buffer\n");
82 if (codec->hw)
83 hw = &codec->hw[stream];
84 else
85 hw = &sunxi_pcm_hardware[stream];
86 size = hw->buffer_bytes_max;
87 buf->addr = dma_alloc_coherent(size);
88 if (!buf->addr)
89 return -ENOMEM;
90 buf->bytes = size;
91
92 return 0;
93 }
94
sunxi_pcm_new(struct snd_pcm * pcm)95 int sunxi_pcm_new(struct snd_pcm *pcm)
96 {
97 int ret;
98
99 snd_print("\n");
100 if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK]) {
101 ret = sunxi_pcm_preallocate_dma_buffer(pcm,
102 SNDRV_PCM_STREAM_PLAYBACK);
103 if (ret != 0)
104 goto err;
105 }
106 if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE]) {
107 ret = sunxi_pcm_preallocate_dma_buffer(pcm,
108 SNDRV_PCM_STREAM_CAPTURE);
109 if (ret != 0)
110 goto err;
111 }
112 err:
113 return 0;
114 }
115
sunxi_pcm_free(struct snd_pcm * pcm)116 void sunxi_pcm_free(struct snd_pcm *pcm)
117 {
118 struct snd_pcm_substream *substream;
119 struct snd_dma_buffer *buf;
120 int stream;
121
122 snd_print("\n");
123 for (stream = 0; stream < 2; stream++) {
124 substream = pcm->streams[stream];
125 if (!substream)
126 continue;
127 buf = &substream->dma_buffer;
128 if (!buf->addr)
129 continue;
130 snd_print("dma free.\n");
131 dma_free_coherent(buf->addr);
132 buf->addr = NULL;
133 }
134 return ;
135 }
136
sunxi_pcm_open(struct snd_pcm_substream * substream)137 static int sunxi_pcm_open(struct snd_pcm_substream *substream)
138 {
139 int ret;
140 const struct snd_pcm_hardware *hw = NULL;
141 struct snd_codec *codec = substream->pcm->card->codec;
142
143 if (codec->hw)
144 hw = &codec->hw[substream->stream];
145 else
146 hw = &sunxi_pcm_hardware[substream->stream];
147 snd_set_runtime_hwparams(substream, hw);
148 snd_print("request dma channel\n");
149 /* request dma channel */
150 ret = snd_dmaengine_pcm_open_request_chan(substream);
151 if (ret != 0)
152 snd_err("dmaengine pcm open failed with err %d\n", ret);
153 return ret;
154 }
155
sunxi_pcm_close(struct snd_pcm_substream * substream)156 static int sunxi_pcm_close(struct snd_pcm_substream *substream)
157 {
158 snd_print("\n");
159 return snd_dmaengine_pcm_close_release_chan(substream);
160 }
161
sunxi_pcm_hw_params(struct snd_pcm_substream * substream,struct snd_pcm_hw_params * params)162 static int sunxi_pcm_hw_params(struct snd_pcm_substream *substream,
163 struct snd_pcm_hw_params *params)
164 {
165 int ret;
166 struct snd_card *card = substream->pcm->card;
167 struct snd_dai *cpu_dai = card->platform->cpu_dai;
168 // struct dmaengine_pcm_runtime_data *prtd = substream->runtime->private_data;
169 struct dma_chan *chan = snd_dmaengine_pcm_get_chan(substream);
170 struct dma_slave_config slave_config = {0};
171 struct sunxi_dma_params *dmap;
172 #ifdef CONFIG_SND_PLATFORM_SUNXI_MAD
173 struct snd_platform *platform = cpu_dai->component;
174 unsigned int mad_bind = 0;
175 #endif
176
177 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
178 dmap = cpu_dai->playback_dma_data;
179 else
180 dmap = cpu_dai->capture_dma_data;
181
182 ret = snd_hwparams_to_dma_slave_config(substream, params, &slave_config);
183 if (ret != 0) {
184 snd_err("hw params config failed with err %d\n", ret);
185 return ret;
186 }
187 slave_config.dst_maxburst = dmap->dst_maxburst;
188 slave_config.src_maxburst = dmap->src_maxburst;
189 #ifdef CONFIG_SND_PLATFORM_SUNXI_MAD
190 if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
191 if (!strncmp(card->name, "snddmic", 7)) {
192 struct sunxi_dmic_info *sunxi_dmic = platform->private_data;
193 mad_bind = sunxi_dmic->mad_priv.mad_bind;
194 } else if (!strncmp(card->name, "snddaudio0", 10)) {
195 struct sunxi_daudio_info *sunxi_daudio = platform->private_data;
196 mad_bind = sunxi_daudio->mad_priv.mad_bind;
197 }
198 printf(SNDRV_LOG_COLOR_BLUE "mad_bind[%s]: %s\n" SNDRV_LOG_COLOR_NONE,
199 card->name, mad_bind ? "On":"Off");
200 if (mad_bind == 1)
201 slave_config.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
202 }
203 #endif
204
205 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
206 slave_config.dst_addr = (unsigned long)dmap->dma_addr;
207 slave_config.src_addr_width = slave_config.dst_addr_width;
208 slave_config.slave_id = sunxi_slave_id(dmap->dma_drq_type_num, 0);
209 } else {
210 slave_config.src_addr = (unsigned long)dmap->dma_addr;
211 slave_config.dst_addr_width = slave_config.src_addr_width;
212 slave_config.slave_id = sunxi_slave_id(0,
213 dmap->dma_drq_type_num);
214 }
215 snd_info("src_addr:%p, dst_addr:%p, drq_type:%d\n",
216 slave_config.src_addr, slave_config.dst_addr,
217 dmap->dma_drq_type_num);
218
219 ret = dmaengine_slave_config(chan, &slave_config);
220 if (ret != 0) {
221 snd_err("dmaengine_slave_config failed with err %d\n", ret);
222 return ret;
223 }
224
225 snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
226
227 return 0;
228 }
229
sunxi_pcm_hw_free(struct snd_pcm_substream * substream)230 static int sunxi_pcm_hw_free(struct snd_pcm_substream *substream)
231 {
232 snd_print("\n");
233 snd_pcm_set_runtime_buffer(substream, NULL);
234 return 0;
235 }
236
sunxi_pcm_trigger(struct snd_pcm_substream * substream,int cmd)237 static int sunxi_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
238 {
239 snd_print(" stream:%u, cmd:%u\n", substream->stream, cmd);
240 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
241 switch (cmd) {
242 case SNDRV_PCM_TRIGGER_START:
243 case SNDRV_PCM_TRIGGER_RESUME:
244 case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
245 snd_dmaengine_pcm_trigger(substream,
246 SNDRV_PCM_TRIGGER_START);
247 break;
248 case SNDRV_PCM_TRIGGER_SUSPEND:
249 case SNDRV_PCM_TRIGGER_STOP:
250 case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
251 snd_dmaengine_pcm_trigger(substream,
252 SNDRV_PCM_TRIGGER_STOP);
253 break;
254 }
255 } else {
256 switch (cmd) {
257 case SNDRV_PCM_TRIGGER_START:
258 case SNDRV_PCM_TRIGGER_RESUME:
259 case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
260 snd_dmaengine_pcm_trigger(substream,
261 SNDRV_PCM_TRIGGER_START);
262 break;
263 case SNDRV_PCM_TRIGGER_SUSPEND:
264 case SNDRV_PCM_TRIGGER_STOP:
265 case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
266 snd_dmaengine_pcm_trigger(substream,
267 SNDRV_PCM_TRIGGER_STOP);
268 break;
269 }
270 }
271 return 0;
272 }
273
274 struct snd_pcm_ops sunxi_pcm_ops = {
275 .open = sunxi_pcm_open,
276 .close = sunxi_pcm_close,
277 .hw_params = sunxi_pcm_hw_params,
278 .hw_free = sunxi_pcm_hw_free,
279 .trigger = sunxi_pcm_trigger,
280 .pointer = snd_dmaengine_pcm_pointer,
281 };
282
283