Commit | Line | Data |
---|---|---|
da607e19 | 1 | // SPDX-License-Identifier: GPL-2.0-only |
315fd41f TS |
2 | /* |
3 | * fireworks_stream.c - a part of driver for Fireworks based devices | |
4 | * | |
5 | * Copyright (c) 2013-2014 Takashi Sakamoto | |
315fd41f TS |
6 | */ |
7 | #include "./fireworks.h" | |
8 | ||
9 | #define CALLBACK_TIMEOUT 100 | |
10 | ||
94491c17 | 11 | static int init_stream(struct snd_efw *efw, struct amdtp_stream *stream) |
315fd41f TS |
12 | { |
13 | struct cmp_connection *conn; | |
14 | enum cmp_direction c_dir; | |
15 | enum amdtp_stream_direction s_dir; | |
16 | int err; | |
17 | ||
18 | if (stream == &efw->tx_stream) { | |
19 | conn = &efw->out_conn; | |
20 | c_dir = CMP_OUTPUT; | |
21 | s_dir = AMDTP_IN_STREAM; | |
22 | } else { | |
23 | conn = &efw->in_conn; | |
24 | c_dir = CMP_INPUT; | |
25 | s_dir = AMDTP_OUT_STREAM; | |
26 | } | |
27 | ||
28 | err = cmp_connection_init(conn, efw->unit, c_dir, 0); | |
29 | if (err < 0) | |
94491c17 | 30 | return err; |
315fd41f | 31 | |
5955815e | 32 | err = amdtp_am824_init(stream, efw->unit, s_dir, CIP_BLOCKING); |
315fd41f TS |
33 | if (err < 0) { |
34 | amdtp_stream_destroy(stream); | |
35 | cmp_connection_destroy(conn); | |
94491c17 TS |
36 | return err; |
37 | } | |
38 | ||
39 | if (stream == &efw->tx_stream) { | |
40 | // Fireworks transmits NODATA packets with TAG0. | |
41 | efw->tx_stream.flags |= CIP_EMPTY_WITH_TAG0; | |
42 | // Fireworks has its own meaning for dbc. | |
43 | efw->tx_stream.flags |= CIP_DBC_IS_END_EVENT; | |
44 | // Fireworks reset dbc at bus reset. | |
45 | efw->tx_stream.flags |= CIP_SKIP_DBC_ZERO_CHECK; | |
46 | // But Recent firmwares starts packets with non-zero dbc. | |
47 | // Driver version 5.7.6 installs firmware version 5.7.3. | |
48 | if (efw->is_fireworks3 && | |
49 | (efw->firmware_version == 0x5070000 || | |
50 | efw->firmware_version == 0x5070300 || | |
51 | efw->firmware_version == 0x5080000)) | |
52 | efw->tx_stream.flags |= CIP_UNALIGHED_DBC; | |
53 | // AudioFire9 always reports wrong dbs. | |
54 | if (efw->is_af9) | |
55 | efw->tx_stream.flags |= CIP_WRONG_DBS; | |
56 | // Firmware version 5.5 reports fixed interval for dbc. | |
57 | if (efw->firmware_version == 0x5050000) | |
58 | efw->tx_stream.ctx_data.tx.dbc_interval = 8; | |
315fd41f | 59 | } |
94491c17 | 60 | |
315fd41f TS |
61 | return err; |
62 | } | |
63 | ||
206cf896 TS |
64 | static int start_stream(struct snd_efw *efw, struct amdtp_stream *stream, |
65 | unsigned int rate) | |
315fd41f TS |
66 | { |
67 | struct cmp_connection *conn; | |
315fd41f TS |
68 | int err; |
69 | ||
206cf896 | 70 | if (stream == &efw->tx_stream) |
315fd41f | 71 | conn = &efw->out_conn; |
206cf896 | 72 | else |
315fd41f | 73 | conn = &efw->in_conn; |
315fd41f | 74 | |
206cf896 | 75 | // Establish connection via CMP. |
7bc93821 | 76 | err = cmp_connection_establish(conn); |
315fd41f | 77 | if (err < 0) |
206cf896 | 78 | return err; |
315fd41f | 79 | |
206cf896 | 80 | // Start amdtp stream. |
db40eeb2 TS |
81 | err = amdtp_domain_add_stream(&efw->domain, stream, |
82 | conn->resources.channel, conn->speed); | |
315fd41f | 83 | if (err < 0) { |
206cf896 TS |
84 | cmp_connection_break(conn); |
85 | return err; | |
315fd41f TS |
86 | } |
87 | ||
206cf896 | 88 | return 0; |
315fd41f TS |
89 | } |
90 | ||
94491c17 TS |
91 | // This function should be called before starting the stream or after stopping |
92 | // the streams. | |
93 | static void destroy_stream(struct snd_efw *efw, struct amdtp_stream *stream) | |
315fd41f | 94 | { |
94491c17 | 95 | amdtp_stream_destroy(stream); |
315fd41f TS |
96 | |
97 | if (stream == &efw->tx_stream) | |
94491c17 | 98 | cmp_connection_destroy(&efw->out_conn); |
315fd41f | 99 | else |
94491c17 | 100 | cmp_connection_destroy(&efw->in_conn); |
315fd41f TS |
101 | } |
102 | ||
315fd41f TS |
103 | static int |
104 | check_connection_used_by_others(struct snd_efw *efw, struct amdtp_stream *s) | |
105 | { | |
106 | struct cmp_connection *conn; | |
107 | bool used; | |
108 | int err; | |
109 | ||
110 | if (s == &efw->tx_stream) | |
111 | conn = &efw->out_conn; | |
112 | else | |
113 | conn = &efw->in_conn; | |
114 | ||
115 | err = cmp_connection_check_used(conn, &used); | |
116 | if ((err >= 0) && used && !amdtp_stream_running(s)) { | |
117 | dev_err(&efw->unit->device, | |
118 | "Connection established by others: %cPCR[%d]\n", | |
119 | (conn->direction == CMP_OUTPUT) ? 'o' : 'i', | |
120 | conn->pcr_index); | |
121 | err = -EBUSY; | |
122 | } | |
123 | ||
124 | return err; | |
125 | } | |
126 | ||
127 | int snd_efw_stream_init_duplex(struct snd_efw *efw) | |
128 | { | |
129 | int err; | |
130 | ||
131 | err = init_stream(efw, &efw->tx_stream); | |
132 | if (err < 0) | |
94491c17 | 133 | return err; |
315fd41f TS |
134 | |
135 | err = init_stream(efw, &efw->rx_stream); | |
136 | if (err < 0) { | |
137 | destroy_stream(efw, &efw->tx_stream); | |
94491c17 | 138 | return err; |
315fd41f TS |
139 | } |
140 | ||
db40eeb2 TS |
141 | err = amdtp_domain_init(&efw->domain); |
142 | if (err < 0) { | |
143 | destroy_stream(efw, &efw->tx_stream); | |
144 | destroy_stream(efw, &efw->rx_stream); | |
145 | return err; | |
146 | } | |
147 | ||
94491c17 | 148 | // set IEC61883 compliant mode (actually not fully compliant...). |
315fd41f TS |
149 | err = snd_efw_command_set_tx_mode(efw, SND_EFW_TRANSPORT_MODE_IEC61883); |
150 | if (err < 0) { | |
151 | destroy_stream(efw, &efw->tx_stream); | |
152 | destroy_stream(efw, &efw->rx_stream); | |
153 | } | |
94491c17 | 154 | |
315fd41f TS |
155 | return err; |
156 | } | |
157 | ||
206cf896 TS |
158 | static int keep_resources(struct snd_efw *efw, struct amdtp_stream *stream, |
159 | unsigned int rate, unsigned int mode) | |
160 | { | |
161 | unsigned int pcm_channels; | |
162 | unsigned int midi_ports; | |
7bc93821 TS |
163 | struct cmp_connection *conn; |
164 | int err; | |
206cf896 TS |
165 | |
166 | if (stream == &efw->tx_stream) { | |
167 | pcm_channels = efw->pcm_capture_channels[mode]; | |
168 | midi_ports = efw->midi_out_ports; | |
7bc93821 | 169 | conn = &efw->out_conn; |
206cf896 TS |
170 | } else { |
171 | pcm_channels = efw->pcm_playback_channels[mode]; | |
172 | midi_ports = efw->midi_in_ports; | |
7bc93821 | 173 | conn = &efw->in_conn; |
206cf896 TS |
174 | } |
175 | ||
7bc93821 TS |
176 | err = amdtp_am824_set_parameters(stream, rate, pcm_channels, |
177 | midi_ports, false); | |
178 | if (err < 0) | |
179 | return err; | |
180 | ||
181 | return cmp_connection_reserve(conn, amdtp_stream_get_max_payload(stream)); | |
206cf896 TS |
182 | } |
183 | ||
dd20e68a | 184 | int snd_efw_stream_reserve_duplex(struct snd_efw *efw, unsigned int rate, |
659c6af5 TS |
185 | unsigned int frames_per_period, |
186 | unsigned int frames_per_buffer) | |
315fd41f | 187 | { |
315fd41f | 188 | unsigned int curr_rate; |
3d725066 | 189 | int err; |
315fd41f | 190 | |
3d725066 TS |
191 | // Considering JACK/FFADO streaming: |
192 | // TODO: This can be removed hwdep functionality becomes popular. | |
eb4a378f | 193 | err = check_connection_used_by_others(efw, &efw->rx_stream); |
315fd41f | 194 | if (err < 0) |
3d725066 | 195 | return err; |
315fd41f | 196 | |
3d725066 | 197 | // stop streams if rate is different. |
315fd41f TS |
198 | err = snd_efw_command_get_sampling_rate(efw, &curr_rate); |
199 | if (err < 0) | |
3d725066 | 200 | return err; |
315fd41f TS |
201 | if (rate == 0) |
202 | rate = curr_rate; | |
3d725066 | 203 | if (rate != curr_rate) { |
db40eeb2 TS |
204 | amdtp_domain_stop(&efw->domain); |
205 | ||
206 | cmp_connection_break(&efw->out_conn); | |
207 | cmp_connection_break(&efw->in_conn); | |
a9679dd3 TS |
208 | |
209 | cmp_connection_release(&efw->out_conn); | |
210 | cmp_connection_release(&efw->in_conn); | |
315fd41f TS |
211 | } |
212 | ||
3d725066 | 213 | if (efw->substreams_counter == 0 || rate != curr_rate) { |
206cf896 TS |
214 | unsigned int mode; |
215 | ||
315fd41f TS |
216 | err = snd_efw_command_set_sampling_rate(efw, rate); |
217 | if (err < 0) | |
3d725066 | 218 | return err; |
206cf896 TS |
219 | |
220 | err = snd_efw_get_multiplier_mode(rate, &mode); | |
221 | if (err < 0) | |
222 | return err; | |
223 | ||
224 | err = keep_resources(efw, &efw->tx_stream, rate, mode); | |
225 | if (err < 0) | |
226 | return err; | |
227 | ||
228 | err = keep_resources(efw, &efw->rx_stream, rate, mode); | |
7bc93821 TS |
229 | if (err < 0) { |
230 | cmp_connection_release(&efw->in_conn); | |
206cf896 | 231 | return err; |
7bc93821 | 232 | } |
dd20e68a TS |
233 | |
234 | err = amdtp_domain_set_events_per_period(&efw->domain, | |
659c6af5 | 235 | frames_per_period, frames_per_buffer); |
dd20e68a TS |
236 | if (err < 0) { |
237 | cmp_connection_release(&efw->in_conn); | |
238 | cmp_connection_release(&efw->out_conn); | |
239 | return err; | |
240 | } | |
3d725066 TS |
241 | } |
242 | ||
243 | return 0; | |
244 | } | |
245 | ||
246 | int snd_efw_stream_start_duplex(struct snd_efw *efw) | |
247 | { | |
248 | unsigned int rate; | |
249 | int err = 0; | |
250 | ||
251 | // Need no substreams. | |
252 | if (efw->substreams_counter == 0) | |
253 | return -EIO; | |
254 | ||
3d725066 TS |
255 | if (amdtp_streaming_error(&efw->rx_stream) || |
256 | amdtp_streaming_error(&efw->tx_stream)) { | |
db40eeb2 TS |
257 | amdtp_domain_stop(&efw->domain); |
258 | cmp_connection_break(&efw->out_conn); | |
259 | cmp_connection_break(&efw->in_conn); | |
3d725066 TS |
260 | } |
261 | ||
db40eeb2 TS |
262 | err = snd_efw_command_get_sampling_rate(efw, &rate); |
263 | if (err < 0) | |
264 | return err; | |
265 | ||
3d725066 | 266 | if (!amdtp_stream_running(&efw->rx_stream)) { |
eb4a378f | 267 | err = start_stream(efw, &efw->rx_stream, rate); |
db40eeb2 | 268 | if (err < 0) |
3d725066 | 269 | goto error; |
315fd41f | 270 | |
eb4a378f | 271 | err = start_stream(efw, &efw->tx_stream, rate); |
db40eeb2 TS |
272 | if (err < 0) |
273 | goto error; | |
274 | ||
275 | err = amdtp_domain_start(&efw->domain); | |
276 | if (err < 0) | |
277 | goto error; | |
278 | ||
279 | // Wait first callback. | |
280 | if (!amdtp_stream_wait_callback(&efw->rx_stream, | |
281 | CALLBACK_TIMEOUT) || | |
282 | !amdtp_stream_wait_callback(&efw->tx_stream, | |
283 | CALLBACK_TIMEOUT)) { | |
284 | err = -ETIMEDOUT; | |
3d725066 | 285 | goto error; |
315fd41f TS |
286 | } |
287 | } | |
3d725066 TS |
288 | |
289 | return 0; | |
290 | error: | |
db40eeb2 TS |
291 | amdtp_domain_stop(&efw->domain); |
292 | ||
293 | cmp_connection_break(&efw->out_conn); | |
294 | cmp_connection_break(&efw->in_conn); | |
295 | ||
315fd41f TS |
296 | return err; |
297 | } | |
298 | ||
299 | void snd_efw_stream_stop_duplex(struct snd_efw *efw) | |
300 | { | |
1dc59210 | 301 | if (efw->substreams_counter == 0) { |
db40eeb2 TS |
302 | amdtp_domain_stop(&efw->domain); |
303 | ||
304 | cmp_connection_break(&efw->out_conn); | |
305 | cmp_connection_break(&efw->in_conn); | |
7bc93821 TS |
306 | |
307 | cmp_connection_release(&efw->out_conn); | |
308 | cmp_connection_release(&efw->in_conn); | |
315fd41f | 309 | } |
315fd41f TS |
310 | } |
311 | ||
312 | void snd_efw_stream_update_duplex(struct snd_efw *efw) | |
313 | { | |
db40eeb2 TS |
314 | amdtp_domain_stop(&efw->domain); |
315 | ||
316 | cmp_connection_break(&efw->out_conn); | |
317 | cmp_connection_break(&efw->in_conn); | |
7eb7b18e TS |
318 | |
319 | amdtp_stream_pcm_abort(&efw->rx_stream); | |
320 | amdtp_stream_pcm_abort(&efw->tx_stream); | |
315fd41f TS |
321 | } |
322 | ||
323 | void snd_efw_stream_destroy_duplex(struct snd_efw *efw) | |
324 | { | |
db40eeb2 TS |
325 | amdtp_domain_destroy(&efw->domain); |
326 | ||
315fd41f TS |
327 | destroy_stream(efw, &efw->rx_stream); |
328 | destroy_stream(efw, &efw->tx_stream); | |
315fd41f | 329 | } |
594ddced TS |
330 | |
331 | void snd_efw_stream_lock_changed(struct snd_efw *efw) | |
332 | { | |
333 | efw->dev_lock_changed = true; | |
334 | wake_up(&efw->hwdep_wait); | |
335 | } | |
336 | ||
337 | int snd_efw_stream_lock_try(struct snd_efw *efw) | |
338 | { | |
339 | int err; | |
340 | ||
341 | spin_lock_irq(&efw->lock); | |
342 | ||
343 | /* user land lock this */ | |
344 | if (efw->dev_lock_count < 0) { | |
345 | err = -EBUSY; | |
346 | goto end; | |
347 | } | |
348 | ||
349 | /* this is the first time */ | |
350 | if (efw->dev_lock_count++ == 0) | |
351 | snd_efw_stream_lock_changed(efw); | |
352 | err = 0; | |
353 | end: | |
354 | spin_unlock_irq(&efw->lock); | |
355 | return err; | |
356 | } | |
357 | ||
358 | void snd_efw_stream_lock_release(struct snd_efw *efw) | |
359 | { | |
360 | spin_lock_irq(&efw->lock); | |
361 | ||
362 | if (WARN_ON(efw->dev_lock_count <= 0)) | |
363 | goto end; | |
364 | if (--efw->dev_lock_count == 0) | |
365 | snd_efw_stream_lock_changed(efw); | |
366 | end: | |
367 | spin_unlock_irq(&efw->lock); | |
368 | } |