1da607e19SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only 23a691b28SClemens Ladisch /* 33a691b28SClemens Ladisch * Apple iSight audio driver 43a691b28SClemens Ladisch * 53a691b28SClemens Ladisch * Copyright (c) Clemens Ladisch <clemens@ladisch.de> 63a691b28SClemens Ladisch */ 73a691b28SClemens Ladisch 8ac34dad2SStefan Richter #include <asm/byteorder.h> 93a691b28SClemens Ladisch #include <linux/delay.h> 103a691b28SClemens Ladisch #include <linux/device.h> 113a691b28SClemens Ladisch #include <linux/firewire.h> 123a691b28SClemens Ladisch #include <linux/firewire-constants.h> 133a691b28SClemens Ladisch #include <linux/module.h> 143a691b28SClemens Ladisch #include <linux/mod_devicetable.h> 153a691b28SClemens Ladisch #include <linux/mutex.h> 163a691b28SClemens Ladisch #include <linux/string.h> 173a691b28SClemens Ladisch #include <sound/control.h> 183a691b28SClemens Ladisch #include <sound/core.h> 193a691b28SClemens Ladisch #include <sound/initval.h> 203a691b28SClemens Ladisch #include <sound/pcm.h> 213a691b28SClemens Ladisch #include <sound/tlv.h> 223a691b28SClemens Ladisch #include "lib.h" 233a691b28SClemens Ladisch #include "iso-resources.h" 243a691b28SClemens Ladisch #include "packets-buffer.h" 253a691b28SClemens Ladisch 263a691b28SClemens Ladisch #define OUI_APPLE 0x000a27 273a691b28SClemens Ladisch #define MODEL_APPLE_ISIGHT 0x000008 283a691b28SClemens Ladisch #define SW_ISIGHT_AUDIO 0x000010 293a691b28SClemens Ladisch 303a691b28SClemens Ladisch #define REG_AUDIO_ENABLE 0x000 313a691b28SClemens Ladisch #define AUDIO_ENABLE 0x80000000 323a691b28SClemens Ladisch #define REG_DEF_AUDIO_GAIN 0x204 333a691b28SClemens Ladisch #define REG_GAIN_RAW_START 0x210 343a691b28SClemens Ladisch #define REG_GAIN_RAW_END 0x214 353a691b28SClemens Ladisch #define REG_GAIN_DB_START 0x218 363a691b28SClemens Ladisch #define REG_GAIN_DB_END 0x21c 373a691b28SClemens Ladisch #define REG_SAMPLE_RATE_INQUIRY 0x280 383a691b28SClemens Ladisch #define REG_ISO_TX_CONFIG 0x300 393a691b28SClemens Ladisch #define SPEED_SHIFT 16 403a691b28SClemens Ladisch #define REG_SAMPLE_RATE 0x400 413a691b28SClemens Ladisch #define RATE_48000 0x80000000 423a691b28SClemens Ladisch #define REG_GAIN 0x500 433a691b28SClemens Ladisch #define REG_MUTE 0x504 443a691b28SClemens Ladisch 453a691b28SClemens Ladisch #define MAX_FRAMES_PER_PACKET 475 463a691b28SClemens Ladisch 473a691b28SClemens Ladisch #define QUEUE_LENGTH 20 483a691b28SClemens Ladisch 493a691b28SClemens Ladisch struct isight { 503a691b28SClemens Ladisch struct snd_card *card; 513a691b28SClemens Ladisch struct fw_unit *unit; 523a691b28SClemens Ladisch struct fw_device *device; 533a691b28SClemens Ladisch u64 audio_base; 543a691b28SClemens Ladisch struct snd_pcm_substream *pcm; 553a691b28SClemens Ladisch struct mutex mutex; 563a691b28SClemens Ladisch struct iso_packets_buffer buffer; 573a691b28SClemens Ladisch struct fw_iso_resources resources; 583a691b28SClemens Ladisch struct fw_iso_context *context; 5903c29680SClemens Ladisch bool pcm_active; 603a691b28SClemens Ladisch bool pcm_running; 613a691b28SClemens Ladisch bool first_packet; 623a691b28SClemens Ladisch int packet_index; 633a691b28SClemens Ladisch u32 total_samples; 643a691b28SClemens Ladisch unsigned int buffer_pointer; 653a691b28SClemens Ladisch unsigned int period_counter; 663a691b28SClemens Ladisch s32 gain_min, gain_max; 673a691b28SClemens Ladisch unsigned int gain_tlv[4]; 683a691b28SClemens Ladisch }; 693a691b28SClemens Ladisch 703a691b28SClemens Ladisch struct audio_payload { 713a691b28SClemens Ladisch __be32 sample_count; 723a691b28SClemens Ladisch __be32 signature; 733a691b28SClemens Ladisch __be32 sample_total; 743a691b28SClemens Ladisch __be32 reserved; 753a691b28SClemens Ladisch __be16 samples[2 * MAX_FRAMES_PER_PACKET]; 763a691b28SClemens Ladisch }; 773a691b28SClemens Ladisch 783a691b28SClemens Ladisch MODULE_DESCRIPTION("iSight audio driver"); 793a691b28SClemens Ladisch MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>"); 803a691b28SClemens Ladisch MODULE_LICENSE("GPL v2"); 813a691b28SClemens Ladisch 823a691b28SClemens Ladisch static struct fw_iso_packet audio_packet = { 833a691b28SClemens Ladisch .payload_length = sizeof(struct audio_payload), 843a691b28SClemens Ladisch .interrupt = 1, 85f2934cd4SClemens Ladisch .header_length = 4, 863a691b28SClemens Ladisch }; 873a691b28SClemens Ladisch 883a691b28SClemens Ladisch static void isight_update_pointers(struct isight *isight, unsigned int count) 893a691b28SClemens Ladisch { 903a691b28SClemens Ladisch struct snd_pcm_runtime *runtime = isight->pcm->runtime; 913a691b28SClemens Ladisch unsigned int ptr; 923a691b28SClemens Ladisch 933a691b28SClemens Ladisch smp_wmb(); /* update buffer data before buffer pointer */ 943a691b28SClemens Ladisch 953a691b28SClemens Ladisch ptr = isight->buffer_pointer; 963a691b28SClemens Ladisch ptr += count; 973a691b28SClemens Ladisch if (ptr >= runtime->buffer_size) 983a691b28SClemens Ladisch ptr -= runtime->buffer_size; 996aa7de05SMark Rutland WRITE_ONCE(isight->buffer_pointer, ptr); 1003a691b28SClemens Ladisch 1013a691b28SClemens Ladisch isight->period_counter += count; 1023a691b28SClemens Ladisch if (isight->period_counter >= runtime->period_size) { 1033a691b28SClemens Ladisch isight->period_counter -= runtime->period_size; 1043a691b28SClemens Ladisch snd_pcm_period_elapsed(isight->pcm); 1053a691b28SClemens Ladisch } 1063a691b28SClemens Ladisch } 1073a691b28SClemens Ladisch 1083a691b28SClemens Ladisch static void isight_samples(struct isight *isight, 1093a691b28SClemens Ladisch const __be16 *samples, unsigned int count) 1103a691b28SClemens Ladisch { 1113a691b28SClemens Ladisch struct snd_pcm_runtime *runtime; 1123a691b28SClemens Ladisch unsigned int count1; 1133a691b28SClemens Ladisch 1146aa7de05SMark Rutland if (!READ_ONCE(isight->pcm_running)) 1153a691b28SClemens Ladisch return; 1163a691b28SClemens Ladisch 1173a691b28SClemens Ladisch runtime = isight->pcm->runtime; 1183a691b28SClemens Ladisch if (isight->buffer_pointer + count <= runtime->buffer_size) { 1193a691b28SClemens Ladisch memcpy(runtime->dma_area + isight->buffer_pointer * 4, 1203a691b28SClemens Ladisch samples, count * 4); 1213a691b28SClemens Ladisch } else { 1223a691b28SClemens Ladisch count1 = runtime->buffer_size - isight->buffer_pointer; 1233a691b28SClemens Ladisch memcpy(runtime->dma_area + isight->buffer_pointer * 4, 1243a691b28SClemens Ladisch samples, count1 * 4); 1253a691b28SClemens Ladisch samples += count1 * 2; 1263a691b28SClemens Ladisch memcpy(runtime->dma_area, samples, (count - count1) * 4); 1273a691b28SClemens Ladisch } 1283a691b28SClemens Ladisch 1293a691b28SClemens Ladisch isight_update_pointers(isight, count); 1303a691b28SClemens Ladisch } 1313a691b28SClemens Ladisch 1323a691b28SClemens Ladisch static void isight_pcm_abort(struct isight *isight) 1333a691b28SClemens Ladisch { 1346aa7de05SMark Rutland if (READ_ONCE(isight->pcm_active)) 1351fb8510cSTakashi Iwai snd_pcm_stop_xrun(isight->pcm); 13603c29680SClemens Ladisch } 1373a691b28SClemens Ladisch 1383a691b28SClemens Ladisch static void isight_dropped_samples(struct isight *isight, unsigned int total) 1393a691b28SClemens Ladisch { 1403a691b28SClemens Ladisch struct snd_pcm_runtime *runtime; 1413a691b28SClemens Ladisch u32 dropped; 1423a691b28SClemens Ladisch unsigned int count1; 1433a691b28SClemens Ladisch 1446aa7de05SMark Rutland if (!READ_ONCE(isight->pcm_running)) 1453a691b28SClemens Ladisch return; 1463a691b28SClemens Ladisch 1473a691b28SClemens Ladisch runtime = isight->pcm->runtime; 1483a691b28SClemens Ladisch dropped = total - isight->total_samples; 1493a691b28SClemens Ladisch if (dropped < runtime->buffer_size) { 1503a691b28SClemens Ladisch if (isight->buffer_pointer + dropped <= runtime->buffer_size) { 1513a691b28SClemens Ladisch memset(runtime->dma_area + isight->buffer_pointer * 4, 1523a691b28SClemens Ladisch 0, dropped * 4); 1533a691b28SClemens Ladisch } else { 1543a691b28SClemens Ladisch count1 = runtime->buffer_size - isight->buffer_pointer; 1553a691b28SClemens Ladisch memset(runtime->dma_area + isight->buffer_pointer * 4, 1563a691b28SClemens Ladisch 0, count1 * 4); 1573a691b28SClemens Ladisch memset(runtime->dma_area, 0, (dropped - count1) * 4); 1583a691b28SClemens Ladisch } 1593a691b28SClemens Ladisch isight_update_pointers(isight, dropped); 1603a691b28SClemens Ladisch } else { 1613a691b28SClemens Ladisch isight_pcm_abort(isight); 1623a691b28SClemens Ladisch } 1633a691b28SClemens Ladisch } 1643a691b28SClemens Ladisch 1653a691b28SClemens Ladisch static void isight_packet(struct fw_iso_context *context, u32 cycle, 1663a691b28SClemens Ladisch size_t header_length, void *header, void *data) 1673a691b28SClemens Ladisch { 1683a691b28SClemens Ladisch struct isight *isight = data; 1693a691b28SClemens Ladisch const struct audio_payload *payload; 1703a691b28SClemens Ladisch unsigned int index, length, count, total; 1713a691b28SClemens Ladisch int err; 1723a691b28SClemens Ladisch 1733a691b28SClemens Ladisch if (isight->packet_index < 0) 1743a691b28SClemens Ladisch return; 1753a691b28SClemens Ladisch index = isight->packet_index; 1763a691b28SClemens Ladisch payload = isight->buffer.packets[index].buffer; 1773a691b28SClemens Ladisch length = be32_to_cpup(header) >> 16; 1783a691b28SClemens Ladisch 1793a691b28SClemens Ladisch if (likely(length >= 16 && 1803a691b28SClemens Ladisch payload->signature == cpu_to_be32(0x73676874/*"sght"*/))) { 1813a691b28SClemens Ladisch count = be32_to_cpu(payload->sample_count); 1823a691b28SClemens Ladisch if (likely(count <= (length - 16) / 4)) { 1833a691b28SClemens Ladisch total = be32_to_cpu(payload->sample_total); 1843a691b28SClemens Ladisch if (unlikely(total != isight->total_samples)) { 1853a691b28SClemens Ladisch if (!isight->first_packet) 1863a691b28SClemens Ladisch isight_dropped_samples(isight, total); 1873a691b28SClemens Ladisch isight->first_packet = false; 1883a691b28SClemens Ladisch isight->total_samples = total; 1893a691b28SClemens Ladisch } 1903a691b28SClemens Ladisch 1913a691b28SClemens Ladisch isight_samples(isight, payload->samples, count); 1923a691b28SClemens Ladisch isight->total_samples += count; 1933a691b28SClemens Ladisch } 1943a691b28SClemens Ladisch } 1953a691b28SClemens Ladisch 1963a691b28SClemens Ladisch err = fw_iso_context_queue(isight->context, &audio_packet, 1973a691b28SClemens Ladisch &isight->buffer.iso_buffer, 1983a691b28SClemens Ladisch isight->buffer.packets[index].offset); 1993a691b28SClemens Ladisch if (err < 0) { 2003a691b28SClemens Ladisch dev_err(&isight->unit->device, "queueing error: %d\n", err); 2013a691b28SClemens Ladisch isight_pcm_abort(isight); 2023a691b28SClemens Ladisch isight->packet_index = -1; 2033a691b28SClemens Ladisch return; 2043a691b28SClemens Ladisch } 205cf6f1ff1SClemens Ladisch fw_iso_context_queue_flush(isight->context); 2063a691b28SClemens Ladisch 207898732d1SClemens Ladisch if (++index >= QUEUE_LENGTH) 208898732d1SClemens Ladisch index = 0; 2093a691b28SClemens Ladisch isight->packet_index = index; 2103a691b28SClemens Ladisch } 2113a691b28SClemens Ladisch 2123a691b28SClemens Ladisch static int isight_connect(struct isight *isight) 2133a691b28SClemens Ladisch { 2141b70485fSClemens Ladisch int ch, err; 2153a691b28SClemens Ladisch __be32 value; 2163a691b28SClemens Ladisch 2173a691b28SClemens Ladisch retry_after_bus_reset: 2183a691b28SClemens Ladisch ch = fw_iso_resources_allocate(&isight->resources, 2193a691b28SClemens Ladisch sizeof(struct audio_payload), 2203a691b28SClemens Ladisch isight->device->max_speed); 2213a691b28SClemens Ladisch if (ch < 0) { 2223a691b28SClemens Ladisch err = ch; 2233a691b28SClemens Ladisch goto error; 2243a691b28SClemens Ladisch } 2253a691b28SClemens Ladisch 2263a691b28SClemens Ladisch value = cpu_to_be32(ch | (isight->device->max_speed << SPEED_SHIFT)); 2271b70485fSClemens Ladisch err = snd_fw_transaction(isight->unit, TCODE_WRITE_QUADLET_REQUEST, 2283a691b28SClemens Ladisch isight->audio_base + REG_ISO_TX_CONFIG, 2291b70485fSClemens Ladisch &value, 4, FW_FIXED_GENERATION | 2301b70485fSClemens Ladisch isight->resources.generation); 2311b70485fSClemens Ladisch if (err == -EAGAIN) { 2323a691b28SClemens Ladisch fw_iso_resources_free(&isight->resources); 2333a691b28SClemens Ladisch goto retry_after_bus_reset; 2341b70485fSClemens Ladisch } else if (err < 0) { 2353a691b28SClemens Ladisch goto err_resources; 2363a691b28SClemens Ladisch } 2371b70485fSClemens Ladisch 2381b70485fSClemens Ladisch return 0; 2393a691b28SClemens Ladisch 2403a691b28SClemens Ladisch err_resources: 2413a691b28SClemens Ladisch fw_iso_resources_free(&isight->resources); 2423a691b28SClemens Ladisch error: 2433a691b28SClemens Ladisch return err; 2443a691b28SClemens Ladisch } 2453a691b28SClemens Ladisch 2463a691b28SClemens Ladisch static int isight_open(struct snd_pcm_substream *substream) 2473a691b28SClemens Ladisch { 2483a691b28SClemens Ladisch static const struct snd_pcm_hardware hardware = { 2493a691b28SClemens Ladisch .info = SNDRV_PCM_INFO_MMAP | 2503a691b28SClemens Ladisch SNDRV_PCM_INFO_MMAP_VALID | 2513a691b28SClemens Ladisch SNDRV_PCM_INFO_BATCH | 2523a691b28SClemens Ladisch SNDRV_PCM_INFO_INTERLEAVED | 2533a691b28SClemens Ladisch SNDRV_PCM_INFO_BLOCK_TRANSFER, 2543a691b28SClemens Ladisch .formats = SNDRV_PCM_FMTBIT_S16_BE, 2553a691b28SClemens Ladisch .rates = SNDRV_PCM_RATE_48000, 2563a691b28SClemens Ladisch .rate_min = 48000, 2573a691b28SClemens Ladisch .rate_max = 48000, 2583a691b28SClemens Ladisch .channels_min = 2, 2593a691b28SClemens Ladisch .channels_max = 2, 2603a691b28SClemens Ladisch .buffer_bytes_max = 4 * 1024 * 1024, 2613a691b28SClemens Ladisch .period_bytes_min = MAX_FRAMES_PER_PACKET * 4, 2623a691b28SClemens Ladisch .period_bytes_max = 1024 * 1024, 2633a691b28SClemens Ladisch .periods_min = 2, 2643a691b28SClemens Ladisch .periods_max = UINT_MAX, 2653a691b28SClemens Ladisch }; 2663a691b28SClemens Ladisch struct isight *isight = substream->private_data; 2673a691b28SClemens Ladisch 2683a691b28SClemens Ladisch substream->runtime->hw = hardware; 2693a691b28SClemens Ladisch 2703a691b28SClemens Ladisch return iso_packets_buffer_init(&isight->buffer, isight->unit, 2713a691b28SClemens Ladisch QUEUE_LENGTH, 2723a691b28SClemens Ladisch sizeof(struct audio_payload), 2733a691b28SClemens Ladisch DMA_FROM_DEVICE); 2743a691b28SClemens Ladisch } 2753a691b28SClemens Ladisch 2763a691b28SClemens Ladisch static int isight_close(struct snd_pcm_substream *substream) 2773a691b28SClemens Ladisch { 2783a691b28SClemens Ladisch struct isight *isight = substream->private_data; 2793a691b28SClemens Ladisch 2803a691b28SClemens Ladisch iso_packets_buffer_destroy(&isight->buffer, isight->unit); 2813a691b28SClemens Ladisch 2823a691b28SClemens Ladisch return 0; 2833a691b28SClemens Ladisch } 2843a691b28SClemens Ladisch 2853a691b28SClemens Ladisch static int isight_hw_params(struct snd_pcm_substream *substream, 2863a691b28SClemens Ladisch struct snd_pcm_hw_params *hw_params) 2873a691b28SClemens Ladisch { 28803c29680SClemens Ladisch struct isight *isight = substream->private_data; 28903c29680SClemens Ladisch 2906aa7de05SMark Rutland WRITE_ONCE(isight->pcm_active, true); 29103c29680SClemens Ladisch 29203c29680SClemens Ladisch return 0; 2933a691b28SClemens Ladisch } 2943a691b28SClemens Ladisch 295ac34dad2SStefan Richter static int reg_read(struct isight *isight, int offset, __be32 *value) 296ac34dad2SStefan Richter { 297ac34dad2SStefan Richter return snd_fw_transaction(isight->unit, TCODE_READ_QUADLET_REQUEST, 2981b70485fSClemens Ladisch isight->audio_base + offset, value, 4, 0); 299ac34dad2SStefan Richter } 300ac34dad2SStefan Richter 301ac34dad2SStefan Richter static int reg_write(struct isight *isight, int offset, __be32 value) 302ac34dad2SStefan Richter { 303ac34dad2SStefan Richter return snd_fw_transaction(isight->unit, TCODE_WRITE_QUADLET_REQUEST, 3041b70485fSClemens Ladisch isight->audio_base + offset, &value, 4, 0); 305ac34dad2SStefan Richter } 306ac34dad2SStefan Richter 3073a691b28SClemens Ladisch static void isight_stop_streaming(struct isight *isight) 3083a691b28SClemens Ladisch { 3091b70485fSClemens Ladisch __be32 value; 3101b70485fSClemens Ladisch 3113a691b28SClemens Ladisch if (!isight->context) 3123a691b28SClemens Ladisch return; 3133a691b28SClemens Ladisch 3143a691b28SClemens Ladisch fw_iso_context_stop(isight->context); 3153a691b28SClemens Ladisch fw_iso_context_destroy(isight->context); 3163a691b28SClemens Ladisch isight->context = NULL; 3173a691b28SClemens Ladisch fw_iso_resources_free(&isight->resources); 3181b70485fSClemens Ladisch value = 0; 3191b70485fSClemens Ladisch snd_fw_transaction(isight->unit, TCODE_WRITE_QUADLET_REQUEST, 3201b70485fSClemens Ladisch isight->audio_base + REG_AUDIO_ENABLE, 3211b70485fSClemens Ladisch &value, 4, FW_QUIET); 3223a691b28SClemens Ladisch } 3233a691b28SClemens Ladisch 3243a691b28SClemens Ladisch static int isight_hw_free(struct snd_pcm_substream *substream) 3253a691b28SClemens Ladisch { 3263a691b28SClemens Ladisch struct isight *isight = substream->private_data; 3273a691b28SClemens Ladisch 3286aa7de05SMark Rutland WRITE_ONCE(isight->pcm_active, false); 32903c29680SClemens Ladisch 3303a691b28SClemens Ladisch mutex_lock(&isight->mutex); 3313a691b28SClemens Ladisch isight_stop_streaming(isight); 3323a691b28SClemens Ladisch mutex_unlock(&isight->mutex); 3333a691b28SClemens Ladisch 334*7641d549STakashi Iwai return 0; 3353a691b28SClemens Ladisch } 3363a691b28SClemens Ladisch 3373a691b28SClemens Ladisch static int isight_start_streaming(struct isight *isight) 3383a691b28SClemens Ladisch { 3393a691b28SClemens Ladisch unsigned int i; 3403a691b28SClemens Ladisch int err; 3413a691b28SClemens Ladisch 3423a691b28SClemens Ladisch if (isight->context) { 3433a691b28SClemens Ladisch if (isight->packet_index < 0) 3443a691b28SClemens Ladisch isight_stop_streaming(isight); 3453a691b28SClemens Ladisch else 3463a691b28SClemens Ladisch return 0; 3473a691b28SClemens Ladisch } 3483a691b28SClemens Ladisch 349ac34dad2SStefan Richter err = reg_write(isight, REG_SAMPLE_RATE, cpu_to_be32(RATE_48000)); 3503a691b28SClemens Ladisch if (err < 0) 351ac34dad2SStefan Richter goto error; 3523a691b28SClemens Ladisch 3533a691b28SClemens Ladisch err = isight_connect(isight); 3543a691b28SClemens Ladisch if (err < 0) 3553a691b28SClemens Ladisch goto error; 3563a691b28SClemens Ladisch 357ac34dad2SStefan Richter err = reg_write(isight, REG_AUDIO_ENABLE, cpu_to_be32(AUDIO_ENABLE)); 3588839eedaSStefan Richter if (err < 0) 3598839eedaSStefan Richter goto err_resources; 3608839eedaSStefan Richter 3613a691b28SClemens Ladisch isight->context = fw_iso_context_create(isight->device->card, 3623a691b28SClemens Ladisch FW_ISO_CONTEXT_RECEIVE, 3633a691b28SClemens Ladisch isight->resources.channel, 3643a691b28SClemens Ladisch isight->device->max_speed, 3653a691b28SClemens Ladisch 4, isight_packet, isight); 3663a691b28SClemens Ladisch if (IS_ERR(isight->context)) { 3673a691b28SClemens Ladisch err = PTR_ERR(isight->context); 3683a691b28SClemens Ladisch isight->context = NULL; 3693a691b28SClemens Ladisch goto err_resources; 3703a691b28SClemens Ladisch } 3713a691b28SClemens Ladisch 3723a691b28SClemens Ladisch for (i = 0; i < QUEUE_LENGTH; ++i) { 3733a691b28SClemens Ladisch err = fw_iso_context_queue(isight->context, &audio_packet, 3743a691b28SClemens Ladisch &isight->buffer.iso_buffer, 3753a691b28SClemens Ladisch isight->buffer.packets[i].offset); 3763a691b28SClemens Ladisch if (err < 0) 3773a691b28SClemens Ladisch goto err_context; 3783a691b28SClemens Ladisch } 3793a691b28SClemens Ladisch 3803a691b28SClemens Ladisch isight->first_packet = true; 3813a691b28SClemens Ladisch isight->packet_index = 0; 3823a691b28SClemens Ladisch 3833a691b28SClemens Ladisch err = fw_iso_context_start(isight->context, -1, 0, 3843a691b28SClemens Ladisch FW_ISO_CONTEXT_MATCH_ALL_TAGS/*?*/); 3853a691b28SClemens Ladisch if (err < 0) 3863a691b28SClemens Ladisch goto err_context; 3873a691b28SClemens Ladisch 3883a691b28SClemens Ladisch return 0; 3893a691b28SClemens Ladisch 3903a691b28SClemens Ladisch err_context: 3913a691b28SClemens Ladisch fw_iso_context_destroy(isight->context); 3923a691b28SClemens Ladisch isight->context = NULL; 3933a691b28SClemens Ladisch err_resources: 3943a691b28SClemens Ladisch fw_iso_resources_free(&isight->resources); 395ac34dad2SStefan Richter reg_write(isight, REG_AUDIO_ENABLE, 0); 3963a691b28SClemens Ladisch error: 3973a691b28SClemens Ladisch return err; 3983a691b28SClemens Ladisch } 3993a691b28SClemens Ladisch 4003a691b28SClemens Ladisch static int isight_prepare(struct snd_pcm_substream *substream) 4013a691b28SClemens Ladisch { 4023a691b28SClemens Ladisch struct isight *isight = substream->private_data; 4033a691b28SClemens Ladisch int err; 4043a691b28SClemens Ladisch 4053a691b28SClemens Ladisch isight->buffer_pointer = 0; 4063a691b28SClemens Ladisch isight->period_counter = 0; 4073a691b28SClemens Ladisch 4083a691b28SClemens Ladisch mutex_lock(&isight->mutex); 4093a691b28SClemens Ladisch err = isight_start_streaming(isight); 4103a691b28SClemens Ladisch mutex_unlock(&isight->mutex); 4113a691b28SClemens Ladisch 4123a691b28SClemens Ladisch return err; 4133a691b28SClemens Ladisch } 4143a691b28SClemens Ladisch 4153a691b28SClemens Ladisch static int isight_trigger(struct snd_pcm_substream *substream, int cmd) 4163a691b28SClemens Ladisch { 4173a691b28SClemens Ladisch struct isight *isight = substream->private_data; 4183a691b28SClemens Ladisch 4193a691b28SClemens Ladisch switch (cmd) { 4203a691b28SClemens Ladisch case SNDRV_PCM_TRIGGER_START: 4216aa7de05SMark Rutland WRITE_ONCE(isight->pcm_running, true); 4223a691b28SClemens Ladisch break; 4233a691b28SClemens Ladisch case SNDRV_PCM_TRIGGER_STOP: 4246aa7de05SMark Rutland WRITE_ONCE(isight->pcm_running, false); 4253a691b28SClemens Ladisch break; 4263a691b28SClemens Ladisch default: 4273a691b28SClemens Ladisch return -EINVAL; 4283a691b28SClemens Ladisch } 4293a691b28SClemens Ladisch return 0; 4303a691b28SClemens Ladisch } 4313a691b28SClemens Ladisch 4323a691b28SClemens Ladisch static snd_pcm_uframes_t isight_pointer(struct snd_pcm_substream *substream) 4333a691b28SClemens Ladisch { 4343a691b28SClemens Ladisch struct isight *isight = substream->private_data; 4353a691b28SClemens Ladisch 4366aa7de05SMark Rutland return READ_ONCE(isight->buffer_pointer); 4373a691b28SClemens Ladisch } 4383a691b28SClemens Ladisch 4393a691b28SClemens Ladisch static int isight_create_pcm(struct isight *isight) 4403a691b28SClemens Ladisch { 441b2165f38SArvind Yadav static const struct snd_pcm_ops ops = { 4423a691b28SClemens Ladisch .open = isight_open, 4433a691b28SClemens Ladisch .close = isight_close, 4443a691b28SClemens Ladisch .hw_params = isight_hw_params, 4453a691b28SClemens Ladisch .hw_free = isight_hw_free, 4463a691b28SClemens Ladisch .prepare = isight_prepare, 4473a691b28SClemens Ladisch .trigger = isight_trigger, 4483a691b28SClemens Ladisch .pointer = isight_pointer, 4493a691b28SClemens Ladisch }; 4503a691b28SClemens Ladisch struct snd_pcm *pcm; 4513a691b28SClemens Ladisch int err; 4523a691b28SClemens Ladisch 4533a691b28SClemens Ladisch err = snd_pcm_new(isight->card, "iSight", 0, 0, 1, &pcm); 4543a691b28SClemens Ladisch if (err < 0) 4553a691b28SClemens Ladisch return err; 4563a691b28SClemens Ladisch pcm->private_data = isight; 4573a691b28SClemens Ladisch strcpy(pcm->name, "iSight"); 4583a691b28SClemens Ladisch isight->pcm = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream; 4593a691b28SClemens Ladisch isight->pcm->ops = &ops; 460*7641d549STakashi Iwai snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_VMALLOC, NULL, 0, 0); 4613a691b28SClemens Ladisch 4623a691b28SClemens Ladisch return 0; 4633a691b28SClemens Ladisch } 4643a691b28SClemens Ladisch 4653a691b28SClemens Ladisch static int isight_gain_info(struct snd_kcontrol *ctl, 4663a691b28SClemens Ladisch struct snd_ctl_elem_info *info) 4673a691b28SClemens Ladisch { 4683a691b28SClemens Ladisch struct isight *isight = ctl->private_data; 4693a691b28SClemens Ladisch 4703a691b28SClemens Ladisch info->type = SNDRV_CTL_ELEM_TYPE_INTEGER; 4713a691b28SClemens Ladisch info->count = 1; 4723a691b28SClemens Ladisch info->value.integer.min = isight->gain_min; 4733a691b28SClemens Ladisch info->value.integer.max = isight->gain_max; 4743a691b28SClemens Ladisch 4753a691b28SClemens Ladisch return 0; 4763a691b28SClemens Ladisch } 4773a691b28SClemens Ladisch 4783a691b28SClemens Ladisch static int isight_gain_get(struct snd_kcontrol *ctl, 4793a691b28SClemens Ladisch struct snd_ctl_elem_value *value) 4803a691b28SClemens Ladisch { 4813a691b28SClemens Ladisch struct isight *isight = ctl->private_data; 4823a691b28SClemens Ladisch __be32 gain; 4833a691b28SClemens Ladisch int err; 4843a691b28SClemens Ladisch 485ac34dad2SStefan Richter err = reg_read(isight, REG_GAIN, &gain); 4863a691b28SClemens Ladisch if (err < 0) 4873a691b28SClemens Ladisch return err; 4883a691b28SClemens Ladisch 4893a691b28SClemens Ladisch value->value.integer.value[0] = (s32)be32_to_cpu(gain); 4903a691b28SClemens Ladisch 4913a691b28SClemens Ladisch return 0; 4923a691b28SClemens Ladisch } 4933a691b28SClemens Ladisch 4943a691b28SClemens Ladisch static int isight_gain_put(struct snd_kcontrol *ctl, 4953a691b28SClemens Ladisch struct snd_ctl_elem_value *value) 4963a691b28SClemens Ladisch { 4973a691b28SClemens Ladisch struct isight *isight = ctl->private_data; 4983a691b28SClemens Ladisch 4993a691b28SClemens Ladisch if (value->value.integer.value[0] < isight->gain_min || 5003a691b28SClemens Ladisch value->value.integer.value[0] > isight->gain_max) 5013a691b28SClemens Ladisch return -EINVAL; 5023a691b28SClemens Ladisch 503ac34dad2SStefan Richter return reg_write(isight, REG_GAIN, 504ac34dad2SStefan Richter cpu_to_be32(value->value.integer.value[0])); 5053a691b28SClemens Ladisch } 5063a691b28SClemens Ladisch 5073a691b28SClemens Ladisch static int isight_mute_get(struct snd_kcontrol *ctl, 5083a691b28SClemens Ladisch struct snd_ctl_elem_value *value) 5093a691b28SClemens Ladisch { 5103a691b28SClemens Ladisch struct isight *isight = ctl->private_data; 5113a691b28SClemens Ladisch __be32 mute; 5123a691b28SClemens Ladisch int err; 5133a691b28SClemens Ladisch 514ac34dad2SStefan Richter err = reg_read(isight, REG_MUTE, &mute); 5153a691b28SClemens Ladisch if (err < 0) 5163a691b28SClemens Ladisch return err; 5173a691b28SClemens Ladisch 5183a691b28SClemens Ladisch value->value.integer.value[0] = !mute; 5193a691b28SClemens Ladisch 5203a691b28SClemens Ladisch return 0; 5213a691b28SClemens Ladisch } 5223a691b28SClemens Ladisch 5233a691b28SClemens Ladisch static int isight_mute_put(struct snd_kcontrol *ctl, 5243a691b28SClemens Ladisch struct snd_ctl_elem_value *value) 5253a691b28SClemens Ladisch { 5263a691b28SClemens Ladisch struct isight *isight = ctl->private_data; 5273a691b28SClemens Ladisch 528ac34dad2SStefan Richter return reg_write(isight, REG_MUTE, 529ac34dad2SStefan Richter (__force __be32)!value->value.integer.value[0]); 5303a691b28SClemens Ladisch } 5313a691b28SClemens Ladisch 5323a691b28SClemens Ladisch static int isight_create_mixer(struct isight *isight) 5333a691b28SClemens Ladisch { 5343a691b28SClemens Ladisch static const struct snd_kcontrol_new gain_control = { 5353a691b28SClemens Ladisch .iface = SNDRV_CTL_ELEM_IFACE_MIXER, 5363a691b28SClemens Ladisch .name = "Mic Capture Volume", 5373a691b28SClemens Ladisch .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | 5383a691b28SClemens Ladisch SNDRV_CTL_ELEM_ACCESS_TLV_READ, 5393a691b28SClemens Ladisch .info = isight_gain_info, 5403a691b28SClemens Ladisch .get = isight_gain_get, 5413a691b28SClemens Ladisch .put = isight_gain_put, 5423a691b28SClemens Ladisch }; 5433a691b28SClemens Ladisch static const struct snd_kcontrol_new mute_control = { 5443a691b28SClemens Ladisch .iface = SNDRV_CTL_ELEM_IFACE_MIXER, 5453a691b28SClemens Ladisch .name = "Mic Capture Switch", 5463a691b28SClemens Ladisch .info = snd_ctl_boolean_mono_info, 5473a691b28SClemens Ladisch .get = isight_mute_get, 5483a691b28SClemens Ladisch .put = isight_mute_put, 5493a691b28SClemens Ladisch }; 5503a691b28SClemens Ladisch __be32 value; 5513a691b28SClemens Ladisch struct snd_kcontrol *ctl; 5523a691b28SClemens Ladisch int err; 5533a691b28SClemens Ladisch 554ac34dad2SStefan Richter err = reg_read(isight, REG_GAIN_RAW_START, &value); 5553a691b28SClemens Ladisch if (err < 0) 5563a691b28SClemens Ladisch return err; 5573a691b28SClemens Ladisch isight->gain_min = be32_to_cpu(value); 5583a691b28SClemens Ladisch 559ac34dad2SStefan Richter err = reg_read(isight, REG_GAIN_RAW_END, &value); 5603a691b28SClemens Ladisch if (err < 0) 5613a691b28SClemens Ladisch return err; 5623a691b28SClemens Ladisch isight->gain_max = be32_to_cpu(value); 5633a691b28SClemens Ladisch 5644844f512STakashi Sakamoto isight->gain_tlv[SNDRV_CTL_TLVO_TYPE] = SNDRV_CTL_TLVT_DB_MINMAX; 5654844f512STakashi Sakamoto isight->gain_tlv[SNDRV_CTL_TLVO_LEN] = 2 * sizeof(unsigned int); 566ac34dad2SStefan Richter 567ac34dad2SStefan Richter err = reg_read(isight, REG_GAIN_DB_START, &value); 5683a691b28SClemens Ladisch if (err < 0) 5693a691b28SClemens Ladisch return err; 5704844f512STakashi Sakamoto isight->gain_tlv[SNDRV_CTL_TLVO_DB_MINMAX_MIN] = 5714844f512STakashi Sakamoto (s32)be32_to_cpu(value) * 100; 572ac34dad2SStefan Richter 573ac34dad2SStefan Richter err = reg_read(isight, REG_GAIN_DB_END, &value); 5743a691b28SClemens Ladisch if (err < 0) 5753a691b28SClemens Ladisch return err; 5764844f512STakashi Sakamoto isight->gain_tlv[SNDRV_CTL_TLVO_DB_MINMAX_MAX] = 5774844f512STakashi Sakamoto (s32)be32_to_cpu(value) * 100; 5783a691b28SClemens Ladisch 5793a691b28SClemens Ladisch ctl = snd_ctl_new1(&gain_control, isight); 5803a691b28SClemens Ladisch if (ctl) 5813a691b28SClemens Ladisch ctl->tlv.p = isight->gain_tlv; 5823a691b28SClemens Ladisch err = snd_ctl_add(isight->card, ctl); 5833a691b28SClemens Ladisch if (err < 0) 5843a691b28SClemens Ladisch return err; 5853a691b28SClemens Ladisch 5863a691b28SClemens Ladisch err = snd_ctl_add(isight->card, snd_ctl_new1(&mute_control, isight)); 5873a691b28SClemens Ladisch if (err < 0) 5883a691b28SClemens Ladisch return err; 5893a691b28SClemens Ladisch 5903a691b28SClemens Ladisch return 0; 5913a691b28SClemens Ladisch } 5923a691b28SClemens Ladisch 5933a691b28SClemens Ladisch static void isight_card_free(struct snd_card *card) 5943a691b28SClemens Ladisch { 5953a691b28SClemens Ladisch struct isight *isight = card->private_data; 5963a691b28SClemens Ladisch 5973a691b28SClemens Ladisch fw_iso_resources_destroy(&isight->resources); 5983a691b28SClemens Ladisch } 5993a691b28SClemens Ladisch 6003a691b28SClemens Ladisch static u64 get_unit_base(struct fw_unit *unit) 6013a691b28SClemens Ladisch { 6023a691b28SClemens Ladisch struct fw_csr_iterator i; 6033a691b28SClemens Ladisch int key, value; 6043a691b28SClemens Ladisch 6053a691b28SClemens Ladisch fw_csr_iterator_init(&i, unit->directory); 6063a691b28SClemens Ladisch while (fw_csr_iterator_next(&i, &key, &value)) 6073a691b28SClemens Ladisch if (key == CSR_OFFSET) 6083a691b28SClemens Ladisch return CSR_REGISTER_BASE + value * 4; 6093a691b28SClemens Ladisch return 0; 6103a691b28SClemens Ladisch } 6113a691b28SClemens Ladisch 61294a87157SStefan Richter static int isight_probe(struct fw_unit *unit, 61394a87157SStefan Richter const struct ieee1394_device_id *id) 6143a691b28SClemens Ladisch { 6153a691b28SClemens Ladisch struct fw_device *fw_dev = fw_parent_device(unit); 6163a691b28SClemens Ladisch struct snd_card *card; 6173a691b28SClemens Ladisch struct isight *isight; 6183a691b28SClemens Ladisch int err; 6193a691b28SClemens Ladisch 62006b45f00STakashi Iwai err = snd_card_new(&unit->device, -1, NULL, THIS_MODULE, 62106b45f00STakashi Iwai sizeof(*isight), &card); 6223a691b28SClemens Ladisch if (err < 0) 6233a691b28SClemens Ladisch return err; 6243a691b28SClemens Ladisch 6253a691b28SClemens Ladisch isight = card->private_data; 6263a691b28SClemens Ladisch isight->card = card; 6273a691b28SClemens Ladisch mutex_init(&isight->mutex); 6283a691b28SClemens Ladisch isight->unit = fw_unit_get(unit); 62921076226SStefan Richter isight->device = fw_dev; 6303a691b28SClemens Ladisch isight->audio_base = get_unit_base(unit); 6313a691b28SClemens Ladisch if (!isight->audio_base) { 6323a691b28SClemens Ladisch dev_err(&unit->device, "audio unit base not found\n"); 6333a691b28SClemens Ladisch err = -ENXIO; 63451e68fb0STakashi Sakamoto goto error; 6353a691b28SClemens Ladisch } 6363a691b28SClemens Ladisch fw_iso_resources_init(&isight->resources, unit); 6373a691b28SClemens Ladisch 6383a691b28SClemens Ladisch card->private_free = isight_card_free; 6393a691b28SClemens Ladisch 6403a691b28SClemens Ladisch strcpy(card->driver, "iSight"); 6413a691b28SClemens Ladisch strcpy(card->shortname, "Apple iSight"); 6423a691b28SClemens Ladisch snprintf(card->longname, sizeof(card->longname), 6433a691b28SClemens Ladisch "Apple iSight (GUID %08x%08x) at %s, S%d", 6443a691b28SClemens Ladisch fw_dev->config_rom[3], fw_dev->config_rom[4], 6453a691b28SClemens Ladisch dev_name(&unit->device), 100 << fw_dev->max_speed); 6463a691b28SClemens Ladisch strcpy(card->mixername, "iSight"); 6473a691b28SClemens Ladisch 6483a691b28SClemens Ladisch err = isight_create_pcm(isight); 6493a691b28SClemens Ladisch if (err < 0) 6503a691b28SClemens Ladisch goto error; 6513a691b28SClemens Ladisch 6523a691b28SClemens Ladisch err = isight_create_mixer(isight); 6533a691b28SClemens Ladisch if (err < 0) 6543a691b28SClemens Ladisch goto error; 6553a691b28SClemens Ladisch 6563a691b28SClemens Ladisch err = snd_card_register(card); 6573a691b28SClemens Ladisch if (err < 0) 6583a691b28SClemens Ladisch goto error; 6593a691b28SClemens Ladisch 66094a87157SStefan Richter dev_set_drvdata(&unit->device, isight); 6613a691b28SClemens Ladisch 6623a691b28SClemens Ladisch return 0; 6633a691b28SClemens Ladisch error: 6643a691b28SClemens Ladisch snd_card_free(card); 66551e68fb0STakashi Sakamoto 66651e68fb0STakashi Sakamoto mutex_destroy(&isight->mutex); 66751e68fb0STakashi Sakamoto fw_unit_put(isight->unit); 66851e68fb0STakashi Sakamoto 6693a691b28SClemens Ladisch return err; 6703a691b28SClemens Ladisch } 6713a691b28SClemens Ladisch 6723a691b28SClemens Ladisch static void isight_bus_reset(struct fw_unit *unit) 6733a691b28SClemens Ladisch { 6743a691b28SClemens Ladisch struct isight *isight = dev_get_drvdata(&unit->device); 6753a691b28SClemens Ladisch 6763a691b28SClemens Ladisch if (fw_iso_resources_update(&isight->resources) < 0) { 6773a691b28SClemens Ladisch isight_pcm_abort(isight); 678f3f7c183SClemens Ladisch 679f3f7c183SClemens Ladisch mutex_lock(&isight->mutex); 6803a691b28SClemens Ladisch isight_stop_streaming(isight); 6813a691b28SClemens Ladisch mutex_unlock(&isight->mutex); 6823a691b28SClemens Ladisch } 683f3f7c183SClemens Ladisch } 6843a691b28SClemens Ladisch 68594a87157SStefan Richter static void isight_remove(struct fw_unit *unit) 68694a87157SStefan Richter { 68794a87157SStefan Richter struct isight *isight = dev_get_drvdata(&unit->device); 68894a87157SStefan Richter 68994a87157SStefan Richter isight_pcm_abort(isight); 69094a87157SStefan Richter 69194a87157SStefan Richter snd_card_disconnect(isight->card); 69294a87157SStefan Richter 69394a87157SStefan Richter mutex_lock(&isight->mutex); 69494a87157SStefan Richter isight_stop_streaming(isight); 69594a87157SStefan Richter mutex_unlock(&isight->mutex); 69694a87157SStefan Richter 69761ccc6f6STakashi Sakamoto // Block till all of ALSA character devices are released. 69861ccc6f6STakashi Sakamoto snd_card_free(isight->card); 6995b14ec25STakashi Sakamoto 7005b14ec25STakashi Sakamoto mutex_destroy(&isight->mutex); 7015b14ec25STakashi Sakamoto fw_unit_put(isight->unit); 70294a87157SStefan Richter } 70394a87157SStefan Richter 7043a691b28SClemens Ladisch static const struct ieee1394_device_id isight_id_table[] = { 7053a691b28SClemens Ladisch { 7063a691b28SClemens Ladisch .match_flags = IEEE1394_MATCH_SPECIFIER_ID | 7073a691b28SClemens Ladisch IEEE1394_MATCH_VERSION, 7083a691b28SClemens Ladisch .specifier_id = OUI_APPLE, 7093a691b28SClemens Ladisch .version = SW_ISIGHT_AUDIO, 7103a691b28SClemens Ladisch }, 7113a691b28SClemens Ladisch { } 7123a691b28SClemens Ladisch }; 7133a691b28SClemens Ladisch MODULE_DEVICE_TABLE(ieee1394, isight_id_table); 7143a691b28SClemens Ladisch 7153a691b28SClemens Ladisch static struct fw_driver isight_driver = { 7163a691b28SClemens Ladisch .driver = { 7173a691b28SClemens Ladisch .owner = THIS_MODULE, 7183a691b28SClemens Ladisch .name = KBUILD_MODNAME, 7193a691b28SClemens Ladisch .bus = &fw_bus_type, 7203a691b28SClemens Ladisch }, 72194a87157SStefan Richter .probe = isight_probe, 7223a691b28SClemens Ladisch .update = isight_bus_reset, 72394a87157SStefan Richter .remove = isight_remove, 7243a691b28SClemens Ladisch .id_table = isight_id_table, 7253a691b28SClemens Ladisch }; 7263a691b28SClemens Ladisch 7273a691b28SClemens Ladisch static int __init alsa_isight_init(void) 7283a691b28SClemens Ladisch { 7293a691b28SClemens Ladisch return driver_register(&isight_driver.driver); 7303a691b28SClemens Ladisch } 7313a691b28SClemens Ladisch 7323a691b28SClemens Ladisch static void __exit alsa_isight_exit(void) 7333a691b28SClemens Ladisch { 7343a691b28SClemens Ladisch driver_unregister(&isight_driver.driver); 7353a691b28SClemens Ladisch } 7363a691b28SClemens Ladisch 7373a691b28SClemens Ladisch module_init(alsa_isight_init); 7383a691b28SClemens Ladisch module_exit(alsa_isight_exit); 739