Commit | Line | Data |
---|---|---|
da607e19 | 1 | // SPDX-License-Identifier: GPL-2.0-only |
35efa5c4 TS |
2 | /* |
3 | * tascam-stream.c - a part of driver for TASCAM FireWire series | |
4 | * | |
5 | * Copyright (c) 2015 Takashi Sakamoto | |
35efa5c4 TS |
6 | */ |
7 | ||
8 | #include <linux/delay.h> | |
9 | #include "tascam.h" | |
10 | ||
e1a00b5b TS |
11 | #define CLOCK_STATUS_MASK 0xffff0000 |
12 | #define CLOCK_CONFIG_MASK 0x0000ffff | |
13 | ||
35efa5c4 TS |
14 | #define CALLBACK_TIMEOUT 500 |
15 | ||
16 | static int get_clock(struct snd_tscm *tscm, u32 *data) | |
17 | { | |
e1a00b5b | 18 | int trial = 0; |
35efa5c4 TS |
19 | __be32 reg; |
20 | int err; | |
21 | ||
e1a00b5b TS |
22 | while (trial++ < 5) { |
23 | err = snd_fw_transaction(tscm->unit, TCODE_READ_QUADLET_REQUEST, | |
24 | TSCM_ADDR_BASE + TSCM_OFFSET_CLOCK_STATUS, | |
25 | ®, sizeof(reg), 0); | |
26 | if (err < 0) | |
27 | return err; | |
28 | ||
35efa5c4 | 29 | *data = be32_to_cpu(reg); |
e1a00b5b TS |
30 | if (*data & CLOCK_STATUS_MASK) |
31 | break; | |
35efa5c4 | 32 | |
e1a00b5b TS |
33 | // In intermediate state after changing clock status. |
34 | msleep(50); | |
35 | } | |
36 | ||
37 | // Still in the intermediate state. | |
38 | if (trial >= 5) | |
39 | return -EAGAIN; | |
40 | ||
41 | return 0; | |
35efa5c4 TS |
42 | } |
43 | ||
44 | static int set_clock(struct snd_tscm *tscm, unsigned int rate, | |
45 | enum snd_tscm_clock clock) | |
46 | { | |
47 | u32 data; | |
48 | __be32 reg; | |
49 | int err; | |
50 | ||
51 | err = get_clock(tscm, &data); | |
52 | if (err < 0) | |
53 | return err; | |
e1a00b5b | 54 | data &= CLOCK_CONFIG_MASK; |
35efa5c4 TS |
55 | |
56 | if (rate > 0) { | |
57 | data &= 0x000000ff; | |
58 | /* Base rate. */ | |
59 | if ((rate % 44100) == 0) { | |
60 | data |= 0x00000100; | |
61 | /* Multiplier. */ | |
62 | if (rate / 44100 == 2) | |
63 | data |= 0x00008000; | |
64 | } else if ((rate % 48000) == 0) { | |
65 | data |= 0x00000200; | |
66 | /* Multiplier. */ | |
67 | if (rate / 48000 == 2) | |
68 | data |= 0x00008000; | |
69 | } else { | |
70 | return -EAGAIN; | |
71 | } | |
72 | } | |
73 | ||
74 | if (clock != INT_MAX) { | |
75 | data &= 0x0000ff00; | |
76 | data |= clock + 1; | |
77 | } | |
78 | ||
79 | reg = cpu_to_be32(data); | |
80 | ||
81 | err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, | |
82 | TSCM_ADDR_BASE + TSCM_OFFSET_CLOCK_STATUS, | |
83 | ®, sizeof(reg), 0); | |
84 | if (err < 0) | |
85 | return err; | |
86 | ||
87 | if (data & 0x00008000) | |
88 | reg = cpu_to_be32(0x0000001a); | |
89 | else | |
90 | reg = cpu_to_be32(0x0000000d); | |
91 | ||
92 | return snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, | |
93 | TSCM_ADDR_BASE + TSCM_OFFSET_MULTIPLEX_MODE, | |
94 | ®, sizeof(reg), 0); | |
95 | } | |
96 | ||
97 | int snd_tscm_stream_get_rate(struct snd_tscm *tscm, unsigned int *rate) | |
98 | { | |
e1a00b5b | 99 | u32 data; |
35efa5c4 TS |
100 | int err; |
101 | ||
e1a00b5b TS |
102 | err = get_clock(tscm, &data); |
103 | if (err < 0) | |
104 | return err; | |
35efa5c4 | 105 | |
e1a00b5b | 106 | data = (data & 0xff000000) >> 24; |
35efa5c4 TS |
107 | |
108 | /* Check base rate. */ | |
109 | if ((data & 0x0f) == 0x01) | |
110 | *rate = 44100; | |
111 | else if ((data & 0x0f) == 0x02) | |
112 | *rate = 48000; | |
113 | else | |
114 | return -EAGAIN; | |
115 | ||
116 | /* Check multiplier. */ | |
117 | if ((data & 0xf0) == 0x80) | |
118 | *rate *= 2; | |
119 | else if ((data & 0xf0) != 0x00) | |
120 | return -EAGAIN; | |
121 | ||
122 | return err; | |
123 | } | |
124 | ||
125 | int snd_tscm_stream_get_clock(struct snd_tscm *tscm, enum snd_tscm_clock *clock) | |
126 | { | |
127 | u32 data; | |
128 | int err; | |
129 | ||
130 | err = get_clock(tscm, &data); | |
131 | if (err < 0) | |
132 | return err; | |
133 | ||
134 | *clock = ((data & 0x00ff0000) >> 16) - 1; | |
135 | if (*clock < 0 || *clock > SND_TSCM_CLOCK_ADAT) | |
136 | return -EIO; | |
137 | ||
138 | return 0; | |
139 | } | |
140 | ||
141 | static int enable_data_channels(struct snd_tscm *tscm) | |
142 | { | |
143 | __be32 reg; | |
144 | u32 data; | |
145 | unsigned int i; | |
146 | int err; | |
147 | ||
148 | data = 0; | |
149 | for (i = 0; i < tscm->spec->pcm_capture_analog_channels; ++i) | |
150 | data |= BIT(i); | |
151 | if (tscm->spec->has_adat) | |
152 | data |= 0x0000ff00; | |
153 | if (tscm->spec->has_spdif) | |
154 | data |= 0x00030000; | |
155 | ||
156 | reg = cpu_to_be32(data); | |
157 | err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, | |
158 | TSCM_ADDR_BASE + TSCM_OFFSET_TX_PCM_CHANNELS, | |
159 | ®, sizeof(reg), 0); | |
160 | if (err < 0) | |
161 | return err; | |
162 | ||
163 | data = 0; | |
164 | for (i = 0; i < tscm->spec->pcm_playback_analog_channels; ++i) | |
165 | data |= BIT(i); | |
166 | if (tscm->spec->has_adat) | |
167 | data |= 0x0000ff00; | |
168 | if (tscm->spec->has_spdif) | |
169 | data |= 0x00030000; | |
170 | ||
171 | reg = cpu_to_be32(data); | |
172 | return snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, | |
173 | TSCM_ADDR_BASE + TSCM_OFFSET_RX_PCM_CHANNELS, | |
174 | ®, sizeof(reg), 0); | |
175 | } | |
176 | ||
177 | static int set_stream_formats(struct snd_tscm *tscm, unsigned int rate) | |
178 | { | |
179 | __be32 reg; | |
180 | int err; | |
181 | ||
07b26642 | 182 | // Set an option for unknown purpose. |
35efa5c4 TS |
183 | reg = cpu_to_be32(0x00200000); |
184 | err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, | |
185 | TSCM_ADDR_BASE + TSCM_OFFSET_SET_OPTION, | |
186 | ®, sizeof(reg), 0); | |
187 | if (err < 0) | |
188 | return err; | |
189 | ||
07b26642 | 190 | return enable_data_channels(tscm); |
35efa5c4 TS |
191 | } |
192 | ||
193 | static void finish_session(struct snd_tscm *tscm) | |
194 | { | |
195 | __be32 reg; | |
196 | ||
197 | reg = 0; | |
198 | snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, | |
199 | TSCM_ADDR_BASE + TSCM_OFFSET_START_STREAMING, | |
200 | ®, sizeof(reg), 0); | |
201 | ||
202 | reg = 0; | |
203 | snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, | |
204 | TSCM_ADDR_BASE + TSCM_OFFSET_ISOC_RX_ON, | |
205 | ®, sizeof(reg), 0); | |
206 | ||
2ef0b7cf TS |
207 | // Unregister channels. |
208 | reg = cpu_to_be32(0x00000000); | |
209 | snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, | |
210 | TSCM_ADDR_BASE + TSCM_OFFSET_ISOC_TX_CH, | |
211 | ®, sizeof(reg), 0); | |
212 | reg = cpu_to_be32(0x00000000); | |
213 | snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, | |
214 | TSCM_ADDR_BASE + TSCM_OFFSET_UNKNOWN, | |
215 | ®, sizeof(reg), 0); | |
216 | reg = cpu_to_be32(0x00000000); | |
217 | snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, | |
218 | TSCM_ADDR_BASE + TSCM_OFFSET_ISOC_RX_CH, | |
219 | ®, sizeof(reg), 0); | |
35efa5c4 TS |
220 | } |
221 | ||
222 | static int begin_session(struct snd_tscm *tscm) | |
223 | { | |
224 | __be32 reg; | |
225 | int err; | |
226 | ||
2ef0b7cf TS |
227 | // Register the isochronous channel for transmitting stream. |
228 | reg = cpu_to_be32(tscm->tx_resources.channel); | |
229 | err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, | |
230 | TSCM_ADDR_BASE + TSCM_OFFSET_ISOC_TX_CH, | |
231 | ®, sizeof(reg), 0); | |
232 | if (err < 0) | |
233 | return err; | |
234 | ||
235 | // Unknown. | |
236 | reg = cpu_to_be32(0x00000002); | |
237 | err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, | |
238 | TSCM_ADDR_BASE + TSCM_OFFSET_UNKNOWN, | |
239 | ®, sizeof(reg), 0); | |
240 | if (err < 0) | |
241 | return err; | |
242 | ||
243 | // Register the isochronous channel for receiving stream. | |
244 | reg = cpu_to_be32(tscm->rx_resources.channel); | |
245 | err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, | |
246 | TSCM_ADDR_BASE + TSCM_OFFSET_ISOC_RX_CH, | |
247 | ®, sizeof(reg), 0); | |
248 | if (err < 0) | |
249 | return err; | |
250 | ||
35efa5c4 TS |
251 | reg = cpu_to_be32(0x00000001); |
252 | err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, | |
253 | TSCM_ADDR_BASE + TSCM_OFFSET_START_STREAMING, | |
254 | ®, sizeof(reg), 0); | |
255 | if (err < 0) | |
256 | return err; | |
257 | ||
258 | reg = cpu_to_be32(0x00000001); | |
259 | err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, | |
260 | TSCM_ADDR_BASE + TSCM_OFFSET_ISOC_RX_ON, | |
261 | ®, sizeof(reg), 0); | |
262 | if (err < 0) | |
263 | return err; | |
264 | ||
2ef0b7cf | 265 | // Set an option for unknown purpose. |
35efa5c4 TS |
266 | reg = cpu_to_be32(0x00002000); |
267 | err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, | |
268 | TSCM_ADDR_BASE + TSCM_OFFSET_SET_OPTION, | |
269 | ®, sizeof(reg), 0); | |
270 | if (err < 0) | |
271 | return err; | |
272 | ||
2ef0b7cf | 273 | // Start multiplexing PCM samples on packets. |
35efa5c4 TS |
274 | reg = cpu_to_be32(0x00000001); |
275 | return snd_fw_transaction(tscm->unit, | |
276 | TCODE_WRITE_QUADLET_REQUEST, | |
277 | TSCM_ADDR_BASE + TSCM_OFFSET_ISOC_TX_ON, | |
278 | ®, sizeof(reg), 0); | |
279 | } | |
280 | ||
810b37ff TS |
281 | static int keep_resources(struct snd_tscm *tscm, unsigned int rate, |
282 | struct amdtp_stream *stream) | |
35efa5c4 | 283 | { |
810b37ff | 284 | struct fw_iso_resources *resources; |
35efa5c4 TS |
285 | int err; |
286 | ||
810b37ff TS |
287 | if (stream == &tscm->tx_stream) |
288 | resources = &tscm->tx_resources; | |
289 | else | |
290 | resources = &tscm->rx_resources; | |
35efa5c4 | 291 | |
810b37ff | 292 | err = amdtp_tscm_set_parameters(stream, rate); |
35efa5c4 TS |
293 | if (err < 0) |
294 | return err; | |
295 | ||
810b37ff TS |
296 | return fw_iso_resources_allocate(resources, |
297 | amdtp_stream_get_max_payload(stream), | |
298 | fw_parent_device(tscm->unit)->max_speed); | |
35efa5c4 TS |
299 | } |
300 | ||
5f9625a5 | 301 | static int init_stream(struct snd_tscm *tscm, struct amdtp_stream *s) |
35efa5c4 | 302 | { |
5f9625a5 TS |
303 | struct fw_iso_resources *resources; |
304 | enum amdtp_stream_direction dir; | |
35efa5c4 TS |
305 | unsigned int pcm_channels; |
306 | int err; | |
307 | ||
5f9625a5 TS |
308 | if (s == &tscm->tx_stream) { |
309 | resources = &tscm->tx_resources; | |
310 | dir = AMDTP_IN_STREAM; | |
311 | pcm_channels = tscm->spec->pcm_capture_analog_channels; | |
312 | } else { | |
313 | resources = &tscm->rx_resources; | |
314 | dir = AMDTP_OUT_STREAM; | |
315 | pcm_channels = tscm->spec->pcm_playback_analog_channels; | |
316 | } | |
317 | ||
35efa5c4 TS |
318 | if (tscm->spec->has_adat) |
319 | pcm_channels += 8; | |
320 | if (tscm->spec->has_spdif) | |
321 | pcm_channels += 2; | |
5f9625a5 TS |
322 | |
323 | err = fw_iso_resources_init(resources, tscm->unit); | |
35efa5c4 TS |
324 | if (err < 0) |
325 | return err; | |
326 | ||
5f9625a5 | 327 | err = amdtp_tscm_init(s, tscm->unit, dir, pcm_channels); |
35efa5c4 | 328 | if (err < 0) |
5f9625a5 TS |
329 | fw_iso_resources_free(resources); |
330 | ||
331 | return err; | |
332 | } | |
333 | ||
334 | static void destroy_stream(struct snd_tscm *tscm, struct amdtp_stream *s) | |
335 | { | |
336 | amdtp_stream_destroy(s); | |
337 | ||
338 | if (s == &tscm->tx_stream) | |
339 | fw_iso_resources_destroy(&tscm->tx_resources); | |
340 | else | |
341 | fw_iso_resources_destroy(&tscm->rx_resources); | |
342 | } | |
343 | ||
344 | int snd_tscm_stream_init_duplex(struct snd_tscm *tscm) | |
345 | { | |
346 | int err; | |
347 | ||
348 | err = init_stream(tscm, &tscm->tx_stream); | |
35efa5c4 | 349 | if (err < 0) |
35efa5c4 | 350 | return err; |
5f9625a5 TS |
351 | |
352 | err = init_stream(tscm, &tscm->rx_stream); | |
c281d46a TS |
353 | if (err < 0) { |
354 | destroy_stream(tscm, &tscm->tx_stream); | |
355 | return err; | |
356 | } | |
357 | ||
358 | err = amdtp_domain_init(&tscm->domain); | |
359 | if (err < 0) { | |
5f9625a5 | 360 | destroy_stream(tscm, &tscm->tx_stream); |
c281d46a TS |
361 | destroy_stream(tscm, &tscm->rx_stream); |
362 | } | |
35efa5c4 | 363 | |
6a2a2f45 | 364 | return err; |
35efa5c4 TS |
365 | } |
366 | ||
a0c049a6 | 367 | // At bus reset, streaming is stopped and some registers are clear. |
35efa5c4 TS |
368 | void snd_tscm_stream_update_duplex(struct snd_tscm *tscm) |
369 | { | |
c281d46a | 370 | amdtp_domain_stop(&tscm->domain); |
35efa5c4 | 371 | |
c281d46a | 372 | amdtp_stream_pcm_abort(&tscm->tx_stream); |
35efa5c4 | 373 | amdtp_stream_pcm_abort(&tscm->rx_stream); |
35efa5c4 TS |
374 | } |
375 | ||
5f9625a5 TS |
376 | // This function should be called before starting streams or after stopping |
377 | // streams. | |
35efa5c4 TS |
378 | void snd_tscm_stream_destroy_duplex(struct snd_tscm *tscm) |
379 | { | |
c281d46a | 380 | amdtp_domain_destroy(&tscm->domain); |
35efa5c4 | 381 | |
5f9625a5 TS |
382 | destroy_stream(tscm, &tscm->rx_stream); |
383 | destroy_stream(tscm, &tscm->tx_stream); | |
35efa5c4 TS |
384 | } |
385 | ||
07b26642 | 386 | int snd_tscm_stream_reserve_duplex(struct snd_tscm *tscm, unsigned int rate) |
35efa5c4 TS |
387 | { |
388 | unsigned int curr_rate; | |
389 | int err; | |
390 | ||
35efa5c4 TS |
391 | err = snd_tscm_stream_get_rate(tscm, &curr_rate); |
392 | if (err < 0) | |
393 | return err; | |
35efa5c4 | 394 | |
07b26642 | 395 | if (tscm->substreams_counter == 0 || rate != curr_rate) { |
c281d46a TS |
396 | amdtp_domain_stop(&tscm->domain); |
397 | ||
07b26642 TS |
398 | finish_session(tscm); |
399 | ||
a364af2e TS |
400 | fw_iso_resources_free(&tscm->tx_resources); |
401 | fw_iso_resources_free(&tscm->rx_resources); | |
35efa5c4 | 402 | |
07b26642 TS |
403 | err = set_clock(tscm, rate, INT_MAX); |
404 | if (err < 0) | |
405 | return err; | |
406 | ||
810b37ff TS |
407 | err = keep_resources(tscm, rate, &tscm->tx_stream); |
408 | if (err < 0) | |
07b26642 | 409 | return err; |
810b37ff TS |
410 | |
411 | err = keep_resources(tscm, rate, &tscm->rx_stream); | |
07b26642 TS |
412 | if (err < 0) { |
413 | fw_iso_resources_free(&tscm->tx_resources); | |
414 | return err; | |
415 | } | |
416 | } | |
417 | ||
418 | return 0; | |
419 | } | |
420 | ||
07b26642 TS |
421 | int snd_tscm_stream_start_duplex(struct snd_tscm *tscm, unsigned int rate) |
422 | { | |
a0c049a6 | 423 | unsigned int generation = tscm->rx_resources.generation; |
07b26642 TS |
424 | int err; |
425 | ||
426 | if (tscm->substreams_counter == 0) | |
427 | return 0; | |
428 | ||
429 | if (amdtp_streaming_error(&tscm->rx_stream) || | |
c281d46a TS |
430 | amdtp_streaming_error(&tscm->tx_stream)) { |
431 | amdtp_domain_stop(&tscm->domain); | |
07b26642 | 432 | finish_session(tscm); |
c281d46a | 433 | } |
07b26642 | 434 | |
a0c049a6 TS |
435 | if (generation != fw_parent_device(tscm->unit)->card->generation) { |
436 | err = fw_iso_resources_update(&tscm->tx_resources); | |
437 | if (err < 0) | |
438 | goto error; | |
439 | ||
440 | err = fw_iso_resources_update(&tscm->rx_resources); | |
441 | if (err < 0) | |
442 | goto error; | |
443 | } | |
444 | ||
07b26642 | 445 | if (!amdtp_stream_running(&tscm->rx_stream)) { |
c281d46a TS |
446 | int spd = fw_parent_device(tscm->unit)->max_speed; |
447 | ||
35efa5c4 TS |
448 | err = set_stream_formats(tscm, rate); |
449 | if (err < 0) | |
450 | goto error; | |
451 | ||
452 | err = begin_session(tscm); | |
453 | if (err < 0) | |
454 | goto error; | |
455 | ||
c281d46a TS |
456 | err = amdtp_domain_add_stream(&tscm->domain, &tscm->rx_stream, |
457 | tscm->rx_resources.channel, spd); | |
35efa5c4 TS |
458 | if (err < 0) |
459 | goto error; | |
460 | ||
c281d46a TS |
461 | err = amdtp_domain_add_stream(&tscm->domain, &tscm->tx_stream, |
462 | tscm->tx_resources.channel, spd); | |
463 | if (err < 0) | |
35efa5c4 | 464 | goto error; |
35efa5c4 | 465 | |
c281d46a | 466 | err = amdtp_domain_start(&tscm->domain); |
35efa5c4 | 467 | if (err < 0) |
c281d46a | 468 | return err; |
35efa5c4 | 469 | |
c281d46a TS |
470 | if (!amdtp_stream_wait_callback(&tscm->rx_stream, |
471 | CALLBACK_TIMEOUT) || | |
472 | !amdtp_stream_wait_callback(&tscm->tx_stream, | |
35efa5c4 TS |
473 | CALLBACK_TIMEOUT)) { |
474 | err = -ETIMEDOUT; | |
475 | goto error; | |
476 | } | |
477 | } | |
478 | ||
479 | return 0; | |
480 | error: | |
c281d46a | 481 | amdtp_domain_stop(&tscm->domain); |
35efa5c4 | 482 | finish_session(tscm); |
a364af2e | 483 | |
35efa5c4 TS |
484 | return err; |
485 | } | |
486 | ||
487 | void snd_tscm_stream_stop_duplex(struct snd_tscm *tscm) | |
488 | { | |
9628fc82 | 489 | if (tscm->substreams_counter == 0) { |
c281d46a | 490 | amdtp_domain_stop(&tscm->domain); |
07b26642 | 491 | finish_session(tscm); |
9628fc82 TS |
492 | |
493 | fw_iso_resources_free(&tscm->tx_resources); | |
494 | fw_iso_resources_free(&tscm->rx_resources); | |
495 | } | |
35efa5c4 | 496 | } |
e5e0c3dd TS |
497 | |
498 | void snd_tscm_stream_lock_changed(struct snd_tscm *tscm) | |
499 | { | |
500 | tscm->dev_lock_changed = true; | |
501 | wake_up(&tscm->hwdep_wait); | |
502 | } | |
503 | ||
504 | int snd_tscm_stream_lock_try(struct snd_tscm *tscm) | |
505 | { | |
506 | int err; | |
507 | ||
508 | spin_lock_irq(&tscm->lock); | |
509 | ||
510 | /* user land lock this */ | |
511 | if (tscm->dev_lock_count < 0) { | |
512 | err = -EBUSY; | |
513 | goto end; | |
514 | } | |
515 | ||
516 | /* this is the first time */ | |
517 | if (tscm->dev_lock_count++ == 0) | |
518 | snd_tscm_stream_lock_changed(tscm); | |
519 | err = 0; | |
520 | end: | |
521 | spin_unlock_irq(&tscm->lock); | |
522 | return err; | |
523 | } | |
524 | ||
525 | void snd_tscm_stream_lock_release(struct snd_tscm *tscm) | |
526 | { | |
527 | spin_lock_irq(&tscm->lock); | |
528 | ||
529 | if (WARN_ON(tscm->dev_lock_count <= 0)) | |
530 | goto end; | |
531 | if (--tscm->dev_lock_count == 0) | |
532 | snd_tscm_stream_lock_changed(tscm); | |
533 | end: | |
534 | spin_unlock_irq(&tscm->lock); | |
535 | } |