[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