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