1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * fireworks_stream.c - a part of driver for Fireworks based devices |
4 | * |
5 | * Copyright (c) 2013-2014 Takashi Sakamoto |
6 | */ |
7 | #include "./fireworks.h" |
8 | |
9 | #define READY_TIMEOUT_MS 1000 |
10 | |
11 | static int init_stream(struct snd_efw *efw, struct amdtp_stream *stream) |
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(connection: conn, unit: efw->unit, direction: c_dir, pcr_index: 0); |
29 | if (err < 0) |
30 | return err; |
31 | |
32 | err = amdtp_am824_init(s: stream, unit: efw->unit, dir: s_dir, flags: CIP_BLOCKING | CIP_UNAWARE_SYT); |
33 | if (err < 0) { |
34 | amdtp_stream_destroy(s: stream); |
35 | cmp_connection_destroy(connection: conn); |
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. Onyx 1200F with the latest firmware (v4.6.0) |
54 | // also report wrong dbs at 88.2 kHz or greater. |
55 | if (efw->is_af9 || efw->firmware_version == 0x4060000) |
56 | efw->tx_stream.flags |= CIP_WRONG_DBS; |
57 | // Firmware version 5.5 reports fixed interval for dbc. |
58 | if (efw->firmware_version == 0x5050000) |
59 | efw->tx_stream.ctx_data.tx.dbc_interval = 8; |
60 | } |
61 | |
62 | return err; |
63 | } |
64 | |
65 | static int start_stream(struct snd_efw *efw, struct amdtp_stream *stream, |
66 | unsigned int rate) |
67 | { |
68 | struct cmp_connection *conn; |
69 | int err; |
70 | |
71 | if (stream == &efw->tx_stream) |
72 | conn = &efw->out_conn; |
73 | else |
74 | conn = &efw->in_conn; |
75 | |
76 | // Establish connection via CMP. |
77 | err = cmp_connection_establish(connection: conn); |
78 | if (err < 0) |
79 | return err; |
80 | |
81 | // Start amdtp stream. |
82 | err = amdtp_domain_add_stream(d: &efw->domain, s: stream, |
83 | channel: conn->resources.channel, speed: conn->speed); |
84 | if (err < 0) { |
85 | cmp_connection_break(connection: conn); |
86 | return err; |
87 | } |
88 | |
89 | return 0; |
90 | } |
91 | |
92 | // This function should be called before starting the stream or after stopping |
93 | // the streams. |
94 | static void destroy_stream(struct snd_efw *efw, struct amdtp_stream *stream) |
95 | { |
96 | amdtp_stream_destroy(s: stream); |
97 | |
98 | if (stream == &efw->tx_stream) |
99 | cmp_connection_destroy(connection: &efw->out_conn); |
100 | else |
101 | cmp_connection_destroy(connection: &efw->in_conn); |
102 | } |
103 | |
104 | static int |
105 | check_connection_used_by_others(struct snd_efw *efw, struct amdtp_stream *s) |
106 | { |
107 | struct cmp_connection *conn; |
108 | bool used; |
109 | int err; |
110 | |
111 | if (s == &efw->tx_stream) |
112 | conn = &efw->out_conn; |
113 | else |
114 | conn = &efw->in_conn; |
115 | |
116 | err = cmp_connection_check_used(connection: conn, used: &used); |
117 | if ((err >= 0) && used && !amdtp_stream_running(s)) { |
118 | dev_err(&efw->unit->device, |
119 | "Connection established by others: %cPCR[%d]\n" , |
120 | (conn->direction == CMP_OUTPUT) ? 'o' : 'i', |
121 | conn->pcr_index); |
122 | err = -EBUSY; |
123 | } |
124 | |
125 | return err; |
126 | } |
127 | |
128 | int snd_efw_stream_init_duplex(struct snd_efw *efw) |
129 | { |
130 | int err; |
131 | |
132 | err = init_stream(efw, stream: &efw->tx_stream); |
133 | if (err < 0) |
134 | return err; |
135 | |
136 | err = init_stream(efw, stream: &efw->rx_stream); |
137 | if (err < 0) { |
138 | destroy_stream(efw, stream: &efw->tx_stream); |
139 | return err; |
140 | } |
141 | |
142 | err = amdtp_domain_init(d: &efw->domain); |
143 | if (err < 0) { |
144 | destroy_stream(efw, stream: &efw->tx_stream); |
145 | destroy_stream(efw, stream: &efw->rx_stream); |
146 | return err; |
147 | } |
148 | |
149 | // set IEC61883 compliant mode (actually not fully compliant...). |
150 | err = snd_efw_command_set_tx_mode(efw, mode: SND_EFW_TRANSPORT_MODE_IEC61883); |
151 | if (err < 0) { |
152 | destroy_stream(efw, stream: &efw->tx_stream); |
153 | destroy_stream(efw, stream: &efw->rx_stream); |
154 | } |
155 | |
156 | return err; |
157 | } |
158 | |
159 | static int keep_resources(struct snd_efw *efw, struct amdtp_stream *stream, |
160 | unsigned int rate, unsigned int mode) |
161 | { |
162 | unsigned int pcm_channels; |
163 | unsigned int midi_ports; |
164 | struct cmp_connection *conn; |
165 | int err; |
166 | |
167 | if (stream == &efw->tx_stream) { |
168 | pcm_channels = efw->pcm_capture_channels[mode]; |
169 | midi_ports = efw->midi_out_ports; |
170 | conn = &efw->out_conn; |
171 | } else { |
172 | pcm_channels = efw->pcm_playback_channels[mode]; |
173 | midi_ports = efw->midi_in_ports; |
174 | conn = &efw->in_conn; |
175 | } |
176 | |
177 | err = amdtp_am824_set_parameters(s: stream, rate, pcm_channels, |
178 | midi_ports, double_pcm_frames: false); |
179 | if (err < 0) |
180 | return err; |
181 | |
182 | return cmp_connection_reserve(connection: conn, max_payload: amdtp_stream_get_max_payload(s: stream)); |
183 | } |
184 | |
185 | int snd_efw_stream_reserve_duplex(struct snd_efw *efw, unsigned int rate, |
186 | unsigned int frames_per_period, |
187 | unsigned int frames_per_buffer) |
188 | { |
189 | unsigned int curr_rate; |
190 | int err; |
191 | |
192 | // Considering JACK/FFADO streaming: |
193 | // TODO: This can be removed hwdep functionality becomes popular. |
194 | err = check_connection_used_by_others(efw, s: &efw->rx_stream); |
195 | if (err < 0) |
196 | return err; |
197 | |
198 | // stop streams if rate is different. |
199 | err = snd_efw_command_get_sampling_rate(efw, rate: &curr_rate); |
200 | if (err < 0) |
201 | return err; |
202 | if (rate == 0) |
203 | rate = curr_rate; |
204 | if (rate != curr_rate) { |
205 | amdtp_domain_stop(d: &efw->domain); |
206 | |
207 | cmp_connection_break(connection: &efw->out_conn); |
208 | cmp_connection_break(connection: &efw->in_conn); |
209 | |
210 | cmp_connection_release(connection: &efw->out_conn); |
211 | cmp_connection_release(connection: &efw->in_conn); |
212 | } |
213 | |
214 | if (efw->substreams_counter == 0 || rate != curr_rate) { |
215 | unsigned int mode; |
216 | |
217 | err = snd_efw_command_set_sampling_rate(efw, rate); |
218 | if (err < 0) |
219 | return err; |
220 | |
221 | err = snd_efw_get_multiplier_mode(sampling_rate: rate, mode: &mode); |
222 | if (err < 0) |
223 | return err; |
224 | |
225 | err = keep_resources(efw, stream: &efw->tx_stream, rate, mode); |
226 | if (err < 0) |
227 | return err; |
228 | |
229 | err = keep_resources(efw, stream: &efw->rx_stream, rate, mode); |
230 | if (err < 0) { |
231 | cmp_connection_release(connection: &efw->in_conn); |
232 | return err; |
233 | } |
234 | |
235 | err = amdtp_domain_set_events_per_period(d: &efw->domain, |
236 | events_per_period: frames_per_period, events_per_buffer: frames_per_buffer); |
237 | if (err < 0) { |
238 | cmp_connection_release(connection: &efw->in_conn); |
239 | cmp_connection_release(connection: &efw->out_conn); |
240 | return err; |
241 | } |
242 | } |
243 | |
244 | return 0; |
245 | } |
246 | |
247 | int snd_efw_stream_start_duplex(struct snd_efw *efw) |
248 | { |
249 | unsigned int rate; |
250 | int err = 0; |
251 | |
252 | // Need no substreams. |
253 | if (efw->substreams_counter == 0) |
254 | return -EIO; |
255 | |
256 | if (amdtp_streaming_error(s: &efw->rx_stream) || |
257 | amdtp_streaming_error(s: &efw->tx_stream)) { |
258 | amdtp_domain_stop(d: &efw->domain); |
259 | cmp_connection_break(connection: &efw->out_conn); |
260 | cmp_connection_break(connection: &efw->in_conn); |
261 | } |
262 | |
263 | err = snd_efw_command_get_sampling_rate(efw, rate: &rate); |
264 | if (err < 0) |
265 | return err; |
266 | |
267 | if (!amdtp_stream_running(s: &efw->rx_stream)) { |
268 | unsigned int tx_init_skip_cycles; |
269 | |
270 | // Audiofire 2/4 skip an isochronous cycle several thousands after starting |
271 | // packet transmission. |
272 | if (efw->is_fireworks3 && !efw->is_af9) |
273 | tx_init_skip_cycles = 6000; |
274 | else |
275 | tx_init_skip_cycles = 0; |
276 | |
277 | err = start_stream(efw, stream: &efw->rx_stream, rate); |
278 | if (err < 0) |
279 | goto error; |
280 | |
281 | err = start_stream(efw, stream: &efw->tx_stream, rate); |
282 | if (err < 0) |
283 | goto error; |
284 | |
285 | // NOTE: The device ignores presentation time expressed by the value of syt field |
286 | // of CIP header in received packets. The sequence of the number of data blocks per |
287 | // packet is important for media clock recovery. |
288 | err = amdtp_domain_start(d: &efw->domain, tx_init_skip_cycles, replay_seq: true, replay_on_the_fly: false); |
289 | if (err < 0) |
290 | goto error; |
291 | |
292 | if (!amdtp_domain_wait_ready(d: &efw->domain, READY_TIMEOUT_MS)) { |
293 | err = -ETIMEDOUT; |
294 | goto error; |
295 | } |
296 | } |
297 | |
298 | return 0; |
299 | error: |
300 | amdtp_domain_stop(d: &efw->domain); |
301 | |
302 | cmp_connection_break(connection: &efw->out_conn); |
303 | cmp_connection_break(connection: &efw->in_conn); |
304 | |
305 | return err; |
306 | } |
307 | |
308 | void snd_efw_stream_stop_duplex(struct snd_efw *efw) |
309 | { |
310 | if (efw->substreams_counter == 0) { |
311 | amdtp_domain_stop(d: &efw->domain); |
312 | |
313 | cmp_connection_break(connection: &efw->out_conn); |
314 | cmp_connection_break(connection: &efw->in_conn); |
315 | |
316 | cmp_connection_release(connection: &efw->out_conn); |
317 | cmp_connection_release(connection: &efw->in_conn); |
318 | } |
319 | } |
320 | |
321 | void snd_efw_stream_update_duplex(struct snd_efw *efw) |
322 | { |
323 | amdtp_domain_stop(d: &efw->domain); |
324 | |
325 | cmp_connection_break(connection: &efw->out_conn); |
326 | cmp_connection_break(connection: &efw->in_conn); |
327 | |
328 | amdtp_stream_pcm_abort(s: &efw->rx_stream); |
329 | amdtp_stream_pcm_abort(s: &efw->tx_stream); |
330 | } |
331 | |
332 | void snd_efw_stream_destroy_duplex(struct snd_efw *efw) |
333 | { |
334 | amdtp_domain_destroy(d: &efw->domain); |
335 | |
336 | destroy_stream(efw, stream: &efw->rx_stream); |
337 | destroy_stream(efw, stream: &efw->tx_stream); |
338 | } |
339 | |
340 | void snd_efw_stream_lock_changed(struct snd_efw *efw) |
341 | { |
342 | efw->dev_lock_changed = true; |
343 | wake_up(&efw->hwdep_wait); |
344 | } |
345 | |
346 | int snd_efw_stream_lock_try(struct snd_efw *efw) |
347 | { |
348 | int err; |
349 | |
350 | spin_lock_irq(lock: &efw->lock); |
351 | |
352 | /* user land lock this */ |
353 | if (efw->dev_lock_count < 0) { |
354 | err = -EBUSY; |
355 | goto end; |
356 | } |
357 | |
358 | /* this is the first time */ |
359 | if (efw->dev_lock_count++ == 0) |
360 | snd_efw_stream_lock_changed(efw); |
361 | err = 0; |
362 | end: |
363 | spin_unlock_irq(lock: &efw->lock); |
364 | return err; |
365 | } |
366 | |
367 | void snd_efw_stream_lock_release(struct snd_efw *efw) |
368 | { |
369 | spin_lock_irq(lock: &efw->lock); |
370 | |
371 | if (WARN_ON(efw->dev_lock_count <= 0)) |
372 | goto end; |
373 | if (--efw->dev_lock_count == 0) |
374 | snd_efw_stream_lock_changed(efw); |
375 | end: |
376 | spin_unlock_irq(lock: &efw->lock); |
377 | } |
378 | |