162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Apple iSight audio driver 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright (c) Clemens Ladisch <clemens@ladisch.de> 662306a36Sopenharmony_ci */ 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci#include <asm/byteorder.h> 962306a36Sopenharmony_ci#include <linux/delay.h> 1062306a36Sopenharmony_ci#include <linux/device.h> 1162306a36Sopenharmony_ci#include <linux/firewire.h> 1262306a36Sopenharmony_ci#include <linux/firewire-constants.h> 1362306a36Sopenharmony_ci#include <linux/module.h> 1462306a36Sopenharmony_ci#include <linux/mod_devicetable.h> 1562306a36Sopenharmony_ci#include <linux/mutex.h> 1662306a36Sopenharmony_ci#include <linux/string.h> 1762306a36Sopenharmony_ci#include <sound/control.h> 1862306a36Sopenharmony_ci#include <sound/core.h> 1962306a36Sopenharmony_ci#include <sound/initval.h> 2062306a36Sopenharmony_ci#include <sound/pcm.h> 2162306a36Sopenharmony_ci#include <sound/tlv.h> 2262306a36Sopenharmony_ci#include "lib.h" 2362306a36Sopenharmony_ci#include "iso-resources.h" 2462306a36Sopenharmony_ci#include "packets-buffer.h" 2562306a36Sopenharmony_ci 2662306a36Sopenharmony_ci#define OUI_APPLE 0x000a27 2762306a36Sopenharmony_ci#define MODEL_APPLE_ISIGHT 0x000008 2862306a36Sopenharmony_ci#define SW_ISIGHT_AUDIO 0x000010 2962306a36Sopenharmony_ci 3062306a36Sopenharmony_ci#define REG_AUDIO_ENABLE 0x000 3162306a36Sopenharmony_ci#define AUDIO_ENABLE 0x80000000 3262306a36Sopenharmony_ci#define REG_DEF_AUDIO_GAIN 0x204 3362306a36Sopenharmony_ci#define REG_GAIN_RAW_START 0x210 3462306a36Sopenharmony_ci#define REG_GAIN_RAW_END 0x214 3562306a36Sopenharmony_ci#define REG_GAIN_DB_START 0x218 3662306a36Sopenharmony_ci#define REG_GAIN_DB_END 0x21c 3762306a36Sopenharmony_ci#define REG_SAMPLE_RATE_INQUIRY 0x280 3862306a36Sopenharmony_ci#define REG_ISO_TX_CONFIG 0x300 3962306a36Sopenharmony_ci#define SPEED_SHIFT 16 4062306a36Sopenharmony_ci#define REG_SAMPLE_RATE 0x400 4162306a36Sopenharmony_ci#define RATE_48000 0x80000000 4262306a36Sopenharmony_ci#define REG_GAIN 0x500 4362306a36Sopenharmony_ci#define REG_MUTE 0x504 4462306a36Sopenharmony_ci 4562306a36Sopenharmony_ci#define MAX_FRAMES_PER_PACKET 475 4662306a36Sopenharmony_ci 4762306a36Sopenharmony_ci#define QUEUE_LENGTH 20 4862306a36Sopenharmony_ci 4962306a36Sopenharmony_cistruct isight { 5062306a36Sopenharmony_ci struct snd_card *card; 5162306a36Sopenharmony_ci struct fw_unit *unit; 5262306a36Sopenharmony_ci struct fw_device *device; 5362306a36Sopenharmony_ci u64 audio_base; 5462306a36Sopenharmony_ci struct snd_pcm_substream *pcm; 5562306a36Sopenharmony_ci struct mutex mutex; 5662306a36Sopenharmony_ci struct iso_packets_buffer buffer; 5762306a36Sopenharmony_ci struct fw_iso_resources resources; 5862306a36Sopenharmony_ci struct fw_iso_context *context; 5962306a36Sopenharmony_ci bool pcm_active; 6062306a36Sopenharmony_ci bool pcm_running; 6162306a36Sopenharmony_ci bool first_packet; 6262306a36Sopenharmony_ci int packet_index; 6362306a36Sopenharmony_ci u32 total_samples; 6462306a36Sopenharmony_ci unsigned int buffer_pointer; 6562306a36Sopenharmony_ci unsigned int period_counter; 6662306a36Sopenharmony_ci s32 gain_min, gain_max; 6762306a36Sopenharmony_ci unsigned int gain_tlv[4]; 6862306a36Sopenharmony_ci}; 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_cistruct audio_payload { 7162306a36Sopenharmony_ci __be32 sample_count; 7262306a36Sopenharmony_ci __be32 signature; 7362306a36Sopenharmony_ci __be32 sample_total; 7462306a36Sopenharmony_ci __be32 reserved; 7562306a36Sopenharmony_ci __be16 samples[2 * MAX_FRAMES_PER_PACKET]; 7662306a36Sopenharmony_ci}; 7762306a36Sopenharmony_ci 7862306a36Sopenharmony_ciMODULE_DESCRIPTION("iSight audio driver"); 7962306a36Sopenharmony_ciMODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>"); 8062306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_cistatic struct fw_iso_packet audio_packet = { 8362306a36Sopenharmony_ci .payload_length = sizeof(struct audio_payload), 8462306a36Sopenharmony_ci .interrupt = 1, 8562306a36Sopenharmony_ci .header_length = 4, 8662306a36Sopenharmony_ci}; 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_cistatic void isight_update_pointers(struct isight *isight, unsigned int count) 8962306a36Sopenharmony_ci{ 9062306a36Sopenharmony_ci struct snd_pcm_runtime *runtime = isight->pcm->runtime; 9162306a36Sopenharmony_ci unsigned int ptr; 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_ci smp_wmb(); /* update buffer data before buffer pointer */ 9462306a36Sopenharmony_ci 9562306a36Sopenharmony_ci ptr = isight->buffer_pointer; 9662306a36Sopenharmony_ci ptr += count; 9762306a36Sopenharmony_ci if (ptr >= runtime->buffer_size) 9862306a36Sopenharmony_ci ptr -= runtime->buffer_size; 9962306a36Sopenharmony_ci WRITE_ONCE(isight->buffer_pointer, ptr); 10062306a36Sopenharmony_ci 10162306a36Sopenharmony_ci isight->period_counter += count; 10262306a36Sopenharmony_ci if (isight->period_counter >= runtime->period_size) { 10362306a36Sopenharmony_ci isight->period_counter -= runtime->period_size; 10462306a36Sopenharmony_ci snd_pcm_period_elapsed(isight->pcm); 10562306a36Sopenharmony_ci } 10662306a36Sopenharmony_ci} 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_cistatic void isight_samples(struct isight *isight, 10962306a36Sopenharmony_ci const __be16 *samples, unsigned int count) 11062306a36Sopenharmony_ci{ 11162306a36Sopenharmony_ci struct snd_pcm_runtime *runtime; 11262306a36Sopenharmony_ci unsigned int count1; 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_ci if (!READ_ONCE(isight->pcm_running)) 11562306a36Sopenharmony_ci return; 11662306a36Sopenharmony_ci 11762306a36Sopenharmony_ci runtime = isight->pcm->runtime; 11862306a36Sopenharmony_ci if (isight->buffer_pointer + count <= runtime->buffer_size) { 11962306a36Sopenharmony_ci memcpy(runtime->dma_area + isight->buffer_pointer * 4, 12062306a36Sopenharmony_ci samples, count * 4); 12162306a36Sopenharmony_ci } else { 12262306a36Sopenharmony_ci count1 = runtime->buffer_size - isight->buffer_pointer; 12362306a36Sopenharmony_ci memcpy(runtime->dma_area + isight->buffer_pointer * 4, 12462306a36Sopenharmony_ci samples, count1 * 4); 12562306a36Sopenharmony_ci samples += count1 * 2; 12662306a36Sopenharmony_ci memcpy(runtime->dma_area, samples, (count - count1) * 4); 12762306a36Sopenharmony_ci } 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_ci isight_update_pointers(isight, count); 13062306a36Sopenharmony_ci} 13162306a36Sopenharmony_ci 13262306a36Sopenharmony_cistatic void isight_pcm_abort(struct isight *isight) 13362306a36Sopenharmony_ci{ 13462306a36Sopenharmony_ci if (READ_ONCE(isight->pcm_active)) 13562306a36Sopenharmony_ci snd_pcm_stop_xrun(isight->pcm); 13662306a36Sopenharmony_ci} 13762306a36Sopenharmony_ci 13862306a36Sopenharmony_cistatic void isight_dropped_samples(struct isight *isight, unsigned int total) 13962306a36Sopenharmony_ci{ 14062306a36Sopenharmony_ci struct snd_pcm_runtime *runtime; 14162306a36Sopenharmony_ci u32 dropped; 14262306a36Sopenharmony_ci unsigned int count1; 14362306a36Sopenharmony_ci 14462306a36Sopenharmony_ci if (!READ_ONCE(isight->pcm_running)) 14562306a36Sopenharmony_ci return; 14662306a36Sopenharmony_ci 14762306a36Sopenharmony_ci runtime = isight->pcm->runtime; 14862306a36Sopenharmony_ci dropped = total - isight->total_samples; 14962306a36Sopenharmony_ci if (dropped < runtime->buffer_size) { 15062306a36Sopenharmony_ci if (isight->buffer_pointer + dropped <= runtime->buffer_size) { 15162306a36Sopenharmony_ci memset(runtime->dma_area + isight->buffer_pointer * 4, 15262306a36Sopenharmony_ci 0, dropped * 4); 15362306a36Sopenharmony_ci } else { 15462306a36Sopenharmony_ci count1 = runtime->buffer_size - isight->buffer_pointer; 15562306a36Sopenharmony_ci memset(runtime->dma_area + isight->buffer_pointer * 4, 15662306a36Sopenharmony_ci 0, count1 * 4); 15762306a36Sopenharmony_ci memset(runtime->dma_area, 0, (dropped - count1) * 4); 15862306a36Sopenharmony_ci } 15962306a36Sopenharmony_ci isight_update_pointers(isight, dropped); 16062306a36Sopenharmony_ci } else { 16162306a36Sopenharmony_ci isight_pcm_abort(isight); 16262306a36Sopenharmony_ci } 16362306a36Sopenharmony_ci} 16462306a36Sopenharmony_ci 16562306a36Sopenharmony_cistatic void isight_packet(struct fw_iso_context *context, u32 cycle, 16662306a36Sopenharmony_ci size_t header_length, void *header, void *data) 16762306a36Sopenharmony_ci{ 16862306a36Sopenharmony_ci struct isight *isight = data; 16962306a36Sopenharmony_ci const struct audio_payload *payload; 17062306a36Sopenharmony_ci unsigned int index, length, count, total; 17162306a36Sopenharmony_ci int err; 17262306a36Sopenharmony_ci 17362306a36Sopenharmony_ci if (isight->packet_index < 0) 17462306a36Sopenharmony_ci return; 17562306a36Sopenharmony_ci index = isight->packet_index; 17662306a36Sopenharmony_ci payload = isight->buffer.packets[index].buffer; 17762306a36Sopenharmony_ci length = be32_to_cpup(header) >> 16; 17862306a36Sopenharmony_ci 17962306a36Sopenharmony_ci if (likely(length >= 16 && 18062306a36Sopenharmony_ci payload->signature == cpu_to_be32(0x73676874/*"sght"*/))) { 18162306a36Sopenharmony_ci count = be32_to_cpu(payload->sample_count); 18262306a36Sopenharmony_ci if (likely(count <= (length - 16) / 4)) { 18362306a36Sopenharmony_ci total = be32_to_cpu(payload->sample_total); 18462306a36Sopenharmony_ci if (unlikely(total != isight->total_samples)) { 18562306a36Sopenharmony_ci if (!isight->first_packet) 18662306a36Sopenharmony_ci isight_dropped_samples(isight, total); 18762306a36Sopenharmony_ci isight->first_packet = false; 18862306a36Sopenharmony_ci isight->total_samples = total; 18962306a36Sopenharmony_ci } 19062306a36Sopenharmony_ci 19162306a36Sopenharmony_ci isight_samples(isight, payload->samples, count); 19262306a36Sopenharmony_ci isight->total_samples += count; 19362306a36Sopenharmony_ci } 19462306a36Sopenharmony_ci } 19562306a36Sopenharmony_ci 19662306a36Sopenharmony_ci err = fw_iso_context_queue(isight->context, &audio_packet, 19762306a36Sopenharmony_ci &isight->buffer.iso_buffer, 19862306a36Sopenharmony_ci isight->buffer.packets[index].offset); 19962306a36Sopenharmony_ci if (err < 0) { 20062306a36Sopenharmony_ci dev_err(&isight->unit->device, "queueing error: %d\n", err); 20162306a36Sopenharmony_ci isight_pcm_abort(isight); 20262306a36Sopenharmony_ci isight->packet_index = -1; 20362306a36Sopenharmony_ci return; 20462306a36Sopenharmony_ci } 20562306a36Sopenharmony_ci fw_iso_context_queue_flush(isight->context); 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_ci if (++index >= QUEUE_LENGTH) 20862306a36Sopenharmony_ci index = 0; 20962306a36Sopenharmony_ci isight->packet_index = index; 21062306a36Sopenharmony_ci} 21162306a36Sopenharmony_ci 21262306a36Sopenharmony_cistatic int isight_connect(struct isight *isight) 21362306a36Sopenharmony_ci{ 21462306a36Sopenharmony_ci int ch, err; 21562306a36Sopenharmony_ci __be32 value; 21662306a36Sopenharmony_ci 21762306a36Sopenharmony_ciretry_after_bus_reset: 21862306a36Sopenharmony_ci ch = fw_iso_resources_allocate(&isight->resources, 21962306a36Sopenharmony_ci sizeof(struct audio_payload), 22062306a36Sopenharmony_ci isight->device->max_speed); 22162306a36Sopenharmony_ci if (ch < 0) { 22262306a36Sopenharmony_ci err = ch; 22362306a36Sopenharmony_ci goto error; 22462306a36Sopenharmony_ci } 22562306a36Sopenharmony_ci 22662306a36Sopenharmony_ci value = cpu_to_be32(ch | (isight->device->max_speed << SPEED_SHIFT)); 22762306a36Sopenharmony_ci err = snd_fw_transaction(isight->unit, TCODE_WRITE_QUADLET_REQUEST, 22862306a36Sopenharmony_ci isight->audio_base + REG_ISO_TX_CONFIG, 22962306a36Sopenharmony_ci &value, 4, FW_FIXED_GENERATION | 23062306a36Sopenharmony_ci isight->resources.generation); 23162306a36Sopenharmony_ci if (err == -EAGAIN) { 23262306a36Sopenharmony_ci fw_iso_resources_free(&isight->resources); 23362306a36Sopenharmony_ci goto retry_after_bus_reset; 23462306a36Sopenharmony_ci } else if (err < 0) { 23562306a36Sopenharmony_ci goto err_resources; 23662306a36Sopenharmony_ci } 23762306a36Sopenharmony_ci 23862306a36Sopenharmony_ci return 0; 23962306a36Sopenharmony_ci 24062306a36Sopenharmony_cierr_resources: 24162306a36Sopenharmony_ci fw_iso_resources_free(&isight->resources); 24262306a36Sopenharmony_cierror: 24362306a36Sopenharmony_ci return err; 24462306a36Sopenharmony_ci} 24562306a36Sopenharmony_ci 24662306a36Sopenharmony_cistatic int isight_open(struct snd_pcm_substream *substream) 24762306a36Sopenharmony_ci{ 24862306a36Sopenharmony_ci static const struct snd_pcm_hardware hardware = { 24962306a36Sopenharmony_ci .info = SNDRV_PCM_INFO_MMAP | 25062306a36Sopenharmony_ci SNDRV_PCM_INFO_MMAP_VALID | 25162306a36Sopenharmony_ci SNDRV_PCM_INFO_BATCH | 25262306a36Sopenharmony_ci SNDRV_PCM_INFO_INTERLEAVED | 25362306a36Sopenharmony_ci SNDRV_PCM_INFO_BLOCK_TRANSFER, 25462306a36Sopenharmony_ci .formats = SNDRV_PCM_FMTBIT_S16_BE, 25562306a36Sopenharmony_ci .rates = SNDRV_PCM_RATE_48000, 25662306a36Sopenharmony_ci .rate_min = 48000, 25762306a36Sopenharmony_ci .rate_max = 48000, 25862306a36Sopenharmony_ci .channels_min = 2, 25962306a36Sopenharmony_ci .channels_max = 2, 26062306a36Sopenharmony_ci .buffer_bytes_max = 4 * 1024 * 1024, 26162306a36Sopenharmony_ci .period_bytes_min = MAX_FRAMES_PER_PACKET * 4, 26262306a36Sopenharmony_ci .period_bytes_max = 1024 * 1024, 26362306a36Sopenharmony_ci .periods_min = 2, 26462306a36Sopenharmony_ci .periods_max = UINT_MAX, 26562306a36Sopenharmony_ci }; 26662306a36Sopenharmony_ci struct isight *isight = substream->private_data; 26762306a36Sopenharmony_ci 26862306a36Sopenharmony_ci substream->runtime->hw = hardware; 26962306a36Sopenharmony_ci 27062306a36Sopenharmony_ci return iso_packets_buffer_init(&isight->buffer, isight->unit, 27162306a36Sopenharmony_ci QUEUE_LENGTH, 27262306a36Sopenharmony_ci sizeof(struct audio_payload), 27362306a36Sopenharmony_ci DMA_FROM_DEVICE); 27462306a36Sopenharmony_ci} 27562306a36Sopenharmony_ci 27662306a36Sopenharmony_cistatic int isight_close(struct snd_pcm_substream *substream) 27762306a36Sopenharmony_ci{ 27862306a36Sopenharmony_ci struct isight *isight = substream->private_data; 27962306a36Sopenharmony_ci 28062306a36Sopenharmony_ci iso_packets_buffer_destroy(&isight->buffer, isight->unit); 28162306a36Sopenharmony_ci 28262306a36Sopenharmony_ci return 0; 28362306a36Sopenharmony_ci} 28462306a36Sopenharmony_ci 28562306a36Sopenharmony_cistatic int isight_hw_params(struct snd_pcm_substream *substream, 28662306a36Sopenharmony_ci struct snd_pcm_hw_params *hw_params) 28762306a36Sopenharmony_ci{ 28862306a36Sopenharmony_ci struct isight *isight = substream->private_data; 28962306a36Sopenharmony_ci 29062306a36Sopenharmony_ci WRITE_ONCE(isight->pcm_active, true); 29162306a36Sopenharmony_ci 29262306a36Sopenharmony_ci return 0; 29362306a36Sopenharmony_ci} 29462306a36Sopenharmony_ci 29562306a36Sopenharmony_cistatic int reg_read(struct isight *isight, int offset, __be32 *value) 29662306a36Sopenharmony_ci{ 29762306a36Sopenharmony_ci return snd_fw_transaction(isight->unit, TCODE_READ_QUADLET_REQUEST, 29862306a36Sopenharmony_ci isight->audio_base + offset, value, 4, 0); 29962306a36Sopenharmony_ci} 30062306a36Sopenharmony_ci 30162306a36Sopenharmony_cistatic int reg_write(struct isight *isight, int offset, __be32 value) 30262306a36Sopenharmony_ci{ 30362306a36Sopenharmony_ci return snd_fw_transaction(isight->unit, TCODE_WRITE_QUADLET_REQUEST, 30462306a36Sopenharmony_ci isight->audio_base + offset, &value, 4, 0); 30562306a36Sopenharmony_ci} 30662306a36Sopenharmony_ci 30762306a36Sopenharmony_cistatic void isight_stop_streaming(struct isight *isight) 30862306a36Sopenharmony_ci{ 30962306a36Sopenharmony_ci __be32 value; 31062306a36Sopenharmony_ci 31162306a36Sopenharmony_ci if (!isight->context) 31262306a36Sopenharmony_ci return; 31362306a36Sopenharmony_ci 31462306a36Sopenharmony_ci fw_iso_context_stop(isight->context); 31562306a36Sopenharmony_ci fw_iso_context_destroy(isight->context); 31662306a36Sopenharmony_ci isight->context = NULL; 31762306a36Sopenharmony_ci fw_iso_resources_free(&isight->resources); 31862306a36Sopenharmony_ci value = 0; 31962306a36Sopenharmony_ci snd_fw_transaction(isight->unit, TCODE_WRITE_QUADLET_REQUEST, 32062306a36Sopenharmony_ci isight->audio_base + REG_AUDIO_ENABLE, 32162306a36Sopenharmony_ci &value, 4, FW_QUIET); 32262306a36Sopenharmony_ci} 32362306a36Sopenharmony_ci 32462306a36Sopenharmony_cistatic int isight_hw_free(struct snd_pcm_substream *substream) 32562306a36Sopenharmony_ci{ 32662306a36Sopenharmony_ci struct isight *isight = substream->private_data; 32762306a36Sopenharmony_ci 32862306a36Sopenharmony_ci WRITE_ONCE(isight->pcm_active, false); 32962306a36Sopenharmony_ci 33062306a36Sopenharmony_ci mutex_lock(&isight->mutex); 33162306a36Sopenharmony_ci isight_stop_streaming(isight); 33262306a36Sopenharmony_ci mutex_unlock(&isight->mutex); 33362306a36Sopenharmony_ci 33462306a36Sopenharmony_ci return 0; 33562306a36Sopenharmony_ci} 33662306a36Sopenharmony_ci 33762306a36Sopenharmony_cistatic int isight_start_streaming(struct isight *isight) 33862306a36Sopenharmony_ci{ 33962306a36Sopenharmony_ci unsigned int i; 34062306a36Sopenharmony_ci int err; 34162306a36Sopenharmony_ci 34262306a36Sopenharmony_ci if (isight->context) { 34362306a36Sopenharmony_ci if (isight->packet_index < 0) 34462306a36Sopenharmony_ci isight_stop_streaming(isight); 34562306a36Sopenharmony_ci else 34662306a36Sopenharmony_ci return 0; 34762306a36Sopenharmony_ci } 34862306a36Sopenharmony_ci 34962306a36Sopenharmony_ci err = reg_write(isight, REG_SAMPLE_RATE, cpu_to_be32(RATE_48000)); 35062306a36Sopenharmony_ci if (err < 0) 35162306a36Sopenharmony_ci goto error; 35262306a36Sopenharmony_ci 35362306a36Sopenharmony_ci err = isight_connect(isight); 35462306a36Sopenharmony_ci if (err < 0) 35562306a36Sopenharmony_ci goto error; 35662306a36Sopenharmony_ci 35762306a36Sopenharmony_ci err = reg_write(isight, REG_AUDIO_ENABLE, cpu_to_be32(AUDIO_ENABLE)); 35862306a36Sopenharmony_ci if (err < 0) 35962306a36Sopenharmony_ci goto err_resources; 36062306a36Sopenharmony_ci 36162306a36Sopenharmony_ci isight->context = fw_iso_context_create(isight->device->card, 36262306a36Sopenharmony_ci FW_ISO_CONTEXT_RECEIVE, 36362306a36Sopenharmony_ci isight->resources.channel, 36462306a36Sopenharmony_ci isight->device->max_speed, 36562306a36Sopenharmony_ci 4, isight_packet, isight); 36662306a36Sopenharmony_ci if (IS_ERR(isight->context)) { 36762306a36Sopenharmony_ci err = PTR_ERR(isight->context); 36862306a36Sopenharmony_ci isight->context = NULL; 36962306a36Sopenharmony_ci goto err_resources; 37062306a36Sopenharmony_ci } 37162306a36Sopenharmony_ci 37262306a36Sopenharmony_ci for (i = 0; i < QUEUE_LENGTH; ++i) { 37362306a36Sopenharmony_ci err = fw_iso_context_queue(isight->context, &audio_packet, 37462306a36Sopenharmony_ci &isight->buffer.iso_buffer, 37562306a36Sopenharmony_ci isight->buffer.packets[i].offset); 37662306a36Sopenharmony_ci if (err < 0) 37762306a36Sopenharmony_ci goto err_context; 37862306a36Sopenharmony_ci } 37962306a36Sopenharmony_ci 38062306a36Sopenharmony_ci isight->first_packet = true; 38162306a36Sopenharmony_ci isight->packet_index = 0; 38262306a36Sopenharmony_ci 38362306a36Sopenharmony_ci err = fw_iso_context_start(isight->context, -1, 0, 38462306a36Sopenharmony_ci FW_ISO_CONTEXT_MATCH_ALL_TAGS/*?*/); 38562306a36Sopenharmony_ci if (err < 0) 38662306a36Sopenharmony_ci goto err_context; 38762306a36Sopenharmony_ci 38862306a36Sopenharmony_ci return 0; 38962306a36Sopenharmony_ci 39062306a36Sopenharmony_cierr_context: 39162306a36Sopenharmony_ci fw_iso_context_destroy(isight->context); 39262306a36Sopenharmony_ci isight->context = NULL; 39362306a36Sopenharmony_cierr_resources: 39462306a36Sopenharmony_ci fw_iso_resources_free(&isight->resources); 39562306a36Sopenharmony_ci reg_write(isight, REG_AUDIO_ENABLE, 0); 39662306a36Sopenharmony_cierror: 39762306a36Sopenharmony_ci return err; 39862306a36Sopenharmony_ci} 39962306a36Sopenharmony_ci 40062306a36Sopenharmony_cistatic int isight_prepare(struct snd_pcm_substream *substream) 40162306a36Sopenharmony_ci{ 40262306a36Sopenharmony_ci struct isight *isight = substream->private_data; 40362306a36Sopenharmony_ci int err; 40462306a36Sopenharmony_ci 40562306a36Sopenharmony_ci isight->buffer_pointer = 0; 40662306a36Sopenharmony_ci isight->period_counter = 0; 40762306a36Sopenharmony_ci 40862306a36Sopenharmony_ci mutex_lock(&isight->mutex); 40962306a36Sopenharmony_ci err = isight_start_streaming(isight); 41062306a36Sopenharmony_ci mutex_unlock(&isight->mutex); 41162306a36Sopenharmony_ci 41262306a36Sopenharmony_ci return err; 41362306a36Sopenharmony_ci} 41462306a36Sopenharmony_ci 41562306a36Sopenharmony_cistatic int isight_trigger(struct snd_pcm_substream *substream, int cmd) 41662306a36Sopenharmony_ci{ 41762306a36Sopenharmony_ci struct isight *isight = substream->private_data; 41862306a36Sopenharmony_ci 41962306a36Sopenharmony_ci switch (cmd) { 42062306a36Sopenharmony_ci case SNDRV_PCM_TRIGGER_START: 42162306a36Sopenharmony_ci WRITE_ONCE(isight->pcm_running, true); 42262306a36Sopenharmony_ci break; 42362306a36Sopenharmony_ci case SNDRV_PCM_TRIGGER_STOP: 42462306a36Sopenharmony_ci WRITE_ONCE(isight->pcm_running, false); 42562306a36Sopenharmony_ci break; 42662306a36Sopenharmony_ci default: 42762306a36Sopenharmony_ci return -EINVAL; 42862306a36Sopenharmony_ci } 42962306a36Sopenharmony_ci return 0; 43062306a36Sopenharmony_ci} 43162306a36Sopenharmony_ci 43262306a36Sopenharmony_cistatic snd_pcm_uframes_t isight_pointer(struct snd_pcm_substream *substream) 43362306a36Sopenharmony_ci{ 43462306a36Sopenharmony_ci struct isight *isight = substream->private_data; 43562306a36Sopenharmony_ci 43662306a36Sopenharmony_ci return READ_ONCE(isight->buffer_pointer); 43762306a36Sopenharmony_ci} 43862306a36Sopenharmony_ci 43962306a36Sopenharmony_cistatic int isight_create_pcm(struct isight *isight) 44062306a36Sopenharmony_ci{ 44162306a36Sopenharmony_ci static const struct snd_pcm_ops ops = { 44262306a36Sopenharmony_ci .open = isight_open, 44362306a36Sopenharmony_ci .close = isight_close, 44462306a36Sopenharmony_ci .hw_params = isight_hw_params, 44562306a36Sopenharmony_ci .hw_free = isight_hw_free, 44662306a36Sopenharmony_ci .prepare = isight_prepare, 44762306a36Sopenharmony_ci .trigger = isight_trigger, 44862306a36Sopenharmony_ci .pointer = isight_pointer, 44962306a36Sopenharmony_ci }; 45062306a36Sopenharmony_ci struct snd_pcm *pcm; 45162306a36Sopenharmony_ci int err; 45262306a36Sopenharmony_ci 45362306a36Sopenharmony_ci err = snd_pcm_new(isight->card, "iSight", 0, 0, 1, &pcm); 45462306a36Sopenharmony_ci if (err < 0) 45562306a36Sopenharmony_ci return err; 45662306a36Sopenharmony_ci pcm->private_data = isight; 45762306a36Sopenharmony_ci strcpy(pcm->name, "iSight"); 45862306a36Sopenharmony_ci isight->pcm = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream; 45962306a36Sopenharmony_ci isight->pcm->ops = &ops; 46062306a36Sopenharmony_ci snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_VMALLOC, NULL, 0, 0); 46162306a36Sopenharmony_ci 46262306a36Sopenharmony_ci return 0; 46362306a36Sopenharmony_ci} 46462306a36Sopenharmony_ci 46562306a36Sopenharmony_cistatic int isight_gain_info(struct snd_kcontrol *ctl, 46662306a36Sopenharmony_ci struct snd_ctl_elem_info *info) 46762306a36Sopenharmony_ci{ 46862306a36Sopenharmony_ci struct isight *isight = ctl->private_data; 46962306a36Sopenharmony_ci 47062306a36Sopenharmony_ci info->type = SNDRV_CTL_ELEM_TYPE_INTEGER; 47162306a36Sopenharmony_ci info->count = 1; 47262306a36Sopenharmony_ci info->value.integer.min = isight->gain_min; 47362306a36Sopenharmony_ci info->value.integer.max = isight->gain_max; 47462306a36Sopenharmony_ci 47562306a36Sopenharmony_ci return 0; 47662306a36Sopenharmony_ci} 47762306a36Sopenharmony_ci 47862306a36Sopenharmony_cistatic int isight_gain_get(struct snd_kcontrol *ctl, 47962306a36Sopenharmony_ci struct snd_ctl_elem_value *value) 48062306a36Sopenharmony_ci{ 48162306a36Sopenharmony_ci struct isight *isight = ctl->private_data; 48262306a36Sopenharmony_ci __be32 gain; 48362306a36Sopenharmony_ci int err; 48462306a36Sopenharmony_ci 48562306a36Sopenharmony_ci err = reg_read(isight, REG_GAIN, &gain); 48662306a36Sopenharmony_ci if (err < 0) 48762306a36Sopenharmony_ci return err; 48862306a36Sopenharmony_ci 48962306a36Sopenharmony_ci value->value.integer.value[0] = (s32)be32_to_cpu(gain); 49062306a36Sopenharmony_ci 49162306a36Sopenharmony_ci return 0; 49262306a36Sopenharmony_ci} 49362306a36Sopenharmony_ci 49462306a36Sopenharmony_cistatic int isight_gain_put(struct snd_kcontrol *ctl, 49562306a36Sopenharmony_ci struct snd_ctl_elem_value *value) 49662306a36Sopenharmony_ci{ 49762306a36Sopenharmony_ci struct isight *isight = ctl->private_data; 49862306a36Sopenharmony_ci 49962306a36Sopenharmony_ci if (value->value.integer.value[0] < isight->gain_min || 50062306a36Sopenharmony_ci value->value.integer.value[0] > isight->gain_max) 50162306a36Sopenharmony_ci return -EINVAL; 50262306a36Sopenharmony_ci 50362306a36Sopenharmony_ci return reg_write(isight, REG_GAIN, 50462306a36Sopenharmony_ci cpu_to_be32(value->value.integer.value[0])); 50562306a36Sopenharmony_ci} 50662306a36Sopenharmony_ci 50762306a36Sopenharmony_cistatic int isight_mute_get(struct snd_kcontrol *ctl, 50862306a36Sopenharmony_ci struct snd_ctl_elem_value *value) 50962306a36Sopenharmony_ci{ 51062306a36Sopenharmony_ci struct isight *isight = ctl->private_data; 51162306a36Sopenharmony_ci __be32 mute; 51262306a36Sopenharmony_ci int err; 51362306a36Sopenharmony_ci 51462306a36Sopenharmony_ci err = reg_read(isight, REG_MUTE, &mute); 51562306a36Sopenharmony_ci if (err < 0) 51662306a36Sopenharmony_ci return err; 51762306a36Sopenharmony_ci 51862306a36Sopenharmony_ci value->value.integer.value[0] = !mute; 51962306a36Sopenharmony_ci 52062306a36Sopenharmony_ci return 0; 52162306a36Sopenharmony_ci} 52262306a36Sopenharmony_ci 52362306a36Sopenharmony_cistatic int isight_mute_put(struct snd_kcontrol *ctl, 52462306a36Sopenharmony_ci struct snd_ctl_elem_value *value) 52562306a36Sopenharmony_ci{ 52662306a36Sopenharmony_ci struct isight *isight = ctl->private_data; 52762306a36Sopenharmony_ci 52862306a36Sopenharmony_ci return reg_write(isight, REG_MUTE, 52962306a36Sopenharmony_ci (__force __be32)!value->value.integer.value[0]); 53062306a36Sopenharmony_ci} 53162306a36Sopenharmony_ci 53262306a36Sopenharmony_cistatic int isight_create_mixer(struct isight *isight) 53362306a36Sopenharmony_ci{ 53462306a36Sopenharmony_ci static const struct snd_kcontrol_new gain_control = { 53562306a36Sopenharmony_ci .iface = SNDRV_CTL_ELEM_IFACE_MIXER, 53662306a36Sopenharmony_ci .name = "Mic Capture Volume", 53762306a36Sopenharmony_ci .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | 53862306a36Sopenharmony_ci SNDRV_CTL_ELEM_ACCESS_TLV_READ, 53962306a36Sopenharmony_ci .info = isight_gain_info, 54062306a36Sopenharmony_ci .get = isight_gain_get, 54162306a36Sopenharmony_ci .put = isight_gain_put, 54262306a36Sopenharmony_ci }; 54362306a36Sopenharmony_ci static const struct snd_kcontrol_new mute_control = { 54462306a36Sopenharmony_ci .iface = SNDRV_CTL_ELEM_IFACE_MIXER, 54562306a36Sopenharmony_ci .name = "Mic Capture Switch", 54662306a36Sopenharmony_ci .info = snd_ctl_boolean_mono_info, 54762306a36Sopenharmony_ci .get = isight_mute_get, 54862306a36Sopenharmony_ci .put = isight_mute_put, 54962306a36Sopenharmony_ci }; 55062306a36Sopenharmony_ci __be32 value; 55162306a36Sopenharmony_ci struct snd_kcontrol *ctl; 55262306a36Sopenharmony_ci int err; 55362306a36Sopenharmony_ci 55462306a36Sopenharmony_ci err = reg_read(isight, REG_GAIN_RAW_START, &value); 55562306a36Sopenharmony_ci if (err < 0) 55662306a36Sopenharmony_ci return err; 55762306a36Sopenharmony_ci isight->gain_min = be32_to_cpu(value); 55862306a36Sopenharmony_ci 55962306a36Sopenharmony_ci err = reg_read(isight, REG_GAIN_RAW_END, &value); 56062306a36Sopenharmony_ci if (err < 0) 56162306a36Sopenharmony_ci return err; 56262306a36Sopenharmony_ci isight->gain_max = be32_to_cpu(value); 56362306a36Sopenharmony_ci 56462306a36Sopenharmony_ci isight->gain_tlv[SNDRV_CTL_TLVO_TYPE] = SNDRV_CTL_TLVT_DB_MINMAX; 56562306a36Sopenharmony_ci isight->gain_tlv[SNDRV_CTL_TLVO_LEN] = 2 * sizeof(unsigned int); 56662306a36Sopenharmony_ci 56762306a36Sopenharmony_ci err = reg_read(isight, REG_GAIN_DB_START, &value); 56862306a36Sopenharmony_ci if (err < 0) 56962306a36Sopenharmony_ci return err; 57062306a36Sopenharmony_ci isight->gain_tlv[SNDRV_CTL_TLVO_DB_MINMAX_MIN] = 57162306a36Sopenharmony_ci (s32)be32_to_cpu(value) * 100; 57262306a36Sopenharmony_ci 57362306a36Sopenharmony_ci err = reg_read(isight, REG_GAIN_DB_END, &value); 57462306a36Sopenharmony_ci if (err < 0) 57562306a36Sopenharmony_ci return err; 57662306a36Sopenharmony_ci isight->gain_tlv[SNDRV_CTL_TLVO_DB_MINMAX_MAX] = 57762306a36Sopenharmony_ci (s32)be32_to_cpu(value) * 100; 57862306a36Sopenharmony_ci 57962306a36Sopenharmony_ci ctl = snd_ctl_new1(&gain_control, isight); 58062306a36Sopenharmony_ci if (ctl) 58162306a36Sopenharmony_ci ctl->tlv.p = isight->gain_tlv; 58262306a36Sopenharmony_ci err = snd_ctl_add(isight->card, ctl); 58362306a36Sopenharmony_ci if (err < 0) 58462306a36Sopenharmony_ci return err; 58562306a36Sopenharmony_ci 58662306a36Sopenharmony_ci err = snd_ctl_add(isight->card, snd_ctl_new1(&mute_control, isight)); 58762306a36Sopenharmony_ci if (err < 0) 58862306a36Sopenharmony_ci return err; 58962306a36Sopenharmony_ci 59062306a36Sopenharmony_ci return 0; 59162306a36Sopenharmony_ci} 59262306a36Sopenharmony_ci 59362306a36Sopenharmony_cistatic void isight_card_free(struct snd_card *card) 59462306a36Sopenharmony_ci{ 59562306a36Sopenharmony_ci struct isight *isight = card->private_data; 59662306a36Sopenharmony_ci 59762306a36Sopenharmony_ci fw_iso_resources_destroy(&isight->resources); 59862306a36Sopenharmony_ci} 59962306a36Sopenharmony_ci 60062306a36Sopenharmony_cistatic u64 get_unit_base(struct fw_unit *unit) 60162306a36Sopenharmony_ci{ 60262306a36Sopenharmony_ci struct fw_csr_iterator i; 60362306a36Sopenharmony_ci int key, value; 60462306a36Sopenharmony_ci 60562306a36Sopenharmony_ci fw_csr_iterator_init(&i, unit->directory); 60662306a36Sopenharmony_ci while (fw_csr_iterator_next(&i, &key, &value)) 60762306a36Sopenharmony_ci if (key == CSR_OFFSET) 60862306a36Sopenharmony_ci return CSR_REGISTER_BASE + value * 4; 60962306a36Sopenharmony_ci return 0; 61062306a36Sopenharmony_ci} 61162306a36Sopenharmony_ci 61262306a36Sopenharmony_cistatic int isight_probe(struct fw_unit *unit, 61362306a36Sopenharmony_ci const struct ieee1394_device_id *id) 61462306a36Sopenharmony_ci{ 61562306a36Sopenharmony_ci struct fw_device *fw_dev = fw_parent_device(unit); 61662306a36Sopenharmony_ci struct snd_card *card; 61762306a36Sopenharmony_ci struct isight *isight; 61862306a36Sopenharmony_ci int err; 61962306a36Sopenharmony_ci 62062306a36Sopenharmony_ci err = snd_card_new(&unit->device, -1, NULL, THIS_MODULE, 62162306a36Sopenharmony_ci sizeof(*isight), &card); 62262306a36Sopenharmony_ci if (err < 0) 62362306a36Sopenharmony_ci return err; 62462306a36Sopenharmony_ci 62562306a36Sopenharmony_ci isight = card->private_data; 62662306a36Sopenharmony_ci isight->card = card; 62762306a36Sopenharmony_ci mutex_init(&isight->mutex); 62862306a36Sopenharmony_ci isight->unit = fw_unit_get(unit); 62962306a36Sopenharmony_ci isight->device = fw_dev; 63062306a36Sopenharmony_ci isight->audio_base = get_unit_base(unit); 63162306a36Sopenharmony_ci if (!isight->audio_base) { 63262306a36Sopenharmony_ci dev_err(&unit->device, "audio unit base not found\n"); 63362306a36Sopenharmony_ci err = -ENXIO; 63462306a36Sopenharmony_ci goto error; 63562306a36Sopenharmony_ci } 63662306a36Sopenharmony_ci fw_iso_resources_init(&isight->resources, unit); 63762306a36Sopenharmony_ci 63862306a36Sopenharmony_ci card->private_free = isight_card_free; 63962306a36Sopenharmony_ci 64062306a36Sopenharmony_ci strcpy(card->driver, "iSight"); 64162306a36Sopenharmony_ci strcpy(card->shortname, "Apple iSight"); 64262306a36Sopenharmony_ci snprintf(card->longname, sizeof(card->longname), 64362306a36Sopenharmony_ci "Apple iSight (GUID %08x%08x) at %s, S%d", 64462306a36Sopenharmony_ci fw_dev->config_rom[3], fw_dev->config_rom[4], 64562306a36Sopenharmony_ci dev_name(&unit->device), 100 << fw_dev->max_speed); 64662306a36Sopenharmony_ci strcpy(card->mixername, "iSight"); 64762306a36Sopenharmony_ci 64862306a36Sopenharmony_ci err = isight_create_pcm(isight); 64962306a36Sopenharmony_ci if (err < 0) 65062306a36Sopenharmony_ci goto error; 65162306a36Sopenharmony_ci 65262306a36Sopenharmony_ci err = isight_create_mixer(isight); 65362306a36Sopenharmony_ci if (err < 0) 65462306a36Sopenharmony_ci goto error; 65562306a36Sopenharmony_ci 65662306a36Sopenharmony_ci err = snd_card_register(card); 65762306a36Sopenharmony_ci if (err < 0) 65862306a36Sopenharmony_ci goto error; 65962306a36Sopenharmony_ci 66062306a36Sopenharmony_ci dev_set_drvdata(&unit->device, isight); 66162306a36Sopenharmony_ci 66262306a36Sopenharmony_ci return 0; 66362306a36Sopenharmony_cierror: 66462306a36Sopenharmony_ci snd_card_free(card); 66562306a36Sopenharmony_ci 66662306a36Sopenharmony_ci mutex_destroy(&isight->mutex); 66762306a36Sopenharmony_ci fw_unit_put(isight->unit); 66862306a36Sopenharmony_ci 66962306a36Sopenharmony_ci return err; 67062306a36Sopenharmony_ci} 67162306a36Sopenharmony_ci 67262306a36Sopenharmony_cistatic void isight_bus_reset(struct fw_unit *unit) 67362306a36Sopenharmony_ci{ 67462306a36Sopenharmony_ci struct isight *isight = dev_get_drvdata(&unit->device); 67562306a36Sopenharmony_ci 67662306a36Sopenharmony_ci if (fw_iso_resources_update(&isight->resources) < 0) { 67762306a36Sopenharmony_ci isight_pcm_abort(isight); 67862306a36Sopenharmony_ci 67962306a36Sopenharmony_ci mutex_lock(&isight->mutex); 68062306a36Sopenharmony_ci isight_stop_streaming(isight); 68162306a36Sopenharmony_ci mutex_unlock(&isight->mutex); 68262306a36Sopenharmony_ci } 68362306a36Sopenharmony_ci} 68462306a36Sopenharmony_ci 68562306a36Sopenharmony_cistatic void isight_remove(struct fw_unit *unit) 68662306a36Sopenharmony_ci{ 68762306a36Sopenharmony_ci struct isight *isight = dev_get_drvdata(&unit->device); 68862306a36Sopenharmony_ci 68962306a36Sopenharmony_ci isight_pcm_abort(isight); 69062306a36Sopenharmony_ci 69162306a36Sopenharmony_ci snd_card_disconnect(isight->card); 69262306a36Sopenharmony_ci 69362306a36Sopenharmony_ci mutex_lock(&isight->mutex); 69462306a36Sopenharmony_ci isight_stop_streaming(isight); 69562306a36Sopenharmony_ci mutex_unlock(&isight->mutex); 69662306a36Sopenharmony_ci 69762306a36Sopenharmony_ci // Block till all of ALSA character devices are released. 69862306a36Sopenharmony_ci snd_card_free(isight->card); 69962306a36Sopenharmony_ci 70062306a36Sopenharmony_ci mutex_destroy(&isight->mutex); 70162306a36Sopenharmony_ci fw_unit_put(isight->unit); 70262306a36Sopenharmony_ci} 70362306a36Sopenharmony_ci 70462306a36Sopenharmony_cistatic const struct ieee1394_device_id isight_id_table[] = { 70562306a36Sopenharmony_ci { 70662306a36Sopenharmony_ci .match_flags = IEEE1394_MATCH_SPECIFIER_ID | 70762306a36Sopenharmony_ci IEEE1394_MATCH_VERSION, 70862306a36Sopenharmony_ci .specifier_id = OUI_APPLE, 70962306a36Sopenharmony_ci .version = SW_ISIGHT_AUDIO, 71062306a36Sopenharmony_ci }, 71162306a36Sopenharmony_ci { } 71262306a36Sopenharmony_ci}; 71362306a36Sopenharmony_ciMODULE_DEVICE_TABLE(ieee1394, isight_id_table); 71462306a36Sopenharmony_ci 71562306a36Sopenharmony_cistatic struct fw_driver isight_driver = { 71662306a36Sopenharmony_ci .driver = { 71762306a36Sopenharmony_ci .owner = THIS_MODULE, 71862306a36Sopenharmony_ci .name = KBUILD_MODNAME, 71962306a36Sopenharmony_ci .bus = &fw_bus_type, 72062306a36Sopenharmony_ci }, 72162306a36Sopenharmony_ci .probe = isight_probe, 72262306a36Sopenharmony_ci .update = isight_bus_reset, 72362306a36Sopenharmony_ci .remove = isight_remove, 72462306a36Sopenharmony_ci .id_table = isight_id_table, 72562306a36Sopenharmony_ci}; 72662306a36Sopenharmony_ci 72762306a36Sopenharmony_cistatic int __init alsa_isight_init(void) 72862306a36Sopenharmony_ci{ 72962306a36Sopenharmony_ci return driver_register(&isight_driver.driver); 73062306a36Sopenharmony_ci} 73162306a36Sopenharmony_ci 73262306a36Sopenharmony_cistatic void __exit alsa_isight_exit(void) 73362306a36Sopenharmony_ci{ 73462306a36Sopenharmony_ci driver_unregister(&isight_driver.driver); 73562306a36Sopenharmony_ci} 73662306a36Sopenharmony_ci 73762306a36Sopenharmony_cimodule_init(alsa_isight_init); 73862306a36Sopenharmony_cimodule_exit(alsa_isight_exit); 739