Commit | Line | Data |
---|---|---|
da607e19 | 1 | // SPDX-License-Identifier: GPL-2.0-only |
75d6d898 TS |
2 | /* |
3 | * ff-stream.c - a part of driver for RME Fireface series | |
4 | * | |
5 | * Copyright (c) 2015-2017 Takashi Sakamoto | |
75d6d898 TS |
6 | */ |
7 | ||
8 | #include "ff.h" | |
9 | ||
bdaedca7 | 10 | #define READY_TIMEOUT_MS 200 |
75d6d898 | 11 | |
76ea4688 TS |
12 | int snd_ff_stream_get_multiplier_mode(enum cip_sfc sfc, |
13 | enum snd_ff_stream_mode *mode) | |
75d6d898 | 14 | { |
76ea4688 TS |
15 | static const enum snd_ff_stream_mode modes[] = { |
16 | [CIP_SFC_32000] = SND_FF_STREAM_MODE_LOW, | |
17 | [CIP_SFC_44100] = SND_FF_STREAM_MODE_LOW, | |
18 | [CIP_SFC_48000] = SND_FF_STREAM_MODE_LOW, | |
19 | [CIP_SFC_88200] = SND_FF_STREAM_MODE_MID, | |
20 | [CIP_SFC_96000] = SND_FF_STREAM_MODE_MID, | |
21 | [CIP_SFC_176400] = SND_FF_STREAM_MODE_HIGH, | |
22 | [CIP_SFC_192000] = SND_FF_STREAM_MODE_HIGH, | |
23 | }; | |
24 | ||
25 | if (sfc >= CIP_SFC_COUNT) | |
75d6d898 TS |
26 | return -EINVAL; |
27 | ||
76ea4688 | 28 | *mode = modes[sfc]; |
75d6d898 TS |
29 | |
30 | return 0; | |
31 | } | |
32 | ||
75d6d898 TS |
33 | static inline void finish_session(struct snd_ff *ff) |
34 | { | |
35 | ff->spec->protocol->finish_session(ff); | |
ae3053c2 | 36 | ff->spec->protocol->switch_fetching_mode(ff, false); |
75d6d898 TS |
37 | } |
38 | ||
42355abb | 39 | static int init_stream(struct snd_ff *ff, struct amdtp_stream *s) |
75d6d898 | 40 | { |
75d6d898 | 41 | struct fw_iso_resources *resources; |
42355abb TS |
42 | enum amdtp_stream_direction dir; |
43 | int err; | |
75d6d898 | 44 | |
42355abb | 45 | if (s == &ff->tx_stream) { |
75d6d898 | 46 | resources = &ff->tx_resources; |
42355abb | 47 | dir = AMDTP_IN_STREAM; |
75d6d898 TS |
48 | } else { |
49 | resources = &ff->rx_resources; | |
42355abb | 50 | dir = AMDTP_OUT_STREAM; |
75d6d898 TS |
51 | } |
52 | ||
53 | err = fw_iso_resources_init(resources, ff->unit); | |
54 | if (err < 0) | |
55 | return err; | |
56 | ||
42355abb | 57 | err = amdtp_ff_init(s, ff->unit, dir); |
75d6d898 TS |
58 | if (err < 0) |
59 | fw_iso_resources_destroy(resources); | |
60 | ||
61 | return err; | |
62 | } | |
63 | ||
42355abb | 64 | static void destroy_stream(struct snd_ff *ff, struct amdtp_stream *s) |
75d6d898 | 65 | { |
42355abb TS |
66 | amdtp_stream_destroy(s); |
67 | ||
68 | if (s == &ff->tx_stream) | |
75d6d898 | 69 | fw_iso_resources_destroy(&ff->tx_resources); |
42355abb | 70 | else |
75d6d898 | 71 | fw_iso_resources_destroy(&ff->rx_resources); |
75d6d898 TS |
72 | } |
73 | ||
74 | int snd_ff_stream_init_duplex(struct snd_ff *ff) | |
75 | { | |
76 | int err; | |
77 | ||
42355abb | 78 | err = init_stream(ff, &ff->rx_stream); |
75d6d898 | 79 | if (err < 0) |
42355abb | 80 | return err; |
75d6d898 | 81 | |
42355abb | 82 | err = init_stream(ff, &ff->tx_stream); |
c9a9ce89 | 83 | if (err < 0) { |
42355abb | 84 | destroy_stream(ff, &ff->rx_stream); |
c9a9ce89 TS |
85 | return err; |
86 | } | |
87 | ||
88 | err = amdtp_domain_init(&ff->domain); | |
89 | if (err < 0) { | |
90 | destroy_stream(ff, &ff->rx_stream); | |
91 | destroy_stream(ff, &ff->tx_stream); | |
92 | } | |
42355abb | 93 | |
75d6d898 TS |
94 | return err; |
95 | } | |
96 | ||
97 | /* | |
98 | * This function should be called before starting streams or after stopping | |
99 | * streams. | |
100 | */ | |
101 | void snd_ff_stream_destroy_duplex(struct snd_ff *ff) | |
102 | { | |
c9a9ce89 TS |
103 | amdtp_domain_destroy(&ff->domain); |
104 | ||
42355abb TS |
105 | destroy_stream(ff, &ff->rx_stream); |
106 | destroy_stream(ff, &ff->tx_stream); | |
75d6d898 TS |
107 | } |
108 | ||
9d9ff58c | 109 | int snd_ff_stream_reserve_duplex(struct snd_ff *ff, unsigned int rate, |
4de3eb06 TS |
110 | unsigned int frames_per_period, |
111 | unsigned int frames_per_buffer) | |
75d6d898 TS |
112 | { |
113 | unsigned int curr_rate; | |
114 | enum snd_ff_clock_src src; | |
115 | int err; | |
116 | ||
b1d0cb0a | 117 | err = ff->spec->protocol->get_clock(ff, &curr_rate, &src); |
75d6d898 TS |
118 | if (err < 0) |
119 | return err; | |
55162d2b TS |
120 | |
121 | if (ff->substreams_counter == 0 || curr_rate != rate) { | |
122 | enum snd_ff_stream_mode mode; | |
123 | int i; | |
75d6d898 | 124 | |
c9a9ce89 | 125 | amdtp_domain_stop(&ff->domain); |
55162d2b | 126 | finish_session(ff); |
75d6d898 | 127 | |
55162d2b TS |
128 | fw_iso_resources_free(&ff->tx_resources); |
129 | fw_iso_resources_free(&ff->rx_resources); | |
365c00d0 TS |
130 | |
131 | for (i = 0; i < CIP_SFC_COUNT; ++i) { | |
132 | if (amdtp_rate_table[i] == rate) | |
133 | break; | |
134 | } | |
135 | if (i >= CIP_SFC_COUNT) | |
136 | return -EINVAL; | |
137 | ||
138 | err = snd_ff_stream_get_multiplier_mode(i, &mode); | |
75d6d898 | 139 | if (err < 0) |
365c00d0 TS |
140 | return err; |
141 | ||
142 | err = amdtp_ff_set_parameters(&ff->tx_stream, rate, | |
143 | ff->spec->pcm_capture_channels[mode]); | |
144 | if (err < 0) | |
145 | return err; | |
146 | ||
147 | err = amdtp_ff_set_parameters(&ff->rx_stream, rate, | |
148 | ff->spec->pcm_playback_channels[mode]); | |
149 | if (err < 0) | |
150 | return err; | |
75d6d898 | 151 | |
47b87c8e TS |
152 | err = ff->spec->protocol->allocate_resources(ff, rate); |
153 | if (err < 0) | |
55162d2b | 154 | return err; |
9d9ff58c TS |
155 | |
156 | err = amdtp_domain_set_events_per_period(&ff->domain, | |
4de3eb06 | 157 | frames_per_period, frames_per_buffer); |
9d9ff58c TS |
158 | if (err < 0) { |
159 | fw_iso_resources_free(&ff->tx_resources); | |
160 | fw_iso_resources_free(&ff->rx_resources); | |
161 | return err; | |
162 | } | |
55162d2b TS |
163 | } |
164 | ||
165 | return 0; | |
166 | } | |
167 | ||
55162d2b TS |
168 | int snd_ff_stream_start_duplex(struct snd_ff *ff, unsigned int rate) |
169 | { | |
170 | int err; | |
171 | ||
172 | if (ff->substreams_counter == 0) | |
173 | return 0; | |
47b87c8e | 174 | |
55162d2b | 175 | if (amdtp_streaming_error(&ff->tx_stream) || |
c9a9ce89 TS |
176 | amdtp_streaming_error(&ff->rx_stream)) { |
177 | amdtp_domain_stop(&ff->domain); | |
55162d2b | 178 | finish_session(ff); |
c9a9ce89 | 179 | } |
55162d2b TS |
180 | |
181 | /* | |
182 | * Regardless of current source of clock signal, drivers transfer some | |
183 | * packets. Then, the device transfers packets. | |
184 | */ | |
185 | if (!amdtp_stream_running(&ff->rx_stream)) { | |
c9a9ce89 TS |
186 | int spd = fw_parent_device(ff->unit)->max_speed; |
187 | ||
75d6d898 TS |
188 | err = ff->spec->protocol->begin_session(ff, rate); |
189 | if (err < 0) | |
190 | goto error; | |
191 | ||
c9a9ce89 TS |
192 | err = amdtp_domain_add_stream(&ff->domain, &ff->rx_stream, |
193 | ff->rx_resources.channel, spd); | |
75d6d898 TS |
194 | if (err < 0) |
195 | goto error; | |
196 | ||
c9a9ce89 TS |
197 | err = amdtp_domain_add_stream(&ff->domain, &ff->tx_stream, |
198 | ff->tx_resources.channel, spd); | |
75d6d898 TS |
199 | if (err < 0) |
200 | goto error; | |
75d6d898 | 201 | |
dfacca39 TS |
202 | // NOTE: The device doesn't transfer packets unless receiving any packet. The |
203 | // sequence of tx packets includes cycle skip corresponding to empty packet or | |
204 | // NODATA packet in IEC 61883-1/6. The sequence of the number of data blocks per | |
205 | // packet is important for media clock recovery. | |
206 | err = amdtp_domain_start(&ff->domain, 0, true, true); | |
75d6d898 TS |
207 | if (err < 0) |
208 | goto error; | |
209 | ||
bdaedca7 | 210 | if (!amdtp_domain_wait_ready(&ff->domain, READY_TIMEOUT_MS)) { |
75d6d898 TS |
211 | err = -ETIMEDOUT; |
212 | goto error; | |
213 | } | |
c9a9ce89 TS |
214 | |
215 | err = ff->spec->protocol->switch_fetching_mode(ff, true); | |
216 | if (err < 0) | |
217 | goto error; | |
75d6d898 TS |
218 | } |
219 | ||
220 | return 0; | |
221 | error: | |
c9a9ce89 | 222 | amdtp_domain_stop(&ff->domain); |
75d6d898 | 223 | finish_session(ff); |
75d6d898 TS |
224 | |
225 | return err; | |
226 | } | |
227 | ||
228 | void snd_ff_stream_stop_duplex(struct snd_ff *ff) | |
229 | { | |
af26bacc | 230 | if (ff->substreams_counter == 0) { |
c9a9ce89 | 231 | amdtp_domain_stop(&ff->domain); |
f55e2a89 | 232 | finish_session(ff); |
af26bacc TS |
233 | |
234 | fw_iso_resources_free(&ff->tx_resources); | |
235 | fw_iso_resources_free(&ff->rx_resources); | |
236 | } | |
75d6d898 TS |
237 | } |
238 | ||
239 | void snd_ff_stream_update_duplex(struct snd_ff *ff) | |
240 | { | |
c9a9ce89 TS |
241 | amdtp_domain_stop(&ff->domain); |
242 | ||
b88f4d7c | 243 | // The device discontinue to transfer packets. |
75d6d898 | 244 | amdtp_stream_pcm_abort(&ff->tx_stream); |
75d6d898 | 245 | amdtp_stream_pcm_abort(&ff->rx_stream); |
75d6d898 | 246 | } |
f656edd5 TS |
247 | |
248 | void snd_ff_stream_lock_changed(struct snd_ff *ff) | |
249 | { | |
250 | ff->dev_lock_changed = true; | |
251 | wake_up(&ff->hwdep_wait); | |
252 | } | |
253 | ||
254 | int snd_ff_stream_lock_try(struct snd_ff *ff) | |
255 | { | |
256 | int err; | |
257 | ||
258 | spin_lock_irq(&ff->lock); | |
259 | ||
260 | /* user land lock this */ | |
261 | if (ff->dev_lock_count < 0) { | |
262 | err = -EBUSY; | |
263 | goto end; | |
264 | } | |
265 | ||
266 | /* this is the first time */ | |
267 | if (ff->dev_lock_count++ == 0) | |
268 | snd_ff_stream_lock_changed(ff); | |
269 | err = 0; | |
270 | end: | |
271 | spin_unlock_irq(&ff->lock); | |
272 | return err; | |
273 | } | |
274 | ||
275 | void snd_ff_stream_lock_release(struct snd_ff *ff) | |
276 | { | |
277 | spin_lock_irq(&ff->lock); | |
278 | ||
279 | if (WARN_ON(ff->dev_lock_count <= 0)) | |
280 | goto end; | |
281 | if (--ff->dev_lock_count == 0) | |
282 | snd_ff_stream_lock_changed(ff); | |
283 | end: | |
284 | spin_unlock_irq(&ff->lock); | |
285 | } |