From 3a691b28a0ca3cf4d9010c6158318159e0275d2c Mon Sep 17 00:00:00 2001 From: Clemens Ladisch Date: Wed, 11 May 2011 10:44:51 +0200 Subject: ALSA: add Apple iSight microphone driver This adds an experimental driver for the front and rear microphones of the Apple iSight web camera. Signed-off-by: Clemens Ladisch Signed-off-by: Takashi Iwai --- sound/firewire/Kconfig | 12 + sound/firewire/Makefile | 2 + sound/firewire/isight.c | 744 ++++++++++++++++++++++++++++++++++++++++ sound/firewire/iso-resources.c | 5 + sound/firewire/packets-buffer.c | 2 + 5 files changed, 765 insertions(+) create mode 100644 sound/firewire/isight.c (limited to 'sound/firewire') diff --git a/sound/firewire/Kconfig b/sound/firewire/Kconfig index e486f48660fb..312f90d7ec69 100644 --- a/sound/firewire/Kconfig +++ b/sound/firewire/Kconfig @@ -22,4 +22,16 @@ config SND_FIREWIRE_SPEAKERS To compile this driver as a module, choose M here: the module will be called snd-firewire-speakers. +config SND_ISIGHT + tristate "Apple iSight microphone (EXPERIMENTAL)" + depends on EXPERIMENTAL + select SND_PCM + select SND_FIREWIRE_LIB + help + Say Y here to include support for the front and rear microphones + of the Apple iSight web camera. + + To compile this driver as a module, choose M here: the module + will be called snd-isight. + endif # SND_FIREWIRE diff --git a/sound/firewire/Makefile b/sound/firewire/Makefile index e5b1634d9ad4..d71ed8935f76 100644 --- a/sound/firewire/Makefile +++ b/sound/firewire/Makefile @@ -1,6 +1,8 @@ snd-firewire-lib-objs := lib.o iso-resources.o packets-buffer.o \ fcp.o cmp.o amdtp.o snd-firewire-speakers-objs := speakers.o +snd-isight-objs := isight.o obj-$(CONFIG_SND_FIREWIRE_LIB) += snd-firewire-lib.o obj-$(CONFIG_SND_FIREWIRE_SPEAKERS) += snd-firewire-speakers.o +obj-$(CONFIG_SND_ISIGHT) += snd-isight.o diff --git a/sound/firewire/isight.c b/sound/firewire/isight.c new file mode 100644 index 000000000000..a6f19f57a1c3 --- /dev/null +++ b/sound/firewire/isight.c @@ -0,0 +1,744 @@ +/* + * Apple iSight audio driver + * + * Copyright (c) Clemens Ladisch + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "lib.h" +#include "iso-resources.h" +#include "packets-buffer.h" + +#define OUI_APPLE 0x000a27 +#define MODEL_APPLE_ISIGHT 0x000008 +#define SW_ISIGHT_AUDIO 0x000010 + +#define REG_AUDIO_ENABLE 0x000 +#define AUDIO_ENABLE 0x80000000 +#define REG_DEF_AUDIO_GAIN 0x204 +#define REG_GAIN_RAW_START 0x210 +#define REG_GAIN_RAW_END 0x214 +#define REG_GAIN_DB_START 0x218 +#define REG_GAIN_DB_END 0x21c +#define REG_SAMPLE_RATE_INQUIRY 0x280 +#define REG_ISO_TX_CONFIG 0x300 +#define SPEED_SHIFT 16 +#define REG_SAMPLE_RATE 0x400 +#define RATE_48000 0x80000000 +#define REG_GAIN 0x500 +#define REG_MUTE 0x504 + +#define MAX_FRAMES_PER_PACKET 475 + +#define QUEUE_LENGTH 20 + +struct isight { + struct snd_card *card; + struct fw_unit *unit; + struct fw_device *device; + u64 audio_base; + struct fw_address_handler iris_handler; + struct snd_pcm_substream *pcm; + struct mutex mutex; + struct iso_packets_buffer buffer; + struct fw_iso_resources resources; + struct fw_iso_context *context; + bool pcm_running; + bool first_packet; + int packet_index; + u32 total_samples; + unsigned int buffer_pointer; + unsigned int period_counter; + s32 gain_min, gain_max; + unsigned int gain_tlv[4]; +}; + +struct audio_payload { + __be32 sample_count; + __be32 signature; + __be32 sample_total; + __be32 reserved; + __be16 samples[2 * MAX_FRAMES_PER_PACKET]; +}; + +MODULE_DESCRIPTION("iSight audio driver"); +MODULE_AUTHOR("Clemens Ladisch "); +MODULE_LICENSE("GPL v2"); + +static struct fw_iso_packet audio_packet = { + .payload_length = sizeof(struct audio_payload), + .interrupt = 1, +}; + +static void isight_update_pointers(struct isight *isight, unsigned int count) +{ + struct snd_pcm_runtime *runtime = isight->pcm->runtime; + unsigned int ptr; + + smp_wmb(); /* update buffer data before buffer pointer */ + + ptr = isight->buffer_pointer; + ptr += count; + if (ptr >= runtime->buffer_size) + ptr -= runtime->buffer_size; + ACCESS_ONCE(isight->buffer_pointer) = ptr; + + isight->period_counter += count; + if (isight->period_counter >= runtime->period_size) { + isight->period_counter -= runtime->period_size; + snd_pcm_period_elapsed(isight->pcm); + } +} + +static void isight_samples(struct isight *isight, + const __be16 *samples, unsigned int count) +{ + struct snd_pcm_runtime *runtime; + unsigned int count1; + + if (!ACCESS_ONCE(isight->pcm_running)) + return; + + runtime = isight->pcm->runtime; + if (isight->buffer_pointer + count <= runtime->buffer_size) { + memcpy(runtime->dma_area + isight->buffer_pointer * 4, + samples, count * 4); + } else { + count1 = runtime->buffer_size - isight->buffer_pointer; + memcpy(runtime->dma_area + isight->buffer_pointer * 4, + samples, count1 * 4); + samples += count1 * 2; + memcpy(runtime->dma_area, samples, (count - count1) * 4); + } + + isight_update_pointers(isight, count); +} + +static void isight_pcm_abort(struct isight *isight) +{ + unsigned long flags; + + snd_pcm_stream_lock_irqsave(isight->pcm, flags); + if (snd_pcm_running(isight->pcm)) + snd_pcm_stop(isight->pcm, SNDRV_PCM_STATE_XRUN); + snd_pcm_stream_unlock_irqrestore(isight->pcm, flags); +} + +static void isight_dropped_samples(struct isight *isight, unsigned int total) +{ + struct snd_pcm_runtime *runtime; + u32 dropped; + unsigned int count1; + + if (!ACCESS_ONCE(isight->pcm_running)) + return; + + runtime = isight->pcm->runtime; + dropped = total - isight->total_samples; + if (dropped < runtime->buffer_size) { + if (isight->buffer_pointer + dropped <= runtime->buffer_size) { + memset(runtime->dma_area + isight->buffer_pointer * 4, + 0, dropped * 4); + } else { + count1 = runtime->buffer_size - isight->buffer_pointer; + memset(runtime->dma_area + isight->buffer_pointer * 4, + 0, count1 * 4); + memset(runtime->dma_area, 0, (dropped - count1) * 4); + } + isight_update_pointers(isight, dropped); + } else { + isight_pcm_abort(isight); + } +} + +static void isight_packet(struct fw_iso_context *context, u32 cycle, + size_t header_length, void *header, void *data) +{ + struct isight *isight = data; + const struct audio_payload *payload; + unsigned int index, length, count, total; + int err; + + if (isight->packet_index < 0) + return; + index = isight->packet_index; + payload = isight->buffer.packets[index].buffer; + length = be32_to_cpup(header) >> 16; + + if (likely(length >= 16 && + payload->signature == cpu_to_be32(0x73676874/*"sght"*/))) { + count = be32_to_cpu(payload->sample_count); + if (likely(count <= (length - 16) / 4)) { + total = be32_to_cpu(payload->sample_total); + if (unlikely(total != isight->total_samples)) { + if (!isight->first_packet) + isight_dropped_samples(isight, total); + isight->first_packet = false; + isight->total_samples = total; + } + + isight_samples(isight, payload->samples, count); + isight->total_samples += count; + } + } + + if (++index >= QUEUE_LENGTH) + index = 0; + + err = fw_iso_context_queue(isight->context, &audio_packet, + &isight->buffer.iso_buffer, + isight->buffer.packets[index].offset); + if (err < 0) { + dev_err(&isight->unit->device, "queueing error: %d\n", err); + isight_pcm_abort(isight); + isight->packet_index = -1; + return; + } + + isight->packet_index = index; +} + +static int isight_connect(struct isight *isight) +{ + int ch, err, rcode, errors = 0; + __be32 value; + +retry_after_bus_reset: + ch = fw_iso_resources_allocate(&isight->resources, + sizeof(struct audio_payload), + isight->device->max_speed); + if (ch < 0) { + err = ch; + goto error; + } + + value = cpu_to_be32(ch | (isight->device->max_speed << SPEED_SHIFT)); + for (;;) { + rcode = fw_run_transaction( + isight->device->card, + TCODE_WRITE_QUADLET_REQUEST, + isight->device->node_id, + isight->resources.generation, + isight->device->max_speed, + isight->audio_base + REG_ISO_TX_CONFIG, + &value, 4); + if (rcode == RCODE_COMPLETE) { + return 0; + } else if (rcode == RCODE_GENERATION) { + fw_iso_resources_free(&isight->resources); + goto retry_after_bus_reset; + } else if (rcode_is_permanent_error(rcode) || ++errors >= 3) { + err = -EIO; + goto err_resources; + } + msleep(5); + } + +err_resources: + fw_iso_resources_free(&isight->resources); +error: + return err; +} + +static int isight_open(struct snd_pcm_substream *substream) +{ + static const struct snd_pcm_hardware hardware = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_BATCH | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER, + .formats = SNDRV_PCM_FMTBIT_S16_BE, + .rates = SNDRV_PCM_RATE_48000, + .rate_min = 48000, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = 4 * 1024 * 1024, + .period_bytes_min = MAX_FRAMES_PER_PACKET * 4, + .period_bytes_max = 1024 * 1024, + .periods_min = 2, + .periods_max = UINT_MAX, + }; + struct isight *isight = substream->private_data; + + substream->runtime->hw = hardware; + + return iso_packets_buffer_init(&isight->buffer, isight->unit, + QUEUE_LENGTH, + sizeof(struct audio_payload), + DMA_FROM_DEVICE); +} + +static int isight_close(struct snd_pcm_substream *substream) +{ + struct isight *isight = substream->private_data; + + iso_packets_buffer_destroy(&isight->buffer, isight->unit); + + return 0; +} + +static int isight_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + return snd_pcm_lib_alloc_vmalloc_buffer(substream, + params_buffer_bytes(hw_params)); +} + +static void isight_stop_streaming(struct isight *isight) +{ + __be32 value; + + if (!isight->context) + return; + + fw_iso_context_stop(isight->context); + fw_iso_context_destroy(isight->context); + isight->context = NULL; + + value = 0; + snd_fw_transaction(isight->unit, TCODE_WRITE_QUADLET_REQUEST, + isight->audio_base + REG_AUDIO_ENABLE, + &value, 4); + + fw_iso_resources_free(&isight->resources); +} + +static int isight_hw_free(struct snd_pcm_substream *substream) +{ + struct isight *isight = substream->private_data; + + mutex_lock(&isight->mutex); + isight_stop_streaming(isight); + mutex_unlock(&isight->mutex); + + return snd_pcm_lib_free_vmalloc_buffer(substream); +} + +static int isight_start_streaming(struct isight *isight) +{ + __be32 sample_rate; + unsigned int i; + int err; + + if (isight->context) { + if (isight->packet_index < 0) + isight_stop_streaming(isight); + else + return 0; + } + + sample_rate = cpu_to_be32(RATE_48000); + err = snd_fw_transaction(isight->unit, TCODE_WRITE_QUADLET_REQUEST, + isight->audio_base + REG_SAMPLE_RATE, + &sample_rate, 4); + if (err < 0) + return err; + + err = isight_connect(isight); + if (err < 0) + goto error; + + isight->context = fw_iso_context_create(isight->device->card, + FW_ISO_CONTEXT_RECEIVE, + isight->resources.channel, + isight->device->max_speed, + 4, isight_packet, isight); + if (IS_ERR(isight->context)) { + err = PTR_ERR(isight->context); + isight->context = NULL; + goto err_resources; + } + + for (i = 0; i < QUEUE_LENGTH; ++i) { + err = fw_iso_context_queue(isight->context, &audio_packet, + &isight->buffer.iso_buffer, + isight->buffer.packets[i].offset); + if (err < 0) + goto err_context; + } + + isight->first_packet = true; + isight->packet_index = 0; + + err = fw_iso_context_start(isight->context, -1, 0, + FW_ISO_CONTEXT_MATCH_ALL_TAGS/*?*/); + if (err < 0) + goto err_context; + + return 0; + +err_context: + fw_iso_context_destroy(isight->context); + isight->context = NULL; +err_resources: + fw_iso_resources_free(&isight->resources); +error: + return err; +} + +static int isight_prepare(struct snd_pcm_substream *substream) +{ + struct isight *isight = substream->private_data; + int err; + + isight->buffer_pointer = 0; + isight->period_counter = 0; + + mutex_lock(&isight->mutex); + err = isight_start_streaming(isight); + mutex_unlock(&isight->mutex); + + return err; +} + +static int isight_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct isight *isight = substream->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + ACCESS_ONCE(isight->pcm_running) = true; + break; + case SNDRV_PCM_TRIGGER_STOP: + ACCESS_ONCE(isight->pcm_running) = false; + break; + default: + return -EINVAL; + } + return 0; +} + +static snd_pcm_uframes_t isight_pointer(struct snd_pcm_substream *substream) +{ + struct isight *isight = substream->private_data; + + return ACCESS_ONCE(isight->buffer_pointer); +} + +static int isight_create_pcm(struct isight *isight) +{ + static struct snd_pcm_ops ops = { + .open = isight_open, + .close = isight_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = isight_hw_params, + .hw_free = isight_hw_free, + .prepare = isight_prepare, + .trigger = isight_trigger, + .pointer = isight_pointer, + .page = snd_pcm_lib_get_vmalloc_page, + .mmap = snd_pcm_lib_mmap_vmalloc, + }; + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(isight->card, "iSight", 0, 0, 1, &pcm); + if (err < 0) + return err; + pcm->private_data = isight; + strcpy(pcm->name, "iSight"); + isight->pcm = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream; + isight->pcm->ops = &ops; + + return 0; +} + +static int isight_gain_info(struct snd_kcontrol *ctl, + struct snd_ctl_elem_info *info) +{ + struct isight *isight = ctl->private_data; + + info->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + info->count = 1; + info->value.integer.min = isight->gain_min; + info->value.integer.max = isight->gain_max; + + return 0; +} + +static int isight_gain_get(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + struct isight *isight = ctl->private_data; + __be32 gain; + int err; + + err = snd_fw_transaction(isight->unit, TCODE_READ_QUADLET_REQUEST, + isight->audio_base + REG_GAIN, &gain, 4); + if (err < 0) + return err; + + value->value.integer.value[0] = (s32)be32_to_cpu(gain); + + return 0; +} + +static int isight_gain_put(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + struct isight *isight = ctl->private_data; + __be32 gain; + + if (value->value.integer.value[0] < isight->gain_min || + value->value.integer.value[0] > isight->gain_max) + return -EINVAL; + + gain = cpu_to_be32(value->value.integer.value[0]); + return snd_fw_transaction(isight->unit, TCODE_WRITE_QUADLET_REQUEST, + isight->audio_base + REG_GAIN, &gain, 4); +} + +static int isight_mute_get(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + struct isight *isight = ctl->private_data; + __be32 mute; + int err; + + err = snd_fw_transaction(isight->unit, TCODE_READ_QUADLET_REQUEST, + isight->audio_base + REG_MUTE, &mute, 4); + if (err < 0) + return err; + + value->value.integer.value[0] = !mute; + + return 0; +} + +static int isight_mute_put(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + struct isight *isight = ctl->private_data; + __be32 mute; + + mute = (__force __be32)!value->value.integer.value[0]; + return snd_fw_transaction(isight->unit, TCODE_WRITE_QUADLET_REQUEST, + isight->audio_base + REG_MUTE, &mute, 4); +} + +static int isight_create_mixer(struct isight *isight) +{ + static const struct snd_kcontrol_new gain_control = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Mic Capture Volume", + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | + SNDRV_CTL_ELEM_ACCESS_TLV_READ, + .info = isight_gain_info, + .get = isight_gain_get, + .put = isight_gain_put, + }; + static const struct snd_kcontrol_new mute_control = { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Mic Capture Switch", + .info = snd_ctl_boolean_mono_info, + .get = isight_mute_get, + .put = isight_mute_put, + }; + __be32 value; + struct snd_kcontrol *ctl; + int err; + + err = snd_fw_transaction(isight->unit, TCODE_READ_QUADLET_REQUEST, + isight->audio_base + REG_GAIN_RAW_START, + &value, 4); + if (err < 0) + return err; + isight->gain_min = be32_to_cpu(value); + + err = snd_fw_transaction(isight->unit, TCODE_READ_QUADLET_REQUEST, + isight->audio_base + REG_GAIN_RAW_END, + &value, 4); + if (err < 0) + return err; + isight->gain_max = be32_to_cpu(value); + + isight->gain_tlv[0] = SNDRV_CTL_TLVT_DB_MINMAX; + isight->gain_tlv[1] = 2 * sizeof(unsigned int); + err = snd_fw_transaction(isight->unit, TCODE_READ_QUADLET_REQUEST, + isight->audio_base + REG_GAIN_DB_START, + &value, 4); + if (err < 0) + return err; + isight->gain_tlv[2] = (s32)be32_to_cpu(value) * 100; + err = snd_fw_transaction(isight->unit, TCODE_READ_QUADLET_REQUEST, + isight->audio_base + REG_GAIN_DB_END, + &value, 4); + if (err < 0) + return err; + isight->gain_tlv[3] = (s32)be32_to_cpu(value) * 100; + + ctl = snd_ctl_new1(&gain_control, isight); + if (ctl) + ctl->tlv.p = isight->gain_tlv; + err = snd_ctl_add(isight->card, ctl); + if (err < 0) + return err; + + err = snd_ctl_add(isight->card, snd_ctl_new1(&mute_control, isight)); + if (err < 0) + return err; + + return 0; +} + +static void isight_card_free(struct snd_card *card) +{ + struct isight *isight = card->private_data; + + fw_iso_resources_destroy(&isight->resources); + fw_unit_put(isight->unit); + fw_device_put(isight->device); + mutex_destroy(&isight->mutex); +} + +static u64 get_unit_base(struct fw_unit *unit) +{ + struct fw_csr_iterator i; + int key, value; + + fw_csr_iterator_init(&i, unit->directory); + while (fw_csr_iterator_next(&i, &key, &value)) + if (key == CSR_OFFSET) + return CSR_REGISTER_BASE + value * 4; + return 0; +} + +static int isight_probe(struct device *unit_dev) +{ + struct fw_unit *unit = fw_unit(unit_dev); + struct fw_device *fw_dev = fw_parent_device(unit); + struct snd_card *card; + struct isight *isight; + int err; + + err = snd_card_create(-1, NULL, THIS_MODULE, sizeof(*isight), &card); + if (err < 0) + return err; + snd_card_set_dev(card, unit_dev); + + isight = card->private_data; + isight->card = card; + mutex_init(&isight->mutex); + isight->unit = fw_unit_get(unit); + isight->device = fw_device_get(fw_dev); + isight->audio_base = get_unit_base(unit); + if (!isight->audio_base) { + dev_err(&unit->device, "audio unit base not found\n"); + err = -ENXIO; + goto err_unit; + } + fw_iso_resources_init(&isight->resources, unit); + + card->private_free = isight_card_free; + + strcpy(card->driver, "iSight"); + strcpy(card->shortname, "Apple iSight"); + snprintf(card->longname, sizeof(card->longname), + "Apple iSight (GUID %08x%08x) at %s, S%d", + fw_dev->config_rom[3], fw_dev->config_rom[4], + dev_name(&unit->device), 100 << fw_dev->max_speed); + strcpy(card->mixername, "iSight"); + + err = isight_create_pcm(isight); + if (err < 0) + goto error; + + err = isight_create_mixer(isight); + if (err < 0) + goto error; + + err = snd_card_register(card); + if (err < 0) + goto error; + + dev_set_drvdata(unit_dev, isight); + + return 0; + +err_unit: + fw_unit_put(isight->unit); + fw_device_put(isight->device); + mutex_destroy(&isight->mutex); +error: + snd_card_free(card); + return err; +} + +static int isight_remove(struct device *dev) +{ + struct isight *isight = dev_get_drvdata(dev); + + snd_card_disconnect(isight->card); + + mutex_lock(&isight->mutex); + isight_pcm_abort(isight); + isight_stop_streaming(isight); + mutex_unlock(&isight->mutex); + + snd_card_free_when_closed(isight->card); + + return 0; +} + +static void isight_bus_reset(struct fw_unit *unit) +{ + struct isight *isight = dev_get_drvdata(&unit->device); + + mutex_lock(&isight->mutex); + if (fw_iso_resources_update(&isight->resources) < 0) { + isight_pcm_abort(isight); + isight_stop_streaming(isight); + } + mutex_unlock(&isight->mutex); +} + +static const struct ieee1394_device_id isight_id_table[] = { + { + .match_flags = IEEE1394_MATCH_SPECIFIER_ID | + IEEE1394_MATCH_VERSION, + .specifier_id = OUI_APPLE, + .version = SW_ISIGHT_AUDIO, + }, + { } +}; +MODULE_DEVICE_TABLE(ieee1394, isight_id_table); + +static struct fw_driver isight_driver = { + .driver = { + .owner = THIS_MODULE, + .name = KBUILD_MODNAME, + .bus = &fw_bus_type, + .probe = isight_probe, + .remove = isight_remove, + }, + .update = isight_bus_reset, + .id_table = isight_id_table, +}; + +static int __init alsa_isight_init(void) +{ + return driver_register(&isight_driver.driver); +} + +static void __exit alsa_isight_exit(void) +{ + driver_unregister(&isight_driver.driver); +} + +module_init(alsa_isight_init); +module_exit(alsa_isight_exit); diff --git a/sound/firewire/iso-resources.c b/sound/firewire/iso-resources.c index 775dbd5f3445..9d4a6714f9ec 100644 --- a/sound/firewire/iso-resources.c +++ b/sound/firewire/iso-resources.c @@ -36,6 +36,7 @@ int fw_iso_resources_init(struct fw_iso_resources *r, struct fw_unit *unit) return 0; } +EXPORT_SYMBOL(fw_iso_resources_init); /** * fw_iso_resources_destroy - destroy a resource manager @@ -48,6 +49,7 @@ void fw_iso_resources_destroy(struct fw_iso_resources *r) mutex_destroy(&r->mutex); fw_unit_put(r->unit); } +EXPORT_SYMBOL(fw_iso_resources_destroy); static unsigned int packet_bandwidth(unsigned int max_payload_bytes, int speed) { @@ -152,6 +154,7 @@ retry_after_bus_reset: return channel; } +EXPORT_SYMBOL(fw_iso_resources_allocate); /** * fw_iso_resources_update - update resource allocations after a bus reset @@ -203,6 +206,7 @@ int fw_iso_resources_update(struct fw_iso_resources *r) return channel; } +EXPORT_SYMBOL(fw_iso_resources_update); /** * fw_iso_resources_free - frees allocated resources @@ -230,3 +234,4 @@ void fw_iso_resources_free(struct fw_iso_resources *r) mutex_unlock(&r->mutex); } +EXPORT_SYMBOL(fw_iso_resources_free); diff --git a/sound/firewire/packets-buffer.c b/sound/firewire/packets-buffer.c index 1e20e60ba6a6..3c61ca2e6152 100644 --- a/sound/firewire/packets-buffer.c +++ b/sound/firewire/packets-buffer.c @@ -60,6 +60,7 @@ err_packets: error: return err; } +EXPORT_SYMBOL(iso_packets_buffer_init); /** * iso_packets_buffer_destroy - frees packet buffer resources @@ -72,3 +73,4 @@ void iso_packets_buffer_destroy(struct iso_packets_buffer *b, fw_iso_buffer_destroy(&b->iso_buffer, fw_parent_device(unit)->card); kfree(b->packets); } +EXPORT_SYMBOL(iso_packets_buffer_destroy); -- cgit v1.2.3 From 03c29680d49662859d14d64f8673550fa3fb2ed1 Mon Sep 17 00:00:00 2001 From: Clemens Ladisch Date: Wed, 11 May 2011 10:47:30 +0200 Subject: ALSA: isight: fix isight_pcm_abort() crashes Fix crashes in isight_pcm_abort() that happen when the driver tries to access isight->pcm->runtime which does not exist when the device is not open. Introduce a new field pcm_active to track this state. Signed-off-by: Clemens Ladisch Reported-by: Stefan Richter Signed-off-by: Takashi Iwai --- sound/firewire/isight.c | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) (limited to 'sound/firewire') diff --git a/sound/firewire/isight.c b/sound/firewire/isight.c index a6f19f57a1c3..0230605c917e 100644 --- a/sound/firewire/isight.c +++ b/sound/firewire/isight.c @@ -56,6 +56,7 @@ struct isight { struct iso_packets_buffer buffer; struct fw_iso_resources resources; struct fw_iso_context *context; + bool pcm_active; bool pcm_running; bool first_packet; int packet_index; @@ -131,10 +132,12 @@ static void isight_pcm_abort(struct isight *isight) { unsigned long flags; - snd_pcm_stream_lock_irqsave(isight->pcm, flags); - if (snd_pcm_running(isight->pcm)) - snd_pcm_stop(isight->pcm, SNDRV_PCM_STATE_XRUN); - snd_pcm_stream_unlock_irqrestore(isight->pcm, flags); + if (ACCESS_ONCE(isight->pcm_active)) { + snd_pcm_stream_lock_irqsave(isight->pcm, flags); + if (snd_pcm_running(isight->pcm)) + snd_pcm_stop(isight->pcm, SNDRV_PCM_STATE_XRUN); + snd_pcm_stream_unlock_irqrestore(isight->pcm, flags); + } } static void isight_dropped_samples(struct isight *isight, unsigned int total) @@ -295,8 +298,17 @@ static int isight_close(struct snd_pcm_substream *substream) static int isight_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *hw_params) { - return snd_pcm_lib_alloc_vmalloc_buffer(substream, - params_buffer_bytes(hw_params)); + struct isight *isight = substream->private_data; + int err; + + err = snd_pcm_lib_alloc_vmalloc_buffer(substream, + params_buffer_bytes(hw_params)); + if (err < 0) + return err; + + ACCESS_ONCE(isight->pcm_active) = true; + + return 0; } static void isight_stop_streaming(struct isight *isight) @@ -322,6 +334,8 @@ static int isight_hw_free(struct snd_pcm_substream *substream) { struct isight *isight = substream->private_data; + ACCESS_ONCE(isight->pcm_active) = false; + mutex_lock(&isight->mutex); isight_stop_streaming(isight); mutex_unlock(&isight->mutex); -- cgit v1.2.3 From 898732d1f1c7181fd3e94e7d7a784edb48d09d95 Mon Sep 17 00:00:00 2001 From: Clemens Ladisch Date: Wed, 11 May 2011 10:48:24 +0200 Subject: ALSA: isight: fix packet requeueing After handling a received packet, we want to resubmit the same packet, so do not increase the packet index too early. Signed-off-by: Clemens Ladisch Signed-off-by: Takashi Iwai --- sound/firewire/isight.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'sound/firewire') diff --git a/sound/firewire/isight.c b/sound/firewire/isight.c index 0230605c917e..4e334919a70f 100644 --- a/sound/firewire/isight.c +++ b/sound/firewire/isight.c @@ -198,9 +198,6 @@ static void isight_packet(struct fw_iso_context *context, u32 cycle, } } - if (++index >= QUEUE_LENGTH) - index = 0; - err = fw_iso_context_queue(isight->context, &audio_packet, &isight->buffer.iso_buffer, isight->buffer.packets[index].offset); @@ -211,6 +208,8 @@ static void isight_packet(struct fw_iso_context *context, u32 cycle, return; } + if (++index >= QUEUE_LENGTH) + index = 0; isight->packet_index = index; } -- cgit v1.2.3 From f2934cd499ba2c7f605787508b4cfcfa3a45b0a4 Mon Sep 17 00:00:00 2001 From: Clemens Ladisch Date: Wed, 11 May 2011 10:49:02 +0200 Subject: ALSA: isight: fix divide error when queueing packets Set the .header_size field when queueing packets to avoid a division by zero. Signed-off-by: Clemens Ladisch Signed-off-by: Takashi Iwai --- sound/firewire/isight.c | 1 + 1 file changed, 1 insertion(+) (limited to 'sound/firewire') diff --git a/sound/firewire/isight.c b/sound/firewire/isight.c index 4e334919a70f..10a9b9b0b2c2 100644 --- a/sound/firewire/isight.c +++ b/sound/firewire/isight.c @@ -82,6 +82,7 @@ MODULE_LICENSE("GPL v2"); static struct fw_iso_packet audio_packet = { .payload_length = sizeof(struct audio_payload), .interrupt = 1, + .header_length = 4, }; static void isight_update_pointers(struct isight *isight, unsigned int count) -- cgit v1.2.3 From 8839eedafd2e91e5b124730825e9b39b1ff493dd Mon Sep 17 00:00:00 2001 From: Stefan Richter Date: Wed, 11 May 2011 10:49:58 +0200 Subject: ALSA: isight: add AudioEnable register write which is needed to get the iSight to talk. Signed-off-by: Stefan Richter Signed-off-by: Clemens Ladisch Signed-off-by: Takashi Iwai --- sound/firewire/isight.c | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) (limited to 'sound/firewire') diff --git a/sound/firewire/isight.c b/sound/firewire/isight.c index 10a9b9b0b2c2..1a8da2614db6 100644 --- a/sound/firewire/isight.c +++ b/sound/firewire/isight.c @@ -345,7 +345,7 @@ static int isight_hw_free(struct snd_pcm_substream *substream) static int isight_start_streaming(struct isight *isight) { - __be32 sample_rate; + __be32 value; unsigned int i; int err; @@ -356,10 +356,10 @@ static int isight_start_streaming(struct isight *isight) return 0; } - sample_rate = cpu_to_be32(RATE_48000); + value = cpu_to_be32(RATE_48000); err = snd_fw_transaction(isight->unit, TCODE_WRITE_QUADLET_REQUEST, isight->audio_base + REG_SAMPLE_RATE, - &sample_rate, 4); + &value, 4); if (err < 0) return err; @@ -367,6 +367,13 @@ static int isight_start_streaming(struct isight *isight) if (err < 0) goto error; + value = cpu_to_be32(AUDIO_ENABLE); + err = snd_fw_transaction(isight->unit, TCODE_WRITE_QUADLET_REQUEST, + isight->audio_base + REG_AUDIO_ENABLE, + &value, 4); + if (err < 0) + goto err_resources; + isight->context = fw_iso_context_create(isight->device->card, FW_ISO_CONTEXT_RECEIVE, isight->resources.channel, @@ -400,6 +407,10 @@ err_context: fw_iso_context_destroy(isight->context); isight->context = NULL; err_resources: + value = 0; + snd_fw_transaction(isight->unit, TCODE_WRITE_QUADLET_REQUEST, + isight->audio_base + REG_AUDIO_ENABLE, + &value, 4); fw_iso_resources_free(&isight->resources); error: return err; -- cgit v1.2.3 From ac34dad26e6786257ef54d8df4f883825bea02eb Mon Sep 17 00:00:00 2001 From: Stefan Richter Date: Wed, 11 May 2011 10:52:21 +0200 Subject: ALSA: isight: wrap up register accesses Signed-off-by: Stefan Richter [cl: removed superfluous variable] Signed-off-by: Clemens Ladisch Signed-off-by: Takashi Iwai --- sound/firewire/isight.c | 76 +++++++++++++++++++------------------------------ 1 file changed, 30 insertions(+), 46 deletions(-) (limited to 'sound/firewire') diff --git a/sound/firewire/isight.c b/sound/firewire/isight.c index 1a8da2614db6..4d2edcfdbbca 100644 --- a/sound/firewire/isight.c +++ b/sound/firewire/isight.c @@ -5,6 +5,7 @@ * Licensed under the terms of the GNU General Public License, version 2. */ +#include #include #include #include @@ -311,23 +312,28 @@ static int isight_hw_params(struct snd_pcm_substream *substream, return 0; } -static void isight_stop_streaming(struct isight *isight) +static int reg_read(struct isight *isight, int offset, __be32 *value) { - __be32 value; + return snd_fw_transaction(isight->unit, TCODE_READ_QUADLET_REQUEST, + isight->audio_base + offset, value, 4); +} + +static int reg_write(struct isight *isight, int offset, __be32 value) +{ + return snd_fw_transaction(isight->unit, TCODE_WRITE_QUADLET_REQUEST, + isight->audio_base + offset, &value, 4); +} +static void isight_stop_streaming(struct isight *isight) +{ if (!isight->context) return; fw_iso_context_stop(isight->context); fw_iso_context_destroy(isight->context); isight->context = NULL; - - value = 0; - snd_fw_transaction(isight->unit, TCODE_WRITE_QUADLET_REQUEST, - isight->audio_base + REG_AUDIO_ENABLE, - &value, 4); - fw_iso_resources_free(&isight->resources); + reg_write(isight, REG_AUDIO_ENABLE, 0); } static int isight_hw_free(struct snd_pcm_substream *substream) @@ -345,7 +351,6 @@ static int isight_hw_free(struct snd_pcm_substream *substream) static int isight_start_streaming(struct isight *isight) { - __be32 value; unsigned int i; int err; @@ -356,21 +361,15 @@ static int isight_start_streaming(struct isight *isight) return 0; } - value = cpu_to_be32(RATE_48000); - err = snd_fw_transaction(isight->unit, TCODE_WRITE_QUADLET_REQUEST, - isight->audio_base + REG_SAMPLE_RATE, - &value, 4); + err = reg_write(isight, REG_SAMPLE_RATE, cpu_to_be32(RATE_48000)); if (err < 0) - return err; + goto error; err = isight_connect(isight); if (err < 0) goto error; - value = cpu_to_be32(AUDIO_ENABLE); - err = snd_fw_transaction(isight->unit, TCODE_WRITE_QUADLET_REQUEST, - isight->audio_base + REG_AUDIO_ENABLE, - &value, 4); + err = reg_write(isight, REG_AUDIO_ENABLE, cpu_to_be32(AUDIO_ENABLE)); if (err < 0) goto err_resources; @@ -407,11 +406,8 @@ err_context: fw_iso_context_destroy(isight->context); isight->context = NULL; err_resources: - value = 0; - snd_fw_transaction(isight->unit, TCODE_WRITE_QUADLET_REQUEST, - isight->audio_base + REG_AUDIO_ENABLE, - &value, 4); fw_iso_resources_free(&isight->resources); + reg_write(isight, REG_AUDIO_ENABLE, 0); error: return err; } @@ -503,8 +499,7 @@ static int isight_gain_get(struct snd_kcontrol *ctl, __be32 gain; int err; - err = snd_fw_transaction(isight->unit, TCODE_READ_QUADLET_REQUEST, - isight->audio_base + REG_GAIN, &gain, 4); + err = reg_read(isight, REG_GAIN, &gain); if (err < 0) return err; @@ -517,15 +512,13 @@ static int isight_gain_put(struct snd_kcontrol *ctl, struct snd_ctl_elem_value *value) { struct isight *isight = ctl->private_data; - __be32 gain; if (value->value.integer.value[0] < isight->gain_min || value->value.integer.value[0] > isight->gain_max) return -EINVAL; - gain = cpu_to_be32(value->value.integer.value[0]); - return snd_fw_transaction(isight->unit, TCODE_WRITE_QUADLET_REQUEST, - isight->audio_base + REG_GAIN, &gain, 4); + return reg_write(isight, REG_GAIN, + cpu_to_be32(value->value.integer.value[0])); } static int isight_mute_get(struct snd_kcontrol *ctl, @@ -535,8 +528,7 @@ static int isight_mute_get(struct snd_kcontrol *ctl, __be32 mute; int err; - err = snd_fw_transaction(isight->unit, TCODE_READ_QUADLET_REQUEST, - isight->audio_base + REG_MUTE, &mute, 4); + err = reg_read(isight, REG_MUTE, &mute); if (err < 0) return err; @@ -549,11 +541,9 @@ static int isight_mute_put(struct snd_kcontrol *ctl, struct snd_ctl_elem_value *value) { struct isight *isight = ctl->private_data; - __be32 mute; - mute = (__force __be32)!value->value.integer.value[0]; - return snd_fw_transaction(isight->unit, TCODE_WRITE_QUADLET_REQUEST, - isight->audio_base + REG_MUTE, &mute, 4); + return reg_write(isight, REG_MUTE, + (__force __be32)!value->value.integer.value[0]); } static int isight_create_mixer(struct isight *isight) @@ -578,31 +568,25 @@ static int isight_create_mixer(struct isight *isight) struct snd_kcontrol *ctl; int err; - err = snd_fw_transaction(isight->unit, TCODE_READ_QUADLET_REQUEST, - isight->audio_base + REG_GAIN_RAW_START, - &value, 4); + err = reg_read(isight, REG_GAIN_RAW_START, &value); if (err < 0) return err; isight->gain_min = be32_to_cpu(value); - err = snd_fw_transaction(isight->unit, TCODE_READ_QUADLET_REQUEST, - isight->audio_base + REG_GAIN_RAW_END, - &value, 4); + err = reg_read(isight, REG_GAIN_RAW_END, &value); if (err < 0) return err; isight->gain_max = be32_to_cpu(value); isight->gain_tlv[0] = SNDRV_CTL_TLVT_DB_MINMAX; isight->gain_tlv[1] = 2 * sizeof(unsigned int); - err = snd_fw_transaction(isight->unit, TCODE_READ_QUADLET_REQUEST, - isight->audio_base + REG_GAIN_DB_START, - &value, 4); + + err = reg_read(isight, REG_GAIN_DB_START, &value); if (err < 0) return err; isight->gain_tlv[2] = (s32)be32_to_cpu(value) * 100; - err = snd_fw_transaction(isight->unit, TCODE_READ_QUADLET_REQUEST, - isight->audio_base + REG_GAIN_DB_END, - &value, 4); + + err = reg_read(isight, REG_GAIN_DB_END, &value); if (err < 0) return err; isight->gain_tlv[3] = (s32)be32_to_cpu(value) * 100; -- cgit v1.2.3 From aee70400184b6a8d39243b02c244aed61259a46b Mon Sep 17 00:00:00 2001 From: Clemens Ladisch Date: Wed, 11 May 2011 10:53:12 +0200 Subject: ALSA: isight: fix hang when unplugging a running device When aborting a PCM stream, the xrun is signaled only if the stream is running. When disconnecting a PCM stream, calling snd_card_disconnect() too early would change the stream into a non-running state and thus prevent the xrun from being noticed by user space. To prevent this, move the snd_card_disconnect() call after the xrun. Signed-off-by: Clemens Ladisch Signed-off-by: Takashi Iwai --- sound/firewire/isight.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'sound/firewire') diff --git a/sound/firewire/isight.c b/sound/firewire/isight.c index 4d2edcfdbbca..96267437d373 100644 --- a/sound/firewire/isight.c +++ b/sound/firewire/isight.c @@ -692,10 +692,9 @@ static int isight_remove(struct device *dev) { struct isight *isight = dev_get_drvdata(dev); - snd_card_disconnect(isight->card); - mutex_lock(&isight->mutex); isight_pcm_abort(isight); + snd_card_disconnect(isight->card); isight_stop_streaming(isight); mutex_unlock(&isight->mutex); -- cgit v1.2.3 From 3cabffd72c303c3b5bbbbe88c95b49043898d1f3 Mon Sep 17 00:00:00 2001 From: Clemens Ladisch Date: Wed, 11 May 2011 10:54:41 +0200 Subject: ALSA: isight: remove experimental status Experiments have shown this driver to work now. Signed-off-by: Clemens Ladisch Tested-by: Stefan Richter Signed-off-by: Takashi Iwai --- sound/firewire/Kconfig | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'sound/firewire') diff --git a/sound/firewire/Kconfig b/sound/firewire/Kconfig index 312f90d7ec69..26071489970b 100644 --- a/sound/firewire/Kconfig +++ b/sound/firewire/Kconfig @@ -23,8 +23,7 @@ config SND_FIREWIRE_SPEAKERS will be called snd-firewire-speakers. config SND_ISIGHT - tristate "Apple iSight microphone (EXPERIMENTAL)" - depends on EXPERIMENTAL + tristate "Apple iSight microphone" select SND_PCM select SND_FIREWIRE_LIB help -- cgit v1.2.3 From f3f7c1837f6bcae3601fc535b339426868bf1549 Mon Sep 17 00:00:00 2001 From: Clemens Ladisch Date: Wed, 11 May 2011 11:07:09 +0200 Subject: ALSA: isight: fix locking Lockdep complains about conflicts between isight->mutex, ALSA's register_mutex, mm->mmap_sem, and pcm->open_mutex. This can be fixed by moving the calls to isight_pcm_abort(), snd_card_disconnect(), and fw_iso_resources_update() out of isight->mutex. These functions are designed to be called asynchronously; the mutex needs to protect only the device streaming state modified by isight_start/stop_streaming(). Signed-off-by: Clemens Ladisch Reported-by: Stefan Richter Signed-off-by: Takashi Iwai --- sound/firewire/isight.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'sound/firewire') diff --git a/sound/firewire/isight.c b/sound/firewire/isight.c index 96267437d373..86ee16ca365e 100644 --- a/sound/firewire/isight.c +++ b/sound/firewire/isight.c @@ -692,9 +692,11 @@ static int isight_remove(struct device *dev) { struct isight *isight = dev_get_drvdata(dev); - mutex_lock(&isight->mutex); isight_pcm_abort(isight); + snd_card_disconnect(isight->card); + + mutex_lock(&isight->mutex); isight_stop_streaming(isight); mutex_unlock(&isight->mutex); @@ -707,12 +709,13 @@ static void isight_bus_reset(struct fw_unit *unit) { struct isight *isight = dev_get_drvdata(&unit->device); - mutex_lock(&isight->mutex); if (fw_iso_resources_update(&isight->resources) < 0) { isight_pcm_abort(isight); + + mutex_lock(&isight->mutex); isight_stop_streaming(isight); + mutex_unlock(&isight->mutex); } - mutex_unlock(&isight->mutex); } static const struct ieee1394_device_id isight_id_table[] = { -- cgit v1.2.3