Commit | Line | Data |
---|---|---|
da607e19 | 1 | // SPDX-License-Identifier: GPL-2.0-only |
dd49b2d1 TS |
2 | /* |
3 | * motu-pcm.c - a part of driver for MOTU FireWire series | |
4 | * | |
5 | * Copyright (c) 2015-2017 Takashi Sakamoto <o-takashi@sakamocchi.jp> | |
dd49b2d1 TS |
6 | */ |
7 | ||
8 | #include <sound/pcm_params.h> | |
9 | #include "motu.h" | |
10 | ||
11 | static int motu_rate_constraint(struct snd_pcm_hw_params *params, | |
12 | struct snd_pcm_hw_rule *rule) | |
13 | { | |
14 | struct snd_motu_packet_format *formats = rule->private; | |
15 | ||
16 | const struct snd_interval *c = | |
17 | hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_CHANNELS); | |
18 | struct snd_interval *r = | |
19 | hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); | |
20 | struct snd_interval rates = { | |
21 | .min = UINT_MAX, .max = 0, .integer = 1 | |
22 | }; | |
23 | unsigned int i, pcm_channels, rate, mode; | |
24 | ||
25 | for (i = 0; i < ARRAY_SIZE(snd_motu_clock_rates); ++i) { | |
26 | rate = snd_motu_clock_rates[i]; | |
27 | mode = i / 2; | |
28 | ||
29 | pcm_channels = formats->fixed_part_pcm_chunks[mode] + | |
30 | formats->differed_part_pcm_chunks[mode]; | |
31 | if (!snd_interval_test(c, pcm_channels)) | |
32 | continue; | |
33 | ||
34 | rates.min = min(rates.min, rate); | |
35 | rates.max = max(rates.max, rate); | |
36 | } | |
37 | ||
38 | return snd_interval_refine(r, &rates); | |
39 | } | |
40 | ||
41 | static int motu_channels_constraint(struct snd_pcm_hw_params *params, | |
42 | struct snd_pcm_hw_rule *rule) | |
43 | { | |
44 | struct snd_motu_packet_format *formats = rule->private; | |
45 | ||
46 | const struct snd_interval *r = | |
47 | hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_RATE); | |
48 | struct snd_interval *c = | |
49 | hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); | |
50 | struct snd_interval channels = { | |
51 | .min = UINT_MAX, .max = 0, .integer = 1 | |
52 | }; | |
53 | unsigned int i, pcm_channels, rate, mode; | |
54 | ||
55 | for (i = 0; i < ARRAY_SIZE(snd_motu_clock_rates); ++i) { | |
56 | rate = snd_motu_clock_rates[i]; | |
57 | mode = i / 2; | |
58 | ||
59 | if (!snd_interval_test(r, rate)) | |
60 | continue; | |
61 | ||
62 | pcm_channels = formats->fixed_part_pcm_chunks[mode] + | |
63 | formats->differed_part_pcm_chunks[mode]; | |
64 | channels.min = min(channels.min, pcm_channels); | |
65 | channels.max = max(channels.max, pcm_channels); | |
66 | } | |
67 | ||
68 | return snd_interval_refine(c, &channels); | |
69 | } | |
70 | ||
71 | static void limit_channels_and_rates(struct snd_motu *motu, | |
72 | struct snd_pcm_runtime *runtime, | |
73 | struct snd_motu_packet_format *formats) | |
74 | { | |
75 | struct snd_pcm_hardware *hw = &runtime->hw; | |
76 | unsigned int i, pcm_channels, rate, mode; | |
77 | ||
78 | hw->channels_min = UINT_MAX; | |
79 | hw->channels_max = 0; | |
80 | ||
81 | for (i = 0; i < ARRAY_SIZE(snd_motu_clock_rates); ++i) { | |
82 | rate = snd_motu_clock_rates[i]; | |
83 | mode = i / 2; | |
84 | ||
85 | pcm_channels = formats->fixed_part_pcm_chunks[mode] + | |
86 | formats->differed_part_pcm_chunks[mode]; | |
87 | if (pcm_channels == 0) | |
88 | continue; | |
89 | ||
90 | hw->rates |= snd_pcm_rate_to_rate_bit(rate); | |
91 | hw->channels_min = min(hw->channels_min, pcm_channels); | |
92 | hw->channels_max = max(hw->channels_max, pcm_channels); | |
93 | } | |
94 | ||
95 | snd_pcm_limit_hw_rates(runtime); | |
96 | } | |
97 | ||
dd49b2d1 TS |
98 | static int init_hw_info(struct snd_motu *motu, |
99 | struct snd_pcm_substream *substream) | |
100 | { | |
101 | struct snd_pcm_runtime *runtime = substream->runtime; | |
102 | struct snd_pcm_hardware *hw = &runtime->hw; | |
103 | struct amdtp_stream *stream; | |
104 | struct snd_motu_packet_format *formats; | |
105 | int err; | |
106 | ||
dd49b2d1 TS |
107 | if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { |
108 | hw->formats = SNDRV_PCM_FMTBIT_S32; | |
109 | stream = &motu->tx_stream; | |
110 | formats = &motu->tx_packet_formats; | |
111 | } else { | |
112 | hw->formats = SNDRV_PCM_FMTBIT_S32; | |
113 | stream = &motu->rx_stream; | |
114 | formats = &motu->rx_packet_formats; | |
115 | } | |
116 | ||
117 | limit_channels_and_rates(motu, runtime, formats); | |
dd49b2d1 TS |
118 | |
119 | err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, | |
120 | motu_rate_constraint, formats, | |
121 | SNDRV_PCM_HW_PARAM_CHANNELS, -1); | |
122 | if (err < 0) | |
123 | return err; | |
124 | err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, | |
125 | motu_channels_constraint, formats, | |
126 | SNDRV_PCM_HW_PARAM_RATE, -1); | |
127 | if (err < 0) | |
128 | return err; | |
129 | ||
130 | return amdtp_motu_add_pcm_hw_constraints(stream, runtime); | |
131 | } | |
132 | ||
133 | static int pcm_open(struct snd_pcm_substream *substream) | |
134 | { | |
135 | struct snd_motu *motu = substream->private_data; | |
136 | const struct snd_motu_protocol *const protocol = motu->spec->protocol; | |
3fd80b20 | 137 | struct amdtp_domain *d = &motu->domain; |
dd49b2d1 | 138 | enum snd_motu_clock_source src; |
dd49b2d1 TS |
139 | int err; |
140 | ||
71c37977 TS |
141 | err = snd_motu_stream_lock_try(motu); |
142 | if (err < 0) | |
143 | return err; | |
144 | ||
dd49b2d1 TS |
145 | mutex_lock(&motu->mutex); |
146 | ||
8b460c76 | 147 | err = snd_motu_stream_cache_packet_formats(motu); |
dd49b2d1 | 148 | if (err < 0) |
71c37977 | 149 | goto err_locked; |
dd49b2d1 TS |
150 | |
151 | err = init_hw_info(motu, substream); | |
152 | if (err < 0) | |
71c37977 | 153 | goto err_locked; |
dd49b2d1 | 154 | |
dd49b2d1 TS |
155 | err = protocol->get_clock_source(motu, &src); |
156 | if (err < 0) | |
71c37977 | 157 | goto err_locked; |
3fd80b20 TS |
158 | |
159 | // When source of clock is not internal or any stream is reserved for | |
160 | // transmission of PCM frames, the available sampling rate is limited | |
161 | // at current one. | |
3f58f004 TS |
162 | if ((src != SND_MOTU_CLOCK_SOURCE_INTERNAL && |
163 | src != SND_MOTU_CLOCK_SOURCE_SPH) || | |
3fd80b20 TS |
164 | (motu->substreams_counter > 0 && d->events_per_period > 0)) { |
165 | unsigned int frames_per_period = d->events_per_period; | |
0f5482e7 | 166 | unsigned int frames_per_buffer = d->events_per_buffer; |
3fd80b20 TS |
167 | unsigned int rate; |
168 | ||
dd49b2d1 TS |
169 | err = protocol->get_clock_rate(motu, &rate); |
170 | if (err < 0) | |
71c37977 | 171 | goto err_locked; |
3fd80b20 | 172 | |
dd49b2d1 TS |
173 | substream->runtime->hw.rate_min = rate; |
174 | substream->runtime->hw.rate_max = rate; | |
3fd80b20 TS |
175 | |
176 | if (frames_per_period > 0) { | |
177 | err = snd_pcm_hw_constraint_minmax(substream->runtime, | |
178 | SNDRV_PCM_HW_PARAM_PERIOD_SIZE, | |
179 | frames_per_period, frames_per_period); | |
180 | if (err < 0) { | |
181 | mutex_unlock(&motu->mutex); | |
182 | goto err_locked; | |
183 | } | |
0f5482e7 TS |
184 | |
185 | err = snd_pcm_hw_constraint_minmax(substream->runtime, | |
186 | SNDRV_PCM_HW_PARAM_BUFFER_SIZE, | |
187 | frames_per_buffer, frames_per_buffer); | |
188 | if (err < 0) { | |
189 | mutex_unlock(&motu->mutex); | |
190 | goto err_locked; | |
191 | } | |
3fd80b20 | 192 | } |
dd49b2d1 TS |
193 | } |
194 | ||
195 | snd_pcm_set_sync(substream); | |
196 | ||
197 | mutex_unlock(&motu->mutex); | |
198 | ||
3fd80b20 | 199 | return 0; |
71c37977 TS |
200 | err_locked: |
201 | mutex_unlock(&motu->mutex); | |
202 | snd_motu_stream_lock_release(motu); | |
dd49b2d1 TS |
203 | return err; |
204 | } | |
205 | ||
206 | static int pcm_close(struct snd_pcm_substream *substream) | |
207 | { | |
71c37977 TS |
208 | struct snd_motu *motu = substream->private_data; |
209 | ||
210 | snd_motu_stream_lock_release(motu); | |
211 | ||
dd49b2d1 TS |
212 | return 0; |
213 | } | |
214 | ||
15d472ec TS |
215 | static int pcm_hw_params(struct snd_pcm_substream *substream, |
216 | struct snd_pcm_hw_params *hw_params) | |
dd49b2d1 TS |
217 | { |
218 | struct snd_motu *motu = substream->private_data; | |
219 | int err; | |
220 | ||
221 | err = snd_pcm_lib_alloc_vmalloc_buffer(substream, | |
222 | params_buffer_bytes(hw_params)); | |
223 | if (err < 0) | |
224 | return err; | |
225 | ||
226 | if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) { | |
8edc56ec | 227 | unsigned int rate = params_rate(hw_params); |
0d39cd0e | 228 | unsigned int frames_per_period = params_period_size(hw_params); |
0f5482e7 | 229 | unsigned int frames_per_buffer = params_buffer_size(hw_params); |
8edc56ec | 230 | |
dd49b2d1 | 231 | mutex_lock(&motu->mutex); |
0d39cd0e | 232 | err = snd_motu_stream_reserve_duplex(motu, rate, |
0f5482e7 | 233 | frames_per_period, frames_per_buffer); |
8edc56ec TS |
234 | if (err >= 0) |
235 | ++motu->substreams_counter; | |
dd49b2d1 TS |
236 | mutex_unlock(&motu->mutex); |
237 | } | |
238 | ||
8edc56ec | 239 | return err; |
dd49b2d1 | 240 | } |
dd49b2d1 | 241 | |
15d472ec | 242 | static int pcm_hw_free(struct snd_pcm_substream *substream) |
dd49b2d1 TS |
243 | { |
244 | struct snd_motu *motu = substream->private_data; | |
245 | ||
246 | mutex_lock(&motu->mutex); | |
247 | ||
248 | if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN) | |
8edc56ec | 249 | --motu->substreams_counter; |
dd49b2d1 TS |
250 | |
251 | snd_motu_stream_stop_duplex(motu); | |
252 | ||
253 | mutex_unlock(&motu->mutex); | |
254 | ||
255 | return snd_pcm_lib_free_vmalloc_buffer(substream); | |
256 | } | |
257 | ||
258 | static int capture_prepare(struct snd_pcm_substream *substream) | |
259 | { | |
260 | struct snd_motu *motu = substream->private_data; | |
261 | int err; | |
262 | ||
263 | mutex_lock(&motu->mutex); | |
8edc56ec | 264 | err = snd_motu_stream_start_duplex(motu); |
dd49b2d1 TS |
265 | mutex_unlock(&motu->mutex); |
266 | if (err >= 0) | |
267 | amdtp_stream_pcm_prepare(&motu->tx_stream); | |
268 | ||
269 | return 0; | |
270 | } | |
271 | static int playback_prepare(struct snd_pcm_substream *substream) | |
272 | { | |
273 | struct snd_motu *motu = substream->private_data; | |
274 | int err; | |
275 | ||
276 | mutex_lock(&motu->mutex); | |
8edc56ec | 277 | err = snd_motu_stream_start_duplex(motu); |
dd49b2d1 TS |
278 | mutex_unlock(&motu->mutex); |
279 | if (err >= 0) | |
280 | amdtp_stream_pcm_prepare(&motu->rx_stream); | |
281 | ||
282 | return err; | |
283 | } | |
284 | ||
285 | static int capture_trigger(struct snd_pcm_substream *substream, int cmd) | |
286 | { | |
287 | struct snd_motu *motu = substream->private_data; | |
288 | ||
289 | switch (cmd) { | |
290 | case SNDRV_PCM_TRIGGER_START: | |
291 | amdtp_stream_pcm_trigger(&motu->tx_stream, substream); | |
292 | break; | |
293 | case SNDRV_PCM_TRIGGER_STOP: | |
294 | amdtp_stream_pcm_trigger(&motu->tx_stream, NULL); | |
295 | break; | |
296 | default: | |
297 | return -EINVAL; | |
298 | } | |
299 | ||
300 | return 0; | |
301 | } | |
302 | static int playback_trigger(struct snd_pcm_substream *substream, int cmd) | |
303 | { | |
304 | struct snd_motu *motu = substream->private_data; | |
305 | ||
306 | switch (cmd) { | |
307 | case SNDRV_PCM_TRIGGER_START: | |
308 | amdtp_stream_pcm_trigger(&motu->rx_stream, substream); | |
309 | break; | |
310 | case SNDRV_PCM_TRIGGER_STOP: | |
311 | amdtp_stream_pcm_trigger(&motu->rx_stream, NULL); | |
312 | break; | |
313 | default: | |
314 | return -EINVAL; | |
315 | } | |
316 | ||
317 | return 0; | |
318 | } | |
319 | ||
320 | static snd_pcm_uframes_t capture_pointer(struct snd_pcm_substream *substream) | |
321 | { | |
322 | struct snd_motu *motu = substream->private_data; | |
323 | ||
f890f9a0 | 324 | return amdtp_domain_stream_pcm_pointer(&motu->domain, &motu->tx_stream); |
dd49b2d1 TS |
325 | } |
326 | static snd_pcm_uframes_t playback_pointer(struct snd_pcm_substream *substream) | |
327 | { | |
328 | struct snd_motu *motu = substream->private_data; | |
329 | ||
f890f9a0 | 330 | return amdtp_domain_stream_pcm_pointer(&motu->domain, &motu->rx_stream); |
dd49b2d1 TS |
331 | } |
332 | ||
875becf8 TS |
333 | static int capture_ack(struct snd_pcm_substream *substream) |
334 | { | |
335 | struct snd_motu *motu = substream->private_data; | |
336 | ||
e6dcc92f | 337 | return amdtp_domain_stream_pcm_ack(&motu->domain, &motu->tx_stream); |
875becf8 TS |
338 | } |
339 | ||
340 | static int playback_ack(struct snd_pcm_substream *substream) | |
341 | { | |
342 | struct snd_motu *motu = substream->private_data; | |
343 | ||
e6dcc92f | 344 | return amdtp_domain_stream_pcm_ack(&motu->domain, &motu->rx_stream); |
875becf8 TS |
345 | } |
346 | ||
dd49b2d1 TS |
347 | int snd_motu_create_pcm_devices(struct snd_motu *motu) |
348 | { | |
b2165f38 | 349 | static const struct snd_pcm_ops capture_ops = { |
dd49b2d1 TS |
350 | .open = pcm_open, |
351 | .close = pcm_close, | |
352 | .ioctl = snd_pcm_lib_ioctl, | |
15d472ec TS |
353 | .hw_params = pcm_hw_params, |
354 | .hw_free = pcm_hw_free, | |
dd49b2d1 TS |
355 | .prepare = capture_prepare, |
356 | .trigger = capture_trigger, | |
357 | .pointer = capture_pointer, | |
875becf8 | 358 | .ack = capture_ack, |
dd49b2d1 | 359 | .page = snd_pcm_lib_get_vmalloc_page, |
dd49b2d1 | 360 | }; |
b2165f38 | 361 | static const struct snd_pcm_ops playback_ops = { |
dd49b2d1 TS |
362 | .open = pcm_open, |
363 | .close = pcm_close, | |
364 | .ioctl = snd_pcm_lib_ioctl, | |
15d472ec TS |
365 | .hw_params = pcm_hw_params, |
366 | .hw_free = pcm_hw_free, | |
dd49b2d1 TS |
367 | .prepare = playback_prepare, |
368 | .trigger = playback_trigger, | |
369 | .pointer = playback_pointer, | |
875becf8 | 370 | .ack = playback_ack, |
dd49b2d1 | 371 | .page = snd_pcm_lib_get_vmalloc_page, |
dd49b2d1 TS |
372 | }; |
373 | struct snd_pcm *pcm; | |
374 | int err; | |
375 | ||
376 | err = snd_pcm_new(motu->card, motu->card->driver, 0, 1, 1, &pcm); | |
377 | if (err < 0) | |
378 | return err; | |
379 | pcm->private_data = motu; | |
380 | strcpy(pcm->name, motu->card->shortname); | |
381 | ||
382 | snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &capture_ops); | |
383 | snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &playback_ops); | |
384 | ||
385 | return 0; | |
386 | } |