1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Apple iSight audio driver |
4 | * |
5 | * Copyright (c) Clemens Ladisch <clemens@ladisch.de> |
6 | */ |
7 | |
8 | #include <asm/byteorder.h> |
9 | #include <linux/delay.h> |
10 | #include <linux/device.h> |
11 | #include <linux/firewire.h> |
12 | #include <linux/firewire-constants.h> |
13 | #include <linux/module.h> |
14 | #include <linux/mod_devicetable.h> |
15 | #include <linux/mutex.h> |
16 | #include <linux/string.h> |
17 | #include <sound/control.h> |
18 | #include <sound/core.h> |
19 | #include <sound/initval.h> |
20 | #include <sound/pcm.h> |
21 | #include <sound/tlv.h> |
22 | #include "lib.h" |
23 | #include "iso-resources.h" |
24 | #include "packets-buffer.h" |
25 | |
26 | #define OUI_APPLE 0x000a27 |
27 | #define MODEL_APPLE_ISIGHT 0x000008 |
28 | #define SW_ISIGHT_AUDIO 0x000010 |
29 | |
30 | #define REG_AUDIO_ENABLE 0x000 |
31 | #define AUDIO_ENABLE 0x80000000 |
32 | #define REG_DEF_AUDIO_GAIN 0x204 |
33 | #define REG_GAIN_RAW_START 0x210 |
34 | #define REG_GAIN_RAW_END 0x214 |
35 | #define REG_GAIN_DB_START 0x218 |
36 | #define REG_GAIN_DB_END 0x21c |
37 | #define REG_SAMPLE_RATE_INQUIRY 0x280 |
38 | #define REG_ISO_TX_CONFIG 0x300 |
39 | #define SPEED_SHIFT 16 |
40 | #define REG_SAMPLE_RATE 0x400 |
41 | #define RATE_48000 0x80000000 |
42 | #define REG_GAIN 0x500 |
43 | #define REG_MUTE 0x504 |
44 | |
45 | #define MAX_FRAMES_PER_PACKET 475 |
46 | |
47 | #define QUEUE_LENGTH 20 |
48 | |
49 | struct isight { |
50 | struct snd_card *card; |
51 | struct fw_unit *unit; |
52 | struct fw_device *device; |
53 | u64 audio_base; |
54 | struct snd_pcm_substream *pcm; |
55 | struct mutex mutex; |
56 | struct iso_packets_buffer buffer; |
57 | struct fw_iso_resources resources; |
58 | struct fw_iso_context *context; |
59 | bool pcm_active; |
60 | bool pcm_running; |
61 | bool first_packet; |
62 | int packet_index; |
63 | u32 total_samples; |
64 | unsigned int buffer_pointer; |
65 | unsigned int period_counter; |
66 | s32 gain_min, gain_max; |
67 | unsigned int gain_tlv[4]; |
68 | }; |
69 | |
70 | struct audio_payload { |
71 | __be32 sample_count; |
72 | __be32 signature; |
73 | __be32 sample_total; |
74 | __be32 reserved; |
75 | __be16 samples[2 * MAX_FRAMES_PER_PACKET]; |
76 | }; |
77 | |
78 | MODULE_DESCRIPTION("iSight audio driver" ); |
79 | MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>" ); |
80 | MODULE_LICENSE("GPL" ); |
81 | |
82 | static struct fw_iso_packet audio_packet = { |
83 | .payload_length = sizeof(struct audio_payload), |
84 | .interrupt = 1, |
85 | .header_length = 4, |
86 | }; |
87 | |
88 | static void isight_update_pointers(struct isight *isight, unsigned int count) |
89 | { |
90 | struct snd_pcm_runtime *runtime = isight->pcm->runtime; |
91 | unsigned int ptr; |
92 | |
93 | smp_wmb(); /* update buffer data before buffer pointer */ |
94 | |
95 | ptr = isight->buffer_pointer; |
96 | ptr += count; |
97 | if (ptr >= runtime->buffer_size) |
98 | ptr -= runtime->buffer_size; |
99 | WRITE_ONCE(isight->buffer_pointer, ptr); |
100 | |
101 | isight->period_counter += count; |
102 | if (isight->period_counter >= runtime->period_size) { |
103 | isight->period_counter -= runtime->period_size; |
104 | snd_pcm_period_elapsed(substream: isight->pcm); |
105 | } |
106 | } |
107 | |
108 | static void isight_samples(struct isight *isight, |
109 | const __be16 *samples, unsigned int count) |
110 | { |
111 | struct snd_pcm_runtime *runtime; |
112 | unsigned int count1; |
113 | |
114 | if (!READ_ONCE(isight->pcm_running)) |
115 | return; |
116 | |
117 | runtime = isight->pcm->runtime; |
118 | if (isight->buffer_pointer + count <= runtime->buffer_size) { |
119 | memcpy(runtime->dma_area + isight->buffer_pointer * 4, |
120 | samples, count * 4); |
121 | } else { |
122 | count1 = runtime->buffer_size - isight->buffer_pointer; |
123 | memcpy(runtime->dma_area + isight->buffer_pointer * 4, |
124 | samples, count1 * 4); |
125 | samples += count1 * 2; |
126 | memcpy(runtime->dma_area, samples, (count - count1) * 4); |
127 | } |
128 | |
129 | isight_update_pointers(isight, count); |
130 | } |
131 | |
132 | static void isight_pcm_abort(struct isight *isight) |
133 | { |
134 | if (READ_ONCE(isight->pcm_active)) |
135 | snd_pcm_stop_xrun(substream: isight->pcm); |
136 | } |
137 | |
138 | static void isight_dropped_samples(struct isight *isight, unsigned int total) |
139 | { |
140 | struct snd_pcm_runtime *runtime; |
141 | u32 dropped; |
142 | unsigned int count1; |
143 | |
144 | if (!READ_ONCE(isight->pcm_running)) |
145 | return; |
146 | |
147 | runtime = isight->pcm->runtime; |
148 | dropped = total - isight->total_samples; |
149 | if (dropped < runtime->buffer_size) { |
150 | if (isight->buffer_pointer + dropped <= runtime->buffer_size) { |
151 | memset(runtime->dma_area + isight->buffer_pointer * 4, |
152 | 0, dropped * 4); |
153 | } else { |
154 | count1 = runtime->buffer_size - isight->buffer_pointer; |
155 | memset(runtime->dma_area + isight->buffer_pointer * 4, |
156 | 0, count1 * 4); |
157 | memset(runtime->dma_area, 0, (dropped - count1) * 4); |
158 | } |
159 | isight_update_pointers(isight, count: dropped); |
160 | } else { |
161 | isight_pcm_abort(isight); |
162 | } |
163 | } |
164 | |
165 | static void isight_packet(struct fw_iso_context *context, u32 cycle, |
166 | size_t , void *, void *data) |
167 | { |
168 | struct isight *isight = data; |
169 | const struct audio_payload *payload; |
170 | unsigned int index, length, count, total; |
171 | int err; |
172 | |
173 | if (isight->packet_index < 0) |
174 | return; |
175 | index = isight->packet_index; |
176 | payload = isight->buffer.packets[index].buffer; |
177 | length = be32_to_cpup(p: header) >> 16; |
178 | |
179 | if (likely(length >= 16 && |
180 | payload->signature == cpu_to_be32(0x73676874/*"sght"*/))) { |
181 | count = be32_to_cpu(payload->sample_count); |
182 | if (likely(count <= (length - 16) / 4)) { |
183 | total = be32_to_cpu(payload->sample_total); |
184 | if (unlikely(total != isight->total_samples)) { |
185 | if (!isight->first_packet) |
186 | isight_dropped_samples(isight, total); |
187 | isight->first_packet = false; |
188 | isight->total_samples = total; |
189 | } |
190 | |
191 | isight_samples(isight, samples: payload->samples, count); |
192 | isight->total_samples += count; |
193 | } |
194 | } |
195 | |
196 | err = fw_iso_context_queue(ctx: isight->context, packet: &audio_packet, |
197 | buffer: &isight->buffer.iso_buffer, |
198 | payload: isight->buffer.packets[index].offset); |
199 | if (err < 0) { |
200 | dev_err(&isight->unit->device, "queueing error: %d\n" , err); |
201 | isight_pcm_abort(isight); |
202 | isight->packet_index = -1; |
203 | return; |
204 | } |
205 | fw_iso_context_queue_flush(ctx: isight->context); |
206 | |
207 | if (++index >= QUEUE_LENGTH) |
208 | index = 0; |
209 | isight->packet_index = index; |
210 | } |
211 | |
212 | static int isight_connect(struct isight *isight) |
213 | { |
214 | int ch, err; |
215 | __be32 value; |
216 | |
217 | retry_after_bus_reset: |
218 | ch = fw_iso_resources_allocate(r: &isight->resources, |
219 | max_payload_bytes: sizeof(struct audio_payload), |
220 | speed: isight->device->max_speed); |
221 | if (ch < 0) { |
222 | err = ch; |
223 | goto error; |
224 | } |
225 | |
226 | value = cpu_to_be32(ch | (isight->device->max_speed << SPEED_SHIFT)); |
227 | err = snd_fw_transaction(unit: isight->unit, TCODE_WRITE_QUADLET_REQUEST, |
228 | offset: isight->audio_base + REG_ISO_TX_CONFIG, |
229 | buffer: &value, length: 4, FW_FIXED_GENERATION | |
230 | isight->resources.generation); |
231 | if (err == -EAGAIN) { |
232 | fw_iso_resources_free(r: &isight->resources); |
233 | goto retry_after_bus_reset; |
234 | } else if (err < 0) { |
235 | goto err_resources; |
236 | } |
237 | |
238 | return 0; |
239 | |
240 | err_resources: |
241 | fw_iso_resources_free(r: &isight->resources); |
242 | error: |
243 | return err; |
244 | } |
245 | |
246 | static int isight_open(struct snd_pcm_substream *substream) |
247 | { |
248 | static const struct snd_pcm_hardware hardware = { |
249 | .info = SNDRV_PCM_INFO_MMAP | |
250 | SNDRV_PCM_INFO_MMAP_VALID | |
251 | SNDRV_PCM_INFO_BATCH | |
252 | SNDRV_PCM_INFO_INTERLEAVED | |
253 | SNDRV_PCM_INFO_BLOCK_TRANSFER, |
254 | .formats = SNDRV_PCM_FMTBIT_S16_BE, |
255 | .rates = SNDRV_PCM_RATE_48000, |
256 | .rate_min = 48000, |
257 | .rate_max = 48000, |
258 | .channels_min = 2, |
259 | .channels_max = 2, |
260 | .buffer_bytes_max = 4 * 1024 * 1024, |
261 | .period_bytes_min = MAX_FRAMES_PER_PACKET * 4, |
262 | .period_bytes_max = 1024 * 1024, |
263 | .periods_min = 2, |
264 | .periods_max = UINT_MAX, |
265 | }; |
266 | struct isight *isight = substream->private_data; |
267 | |
268 | substream->runtime->hw = hardware; |
269 | |
270 | return iso_packets_buffer_init(b: &isight->buffer, unit: isight->unit, |
271 | QUEUE_LENGTH, |
272 | packet_size: sizeof(struct audio_payload), |
273 | direction: DMA_FROM_DEVICE); |
274 | } |
275 | |
276 | static int isight_close(struct snd_pcm_substream *substream) |
277 | { |
278 | struct isight *isight = substream->private_data; |
279 | |
280 | iso_packets_buffer_destroy(b: &isight->buffer, unit: isight->unit); |
281 | |
282 | return 0; |
283 | } |
284 | |
285 | static int isight_hw_params(struct snd_pcm_substream *substream, |
286 | struct snd_pcm_hw_params *hw_params) |
287 | { |
288 | struct isight *isight = substream->private_data; |
289 | |
290 | WRITE_ONCE(isight->pcm_active, true); |
291 | |
292 | return 0; |
293 | } |
294 | |
295 | static int reg_read(struct isight *isight, int offset, __be32 *value) |
296 | { |
297 | return snd_fw_transaction(unit: isight->unit, TCODE_READ_QUADLET_REQUEST, |
298 | offset: isight->audio_base + offset, buffer: value, length: 4, flags: 0); |
299 | } |
300 | |
301 | static int reg_write(struct isight *isight, int offset, __be32 value) |
302 | { |
303 | return snd_fw_transaction(unit: isight->unit, TCODE_WRITE_QUADLET_REQUEST, |
304 | offset: isight->audio_base + offset, buffer: &value, length: 4, flags: 0); |
305 | } |
306 | |
307 | static void isight_stop_streaming(struct isight *isight) |
308 | { |
309 | __be32 value; |
310 | |
311 | if (!isight->context) |
312 | return; |
313 | |
314 | fw_iso_context_stop(ctx: isight->context); |
315 | fw_iso_context_destroy(ctx: isight->context); |
316 | isight->context = NULL; |
317 | fw_iso_resources_free(r: &isight->resources); |
318 | value = 0; |
319 | snd_fw_transaction(unit: isight->unit, TCODE_WRITE_QUADLET_REQUEST, |
320 | offset: isight->audio_base + REG_AUDIO_ENABLE, |
321 | buffer: &value, length: 4, FW_QUIET); |
322 | } |
323 | |
324 | static int isight_hw_free(struct snd_pcm_substream *substream) |
325 | { |
326 | struct isight *isight = substream->private_data; |
327 | |
328 | WRITE_ONCE(isight->pcm_active, false); |
329 | |
330 | mutex_lock(&isight->mutex); |
331 | isight_stop_streaming(isight); |
332 | mutex_unlock(lock: &isight->mutex); |
333 | |
334 | return 0; |
335 | } |
336 | |
337 | static int isight_start_streaming(struct isight *isight) |
338 | { |
339 | unsigned int i; |
340 | int err; |
341 | |
342 | if (isight->context) { |
343 | if (isight->packet_index < 0) |
344 | isight_stop_streaming(isight); |
345 | else |
346 | return 0; |
347 | } |
348 | |
349 | err = reg_write(isight, REG_SAMPLE_RATE, cpu_to_be32(RATE_48000)); |
350 | if (err < 0) |
351 | goto error; |
352 | |
353 | err = isight_connect(isight); |
354 | if (err < 0) |
355 | goto error; |
356 | |
357 | err = reg_write(isight, REG_AUDIO_ENABLE, cpu_to_be32(AUDIO_ENABLE)); |
358 | if (err < 0) |
359 | goto err_resources; |
360 | |
361 | isight->context = fw_iso_context_create(card: isight->device->card, |
362 | FW_ISO_CONTEXT_RECEIVE, |
363 | channel: isight->resources.channel, |
364 | speed: isight->device->max_speed, |
365 | header_size: 4, callback: isight_packet, callback_data: isight); |
366 | if (IS_ERR(ptr: isight->context)) { |
367 | err = PTR_ERR(ptr: isight->context); |
368 | isight->context = NULL; |
369 | goto err_resources; |
370 | } |
371 | |
372 | for (i = 0; i < QUEUE_LENGTH; ++i) { |
373 | err = fw_iso_context_queue(ctx: isight->context, packet: &audio_packet, |
374 | buffer: &isight->buffer.iso_buffer, |
375 | payload: isight->buffer.packets[i].offset); |
376 | if (err < 0) |
377 | goto err_context; |
378 | } |
379 | |
380 | isight->first_packet = true; |
381 | isight->packet_index = 0; |
382 | |
383 | err = fw_iso_context_start(ctx: isight->context, cycle: -1, sync: 0, |
384 | FW_ISO_CONTEXT_MATCH_ALL_TAGS/*?*/); |
385 | if (err < 0) |
386 | goto err_context; |
387 | |
388 | return 0; |
389 | |
390 | err_context: |
391 | fw_iso_context_destroy(ctx: isight->context); |
392 | isight->context = NULL; |
393 | err_resources: |
394 | fw_iso_resources_free(r: &isight->resources); |
395 | reg_write(isight, REG_AUDIO_ENABLE, value: 0); |
396 | error: |
397 | return err; |
398 | } |
399 | |
400 | static int isight_prepare(struct snd_pcm_substream *substream) |
401 | { |
402 | struct isight *isight = substream->private_data; |
403 | int err; |
404 | |
405 | isight->buffer_pointer = 0; |
406 | isight->period_counter = 0; |
407 | |
408 | mutex_lock(&isight->mutex); |
409 | err = isight_start_streaming(isight); |
410 | mutex_unlock(lock: &isight->mutex); |
411 | |
412 | return err; |
413 | } |
414 | |
415 | static int isight_trigger(struct snd_pcm_substream *substream, int cmd) |
416 | { |
417 | struct isight *isight = substream->private_data; |
418 | |
419 | switch (cmd) { |
420 | case SNDRV_PCM_TRIGGER_START: |
421 | WRITE_ONCE(isight->pcm_running, true); |
422 | break; |
423 | case SNDRV_PCM_TRIGGER_STOP: |
424 | WRITE_ONCE(isight->pcm_running, false); |
425 | break; |
426 | default: |
427 | return -EINVAL; |
428 | } |
429 | return 0; |
430 | } |
431 | |
432 | static snd_pcm_uframes_t isight_pointer(struct snd_pcm_substream *substream) |
433 | { |
434 | struct isight *isight = substream->private_data; |
435 | |
436 | return READ_ONCE(isight->buffer_pointer); |
437 | } |
438 | |
439 | static int isight_create_pcm(struct isight *isight) |
440 | { |
441 | static const struct snd_pcm_ops ops = { |
442 | .open = isight_open, |
443 | .close = isight_close, |
444 | .hw_params = isight_hw_params, |
445 | .hw_free = isight_hw_free, |
446 | .prepare = isight_prepare, |
447 | .trigger = isight_trigger, |
448 | .pointer = isight_pointer, |
449 | }; |
450 | struct snd_pcm *pcm; |
451 | int err; |
452 | |
453 | err = snd_pcm_new(card: isight->card, id: "iSight" , device: 0, playback_count: 0, capture_count: 1, rpcm: &pcm); |
454 | if (err < 0) |
455 | return err; |
456 | pcm->private_data = isight; |
457 | strcpy(p: pcm->name, q: "iSight" ); |
458 | isight->pcm = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream; |
459 | isight->pcm->ops = &ops; |
460 | snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_VMALLOC, NULL, size: 0, max: 0); |
461 | |
462 | return 0; |
463 | } |
464 | |
465 | static int isight_gain_info(struct snd_kcontrol *ctl, |
466 | struct snd_ctl_elem_info *info) |
467 | { |
468 | struct isight *isight = ctl->private_data; |
469 | |
470 | info->type = SNDRV_CTL_ELEM_TYPE_INTEGER; |
471 | info->count = 1; |
472 | info->value.integer.min = isight->gain_min; |
473 | info->value.integer.max = isight->gain_max; |
474 | |
475 | return 0; |
476 | } |
477 | |
478 | static int isight_gain_get(struct snd_kcontrol *ctl, |
479 | struct snd_ctl_elem_value *value) |
480 | { |
481 | struct isight *isight = ctl->private_data; |
482 | __be32 gain; |
483 | int err; |
484 | |
485 | err = reg_read(isight, REG_GAIN, value: &gain); |
486 | if (err < 0) |
487 | return err; |
488 | |
489 | value->value.integer.value[0] = (s32)be32_to_cpu(gain); |
490 | |
491 | return 0; |
492 | } |
493 | |
494 | static int isight_gain_put(struct snd_kcontrol *ctl, |
495 | struct snd_ctl_elem_value *value) |
496 | { |
497 | struct isight *isight = ctl->private_data; |
498 | |
499 | if (value->value.integer.value[0] < isight->gain_min || |
500 | value->value.integer.value[0] > isight->gain_max) |
501 | return -EINVAL; |
502 | |
503 | return reg_write(isight, REG_GAIN, |
504 | cpu_to_be32(value->value.integer.value[0])); |
505 | } |
506 | |
507 | static int isight_mute_get(struct snd_kcontrol *ctl, |
508 | struct snd_ctl_elem_value *value) |
509 | { |
510 | struct isight *isight = ctl->private_data; |
511 | __be32 mute; |
512 | int err; |
513 | |
514 | err = reg_read(isight, REG_MUTE, value: &mute); |
515 | if (err < 0) |
516 | return err; |
517 | |
518 | value->value.integer.value[0] = !mute; |
519 | |
520 | return 0; |
521 | } |
522 | |
523 | static int isight_mute_put(struct snd_kcontrol *ctl, |
524 | struct snd_ctl_elem_value *value) |
525 | { |
526 | struct isight *isight = ctl->private_data; |
527 | |
528 | return reg_write(isight, REG_MUTE, |
529 | value: (__force __be32)!value->value.integer.value[0]); |
530 | } |
531 | |
532 | static int isight_create_mixer(struct isight *isight) |
533 | { |
534 | static const struct snd_kcontrol_new gain_control = { |
535 | .iface = SNDRV_CTL_ELEM_IFACE_MIXER, |
536 | .name = "Mic Capture Volume" , |
537 | .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | |
538 | SNDRV_CTL_ELEM_ACCESS_TLV_READ, |
539 | .info = isight_gain_info, |
540 | .get = isight_gain_get, |
541 | .put = isight_gain_put, |
542 | }; |
543 | static const struct snd_kcontrol_new mute_control = { |
544 | .iface = SNDRV_CTL_ELEM_IFACE_MIXER, |
545 | .name = "Mic Capture Switch" , |
546 | .info = snd_ctl_boolean_mono_info, |
547 | .get = isight_mute_get, |
548 | .put = isight_mute_put, |
549 | }; |
550 | __be32 value; |
551 | struct snd_kcontrol *ctl; |
552 | int err; |
553 | |
554 | err = reg_read(isight, REG_GAIN_RAW_START, value: &value); |
555 | if (err < 0) |
556 | return err; |
557 | isight->gain_min = be32_to_cpu(value); |
558 | |
559 | err = reg_read(isight, REG_GAIN_RAW_END, value: &value); |
560 | if (err < 0) |
561 | return err; |
562 | isight->gain_max = be32_to_cpu(value); |
563 | |
564 | isight->gain_tlv[SNDRV_CTL_TLVO_TYPE] = SNDRV_CTL_TLVT_DB_MINMAX; |
565 | isight->gain_tlv[SNDRV_CTL_TLVO_LEN] = 2 * sizeof(unsigned int); |
566 | |
567 | err = reg_read(isight, REG_GAIN_DB_START, value: &value); |
568 | if (err < 0) |
569 | return err; |
570 | isight->gain_tlv[SNDRV_CTL_TLVO_DB_MINMAX_MIN] = |
571 | (s32)be32_to_cpu(value) * 100; |
572 | |
573 | err = reg_read(isight, REG_GAIN_DB_END, value: &value); |
574 | if (err < 0) |
575 | return err; |
576 | isight->gain_tlv[SNDRV_CTL_TLVO_DB_MINMAX_MAX] = |
577 | (s32)be32_to_cpu(value) * 100; |
578 | |
579 | ctl = snd_ctl_new1(kcontrolnew: &gain_control, private_data: isight); |
580 | if (ctl) |
581 | ctl->tlv.p = isight->gain_tlv; |
582 | err = snd_ctl_add(card: isight->card, kcontrol: ctl); |
583 | if (err < 0) |
584 | return err; |
585 | |
586 | err = snd_ctl_add(card: isight->card, kcontrol: snd_ctl_new1(kcontrolnew: &mute_control, private_data: isight)); |
587 | if (err < 0) |
588 | return err; |
589 | |
590 | return 0; |
591 | } |
592 | |
593 | static void isight_card_free(struct snd_card *card) |
594 | { |
595 | struct isight *isight = card->private_data; |
596 | |
597 | fw_iso_resources_destroy(r: &isight->resources); |
598 | } |
599 | |
600 | static u64 get_unit_base(struct fw_unit *unit) |
601 | { |
602 | struct fw_csr_iterator i; |
603 | int key, value; |
604 | |
605 | fw_csr_iterator_init(ci: &i, p: unit->directory); |
606 | while (fw_csr_iterator_next(ci: &i, key: &key, value: &value)) |
607 | if (key == CSR_OFFSET) |
608 | return CSR_REGISTER_BASE + value * 4; |
609 | return 0; |
610 | } |
611 | |
612 | static int isight_probe(struct fw_unit *unit, |
613 | const struct ieee1394_device_id *id) |
614 | { |
615 | struct fw_device *fw_dev = fw_parent_device(unit); |
616 | struct snd_card *card; |
617 | struct isight *isight; |
618 | int err; |
619 | |
620 | err = snd_card_new(parent: &unit->device, idx: -1, NULL, THIS_MODULE, |
621 | extra_size: sizeof(*isight), card_ret: &card); |
622 | if (err < 0) |
623 | return err; |
624 | |
625 | isight = card->private_data; |
626 | isight->card = card; |
627 | mutex_init(&isight->mutex); |
628 | isight->unit = fw_unit_get(unit); |
629 | isight->device = fw_dev; |
630 | isight->audio_base = get_unit_base(unit); |
631 | if (!isight->audio_base) { |
632 | dev_err(&unit->device, "audio unit base not found\n" ); |
633 | err = -ENXIO; |
634 | goto error; |
635 | } |
636 | fw_iso_resources_init(r: &isight->resources, unit); |
637 | |
638 | card->private_free = isight_card_free; |
639 | |
640 | strcpy(p: card->driver, q: "iSight" ); |
641 | strcpy(p: card->shortname, q: "Apple iSight" ); |
642 | snprintf(buf: card->longname, size: sizeof(card->longname), |
643 | fmt: "Apple iSight (GUID %08x%08x) at %s, S%d" , |
644 | fw_dev->config_rom[3], fw_dev->config_rom[4], |
645 | dev_name(dev: &unit->device), 100 << fw_dev->max_speed); |
646 | strcpy(p: card->mixername, q: "iSight" ); |
647 | |
648 | err = isight_create_pcm(isight); |
649 | if (err < 0) |
650 | goto error; |
651 | |
652 | err = isight_create_mixer(isight); |
653 | if (err < 0) |
654 | goto error; |
655 | |
656 | err = snd_card_register(card); |
657 | if (err < 0) |
658 | goto error; |
659 | |
660 | dev_set_drvdata(dev: &unit->device, data: isight); |
661 | |
662 | return 0; |
663 | error: |
664 | snd_card_free(card); |
665 | |
666 | mutex_destroy(lock: &isight->mutex); |
667 | fw_unit_put(unit: isight->unit); |
668 | |
669 | return err; |
670 | } |
671 | |
672 | static void isight_bus_reset(struct fw_unit *unit) |
673 | { |
674 | struct isight *isight = dev_get_drvdata(dev: &unit->device); |
675 | |
676 | if (fw_iso_resources_update(r: &isight->resources) < 0) { |
677 | isight_pcm_abort(isight); |
678 | |
679 | mutex_lock(&isight->mutex); |
680 | isight_stop_streaming(isight); |
681 | mutex_unlock(lock: &isight->mutex); |
682 | } |
683 | } |
684 | |
685 | static void isight_remove(struct fw_unit *unit) |
686 | { |
687 | struct isight *isight = dev_get_drvdata(dev: &unit->device); |
688 | |
689 | isight_pcm_abort(isight); |
690 | |
691 | snd_card_disconnect(card: isight->card); |
692 | |
693 | mutex_lock(&isight->mutex); |
694 | isight_stop_streaming(isight); |
695 | mutex_unlock(lock: &isight->mutex); |
696 | |
697 | // Block till all of ALSA character devices are released. |
698 | snd_card_free(card: isight->card); |
699 | |
700 | mutex_destroy(lock: &isight->mutex); |
701 | fw_unit_put(unit: isight->unit); |
702 | } |
703 | |
704 | static const struct ieee1394_device_id isight_id_table[] = { |
705 | { |
706 | .match_flags = IEEE1394_MATCH_SPECIFIER_ID | |
707 | IEEE1394_MATCH_VERSION, |
708 | .specifier_id = OUI_APPLE, |
709 | .version = SW_ISIGHT_AUDIO, |
710 | }, |
711 | { } |
712 | }; |
713 | MODULE_DEVICE_TABLE(ieee1394, isight_id_table); |
714 | |
715 | static struct fw_driver isight_driver = { |
716 | .driver = { |
717 | .owner = THIS_MODULE, |
718 | .name = KBUILD_MODNAME, |
719 | .bus = &fw_bus_type, |
720 | }, |
721 | .probe = isight_probe, |
722 | .update = isight_bus_reset, |
723 | .remove = isight_remove, |
724 | .id_table = isight_id_table, |
725 | }; |
726 | |
727 | static int __init alsa_isight_init(void) |
728 | { |
729 | return driver_register(drv: &isight_driver.driver); |
730 | } |
731 | |
732 | static void __exit alsa_isight_exit(void) |
733 | { |
734 | driver_unregister(drv: &isight_driver.driver); |
735 | } |
736 | |
737 | module_init(alsa_isight_init); |
738 | module_exit(alsa_isight_exit); |
739 | |