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