Commit | Line | Data |
---|---|---|
6eb6c81e TS |
1 | /* |
2 | * dice_stream.c - a part of driver for DICE based devices | |
3 | * | |
4 | * Copyright (c) Clemens Ladisch <clemens@ladisch.de> | |
5 | * Copyright (c) 2014 Takashi Sakamoto <o-takashi@sakamocchi.jp> | |
6 | * | |
7 | * Licensed under the terms of the GNU General Public License, version 2. | |
8 | */ | |
9 | ||
10 | #include "dice.h" | |
11 | ||
288a8d0c | 12 | #define CALLBACK_TIMEOUT 200 |
dfabc0ee | 13 | #define NOTIFICATION_TIMEOUT_MS (2 * MSEC_PER_SEC) |
288a8d0c | 14 | |
8cc1a8ab TS |
15 | struct reg_params { |
16 | unsigned int count; | |
17 | unsigned int size; | |
18 | }; | |
19 | ||
6eb6c81e TS |
20 | const unsigned int snd_dice_rates[SND_DICE_RATES_COUNT] = { |
21 | /* mode 0 */ | |
22 | [0] = 32000, | |
23 | [1] = 44100, | |
24 | [2] = 48000, | |
25 | /* mode 1 */ | |
26 | [3] = 88200, | |
27 | [4] = 96000, | |
28 | /* mode 2 */ | |
29 | [5] = 176400, | |
30 | [6] = 192000, | |
31 | }; | |
32 | ||
dfabc0ee TS |
33 | /* |
34 | * This operation has an effect to synchronize GLOBAL_STATUS/GLOBAL_SAMPLE_RATE | |
35 | * to GLOBAL_STATUS. Especially, just after powering on, these are different. | |
36 | */ | |
37 | static int ensure_phase_lock(struct snd_dice *dice) | |
38 | { | |
fbeac84d | 39 | __be32 reg, nominal; |
dfabc0ee TS |
40 | int err; |
41 | ||
42 | err = snd_dice_transaction_read_global(dice, GLOBAL_CLOCK_SELECT, | |
43 | ®, sizeof(reg)); | |
44 | if (err < 0) | |
45 | return err; | |
46 | ||
47 | if (completion_done(&dice->clock_accepted)) | |
48 | reinit_completion(&dice->clock_accepted); | |
49 | ||
50 | err = snd_dice_transaction_write_global(dice, GLOBAL_CLOCK_SELECT, | |
51 | ®, sizeof(reg)); | |
52 | if (err < 0) | |
53 | return err; | |
54 | ||
55 | if (wait_for_completion_timeout(&dice->clock_accepted, | |
fbeac84d TS |
56 | msecs_to_jiffies(NOTIFICATION_TIMEOUT_MS)) == 0) { |
57 | /* | |
58 | * Old versions of Dice firmware transfer no notification when | |
59 | * the same clock status as current one is set. In this case, | |
60 | * just check current clock status. | |
61 | */ | |
62 | err = snd_dice_transaction_read_global(dice, GLOBAL_STATUS, | |
63 | &nominal, sizeof(nominal)); | |
64 | if (err < 0) | |
65 | return err; | |
66 | if (!(be32_to_cpu(nominal) & STATUS_SOURCE_LOCKED)) | |
67 | return -ETIMEDOUT; | |
68 | } | |
dfabc0ee TS |
69 | |
70 | return 0; | |
71 | } | |
72 | ||
8cc1a8ab TS |
73 | static int get_register_params(struct snd_dice *dice, |
74 | struct reg_params *tx_params, | |
75 | struct reg_params *rx_params) | |
6eb6c81e | 76 | { |
436b5abe | 77 | __be32 reg[2]; |
6eb6c81e TS |
78 | int err; |
79 | ||
436b5abe | 80 | err = snd_dice_transaction_read_tx(dice, TX_NUMBER, reg, sizeof(reg)); |
6eb6c81e | 81 | if (err < 0) |
436b5abe | 82 | return err; |
8cc1a8ab TS |
83 | tx_params->count = |
84 | min_t(unsigned int, be32_to_cpu(reg[0]), MAX_STREAMS); | |
85 | tx_params->size = be32_to_cpu(reg[1]) * 4; | |
6eb6c81e | 86 | |
436b5abe | 87 | err = snd_dice_transaction_read_rx(dice, RX_NUMBER, reg, sizeof(reg)); |
288a8d0c | 88 | if (err < 0) |
436b5abe | 89 | return err; |
8cc1a8ab TS |
90 | rx_params->count = |
91 | min_t(unsigned int, be32_to_cpu(reg[0]), MAX_STREAMS); | |
92 | rx_params->size = be32_to_cpu(reg[1]) * 4; | |
436b5abe TS |
93 | |
94 | return 0; | |
6eb6c81e TS |
95 | } |
96 | ||
436b5abe | 97 | static void release_resources(struct snd_dice *dice) |
6eb6c81e | 98 | { |
436b5abe TS |
99 | unsigned int i; |
100 | ||
101 | for (i = 0; i < MAX_STREAMS; i++) { | |
102 | if (amdtp_stream_running(&dice->tx_stream[i])) { | |
103 | amdtp_stream_pcm_abort(&dice->tx_stream[i]); | |
104 | amdtp_stream_stop(&dice->tx_stream[i]); | |
105 | } | |
106 | if (amdtp_stream_running(&dice->rx_stream[i])) { | |
107 | amdtp_stream_pcm_abort(&dice->rx_stream[i]); | |
108 | amdtp_stream_stop(&dice->rx_stream[i]); | |
109 | } | |
c50fb91f | 110 | |
436b5abe TS |
111 | fw_iso_resources_free(&dice->tx_resources[i]); |
112 | fw_iso_resources_free(&dice->rx_resources[i]); | |
113 | } | |
6eb6c81e TS |
114 | } |
115 | ||
436b5abe | 116 | static void stop_streams(struct snd_dice *dice, enum amdtp_stream_direction dir, |
8cc1a8ab | 117 | struct reg_params *params) |
6eb6c81e | 118 | { |
436b5abe TS |
119 | __be32 reg; |
120 | unsigned int i; | |
121 | ||
8cc1a8ab | 122 | for (i = 0; i < params->count; i++) { |
436b5abe TS |
123 | reg = cpu_to_be32((u32)-1); |
124 | if (dir == AMDTP_IN_STREAM) { | |
125 | snd_dice_transaction_write_tx(dice, | |
8cc1a8ab TS |
126 | params->size * i + TX_ISOCHRONOUS, |
127 | ®, sizeof(reg)); | |
436b5abe TS |
128 | } else { |
129 | snd_dice_transaction_write_rx(dice, | |
8cc1a8ab TS |
130 | params->size * i + RX_ISOCHRONOUS, |
131 | ®, sizeof(reg)); | |
436b5abe TS |
132 | } |
133 | } | |
134 | } | |
135 | ||
136 | static int keep_resources(struct snd_dice *dice, | |
137 | enum amdtp_stream_direction dir, unsigned int index, | |
138 | unsigned int rate, unsigned int pcm_chs, | |
139 | unsigned int midi_ports) | |
140 | { | |
141 | struct amdtp_stream *stream; | |
9a02843c | 142 | struct fw_iso_resources *resources; |
27ec83b5 | 143 | bool double_pcm_frames; |
436b5abe | 144 | unsigned int i; |
288a8d0c | 145 | int err; |
6eb6c81e | 146 | |
436b5abe TS |
147 | if (dir == AMDTP_IN_STREAM) { |
148 | stream = &dice->tx_stream[index]; | |
149 | resources = &dice->tx_resources[index]; | |
9a02843c | 150 | } else { |
436b5abe TS |
151 | stream = &dice->rx_stream[index]; |
152 | resources = &dice->rx_resources[index]; | |
9a02843c | 153 | } |
6eb6c81e | 154 | |
288a8d0c TS |
155 | /* |
156 | * At 176.4/192.0 kHz, Dice has a quirk to transfer two PCM frames in | |
157 | * one data block of AMDTP packet. Thus sampling transfer frequency is | |
158 | * a half of PCM sampling frequency, i.e. PCM frames at 192.0 kHz are | |
159 | * transferred on AMDTP packets at 96 kHz. Two successive samples of a | |
160 | * channel are stored consecutively in the packet. This quirk is called | |
161 | * as 'Dual Wire'. | |
162 | * For this quirk, blocking mode is required and PCM buffer size should | |
163 | * be aligned to SYT_INTERVAL. | |
164 | */ | |
6f688268 | 165 | double_pcm_frames = rate > 96000; |
27ec83b5 | 166 | if (double_pcm_frames) { |
288a8d0c TS |
167 | rate /= 2; |
168 | pcm_chs *= 2; | |
288a8d0c | 169 | } |
6eb6c81e | 170 | |
51c29fd2 TS |
171 | err = amdtp_am824_set_parameters(stream, rate, pcm_chs, midi_ports, |
172 | double_pcm_frames); | |
547e631c | 173 | if (err < 0) |
436b5abe | 174 | return err; |
547e631c | 175 | |
27ec83b5 | 176 | if (double_pcm_frames) { |
288a8d0c | 177 | pcm_chs /= 2; |
6eb6c81e | 178 | |
288a8d0c | 179 | for (i = 0; i < pcm_chs; i++) { |
f65be911 TS |
180 | amdtp_am824_set_pcm_position(stream, i, i * 2); |
181 | amdtp_am824_set_pcm_position(stream, i + pcm_chs, | |
182 | i * 2 + 1); | |
288a8d0c TS |
183 | } |
184 | } | |
185 | ||
436b5abe TS |
186 | return fw_iso_resources_allocate(resources, |
187 | amdtp_stream_get_max_payload(stream), | |
188 | fw_parent_device(dice->unit)->max_speed); | |
189 | } | |
190 | ||
191 | static int start_streams(struct snd_dice *dice, enum amdtp_stream_direction dir, | |
8cc1a8ab | 192 | unsigned int rate, struct reg_params *params) |
436b5abe TS |
193 | { |
194 | __be32 reg[2]; | |
195 | unsigned int i, pcm_chs, midi_ports; | |
196 | struct amdtp_stream *streams; | |
197 | struct fw_iso_resources *resources; | |
198 | int err = 0; | |
199 | ||
200 | if (dir == AMDTP_IN_STREAM) { | |
201 | streams = dice->tx_stream; | |
202 | resources = dice->tx_resources; | |
203 | } else { | |
204 | streams = dice->rx_stream; | |
205 | resources = dice->rx_resources; | |
206 | } | |
207 | ||
8cc1a8ab | 208 | for (i = 0; i < params->count; i++) { |
436b5abe TS |
209 | if (dir == AMDTP_IN_STREAM) { |
210 | err = snd_dice_transaction_read_tx(dice, | |
8cc1a8ab TS |
211 | params->size * i + TX_NUMBER_AUDIO, |
212 | reg, sizeof(reg)); | |
436b5abe TS |
213 | } else { |
214 | err = snd_dice_transaction_read_rx(dice, | |
8cc1a8ab TS |
215 | params->size * i + RX_NUMBER_AUDIO, |
216 | reg, sizeof(reg)); | |
436b5abe TS |
217 | } |
218 | if (err < 0) | |
219 | return err; | |
220 | pcm_chs = be32_to_cpu(reg[0]); | |
221 | midi_ports = be32_to_cpu(reg[1]); | |
222 | ||
223 | err = keep_resources(dice, dir, i, rate, pcm_chs, midi_ports); | |
224 | if (err < 0) | |
225 | return err; | |
226 | ||
227 | reg[0] = cpu_to_be32(resources[i].channel); | |
228 | if (dir == AMDTP_IN_STREAM) { | |
229 | err = snd_dice_transaction_write_tx(dice, | |
8cc1a8ab TS |
230 | params->size * i + TX_ISOCHRONOUS, |
231 | reg, sizeof(reg[0])); | |
436b5abe TS |
232 | } else { |
233 | err = snd_dice_transaction_write_rx(dice, | |
8cc1a8ab TS |
234 | params->size * i + RX_ISOCHRONOUS, |
235 | reg, sizeof(reg[0])); | |
436b5abe TS |
236 | } |
237 | if (err < 0) | |
238 | return err; | |
239 | ||
240 | err = amdtp_stream_start(&streams[i], resources[i].channel, | |
241 | fw_parent_device(dice->unit)->max_speed); | |
242 | if (err < 0) | |
243 | return err; | |
288a8d0c TS |
244 | } |
245 | ||
288a8d0c TS |
246 | return err; |
247 | } | |
248 | ||
436b5abe TS |
249 | /* |
250 | * MEMO: After this function, there're two states of streams: | |
251 | * - None streams are running. | |
252 | * - All streams are running. | |
253 | */ | |
9a02843c | 254 | int snd_dice_stream_start_duplex(struct snd_dice *dice, unsigned int rate) |
288a8d0c TS |
255 | { |
256 | unsigned int curr_rate; | |
436b5abe | 257 | unsigned int i; |
8cc1a8ab | 258 | struct reg_params tx_params, rx_params; |
436b5abe TS |
259 | bool need_to_start; |
260 | int err; | |
9a02843c TS |
261 | |
262 | if (dice->substreams_counter == 0) | |
436b5abe | 263 | return -EIO; |
288a8d0c | 264 | |
8cc1a8ab | 265 | err = get_register_params(dice, &tx_params, &rx_params); |
436b5abe TS |
266 | if (err < 0) |
267 | return err; | |
288a8d0c | 268 | |
288a8d0c TS |
269 | err = snd_dice_transaction_get_rate(dice, &curr_rate); |
270 | if (err < 0) { | |
271 | dev_err(&dice->unit->device, | |
272 | "fail to get sampling rate\n"); | |
436b5abe | 273 | return err; |
288a8d0c | 274 | } |
a113ff88 TS |
275 | if (rate == 0) |
276 | rate = curr_rate; | |
436b5abe TS |
277 | if (rate != curr_rate) |
278 | return -EINVAL; | |
279 | ||
280 | /* Judge to need to restart streams. */ | |
281 | for (i = 0; i < MAX_STREAMS; i++) { | |
8cc1a8ab | 282 | if (i < tx_params.count) { |
436b5abe TS |
283 | if (amdtp_streaming_error(&dice->tx_stream[i]) || |
284 | !amdtp_stream_running(&dice->tx_stream[i])) | |
285 | break; | |
286 | } | |
8cc1a8ab | 287 | if (i < rx_params.count) { |
436b5abe TS |
288 | if (amdtp_streaming_error(&dice->rx_stream[i]) || |
289 | !amdtp_stream_running(&dice->rx_stream[i])) | |
290 | break; | |
291 | } | |
1bc8e12d | 292 | } |
436b5abe | 293 | need_to_start = (i < MAX_STREAMS); |
288a8d0c | 294 | |
436b5abe TS |
295 | if (need_to_start) { |
296 | /* Stop transmission. */ | |
288a8d0c | 297 | snd_dice_transaction_clear_enable(dice); |
8cc1a8ab TS |
298 | stop_streams(dice, AMDTP_IN_STREAM, &tx_params); |
299 | stop_streams(dice, AMDTP_OUT_STREAM, &rx_params); | |
436b5abe | 300 | release_resources(dice); |
288a8d0c | 301 | |
dfabc0ee | 302 | err = ensure_phase_lock(dice); |
288a8d0c TS |
303 | if (err < 0) { |
304 | dev_err(&dice->unit->device, | |
dfabc0ee | 305 | "fail to ensure phase lock\n"); |
436b5abe | 306 | return err; |
288a8d0c TS |
307 | } |
308 | ||
9a02843c | 309 | /* Start both streams. */ |
8cc1a8ab | 310 | err = start_streams(dice, AMDTP_IN_STREAM, rate, &tx_params); |
436b5abe TS |
311 | if (err < 0) |
312 | goto error; | |
8cc1a8ab | 313 | err = start_streams(dice, AMDTP_OUT_STREAM, rate, &rx_params); |
436b5abe TS |
314 | if (err < 0) |
315 | goto error; | |
316 | ||
288a8d0c TS |
317 | err = snd_dice_transaction_set_enable(dice); |
318 | if (err < 0) { | |
319 | dev_err(&dice->unit->device, | |
320 | "fail to enable interface\n"); | |
436b5abe | 321 | goto error; |
288a8d0c TS |
322 | } |
323 | ||
436b5abe | 324 | for (i = 0; i < MAX_STREAMS; i++) { |
8cc1a8ab | 325 | if ((i < tx_params.count && |
436b5abe TS |
326 | !amdtp_stream_wait_callback(&dice->tx_stream[i], |
327 | CALLBACK_TIMEOUT)) || | |
8cc1a8ab | 328 | (i < rx_params.count && |
436b5abe TS |
329 | !amdtp_stream_wait_callback(&dice->rx_stream[i], |
330 | CALLBACK_TIMEOUT))) { | |
331 | err = -ETIMEDOUT; | |
332 | goto error; | |
333 | } | |
288a8d0c TS |
334 | } |
335 | } | |
436b5abe TS |
336 | |
337 | return err; | |
338 | error: | |
339 | snd_dice_transaction_clear_enable(dice); | |
8cc1a8ab TS |
340 | stop_streams(dice, AMDTP_IN_STREAM, &tx_params); |
341 | stop_streams(dice, AMDTP_OUT_STREAM, &rx_params); | |
436b5abe | 342 | release_resources(dice); |
288a8d0c TS |
343 | return err; |
344 | } | |
345 | ||
436b5abe TS |
346 | /* |
347 | * MEMO: After this function, there're two states of streams: | |
348 | * - None streams are running. | |
349 | * - All streams are running. | |
350 | */ | |
9a02843c | 351 | void snd_dice_stream_stop_duplex(struct snd_dice *dice) |
288a8d0c | 352 | { |
8cc1a8ab | 353 | struct reg_params tx_params, rx_params; |
436b5abe | 354 | |
9a02843c TS |
355 | if (dice->substreams_counter > 0) |
356 | return; | |
357 | ||
288a8d0c | 358 | snd_dice_transaction_clear_enable(dice); |
9a02843c | 359 | |
8cc1a8ab TS |
360 | if (get_register_params(dice, &tx_params, &rx_params) == 0) { |
361 | stop_streams(dice, AMDTP_IN_STREAM, &tx_params); | |
362 | stop_streams(dice, AMDTP_OUT_STREAM, &rx_params); | |
436b5abe TS |
363 | } |
364 | ||
365 | release_resources(dice); | |
6eb6c81e TS |
366 | } |
367 | ||
436b5abe TS |
368 | static int init_stream(struct snd_dice *dice, enum amdtp_stream_direction dir, |
369 | unsigned int index) | |
6eb6c81e | 370 | { |
436b5abe | 371 | struct amdtp_stream *stream; |
9a02843c | 372 | struct fw_iso_resources *resources; |
436b5abe | 373 | int err; |
9a02843c | 374 | |
436b5abe TS |
375 | if (dir == AMDTP_IN_STREAM) { |
376 | stream = &dice->tx_stream[index]; | |
377 | resources = &dice->tx_resources[index]; | |
9a02843c | 378 | } else { |
436b5abe TS |
379 | stream = &dice->rx_stream[index]; |
380 | resources = &dice->rx_resources[index]; | |
9a02843c | 381 | } |
6eb6c81e | 382 | |
9a02843c | 383 | err = fw_iso_resources_init(resources, dice->unit); |
6eb6c81e TS |
384 | if (err < 0) |
385 | goto end; | |
9a02843c | 386 | resources->channels_mask = 0x00000000ffffffffuLL; |
6eb6c81e | 387 | |
5955815e | 388 | err = amdtp_am824_init(stream, dice->unit, dir, CIP_BLOCKING); |
9a02843c TS |
389 | if (err < 0) { |
390 | amdtp_stream_destroy(stream); | |
391 | fw_iso_resources_destroy(resources); | |
392 | } | |
393 | end: | |
394 | return err; | |
395 | } | |
396 | ||
d23c2cc4 TS |
397 | /* |
398 | * This function should be called before starting streams or after stopping | |
399 | * streams. | |
400 | */ | |
436b5abe TS |
401 | static void destroy_stream(struct snd_dice *dice, |
402 | enum amdtp_stream_direction dir, | |
403 | unsigned int index) | |
9a02843c | 404 | { |
436b5abe | 405 | struct amdtp_stream *stream; |
d23c2cc4 | 406 | struct fw_iso_resources *resources; |
9a02843c | 407 | |
436b5abe TS |
408 | if (dir == AMDTP_IN_STREAM) { |
409 | stream = &dice->tx_stream[index]; | |
410 | resources = &dice->tx_resources[index]; | |
411 | } else { | |
412 | stream = &dice->rx_stream[index]; | |
413 | resources = &dice->rx_resources[index]; | |
414 | } | |
d23c2cc4 TS |
415 | |
416 | amdtp_stream_destroy(stream); | |
417 | fw_iso_resources_destroy(resources); | |
9a02843c TS |
418 | } |
419 | ||
420 | int snd_dice_stream_init_duplex(struct snd_dice *dice) | |
421 | { | |
436b5abe | 422 | int i, err; |
9a02843c | 423 | |
436b5abe TS |
424 | for (i = 0; i < MAX_STREAMS; i++) { |
425 | err = init_stream(dice, AMDTP_IN_STREAM, i); | |
426 | if (err < 0) { | |
427 | for (; i >= 0; i--) | |
428 | destroy_stream(dice, AMDTP_OUT_STREAM, i); | |
429 | goto end; | |
430 | } | |
431 | } | |
6eb6c81e | 432 | |
436b5abe TS |
433 | for (i = 0; i < MAX_STREAMS; i++) { |
434 | err = init_stream(dice, AMDTP_OUT_STREAM, i); | |
435 | if (err < 0) { | |
436 | for (; i >= 0; i--) | |
437 | destroy_stream(dice, AMDTP_OUT_STREAM, i); | |
438 | for (i = 0; i < MAX_STREAMS; i++) | |
439 | destroy_stream(dice, AMDTP_IN_STREAM, i); | |
440 | break; | |
441 | } | |
442 | } | |
6eb6c81e TS |
443 | end: |
444 | return err; | |
6eb6c81e TS |
445 | } |
446 | ||
9a02843c | 447 | void snd_dice_stream_destroy_duplex(struct snd_dice *dice) |
6eb6c81e | 448 | { |
6b94fb14 | 449 | unsigned int i; |
9a02843c | 450 | |
6b94fb14 TS |
451 | for (i = 0; i < MAX_STREAMS; i++) { |
452 | destroy_stream(dice, AMDTP_IN_STREAM, i); | |
453 | destroy_stream(dice, AMDTP_OUT_STREAM, i); | |
436b5abe | 454 | } |
6eb6c81e TS |
455 | } |
456 | ||
9a02843c | 457 | void snd_dice_stream_update_duplex(struct snd_dice *dice) |
6eb6c81e | 458 | { |
8cc1a8ab | 459 | struct reg_params tx_params, rx_params; |
436b5abe | 460 | |
6eb6c81e TS |
461 | /* |
462 | * On a bus reset, the DICE firmware disables streaming and then goes | |
463 | * off contemplating its own navel for hundreds of milliseconds before | |
464 | * it can react to any of our attempts to reenable streaming. This | |
465 | * means that we lose synchronization anyway, so we force our streams | |
466 | * to stop so that the application can restart them in an orderly | |
467 | * manner. | |
468 | */ | |
469 | dice->global_enabled = false; | |
470 | ||
8cc1a8ab TS |
471 | if (get_register_params(dice, &tx_params, &rx_params) == 0) { |
472 | stop_streams(dice, AMDTP_IN_STREAM, &tx_params); | |
473 | stop_streams(dice, AMDTP_OUT_STREAM, &rx_params); | |
436b5abe | 474 | } |
6eb6c81e TS |
475 | } |
476 | ||
477 | static void dice_lock_changed(struct snd_dice *dice) | |
478 | { | |
479 | dice->dev_lock_changed = true; | |
480 | wake_up(&dice->hwdep_wait); | |
481 | } | |
482 | ||
483 | int snd_dice_stream_lock_try(struct snd_dice *dice) | |
484 | { | |
485 | int err; | |
486 | ||
487 | spin_lock_irq(&dice->lock); | |
488 | ||
489 | if (dice->dev_lock_count < 0) { | |
490 | err = -EBUSY; | |
491 | goto out; | |
492 | } | |
493 | ||
494 | if (dice->dev_lock_count++ == 0) | |
495 | dice_lock_changed(dice); | |
496 | err = 0; | |
497 | out: | |
498 | spin_unlock_irq(&dice->lock); | |
499 | return err; | |
500 | } | |
501 | ||
502 | void snd_dice_stream_lock_release(struct snd_dice *dice) | |
503 | { | |
504 | spin_lock_irq(&dice->lock); | |
505 | ||
506 | if (WARN_ON(dice->dev_lock_count <= 0)) | |
507 | goto out; | |
508 | ||
509 | if (--dice->dev_lock_count == 0) | |
510 | dice_lock_changed(dice); | |
511 | out: | |
512 | spin_unlock_irq(&dice->lock); | |
513 | } |