1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * atmel-pcm.c -- ALSA PCM interface for the Atmel atmel SoC. |
4 | * |
5 | * Copyright (C) 2005 SAN People |
6 | * Copyright (C) 2008 Atmel |
7 | * |
8 | * Authors: Sedji Gaouaou <sedji.gaouaou@atmel.com> |
9 | * |
10 | * Based on at91-pcm. by: |
11 | * Frank Mandarino <fmandarino@endrelia.com> |
12 | * Copyright 2006 Endrelia Technologies Inc. |
13 | * |
14 | * Based on pxa2xx-pcm.c by: |
15 | * |
16 | * Author: Nicolas Pitre |
17 | * Created: Nov 30, 2004 |
18 | * Copyright: (C) 2004 MontaVista Software, Inc. |
19 | */ |
20 | |
21 | #include <linux/module.h> |
22 | #include <linux/init.h> |
23 | #include <linux/platform_device.h> |
24 | #include <linux/slab.h> |
25 | #include <linux/dma-mapping.h> |
26 | #include <linux/atmel_pdc.h> |
27 | #include <linux/atmel-ssc.h> |
28 | |
29 | #include <sound/core.h> |
30 | #include <sound/pcm.h> |
31 | #include <sound/pcm_params.h> |
32 | #include <sound/soc.h> |
33 | |
34 | #include "atmel-pcm.h" |
35 | |
36 | |
37 | static int atmel_pcm_new(struct snd_soc_component *component, |
38 | struct snd_soc_pcm_runtime *rtd) |
39 | { |
40 | struct snd_card *card = rtd->card->snd_card; |
41 | int ret; |
42 | |
43 | ret = dma_coerce_mask_and_coherent(dev: card->dev, DMA_BIT_MASK(32)); |
44 | if (ret) |
45 | return ret; |
46 | |
47 | snd_pcm_set_managed_buffer_all(pcm: rtd->pcm, SNDRV_DMA_TYPE_DEV, |
48 | data: card->dev, ATMEL_SSC_DMABUF_SIZE, |
49 | ATMEL_SSC_DMABUF_SIZE); |
50 | |
51 | return 0; |
52 | } |
53 | |
54 | /*--------------------------------------------------------------------------*\ |
55 | * Hardware definition |
56 | \*--------------------------------------------------------------------------*/ |
57 | /* TODO: These values were taken from the AT91 platform driver, check |
58 | * them against real values for AT32 |
59 | */ |
60 | static const struct snd_pcm_hardware atmel_pcm_hardware = { |
61 | .info = SNDRV_PCM_INFO_MMAP | |
62 | SNDRV_PCM_INFO_MMAP_VALID | |
63 | SNDRV_PCM_INFO_INTERLEAVED | |
64 | SNDRV_PCM_INFO_PAUSE, |
65 | .period_bytes_min = 32, |
66 | .period_bytes_max = 8192, |
67 | .periods_min = 2, |
68 | .periods_max = 1024, |
69 | .buffer_bytes_max = ATMEL_SSC_DMABUF_SIZE, |
70 | }; |
71 | |
72 | |
73 | /*--------------------------------------------------------------------------*\ |
74 | * Data types |
75 | \*--------------------------------------------------------------------------*/ |
76 | struct atmel_runtime_data { |
77 | struct atmel_pcm_dma_params *params; |
78 | dma_addr_t dma_buffer; /* physical address of dma buffer */ |
79 | dma_addr_t dma_buffer_end; /* first address beyond DMA buffer */ |
80 | size_t period_size; |
81 | |
82 | dma_addr_t period_ptr; /* physical address of next period */ |
83 | }; |
84 | |
85 | /*--------------------------------------------------------------------------*\ |
86 | * ISR |
87 | \*--------------------------------------------------------------------------*/ |
88 | static void atmel_pcm_dma_irq(u32 ssc_sr, |
89 | struct snd_pcm_substream *substream) |
90 | { |
91 | struct atmel_runtime_data *prtd = substream->runtime->private_data; |
92 | struct atmel_pcm_dma_params *params = prtd->params; |
93 | static int count; |
94 | |
95 | count++; |
96 | |
97 | if (ssc_sr & params->mask->ssc_endbuf) { |
98 | pr_warn("atmel-pcm: buffer %s on %s (SSC_SR=%#x, count=%d)\n" , |
99 | substream->stream == SNDRV_PCM_STREAM_PLAYBACK |
100 | ? "underrun" : "overrun" , |
101 | params->name, ssc_sr, count); |
102 | |
103 | /* re-start the PDC */ |
104 | ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR, |
105 | params->mask->pdc_disable); |
106 | prtd->period_ptr += prtd->period_size; |
107 | if (prtd->period_ptr >= prtd->dma_buffer_end) |
108 | prtd->period_ptr = prtd->dma_buffer; |
109 | |
110 | ssc_writex(params->ssc->regs, params->pdc->xpr, |
111 | prtd->period_ptr); |
112 | ssc_writex(params->ssc->regs, params->pdc->xcr, |
113 | prtd->period_size / params->pdc_xfer_size); |
114 | ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR, |
115 | params->mask->pdc_enable); |
116 | } |
117 | |
118 | if (ssc_sr & params->mask->ssc_endx) { |
119 | /* Load the PDC next pointer and counter registers */ |
120 | prtd->period_ptr += prtd->period_size; |
121 | if (prtd->period_ptr >= prtd->dma_buffer_end) |
122 | prtd->period_ptr = prtd->dma_buffer; |
123 | |
124 | ssc_writex(params->ssc->regs, params->pdc->xnpr, |
125 | prtd->period_ptr); |
126 | ssc_writex(params->ssc->regs, params->pdc->xncr, |
127 | prtd->period_size / params->pdc_xfer_size); |
128 | } |
129 | |
130 | snd_pcm_period_elapsed(substream); |
131 | } |
132 | |
133 | |
134 | /*--------------------------------------------------------------------------*\ |
135 | * PCM operations |
136 | \*--------------------------------------------------------------------------*/ |
137 | static int atmel_pcm_hw_params(struct snd_soc_component *component, |
138 | struct snd_pcm_substream *substream, |
139 | struct snd_pcm_hw_params *params) |
140 | { |
141 | struct snd_pcm_runtime *runtime = substream->runtime; |
142 | struct atmel_runtime_data *prtd = runtime->private_data; |
143 | struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); |
144 | |
145 | /* this may get called several times by oss emulation |
146 | * with different params */ |
147 | |
148 | prtd->params = snd_soc_dai_get_dma_data(snd_soc_rtd_to_cpu(rtd, 0), substream); |
149 | prtd->params->dma_intr_handler = atmel_pcm_dma_irq; |
150 | |
151 | prtd->dma_buffer = runtime->dma_addr; |
152 | prtd->dma_buffer_end = runtime->dma_addr + runtime->dma_bytes; |
153 | prtd->period_size = params_period_bytes(p: params); |
154 | |
155 | pr_debug("atmel-pcm: " |
156 | "hw_params: DMA for %s initialized " |
157 | "(dma_bytes=%zu, period_size=%zu)\n" , |
158 | prtd->params->name, |
159 | runtime->dma_bytes, |
160 | prtd->period_size); |
161 | return 0; |
162 | } |
163 | |
164 | static int atmel_pcm_hw_free(struct snd_soc_component *component, |
165 | struct snd_pcm_substream *substream) |
166 | { |
167 | struct atmel_runtime_data *prtd = substream->runtime->private_data; |
168 | struct atmel_pcm_dma_params *params = prtd->params; |
169 | |
170 | if (params != NULL) { |
171 | ssc_writex(params->ssc->regs, SSC_PDC_PTCR, |
172 | params->mask->pdc_disable); |
173 | prtd->params->dma_intr_handler = NULL; |
174 | } |
175 | |
176 | return 0; |
177 | } |
178 | |
179 | static int atmel_pcm_prepare(struct snd_soc_component *component, |
180 | struct snd_pcm_substream *substream) |
181 | { |
182 | struct atmel_runtime_data *prtd = substream->runtime->private_data; |
183 | struct atmel_pcm_dma_params *params = prtd->params; |
184 | |
185 | ssc_writex(params->ssc->regs, SSC_IDR, |
186 | params->mask->ssc_endx | params->mask->ssc_endbuf); |
187 | ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR, |
188 | params->mask->pdc_disable); |
189 | return 0; |
190 | } |
191 | |
192 | static int atmel_pcm_trigger(struct snd_soc_component *component, |
193 | struct snd_pcm_substream *substream, int cmd) |
194 | { |
195 | struct snd_pcm_runtime *rtd = substream->runtime; |
196 | struct atmel_runtime_data *prtd = rtd->private_data; |
197 | struct atmel_pcm_dma_params *params = prtd->params; |
198 | int ret = 0; |
199 | |
200 | pr_debug("atmel-pcm:buffer_size = %ld," |
201 | "dma_area = %p, dma_bytes = %zu\n" , |
202 | rtd->buffer_size, rtd->dma_area, rtd->dma_bytes); |
203 | |
204 | switch (cmd) { |
205 | case SNDRV_PCM_TRIGGER_START: |
206 | prtd->period_ptr = prtd->dma_buffer; |
207 | |
208 | ssc_writex(params->ssc->regs, params->pdc->xpr, |
209 | prtd->period_ptr); |
210 | ssc_writex(params->ssc->regs, params->pdc->xcr, |
211 | prtd->period_size / params->pdc_xfer_size); |
212 | |
213 | prtd->period_ptr += prtd->period_size; |
214 | ssc_writex(params->ssc->regs, params->pdc->xnpr, |
215 | prtd->period_ptr); |
216 | ssc_writex(params->ssc->regs, params->pdc->xncr, |
217 | prtd->period_size / params->pdc_xfer_size); |
218 | |
219 | pr_debug("atmel-pcm: trigger: " |
220 | "period_ptr=%lx, xpr=%u, " |
221 | "xcr=%u, xnpr=%u, xncr=%u\n" , |
222 | (unsigned long)prtd->period_ptr, |
223 | ssc_readx(params->ssc->regs, params->pdc->xpr), |
224 | ssc_readx(params->ssc->regs, params->pdc->xcr), |
225 | ssc_readx(params->ssc->regs, params->pdc->xnpr), |
226 | ssc_readx(params->ssc->regs, params->pdc->xncr)); |
227 | |
228 | ssc_writex(params->ssc->regs, SSC_IER, |
229 | params->mask->ssc_endx | params->mask->ssc_endbuf); |
230 | ssc_writex(params->ssc->regs, SSC_PDC_PTCR, |
231 | params->mask->pdc_enable); |
232 | |
233 | pr_debug("sr=%u imr=%u\n" , |
234 | ssc_readx(params->ssc->regs, SSC_SR), |
235 | ssc_readx(params->ssc->regs, SSC_IER)); |
236 | break; /* SNDRV_PCM_TRIGGER_START */ |
237 | |
238 | case SNDRV_PCM_TRIGGER_STOP: |
239 | case SNDRV_PCM_TRIGGER_SUSPEND: |
240 | case SNDRV_PCM_TRIGGER_PAUSE_PUSH: |
241 | ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR, |
242 | params->mask->pdc_disable); |
243 | break; |
244 | |
245 | case SNDRV_PCM_TRIGGER_RESUME: |
246 | case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: |
247 | ssc_writex(params->ssc->regs, ATMEL_PDC_PTCR, |
248 | params->mask->pdc_enable); |
249 | break; |
250 | |
251 | default: |
252 | ret = -EINVAL; |
253 | } |
254 | |
255 | return ret; |
256 | } |
257 | |
258 | static snd_pcm_uframes_t atmel_pcm_pointer(struct snd_soc_component *component, |
259 | struct snd_pcm_substream *substream) |
260 | { |
261 | struct snd_pcm_runtime *runtime = substream->runtime; |
262 | struct atmel_runtime_data *prtd = runtime->private_data; |
263 | struct atmel_pcm_dma_params *params = prtd->params; |
264 | dma_addr_t ptr; |
265 | snd_pcm_uframes_t x; |
266 | |
267 | ptr = (dma_addr_t) ssc_readx(params->ssc->regs, params->pdc->xpr); |
268 | x = bytes_to_frames(runtime, size: ptr - prtd->dma_buffer); |
269 | |
270 | if (x == runtime->buffer_size) |
271 | x = 0; |
272 | |
273 | return x; |
274 | } |
275 | |
276 | static int atmel_pcm_open(struct snd_soc_component *component, |
277 | struct snd_pcm_substream *substream) |
278 | { |
279 | struct snd_pcm_runtime *runtime = substream->runtime; |
280 | struct atmel_runtime_data *prtd; |
281 | int ret = 0; |
282 | |
283 | snd_soc_set_runtime_hwparams(substream, hw: &atmel_pcm_hardware); |
284 | |
285 | /* ensure that buffer size is a multiple of period size */ |
286 | ret = snd_pcm_hw_constraint_integer(runtime, |
287 | SNDRV_PCM_HW_PARAM_PERIODS); |
288 | if (ret < 0) |
289 | goto out; |
290 | |
291 | prtd = kzalloc(size: sizeof(struct atmel_runtime_data), GFP_KERNEL); |
292 | if (prtd == NULL) { |
293 | ret = -ENOMEM; |
294 | goto out; |
295 | } |
296 | runtime->private_data = prtd; |
297 | |
298 | out: |
299 | return ret; |
300 | } |
301 | |
302 | static int atmel_pcm_close(struct snd_soc_component *component, |
303 | struct snd_pcm_substream *substream) |
304 | { |
305 | struct atmel_runtime_data *prtd = substream->runtime->private_data; |
306 | |
307 | kfree(objp: prtd); |
308 | return 0; |
309 | } |
310 | |
311 | static const struct snd_soc_component_driver atmel_soc_platform = { |
312 | .open = atmel_pcm_open, |
313 | .close = atmel_pcm_close, |
314 | .hw_params = atmel_pcm_hw_params, |
315 | .hw_free = atmel_pcm_hw_free, |
316 | .prepare = atmel_pcm_prepare, |
317 | .trigger = atmel_pcm_trigger, |
318 | .pointer = atmel_pcm_pointer, |
319 | .pcm_construct = atmel_pcm_new, |
320 | }; |
321 | |
322 | int atmel_pcm_pdc_platform_register(struct device *dev) |
323 | { |
324 | return devm_snd_soc_register_component(dev, component_driver: &atmel_soc_platform, |
325 | NULL, num_dai: 0); |
326 | } |
327 | EXPORT_SYMBOL(atmel_pcm_pdc_platform_register); |
328 | |
329 | MODULE_AUTHOR("Sedji Gaouaou <sedji.gaouaou@atmel.com>" ); |
330 | MODULE_DESCRIPTION("Atmel PCM module" ); |
331 | MODULE_LICENSE("GPL" ); |
332 | |