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