Commit | Line | Data |
---|---|---|
da607e19 | 1 | // SPDX-License-Identifier: GPL-2.0-only |
3a2a1797 TS |
2 | /* |
3 | * digi00x-stream.c - a part of driver for Digidesign Digi 002/003 family | |
4 | * | |
5 | * Copyright (c) 2014-2015 Takashi Sakamoto | |
3a2a1797 TS |
6 | */ |
7 | ||
8 | #include "digi00x.h" | |
9 | ||
10 | #define CALLBACK_TIMEOUT 500 | |
11 | ||
12 | const unsigned int snd_dg00x_stream_rates[SND_DG00X_RATE_COUNT] = { | |
13 | [SND_DG00X_RATE_44100] = 44100, | |
14 | [SND_DG00X_RATE_48000] = 48000, | |
15 | [SND_DG00X_RATE_88200] = 88200, | |
16 | [SND_DG00X_RATE_96000] = 96000, | |
17 | }; | |
18 | ||
19 | /* Multi Bit Linear Audio data channels for each sampling transfer frequency. */ | |
20 | const unsigned int | |
21 | snd_dg00x_stream_pcm_channels[SND_DG00X_RATE_COUNT] = { | |
22 | /* Analog/ADAT/SPDIF */ | |
23 | [SND_DG00X_RATE_44100] = (8 + 8 + 2), | |
24 | [SND_DG00X_RATE_48000] = (8 + 8 + 2), | |
25 | /* Analog/SPDIF */ | |
26 | [SND_DG00X_RATE_88200] = (8 + 2), | |
27 | [SND_DG00X_RATE_96000] = (8 + 2), | |
28 | }; | |
29 | ||
30 | int snd_dg00x_stream_get_local_rate(struct snd_dg00x *dg00x, unsigned int *rate) | |
31 | { | |
32 | u32 data; | |
33 | __be32 reg; | |
34 | int err; | |
35 | ||
36 | err = snd_fw_transaction(dg00x->unit, TCODE_READ_QUADLET_REQUEST, | |
37 | DG00X_ADDR_BASE + DG00X_OFFSET_LOCAL_RATE, | |
38 | ®, sizeof(reg), 0); | |
39 | if (err < 0) | |
40 | return err; | |
41 | ||
42 | data = be32_to_cpu(reg) & 0x0f; | |
43 | if (data < ARRAY_SIZE(snd_dg00x_stream_rates)) | |
44 | *rate = snd_dg00x_stream_rates[data]; | |
45 | else | |
46 | err = -EIO; | |
47 | ||
48 | return err; | |
49 | } | |
50 | ||
51 | int snd_dg00x_stream_set_local_rate(struct snd_dg00x *dg00x, unsigned int rate) | |
52 | { | |
53 | __be32 reg; | |
54 | unsigned int i; | |
55 | ||
56 | for (i = 0; i < ARRAY_SIZE(snd_dg00x_stream_rates); i++) { | |
57 | if (rate == snd_dg00x_stream_rates[i]) | |
58 | break; | |
59 | } | |
60 | if (i == ARRAY_SIZE(snd_dg00x_stream_rates)) | |
61 | return -EINVAL; | |
62 | ||
63 | reg = cpu_to_be32(i); | |
64 | return snd_fw_transaction(dg00x->unit, TCODE_WRITE_QUADLET_REQUEST, | |
65 | DG00X_ADDR_BASE + DG00X_OFFSET_LOCAL_RATE, | |
66 | ®, sizeof(reg), 0); | |
67 | } | |
68 | ||
69 | int snd_dg00x_stream_get_clock(struct snd_dg00x *dg00x, | |
70 | enum snd_dg00x_clock *clock) | |
71 | { | |
72 | __be32 reg; | |
73 | int err; | |
74 | ||
75 | err = snd_fw_transaction(dg00x->unit, TCODE_READ_QUADLET_REQUEST, | |
76 | DG00X_ADDR_BASE + DG00X_OFFSET_CLOCK_SOURCE, | |
77 | ®, sizeof(reg), 0); | |
78 | if (err < 0) | |
79 | return err; | |
80 | ||
81 | *clock = be32_to_cpu(reg) & 0x0f; | |
82 | if (*clock >= SND_DG00X_CLOCK_COUNT) | |
83 | err = -EIO; | |
84 | ||
85 | return err; | |
86 | } | |
87 | ||
88 | int snd_dg00x_stream_check_external_clock(struct snd_dg00x *dg00x, bool *detect) | |
89 | { | |
90 | __be32 reg; | |
91 | int err; | |
92 | ||
93 | err = snd_fw_transaction(dg00x->unit, TCODE_READ_QUADLET_REQUEST, | |
94 | DG00X_ADDR_BASE + DG00X_OFFSET_DETECT_EXTERNAL, | |
95 | ®, sizeof(reg), 0); | |
96 | if (err >= 0) | |
97 | *detect = be32_to_cpu(reg) > 0; | |
98 | ||
99 | return err; | |
100 | } | |
101 | ||
102 | int snd_dg00x_stream_get_external_rate(struct snd_dg00x *dg00x, | |
103 | unsigned int *rate) | |
104 | { | |
105 | u32 data; | |
106 | __be32 reg; | |
107 | int err; | |
108 | ||
109 | err = snd_fw_transaction(dg00x->unit, TCODE_READ_QUADLET_REQUEST, | |
110 | DG00X_ADDR_BASE + DG00X_OFFSET_EXTERNAL_RATE, | |
111 | ®, sizeof(reg), 0); | |
112 | if (err < 0) | |
113 | return err; | |
114 | ||
115 | data = be32_to_cpu(reg) & 0x0f; | |
116 | if (data < ARRAY_SIZE(snd_dg00x_stream_rates)) | |
117 | *rate = snd_dg00x_stream_rates[data]; | |
118 | /* This means desync. */ | |
119 | else | |
120 | err = -EBUSY; | |
121 | ||
122 | return err; | |
123 | } | |
124 | ||
125 | static void finish_session(struct snd_dg00x *dg00x) | |
126 | { | |
d18b0a6e TS |
127 | __be32 data; |
128 | ||
d18b0a6e | 129 | data = cpu_to_be32(0x00000003); |
3a2a1797 TS |
130 | snd_fw_transaction(dg00x->unit, TCODE_WRITE_QUADLET_REQUEST, |
131 | DG00X_ADDR_BASE + DG00X_OFFSET_STREAMING_SET, | |
132 | &data, sizeof(data), 0); | |
6bc93229 TS |
133 | |
134 | // Unregister isochronous channels for both direction. | |
135 | data = 0; | |
136 | snd_fw_transaction(dg00x->unit, TCODE_WRITE_QUADLET_REQUEST, | |
137 | DG00X_ADDR_BASE + DG00X_OFFSET_ISOC_CHANNELS, | |
138 | &data, sizeof(data), 0); | |
d18b0a6e TS |
139 | |
140 | // Just after finishing the session, the device may lost transmitting | |
141 | // functionality for a short time. | |
142 | msleep(50); | |
3a2a1797 TS |
143 | } |
144 | ||
145 | static int begin_session(struct snd_dg00x *dg00x) | |
146 | { | |
147 | __be32 data; | |
148 | u32 curr; | |
149 | int err; | |
150 | ||
6bc93229 TS |
151 | // Register isochronous channels for both direction. |
152 | data = cpu_to_be32((dg00x->tx_resources.channel << 16) | | |
153 | dg00x->rx_resources.channel); | |
154 | err = snd_fw_transaction(dg00x->unit, TCODE_WRITE_QUADLET_REQUEST, | |
155 | DG00X_ADDR_BASE + DG00X_OFFSET_ISOC_CHANNELS, | |
156 | &data, sizeof(data), 0); | |
157 | if (err < 0) | |
638e19fc | 158 | return err; |
6bc93229 | 159 | |
3a2a1797 TS |
160 | err = snd_fw_transaction(dg00x->unit, TCODE_READ_QUADLET_REQUEST, |
161 | DG00X_ADDR_BASE + DG00X_OFFSET_STREAMING_STATE, | |
162 | &data, sizeof(data), 0); | |
163 | if (err < 0) | |
638e19fc | 164 | return err; |
3a2a1797 TS |
165 | curr = be32_to_cpu(data); |
166 | ||
167 | if (curr == 0) | |
168 | curr = 2; | |
169 | ||
170 | curr--; | |
171 | while (curr > 0) { | |
172 | data = cpu_to_be32(curr); | |
173 | err = snd_fw_transaction(dg00x->unit, | |
174 | TCODE_WRITE_QUADLET_REQUEST, | |
175 | DG00X_ADDR_BASE + | |
176 | DG00X_OFFSET_STREAMING_SET, | |
177 | &data, sizeof(data), 0); | |
178 | if (err < 0) | |
638e19fc | 179 | break; |
3a2a1797 TS |
180 | |
181 | msleep(20); | |
182 | curr--; | |
183 | } | |
184 | ||
3a2a1797 TS |
185 | return err; |
186 | } | |
187 | ||
ad306505 TS |
188 | static int keep_resources(struct snd_dg00x *dg00x, struct amdtp_stream *stream, |
189 | unsigned int rate) | |
3a2a1797 | 190 | { |
ad306505 TS |
191 | struct fw_iso_resources *resources; |
192 | int i; | |
3a2a1797 TS |
193 | int err; |
194 | ||
ad306505 | 195 | // Check sampling rate. |
3a2a1797 TS |
196 | for (i = 0; i < SND_DG00X_RATE_COUNT; i++) { |
197 | if (snd_dg00x_stream_rates[i] == rate) | |
198 | break; | |
199 | } | |
200 | if (i == SND_DG00X_RATE_COUNT) | |
201 | return -EINVAL; | |
202 | ||
ad306505 TS |
203 | if (stream == &dg00x->tx_stream) |
204 | resources = &dg00x->tx_resources; | |
205 | else | |
206 | resources = &dg00x->rx_resources; | |
3a2a1797 | 207 | |
ad306505 | 208 | err = amdtp_dot_set_parameters(stream, rate, |
9dc5d31c | 209 | snd_dg00x_stream_pcm_channels[i]); |
3a2a1797 TS |
210 | if (err < 0) |
211 | return err; | |
3a2a1797 | 212 | |
ad306505 TS |
213 | return fw_iso_resources_allocate(resources, |
214 | amdtp_stream_get_max_payload(stream), | |
215 | fw_parent_device(dg00x->unit)->max_speed); | |
3a2a1797 TS |
216 | } |
217 | ||
d79360eb | 218 | static int init_stream(struct snd_dg00x *dg00x, struct amdtp_stream *s) |
3a2a1797 | 219 | { |
d79360eb TS |
220 | struct fw_iso_resources *resources; |
221 | enum amdtp_stream_direction dir; | |
3a2a1797 TS |
222 | int err; |
223 | ||
d79360eb TS |
224 | if (s == &dg00x->tx_stream) { |
225 | resources = &dg00x->tx_resources; | |
226 | dir = AMDTP_IN_STREAM; | |
227 | } else { | |
228 | resources = &dg00x->rx_resources; | |
229 | dir = AMDTP_OUT_STREAM; | |
230 | } | |
231 | ||
232 | err = fw_iso_resources_init(resources, dg00x->unit); | |
3a2a1797 | 233 | if (err < 0) |
d79360eb TS |
234 | return err; |
235 | ||
236 | err = amdtp_dot_init(s, dg00x->unit, dir); | |
3a2a1797 | 237 | if (err < 0) |
d79360eb TS |
238 | fw_iso_resources_destroy(resources); |
239 | ||
240 | return err; | |
241 | } | |
242 | ||
243 | static void destroy_stream(struct snd_dg00x *dg00x, struct amdtp_stream *s) | |
244 | { | |
245 | amdtp_stream_destroy(s); | |
3a2a1797 | 246 | |
d79360eb TS |
247 | if (s == &dg00x->tx_stream) |
248 | fw_iso_resources_destroy(&dg00x->tx_resources); | |
249 | else | |
250 | fw_iso_resources_destroy(&dg00x->rx_resources); | |
251 | } | |
252 | ||
253 | int snd_dg00x_stream_init_duplex(struct snd_dg00x *dg00x) | |
254 | { | |
255 | int err; | |
256 | ||
257 | err = init_stream(dg00x, &dg00x->rx_stream); | |
3a2a1797 | 258 | if (err < 0) |
d79360eb TS |
259 | return err; |
260 | ||
261 | err = init_stream(dg00x, &dg00x->tx_stream); | |
3a2a1797 | 262 | if (err < 0) |
d79360eb | 263 | destroy_stream(dg00x, &dg00x->rx_stream); |
3a2a1797 | 264 | |
9a08067e TS |
265 | err = amdtp_domain_init(&dg00x->domain); |
266 | if (err < 0) { | |
267 | destroy_stream(dg00x, &dg00x->rx_stream); | |
268 | destroy_stream(dg00x, &dg00x->tx_stream); | |
269 | } | |
270 | ||
3a2a1797 TS |
271 | return err; |
272 | } | |
273 | ||
9a08067e TS |
274 | /* |
275 | * This function should be called before starting streams or after stopping | |
276 | * streams. | |
277 | */ | |
3a2a1797 TS |
278 | void snd_dg00x_stream_destroy_duplex(struct snd_dg00x *dg00x) |
279 | { | |
9a08067e TS |
280 | amdtp_domain_destroy(&dg00x->domain); |
281 | ||
d79360eb TS |
282 | destroy_stream(dg00x, &dg00x->rx_stream); |
283 | destroy_stream(dg00x, &dg00x->tx_stream); | |
3a2a1797 TS |
284 | } |
285 | ||
18b7f18f TS |
286 | int snd_dg00x_stream_reserve_duplex(struct snd_dg00x *dg00x, unsigned int rate, |
287 | unsigned int frames_per_period) | |
3a2a1797 TS |
288 | { |
289 | unsigned int curr_rate; | |
ae8ffbb2 | 290 | int err; |
3a2a1797 | 291 | |
3a2a1797 TS |
292 | err = snd_dg00x_stream_get_local_rate(dg00x, &curr_rate); |
293 | if (err < 0) | |
ae8ffbb2 | 294 | return err; |
9fbfd38b TS |
295 | if (rate == 0) |
296 | rate = curr_rate; | |
ae8ffbb2 TS |
297 | |
298 | if (dg00x->substreams_counter == 0 || curr_rate != rate) { | |
9a08067e TS |
299 | amdtp_domain_stop(&dg00x->domain); |
300 | ||
3a2a1797 TS |
301 | finish_session(dg00x); |
302 | ||
ae8ffbb2 TS |
303 | fw_iso_resources_free(&dg00x->tx_resources); |
304 | fw_iso_resources_free(&dg00x->rx_resources); | |
3a2a1797 | 305 | |
3a2a1797 TS |
306 | err = snd_dg00x_stream_set_local_rate(dg00x, rate); |
307 | if (err < 0) | |
ae8ffbb2 | 308 | return err; |
3a2a1797 | 309 | |
ad306505 TS |
310 | err = keep_resources(dg00x, &dg00x->rx_stream, rate); |
311 | if (err < 0) | |
ae8ffbb2 | 312 | return err; |
ad306505 TS |
313 | |
314 | err = keep_resources(dg00x, &dg00x->tx_stream, rate); | |
ae8ffbb2 TS |
315 | if (err < 0) { |
316 | fw_iso_resources_free(&dg00x->rx_resources); | |
317 | return err; | |
318 | } | |
18b7f18f TS |
319 | |
320 | err = amdtp_domain_set_events_per_period(&dg00x->domain, | |
321 | frames_per_period); | |
322 | if (err < 0) { | |
323 | fw_iso_resources_free(&dg00x->rx_resources); | |
324 | fw_iso_resources_free(&dg00x->tx_resources); | |
325 | return err; | |
326 | } | |
ae8ffbb2 TS |
327 | } |
328 | ||
329 | return 0; | |
330 | } | |
3a2a1797 | 331 | |
ae8ffbb2 TS |
332 | int snd_dg00x_stream_start_duplex(struct snd_dg00x *dg00x) |
333 | { | |
24bb77b3 | 334 | unsigned int generation = dg00x->rx_resources.generation; |
ae8ffbb2 TS |
335 | int err = 0; |
336 | ||
337 | if (dg00x->substreams_counter == 0) | |
338 | return 0; | |
339 | ||
340 | if (amdtp_streaming_error(&dg00x->tx_stream) || | |
9a08067e TS |
341 | amdtp_streaming_error(&dg00x->rx_stream)) { |
342 | amdtp_domain_stop(&dg00x->domain); | |
ae8ffbb2 | 343 | finish_session(dg00x); |
9a08067e | 344 | } |
ae8ffbb2 | 345 | |
24bb77b3 TS |
346 | if (generation != fw_parent_device(dg00x->unit)->card->generation) { |
347 | err = fw_iso_resources_update(&dg00x->tx_resources); | |
348 | if (err < 0) | |
349 | goto error; | |
350 | ||
351 | err = fw_iso_resources_update(&dg00x->rx_resources); | |
352 | if (err < 0) | |
353 | goto error; | |
354 | } | |
355 | ||
ae8ffbb2 TS |
356 | /* |
357 | * No packets are transmitted without receiving packets, reagardless of | |
358 | * which source of clock is used. | |
359 | */ | |
360 | if (!amdtp_stream_running(&dg00x->rx_stream)) { | |
9a08067e TS |
361 | int spd = fw_parent_device(dg00x->unit)->max_speed; |
362 | ||
3a2a1797 TS |
363 | err = begin_session(dg00x); |
364 | if (err < 0) | |
365 | goto error; | |
366 | ||
9a08067e TS |
367 | err = amdtp_domain_add_stream(&dg00x->domain, &dg00x->rx_stream, |
368 | dg00x->rx_resources.channel, spd); | |
3a2a1797 TS |
369 | if (err < 0) |
370 | goto error; | |
371 | ||
9a08067e TS |
372 | err = amdtp_domain_add_stream(&dg00x->domain, &dg00x->tx_stream, |
373 | dg00x->tx_resources.channel, spd); | |
374 | if (err < 0) | |
3a2a1797 | 375 | goto error; |
3a2a1797 | 376 | |
9a08067e | 377 | err = amdtp_domain_start(&dg00x->domain); |
3a2a1797 TS |
378 | if (err < 0) |
379 | goto error; | |
380 | ||
9a08067e TS |
381 | if (!amdtp_stream_wait_callback(&dg00x->rx_stream, |
382 | CALLBACK_TIMEOUT) || | |
383 | !amdtp_stream_wait_callback(&dg00x->tx_stream, | |
384 | CALLBACK_TIMEOUT)) { | |
3a2a1797 TS |
385 | err = -ETIMEDOUT; |
386 | goto error; | |
387 | } | |
388 | } | |
ae8ffbb2 TS |
389 | |
390 | return 0; | |
3a2a1797 | 391 | error: |
9a08067e | 392 | amdtp_domain_stop(&dg00x->domain); |
3a2a1797 TS |
393 | finish_session(dg00x); |
394 | ||
3a2a1797 TS |
395 | return err; |
396 | } | |
397 | ||
398 | void snd_dg00x_stream_stop_duplex(struct snd_dg00x *dg00x) | |
399 | { | |
b9434540 | 400 | if (dg00x->substreams_counter == 0) { |
9a08067e | 401 | amdtp_domain_stop(&dg00x->domain); |
ae8ffbb2 | 402 | finish_session(dg00x); |
b9434540 TS |
403 | |
404 | fw_iso_resources_free(&dg00x->tx_resources); | |
405 | fw_iso_resources_free(&dg00x->rx_resources); | |
406 | } | |
3a2a1797 TS |
407 | } |
408 | ||
409 | void snd_dg00x_stream_update_duplex(struct snd_dg00x *dg00x) | |
410 | { | |
411 | fw_iso_resources_update(&dg00x->tx_resources); | |
412 | fw_iso_resources_update(&dg00x->rx_resources); | |
413 | ||
414 | amdtp_stream_update(&dg00x->tx_stream); | |
415 | amdtp_stream_update(&dg00x->rx_stream); | |
416 | } | |
660dd3d5 TS |
417 | |
418 | void snd_dg00x_stream_lock_changed(struct snd_dg00x *dg00x) | |
419 | { | |
420 | dg00x->dev_lock_changed = true; | |
421 | wake_up(&dg00x->hwdep_wait); | |
422 | } | |
423 | ||
424 | int snd_dg00x_stream_lock_try(struct snd_dg00x *dg00x) | |
425 | { | |
426 | int err; | |
427 | ||
428 | spin_lock_irq(&dg00x->lock); | |
429 | ||
430 | /* user land lock this */ | |
431 | if (dg00x->dev_lock_count < 0) { | |
432 | err = -EBUSY; | |
433 | goto end; | |
434 | } | |
435 | ||
436 | /* this is the first time */ | |
437 | if (dg00x->dev_lock_count++ == 0) | |
438 | snd_dg00x_stream_lock_changed(dg00x); | |
439 | err = 0; | |
440 | end: | |
441 | spin_unlock_irq(&dg00x->lock); | |
442 | return err; | |
443 | } | |
444 | ||
445 | void snd_dg00x_stream_lock_release(struct snd_dg00x *dg00x) | |
446 | { | |
447 | spin_lock_irq(&dg00x->lock); | |
448 | ||
449 | if (WARN_ON(dg00x->dev_lock_count <= 0)) | |
450 | goto end; | |
451 | if (--dg00x->dev_lock_count == 0) | |
452 | snd_dg00x_stream_lock_changed(dg00x); | |
453 | end: | |
454 | spin_unlock_irq(&dg00x->lock); | |
455 | } |