[alsa-devel] [PATCH 09/13] bebob: add support for M-Audio Firewire series

Takashi Sakamoto o-takashi at sakamocchi.jp
Sun Nov 24 07:34:23 CET 2013


Hi,

I found this commit includes a bug...

The 'snd_bebob_probe' do snd_bebob_stream_discover() two times. This bug 
doubles the number of channels for each AMDTP streams. Please remove 
these lines below after you apply patch 9.

@@ -203,10 +249,6 @@ snd_bebob_probe(struct fw_unit *unit,

         snd_bebob_proc_init(bebob);

-       err = snd_bebob_stream_discover(bebob);
-       if (err < 0)
-               goto error;
-
         err = snd_bebob_stream_init_duplex(bebob);
         if (err < 0)
                 goto error;


Regards

Takashi Sakamoto

(2013年11月23日 15:09), Takashi Sakamoto wrote:
> This commit allows this driver to support all of models which M-Audio produces
> with BeBoB chipset. They are:
>   - Firewire Solo
>   - Firewire AudioPhile
>   - Firewire 410
>   - Firewire 1814
>   - Ozonic
>   - NRV10
>   - ProjectMix I/O
>   - FirewireLightBridge
>
> This commit adds M-Audio specific operations, quirks and functionalities. They
> are categorized to three parts.
>
> 1. send cue to load firmware
> This is for 'Firewire Audiophile', 'Firewire410', 'Firewire1814' and
> 'ProjectMix I/O'. With firmware version 5058 or later, they wait to receive a
> cue to load firmware just after powering on. With less firmware version, they
> wait to upload firmware binary blob so this driver can't handle this versions.
> This functionality can be implemented in user land but currently in kernel land
> for my convinience.
>
> 2. switching source of clock
> This if for 'Firewire Solo', 'Firewire Audiophile', 'Firewire410',
> 'Firewire1814' and'ProjectMix I/O'. They can switch source of clock with signal
> source command but the value of parameters is a bit different.
>
> 3. handling metering information
> This is all of models, mainly for my debugging. This information includes
> information for clock synchronization.
>
> Firewire1814 and ProjectMix I/O has heavily customized BeBoB chipset. The usual
> operation to handle BeBoB chipset cannot be applied for them. For this reason,
> this commit add some model specific members in 'struct snd_bebob' and model
> specific functions.
>
> Signed-off-by: Takashi Sakamoto <o-takashi at sakamocchi.jp>
> ---
>   sound/firewire/Kconfig               |   2 +
>   sound/firewire/bebob/Makefile        |   1 +
>   sound/firewire/bebob/bebob.c         |  75 +++
>   sound/firewire/bebob/bebob.h         |  21 +
>   sound/firewire/bebob/bebob_control.c |   3 +
>   sound/firewire/bebob/bebob_maudio.c  | 910 +++++++++++++++++++++++++++++++++++
>   sound/firewire/bebob/bebob_stream.c  |  22 +-
>   7 files changed, 1031 insertions(+), 3 deletions(-)
>   create mode 100644 sound/firewire/bebob/bebob_maudio.c
>
> diff --git a/sound/firewire/Kconfig b/sound/firewire/Kconfig
> index c46578e..427def7 100644
> --- a/sound/firewire/Kconfig
> +++ b/sound/firewire/Kconfig
> @@ -80,6 +80,8 @@ config SND_BEBOB
>   	  * CME MatrixKFW
>   	  * Phonic HB24U
>   	  * BridgeCo RDAudio1/Audio5
> +	  * M-Audio Firewire410/AudioPhile/Solo/Ozonic/NRV10
> +	  * M-Audio Firewire1814/ProjectMix IO/ProfireLightBridge
>
>             To compile this driver as a module, choose M here: the module
>             will be called snd-fireworks.
> diff --git a/sound/firewire/bebob/Makefile b/sound/firewire/bebob/Makefile
> index 81e14fa..2e87d02 100644
> --- a/sound/firewire/bebob/Makefile
> +++ b/sound/firewire/bebob/Makefile
> @@ -1,4 +1,5 @@
>   snd-bebob-objs := bebob_command.o bebob_stream.o bebob_proc.o bebob_control.o \
>   		  bebob_midi.o bebob_pcm.o bebob_hwdep.o \
> +		  bebob_maudio.o \
>   		  bebob.o
>   obj-m += snd-bebob.o
> diff --git a/sound/firewire/bebob/bebob.c b/sound/firewire/bebob/bebob.c
> index 7c3f1b3..576d801 100644
> --- a/sound/firewire/bebob/bebob.c
> +++ b/sound/firewire/bebob/bebob.c
> @@ -50,6 +50,12 @@ static unsigned int devices_used;
>   #define VEN_ACOUSTIC	0x00000002
>   #define VEN_CME		0x0000000a
>   #define VEN_PHONIC	0x00001496
> +#define VEN_MAUDIO1	0x00000d6c
> +#define VEN_MAUDIO2	0x000007f5
> +
> +#define MODEL_MAUDIO_AUDIOPHILE_BOTH	0x00010060
> +#define MODEL_MAUDIO_FW1814		0x00010071
> +#define MODEL_MAUDIO_PROJECTMIX		0x00010091
>
>   static int
>   name_device(struct snd_bebob *bebob, unsigned int vendor_id)
> @@ -82,6 +88,8 @@ name_device(struct snd_bebob *bebob, unsigned int vendor_id)
>   		strcpy(vendor, "CME");
>   	else if (vendor_id == VEN_PHONIC)
>   		strcpy(vendor, "Phonic");
> +	else if ((vendor_id == VEN_MAUDIO1) || (vendor_id == VEN_MAUDIO2))
> +		strcpy(vendor, "M-Audio");
>
>   	/* get model name */
>   	err = fw_csr_string(bebob->unit->directory, CSR_MODEL,
> @@ -135,6 +143,20 @@ snd_bebob_card_free(struct snd_card *card)
>   	return;
>   }
>
> +static bool
> +check_audiophile_booted(struct fw_unit *unit)
> +{
> +	char name[24] = {0};
> +
> +	if (fw_csr_string(unit->directory, CSR_MODEL, name, sizeof(name)) < 0)
> +		return false;
> +
> +	if (strncmp(name, "FW Audiophile Bootloader", 15) == 0)
> +		return false;
> +	else
> +		return true;
> +}
> +
>   static int
>   snd_bebob_probe(struct fw_unit *unit,
>   		const struct ieee1394_device_id *entry)
> @@ -159,6 +181,18 @@ snd_bebob_probe(struct fw_unit *unit,
>
>   	spec = (const struct snd_bebob_spec *)entry->driver_data;
>
> +	/* if needed, load firmware and exit */
> +	if ((spec->load) &&
> +	    ((entry->model_id != MODEL_MAUDIO_AUDIOPHILE_BOTH) ||
> +	     (!check_audiophile_booted(unit)))) {
> +		spec->load(unit, entry);
> +		dev_info(&unit->device,
> +			 "loading firmware for 0x%08X:0x%08X\n",
> +			 entry->vendor_id, entry->model_id);
> +		err = 0;
> +		goto end;
> +	}
> +
>   	/* create card */
>   	err = snd_card_create(index[card_index], id[card_index],
>   			THIS_MODULE, sizeof(struct snd_bebob), &card);
> @@ -177,6 +211,16 @@ snd_bebob_probe(struct fw_unit *unit,
>   	spin_lock_init(&bebob->lock);
>   	init_waitqueue_head(&bebob->hwdep_wait);
>
> +	/* discover */
> +	if (entry->model_id == MODEL_MAUDIO_FW1814)
> +		err = snd_bebob_maudio_special_discover(bebob, true);
> +	else if (entry->model_id == MODEL_MAUDIO_PROJECTMIX)
> +		err = snd_bebob_maudio_special_discover(bebob, false);
> +	else
> +		err = snd_bebob_stream_discover(bebob);
> +	if (err < 0)
> +		goto error;
> +
>   	/* name device with communication */
>   	err = name_device(bebob, entry->vendor_id);
>   	if (err < 0)
> @@ -237,6 +281,11 @@ static void
>   snd_bebob_update(struct fw_unit *unit)
>   {
>   	struct snd_bebob *bebob = dev_get_drvdata(&unit->device);
> +
> +	/* this is for firmware bootloader */
> +	if (bebob == NULL)
> +		return;
> +
>   	fcp_bus_reset(bebob->unit);
>   	snd_bebob_stream_update_duplex(bebob);
>   }
> @@ -245,6 +294,11 @@ snd_bebob_update(struct fw_unit *unit)
>   static void snd_bebob_remove(struct fw_unit *unit)
>   {
>   	struct snd_bebob *bebob = dev_get_drvdata(&unit->device);
> +
> +	/* this is for firmware bootloader */
> +	if (bebob == NULL)
> +		return;
> +
>   	snd_bebob_stream_destroy_duplex(bebob);
>   	snd_card_disconnect(bebob->card);
>   	snd_card_free_when_closed(bebob->card);
> @@ -287,6 +341,27 @@ static const struct ieee1394_device_id snd_bebob_id_table[] = {
>   	SND_BEBOB_DEV_ENTRY(VEN_CME, 0x00030000, spec_normal),
>   	/* Phonic, HB24U */
>   	SND_BEBOB_DEV_ENTRY(VEN_PHONIC, 0x00000000, spec_normal),
> +	/* M-Audio, Ozonic */
> +	SND_BEBOB_DEV_ENTRY(VEN_MAUDIO1, 0x0000000a, maudio_ozonic_spec),
> +	/* M-Audio, Firewire 410.  */
> +	SND_BEBOB_DEV_ENTRY(VEN_MAUDIO2, 0x00010058, maudio_bootloader_spec),
> +	SND_BEBOB_DEV_ENTRY(VEN_MAUDIO2, 0x00010046, maudio_fw410_spec),
> +	/* M-Audio, Firewire Audiophile, both of bootloader and firmware */
> +	SND_BEBOB_DEV_ENTRY(VEN_MAUDIO1, MODEL_MAUDIO_AUDIOPHILE_BOTH,
> +						maudio_audiophile_spec),
> +	/* M-Audio, Firewire Solo */
> +	SND_BEBOB_DEV_ENTRY(VEN_MAUDIO1, 0x00010062, maudio_solo_spec),
> +	/* M-Audio NRV10 */
> +	SND_BEBOB_DEV_ENTRY(VEN_MAUDIO1, 0x00010081, maudio_nrv10_spec),
> +	/* Firewire 1814 */
> +	SND_BEBOB_DEV_ENTRY(VEN_MAUDIO1, 0x00010070, maudio_bootloader_spec),
> +	SND_BEBOB_DEV_ENTRY(VEN_MAUDIO1, MODEL_MAUDIO_FW1814,
> +						maudio_special_spec),
> +	/* M-Audio ProjectMix */
> +	SND_BEBOB_DEV_ENTRY(VEN_MAUDIO1, MODEL_MAUDIO_PROJECTMIX,
> +						maudio_special_spec),
> +	/* M-Audio, ProFireLightbridge */
> +	SND_BEBOB_DEV_ENTRY(VEN_MAUDIO1, 0x000100a1, spec_normal),
>   	{}
>   };
>   MODULE_DEVICE_TABLE(ieee1394, snd_bebob_id_table);
> diff --git a/sound/firewire/bebob/bebob.h b/sound/firewire/bebob/bebob.h
> index b35fc52..fb9d99d 100644
> --- a/sound/firewire/bebob/bebob.h
> +++ b/sound/firewire/bebob/bebob.h
> @@ -113,6 +113,15 @@ struct snd_bebob {
>           int dev_lock_count;
>           bool dev_lock_changed;
>           wait_queue_head_t hwdep_wait;
> +
> +	/* for M-Audio special devices */
> +	bool maudio_special_quirk;
> +	bool maudio_is1814;
> +	unsigned int clk_src;
> +	unsigned int dig_in_iface;
> +	unsigned int dig_in_fmt;
> +	unsigned int dig_out_fmt;
> +	unsigned int clk_lock;
>   };
>
>   static inline int
> @@ -214,6 +223,18 @@ int snd_bebob_create_pcm_devices(struct snd_bebob *bebob);
>
>   int snd_bebob_create_hwdep_device(struct snd_bebob *bebob);
>
> +/* device specific operations */
> +int snd_bebob_maudio_special_discover(struct snd_bebob *bebob, bool is1814);
> +int snd_bebob_maudio_special_add_controls(struct snd_bebob *bebob);
> +
> +extern struct snd_bebob_spec maudio_bootloader_spec;
> +extern struct snd_bebob_spec maudio_special_spec;
> +extern struct snd_bebob_spec maudio_nrv10_spec;
> +extern struct snd_bebob_spec maudio_fw410_spec;
> +extern struct snd_bebob_spec maudio_audiophile_spec;
> +extern struct snd_bebob_spec maudio_solo_spec;
> +extern struct snd_bebob_spec maudio_ozonic_spec;
> +
>   #define SND_BEBOB_DEV_ENTRY(vendor, model, private_data) \
>   { \
>   	.match_flags	= IEEE1394_MATCH_VENDOR_ID | \
> diff --git a/sound/firewire/bebob/bebob_control.c b/sound/firewire/bebob/bebob_control.c
> index 3b311c3..87c50a37 100644
> --- a/sound/firewire/bebob/bebob_control.c
> +++ b/sound/firewire/bebob/bebob_control.c
> @@ -323,6 +323,9 @@ int snd_bebob_create_control_devices(struct snd_bebob *bebob)
>   		if (err < 0)
>   			goto end;
>   	}
> +
> +	if (bebob->maudio_special_quirk)
> +		err = snd_bebob_maudio_special_add_controls(bebob);
>   end:
>   	return err;
>   }
> diff --git a/sound/firewire/bebob/bebob_maudio.c b/sound/firewire/bebob/bebob_maudio.c
> new file mode 100644
> index 0000000..8afc835
> --- /dev/null
> +++ b/sound/firewire/bebob/bebob_maudio.c
> @@ -0,0 +1,910 @@
> +/*
> + * bebob_maudio.c - a part of driver for BeBoB based devices
> + *
> + * Copyright (c) 2013 Takashi Sakamoto
> + *
> + * This driver is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License, version 2.
> + *
> + * This driver is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this driver; if not, see <http://www.gnu.org/licenses/>.
> + */
> +
> +#include "./bebob.h"
> +
> +/*
> + * NOTE:
> + * For Firewire 410 and Firewire Audiophile, this module requests firmware
> + * version 5058 or later. With former version, BeBoB chipset needs downloading
> + * firmware and the driver should do this. To do this with ALSA, I need to
> + * examinate whether it's OK or not to include firmware binary blob to
> + * alsa-firmware package. With later version, the firmware is in ROM of chipset
> + * and the driver just send a cue to load it when probing the device. This cue
> + * is sent just once.
> + *
> + * For streaming, both of output and input streams are needed for Firewire 410
> + * and Ozonic. The single stream is OK for the other devices even if the clock
> + * source is not SYT-Match (I note no devices use SYT-Match).
> + *
> + * Without streaming, the devices except for Firewire Audiophile can mix any
> + * input and output. For this purpose, use ffado-mixer. Audiophile need to
> + * any stream for this purpose.
> + *
> + * Firewire 1814 and ProjectMix I/O uses special firmware. It will be freezed
> + * if receiving any commands which the firmware can't understand. These devices
> + * utilize completely different system to control. It is write transaction
> + * directly into a certain address. All of addresses for mixer functionality is
> + * between 0xffc700700000 to 0xffc70070009c.
> + */
> +
> +#define MAUDIO_BOOTLOADER_CUE1	0x01000000
> +#define MAUDIO_BOOTLOADER_CUE2	0x00001101
> +#define MAUDIO_BOOTLOADER_CUE3	0x00000000
> +
> +#define MAUDIO_SPECIFIC_ADDRESS	0xffc700000000
> +
> +#define METER_OFFSET		0x00600000
> +
> +/* some device has sync info after metering data */
> +#define METER_SIZE_SPECIAL	84	/* with sync info */
> +#define METER_SIZE_FW410	76	/* with sync info */
> +#define METER_SIZE_AUDIOPHILE	60	/* with sync info */
> +#define METER_SIZE_SOLO		52	/* with sync info */
> +#define METER_SIZE_OZONIC	48
> +#define METER_SIZE_NRV10	80
> +
> +/* labels for metering */
> +#define ANA_IN		"Analog In"
> +#define ANA_OUT		"Analog Out"
> +#define DIG_IN		"Digital In"
> +#define SPDIF_IN	"S/PDIF In"
> +#define ADAT_IN		"ADAT In"
> +#define DIG_OUT		"Digital Out"
> +#define SPDIF_OUT	"S/PDIF Out"
> +#define ADAT_OUT	"ADAT Out"
> +#define STRM_IN		"Stream In"
> +#define AUX_OUT		"Aux Out"
> +#define HP_OUT		"HP Out"
> +/* for NRV */
> +#define UNKNOWN_METER	"Unknown"
> +
> +/*
> + * FW1814/ProjectMix don't use AVC for control. The driver cannot refer to
> + * current parameters by asynchronous transaction. The driver is allowed to
> + * write transaction so MUST remember the current values.
> + */
> +#define	MAUDIO_CONTROL_OFFSET	0x00700000
> +
> +/* If we make any transaction to load firmware, the operation may failed. */
> +/* TODO: change snd-firewire-lib and use it */
> +static int
> +run_a_transaction(struct fw_unit *unit, int tcode,
> +		  u64 offset, void *buffer, size_t length)
> +{
> +	struct fw_device *device = fw_parent_device(unit);
> +	int generation, rcode;
> +
> +	generation = device->generation;
> +	smp_rmb();	/* node id vs. generation*/
> +	rcode = fw_run_transaction(device->card, tcode,
> +				   device->node_id, generation,
> +				   device->max_speed, offset,
> +				   buffer, length);
> +	if (rcode == RCODE_COMPLETE)
> +		return 0;
> +
> +	dev_err(&unit->device, "Failed to send a queue to load firmware\n");
> +	return -EIO;
> +}
> +
> +/*
> + * For some M-Audio devices, this module just send cue to load
> + * firmware. After loading, the device generates bus reset and
> + * newly detected.
> + */
> +static int
> +firmware_load(struct fw_unit *unit, const struct ieee1394_device_id *entry)
> +{
> +	__be32 cues[3];
> +
> +	cues[0] = cpu_to_be32(MAUDIO_BOOTLOADER_CUE1);
> +	cues[1] = cpu_to_be32(MAUDIO_BOOTLOADER_CUE2);
> +	cues[2] = cpu_to_be32(MAUDIO_BOOTLOADER_CUE3);
> +
> +	return run_a_transaction(unit, TCODE_WRITE_BLOCK_REQUEST,
> +				 BEBOB_ADDR_REG_REQ, cues, sizeof(cues));
> +}
> +
> +static inline int
> +get_meter(struct snd_bebob *bebob, void *buf, unsigned int size)
> +{
> +	return snd_fw_transaction(bebob->unit, TCODE_READ_BLOCK_REQUEST,
> +				  MAUDIO_SPECIFIC_ADDRESS + METER_OFFSET,
> +				  buf, size, 0);
> +}
> +
> +/*
> + * BeBoB don't tell drivers to detect digital input, just show clock sync or not.
> + */
> +static int
> +check_clk_sync(struct snd_bebob *bebob, unsigned int size, bool *sync)
> +{
> +	int err;
> +	u8 *buf;
> +
> +	buf = kmalloc(size, GFP_KERNEL);
> +	if (buf == NULL)
> +		return -ENOMEM;
> +
> +	err = get_meter(bebob, buf, size);
> +	if (err < 0)
> +		goto end;
> +
> +	/* if synced, this value is the same of SFC of FDF in CIP header */
> +	*sync = (buf[size - 2] != 0xff);
> +	err = 0;
> +end:
> +	kfree(buf);
> +	return err;
> +}
> +
> +/*
> + * dig_fmt: 0x00:S/PDIF, 0x01:ADAT
> + * clk_lock: 0x00:unlock, 0x01:lock
> + */
> +static int
> +special_clk_set_params(struct snd_bebob *bebob, unsigned int clk_src,
> +		       unsigned int dig_in_fmt, unsigned int dig_out_fmt,
> +		       unsigned int clk_lock)
> +{
> +	int err;
> +	u8 *buf;
> +
> +	buf = kmalloc(12, GFP_KERNEL);
> +	if (buf == NULL)
> +		return -ENOMEM;
> +
> +	buf[0]  = 0x00;		/* CONTROL */
> +	buf[1]  = 0xff;		/* UNIT */
> +	buf[2]  = 0x00;		/* vendor dependent */
> +	buf[3]  = 0x04;		/* company ID high */
> +	buf[4]  = 0x00;		/* company ID middle */
> +	buf[5]  = 0x04;		/* company ID low */
> +	buf[6]  = 0xff & clk_src;	/* clock source */
> +	buf[7]  = 0xff & dig_in_fmt;	/* input digital format */
> +	buf[8]  = 0xff & dig_out_fmt;	/* output digital format */
> +	buf[9]  = 0xff & clk_lock;	/* lock these settings */
> +	buf[10] = 0x00;		/* padding  */
> +	buf[11] = 0x00;		/* padding */
> +
> +	/* do transaction and check buf[1-9] are the same against command */
> +	err = fcp_avc_transaction(bebob->unit, buf, 12, buf, 12,
> +				  BIT(1) | BIT(2) | BIT(3) | BIT(4) |
> +				  BIT(5) | BIT(6) | BIT(7) | BIT(8) |
> +				  BIT(9));
> +	if (err < 0)
> +		goto end;
> +	if ((err < 6) || (buf[0] != 0x09)) {
> +		dev_err(&bebob->unit->device,
> +			"failed to set clock params\n");
> +		err = -EIO;
> +		goto end;
> +	}
> +
> +	bebob->clk_src		= buf[6];
> +	/* handle both of input and output in this member */
> +	bebob->dig_in_fmt	= buf[7];
> +	bebob->dig_out_fmt	= buf[8];
> +	bebob->clk_lock		= buf[9];
> +
> +	err = 0;
> +end:
> +	kfree(buf);
> +	return err;
> +}
> +/*
> + * For special customized devices.
> + * The driver can't receive response from this firmware frequently.
> + * So need to reduce execution of command.
> + */
> +static void
> +special_stream_formation_set(struct snd_bebob *bebob)
> +{
> +	unsigned int i;
> +
> +	/*
> +	 * the stream formation is different depending on digital interface
> +	 */
> +	if (bebob->dig_in_fmt== 0x01) {
> +		bebob->tx_stream_formations[3].pcm = 16;
> +		bebob->tx_stream_formations[4].pcm = 16;
> +		bebob->tx_stream_formations[5].pcm = 12;
> +		bebob->tx_stream_formations[6].pcm = 12;
> +		if (bebob->maudio_is1814) {
> +			bebob->tx_stream_formations[7].pcm = 2;
> +			bebob->tx_stream_formations[8].pcm = 2;
> +		}
> +	} else {
> +		bebob->tx_stream_formations[3].pcm = 10;
> +		bebob->tx_stream_formations[4].pcm = 10;
> +		bebob->tx_stream_formations[5].pcm = 10;
> +		bebob->tx_stream_formations[6].pcm = 10;
> +		if (bebob->maudio_is1814) {
> +			bebob->tx_stream_formations[7].pcm = 2;
> +			bebob->tx_stream_formations[8].pcm = 2;
> +		}
> +	}
> +
> +	if (bebob->dig_out_fmt == 0x01) {
> +		bebob->rx_stream_formations[3].pcm = 12;
> +		bebob->rx_stream_formations[4].pcm = 12;
> +		bebob->rx_stream_formations[5].pcm = 8;
> +		bebob->rx_stream_formations[6].pcm = 8;
> +		if (bebob->maudio_is1814) {
> +			bebob->rx_stream_formations[7].pcm = 4;
> +			bebob->rx_stream_formations[8].pcm = 4;
> +		}
> +	} else {
> +		bebob->rx_stream_formations[3].pcm = 6;
> +		bebob->rx_stream_formations[4].pcm = 6;
> +		bebob->rx_stream_formations[5].pcm = 6;
> +		bebob->rx_stream_formations[6].pcm = 6;
> +		if (bebob->maudio_is1814) {
> +			bebob->rx_stream_formations[7].pcm = 4;
> +			bebob->rx_stream_formations[8].pcm = 4;
> +		}
> +	}
> +
> +	for (i = 3; i < SND_BEBOB_STRM_FMT_ENTRIES; i++) {
> +		bebob->tx_stream_formations[i].midi = 1;
> +		bebob->rx_stream_formations[i].midi = 1;
> +		if ((i > 7) && (bebob->maudio_is1814))
> +			break;
> +	}
> +}
> +
> +int
> +snd_bebob_maudio_special_discover(struct snd_bebob *bebob, bool is1814)
> +{
> +	int err;
> +
> +	bebob->maudio_is1814 = is1814;
> +
> +	/* initialize these parameters because doesn't allow driver to ask */
> +	err = special_clk_set_params(bebob, 0x03, 0x00, 0x00, 0x00);
> +	if (err < 0) {
> +		dev_err(&bebob->unit->device,
> +			"failed to initialize clock params\n");
> +	}
> +
> +	err = avc_audio_get_selector(bebob->unit, 0x00, 0x04,
> +				     &bebob->dig_in_iface);
> +	if (err < 0) {
> +		dev_err(&bebob->unit->device,
> +			"failed to get current dig iface.");
> +	}
> +
> +	special_stream_formation_set(bebob);
> +
> +	if (bebob->maudio_is1814) {
> +		bebob->midi_input_ports = 1;
> +		bebob->midi_output_ports = 1;
> +	} else {
> +		bebob->midi_input_ports = 2;
> +		bebob->midi_output_ports = 2;
> +	}
> +
> +	bebob->maudio_special_quirk = true;
> +
> +	return 0;
> +}
> +/*
> + * Input plug shows actual rate. Output plug is needless for this purpose.
> + */
> +static int special_clk_get_freq(struct snd_bebob *bebob, unsigned int *rate)
> +{
> +	return snd_bebob_get_rate(bebob, rate, AVC_GENERAL_PLUG_DIR_IN);
> +}
> +static char *special_clk_src_labels[] = {
> +	SND_BEBOB_CLOCK_INTERNAL " with Digital Mute", "Digital",
> +	"Word Clock", SND_BEBOB_CLOCK_INTERNAL};
> +static int
> +special_clk_src_get(struct snd_bebob *bebob, unsigned int *id)
> +{
> +	*id = bebob->clk_src;
> +	return 0;
> +}
> +static int
> +special_clk_src_set(struct snd_bebob *bebob, unsigned int id)
> +{
> +	return special_clk_set_params(bebob, id,
> +				      bebob->dig_in_fmt, bebob->dig_out_fmt,
> +				      bebob->clk_lock);
> +}
> +static int
> +special_clk_synced(struct snd_bebob *bebob, bool *synced)
> +{
> +	return check_clk_sync(bebob, METER_SIZE_SPECIAL, synced);
> +}
> +
> +static char *special_meter_labels[] = {
> +	ANA_IN, ANA_IN, ANA_IN, ANA_IN,
> +	SPDIF_IN,
> +	ADAT_IN, ADAT_IN, ADAT_IN, ADAT_IN,
> +	ANA_OUT, ANA_OUT,
> +	SPDIF_OUT,
> +	ADAT_OUT, ADAT_OUT, ADAT_OUT, ADAT_OUT,
> +	HP_OUT, HP_OUT,
> +	AUX_OUT
> +};
> +static int
> +special_meter_get(struct snd_bebob *bebob, u32 *target, unsigned int size)
> +{
> +	u16 *buf;
> +	unsigned int i, c, channels;
> +	int err;
> +
> +	channels = ARRAY_SIZE(special_meter_labels) * 2;
> +	if (size < channels * sizeof(u32))
> +		return -EINVAL;
> +
> +	/* omit last 5 bytes because it's clock info. */
> +	buf = kmalloc(METER_SIZE_SPECIAL - 4, GFP_KERNEL);
> +	if (buf == NULL)
> +		return -ENOMEM;
> +
> +	err = get_meter(bebob, (void *)buf, METER_SIZE_SPECIAL - 4);
> +	if (err < 0)
> +		goto end;
> +
> +	/* some channels are not used and convert u16 to u32 */
> +	for (i = 0, c = 2; c < channels + 2; c++)
> +		target[i++] = be16_to_cpu(buf[c]) << 8;
> +end:
> +	kfree(buf);
> +	return err;
> +}
> +
> +static char *special_dig_iface_labels[] = {
> +	"""S/PDIF Optical", "S/PDIF Coaxial", "ADAT Optical"
> +};
> +static int special_dig_in_iface_info(struct snd_kcontrol *kctl,
> +				      struct snd_ctl_elem_info *einf)
> +{
> +	einf->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
> +	einf->count = 1;
> +	einf->value.enumerated.items = ARRAY_SIZE(special_dig_iface_labels);
> +
> +	if (einf->value.enumerated.item >= einf->value.enumerated.items)
> +		einf->value.enumerated.item = einf->value.enumerated.items - 1;
> +
> +	strcpy(einf->value.enumerated.name,
> +	       special_dig_iface_labels[einf->value.enumerated.item]);
> +
> +	return 0;
> +}
> +static int special_dig_in_iface_get(struct snd_kcontrol *kctl,
> +				     struct snd_ctl_elem_value *uval)
> +{
> +	struct snd_bebob *bebob = snd_kcontrol_chip(kctl);
> +
> +	/* encoded id for user value */
> +	uval->value.enumerated.item[0] =
> +		(bebob->dig_in_fmt << 1) | (bebob->dig_in_iface & 0x01);
> +
> +	return 0;
> +}
> +static int special_dig_in_iface_set(struct snd_kcontrol *kctl,
> +				     struct snd_ctl_elem_value *uval)
> +{
> +	struct snd_bebob *bebob = snd_kcontrol_chip(kctl);
> +	unsigned int id, dig_in_fmt, dig_in_iface;
> +	int err;
> +
> +	id = uval->value.enumerated.item[0];
> +
> +	/* decode user value */
> +	dig_in_fmt = (id >> 1) & 0x01;
> +	dig_in_iface = id & 0x01;
> +
> +	err = special_clk_set_params(bebob, bebob->clk_src, dig_in_fmt,
> +				     bebob->dig_out_fmt, bebob->clk_lock);
> +	if ((err < 0) || (bebob->dig_in_fmt > 0)) /* ADAT */
> +		goto end;
> +
> +	err = avc_audio_set_selector(bebob->unit, 0x00, 0x04, dig_in_iface);
> +	if (err < 0)
> +		goto end;
> +
> +	bebob->dig_in_iface = dig_in_iface;
> +end:
> +	special_stream_formation_set(bebob);
> +	return err;
> +}
> +static struct snd_kcontrol_new special_dig_in_iface = {
> +	.name	= "Digital Input Interface",
> +	.iface	= SNDRV_CTL_ELEM_IFACE_MIXER,
> +	.access	= SNDRV_CTL_ELEM_ACCESS_READWRITE,
> +	.info	= special_dig_in_iface_info,
> +	.get	= special_dig_in_iface_get,
> +	.put	= special_dig_in_iface_set
> +};
> +
> +static int special_dig_out_iface_info(struct snd_kcontrol *kctl,
> +				      struct snd_ctl_elem_info *einf)
> +{
> +	einf->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
> +	einf->count = 1;
> +	einf->value.enumerated.items = ARRAY_SIZE(special_dig_iface_labels) - 1;
> +
> +	if (einf->value.enumerated.item >= einf->value.enumerated.items)
> +		einf->value.enumerated.item = einf->value.enumerated.items - 1;
> +
> +	strcpy(einf->value.enumerated.name,
> +	       special_dig_iface_labels[einf->value.enumerated.item + 1]);
> +
> +	return 0;
> +}
> +static int special_dig_out_iface_get(struct snd_kcontrol *kctl,
> +				     struct snd_ctl_elem_value *uval)
> +{
> +	struct snd_bebob *bebob = snd_kcontrol_chip(kctl);
> +	uval->value.enumerated.item[0] = bebob->dig_out_fmt;
> +	return 0;
> +}
> +static int special_dig_out_iface_set(struct snd_kcontrol *kctl,
> +				     struct snd_ctl_elem_value *uval)
> +{
> +	struct snd_bebob *bebob = snd_kcontrol_chip(kctl);
> +	unsigned int id;
> +	int err;
> +
> +	id = uval->value.enumerated.item[0];
> +
> +	err = special_clk_set_params(bebob, bebob->clk_src, bebob->dig_in_fmt,
> +				     id, bebob->clk_lock);
> +	if (err < 0)
> +		goto end;
> +
> +	special_stream_formation_set(bebob);
> +end:
> +	return err;
> +}
> +static struct snd_kcontrol_new special_dig_out_iface = {
> +	.name	= "Digital Output Interface",
> +	.iface	= SNDRV_CTL_ELEM_IFACE_MIXER,
> +	.access	= SNDRV_CTL_ELEM_ACCESS_READWRITE,
> +	.info	= special_dig_out_iface_info,
> +	.get	= special_dig_out_iface_get,
> +	.put	= special_dig_out_iface_set
> +};
> +
> +int snd_bebob_maudio_special_add_controls(struct snd_bebob *bebob)
> +{
> +	struct snd_kcontrol *kctl;
> +	int err;
> +
> +	kctl = snd_ctl_new1(&special_dig_in_iface, bebob);
> +	err = snd_ctl_add(bebob->card, kctl);
> +	if (err < 0)
> +		goto end;
> +
> +	kctl = snd_ctl_new1(&special_dig_out_iface, bebob);
> +	err = snd_ctl_add(bebob->card, kctl);
> +end:
> +	return err;
> +}
> +
> +/* Firewire 410 specific controls */
> +static char *fw410_clk_src_labels[] = {
> +	SND_BEBOB_CLOCK_INTERNAL, "Digital Optical", "Digital Coaxial"
> +};
> +static int
> +fw410_clk_src_get(struct snd_bebob *bebob, unsigned int *id)
> +{
> +	int err;
> +	unsigned int stype, sid, pid;
> +
> +	err = avc_ccm_get_sig_src(bebob->unit,
> +				  &stype, &sid, &pid, 0x0c, 0x00, 0x01);
> +	if (err < 0)
> +		goto end;
> +
> +	*id = 0;
> +	if ((stype == 0x1f) && (sid == 0x07)) {
> +		if (pid == 0x82)
> +			*id = 2;
> +		else if (pid == 0x83)
> +			*id = 1;
> +	}
> +end:
> +	return err;
> +}
> +static int
> +fw410_clk_src_set(struct snd_bebob *bebob, unsigned int id)
> +{
> +	unsigned int stype, sid, pid;
> +
> +	if (id == 0) {
> +		stype = 0x0c;
> +		sid = 0x00;
> +		pid = 0x01;
> +	} else if (id == 1) {
> +		stype = 0x1f;
> +		sid = 0x07;
> +		pid = 0x83;
> +	} else {
> +		stype = 0x1f;
> +		sid = 0x07;
> +		pid = 0x82;
> +	}
> +
> +	return avc_ccm_set_sig_src(bebob->unit,
> +				   stype, sid, pid, 0x0c, 0x00, 0x01);
> +}
> +static int
> +fw410_clk_synced(struct snd_bebob *bebob, bool *synced)
> +{
> +	return check_clk_sync(bebob, METER_SIZE_FW410, synced);
> +}
> +static char *fw410_meter_labels[] = {
> +	ANA_IN, DIG_IN,
> +	ANA_OUT, ANA_OUT, ANA_OUT, ANA_OUT, DIG_OUT,
> +	HP_OUT
> +};
> +static int
> +fw410_meter_get(struct snd_bebob *bebob, u32 *buf, unsigned int size)
> +{
> +
> +	unsigned int c, channels;
> +	int err;
> +
> +	channels = ARRAY_SIZE(fw410_meter_labels) * 2;
> +	if (size < channels * sizeof(u32))
> +		return -EINVAL;
> +
> +	/* omit last 4 bytes because it's clock info. */
> +	err = get_meter(bebob, (void *)buf, size);
> +	if (err < 0)
> +		goto end;
> +
> +	for (c = 0; c < channels; c++)
> +		buf[c] = be32_to_cpu(buf[c]);
> +end:
> +	return err;
> +}
> +
> +/* Firewire Audiophile specific controls */
> +static char *audiophile_clk_src_labels[] = {
> +	SND_BEBOB_CLOCK_INTERNAL, "Digital Coaxial"
> +};
> +static int
> +audiophile_clk_src_get(struct snd_bebob *bebob, unsigned int *id)
> +{
> +	unsigned int stype, sid, pid;
> +	int err;
> +
> +	err = avc_ccm_get_sig_src(bebob->unit,
> +				  &stype, &sid, &pid, 0x0c, 0x00, 0x01);
> +	if (err < 0)
> +		goto end;
> +
> +	if ((stype == 0x1f) && (sid == 0x07) && (pid == 0x82))
> +		*id = 1;
> +	else
> +		*id = 0;
> +end:
> +	return err;
> +}
> +static int
> +audiophile_clk_src_set(struct snd_bebob *bebob, unsigned int id)
> +{
> +	unsigned int stype, sid, pid;
> +
> +	if (id == 0) {
> +		stype = 0x0c;
> +		sid = 0x00;
> +		pid = 0x01;
> +	} else {
> +		stype = 0x1f;
> +		sid = 0x07;
> +		pid = 0x82;
> +	}
> +
> +	return avc_ccm_set_sig_src(bebob->unit,
> +				   stype, sid, pid, 0x0c, 0x00, 0x01);
> +}
> +static int
> +audiophile_clk_synced(struct snd_bebob *bebob, bool *synced)
> +{
> +	return check_clk_sync(bebob, METER_SIZE_AUDIOPHILE, synced);
> +}
> +static char *audiophile_meter_labels[] = {
> +	ANA_IN, DIG_IN,
> +	ANA_OUT, ANA_OUT, DIG_OUT,
> +	HP_OUT, AUX_OUT,
> +};
> +static int
> +audiophile_meter_get(struct snd_bebob *bebob, u32 *buf, unsigned int size)
> +{
> +	unsigned int c, channels;
> +	int err;
> +
> +	channels = ARRAY_SIZE(audiophile_meter_labels) * 2;
> +	if (size < channels * sizeof(u32))
> +		return -EINVAL;
> +
> +	/* omit last 4 bytes because it's clock info. */
> +	err = get_meter(bebob, (void *)buf, size);
> +	if (err < 0)
> +		goto end;
> +
> +	for (c = 0; c < channels; c++)
> +		buf[c] = be32_to_cpu(buf[c]);
> +end:
> +	return err;
> +}
> +
> +/* Firewire Solo specific controls */
> +static char *solo_clk_src_labels[] = {
> +	SND_BEBOB_CLOCK_INTERNAL, "Digital Coaxial"
> +};
> +static int
> +solo_clk_src_get(struct snd_bebob *bebob, unsigned int *id)
> +{
> +	unsigned int stype, sid, pid;
> +	int err;
> +
> +	err = avc_ccm_get_sig_src(bebob->unit,
> +				  &stype, &sid, &pid, 0x0c, 0x00, 0x01);
> +	if (err < 0)
> +		goto end;
> +
> +	if ((stype == 0x1f) && (sid = 0x07) && (pid== 0x81))
> +		*id = 1;
> +	else
> +		*id = 0;
> +end:
> +	return err;
> +}
> +static int
> +solo_clk_src_set(struct snd_bebob *bebob, unsigned int id)
> +{
> +	unsigned int stype, sid, pid;
> +
> +	if (id == 0) {
> +		stype = 0x0c;
> +		sid = 0x00;
> +		pid = 0x01;
> +	} else {
> +		stype = 0x1f;
> +		sid = 0x07;
> +		pid = 0x81;
> +	}
> +
> +	return avc_ccm_set_sig_src(bebob->unit,
> +				   stype, sid, pid, 0x0c, 0x00, 0x01);
> +}
> +static int
> +solo_clk_synced(struct snd_bebob *bebob, bool *synced)
> +{
> +	return check_clk_sync(bebob, METER_SIZE_SOLO, synced);
> +}
> +static char *solo_meter_labels[] = {
> +	ANA_IN, DIG_IN,
> +	STRM_IN, STRM_IN,
> +	ANA_OUT, DIG_OUT
> +};
> +static int
> +solo_meter_get(struct snd_bebob *bebob, u32 *buf, unsigned int size)
> +{
> +	unsigned int c;
> +	int err;
> +	u32 tmp;
> +
> +	if (size < ARRAY_SIZE(solo_meter_labels) * 2 * sizeof(u32))
> +		return -ENOMEM;
> +
> +	/* omit last 4 bytes because it's clock info. */
> +	err = get_meter(bebob, (void *)buf, size);
> +	if (err < 0)
> +		goto end;
> +
> +	c = 0;
> +	do
> +		buf[c] = be32_to_cpu(buf[c]);
> +	while (++c < 4);
> +
> +	/* swap stream channels because inverted */
> +	tmp = be32_to_cpu(buf[c]);
> +	buf[c] = be32_to_cpu(buf[c + 2]);
> +	buf[c + 2] = tmp;
> +	tmp = be32_to_cpu(buf[c + 1]);
> +	buf[c + 1] = be32_to_cpu(buf[c + 3]);
> +	buf[c + 3] = tmp;
> +
> +	c += 4;
> +	do
> +		buf[c] = be32_to_cpu(buf[c]);
> +	while (++c < 12);
> +end:
> +	return err;
> +}
> +
> +/* Ozonic specific controls */
> +static char *ozonic_meter_labels[] = {
> +	ANA_IN, ANA_IN,
> +	STRM_IN, STRM_IN,
> +	ANA_OUT, ANA_OUT
> +};
> +static int
> +ozonic_meter_get(struct snd_bebob *bebob, u32 *buf, unsigned int size)
> +{
> +	unsigned int c, channels;
> +	int err;
> +
> +	channels = ARRAY_SIZE(ozonic_meter_labels) * 2;
> +	if (size < channels * sizeof(u32))
> +		return -EINVAL;
> +
> +	err = get_meter(bebob, (void *)buf, size);
> +	if (err < 0)
> +		goto end;
> +
> +	for (c = 0; c < channels; c++)
> +		buf[c] = be32_to_cpu(buf[c]);
> +end:
> +	return err;
> +}
> +
> +/* NRV10 specific controls */
> +/* TODO: need testers. this is based on my assumption */
> +static char *nrv10_meter_labels[] = {
> +	ANA_IN, ANA_IN, ANA_IN, ANA_IN,
> +	DIG_IN,
> +	ANA_OUT, ANA_OUT, ANA_OUT, ANA_OUT,
> +	DIG_IN
> +};
> +static int
> +nrv10_meter_get(struct snd_bebob *bebob, u32 *buf, unsigned int size)
> +{
> +	unsigned int c, channels;
> +	int err;
> +
> +	channels = ARRAY_SIZE(nrv10_meter_labels) * 2;
> +	if (size < channels * sizeof(u32))
> +		return -EINVAL;
> +
> +	err = get_meter(bebob, (void *)buf, size);
> +	if (err < 0)
> +		goto end;
> +
> +	for (c = 0; c < channels; c++)
> +		buf[c] = be32_to_cpu(buf[c]);
> +end:
> +	return err;
> +}
> +
> +
> +/* BeBoB bootloader specification */
> +struct snd_bebob_spec maudio_bootloader_spec = {
> +	.load		= &firmware_load,
> +	.clock		= NULL,
> +};
> +
> +/* for special customized devices */
> +static struct snd_bebob_clock_spec special_clk_spec = {
> +	.num		= ARRAY_SIZE(special_clk_src_labels),
> +	.labels		= special_clk_src_labels,
> +	.get_src	= &special_clk_src_get,
> +	.set_src	= &special_clk_src_set,
> +	.get_freq	= &special_clk_get_freq,
> +	.set_freq	= &snd_bebob_stream_set_rate,
> +	.synced		= &special_clk_synced
> +};
> +static struct snd_bebob_meter_spec special_meter_spec = {
> +	.num	= ARRAY_SIZE(special_meter_labels),
> +	.labels	= special_meter_labels,
> +	.get	= &special_meter_get
> +};
> +struct snd_bebob_spec maudio_special_spec = {
> +	.load		= NULL,
> +	.clock		= &special_clk_spec,
> +	.meter		= &special_meter_spec
> +};
> +
> +/* Firewire 410 specification */
> +static struct snd_bebob_clock_spec fw410_clk_spec = {
> +	.num		= ARRAY_SIZE(fw410_clk_src_labels),
> +	.labels		= fw410_clk_src_labels,
> +	.get_src	= &fw410_clk_src_get,
> +	.set_src	= &fw410_clk_src_set,
> +	.get_freq	= &snd_bebob_stream_get_rate,
> +	.set_freq	= &snd_bebob_stream_set_rate,
> +	.synced		= &fw410_clk_synced
> +};
> +static struct snd_bebob_meter_spec fw410_meter_spec = {
> +	.num	= ARRAY_SIZE(fw410_meter_labels),
> +	.labels	= fw410_meter_labels,
> +	.get	= &fw410_meter_get
> +};
> +struct snd_bebob_spec maudio_fw410_spec = {
> +	.load	= NULL,
> +	.clock	= &fw410_clk_spec,
> +	.meter	= &fw410_meter_spec
> +};
> +
> +/* Firewire Audiophile specification */
> +static struct snd_bebob_clock_spec audiophile_clk_spec = {
> +	.num		= ARRAY_SIZE(audiophile_clk_src_labels),
> +	.labels		= audiophile_clk_src_labels,
> +	.get_src	= &audiophile_clk_src_get,
> +	.set_src	= &audiophile_clk_src_set,
> +	.get_freq	= &snd_bebob_stream_get_rate,
> +	.set_freq	= &snd_bebob_stream_set_rate,
> +	.synced		= &audiophile_clk_synced
> +};
> +static struct snd_bebob_meter_spec audiophile_meter_spec = {
> +	.num	= ARRAY_SIZE(audiophile_meter_labels),
> +	.labels	= audiophile_meter_labels,
> +	.get	= &audiophile_meter_get
> +};
> +struct snd_bebob_spec maudio_audiophile_spec = {
> +	.load	= &firmware_load,
> +	.clock	= &audiophile_clk_spec,
> +	.meter	= &audiophile_meter_spec
> +};
> +
> +/* Firewire Solo specification */
> +static struct snd_bebob_clock_spec solo_clk_spec = {
> +	.num		= ARRAY_SIZE(solo_clk_src_labels),
> +	.labels		= solo_clk_src_labels,
> +	.get_src	= &solo_clk_src_get,
> +	.set_src	= &solo_clk_src_set,
> +	.get_freq	= &snd_bebob_stream_get_rate,
> +	.set_freq	= &snd_bebob_stream_set_rate,
> +	.synced		= &solo_clk_synced
> +};
> +static struct snd_bebob_meter_spec solo_meter_spec = {
> +	.num	= ARRAY_SIZE(solo_meter_labels),
> +	.labels	= solo_meter_labels,
> +	.get	= &solo_meter_get
> +};
> +struct snd_bebob_spec maudio_solo_spec = {
> +	.load	= NULL,
> +	.clock	= &solo_clk_spec,
> +	.meter	= &solo_meter_spec
> +};
> +
> +/* Ozonic specification */
> +struct snd_bebob_clock_spec usual_clk_spec = {
> +	.get_freq	= &snd_bebob_stream_get_rate,
> +	.set_freq	= &snd_bebob_stream_set_rate
> +};
> +static struct snd_bebob_meter_spec ozonic_meter_spec = {
> +	.num	= ARRAY_SIZE(ozonic_meter_labels),
> +	.labels	= ozonic_meter_labels,
> +	.get	= &ozonic_meter_get
> +};
> +struct snd_bebob_spec maudio_ozonic_spec = {
> +	.load	= NULL,
> +	.clock	= &usual_clk_spec,
> +	.meter	= &ozonic_meter_spec
> +};
> +
> +/* NRV10 specification */
> +static struct snd_bebob_meter_spec nrv10_meter_spec = {
> +	.num	= ARRAY_SIZE(nrv10_meter_labels),
> +	.labels	= nrv10_meter_labels,
> +	.get	= &nrv10_meter_get
> +};
> +struct snd_bebob_spec maudio_nrv10_spec = {
> +	.load	= NULL,
> +	.clock	= &usual_clk_spec,
> +	.meter	= &nrv10_meter_spec
> +};
> diff --git a/sound/firewire/bebob/bebob_stream.c b/sound/firewire/bebob/bebob_stream.c
> index faa235e..078de08 100644
> --- a/sound/firewire/bebob/bebob_stream.c
> +++ b/sound/firewire/bebob/bebob_stream.c
> @@ -261,9 +261,11 @@ start_stream(struct snd_bebob *bebob, struct amdtp_stream *stream,
>   		conn = &bebob->out_conn;
>
>   	/* channel mapping */
> -	err = snd_bebob_stream_map(bebob, stream);
> -	if (err < 0)
> -		goto end;
> +	if (!bebob->maudio_special_quirk) {
> +		err = snd_bebob_stream_map(bebob, stream);
> +		if (err < 0)
> +			goto end;
> +	}
>
>   	/* start amdtp stream */
>   	err = amdtp_stream_start(stream,
> @@ -384,6 +386,20 @@ int snd_bebob_stream_start_duplex(struct snd_bebob *bebob,
>   			goto end;
>   		}
>
> +		/*
> +		 * NOTE:
> +		 * The firmware customized by M-Audio uses this cue to start
> +		 * transmit stream. This is not in specification.
> +		 */
> +		if (bebob->maudio_special_quirk) {
> +			err = spec->set_freq(bebob, rate);
> +			if (err < 0) {
> +				amdtp_stream_stop(master);
> +				break_both_connections(bebob);
> +				goto end;
> +			}
> +		}
> +
>   		/* wait first callback */
>   		if (!amdtp_stream_wait_callback(master)) {
>   			amdtp_stream_stop(master);
>



More information about the Alsa-devel mailing list