cregit-Linux how code gets into the kernel

Release 4.8 sound/firewire/isight.c

Directory: sound/firewire
/*
 * Apple iSight audio driver
 *
 * Copyright (c) Clemens Ladisch <clemens@ladisch.de>
 * Licensed under the terms of the GNU General Public License, version 2.
 */

#include <asm/byteorder.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/firewire.h>
#include <linux/firewire-constants.h>
#include <linux/module.h>
#include <linux/mod_devicetable.h>
#include <linux/mutex.h>
#include <linux/string.h>
#include <sound/control.h>
#include <sound/core.h>
#include <sound/initval.h>
#include <sound/pcm.h>
#include <sound/tlv.h>
#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 snd_pcm_substream *pcm;
	
struct mutex mutex;
	
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;
	
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 <clemens@ladisch.de>");
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) { 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); } }

Contributors

PersonTokensPropCommitsCommitProp
clemens ladischclemens ladisch100100.00%1100.00%
Total100100.00%1100.00%


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); }

Contributors

PersonTokensPropCommitsCommitProp
clemens ladischclemens ladisch143100.00%1100.00%
Total143100.00%1100.00%


static void isight_pcm_abort(struct isight *isight) { if (ACCESS_ONCE(isight->pcm_active)) snd_pcm_stop_xrun(isight->pcm); }

Contributors

PersonTokensPropCommitsCommitProp
clemens ladischclemens ladisch2696.30%266.67%
takashi iwaitakashi iwai13.70%133.33%
Total27100.00%3100.00%


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); } }

Contributors

PersonTokensPropCommitsCommitProp
clemens ladischclemens ladisch161100.00%1100.00%
Total161100.00%1100.00%


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; } } 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; } fw_iso_context_queue_flush(isight->context); if (++index >= QUEUE_LENGTH) index = 0; isight->packet_index = index; }

Contributors

PersonTokensPropCommitsCommitProp
clemens ladischclemens ladisch289100.00%3100.00%
Total289100.00%3100.00%


static int isight_connect(struct isight *isight) { int ch, err; __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)); err = snd_fw_transaction(isight->unit, TCODE_WRITE_QUADLET_REQUEST, isight->audio_base + REG_ISO_TX_CONFIG, &value, 4, FW_FIXED_GENERATION | isight->resources.generation); if (err == -EAGAIN) { fw_iso_resources_free(&isight->resources); goto retry_after_bus_reset; } else if (err < 0) { goto err_resources; } return 0; err_resources: fw_iso_resources_free(&isight->resources); error: return err; }

Contributors

PersonTokensPropCommitsCommitProp
clemens ladischclemens ladisch155100.00%2100.00%
Total155100.00%2100.00%


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); }

Contributors

PersonTokensPropCommitsCommitProp
clemens ladischclemens ladisch135100.00%1100.00%
Total135100.00%1100.00%


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; }

Contributors

PersonTokensPropCommitsCommitProp
clemens ladischclemens ladisch35100.00%1100.00%
Total35100.00%1100.00%


static int isight_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *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; }

Contributors

PersonTokensPropCommitsCommitProp
clemens ladischclemens ladisch61100.00%2100.00%
Total61100.00%2100.00%


static int reg_read(struct isight *isight, int offset, __be32 *value) { return snd_fw_transaction(isight->unit, TCODE_READ_QUADLET_REQUEST, isight->audio_base + offset, value, 4, 0); }

Contributors

PersonTokensPropCommitsCommitProp
stefan richterstefan richter3895.00%150.00%
clemens ladischclemens ladisch25.00%150.00%
Total40100.00%2100.00%


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, 0); }

Contributors

PersonTokensPropCommitsCommitProp
stefan richterstefan richter3895.00%150.00%
clemens ladischclemens ladisch25.00%150.00%
Total40100.00%2100.00%


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; fw_iso_resources_free(&isight->resources); value = 0; snd_fw_transaction(isight->unit, TCODE_WRITE_QUADLET_REQUEST, isight->audio_base + REG_AUDIO_ENABLE, &value, 4, FW_QUIET); }

Contributors

PersonTokensPropCommitsCommitProp
clemens ladischclemens ladisch7092.11%266.67%
stefan richterstefan richter67.89%133.33%
Total76100.00%3100.00%


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); return snd_pcm_lib_free_vmalloc_buffer(substream); }

Contributors

PersonTokensPropCommitsCommitProp
clemens ladischclemens ladisch56100.00%2100.00%
Total56100.00%2100.00%


static int isight_start_streaming(struct isight *isight) { unsigned int i; int err; if (isight->context) { if (isight->packet_index < 0) isight_stop_streaming(isight); else return 0; } err = reg_write(isight, REG_SAMPLE_RATE, cpu_to_be32(RATE_48000)); if (err < 0) goto error; err = isight_connect(isight); if (err < 0) goto error; err = reg_write(isight, REG_AUDIO_ENABLE, cpu_to_be32(AUDIO_ENABLE)); if (err < 0) goto err_resources; 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); reg_write(isight, REG_AUDIO_ENABLE, 0); error: return err; }

Contributors

PersonTokensPropCommitsCommitProp
clemens ladischclemens ladisch25785.38%133.33%
stefan richterstefan richter4414.62%266.67%
Total301100.00%3100.00%


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; }

Contributors

PersonTokensPropCommitsCommitProp
clemens ladischclemens ladisch61100.00%1100.00%
Total61100.00%1100.00%


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; }

Contributors

PersonTokensPropCommitsCommitProp
clemens ladischclemens ladisch63100.00%1100.00%
Total63100.00%1100.00%


static snd_pcm_uframes_t isight_pointer(struct snd_pcm_substream *substream) { struct isight *isight = substream->private_data; return ACCESS_ONCE(isight->buffer_pointer); }

Contributors

PersonTokensPropCommitsCommitProp
clemens ladischclemens ladisch28100.00%1100.00%
Total28100.00%1100.00%


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; }

Contributors

PersonTokensPropCommitsCommitProp
clemens ladischclemens ladisch145100.00%1100.00%
Total145100.00%1100.00%


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; }

Contributors

PersonTokensPropCommitsCommitProp
clemens ladischclemens ladisch64100.00%1100.00%
Total64100.00%1100.00%


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 = reg_read(isight, REG_GAIN, &gain); if (err < 0) return err; value->value.integer.value[0] = (s32)be32_to_cpu(gain); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
clemens ladischclemens ladisch7398.65%150.00%
stefan richterstefan richter11.35%150.00%
Total74100.00%2100.00%


static int isight_gain_put(struct snd_kcontrol *ctl, struct snd_ctl_elem_value *value) { struct isight *isight = ctl->private_data; if (value->value.integer.value[0] < isight->gain_min || value->value.integer.value[0] > isight->gain_max) return -EINVAL; return reg_write(isight, REG_GAIN, cpu_to_be32(value->value.integer.value[0])); }

Contributors

PersonTokensPropCommitsCommitProp
clemens ladischclemens ladisch7691.57%150.00%
stefan richterstefan richter78.43%150.00%
Total83100.00%2100.00%


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 = reg_read(isight, REG_MUTE, &mute); if (err < 0) return err; value->value.integer.value[0] = !mute; return 0; }

Contributors

PersonTokensPropCommitsCommitProp
clemens ladischclemens ladisch6898.55%150.00%
stefan richterstefan richter11.45%150.00%
Total69100.00%2100.00%


static int isight_mute_put(struct snd_kcontrol *ctl, struct snd_ctl_elem_value *value) { struct isight *isight = ctl->private_data; return reg_write(isight, REG_MUTE, (__force __be32)!value->value.integer.value[0]); }

Contributors

PersonTokensPropCommitsCommitProp
clemens ladischclemens ladisch4285.71%150.00%
stefan richterstefan richter714.29%150.00%
Total49100.00%2100.00%


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 = reg_read(isight, REG_GAIN_RAW_START, &value); if (err < 0) return err; isight->gain_min = be32_to_cpu(value); 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 = reg_read(isight, REG_GAIN_DB_START, &value); if (err < 0) return err; isight->gain_tlv[2] = (s32)be32_to_cpu(value) * 100; err = reg_read(isight, REG_GAIN_DB_END, &value); 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; }

Contributors

PersonTokensPropCommitsCommitProp
clemens ladischclemens ladisch32498.78%150.00%
stefan richterstefan richter41.22%150.00%
Total328100.00%2100.00%


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); mutex_destroy(&isight->mutex); }

Contributors

PersonTokensPropCommitsCommitProp
clemens ladischclemens ladisch43100.00%1100.00%
Total43100.00%1100.00%


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