1// SPDX-License-Identifier: GPL-2.0
2/* Copyright 2011 Broadcom Corporation. All rights reserved. */
3
4#include <linux/interrupt.h>
5#include <linux/slab.h>
6
7#include <sound/asoundef.h>
8
9#include "bcm2835.h"
10
11/* hardware definition */
12static const struct snd_pcm_hardware snd_bcm2835_playback_hw = {
13 .info = (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER |
14 SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID |
15 SNDRV_PCM_INFO_SYNC_APPLPTR | SNDRV_PCM_INFO_BATCH),
16 .formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE,
17 .rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_192000,
18 .rate_min = 8000,
19 .rate_max = 192000,
20 .channels_min = 1,
21 .channels_max = 8,
22 .buffer_bytes_max = 512 * 1024,
23 .period_bytes_min = 1 * 1024,
24 .period_bytes_max = 512 * 1024,
25 .periods_min = 1,
26 .periods_max = 128,
27};
28
29static const struct snd_pcm_hardware snd_bcm2835_playback_spdif_hw = {
30 .info = (SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER |
31 SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID |
32 SNDRV_PCM_INFO_SYNC_APPLPTR | SNDRV_PCM_INFO_BATCH),
33 .formats = SNDRV_PCM_FMTBIT_S16_LE,
34 .rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_44100 |
35 SNDRV_PCM_RATE_48000,
36 .rate_min = 44100,
37 .rate_max = 48000,
38 .channels_min = 2,
39 .channels_max = 2,
40 .buffer_bytes_max = 128 * 1024,
41 .period_bytes_min = 1 * 1024,
42 .period_bytes_max = 128 * 1024,
43 .periods_min = 1,
44 .periods_max = 128,
45};
46
47static void snd_bcm2835_playback_free(struct snd_pcm_runtime *runtime)
48{
49 kfree(objp: runtime->private_data);
50}
51
52void bcm2835_playback_fifo(struct bcm2835_alsa_stream *alsa_stream,
53 unsigned int bytes)
54{
55 struct snd_pcm_substream *substream = alsa_stream->substream;
56 unsigned int pos;
57
58 if (!alsa_stream->period_size)
59 return;
60
61 if (bytes >= alsa_stream->buffer_size) {
62 snd_pcm_stream_lock(substream);
63 snd_pcm_stop(substream,
64 status: alsa_stream->draining ?
65 SNDRV_PCM_STATE_SETUP :
66 SNDRV_PCM_STATE_XRUN);
67 snd_pcm_stream_unlock(substream);
68 return;
69 }
70
71 pos = atomic_read(v: &alsa_stream->pos);
72 pos += bytes;
73 pos %= alsa_stream->buffer_size;
74 atomic_set(v: &alsa_stream->pos, i: pos);
75
76 alsa_stream->period_offset += bytes;
77 alsa_stream->interpolate_start = ktime_get();
78 if (alsa_stream->period_offset >= alsa_stream->period_size) {
79 alsa_stream->period_offset %= alsa_stream->period_size;
80 snd_pcm_period_elapsed(substream);
81 }
82}
83
84/* open callback */
85static int snd_bcm2835_playback_open_generic(struct snd_pcm_substream *substream, int spdif)
86{
87 struct bcm2835_chip *chip = snd_pcm_substream_chip(substream);
88 struct snd_pcm_runtime *runtime = substream->runtime;
89 struct bcm2835_alsa_stream *alsa_stream;
90 int idx;
91 int err;
92
93 mutex_lock(&chip->audio_mutex);
94 idx = substream->number;
95
96 if (spdif && chip->opened) {
97 err = -EBUSY;
98 goto out;
99 } else if (!spdif && (chip->opened & (1 << idx))) {
100 err = -EBUSY;
101 goto out;
102 }
103 if (idx >= MAX_SUBSTREAMS) {
104 dev_err(chip->dev,
105 "substream(%d) device doesn't exist max(%d) substreams allowed\n",
106 idx, MAX_SUBSTREAMS);
107 err = -ENODEV;
108 goto out;
109 }
110
111 alsa_stream = kzalloc(size: sizeof(*alsa_stream), GFP_KERNEL);
112 if (!alsa_stream) {
113 err = -ENOMEM;
114 goto out;
115 }
116
117 /* Initialise alsa_stream */
118 alsa_stream->chip = chip;
119 alsa_stream->substream = substream;
120 alsa_stream->idx = idx;
121
122 err = bcm2835_audio_open(alsa_stream);
123 if (err) {
124 kfree(objp: alsa_stream);
125 goto out;
126 }
127 runtime->private_data = alsa_stream;
128 runtime->private_free = snd_bcm2835_playback_free;
129 if (spdif) {
130 runtime->hw = snd_bcm2835_playback_spdif_hw;
131 } else {
132 /* clear spdif status, as we are not in spdif mode */
133 chip->spdif_status = 0;
134 runtime->hw = snd_bcm2835_playback_hw;
135 }
136 /* minimum 16 bytes alignment (for vchiq bulk transfers) */
137 snd_pcm_hw_constraint_step(runtime,
138 cond: 0,
139 SNDRV_PCM_HW_PARAM_PERIOD_BYTES,
140 step: 16);
141
142 /* position update is in 10ms order */
143 snd_pcm_hw_constraint_minmax(runtime,
144 SNDRV_PCM_HW_PARAM_PERIOD_TIME,
145 min: 10 * 1000, UINT_MAX);
146
147 chip->alsa_stream[idx] = alsa_stream;
148
149 chip->opened |= (1 << idx);
150
151out:
152 mutex_unlock(lock: &chip->audio_mutex);
153
154 return err;
155}
156
157static int snd_bcm2835_playback_open(struct snd_pcm_substream *substream)
158{
159 return snd_bcm2835_playback_open_generic(substream, spdif: 0);
160}
161
162static int snd_bcm2835_playback_spdif_open(struct snd_pcm_substream *substream)
163{
164 return snd_bcm2835_playback_open_generic(substream, spdif: 1);
165}
166
167static int snd_bcm2835_playback_close(struct snd_pcm_substream *substream)
168{
169 struct bcm2835_alsa_stream *alsa_stream;
170 struct snd_pcm_runtime *runtime;
171 struct bcm2835_chip *chip;
172
173 chip = snd_pcm_substream_chip(substream);
174 mutex_lock(&chip->audio_mutex);
175 runtime = substream->runtime;
176 alsa_stream = runtime->private_data;
177
178 alsa_stream->period_size = 0;
179 alsa_stream->buffer_size = 0;
180
181 bcm2835_audio_close(alsa_stream);
182 alsa_stream->chip->alsa_stream[alsa_stream->idx] = NULL;
183 /*
184 * Do not free up alsa_stream here, it will be freed up by
185 * runtime->private_free callback we registered in *_open above
186 */
187
188 chip->opened &= ~(1 << substream->number);
189
190 mutex_unlock(lock: &chip->audio_mutex);
191
192 return 0;
193}
194
195static int snd_bcm2835_pcm_prepare(struct snd_pcm_substream *substream)
196{
197 struct bcm2835_chip *chip = snd_pcm_substream_chip(substream);
198 struct snd_pcm_runtime *runtime = substream->runtime;
199 struct bcm2835_alsa_stream *alsa_stream = runtime->private_data;
200 int channels;
201 int err;
202
203 /* notify the vchiq that it should enter spdif passthrough mode by
204 * setting channels=0 (see
205 * https://github.com/raspberrypi/linux/issues/528)
206 */
207 if (chip->spdif_status & IEC958_AES0_NONAUDIO)
208 channels = 0;
209 else
210 channels = runtime->channels;
211
212 err = bcm2835_audio_set_params(alsa_stream, channels,
213 samplerate: runtime->rate,
214 bps: snd_pcm_format_width(format: runtime->format));
215 if (err < 0)
216 return err;
217
218 memset(&alsa_stream->pcm_indirect, 0, sizeof(alsa_stream->pcm_indirect));
219
220 alsa_stream->pcm_indirect.hw_buffer_size =
221 alsa_stream->pcm_indirect.sw_buffer_size =
222 snd_pcm_lib_buffer_bytes(substream);
223
224 alsa_stream->buffer_size = snd_pcm_lib_buffer_bytes(substream);
225 alsa_stream->period_size = snd_pcm_lib_period_bytes(substream);
226 atomic_set(v: &alsa_stream->pos, i: 0);
227 alsa_stream->period_offset = 0;
228 alsa_stream->draining = false;
229 alsa_stream->interpolate_start = ktime_get();
230
231 return 0;
232}
233
234static void snd_bcm2835_pcm_transfer(struct snd_pcm_substream *substream,
235 struct snd_pcm_indirect *rec, size_t bytes)
236{
237 struct snd_pcm_runtime *runtime = substream->runtime;
238 struct bcm2835_alsa_stream *alsa_stream = runtime->private_data;
239 void *src = (void *)(substream->runtime->dma_area + rec->sw_data);
240
241 bcm2835_audio_write(alsa_stream, count: bytes, src);
242}
243
244static int snd_bcm2835_pcm_ack(struct snd_pcm_substream *substream)
245{
246 struct snd_pcm_runtime *runtime = substream->runtime;
247 struct bcm2835_alsa_stream *alsa_stream = runtime->private_data;
248 struct snd_pcm_indirect *pcm_indirect = &alsa_stream->pcm_indirect;
249
250 return snd_pcm_indirect_playback_transfer(substream, rec: pcm_indirect,
251 copy: snd_bcm2835_pcm_transfer);
252}
253
254/* trigger callback */
255static int snd_bcm2835_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
256{
257 struct snd_pcm_runtime *runtime = substream->runtime;
258 struct bcm2835_alsa_stream *alsa_stream = runtime->private_data;
259
260 switch (cmd) {
261 case SNDRV_PCM_TRIGGER_START:
262 return bcm2835_audio_start(alsa_stream);
263 case SNDRV_PCM_TRIGGER_DRAIN:
264 alsa_stream->draining = true;
265 return bcm2835_audio_drain(alsa_stream);
266 case SNDRV_PCM_TRIGGER_STOP:
267 return bcm2835_audio_stop(alsa_stream);
268 default:
269 return -EINVAL;
270 }
271}
272
273/* pointer callback */
274static snd_pcm_uframes_t
275snd_bcm2835_pcm_pointer(struct snd_pcm_substream *substream)
276{
277 struct snd_pcm_runtime *runtime = substream->runtime;
278 struct bcm2835_alsa_stream *alsa_stream = runtime->private_data;
279 ktime_t now = ktime_get();
280
281 /* Give userspace better delay reporting by interpolating between GPU
282 * notifications, assuming audio speed is close enough to the clock
283 * used for ktime
284 */
285
286 if ((ktime_to_ns(kt: alsa_stream->interpolate_start)) &&
287 (ktime_compare(cmp1: alsa_stream->interpolate_start, cmp2: now) < 0)) {
288 u64 interval =
289 (ktime_to_ns(ktime_sub(now,
290 alsa_stream->interpolate_start)));
291 u64 frames_output_in_interval =
292 div_u64(dividend: (interval * runtime->rate), divisor: 1000000000);
293 snd_pcm_sframes_t frames_output_in_interval_sized =
294 -frames_output_in_interval;
295 runtime->delay = frames_output_in_interval_sized;
296 }
297
298 return snd_pcm_indirect_playback_pointer(substream,
299 rec: &alsa_stream->pcm_indirect,
300 ptr: atomic_read(v: &alsa_stream->pos));
301}
302
303/* operators */
304static const struct snd_pcm_ops snd_bcm2835_playback_ops = {
305 .open = snd_bcm2835_playback_open,
306 .close = snd_bcm2835_playback_close,
307 .prepare = snd_bcm2835_pcm_prepare,
308 .trigger = snd_bcm2835_pcm_trigger,
309 .pointer = snd_bcm2835_pcm_pointer,
310 .ack = snd_bcm2835_pcm_ack,
311};
312
313static const struct snd_pcm_ops snd_bcm2835_playback_spdif_ops = {
314 .open = snd_bcm2835_playback_spdif_open,
315 .close = snd_bcm2835_playback_close,
316 .prepare = snd_bcm2835_pcm_prepare,
317 .trigger = snd_bcm2835_pcm_trigger,
318 .pointer = snd_bcm2835_pcm_pointer,
319 .ack = snd_bcm2835_pcm_ack,
320};
321
322/* create a pcm device */
323int snd_bcm2835_new_pcm(struct bcm2835_chip *chip, const char *name,
324 int idx, enum snd_bcm2835_route route,
325 u32 numchannels, bool spdif)
326{
327 struct snd_pcm *pcm;
328 int err;
329
330 err = snd_pcm_new(card: chip->card, id: name, device: idx, playback_count: numchannels, capture_count: 0, rpcm: &pcm);
331 if (err)
332 return err;
333
334 pcm->private_data = chip;
335 pcm->nonatomic = true;
336 strscpy(pcm->name, name, sizeof(pcm->name));
337 if (!spdif) {
338 chip->dest = route;
339 chip->volume = 0;
340 chip->mute = CTRL_VOL_UNMUTE;
341 }
342
343 snd_pcm_set_ops(pcm, direction: SNDRV_PCM_STREAM_PLAYBACK,
344 ops: spdif ? &snd_bcm2835_playback_spdif_ops :
345 &snd_bcm2835_playback_ops);
346
347 snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV,
348 data: chip->card->dev, size: 128 * 1024, max: 128 * 1024);
349
350 if (spdif)
351 chip->pcm_spdif = pcm;
352 else
353 chip->pcm = pcm;
354 return 0;
355}
356

source code of linux/drivers/staging/vc04_services/bcm2835-audio/bcm2835-pcm.c