[alsa-devel] [RFC][PATCH 00/13] bebob: a new driver for BridgeCo BeBoB based device
In previous series of patch, I showed enhancement of current firewire-lib. So I should show the way to use new functions in each driver side. This driver is under development but functionalities for streaming seems to be fixed. I'm happy to receive your comments.
This is a new driver for BridgeCo BeBoB based device. FFADO (successor of FreeBoB project) currently give driver and mixer control in user space. My aim of this series of patch is just to give a support for kernel streaming.
In theory, this driver just support kernel streaming and related functionality. But for my debugging, this module still includes some functionality which should be in user space.
Additionally, there is a heavily customized version of BeBoB. For some reasons, this driver give a few functionality to control such device.
Takashi Sakamoto (13): bebob: Add skelton for BridgeCo BeBoB based devices bebob: Add command and stream management bebob: Add proc interface for debug bebob: Add functionality to change sampling rate and control interface bebob: Add MIDI interface bebob: Add PCM interface bebob: Add hwdep interface to lock/unlock streaming bebob: Prepare for device specific operations bebob: add support for M-Audio Firewire series bebob: Add support for Focusrite Saffire/SaffirePro series bebob: Add support for Terratec PHASE series bebob: Add support for Yamaha GO series bebob: Add support for Presonus FIREBOX/FIREPOD
include/uapi/sound/asound.h | 3 +- include/uapi/sound/firewire.h | 1 + sound/firewire/Kconfig | 29 ++ sound/firewire/Makefile | 1 + sound/firewire/bebob/Makefile | 6 + sound/firewire/bebob/bebob.c | 450 ++++++++++++++++ sound/firewire/bebob/bebob.h | 255 +++++++++ sound/firewire/bebob/bebob_command.c | 423 +++++++++++++++ sound/firewire/bebob/bebob_control.c | 331 ++++++++++++ sound/firewire/bebob/bebob_focusrite.c | 390 ++++++++++++++ sound/firewire/bebob/bebob_hwdep.c | 207 ++++++++ sound/firewire/bebob/bebob_maudio.c | 910 +++++++++++++++++++++++++++++++++ sound/firewire/bebob/bebob_midi.c | 182 +++++++ sound/firewire/bebob/bebob_pcm.c | 420 +++++++++++++++ sound/firewire/bebob/bebob_presonus.c | 75 +++ sound/firewire/bebob/bebob_proc.c | 164 ++++++ sound/firewire/bebob/bebob_stream.c | 724 ++++++++++++++++++++++++++ sound/firewire/bebob/bebob_terratec.c | 143 ++++++ sound/firewire/bebob/bebob_yamaha.c | 176 +++++++ 19 files changed, 4889 insertions(+), 1 deletion(-) create mode 100644 sound/firewire/bebob/Makefile create mode 100644 sound/firewire/bebob/bebob.c create mode 100644 sound/firewire/bebob/bebob.h create mode 100644 sound/firewire/bebob/bebob_command.c create mode 100644 sound/firewire/bebob/bebob_control.c create mode 100644 sound/firewire/bebob/bebob_focusrite.c create mode 100644 sound/firewire/bebob/bebob_hwdep.c create mode 100644 sound/firewire/bebob/bebob_maudio.c create mode 100644 sound/firewire/bebob/bebob_midi.c create mode 100644 sound/firewire/bebob/bebob_pcm.c create mode 100644 sound/firewire/bebob/bebob_presonus.c create mode 100644 sound/firewire/bebob/bebob_proc.c create mode 100644 sound/firewire/bebob/bebob_stream.c create mode 100644 sound/firewire/bebob/bebob_terratec.c create mode 100644 sound/firewire/bebob/bebob_yamaha.c
This commit add a new driver with no functionality. This driver just reacts to Firewire bus events and create/remove card instance.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/Kconfig | 20 +++ sound/firewire/Makefile | 1 + sound/firewire/bebob/Makefile | 2 + sound/firewire/bebob/bebob.c | 274 ++++++++++++++++++++++++++++++++++++++++++ sound/firewire/bebob/bebob.h | 74 ++++++++++++ 5 files changed, 371 insertions(+) create mode 100644 sound/firewire/bebob/Makefile create mode 100644 sound/firewire/bebob/bebob.c create mode 100644 sound/firewire/bebob/bebob.h
diff --git a/sound/firewire/Kconfig b/sound/firewire/Kconfig index b3e274f..5349652 100644 --- a/sound/firewire/Kconfig +++ b/sound/firewire/Kconfig @@ -61,4 +61,24 @@ config SND_SCS1X To compile this driver as a module, choose M here: the module will be called snd-scs1x.
+config SND_BEBOB + tristate "BridgeCo BeBoB chipset support" + select SND_FIREWIRE_LIB + help + Say Y here to include support for FireWire devices based + on BridgeCo BeBoB chipset: + * Edirol FA-66/FA-101 + * Mackie OnyxFirewire + * Tascam IF-FW/DM + * Behringer X32 + * Apogee Rosetta200 + * ESI Quotafire610 + * AcousticReality eARMasterOne + * CME MatrixKFW + * Phonic HB24U + * BridgeCo RDAudio1/Audio5 + + To compile this driver as a module, choose M here: the module + will be called snd-fireworks. + endif # SND_FIREWIRE diff --git a/sound/firewire/Makefile b/sound/firewire/Makefile index 5099550..f2d1c5c 100644 --- a/sound/firewire/Makefile +++ b/sound/firewire/Makefile @@ -10,3 +10,4 @@ obj-$(CONFIG_SND_DICE) += snd-dice.o obj-$(CONFIG_SND_FIREWIRE_SPEAKERS) += snd-firewire-speakers.o obj-$(CONFIG_SND_ISIGHT) += snd-isight.o obj-$(CONFIG_SND_SCS1X) += snd-scs1x.o +obj-$(CONFIG_SND_BEBOB) += bebob/ diff --git a/sound/firewire/bebob/Makefile b/sound/firewire/bebob/Makefile new file mode 100644 index 0000000..c6f0141 --- /dev/null +++ b/sound/firewire/bebob/Makefile @@ -0,0 +1,2 @@ +snd-bebob-objs := bebob.o +obj-m += snd-bebob.o diff --git a/sound/firewire/bebob/bebob.c b/sound/firewire/bebob/bebob.c new file mode 100644 index 0000000..bec4b6f --- /dev/null +++ b/sound/firewire/bebob/bebob.c @@ -0,0 +1,274 @@ +/* + * bebob.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" + +MODULE_DESCRIPTION("bridgeCo BeBoB driver"); +MODULE_AUTHOR("Takashi Sakamoto o-takashi@sakamocchi.jp"); +MODULE_LICENSE("GPL v2"); + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; +static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "card index"); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string"); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "enable BeBoB sound card"); + +static DEFINE_MUTEX(devices_mutex); +static unsigned int devices_used; + +/* Offsets from information register. */ +#define INFO_OFFSET_GUID 0x10 +#define INFO_OFFSET_HW_MODEL_ID 0x18 +#define INFO_OFFSET_HW_MODEL_REVISION 0x1c + +#define VEN_EDIROL 0x000040ab +#define VEN_BRIDGECO 0x000007f5 +#define VEN_MACKIE 0x0000000f +#define VEN_TASCOM 0x0000022e +#define VEN_BEHRINGER 0x00001564 +#define VEN_APOGEE 0x000003db +#define VEN_ESI 0x00000f1b +#define VEN_ACOUSTIC 0x00000002 +#define VEN_CME 0x0000000a +#define VEN_PHONIC 0x00001496 + +static int +name_device(struct snd_bebob *bebob, unsigned int vendor_id) +{ + char vendor[24] = {}; + char model[24] = {}; + u32 id; + u32 data[2] = {}; + u32 revision; + int err; + + /* get vendor name */ + if (vendor_id == VEN_EDIROL) + strcpy(vendor, "Edirol"); + else if (vendor_id == VEN_BRIDGECO) + strcpy(vendor, "BridgeCo"); + else if (vendor_id == VEN_MACKIE) + strcpy(vendor, "Mackie"); + else if (vendor_id == VEN_TASCOM) + strcpy(vendor, "Tacsom"); + else if (vendor_id == VEN_BEHRINGER) + strcpy(vendor, "Behringer"); + else if (vendor_id == VEN_APOGEE) + strcpy(vendor, "Apogee"); + else if (vendor_id == VEN_ESI) + strcpy(vendor, "ESI"); + else if (vendor_id == VEN_ACOUSTIC) + strcpy(vendor, "AcousticReality"); + else if (vendor_id == VEN_CME) + strcpy(vendor, "CME"); + else if (vendor_id == VEN_PHONIC) + strcpy(vendor, "Phonic"); + + /* get model name */ + err = fw_csr_string(bebob->unit->directory, CSR_MODEL, + model, sizeof(model)); + if (err < 0) + goto end; + + /* get hardware id */ + err = snd_bebob_read_quad(bebob, INFO_OFFSET_HW_MODEL_ID, + &id, sizeof(id)); + if (err < 0) + goto end; + + /* get hardware revision */ + err = snd_bebob_read_quad(bebob, INFO_OFFSET_HW_MODEL_REVISION, + &revision, sizeof(revision)); + if (err < 0) + goto end; + + /* get GUID */ + err = snd_bebob_read_block(bebob, INFO_OFFSET_GUID, + data, sizeof(data)); + if (err < 0) + goto end; + + strcpy(bebob->card->driver, "BeBoB"); + strcpy(bebob->card->shortname, model); + snprintf(bebob->card->longname, sizeof(bebob->card->longname), + "%s %s (id:%d, rev:%d), GUID %08x%08x at %s, S%d", + vendor, model, id, revision, + data[0], data[1], + dev_name(&bebob->unit->device), + 100 << bebob->device->max_speed); +end: + return err; +} + +static void +snd_bebob_card_free(struct snd_card *card) +{ + struct snd_bebob *bebob = card->private_data; + + if (bebob->card_index >= 0) { + mutex_lock(&devices_mutex); + devices_used &= ~BIT(bebob->card_index); + mutex_unlock(&devices_mutex); + } + + mutex_destroy(&bebob->mutex); + + return; +} + +static int +snd_bebob_probe(struct fw_unit *unit, + const struct ieee1394_device_id *entry) +{ + struct snd_card *card; + struct snd_bebob *bebob; + unsigned int card_index; + int err; + + mutex_lock(&devices_mutex); + + /* check registered cards */ + for (card_index = 0; card_index < SNDRV_CARDS; card_index++) { + if (!(devices_used & BIT(card_index)) && enable[card_index]) + break; + } + if (card_index >= SNDRV_CARDS) { + err = -ENOENT; + goto end; + } + + /* create card */ + err = snd_card_create(index[card_index], id[card_index], + THIS_MODULE, sizeof(struct snd_bebob), &card); + if (err < 0) + goto end; + card->private_free = snd_bebob_card_free; + + /* initialize myself */ + bebob = card->private_data; + bebob->card = card; + bebob->device = fw_parent_device(unit); + bebob->unit = unit; + bebob->card_index = -1; + mutex_init(&bebob->mutex); + spin_lock_init(&bebob->lock); + + /* name device with communication */ + err = name_device(bebob, entry->vendor_id); + if (err < 0) + goto error; + + /* register card and device */ + snd_card_set_dev(card, &unit->device); + err = snd_card_register(card); + if (err < 0) { + snd_card_free(card); + goto error; + } + dev_set_drvdata(&unit->device, bebob); + devices_used |= BIT(card_index); + bebob->card_index = card_index; + + /* proved */ + err = 0; +end: + mutex_unlock(&devices_mutex); + return err; +error: + snd_card_free(card); + mutex_unlock(&devices_mutex); + return err; +} + +static void +snd_bebob_update(struct fw_unit *unit) +{ + struct snd_bebob *bebob = dev_get_drvdata(&unit->device); + fcp_bus_reset(bebob->unit); +} + + +static void snd_bebob_remove(struct fw_unit *unit) +{ + struct snd_bebob *bebob = dev_get_drvdata(&unit->device); + snd_card_disconnect(bebob->card); + snd_card_free_when_closed(bebob->card); +} + +static const struct ieee1394_device_id snd_bebob_id_table[] = { + /* Edirol, FA-66 */ + SND_BEBOB_DEV_ENTRY(VEN_EDIROL, 0x00010049), + /* Edirol, FA-101 */ + SND_BEBOB_DEV_ENTRY(VEN_EDIROL, 0x00010048), + /* BridgeCo, RDAudio1 */ + SND_BEBOB_DEV_ENTRY(VEN_BRIDGECO, 0x00010048), + /* BridgeCo, Audio5 */ + SND_BEBOB_DEV_ENTRY(VEN_BRIDGECO, 0x00010049), + /* Mackie, OnyxFirewire */ + SND_BEBOB_DEV_ENTRY(VEN_MACKIE, 0x00010065), + /* Mackie, OnyxFirewire */ + SND_BEBOB_DEV_ENTRY(VEN_MACKIE, 0x00010067), + /* Tascam, IF-FW/DM */ + SND_BEBOB_DEV_ENTRY(VEN_TASCOM, 0x00010067), + /* Behringer, X32 */ + SND_BEBOB_DEV_ENTRY(VEN_BEHRINGER, 0x00000006), + /* ApogeeElectronics, Rosetta200 */ + SND_BEBOB_DEV_ENTRY(VEN_APOGEE, 0x00010048), + /* ESI, Quatafire610 */ + SND_BEBOB_DEV_ENTRY(VEN_ESI, 0x00010064), + /* AcousticReality, eARMasterOne */ + SND_BEBOB_DEV_ENTRY(VEN_ACOUSTIC, 0x00000002), + /* CME, MatrixKFW */ + SND_BEBOB_DEV_ENTRY(VEN_CME, 0x00030000), + /* Phonic, HB24U */ + SND_BEBOB_DEV_ENTRY(VEN_PHONIC, 0x00000000), + {} +}; +MODULE_DEVICE_TABLE(ieee1394, snd_bebob_id_table); + +static struct fw_driver snd_bebob_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "snd-bebob", + .bus = &fw_bus_type, + }, + .probe = snd_bebob_probe, + .update = snd_bebob_update, + .remove = snd_bebob_remove, + .id_table = snd_bebob_id_table, +}; + +static int __init +snd_bebob_init(void) +{ + return driver_register(&snd_bebob_driver.driver); +} + +static void __exit +snd_bebob_exit(void) +{ + driver_unregister(&snd_bebob_driver.driver); + mutex_destroy(&devices_mutex); +} + +module_init(snd_bebob_init); +module_exit(snd_bebob_exit); diff --git a/sound/firewire/bebob/bebob.h b/sound/firewire/bebob/bebob.h new file mode 100644 index 0000000..60d875e --- /dev/null +++ b/sound/firewire/bebob/bebob.h @@ -0,0 +1,74 @@ +/* + * bebob.h - 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/. + */ + +#ifndef SOUND_BEBOB_H_INCLUDED +#define SOUND_BEBOB_H_INCLUDED + +#include <linux/compat.h> +#include <linux/device.h> +#include <linux/firewire.h> +#include <linux/firewire-constants.h> +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/delay.h> +#include <linux/slab.h> + +#include <sound/core.h> +#include <sound/initval.h> + +#include "../lib.h" +#include "../fcp.h" + +/* basic register addresses on bebob chip */ +#define BEBOB_ADDR_REG_INFO 0xffffc8020000 +#define BEBOB_ADDR_REG_REQ 0xffffc8021000 + +struct snd_bebob { + struct snd_card *card; + struct fw_device *device; + struct fw_unit *unit; + int card_index; + + struct mutex mutex; + spinlock_t lock; +}; + +static inline int +snd_bebob_read_block(struct snd_bebob *bebob, u64 addr, void *buf, int size) +{ + return snd_fw_transaction(bebob->unit, TCODE_READ_BLOCK_REQUEST, + BEBOB_ADDR_REG_INFO + addr, + buf, size, 0); +} + +static inline int +snd_bebob_read_quad(struct snd_bebob *bebob, u64 addr, void *buf, int size) +{ + return snd_fw_transaction(bebob->unit, TCODE_READ_QUADLET_REQUEST, + BEBOB_ADDR_REG_INFO + addr, + buf, size, 0); +} + +#define SND_BEBOB_DEV_ENTRY(vendor, model) \ +{ \ + .match_flags = IEEE1394_MATCH_VENDOR_ID | \ + IEEE1394_MATCH_MODEL_ID, \ + .vendor_id = vendor, \ + .model_id = model, \ +} + +#endif
This commit adds BridgeCo's AV/C extension commands and connection/stream management. The driver can get channel combination with the commands.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/bebob/Makefile | 3 +- sound/firewire/bebob/bebob.c | 10 + sound/firewire/bebob/bebob.h | 83 +++++ sound/firewire/bebob/bebob_command.c | 263 ++++++++++++++ sound/firewire/bebob/bebob_stream.c | 666 +++++++++++++++++++++++++++++++++++ 5 files changed, 1024 insertions(+), 1 deletion(-) create mode 100644 sound/firewire/bebob/bebob_command.c create mode 100644 sound/firewire/bebob/bebob_stream.c
diff --git a/sound/firewire/bebob/Makefile b/sound/firewire/bebob/Makefile index c6f0141..b29ca99 100644 --- a/sound/firewire/bebob/Makefile +++ b/sound/firewire/bebob/Makefile @@ -1,2 +1,3 @@ -snd-bebob-objs := bebob.o +snd-bebob-objs := bebob_command.o bebob_stream.o \ + bebob.o obj-m += snd-bebob.o diff --git a/sound/firewire/bebob/bebob.c b/sound/firewire/bebob/bebob.c index bec4b6f..3597a3e 100644 --- a/sound/firewire/bebob/bebob.c +++ b/sound/firewire/bebob/bebob.c @@ -177,6 +177,14 @@ snd_bebob_probe(struct fw_unit *unit, if (err < 0) goto error;
+ err = snd_bebob_stream_discover(bebob); + if (err < 0) + goto error; + + err = snd_bebob_stream_init_duplex(bebob); + if (err < 0) + goto error; + /* register card and device */ snd_card_set_dev(card, &unit->device); err = snd_card_register(card); @@ -204,12 +212,14 @@ snd_bebob_update(struct fw_unit *unit) { struct snd_bebob *bebob = dev_get_drvdata(&unit->device); fcp_bus_reset(bebob->unit); + snd_bebob_stream_update_duplex(bebob); }
static void snd_bebob_remove(struct fw_unit *unit) { struct snd_bebob *bebob = dev_get_drvdata(&unit->device); + snd_bebob_stream_destroy_duplex(bebob); snd_card_disconnect(bebob->card); snd_card_free_when_closed(bebob->card); } diff --git a/sound/firewire/bebob/bebob.h b/sound/firewire/bebob/bebob.h index 60d875e..f8f3025 100644 --- a/sound/firewire/bebob/bebob.h +++ b/sound/firewire/bebob/bebob.h @@ -30,13 +30,27 @@ #include <sound/core.h> #include <sound/initval.h>
+#include "../packets-buffer.h" +#include "../iso-resources.h" +#include "../amdtp.h" #include "../lib.h" +#include "../cmp.h" #include "../fcp.h"
/* basic register addresses on bebob chip */ #define BEBOB_ADDR_REG_INFO 0xffffc8020000 #define BEBOB_ADDR_REG_REQ 0xffffc8021000
+struct snd_bebob; + +#define SND_BEBOB_STRM_FMT_ENTRIES 9 +struct snd_bebob_stream_formation { + unsigned int pcm; + unsigned int midi; +}; +/* this is a lookup table for index of stream formations */ +extern const unsigned int snd_bebob_rate_table[SND_BEBOB_STRM_FMT_ENTRIES]; + struct snd_bebob { struct snd_card *card; struct fw_device *device; @@ -45,6 +59,20 @@ struct snd_bebob {
struct mutex mutex; spinlock_t lock; + + unsigned int midi_input_ports; + unsigned int midi_output_ports; + + struct cmp_connection out_conn; + struct amdtp_stream tx_stream; + struct cmp_connection in_conn; + struct amdtp_stream rx_stream; + + struct snd_bebob_stream_formation + tx_stream_formations[SND_BEBOB_STRM_FMT_ENTRIES]; + struct snd_bebob_stream_formation + rx_stream_formations[SND_BEBOB_STRM_FMT_ENTRIES]; + };
static inline int @@ -63,6 +91,61 @@ snd_bebob_read_quad(struct snd_bebob *bebob, u64 addr, void *buf, int size) buf, size, 0); }
+/* AVC command extensions, AV/C Unit and Subunit, Revision 17 (BridgeCo.) */ +enum snd_bebob_plug_dir { + SND_BEBOB_PLUG_DIR_IN = 0x00, + SND_BEBOB_PLUG_DIR_OUT = 0x01 +}; +enum snd_bebob_plug_unit { + SND_BEBOB_PLUG_UNIT_ISOC = 0x00, + SND_BEBOB_PLUG_UNIT_EXT = 0x01, + SND_BEBOB_PLUG_UNIT_ASYNC = 0x02 +}; +enum snd_bebob_plug_type { + SND_BEBOB_PLUG_TYPE_ISOC = 0x00, + SND_BEBOB_PLUG_TYPE_ASYNC = 0x01, + SND_BEBOB_PLUG_TYPE_MIDI = 0x02, + SND_BEBOB_PLUG_TYPE_SYNC = 0x03, + SND_BEBOB_PLUG_TYPE_ANA = 0x04, + SND_BEBOB_PLUG_TYPE_DIG = 0x05 +}; +int avc_bridgeco_get_plug_ch_pos(struct fw_unit *unit, + enum snd_bebob_plug_dir pdir, + unsigned short pid, u8 *buf, unsigned int len); +int avc_bridgeco_get_plug_type(struct fw_unit *unit, + enum snd_bebob_plug_dir pdir, + enum snd_bebob_plug_unit punit, + unsigned short p_id, + enum snd_bebob_plug_type *type); +int avc_bridgeco_get_plug_cluster_type(struct fw_unit *unit, + enum snd_bebob_plug_dir pdir, + unsigned int pid, unsigned int cluster_id, + u8 *ctype); +int avc_bridgeco_get_plug_strm_fmt(struct fw_unit *unit, + enum snd_bebob_plug_dir pdir, + unsigned short pid, + unsigned int entryid, u8 *buf, + unsigned int *len); + +int snd_bebob_get_rate(struct snd_bebob *bebob, unsigned int *rate, + enum avc_general_plug_dir dir); +int snd_bebob_set_rate(struct snd_bebob *bebob, unsigned int rate, + enum avc_general_plug_dir dir); + +/* for AMDTP streaming */ +int snd_bebob_stream_get_rate(struct snd_bebob *bebob, unsigned int *rate); +int snd_bebob_stream_set_rate(struct snd_bebob *bebob, unsigned int rate); +int snd_bebob_stream_discover(struct snd_bebob *bebob); +int snd_bebob_stream_map(struct snd_bebob *bebob, + struct amdtp_stream *stream); +int snd_bebob_stream_init_duplex(struct snd_bebob *bebob); +int snd_bebob_stream_start_duplex(struct snd_bebob *bebob, + struct amdtp_stream *stream, + unsigned int sampling_rate); +int snd_bebob_stream_stop_duplex(struct snd_bebob *bebob); +void snd_bebob_stream_update_duplex(struct snd_bebob *bebob); +void snd_bebob_stream_destroy_duplex(struct snd_bebob *bebob); + #define SND_BEBOB_DEV_ENTRY(vendor, model) \ { \ .match_flags = IEEE1394_MATCH_VENDOR_ID | \ diff --git a/sound/firewire/bebob/bebob_command.c b/sound/firewire/bebob/bebob_command.c new file mode 100644 index 0000000..98b7668 --- /dev/null +++ b/sound/firewire/bebob/bebob_command.c @@ -0,0 +1,263 @@ +/* + * bebob_command.c - 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" + +#define BEBOB_COMMAND_MAX_TRIAL 3 +#define BEBOB_COMMAND_WAIT_MSEC 100 + +int avc_bridgeco_get_plug_type(struct fw_unit *unit, + enum snd_bebob_plug_dir pdir, + enum snd_bebob_plug_unit punit, + unsigned short pid, + enum snd_bebob_plug_type *type) +{ + u8 *buf; + int err; + + buf = kzalloc(12, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + buf[0] = 0x01; /* AV/C STATUS */ + buf[1] = 0xff; /* UNIT */ + buf[2] = 0x02; /* PLUG INFO */ + buf[3] = 0xC0; /* Extended Plug Info */ + buf[4] = pdir; /* plug direction */ + buf[5] = 0x00; /* address mode [0x00/0x01/0x02] */ + buf[6] = punit; /* plug unit type */ + buf[7] = 0xff & pid; /* plug id */ + buf[8] = 0xff; /* reserved */ + buf[9] = 0x00; /* info type is 'plug type' */ + buf[10] = 0xff; /* plug type in response */ + + /* do transaction and check buf[1-7,9] are the same against command */ + err = fcp_avc_transaction(unit, buf, 12, buf, 12, + BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | + BIT(6) | BIT(7) | BIT(9)); + if (err < 0) + goto end; + /* IMPLEMENTED/STABLE is OK */ + else if ((err < 6) || (buf[0] != 0x0c)) { + err = -EIO; + goto end; + } + + *type = buf[10]; + err = 0; + +end: + kfree(buf); + return err; +} + +int avc_bridgeco_get_plug_ch_pos(struct fw_unit *unit, + enum snd_bebob_plug_dir pdir, + unsigned short pid, u8 *buf, unsigned int len) +{ + unsigned int trial; + int err; + + /* check given buffer */ + if ((buf == NULL) || (len < 256)) { + err = -EINVAL; + goto end; + } + + buf[0] = 0x01; /* AV/C STATUS */ + buf[1] = 0xff; /* Unit */ + buf[2] = 0x02; /* PLUG INFO */ + buf[3] = 0xC0; /* Extended Plug Info */ + buf[4] = pdir; /* plug direction */ + buf[5] = 0x00; /* address mode is 'Unit' */ + buf[6] = 0x00; /* plug unit type is 'ISOC'*/ + buf[7] = 0xff & pid; /* plug id */ + buf[8] = 0xff; /* reserved */ + buf[9] = 0x03; /* info type is 'channel position' */ + + /* + * NOTE: + * M-Audio Firewire 410 returns 0x09 (ACCEPTED) just after changing + * signal format even if this command asks STATE. This is not in + * AV/C command specification. + */ + for (trial = 0; trial < BEBOB_COMMAND_MAX_TRIAL; trial++) { + /* do transaction and check buf[1-7,9] are the same */ + err = fcp_avc_transaction(unit, buf, 12, buf, 256, + BIT(1) | BIT(2) | BIT(3) | BIT(4) | + BIT(5) | BIT(6) | BIT(7) | BIT(9)); + if (err < 0) + goto end; + else if (err < 6) { + err = -EIO; + goto end; + } else if (buf[0] == 0x0c) + break; + else if (trial < BEBOB_COMMAND_MAX_TRIAL) + msleep(BEBOB_COMMAND_WAIT_MSEC); + else { + err = -EIO; + goto end; + } + } + + /* strip command header */ + memmove(buf, buf + 10, err - 10); + err = 0; +end: + return err; +} + +int avc_bridgeco_get_plug_cluster_type(struct fw_unit *unit, + enum snd_bebob_plug_dir pdir, + unsigned int pid, unsigned int cluster_id, + u8 *type) +{ + u8 *buf; + int err; + + /* cluster info includes characters but this module don't need it */ + buf = kzalloc(12, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + buf[0] = 0x01; /* AV/C STATUS */ + buf[1] = 0xff; /* UNIT */ + buf[2] = 0x02; /* PLUG INFO */ + buf[3] = 0xc0; /* Extended Plug Info */ + buf[4] = pdir; /* plug direction */ + buf[5] = 0x00; /* address mode is 'Unit' */ + buf[6] = 0x00; /* plug unit type is 'ISOC' */ + buf[7] = 0xff & pid; /* plug id */ + buf[8] = 0xff; /* reserved */ + buf[9] = 0x07; /* info type is 'cluster info' */ + buf[10] = 0xff & (cluster_id + 1); /* cluster id */ + buf[11] = 0x00; /* type in response */ + + /* do transaction and check buf[1-7,9,10] are the same */ + err = fcp_avc_transaction(unit, buf, 12, buf, 12, + BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | + BIT(6) | BIT(7) | BIT(9) | BIT(10)); + if (err < 0) + goto end; + else if ((err < 12) && (buf[0] != 0x0c)) { + err = -EIO; + goto end; + } + + *type = buf[11]; + err = 0; +end: + kfree(buf); + return err; +} + +int avc_bridgeco_get_plug_strm_fmt(struct fw_unit *unit, + enum snd_bebob_plug_dir pdir, + unsigned short pid, + unsigned int entryid, u8 *buf, + unsigned int *len) +{ + int err; + + /* check given buffer */ + if ((buf == NULL) || (*len < 12)) { + err = -EINVAL; + goto end; + } + + /* fill buffer as command */ + buf[0] = 0x01; /* AV/C STATUS */ + buf[1] = 0xff; /* unit */ + buf[2] = 0x2f; /* opcode is STREAM FORMAT SUPPORT */ + buf[3] = 0xc1; /* COMMAND LIST, BridgeCo extension */ + buf[4] = pdir; /* plug direction */ + buf[5] = 0x00; /* address mode is 'Unit' */ + buf[6] = 0x00; /* plug unit type is 'ISOC' */ + buf[7] = 0xff & pid; /* plug ID */ + buf[8] = 0xff; /* reserved */ + buf[9] = 0xff; /* stream status in response */ + buf[10] = 0xff & entryid; /* entry ID */ + + /* do transaction and check buf[1-7,10] are the same against command */ + err = fcp_avc_transaction(unit, buf, 12, buf, *len, + BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | + BIT(6) | BIT(7) | BIT(10)); + if (err < 0) + goto end; + /* reach the end of entries */ + else if (buf[0] == 0x0a) { + err = 0; + *len = 0; + goto end; + } else if (buf[0] != 0x0c) { + err = -EINVAL; + goto end; + /* the header of this command is 11 bytes */ + } else if (err < 12) { + err = -EIO; + goto end; + } else if (buf[10] != entryid) { + err = -EIO; + goto end; + } + + /* strip command header */ + memmove(buf, buf + 11, err - 11); + *len = err - 11; + err = 0; +end: + return err; +} + +int snd_bebob_get_rate(struct snd_bebob *bebob, unsigned int *rate, + enum avc_general_plug_dir dir) +{ + int err; + + err = avc_general_get_sig_fmt(bebob->unit, rate, dir, 0); + if (err < 0) + goto end; + + /* IMPLEMENTED/STABLE is OK */ + if (err != 0x0c) { + dev_err(&bebob->unit->device, + "failed to get sampling rate\n"); + err = -EIO; + } +end: + return err; +} + +int snd_bebob_set_rate(struct snd_bebob *bebob, unsigned int rate, + enum avc_general_plug_dir dir) +{ + int err; + + err = avc_general_set_sig_fmt(bebob->unit, rate, dir, 0); + if (err < 0) + goto end; + + /* ACCEPTED or INTERIM is OK */ + if ((err != 0x0f) && (err != 0x09)) { + dev_err(&bebob->unit->device, + "failed to set sampling rate\n"); + err = -EIO; + } +end: + return err; +} diff --git a/sound/firewire/bebob/bebob_stream.c b/sound/firewire/bebob/bebob_stream.c new file mode 100644 index 0000000..6852945 --- /dev/null +++ b/sound/firewire/bebob/bebob_stream.c @@ -0,0 +1,666 @@ +/* + * bebob_stream.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 BeBoB streams, Both of input and output CMP connection is important. + * + * [Communication with Windows driver] According to logs of IEEE1394 packets, + * all models seem to make both of connections when booting. + * + * [Actual behavior] In some devices, one CMP connection starts to + * transmit/receive a corresponding stream. But in the others, both of CMP + * connection needs to start transmitting stream. An example of the latter is + * 'M-Audio Firewire 410'. + */ + +/* 128 is an arbitrary length but it seems to be enough */ +#define FORMAT_MAXIMUM_LENGTH 128 + +const unsigned int snd_bebob_rate_table[SND_BEBOB_STRM_FMT_ENTRIES] = { + [0] = 22050, + [1] = 24000, + [2] = 32000, + [3] = 44100, + [4] = 48000, + [5] = 88200, + [6] = 96000, + [7] = 176400, + [8] = 192000, +}; + +static unsigned int +get_formation_index(unsigned int rate) +{ + unsigned int i; + + for (i = 0; i < sizeof(snd_bebob_rate_table); i++) { + if (snd_bebob_rate_table[i] == rate) + return i; + } + return -1; +} + +int +snd_bebob_stream_get_rate(struct snd_bebob *bebob, unsigned int *curr_rate) +{ + unsigned int tx_rate, rx_rate; + int err; + + err = snd_bebob_get_rate(bebob, &tx_rate, AVC_GENERAL_PLUG_DIR_OUT); + if (err < 0) + goto end; + + err = snd_bebob_get_rate(bebob, &rx_rate, AVC_GENERAL_PLUG_DIR_IN); + if (err < 0) + goto end; + + *curr_rate = rx_rate; + if (rx_rate == tx_rate) + goto end; + + /* synchronize receive stream rate to transmit stream rate */ + err = snd_bebob_set_rate(bebob, rx_rate, AVC_GENERAL_PLUG_DIR_IN); +end: + return err; +} + +int +snd_bebob_stream_set_rate(struct snd_bebob *bebob, unsigned int rate) +{ + int err; + + /* TODO: move to strem_start? */ + err = snd_bebob_set_rate(bebob, rate, AVC_GENERAL_PLUG_DIR_OUT); + if (err < 0) + goto end; + + err = snd_bebob_set_rate(bebob, rate, AVC_GENERAL_PLUG_DIR_IN); +end: + return err; +} + +int snd_bebob_stream_map(struct snd_bebob *bebob, + struct amdtp_stream *stream) +{ + unsigned int cl, ch, clusters, channels, pos, pcm, midi; + u8 *buf, ctype; + enum snd_bebob_plug_dir dir; + int err; + + /* + * The length of return value of this command cannot be assumed. Here + * keep the maximum length of AV/C command which defined specification. + */ + buf = kzalloc(256, GFP_KERNEL); + if (buf == NULL) { + err = -ENOMEM; + goto end; + } + + if (stream == &bebob->tx_stream) + dir = SND_BEBOB_PLUG_DIR_OUT; + else + dir = SND_BEBOB_PLUG_DIR_IN; + + err = avc_bridgeco_get_plug_ch_pos(bebob->unit, dir, 0, buf, 256); + if (err < 0) + goto end; + + clusters = *buf; + buf++; + pcm = 0; + midi = 0; + for (cl = 0; cl < clusters; cl++) { + err = avc_bridgeco_get_plug_cluster_type(bebob->unit, dir, 0, + cl, &ctype); + if (err < 0) + goto end; + + channels = *buf; + buf++; + for (ch = 0; ch < channels; ch++) { + pos = *buf - 1; + if (ctype != 0x0a) + stream->pcm_positions[pcm++] = pos; + else + stream->midi_positions[midi++] = pos; + buf += 2; + } + } + +end: + return err; +} + +static int +init_both_connections(struct snd_bebob *bebob) +{ + int err; + + err = cmp_connection_init(&bebob->in_conn, + bebob->unit, CMP_INPUT, 0); + if (err < 0) + goto end; + + err = cmp_connection_init(&bebob->out_conn, + bebob->unit, CMP_OUTPUT, 0); + if (err < 0) + cmp_connection_destroy(&bebob->in_conn); +end: + return err; +} + +static int +check_connection_used_by_others(struct snd_bebob *bebob, + struct amdtp_stream *s, bool *used) +{ + struct cmp_connection *conn; + int err; + + if (s == &bebob->tx_stream) + conn = &bebob->out_conn; + else + conn = &bebob->in_conn; + + err = cmp_connection_check_used(conn, used); + if (err >= 0) + *used = (*used && !amdtp_stream_running(s)); + + return err; +} + +static int +make_both_connections(struct snd_bebob *bebob, unsigned int rate) +{ + int index, pcm_channels, midi_channels, err; + + /* confirm params for both streams */ + index = get_formation_index(rate); + pcm_channels = bebob->tx_stream_formations[index].pcm; + midi_channels = bebob->tx_stream_formations[index].midi; + amdtp_stream_set_parameters(&bebob->tx_stream, + rate, pcm_channels, midi_channels * 8); + pcm_channels = bebob->rx_stream_formations[index].pcm; + midi_channels = bebob->rx_stream_formations[index].midi; + amdtp_stream_set_parameters(&bebob->rx_stream, + rate, pcm_channels, midi_channels * 8); + + /* establish connections for both streams */ + err = cmp_connection_establish(&bebob->out_conn, + amdtp_stream_get_max_payload(&bebob->tx_stream)); + if (err < 0) + goto end; + err = cmp_connection_establish(&bebob->in_conn, + amdtp_stream_get_max_payload(&bebob->rx_stream)); + if (err < 0) + cmp_connection_break(&bebob->out_conn); +end: + return err; +} + +static void +break_both_connections(struct snd_bebob *bebob) +{ + cmp_connection_break(&bebob->in_conn); + cmp_connection_break(&bebob->out_conn); + return; +} + +static void +destroy_both_connections(struct snd_bebob *bebob) +{ + break_both_connections(bebob); + + cmp_connection_destroy(&bebob->in_conn); + cmp_connection_destroy(&bebob->out_conn); +} + +static int +get_roles(struct snd_bebob *bebob, enum cip_flags *sync_mode, + struct amdtp_stream **master, struct amdtp_stream **slave) +{ + /* currently this module doesn't support SYT-Match mode */ + *sync_mode = CIP_SYNC_TO_DEVICE; + *master = &bebob->tx_stream; + *slave = &bebob->rx_stream; + + return 0; +} + +static int +start_stream(struct snd_bebob *bebob, struct amdtp_stream *stream, + unsigned int rate) +{ + struct cmp_connection *conn; + int err = 0; + + /* already running */ + if (amdtp_stream_running(stream)) + goto end; + + if (stream == &bebob->rx_stream) + conn = &bebob->in_conn; + else + conn = &bebob->out_conn; + + /* channel mapping */ + err = snd_bebob_stream_map(bebob, stream); + if (err < 0) + goto end; + + /* start amdtp stream */ + err = amdtp_stream_start(stream, + conn->resources.channel, + conn->speed); +end: + return err; +} + +int snd_bebob_stream_init_duplex(struct snd_bebob *bebob) +{ + int err; + + err = init_both_connections(bebob); + if (err < 0) + goto end; + + err = amdtp_stream_init(&bebob->tx_stream, bebob->unit, + AMDTP_TRANSMIT_STREAM, CIP_BLOCKING); + if (err < 0) { + destroy_both_connections(bebob); + goto end; + } + + err = amdtp_stream_init(&bebob->rx_stream, bebob->unit, + AMDTP_RECEIVE_STREAM, CIP_BLOCKING); + if (err < 0) { + amdtp_stream_destroy(&bebob->tx_stream); + destroy_both_connections(bebob); + } +end: + return err; +} + +int snd_bebob_stream_start_duplex(struct snd_bebob *bebob, + struct amdtp_stream *request, + unsigned int rate) +{ + struct amdtp_stream *master, *slave; + enum cip_flags sync_mode; + unsigned int curr_rate; + bool slave_flag, used; + int err; + + mutex_lock(&bebob->mutex); + + err = get_roles(bebob, &sync_mode, &master, &slave); + if (err < 0) + goto end; + + if ((request == slave) || amdtp_stream_running(slave)) + slave_flag = true; + else + slave_flag = false; + + /* + * Considering JACK/FFADO streaming: + * TODO: This can be removed hwdep functionality becomes popular. + */ + err = check_connection_used_by_others(bebob, master, &used); + if (err < 0) + goto end; + if (used) { + dev_err(&bebob->unit->device, + "connections established by others: %d\n", + used); + err = -EBUSY; + goto end; + } + + /* get current rate */ + err = snd_bebob_stream_get_rate(bebob, &curr_rate); + if (err < 0) + goto end; + if (rate == 0) + rate = curr_rate; + + /* change sampling rate if needed */ + if (rate != curr_rate) { + /* slave is just for MIDI stream */ + if (amdtp_stream_running(slave) && + !amdtp_stream_pcm_running(slave)) + amdtp_stream_stop(slave); + + /* master is just for MIDI stream */ + if (amdtp_stream_running(master) && + !amdtp_stream_pcm_running(master)) { + amdtp_stream_stop(master); + break_both_connections(bebob); + } + } + + /* master should be always running */ + if (!amdtp_stream_running(master)) { + amdtp_stream_set_sync(sync_mode, master, slave); + + /* + * NOTE: + * If establishing connections at first, Yamaha GO46 (and maybe + * TerraTek X24) don't generate sound. + */ + err = snd_bebob_stream_set_rate(bebob, rate); + if (err < 0) + goto end; + + err = make_both_connections(bebob, rate); + if (err < 0) + goto end; + + err = start_stream(bebob, master, rate); + if (err < 0) { + dev_err(&bebob->unit->device, + "fail to run AMDTP master stream:%d\n", err); + break_both_connections(bebob); + goto end; + } + + /* wait first callback */ + if (!amdtp_stream_wait_callback(master)) { + amdtp_stream_stop(master); + break_both_connections(bebob); + err = -ETIMEDOUT; + goto end; + } + } + + /* start slave if needed */ + if (slave_flag && !amdtp_stream_running(slave)) { + err = start_stream(bebob, slave, rate); + if (err < 0) { + dev_err(&bebob->unit->device, + "fail to run AMDTP slave stream:%d\n", err); + amdtp_stream_stop(master); + break_both_connections(bebob); + goto end; + } + + /* wait first callback */ + if (!amdtp_stream_wait_callback(slave)) { + amdtp_stream_stop(slave); + amdtp_stream_stop(master); + break_both_connections(bebob); + err = -ETIMEDOUT; + goto end; + } + } +end: + mutex_unlock(&bebob->mutex); + return err; +} + +int snd_bebob_stream_stop_duplex(struct snd_bebob *bebob) +{ + struct amdtp_stream *master, *slave; + enum cip_flags sync_mode; + int err; + + mutex_lock(&bebob->mutex); + + err = get_roles(bebob, &sync_mode, &master, &slave); + if (err < 0) + goto end; + + if (amdtp_stream_pcm_running(slave) || + amdtp_stream_midi_running(slave)) + goto end; + + amdtp_stream_stop(slave); + + if (amdtp_stream_pcm_running(master) || + amdtp_stream_midi_running(master)) + goto end; + + amdtp_stream_stop(master); + break_both_connections(bebob); +end: + mutex_unlock(&bebob->mutex); + return err; +} + +void snd_bebob_stream_update_duplex(struct snd_bebob *bebob) +{ + if ((cmp_connection_update(&bebob->in_conn) > 0) || + (cmp_connection_update(&bebob->out_conn) > 0)) { + mutex_lock(&bebob->mutex); + amdtp_stream_pcm_abort(&bebob->rx_stream); + amdtp_stream_pcm_abort(&bebob->tx_stream); + break_both_connections(bebob); + mutex_unlock(&bebob->mutex); + } + + amdtp_stream_update(&bebob->rx_stream); + amdtp_stream_update(&bebob->tx_stream); +} + +void snd_bebob_stream_destroy_duplex(struct snd_bebob *bebob) +{ + mutex_lock(&bebob->mutex); + + if (amdtp_stream_pcm_running(&bebob->rx_stream)) + amdtp_stream_pcm_abort(&bebob->rx_stream); + if (amdtp_stream_pcm_running(&bebob->tx_stream)) + amdtp_stream_pcm_abort(&bebob->tx_stream); + + amdtp_stream_stop(&bebob->rx_stream); + amdtp_stream_stop(&bebob->tx_stream); + destroy_both_connections(bebob); + + mutex_unlock(&bebob->mutex); +} + +static void +set_stream_formation(u8 *buf, unsigned int len, + struct snd_bebob_stream_formation *formation) +{ + unsigned int e, channels, format; + + for (e = 0; e < buf[4]; e++) { + channels = buf[5 + e * 2]; + format = buf[6 + e * 2]; + + switch (format) { + /* PCM for IEC 60958-3 */ + case 0x00: + /* PCM for IEC 61937-3 to 7 */ + case 0x01: + case 0x02: + case 0x03: + case 0x04: + case 0x05: + /* PCM for Multi bit linear audio */ + case 0x06: /* raw */ + case 0x07: /* DVD-Audio */ + formation->pcm += channels; + break; + /* MIDI comformant (MMA/AMEI RP-027) */ + case 0x0d: + formation->midi += channels; + break; + default: + break; + } + } + + return; +} + +static int +fill_stream_formations(struct snd_bebob *bebob, enum snd_bebob_plug_dir dir, + unsigned short pid) +{ + static const unsigned int freq_table[] = { + [0x00] = 0, /* 22050 */ + [0x01] = 1, /* 24000 */ + [0x02] = 2, /* 32000 */ + [0x03] = 3, /* 44100 */ + [0x04] = 4, /* 48000 */ + [0x05] = 6, /* 96000 */ + [0x06] = 7, /* 176400 */ + [0x07] = 8, /* 192000 */ + [0x0a] = 5 /* 88200 */ + }; + + u8 *buf; + struct snd_bebob_stream_formation *formations; + unsigned int i, index, len, eid; + int err; + + buf = kmalloc(FORMAT_MAXIMUM_LENGTH, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + if (dir == SND_BEBOB_PLUG_DIR_IN) + formations = bebob->rx_stream_formations; + else + formations = bebob->tx_stream_formations; + + for (eid = 0, index = 0; eid < SND_BEBOB_STRM_FMT_ENTRIES; eid++) { + len = FORMAT_MAXIMUM_LENGTH; + + memset(buf, 0, len); + err = avc_bridgeco_get_plug_strm_fmt(bebob->unit, dir, pid, + eid, buf, &len); + if (err < 0) + goto end; + else if (len < 3) + break; + /* + * this module can support a hierarchy combination that: + * Root: Audio and Music (0x90) + * Level 1: AM824 Compound (0x40) + */ + else if ((buf[0] != 0x90) || (buf[1] != 0x40)) + break; + + /* check sampling rate */ + index = -1; + for (i = 0; i < sizeof(freq_table); i++) { + if (i == buf[2]) + index = freq_table[i]; + } + if (index < 0) + break; + + /* parse and set stream formation */ + set_stream_formation(buf, len, &formations[index]); + } + + err = 0; +end: + kfree(buf); + return err; +} + +/* In this function, 2 means input and output */ +int snd_bebob_stream_discover(struct snd_bebob *bebob) +{ + /* the number of plugs for input and output */ + unsigned short bus_plugs[AVC_GENERAL_PLUG_DIR_COUNT]; + unsigned short ext_plugs[AVC_GENERAL_PLUG_DIR_COUNT]; + enum snd_bebob_plug_type type; + unsigned int i; + int err; + + err = avc_general_get_plug_info(bebob->unit, bus_plugs, ext_plugs); + if (err < 0) + goto end; + + /* + * This module supports one ISOC input plug and one ISOC output plug + * then ignores the others. + */ + if (bus_plugs[AVC_GENERAL_PLUG_DIR_IN] == 0) { + err = -EIO; + goto end; + } + err = avc_bridgeco_get_plug_type(bebob->unit, + SND_BEBOB_PLUG_DIR_IN, + SND_BEBOB_PLUG_UNIT_ISOC, + 0, &type); + if (err < 0) + goto end; + else if (type != SND_BEBOB_PLUG_TYPE_ISOC) { + err = -EIO; + goto end; + } + + if (bus_plugs[AVC_GENERAL_PLUG_DIR_OUT] == 0) { + err = -EIO; + goto end; + } + err = avc_bridgeco_get_plug_type(bebob->unit, + SND_BEBOB_PLUG_DIR_OUT, + SND_BEBOB_PLUG_UNIT_ISOC, + 0, &type); + if (err < 0) + goto end; + else if (type != SND_BEBOB_PLUG_TYPE_ISOC) { + err = -EIO; + goto end; + } + + /* store formations */ + for (i = 0; i < 2; i++) { + err = fill_stream_formations(bebob, i, 0); + if (err < 0) + goto end; + } + + /* count external input plugs for MIDI */ + bebob->midi_input_ports = 0; + for (i = 0; i < ext_plugs[0]; i++) { + err = avc_bridgeco_get_plug_type(bebob->unit, + SND_BEBOB_PLUG_DIR_IN, + SND_BEBOB_PLUG_UNIT_EXT, + i, &type); + if (err < 0) + goto end; + else if (type == SND_BEBOB_PLUG_TYPE_MIDI) + bebob->midi_input_ports++; + } + + /* count external output plugs for MIDI */ + bebob->midi_output_ports = 0; + for (i = 0; i < ext_plugs[1]; i++) { + err = avc_bridgeco_get_plug_type(bebob->unit, + SND_BEBOB_PLUG_DIR_OUT, + SND_BEBOB_PLUG_UNIT_EXT, + i, &type); + if (err < 0) + goto end; + else if (type == SND_BEBOB_PLUG_TYPE_MIDI) + bebob->midi_output_ports++; + } + + err = 0; + +end: + return err; +}
This commit adds proc interface to get chipset information and stream formation for debug.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/bebob/Makefile | 2 +- sound/firewire/bebob/bebob.c | 2 + sound/firewire/bebob/bebob.h | 3 + sound/firewire/bebob/bebob_proc.c | 123 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 129 insertions(+), 1 deletion(-) create mode 100644 sound/firewire/bebob/bebob_proc.c
diff --git a/sound/firewire/bebob/Makefile b/sound/firewire/bebob/Makefile index b29ca99..6e44fa0 100644 --- a/sound/firewire/bebob/Makefile +++ b/sound/firewire/bebob/Makefile @@ -1,3 +1,3 @@ -snd-bebob-objs := bebob_command.o bebob_stream.o \ +snd-bebob-objs := bebob_command.o bebob_stream.o bebob_proc.o \ bebob.o obj-m += snd-bebob.o diff --git a/sound/firewire/bebob/bebob.c b/sound/firewire/bebob/bebob.c index 3597a3e..e5aa96e 100644 --- a/sound/firewire/bebob/bebob.c +++ b/sound/firewire/bebob/bebob.c @@ -177,6 +177,8 @@ snd_bebob_probe(struct fw_unit *unit, if (err < 0) goto error;
+ snd_bebob_proc_init(bebob); + err = snd_bebob_stream_discover(bebob); if (err < 0) goto error; diff --git a/sound/firewire/bebob/bebob.h b/sound/firewire/bebob/bebob.h index f8f3025..2d60165 100644 --- a/sound/firewire/bebob/bebob.h +++ b/sound/firewire/bebob/bebob.h @@ -29,6 +29,7 @@
#include <sound/core.h> #include <sound/initval.h> +#include <sound/info.h>
#include "../packets-buffer.h" #include "../iso-resources.h" @@ -146,6 +147,8 @@ int snd_bebob_stream_stop_duplex(struct snd_bebob *bebob); void snd_bebob_stream_update_duplex(struct snd_bebob *bebob); void snd_bebob_stream_destroy_duplex(struct snd_bebob *bebob);
+void snd_bebob_proc_init(struct snd_bebob *bebob); + #define SND_BEBOB_DEV_ENTRY(vendor, model) \ { \ .match_flags = IEEE1394_MATCH_VENDOR_ID | \ diff --git a/sound/firewire/bebob/bebob_proc.c b/sound/firewire/bebob/bebob_proc.c new file mode 100644 index 0000000..ca7c1a7 --- /dev/null +++ b/sound/firewire/bebob/bebob_proc.c @@ -0,0 +1,123 @@ +/* + * bebob_proc.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" + +/* contents of information register */ +struct hw_info { + u64 manufacturer; + u32 protocol_ver; + u32 bld_ver; + u32 guid[2]; + u32 model_id; + u32 model_rev; + u64 fw_date; + u64 fw_time; + u32 fw_id; + u32 fw_ver; + u32 base_addr; + u32 max_size; + u64 bld_date; + u64 bld_time; +/* may not used in product + u64 dbg_date; + u64 dbg_time; + u32 dbg_id; + u32 dbg_version; +*/ +} __packed; + +static void +proc_read_hw_info(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_bebob *bebob = entry->private_data; + struct hw_info *info; + int err; + + info = kzalloc(sizeof(struct hw_info), GFP_KERNEL); + if (info == NULL) + return; + + err = snd_bebob_read_block(bebob, 0, + info, sizeof(struct hw_info)); + if (err < 0) + goto end; + + snd_iprintf(buffer, "Manufacturer:\t%.8s\n", + (char *)&info->manufacturer); + snd_iprintf(buffer, "Protocol Ver:\t%d\n", info->protocol_ver); + snd_iprintf(buffer, "Build Ver:\t%d\n", info->bld_ver); + snd_iprintf(buffer, "GUID:\t\t0x%.8X%.8X\n", + info->guid[0], info->guid[1]); + snd_iprintf(buffer, "Model ID:\t0x%02X\n", info->model_id); + snd_iprintf(buffer, "Model Rev:\t%d\n", info->model_rev); + snd_iprintf(buffer, "Firmware Date:\t%.8s\n", (char *)&info->fw_date); + snd_iprintf(buffer, "Firmware Time:\t%.8s\n", (char *)&info->fw_time); + snd_iprintf(buffer, "Firmware ID:\t0x%X\n", info->fw_id); + snd_iprintf(buffer, "Firmware Ver:\t%d\n", info->fw_ver); + snd_iprintf(buffer, "Base Addr:\t0x%X\n", info->base_addr); + snd_iprintf(buffer, "Max Size:\t%d\n", info->max_size); + snd_iprintf(buffer, "Loader Date:\t%.8s\n", (char *)&info->bld_date); + snd_iprintf(buffer, "Loader Time:\t%.8s\n", (char *)&info->bld_time); + +end: + kfree(info); + return; +} + +static void +proc_read_formation(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_bebob *bebob = entry->private_data; + struct snd_bebob_stream_formation *formation; + unsigned int i; + + snd_iprintf(buffer, "Output Stream from device:\n"); + snd_iprintf(buffer, "\tRate\tPCM\tMIDI\n"); + formation = bebob->tx_stream_formations; + for (i = 0; i < 9; i += 1) { + snd_iprintf(buffer, + "\t%d\t%d\t%d\n", snd_bebob_rate_table[i], + formation[i].pcm, formation[i].midi); + } + + snd_iprintf(buffer, "Input Stream to device:\n"); + snd_iprintf(buffer, "\tRate\tPCM\tMIDI\n"); + formation = bebob->rx_stream_formations; + for (i = 0; i < 9; i += 1) { + snd_iprintf(buffer, + "\t%d\t%d\t%d\n", snd_bebob_rate_table[i], + formation[i].pcm, formation[i].midi); + } + + return; +} + +void snd_bebob_proc_init(struct snd_bebob *bebob) +{ + struct snd_info_entry *entry; + + if (!snd_card_proc_new(bebob->card, "#hardware", &entry)) + snd_info_set_text_ops(entry, bebob, proc_read_hw_info); + + if (!snd_card_proc_new(bebob->card, "#formation", &entry)) + snd_info_set_text_ops(entry, bebob, proc_read_formation); + + return; +}
This commit adds the functionality to change sampling rate. To debug this, this commit also adds control interface. I plan to extend this control interface for the other functionality.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/bebob/Makefile | 2 +- sound/firewire/bebob/bebob.c | 4 + sound/firewire/bebob/bebob.h | 4 + sound/firewire/bebob/bebob_control.c | 148 +++++++++++++++++++++++++++++++++++ sound/firewire/bebob/bebob_stream.c | 2 + 5 files changed, 159 insertions(+), 1 deletion(-) create mode 100644 sound/firewire/bebob/bebob_control.c
diff --git a/sound/firewire/bebob/Makefile b/sound/firewire/bebob/Makefile index 6e44fa0..bec01cd 100644 --- a/sound/firewire/bebob/Makefile +++ b/sound/firewire/bebob/Makefile @@ -1,3 +1,3 @@ -snd-bebob-objs := bebob_command.o bebob_stream.o bebob_proc.o \ +snd-bebob-objs := bebob_command.o bebob_stream.o bebob_proc.o bebob_control.o \ bebob.o obj-m += snd-bebob.o diff --git a/sound/firewire/bebob/bebob.c b/sound/firewire/bebob/bebob.c index e5aa96e..71a3896 100644 --- a/sound/firewire/bebob/bebob.c +++ b/sound/firewire/bebob/bebob.c @@ -177,6 +177,10 @@ snd_bebob_probe(struct fw_unit *unit, if (err < 0) goto error;
+ err = snd_bebob_create_control_devices(bebob); + if (err < 0) + goto error; + snd_bebob_proc_init(bebob);
err = snd_bebob_stream_discover(bebob); diff --git a/sound/firewire/bebob/bebob.h b/sound/firewire/bebob/bebob.h index 2d60165..d87999e 100644 --- a/sound/firewire/bebob/bebob.h +++ b/sound/firewire/bebob/bebob.h @@ -30,6 +30,7 @@ #include <sound/core.h> #include <sound/initval.h> #include <sound/info.h> +#include <sound/control.h>
#include "../packets-buffer.h" #include "../iso-resources.h" @@ -74,6 +75,7 @@ struct snd_bebob { struct snd_bebob_stream_formation rx_stream_formations[SND_BEBOB_STRM_FMT_ENTRIES];
+ struct snd_ctl_elem_id *ctl_id_freq; };
static inline int @@ -149,6 +151,8 @@ void snd_bebob_stream_destroy_duplex(struct snd_bebob *bebob);
void snd_bebob_proc_init(struct snd_bebob *bebob);
+int snd_bebob_create_control_devices(struct snd_bebob *bebob); + #define SND_BEBOB_DEV_ENTRY(vendor, model) \ { \ .match_flags = IEEE1394_MATCH_VENDOR_ID | \ diff --git a/sound/firewire/bebob/bebob_control.c b/sound/firewire/bebob/bebob_control.c new file mode 100644 index 0000000..f9a9229 --- /dev/null +++ b/sound/firewire/bebob/bebob_control.c @@ -0,0 +1,148 @@ +/* + * bebob_control.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" + + +/* + * Global Control: Sampling Rate Control + * + * refer to snd_bebob_rate_table. + */ +static int +control_sampling_rate_info(struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *einf) +{ + struct snd_bebob *bebob = snd_kcontrol_chip(kctl); + unsigned int i, value; + + /* maximum value for user */ + einf->value.enumerated.items = 0; + for (i = 0; i < ARRAY_SIZE(snd_bebob_rate_table); i++) + if ((bebob->tx_stream_formations[i].pcm > 0) && + (bebob->rx_stream_formations[i].pcm > 0)) + einf->value.enumerated.items++; + + einf->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + einf->count = 1; + + if (einf->value.enumerated.item >= einf->value.enumerated.items) + einf->value.enumerated.item = einf->value.enumerated.items - 1; + + /* skip unsupported sampling rates */ + value = einf->value.enumerated.item; + for (i = 0; i < ARRAY_SIZE(snd_bebob_rate_table); i++) { + if ((bebob->tx_stream_formations[i].pcm == 0) || + (bebob->rx_stream_formations[i].pcm == 0)) + continue; + else if (value == 0) + break; + else + value--; + } + + sprintf(einf->value.enumerated.name, "%dHz", snd_bebob_rate_table[i]); + + return 0; +} +static int +control_sampling_rate_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *uval) +{ + struct snd_bebob *bebob = snd_kcontrol_chip(kctl); + unsigned int i, sampling_rate, index; + int err; + + mutex_lock(&bebob->mutex); + + err = snd_bebob_stream_get_rate(bebob, &sampling_rate); + if (err < 0) + goto end; + + for (index = 0; index < ARRAY_SIZE(snd_bebob_rate_table); index++) + if (snd_bebob_rate_table[index] == sampling_rate) + break; + + uval->value.enumerated.item[0] = 0; + for (i = 0; i < index; i++) + if ((bebob->tx_stream_formations[i].pcm != 0) || + (bebob->rx_stream_formations[i].pcm != 0)) + uval->value.enumerated.item[0]++; + +end: + mutex_unlock(&bebob->mutex); + return err; +} +static int +control_sampling_rate_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *uval) +{ + struct snd_bebob *bebob = snd_kcontrol_chip(kctl); + unsigned int index, sampling_rate; + int value, changed = 0; + + /* get index from user value*/ + value = uval->value.enumerated.item[0]; + for (index = 0; index < ARRAY_SIZE(snd_bebob_rate_table); index++) { + if ((bebob->tx_stream_formations[index].pcm == 0) || + (bebob->rx_stream_formations[index].pcm == 0)) + continue; + else if (value == 0) + break; + else + value--; + } + + sampling_rate = snd_bebob_rate_table[index]; + + mutex_lock(&bebob->mutex); + if (snd_bebob_stream_set_rate(bebob, sampling_rate) < 0) + goto end; + + /* prevent from failure of getting command just after setting */ + msleep(100); + changed = 1; + + snd_ctl_notify(bebob->card, SNDRV_CTL_EVENT_MASK_VALUE, + bebob->ctl_id_freq); +end: + mutex_unlock(&bebob->mutex); + return changed; +} + +static struct snd_kcontrol_new global_sampling_rate_control = { + .name = "Sampling Rate", + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = control_sampling_rate_info, + .get = control_sampling_rate_get, + .put = control_sampling_rate_put +}; + +int snd_bebob_create_control_devices(struct snd_bebob *bebob) +{ + int err = 0; + struct snd_kcontrol *kctl; + + kctl = snd_ctl_new1(&global_sampling_rate_control, bebob); + err = snd_ctl_add(bebob->card, kctl); + if (err < 0) + goto end; + bebob->ctl_id_freq = &kctl->id; +end: + return err; +} diff --git a/sound/firewire/bebob/bebob_stream.c b/sound/firewire/bebob/bebob_stream.c index 6852945..773dffb 100644 --- a/sound/firewire/bebob/bebob_stream.c +++ b/sound/firewire/bebob/bebob_stream.c @@ -368,6 +368,8 @@ int snd_bebob_stream_start_duplex(struct snd_bebob *bebob, err = snd_bebob_stream_set_rate(bebob, rate); if (err < 0) goto end; + snd_ctl_notify(bebob->card, SNDRV_CTL_EVENT_MASK_VALUE, + bebob->ctl_id_freq);
err = make_both_connections(bebob, rate); if (err < 0)
This commit adds the functionality to capture/playback MIDI message. MIDI message is multiplexed/demultiplexed into AMDTP stream.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/Kconfig | 1 + sound/firewire/bebob/Makefile | 1 + sound/firewire/bebob/bebob.c | 7 ++ sound/firewire/bebob/bebob.h | 3 + sound/firewire/bebob/bebob_midi.c | 172 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 184 insertions(+) create mode 100644 sound/firewire/bebob/bebob_midi.c
diff --git a/sound/firewire/Kconfig b/sound/firewire/Kconfig index 5349652..c7987a0 100644 --- a/sound/firewire/Kconfig +++ b/sound/firewire/Kconfig @@ -63,6 +63,7 @@ config SND_SCS1X
config SND_BEBOB tristate "BridgeCo BeBoB chipset support" + select SND_RAWMIDI select SND_FIREWIRE_LIB help Say Y here to include support for FireWire devices based diff --git a/sound/firewire/bebob/Makefile b/sound/firewire/bebob/Makefile index bec01cd..c7aeef7 100644 --- a/sound/firewire/bebob/Makefile +++ b/sound/firewire/bebob/Makefile @@ -1,3 +1,4 @@ snd-bebob-objs := bebob_command.o bebob_stream.o bebob_proc.o bebob_control.o \ + bebob_midi.o \ bebob.o obj-m += snd-bebob.o diff --git a/sound/firewire/bebob/bebob.c b/sound/firewire/bebob/bebob.c index 71a3896..89dc45b 100644 --- a/sound/firewire/bebob/bebob.c +++ b/sound/firewire/bebob/bebob.c @@ -177,6 +177,13 @@ snd_bebob_probe(struct fw_unit *unit, if (err < 0) goto error;
+ if ((bebob->midi_input_ports > 0) || + (bebob->midi_output_ports > 0)) { + err = snd_bebob_create_midi_devices(bebob); + if (err < 0) + goto error; + } + err = snd_bebob_create_control_devices(bebob); if (err < 0) goto error; diff --git a/sound/firewire/bebob/bebob.h b/sound/firewire/bebob/bebob.h index d87999e..2775a73 100644 --- a/sound/firewire/bebob/bebob.h +++ b/sound/firewire/bebob/bebob.h @@ -31,6 +31,7 @@ #include <sound/initval.h> #include <sound/info.h> #include <sound/control.h> +#include <sound/rawmidi.h>
#include "../packets-buffer.h" #include "../iso-resources.h" @@ -153,6 +154,8 @@ void snd_bebob_proc_init(struct snd_bebob *bebob);
int snd_bebob_create_control_devices(struct snd_bebob *bebob);
+int snd_bebob_create_midi_devices(struct snd_bebob *bebob); + #define SND_BEBOB_DEV_ENTRY(vendor, model) \ { \ .match_flags = IEEE1394_MATCH_VENDOR_ID | \ diff --git a/sound/firewire/bebob/bebob_midi.c b/sound/firewire/bebob/bebob_midi.c new file mode 100644 index 0000000..56c7115 --- /dev/null +++ b/sound/firewire/bebob/bebob_midi.c @@ -0,0 +1,172 @@ +/* + * bebob_midi.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" + +/* + * According to MMA/AMEI-027, MIDI stream is multiplexed with PCM stream in + * AMDTP packet. The data rate of MIDI message is much less than PCM so there + * is a little problem to suspend MIDI streams. + */ +static int midi_capture_open(struct snd_rawmidi_substream *substream) +{ + struct snd_bebob *bebob = substream->rmidi->private_data; + int err; + + err = snd_bebob_stream_start_duplex(bebob, &bebob->tx_stream, 0); + if (err < 0) + goto end; + + amdtp_stream_midi_add(&bebob->tx_stream, substream); +end: + return err; +} + +static int midi_playback_open(struct snd_rawmidi_substream *substream) +{ + struct snd_bebob *bebob = substream->rmidi->private_data; + int err; + + err = snd_bebob_stream_start_duplex(bebob, &bebob->rx_stream, 0); + if (err < 0) + goto end; + + amdtp_stream_midi_add(&bebob->rx_stream, substream); +end: + return err; +} + +static int midi_capture_close(struct snd_rawmidi_substream *substream) +{ + struct snd_bebob *bebob = substream->rmidi->private_data; + + amdtp_stream_midi_remove(&bebob->tx_stream, substream); + snd_bebob_stream_stop_duplex(bebob); + + return 0; +} + +static int midi_playback_close(struct snd_rawmidi_substream *substream) +{ + struct snd_bebob *bebob = substream->rmidi->private_data; + + amdtp_stream_midi_remove(&bebob->rx_stream, substream); + snd_bebob_stream_stop_duplex(bebob); + + return 0; +} + +static void midi_capture_trigger(struct snd_rawmidi_substream *substrm, int up) +{ + struct snd_bebob *bebob = substrm->rmidi->private_data; + unsigned long flags; + + spin_lock_irqsave(&bebob->lock, flags); + + if (up) + __set_bit(substrm->number, &bebob->tx_stream.midi_triggered); + else + __clear_bit(substrm->number, &bebob->tx_stream.midi_triggered); + + spin_unlock_irqrestore(&bebob->lock, flags); + + return; +} + +static void midi_playback_trigger(struct snd_rawmidi_substream *substrm, int up) +{ + struct snd_bebob *bebob = substrm->rmidi->private_data; + unsigned long flags; + + spin_lock_irqsave(&bebob->lock, flags); + + if (up) + __set_bit(substrm->number, &bebob->rx_stream.midi_triggered); + else + __clear_bit(substrm->number, &bebob->rx_stream.midi_triggered); + + spin_unlock_irqrestore(&bebob->lock, flags); + + return; +} + +static struct snd_rawmidi_ops midi_capture_ops = { + .open = midi_capture_open, + .close = midi_capture_close, + .trigger = midi_capture_trigger, +}; + +static struct snd_rawmidi_ops midi_playback_ops = { + .open = midi_playback_open, + .close = midi_playback_close, + .trigger = midi_playback_trigger, +}; + +static void set_midi_substream_names(struct snd_bebob *bebob, + struct snd_rawmidi_str *str) +{ + struct snd_rawmidi_substream *subs; + + list_for_each_entry(subs, &str->substreams, list) { + snprintf(subs->name, sizeof(subs->name), + "%s MIDI %d", bebob->card->shortname, subs->number + 1); + } +} + +int snd_bebob_create_midi_devices(struct snd_bebob *bebob) +{ + struct snd_rawmidi *rmidi; + struct snd_rawmidi_str *str; + int err; + + /* create midi ports */ + err = snd_rawmidi_new(bebob->card, bebob->card->driver, 0, + bebob->midi_output_ports, bebob->midi_input_ports, + &rmidi); + if (err < 0) + return err; + + snprintf(rmidi->name, sizeof(rmidi->name), + "%s MIDI", bebob->card->shortname); + rmidi->private_data = bebob; + + if (bebob->midi_input_ports > 0) { + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_INPUT; + + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_INPUT, + &midi_capture_ops); + + str = &rmidi->streams[SNDRV_RAWMIDI_STREAM_INPUT]; + + set_midi_substream_names(bebob, str); + } + + if (bebob->midi_output_ports > 0) { + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_OUTPUT; + + snd_rawmidi_set_ops(rmidi, SNDRV_RAWMIDI_STREAM_OUTPUT, + &midi_playback_ops); + + str = &rmidi->streams[SNDRV_RAWMIDI_STREAM_OUTPUT]; + + set_midi_substream_names(bebob, str); + } + + if ((bebob->midi_output_ports > 0) && (bebob->midi_input_ports > 0)) + rmidi->info_flags |= SNDRV_RAWMIDI_INFO_DUPLEX; + + return 0; +}
This commit adds the functionality to capture/playback PCM samples.
PCM samples are multiplexed/demultiplexed into AMDTP stream safely with stream helper functions.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/Kconfig | 1 + sound/firewire/bebob/Makefile | 2 +- sound/firewire/bebob/bebob.c | 4 + sound/firewire/bebob/bebob.h | 4 + sound/firewire/bebob/bebob_pcm.c | 411 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 421 insertions(+), 1 deletion(-) create mode 100644 sound/firewire/bebob/bebob_pcm.c
diff --git a/sound/firewire/Kconfig b/sound/firewire/Kconfig index c7987a0..7641772 100644 --- a/sound/firewire/Kconfig +++ b/sound/firewire/Kconfig @@ -64,6 +64,7 @@ config SND_SCS1X config SND_BEBOB tristate "BridgeCo BeBoB chipset support" select SND_RAWMIDI + select SND_PCM select SND_FIREWIRE_LIB help Say Y here to include support for FireWire devices based diff --git a/sound/firewire/bebob/Makefile b/sound/firewire/bebob/Makefile index c7aeef7..30e79e6 100644 --- a/sound/firewire/bebob/Makefile +++ b/sound/firewire/bebob/Makefile @@ -1,4 +1,4 @@ snd-bebob-objs := bebob_command.o bebob_stream.o bebob_proc.o bebob_control.o \ - bebob_midi.o \ + bebob_midi.o bebob_pcm.o \ bebob.o obj-m += snd-bebob.o diff --git a/sound/firewire/bebob/bebob.c b/sound/firewire/bebob/bebob.c index 89dc45b..2fdd14b 100644 --- a/sound/firewire/bebob/bebob.c +++ b/sound/firewire/bebob/bebob.c @@ -177,6 +177,10 @@ snd_bebob_probe(struct fw_unit *unit, if (err < 0) goto error;
+ err = snd_bebob_create_pcm_devices(bebob); + if (err < 0) + goto error; + if ((bebob->midi_input_ports > 0) || (bebob->midi_output_ports > 0)) { err = snd_bebob_create_midi_devices(bebob); diff --git a/sound/firewire/bebob/bebob.h b/sound/firewire/bebob/bebob.h index 2775a73..04842b2 100644 --- a/sound/firewire/bebob/bebob.h +++ b/sound/firewire/bebob/bebob.h @@ -32,6 +32,8 @@ #include <sound/info.h> #include <sound/control.h> #include <sound/rawmidi.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h>
#include "../packets-buffer.h" #include "../iso-resources.h" @@ -156,6 +158,8 @@ int snd_bebob_create_control_devices(struct snd_bebob *bebob);
int snd_bebob_create_midi_devices(struct snd_bebob *bebob);
+int snd_bebob_create_pcm_devices(struct snd_bebob *bebob); + #define SND_BEBOB_DEV_ENTRY(vendor, model) \ { \ .match_flags = IEEE1394_MATCH_VENDOR_ID | \ diff --git a/sound/firewire/bebob/bebob_pcm.c b/sound/firewire/bebob/bebob_pcm.c new file mode 100644 index 0000000..04913b2 --- /dev/null +++ b/sound/firewire/bebob/bebob_pcm.c @@ -0,0 +1,411 @@ +/* + * bebob_pcm.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" + +static int +hw_rule_rate(struct snd_pcm_hw_params *params, struct snd_pcm_hw_rule *rule, + struct snd_bebob *bebob, + struct snd_bebob_stream_formation *formations) +{ + struct snd_interval *r = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + const struct snd_interval *c = + hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_CHANNELS); + struct snd_interval t = { + .min = UINT_MAX, .max = 0, .integer = 1 + }; + unsigned int i; + + for (i = 0; i < SND_BEBOB_STRM_FMT_ENTRIES; i++) { + /* entry is invalid */ + if (formations[i].pcm == 0) + continue; + + if (!snd_interval_test(c, formations[i].pcm)) + continue; + + t.min = min(t.min, snd_bebob_rate_table[i]); + t.max = max(t.max, snd_bebob_rate_table[i]); + + } + return snd_interval_refine(r, &t); +} + +static int +hw_rule_channels(struct snd_pcm_hw_params *params, struct snd_pcm_hw_rule *rule, + struct snd_bebob *bebob, + struct snd_bebob_stream_formation *formations) +{ + struct snd_interval *c = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + const struct snd_interval *r = + hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval t = { + .min = UINT_MAX, .max = 0, .integer = 1 + }; + + unsigned int i; + + for (i = 0; i < SND_BEBOB_STRM_FMT_ENTRIES; i++) { + /* entry is invalid */ + if (formations[i].pcm == 0) + continue; + + if (!snd_interval_test(r, snd_bebob_rate_table[i])) + continue; + + t.min = min(t.min, formations[i].pcm); + t.max = max(t.max, formations[i].pcm); + } + + return snd_interval_refine(c, &t); +} + +static inline int +hw_rule_capture_rate(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_bebob *bebob = rule->private; + return hw_rule_rate(params, rule, bebob, + bebob->tx_stream_formations); +} + +static inline int +hw_rule_playback_rate(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_bebob *bebob = rule->private; + return hw_rule_rate(params, rule, bebob, + bebob->rx_stream_formations); +} + +static inline int +hw_rule_capture_channels(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_bebob *bebob = rule->private; + return hw_rule_channels(params, rule, bebob, + bebob->tx_stream_formations); +} + +static inline int +hw_rule_playback_channels(struct snd_pcm_hw_params *params, + struct snd_pcm_hw_rule *rule) +{ + struct snd_bebob *bebob = rule->private; + return hw_rule_channels(params, rule, bebob, + bebob->rx_stream_formations); +} + +static void +prepare_channels(struct snd_pcm_hardware *hw, + struct snd_bebob_stream_formation *formations) +{ + unsigned int i; + + for (i = 0; i < SND_BEBOB_STRM_FMT_ENTRIES; i++) { + /* entry has no PCM channels */ + if (formations[i].pcm == 0) + continue; + + hw->channels_min = min(hw->channels_min, formations[i].pcm); + hw->channels_max = max(hw->channels_max, formations[i].pcm); + } + + return; +} + +static void +prepare_rates(struct snd_pcm_hardware *hw, + struct snd_bebob_stream_formation *formations) +{ + unsigned int i; + + for (i = 0; i < SND_BEBOB_STRM_FMT_ENTRIES; i++) { + /* entry has no PCM channels */ + if (formations[i].pcm == 0) + continue; + + hw->rate_min = min(hw->rate_min, snd_bebob_rate_table[i]); + hw->rate_max = max(hw->rate_max, snd_bebob_rate_table[i]); + hw->rates |= snd_pcm_rate_to_rate_bit(snd_bebob_rate_table[i]); + } + + return; +} + +static int +pcm_init_hw_params(struct snd_bebob *bebob, + struct snd_pcm_substream *substream) +{ + int err; + + static const struct snd_pcm_hardware hw = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_BATCH | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_SYNC_START | + SNDRV_PCM_INFO_FIFO_IN_FRAMES | + SNDRV_PCM_INFO_JOINT_DUPLEX | + /* for Open Sound System compatibility */ + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_BLOCK_TRANSFER, + /* set up later */ + .rates = 0, + .rate_min = UINT_MAX, + .rate_max = 0, + /* set up later */ + .channels_min = UINT_MAX, + .channels_max = 0, + .buffer_bytes_max = 1024 * 1024 * 1024, + .period_bytes_min = 256, + .period_bytes_max = 1024 * 1024 * 1024 / 2, + .periods_min = 2, + .periods_max = 32, + .fifo_size = 0, + }; + + substream->runtime->hw = hw; + substream->runtime->delay = substream->runtime->hw.fifo_size; + + /* add rule between channels and sampling rate */ + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + prepare_rates(&substream->runtime->hw, bebob->tx_stream_formations); + prepare_channels(&substream->runtime->hw, bebob->tx_stream_formations); + substream->runtime->hw.formats = SNDRV_PCM_FMTBIT_S32_LE; + snd_pcm_hw_rule_add(substream->runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + hw_rule_capture_channels, bebob, + SNDRV_PCM_HW_PARAM_RATE, -1); + snd_pcm_hw_rule_add(substream->runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + hw_rule_capture_rate, bebob, + SNDRV_PCM_HW_PARAM_CHANNELS, -1); + } else { + prepare_rates(&substream->runtime->hw, bebob->rx_stream_formations); + prepare_channels(&substream->runtime->hw, bebob->rx_stream_formations); + substream->runtime->hw.formats = AMDTP_OUT_PCM_FORMAT_BITS; + snd_pcm_hw_rule_add(substream->runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + hw_rule_playback_channels, bebob, + SNDRV_PCM_HW_PARAM_RATE, -1); + snd_pcm_hw_rule_add(substream->runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + hw_rule_playback_rate, bebob, + SNDRV_PCM_HW_PARAM_CHANNELS, -1); + } + + /* AM824 in IEC 61883-6 can deliver 24bit data */ + err = snd_pcm_hw_constraint_msbits(substream->runtime, 0, 32, 24); + if (err < 0) + goto end; + + /* + * AMDTP functionality in firewire-lib require periods to be aligned to + * 16 bit, or 24bit inner 32bit. + */ + err = snd_pcm_hw_constraint_step(substream->runtime, 0, + SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 32); + if (err < 0) + goto end; + + /* time for period constraint */ + err = snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_PERIOD_TIME, + 500, UINT_MAX); + if (err < 0) + goto end; + + err = 0; +end: + return err; +} + +static int +pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_bebob *bebob = substream->private_data; + unsigned int sampling_rate; + int err; + + err = pcm_init_hw_params(bebob, substream); + if (err < 0) + goto end; + + if (amdtp_stream_pcm_running(&bebob->tx_stream) || + amdtp_stream_pcm_running(&bebob->rx_stream)) { + err = snd_bebob_stream_get_rate(bebob, &sampling_rate); + if (err < 0) + goto end; + + substream->runtime->hw.rate_min = sampling_rate; + substream->runtime->hw.rate_max = sampling_rate; + } + + snd_pcm_set_sync(substream); + +end: + return err; +} + +static int +pcm_close(struct snd_pcm_substream *substream) +{ + return 0; +} + +static int +pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + return snd_pcm_lib_alloc_vmalloc_buffer(substream, + params_buffer_bytes(hw_params)); +} + +static int +pcm_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_bebob *bebob = substream->private_data; + + snd_bebob_stream_stop_duplex(bebob); + + return snd_pcm_lib_free_vmalloc_buffer(substream); +} + +static int +pcm_capture_prepare(struct snd_pcm_substream *substream) +{ + struct snd_bebob *bebob = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + + err = snd_bebob_stream_start_duplex(bebob, &bebob->tx_stream, + runtime->rate); + if (err < 0) + goto end; + + amdtp_stream_set_pcm_format(&bebob->tx_stream, runtime->format); + amdtp_stream_pcm_prepare(&bebob->tx_stream); +end: + return err; +} +static int +pcm_playback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_bebob *bebob = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + + err = snd_bebob_stream_start_duplex(bebob, &bebob->rx_stream, + runtime->rate); + if (err < 0) + goto end; + + amdtp_stream_set_pcm_format(&bebob->rx_stream, runtime->format); + amdtp_stream_pcm_prepare(&bebob->rx_stream); +end: + return err; +} + +static int +pcm_capture_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_bebob *bebob = substream->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + amdtp_stream_pcm_trigger(&bebob->tx_stream, substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + amdtp_stream_pcm_trigger(&bebob->tx_stream, NULL); + break; + default: + return -EINVAL; + } + + return 0; +} +static int +pcm_playback_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_bebob *bebob = substream->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + amdtp_stream_pcm_trigger(&bebob->rx_stream, substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + amdtp_stream_pcm_trigger(&bebob->rx_stream, NULL); + break; + default: + return -EINVAL; + } + + return 0; +} + +static snd_pcm_uframes_t +pcm_capture_pointer(struct snd_pcm_substream *sbstrm) +{ + struct snd_bebob *bebob = sbstrm->private_data; + return amdtp_stream_pcm_pointer(&bebob->tx_stream); +} +static snd_pcm_uframes_t +pcm_playback_pointer(struct snd_pcm_substream *sbstrm) +{ + struct snd_bebob *bebob = sbstrm->private_data; + return amdtp_stream_pcm_pointer(&bebob->rx_stream); +} + +static struct snd_pcm_ops pcm_capture_ops = { + .open = pcm_open, + .close = pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = pcm_hw_params, + .hw_free = pcm_hw_free, + .prepare = pcm_capture_prepare, + .trigger = pcm_capture_trigger, + .pointer = pcm_capture_pointer, + .page = snd_pcm_lib_get_vmalloc_page, +}; +static struct snd_pcm_ops pcm_playback_ops = { + .open = pcm_open, + .close = pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = pcm_hw_params, + .hw_free = pcm_hw_free, + .prepare = pcm_playback_prepare, + .trigger = pcm_playback_trigger, + .pointer = pcm_playback_pointer, + .page = snd_pcm_lib_get_vmalloc_page, + .mmap = snd_pcm_lib_mmap_vmalloc, +}; + +int snd_bebob_create_pcm_devices(struct snd_bebob *bebob) +{ + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(bebob->card, bebob->card->driver, 0, 1, 1, &pcm); + if (err < 0) + goto end; + + pcm->private_data = bebob; + snprintf(pcm->name, sizeof(pcm->name), "%s PCM", bebob->card->shortname); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &pcm_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &pcm_capture_ops); + +end: + return err; +}
This is for mixer/control application in user land. The application can lock/unlock kernel streaming or get notification at starting kernel streaming to use hwdep interface.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- include/uapi/sound/asound.h | 3 +- include/uapi/sound/firewire.h | 1 + sound/firewire/Kconfig | 1 + sound/firewire/bebob/Makefile | 2 +- sound/firewire/bebob/bebob.c | 5 + sound/firewire/bebob/bebob.h | 13 +++ sound/firewire/bebob/bebob_hwdep.c | 207 ++++++++++++++++++++++++++++++++++++ sound/firewire/bebob/bebob_midi.c | 10 ++ sound/firewire/bebob/bebob_pcm.c | 14 ++- sound/firewire/bebob/bebob_stream.c | 39 +++++++ 10 files changed, 290 insertions(+), 5 deletions(-) create mode 100644 sound/firewire/bebob/bebob_hwdep.c
diff --git a/include/uapi/sound/asound.h b/include/uapi/sound/asound.h index 9fc6219..d8bc723 100644 --- a/include/uapi/sound/asound.h +++ b/include/uapi/sound/asound.h @@ -94,9 +94,10 @@ enum { SNDRV_HWDEP_IFACE_HDA, /* HD-audio */ SNDRV_HWDEP_IFACE_USB_STREAM, /* direct access to usb stream */ SNDRV_HWDEP_IFACE_FW_DICE, /* TC DICE FireWire device */ + SNDRV_HWDEP_IFACE_FW_BEBOB, /* BridgeCo BeBoB based Device */
/* Don't forget to change the following: */ - SNDRV_HWDEP_IFACE_LAST = SNDRV_HWDEP_IFACE_FW_DICE + SNDRV_HWDEP_IFACE_LAST = SNDRV_HWDEP_IFACE_FW_BEBOB };
struct snd_hwdep_info { diff --git a/include/uapi/sound/firewire.h b/include/uapi/sound/firewire.h index 59f5961..c600b143 100644 --- a/include/uapi/sound/firewire.h +++ b/include/uapi/sound/firewire.h @@ -34,6 +34,7 @@ union snd_firewire_event { #define SNDRV_FIREWIRE_IOCTL_UNLOCK _IO('H', 0xfa)
#define SNDRV_FIREWIRE_TYPE_DICE 1 +#define SNDRV_FIREWIRE_TYPE_BEBOB 2 /* Fireworks, AV/C, RME, MOTU, ... */
struct snd_firewire_get_info { diff --git a/sound/firewire/Kconfig b/sound/firewire/Kconfig index 7641772..c46578e 100644 --- a/sound/firewire/Kconfig +++ b/sound/firewire/Kconfig @@ -65,6 +65,7 @@ config SND_BEBOB tristate "BridgeCo BeBoB chipset support" select SND_RAWMIDI select SND_PCM + select SND_HWDEP select SND_FIREWIRE_LIB help Say Y here to include support for FireWire devices based diff --git a/sound/firewire/bebob/Makefile b/sound/firewire/bebob/Makefile index 30e79e6..81e14fa 100644 --- a/sound/firewire/bebob/Makefile +++ b/sound/firewire/bebob/Makefile @@ -1,4 +1,4 @@ snd-bebob-objs := bebob_command.o bebob_stream.o bebob_proc.o bebob_control.o \ - bebob_midi.o bebob_pcm.o \ + bebob_midi.o bebob_pcm.o bebob_hwdep.o \ bebob.o obj-m += snd-bebob.o diff --git a/sound/firewire/bebob/bebob.c b/sound/firewire/bebob/bebob.c index 2fdd14b..219eac7 100644 --- a/sound/firewire/bebob/bebob.c +++ b/sound/firewire/bebob/bebob.c @@ -171,12 +171,17 @@ snd_bebob_probe(struct fw_unit *unit, bebob->card_index = -1; mutex_init(&bebob->mutex); spin_lock_init(&bebob->lock); + init_waitqueue_head(&bebob->hwdep_wait);
/* name device with communication */ err = name_device(bebob, entry->vendor_id); if (err < 0) goto error;
+ err = snd_bebob_create_hwdep_device(bebob); + if (err < 0) + goto error; + err = snd_bebob_create_pcm_devices(bebob); if (err < 0) goto error; diff --git a/sound/firewire/bebob/bebob.h b/sound/firewire/bebob/bebob.h index 04842b2..3950b02 100644 --- a/sound/firewire/bebob/bebob.h +++ b/sound/firewire/bebob/bebob.h @@ -34,6 +34,8 @@ #include <sound/rawmidi.h> #include <sound/pcm.h> #include <sound/pcm_params.h> +#include <sound/firewire.h> +#include <sound/hwdep.h>
#include "../packets-buffer.h" #include "../iso-resources.h" @@ -79,6 +81,11 @@ struct snd_bebob { rx_stream_formations[SND_BEBOB_STRM_FMT_ENTRIES];
struct snd_ctl_elem_id *ctl_id_freq; + + /* for uapi */ + int dev_lock_count; + bool dev_lock_changed; + wait_queue_head_t hwdep_wait; };
static inline int @@ -152,6 +159,10 @@ int snd_bebob_stream_stop_duplex(struct snd_bebob *bebob); void snd_bebob_stream_update_duplex(struct snd_bebob *bebob); void snd_bebob_stream_destroy_duplex(struct snd_bebob *bebob);
+void snd_bebob_stream_lock_changed(struct snd_bebob *bebob); +int snd_bebob_stream_lock_try(struct snd_bebob *bebob); +void snd_bebob_stream_lock_release(struct snd_bebob *bebob); + void snd_bebob_proc_init(struct snd_bebob *bebob);
int snd_bebob_create_control_devices(struct snd_bebob *bebob); @@ -160,6 +171,8 @@ int snd_bebob_create_midi_devices(struct snd_bebob *bebob);
int snd_bebob_create_pcm_devices(struct snd_bebob *bebob);
+int snd_bebob_create_hwdep_device(struct snd_bebob *bebob); + #define SND_BEBOB_DEV_ENTRY(vendor, model) \ { \ .match_flags = IEEE1394_MATCH_VENDOR_ID | \ diff --git a/sound/firewire/bebob/bebob_hwdep.c b/sound/firewire/bebob/bebob_hwdep.c new file mode 100644 index 0000000..be423b7 --- /dev/null +++ b/sound/firewire/bebob/bebob_hwdep.c @@ -0,0 +1,207 @@ +/* + * fireworks_hwdep.c - a part of driver for Fireworks 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/. + */ + +/* + * This codes give three functionality. + * + * 1.get firewire node reference + * 2.lock/unlock stream + * dice driver implements the same functionality. + */ + +#include "bebob.h" + +static long +hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count, + loff_t *offset) +{ + struct snd_bebob *bebob = hwdep->private_data; + DEFINE_WAIT(wait); + union snd_firewire_event event; + + spin_lock_irq(&bebob->lock); + + while (!bebob->dev_lock_changed) { + prepare_to_wait(&bebob->hwdep_wait, &wait, TASK_INTERRUPTIBLE); + spin_unlock_irq(&bebob->lock); + schedule(); + finish_wait(&bebob->hwdep_wait, &wait); + if (signal_pending(current)) + return -ERESTARTSYS; + spin_lock_irq(&bebob->lock); + } + + memset(&event, 0, sizeof(event)); + if (bebob->dev_lock_changed) { + event.lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS; + event.lock_status.status = (bebob->dev_lock_count > 0); + bebob->dev_lock_changed = false; + + count = min(count, (long)sizeof(event.lock_status)); + } + + spin_unlock_irq(&bebob->lock); + + if (copy_to_user(buf, &event, count)) + return -EFAULT; + + return count; +} + +static unsigned int +hwdep_poll(struct snd_hwdep *hwdep, struct file *file, poll_table *wait) +{ + struct snd_bebob *bebob = hwdep->private_data; + unsigned int events; + + poll_wait(file, &bebob->hwdep_wait, wait); + + spin_lock_irq(&bebob->lock); + if (bebob->dev_lock_changed) + events = POLLIN | POLLRDNORM; + else + events = 0; + spin_unlock_irq(&bebob->lock); + + return events; +} + +static int +hwdep_get_info(struct snd_bebob *bebob, void __user *arg) +{ + struct fw_device *dev = fw_parent_device(bebob->unit); + struct snd_firewire_get_info info; + + memset(&info, 0, sizeof(info)); + info.type = SNDRV_FIREWIRE_TYPE_BEBOB; + info.card = dev->card->index; + *(__be32 *)&info.guid[0] = cpu_to_be32(dev->config_rom[3]); + *(__be32 *)&info.guid[4] = cpu_to_be32(dev->config_rom[4]); + strlcpy(info.device_name, dev_name(&dev->device), + sizeof(info.device_name)); + + if (copy_to_user(arg, &info, sizeof(info))) + return -EFAULT; + + return 0; +} + +static int +hwdep_lock(struct snd_bebob *bebob) +{ + int err; + + spin_lock_irq(&bebob->lock); + + if (bebob->dev_lock_count == 0) { + bebob->dev_lock_count = -1; + err = 0; + } else + err = -EBUSY; + + spin_unlock_irq(&bebob->lock); + + return err; +} + +static int +hwdep_unlock(struct snd_bebob *bebob) +{ + int err; + + spin_lock_irq(&bebob->lock); + + if (bebob->dev_lock_count == -1) { + bebob->dev_lock_count = 0; + err = 0; + } else + err = -EBADFD; + + spin_unlock_irq(&bebob->lock); + + return err; +} + +static int +hwdep_release(struct snd_hwdep *hwdep, struct file *file) +{ + struct snd_bebob *bebob = hwdep->private_data; + + spin_lock_irq(&bebob->lock); + if (bebob->dev_lock_count == -1) + bebob->dev_lock_count = 0; + spin_unlock_irq(&bebob->lock); + + return 0; +} + +static int +hwdep_ioctl(struct snd_hwdep *hwdep, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct snd_bebob *bebob = hwdep->private_data; + + switch (cmd) { + case SNDRV_FIREWIRE_IOCTL_GET_INFO: + return hwdep_get_info(bebob, (void __user *)arg); + case SNDRV_FIREWIRE_IOCTL_LOCK: + return hwdep_lock(bebob); + case SNDRV_FIREWIRE_IOCTL_UNLOCK: + return hwdep_unlock(bebob); + default: + return -ENOIOCTLCMD; + } +} + +#ifdef CONFIG_COMPAT +static int +hwdep_compat_ioctl(struct snd_hwdep *hwdep, struct file *file, + unsigned int cmd, unsigned long arg) +{ + return hwdep_ioctl(hwdep, file, cmd, + (unsigned long)compat_ptr(arg)); +} +#else +#define hwdep_compat_ioctl NULL +#endif + +static const struct snd_hwdep_ops hwdep_ops = { + .read = hwdep_read, + .release = hwdep_release, + .poll = hwdep_poll, + .ioctl = hwdep_ioctl, + .ioctl_compat = hwdep_compat_ioctl, +}; + +int snd_bebob_create_hwdep_device(struct snd_bebob *bebob) +{ + struct snd_hwdep *hwdep; + int err; + + err = snd_hwdep_new(bebob->card, "BeBoB", 0, &hwdep); + if (err < 0) + goto end; + strcpy(hwdep->name, "BeBoB"); + hwdep->iface = SNDRV_HWDEP_IFACE_FW_BEBOB; + hwdep->ops = hwdep_ops; + hwdep->private_data = bebob; + hwdep->exclusive = true; +end: + return err; +} + diff --git a/sound/firewire/bebob/bebob_midi.c b/sound/firewire/bebob/bebob_midi.c index 56c7115..a5a4028 100644 --- a/sound/firewire/bebob/bebob_midi.c +++ b/sound/firewire/bebob/bebob_midi.c @@ -26,6 +26,10 @@ static int midi_capture_open(struct snd_rawmidi_substream *substream) struct snd_bebob *bebob = substream->rmidi->private_data; int err;
+ err = snd_bebob_stream_lock_try(bebob); + if (err < 0) + goto end; + err = snd_bebob_stream_start_duplex(bebob, &bebob->tx_stream, 0); if (err < 0) goto end; @@ -40,6 +44,10 @@ static int midi_playback_open(struct snd_rawmidi_substream *substream) struct snd_bebob *bebob = substream->rmidi->private_data; int err;
+ err = snd_bebob_stream_lock_try(bebob); + if (err < 0) + goto end; + err = snd_bebob_stream_start_duplex(bebob, &bebob->rx_stream, 0); if (err < 0) goto end; @@ -55,6 +63,7 @@ static int midi_capture_close(struct snd_rawmidi_substream *substream)
amdtp_stream_midi_remove(&bebob->tx_stream, substream); snd_bebob_stream_stop_duplex(bebob); + snd_bebob_stream_lock_release(bebob);
return 0; } @@ -65,6 +74,7 @@ static int midi_playback_close(struct snd_rawmidi_substream *substream)
amdtp_stream_midi_remove(&bebob->rx_stream, substream); snd_bebob_stream_stop_duplex(bebob); + snd_bebob_stream_lock_release(bebob);
return 0; } diff --git a/sound/firewire/bebob/bebob_pcm.c b/sound/firewire/bebob/bebob_pcm.c index 04913b2..c7cca80 100644 --- a/sound/firewire/bebob/bebob_pcm.c +++ b/sound/firewire/bebob/bebob_pcm.c @@ -239,29 +239,37 @@ pcm_open(struct snd_pcm_substream *substream) unsigned int sampling_rate; int err;
- err = pcm_init_hw_params(bebob, substream); + err = snd_bebob_stream_lock_try(bebob); if (err < 0) goto end;
+ err = pcm_init_hw_params(bebob, substream); + if (err < 0) + goto err_locked; + if (amdtp_stream_pcm_running(&bebob->tx_stream) || amdtp_stream_pcm_running(&bebob->rx_stream)) { err = snd_bebob_stream_get_rate(bebob, &sampling_rate); if (err < 0) - goto end; + goto err_locked;
substream->runtime->hw.rate_min = sampling_rate; substream->runtime->hw.rate_max = sampling_rate; }
snd_pcm_set_sync(substream); - end: return err; +err_locked: + snd_bebob_stream_lock_release(bebob); + return err; }
static int pcm_close(struct snd_pcm_substream *substream) { + struct snd_bebob *bebob = substream->private_data; + snd_bebob_stream_lock_release(bebob); return 0; }
diff --git a/sound/firewire/bebob/bebob_stream.c b/sound/firewire/bebob/bebob_stream.c index 773dffb..f2527bf 100644 --- a/sound/firewire/bebob/bebob_stream.c +++ b/sound/firewire/bebob/bebob_stream.c @@ -666,3 +666,42 @@ int snd_bebob_stream_discover(struct snd_bebob *bebob) end: return err; } + +void snd_bebob_stream_lock_changed(struct snd_bebob *bebob) +{ + bebob->dev_lock_changed = true; + wake_up(&bebob->hwdep_wait); +} + +int snd_bebob_stream_lock_try(struct snd_bebob *bebob) +{ + int err; + + spin_lock_irq(&bebob->lock); + + /* user land lock this */ + if (bebob->dev_lock_count < 0) { + err = -EBUSY; + goto end; + } + + /* this is the first time */ + if (bebob->dev_lock_count++ == 0) + snd_bebob_stream_lock_changed(bebob); + err = 0; +end: + spin_unlock_irq(&bebob->lock); + return err; +} + +void snd_bebob_stream_lock_release(struct snd_bebob *bebob) +{ + spin_lock_irq(&bebob->lock); + + if (WARN_ON(bebob->dev_lock_count <= 0)) + goto end; + if (--bebob->dev_lock_count == 0) + snd_bebob_stream_lock_changed(bebob); +end: + spin_unlock_irq(&bebob->lock); +}
This commit is for some devices designed for semi-professional use. They have some model specific functionality and quirks.
Many functionality should be implemented in user land. Then this commit adds functionality related to stream such as sampling frequency or clock source. And some device need cue to load firmware. This commit adds this mechanism. For debugging, this commit adds the functionality to get metering information if it's available.
To help these functionalities, this commit adds some AV/C commands in 'AV/C Audio Subunit Specification (1394TA)' and 'Connection and Compatibility Management(1394TA)'.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/bebob/bebob.c | 40 ++++--- sound/firewire/bebob/bebob.h | 48 ++++++++- sound/firewire/bebob/bebob_command.c | 160 ++++++++++++++++++++++++++++ sound/firewire/bebob/bebob_control.c | 198 +++++++++++++++++++++++++++++++++-- sound/firewire/bebob/bebob_pcm.c | 3 +- sound/firewire/bebob/bebob_proc.c | 45 +++++++- sound/firewire/bebob/bebob_stream.c | 7 +- 7 files changed, 470 insertions(+), 31 deletions(-)
diff --git a/sound/firewire/bebob/bebob.c b/sound/firewire/bebob/bebob.c index 219eac7..7c3f1b3 100644 --- a/sound/firewire/bebob/bebob.c +++ b/sound/firewire/bebob/bebob.c @@ -141,6 +141,7 @@ snd_bebob_probe(struct fw_unit *unit, { struct snd_card *card; struct snd_bebob *bebob; + const struct snd_bebob_spec *spec; unsigned int card_index; int err;
@@ -156,6 +157,8 @@ snd_bebob_probe(struct fw_unit *unit, goto end; }
+ spec = (const struct snd_bebob_spec *)entry->driver_data; + /* create card */ err = snd_card_create(index[card_index], id[card_index], THIS_MODULE, sizeof(struct snd_bebob), &card); @@ -169,6 +172,7 @@ snd_bebob_probe(struct fw_unit *unit, bebob->device = fw_parent_device(unit); bebob->unit = unit; bebob->card_index = -1; + bebob->spec = spec; mutex_init(&bebob->mutex); spin_lock_init(&bebob->lock); init_waitqueue_head(&bebob->hwdep_wait); @@ -246,33 +250,43 @@ static void snd_bebob_remove(struct fw_unit *unit) snd_card_free_when_closed(bebob->card); }
+struct snd_bebob_clock_spec normal_clk_spec = { + .get_freq = &snd_bebob_stream_get_rate, + .set_freq = &snd_bebob_stream_set_rate +}; +static const struct snd_bebob_spec spec_normal = { + .load = NULL, + .clock = &normal_clk_spec, + .meter = NULL +}; + static const struct ieee1394_device_id snd_bebob_id_table[] = { /* Edirol, FA-66 */ - SND_BEBOB_DEV_ENTRY(VEN_EDIROL, 0x00010049), + SND_BEBOB_DEV_ENTRY(VEN_EDIROL, 0x00010049, spec_normal), /* Edirol, FA-101 */ - SND_BEBOB_DEV_ENTRY(VEN_EDIROL, 0x00010048), + SND_BEBOB_DEV_ENTRY(VEN_EDIROL, 0x00010048, spec_normal), /* BridgeCo, RDAudio1 */ - SND_BEBOB_DEV_ENTRY(VEN_BRIDGECO, 0x00010048), + SND_BEBOB_DEV_ENTRY(VEN_BRIDGECO, 0x00010048, spec_normal), /* BridgeCo, Audio5 */ - SND_BEBOB_DEV_ENTRY(VEN_BRIDGECO, 0x00010049), + SND_BEBOB_DEV_ENTRY(VEN_BRIDGECO, 0x00010049, spec_normal), /* Mackie, OnyxFirewire */ - SND_BEBOB_DEV_ENTRY(VEN_MACKIE, 0x00010065), + SND_BEBOB_DEV_ENTRY(VEN_MACKIE, 0x00010065, spec_normal), /* Mackie, OnyxFirewire */ - SND_BEBOB_DEV_ENTRY(VEN_MACKIE, 0x00010067), + SND_BEBOB_DEV_ENTRY(VEN_MACKIE, 0x00010067, spec_normal), /* Tascam, IF-FW/DM */ - SND_BEBOB_DEV_ENTRY(VEN_TASCOM, 0x00010067), + SND_BEBOB_DEV_ENTRY(VEN_TASCOM, 0x00010067, spec_normal), /* Behringer, X32 */ - SND_BEBOB_DEV_ENTRY(VEN_BEHRINGER, 0x00000006), + SND_BEBOB_DEV_ENTRY(VEN_BEHRINGER, 0x00000006, spec_normal), /* ApogeeElectronics, Rosetta200 */ - SND_BEBOB_DEV_ENTRY(VEN_APOGEE, 0x00010048), + SND_BEBOB_DEV_ENTRY(VEN_APOGEE, 0x00010048, spec_normal), /* ESI, Quatafire610 */ - SND_BEBOB_DEV_ENTRY(VEN_ESI, 0x00010064), + SND_BEBOB_DEV_ENTRY(VEN_ESI, 0x00010064, spec_normal), /* AcousticReality, eARMasterOne */ - SND_BEBOB_DEV_ENTRY(VEN_ACOUSTIC, 0x00000002), + SND_BEBOB_DEV_ENTRY(VEN_ACOUSTIC, 0x00000002, spec_normal), /* CME, MatrixKFW */ - SND_BEBOB_DEV_ENTRY(VEN_CME, 0x00030000), + SND_BEBOB_DEV_ENTRY(VEN_CME, 0x00030000, spec_normal), /* Phonic, HB24U */ - SND_BEBOB_DEV_ENTRY(VEN_PHONIC, 0x00000000), + SND_BEBOB_DEV_ENTRY(VEN_PHONIC, 0x00000000, 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 3950b02..b35fc52 100644 --- a/sound/firewire/bebob/bebob.h +++ b/sound/firewire/bebob/bebob.h @@ -58,6 +58,33 @@ struct snd_bebob_stream_formation { /* this is a lookup table for index of stream formations */ extern const unsigned int snd_bebob_rate_table[SND_BEBOB_STRM_FMT_ENTRIES];
+/* device specific operations */ +#define SND_BEBOB_CLOCK_INTERNAL "Internal" +struct snd_bebob_clock_spec { + unsigned int num; + char **labels; + int (*get_src)(struct snd_bebob *bebob, unsigned int *id); + int (*set_src)(struct snd_bebob *bebob, unsigned int id); + int (*get_freq)(struct snd_bebob *bebob, unsigned int *rate); + int (*set_freq)(struct snd_bebob *bebob, unsigned int rate); + int (*synced)(struct snd_bebob *bebob, bool *synced); + /* private */ + struct snd_ctl_elem_id *ctl_id_src; + struct snd_ctl_elem_id *ctl_id_freq; + struct snd_ctl_elem_id *ctl_id_synced; +}; +struct snd_bebob_meter_spec { + unsigned int num; + char **labels; + int (*get)(struct snd_bebob *bebob, u32 *target, unsigned int size); +}; +struct snd_bebob_spec { + int (*load)(struct fw_unit *unit, + const struct ieee1394_device_id *entry); + struct snd_bebob_clock_spec *clock; + struct snd_bebob_meter_spec *meter; +}; + struct snd_bebob { struct snd_card *card; struct fw_device *device; @@ -67,6 +94,8 @@ struct snd_bebob { struct mutex mutex; spinlock_t lock;
+ const struct snd_bebob_spec *spec; + unsigned int midi_input_ports; unsigned int midi_output_ports;
@@ -80,8 +109,6 @@ struct snd_bebob { struct snd_bebob_stream_formation rx_stream_formations[SND_BEBOB_STRM_FMT_ENTRIES];
- struct snd_ctl_elem_id *ctl_id_freq; - /* for uapi */ int dev_lock_count; bool dev_lock_changed; @@ -104,6 +131,20 @@ snd_bebob_read_quad(struct snd_bebob *bebob, u64 addr, void *buf, int size) buf, size, 0); }
+/* AV/C Audio Subunit Specification 1.0 (1394TA) */ +int avc_audio_set_selector(struct fw_unit *unit, unsigned int subunit_id, + unsigned int fb_id, unsigned int num); +int avc_audio_get_selector(struct fw_unit *unit,unsigned int subunit_id, + unsigned int fb_id, unsigned int *num); + +/* Connection and Compatibility Management 1.0 (1394TA) */ +int avc_ccm_get_sig_src(struct fw_unit *unit, + unsigned int *src_stype, unsigned int *src_sid, unsigned int *src_pid, + unsigned int dst_stype, unsigned int dst_sid, unsigned int dst_pid); +int avc_ccm_set_sig_src(struct fw_unit *unit, + unsigned int src_stype, unsigned int src_sid, unsigned int src_pid, + unsigned int dst_stype, unsigned int dst_sid, unsigned int dst_pid); + /* AVC command extensions, AV/C Unit and Subunit, Revision 17 (BridgeCo.) */ enum snd_bebob_plug_dir { SND_BEBOB_PLUG_DIR_IN = 0x00, @@ -173,12 +214,13 @@ int snd_bebob_create_pcm_devices(struct snd_bebob *bebob);
int snd_bebob_create_hwdep_device(struct snd_bebob *bebob);
-#define SND_BEBOB_DEV_ENTRY(vendor, model) \ +#define SND_BEBOB_DEV_ENTRY(vendor, model, private_data) \ { \ .match_flags = IEEE1394_MATCH_VENDOR_ID | \ IEEE1394_MATCH_MODEL_ID, \ .vendor_id = vendor, \ .model_id = model, \ + .driver_data = (kernel_ulong_t)&private_data \ }
#endif diff --git a/sound/firewire/bebob/bebob_command.c b/sound/firewire/bebob/bebob_command.c index 98b7668..1bf3e5e 100644 --- a/sound/firewire/bebob/bebob_command.c +++ b/sound/firewire/bebob/bebob_command.c @@ -20,6 +20,166 @@ #define BEBOB_COMMAND_MAX_TRIAL 3 #define BEBOB_COMMAND_WAIT_MSEC 100
+int avc_audio_set_selector(struct fw_unit *unit, unsigned int subunit_id, + unsigned int fb_id, unsigned int num) +{ + u8 *buf; + int err; + + buf = kzalloc(12, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + buf[0] = 0x00; /* AV/C CONTROL */ + buf[1] = 0x08 | (0x07 & subunit_id); /* AUDIO SUBUNIT ID */ + buf[2] = 0xb8; /* FUNCTION BLOCK */ + buf[3] = 0x80; /* type is 'selector'*/ + buf[4] = 0xff & fb_id; /* function block id */ + buf[5] = 0x10; /* control attribute is CURRENT */ + buf[6] = 0x02; /* selector length is 2 */ + buf[7] = 0xff & num; /* input function block plug number */ + buf[8] = 0x01; /* control selector is SELECTOR_CONTROL */ + + /* do transaction and check buf[1-8] are the same against command */ + err = fcp_avc_transaction(unit, buf, 12, buf, 12, + BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | + BIT(6) | BIT(7) | BIT(8)); + if (err < 0) + goto end; + if ((err < 6) || (buf[0] != 0x09)) { + dev_err(&unit->device, + "failed to set selector %d: 0x%02X\n", + fb_id, buf[0]); + err = -EIO; + goto end; + } + + err = 0; +end: + kfree(buf); + return err; +} + +int avc_audio_get_selector(struct fw_unit *unit, unsigned int subunit_id, + unsigned int fb_id, unsigned int *num) +{ + u8 *buf; + int err; + + buf = kzalloc(12, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + buf[0] = 0x01; /* AV/C STATUS */ + buf[1] = 0x08 | (0x07 & subunit_id); /* AUDIO SUBUNIT ID */ + buf[2] = 0xb8; /* FUNCTION BLOCK */ + buf[3] = 0x80; /* type is 'selector'*/ + buf[4] = 0xff & fb_id; /* function block id */ + buf[5] = 0x10; /* control attribute is CURRENT */ + buf[6] = 0x02; /* selector length is 2 */ + buf[7] = 0xff; /* input function block plug number */ + buf[8] = 0x01; /* control selector is SELECTOR_CONTROL */ + + /* do transaction and check buf[1-6,8] are the same against command */ + err = fcp_avc_transaction(unit, buf, 12, buf, 12, + BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) | + BIT(6) | BIT(8)); + if (err < 0) + goto end; + if ((err < 6) || (buf[0] != 0x0c)) { + dev_err(&unit->device, + "failed to get selector %d: 0x%02X\n", + fb_id, buf[0]); + err = -EIO; + goto end; + } + + *num = buf[7]; + err = 0; +end: + kfree(buf); + return err; +} + +int avc_ccm_get_sig_src(struct fw_unit *unit, + unsigned int *src_stype, unsigned int *src_sid, unsigned int *src_pid, + unsigned int dst_stype, unsigned int dst_sid, unsigned int dst_pid) +{ + int err; + u8 *buf; + + buf = kzalloc(8, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + buf[0] = 0x01; /* AV/C STATUS */ + buf[1] = 0xff; /* UNIT */ + buf[2] = 0x1A; /* SIGNAL SOURCE */ + buf[3] = 0x0f; + buf[4] = 0xff; + buf[5] = 0xfe; + buf[6] = (0xf8 & (dst_stype << 3)) | dst_sid; + buf[7] = 0xff & dst_pid; + + /* do transaction and check buf[1,2,6,7] are the same against command */ + err = fcp_avc_transaction(unit, buf, 8, buf, 8, + BIT(1) | BIT(2) | BIT(6) | BIT(7)); + if (err < 0) + goto end; + if ((err < 0) || (buf[0] != 0x0c)) { + dev_err(&unit->device, + "failed to get signal status\n"); + err = -EIO; + goto end; + } + + *src_stype = buf[4] >> 3; + *src_sid = buf[4] & 0x07; + *src_pid = buf[5]; +end: + kfree(buf); + return err; +} + +int avc_ccm_set_sig_src(struct fw_unit *unit, + unsigned int src_stype, unsigned int src_sid, unsigned int src_pid, + unsigned int dst_stype, unsigned int dst_sid, unsigned int dst_pid) +{ + int err; + u8 *buf; + + buf = kzalloc(8, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + buf[0] = 0x00; /* AV/C CONTROL */ + buf[1] = 0xff; /* UNIT */ + buf[2] = 0x1A; /* SIGNAL SOURCE */ + buf[3] = 0x0f; + buf[4] = (0xf8 & (src_stype << 3)) | src_sid; + buf[5] = 0xff & src_pid; + buf[6] = (0xf8 & (dst_stype << 3)) | dst_sid; + buf[7] = 0xff & dst_pid; + + /* do transaction and check buf[1-7] are the same against command */ + err = fcp_avc_transaction(unit, buf, 8, buf, 8, + BIT(1) | BIT(2) | BIT(4) | BIT(5) | + BIT(6) | BIT(7)); + if (err < 0) + goto end; + if ((err < 0) || ((buf[0] != 0x09) && (buf[0] != 0x0f))) { + dev_err(&unit->device, + "failed to set signal status\n"); + err = -EIO; + goto end; + } + + err = 0; +end: + kfree(buf); + return err; +} + int avc_bridgeco_get_plug_type(struct fw_unit *unit, enum snd_bebob_plug_dir pdir, enum snd_bebob_plug_unit punit, diff --git a/sound/firewire/bebob/bebob_control.c b/sound/firewire/bebob/bebob_control.c index f9a9229..3b311c3 100644 --- a/sound/firewire/bebob/bebob_control.c +++ b/sound/firewire/bebob/bebob_control.c @@ -17,6 +17,49 @@
#include "bebob.h"
+/* + * Currently this module support any controls related to decision of channels + * in stream, hardware metering and digital format. Users should utilize tools + * which FFADO project developed. + */ + +/* + * Physical metering: + * the value in unavvailable channels is zero. + */ +static int +physical_metering_info(struct snd_kcontrol *ctl, + struct snd_ctl_elem_info *info) +{ + struct snd_bebob *bebob = ctl->private_data; + struct snd_bebob_meter_spec *spec = bebob->spec->meter; + + info->type = SNDRV_CTL_ELEM_TYPE_BYTES; + info->count = 1 + spec->num * 2 * sizeof(u32); + + return 0; +} +static int +physical_metering_get(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + struct snd_bebob *bebob = ctl->private_data; + struct snd_bebob_meter_spec *spec = bebob->spec->meter; + u32 *dst; + + dst = (u32 *)value->value.bytes.data; + *dst = spec->num; + + return spec->get(bebob, dst + 1, spec->num * 2 * sizeof(u32)); +} +static const +struct snd_kcontrol_new physical_metering = { + .iface = SNDRV_CTL_ELEM_IFACE_CARD, + .name = "Physical Metering", + .access = SNDRV_CTL_ELEM_ACCESS_READ | SNDRV_CTL_ELEM_ACCESS_VOLATILE, + .info = physical_metering_info, + .get = physical_metering_get +};
/* * Global Control: Sampling Rate Control @@ -64,12 +107,13 @@ control_sampling_rate_get(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *uval) { struct snd_bebob *bebob = snd_kcontrol_chip(kctl); + struct snd_bebob_clock_spec *spec = bebob->spec->clock; unsigned int i, sampling_rate, index; int err;
mutex_lock(&bebob->mutex);
- err = snd_bebob_stream_get_rate(bebob, &sampling_rate); + err = spec->get_freq(bebob, &sampling_rate); if (err < 0) goto end;
@@ -92,6 +136,7 @@ control_sampling_rate_put(struct snd_kcontrol *kctl, struct snd_ctl_elem_value *uval) { struct snd_bebob *bebob = snd_kcontrol_chip(kctl); + struct snd_bebob_clock_spec *spec = bebob->spec->clock; unsigned int index, sampling_rate; int value, changed = 0;
@@ -110,20 +155,128 @@ control_sampling_rate_put(struct snd_kcontrol *kctl, sampling_rate = snd_bebob_rate_table[index];
mutex_lock(&bebob->mutex); - if (snd_bebob_stream_set_rate(bebob, sampling_rate) < 0) + if (spec->set_freq(bebob, sampling_rate) < 0) goto end;
/* prevent from failure of getting command just after setting */ msleep(100); changed = 1;
- snd_ctl_notify(bebob->card, SNDRV_CTL_EVENT_MASK_VALUE, - bebob->ctl_id_freq); + if (spec->ctl_id_freq) + snd_ctl_notify(bebob->card, SNDRV_CTL_EVENT_MASK_VALUE, + spec->ctl_id_freq); end: mutex_unlock(&bebob->mutex); return changed; }
+/* + * Global Control: Clock Source Control + */ +static int control_clock_source_info(struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *einf) +{ + struct snd_bebob *bebob = snd_kcontrol_chip(kctl); + struct snd_bebob_clock_spec *spec = bebob->spec->clock; + + einf->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + einf->count = 1; + einf->value.enumerated.items = spec->num; + + if (einf->value.enumerated.item >= einf->value.enumerated.items) + einf->value.enumerated.item = einf->value.enumerated.items - 1; + + strcpy(einf->value.enumerated.name, + spec->labels[einf->value.enumerated.item]); + + return 0; +} +static int control_clock_source_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *uval) +{ + struct snd_bebob *bebob = snd_kcontrol_chip(kctl); + struct snd_bebob_clock_spec *spec = bebob->spec->clock; + unsigned int id; + + mutex_lock(&bebob->mutex); + if (spec->get_src(bebob, &id) >= 0) + uval->value.enumerated.item[0] = id; + mutex_unlock(&bebob->mutex); + + return 0; +} +static int control_clock_source_put(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *uval) +{ + struct snd_bebob *bebob = snd_kcontrol_chip(kctl); + struct snd_bebob_clock_spec *spec = bebob->spec->clock; + int value, changed = 0; + + value = uval->value.enumerated.item[0]; + + if (value < spec->num) { + mutex_lock(&bebob->mutex); + if (spec->set_src(bebob, value) >= 0) + changed = 1; + mutex_unlock(&bebob->mutex); + } + + msleep(150); + if (spec->ctl_id_src) + snd_ctl_notify(bebob->card, SNDRV_CTL_EVENT_MASK_VALUE, + spec->ctl_id_src); + + return changed; +} + +/* + * Global Control: Clock Sync Status + */ +static int control_clock_sync_status_info(struct snd_kcontrol *kctl, + struct snd_ctl_elem_info *einf) +{ + einf->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN; + einf->count = 1; + einf->value.integer.min = 0; + einf->value.integer.max = 1; + + return 0; +} +static int control_clock_sync_status_get(struct snd_kcontrol *kctl, + struct snd_ctl_elem_value *uval) +{ + struct snd_bebob *bebob = snd_kcontrol_chip(kctl); + struct snd_bebob_clock_spec *spec = bebob->spec->clock; + bool synced = 0; + + mutex_lock(&bebob->mutex); + if (spec->synced(bebob, &synced) >= 0) { + if (synced == false) + uval->value.enumerated.item[0] = 0; + else + uval->value.enumerated.item[0] = 1; + } + mutex_unlock(&bebob->mutex); + + return 0; +} +static struct snd_kcontrol_new global_clock_source_control = { + .name = "Clock Source", + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, + .info = control_clock_source_info, + .get = control_clock_source_get, + .put = control_clock_source_put +}; + +static struct snd_kcontrol_new global_clock_sync_status = { + .name = "Clock Sync Status", + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .access = SNDRV_CTL_ELEM_ACCESS_READ, + .info = control_clock_sync_status_info, + .get = control_clock_sync_status_get, +}; + static struct snd_kcontrol_new global_sampling_rate_control = { .name = "Sampling Rate", .iface = SNDRV_CTL_ELEM_IFACE_MIXER, @@ -137,12 +290,39 @@ int snd_bebob_create_control_devices(struct snd_bebob *bebob) { int err = 0; struct snd_kcontrol *kctl; + struct snd_bebob_clock_spec *clk = bebob->spec->clock; + struct snd_bebob_meter_spec *meter = bebob->spec->meter;
- kctl = snd_ctl_new1(&global_sampling_rate_control, bebob); - err = snd_ctl_add(bebob->card, kctl); - if (err < 0) - goto end; - bebob->ctl_id_freq = &kctl->id; + if (clk->get_freq != NULL) { + kctl = snd_ctl_new1(&global_sampling_rate_control, bebob); + err = snd_ctl_add(bebob->card, kctl); + if (err < 0) + goto end; + clk->ctl_id_freq = &kctl->id; + } + + if (clk->get_src != NULL) { + kctl = snd_ctl_new1(&global_clock_source_control, bebob); + err = snd_ctl_add(bebob->card, kctl); + if (err < 0) + goto end; + clk->ctl_id_src = &kctl->id; + } + + if (clk->synced != NULL) { + kctl = snd_ctl_new1(&global_clock_sync_status, bebob); + err = snd_ctl_add(bebob->card, kctl); + if (err < 0) + goto end; + clk->ctl_id_synced = &kctl->id; + } + + if (meter != NULL) { + kctl = snd_ctl_new1(&physical_metering, bebob); + err = snd_ctl_add(bebob->card, kctl); + if (err < 0) + goto end; + } end: return err; } diff --git a/sound/firewire/bebob/bebob_pcm.c b/sound/firewire/bebob/bebob_pcm.c index c7cca80..b478df8 100644 --- a/sound/firewire/bebob/bebob_pcm.c +++ b/sound/firewire/bebob/bebob_pcm.c @@ -236,6 +236,7 @@ static int pcm_open(struct snd_pcm_substream *substream) { struct snd_bebob *bebob = substream->private_data; + struct snd_bebob_clock_spec *spec = bebob->spec->clock; unsigned int sampling_rate; int err;
@@ -249,7 +250,7 @@ pcm_open(struct snd_pcm_substream *substream)
if (amdtp_stream_pcm_running(&bebob->tx_stream) || amdtp_stream_pcm_running(&bebob->rx_stream)) { - err = snd_bebob_stream_get_rate(bebob, &sampling_rate); + err = spec->get_freq(bebob, &sampling_rate); if (err < 0) goto err_locked;
diff --git a/sound/firewire/bebob/bebob_proc.c b/sound/firewire/bebob/bebob_proc.c index ca7c1a7..3893e07 100644 --- a/sound/firewire/bebob/bebob_proc.c +++ b/sound/firewire/bebob/bebob_proc.c @@ -81,6 +81,42 @@ end: }
static void +proc_read_meters(struct snd_info_entry *entry, + struct snd_info_buffer *buffer) +{ + struct snd_bebob *bebob = entry->private_data; + struct snd_bebob_meter_spec *spec = bebob->spec->meter; + u32 *buf; + unsigned int i, c, channels, size; + int err; + + if (spec == NULL) + return; + + channels = spec->num * 2; + size = channels * sizeof(u32); + buf = kmalloc(size, GFP_KERNEL); + if (buf == NULL) + return; + + err = spec->get(bebob, buf, size); + if (err < 0) + goto end; + + for (i = 0, c = 1; i < channels; i++) { + snd_iprintf(buffer, "%s %d:\t%d\n", + spec->labels[i / 2], c++, buf[i]); + if ((i + 1 < channels - 1) && + (strcmp(spec->labels[i / 2], + spec->labels[(i + 1) / 2]) != 0)) + c = 1; + } +end: + kfree(buf); + return; +} + +static void proc_read_formation(struct snd_info_entry *entry, struct snd_info_buffer *buffer) { @@ -91,7 +127,7 @@ proc_read_formation(struct snd_info_entry *entry, snd_iprintf(buffer, "Output Stream from device:\n"); snd_iprintf(buffer, "\tRate\tPCM\tMIDI\n"); formation = bebob->tx_stream_formations; - for (i = 0; i < 9; i += 1) { + for (i = 0; i < 9; i++) { snd_iprintf(buffer, "\t%d\t%d\t%d\n", snd_bebob_rate_table[i], formation[i].pcm, formation[i].midi); @@ -100,7 +136,7 @@ proc_read_formation(struct snd_info_entry *entry, snd_iprintf(buffer, "Input Stream to device:\n"); snd_iprintf(buffer, "\tRate\tPCM\tMIDI\n"); formation = bebob->rx_stream_formations; - for (i = 0; i < 9; i += 1) { + for (i = 0; i < 9; i++) { snd_iprintf(buffer, "\t%d\t%d\t%d\n", snd_bebob_rate_table[i], formation[i].pcm, formation[i].midi); @@ -119,5 +155,10 @@ void snd_bebob_proc_init(struct snd_bebob *bebob) if (!snd_card_proc_new(bebob->card, "#formation", &entry)) snd_info_set_text_ops(entry, bebob, proc_read_formation);
+ if (bebob->spec->meter != NULL) { + if (!snd_card_proc_new(bebob->card, "#meter", &entry)) + snd_info_set_text_ops(entry, bebob, proc_read_meters); + } + return; } diff --git a/sound/firewire/bebob/bebob_stream.c b/sound/firewire/bebob/bebob_stream.c index f2527bf..faa235e 100644 --- a/sound/firewire/bebob/bebob_stream.c +++ b/sound/firewire/bebob/bebob_stream.c @@ -302,6 +302,7 @@ int snd_bebob_stream_start_duplex(struct snd_bebob *bebob, struct amdtp_stream *request, unsigned int rate) { + struct snd_bebob_clock_spec *spec = bebob->spec->clock; struct amdtp_stream *master, *slave; enum cip_flags sync_mode; unsigned int curr_rate; @@ -335,7 +336,7 @@ int snd_bebob_stream_start_duplex(struct snd_bebob *bebob, }
/* get current rate */ - err = snd_bebob_stream_get_rate(bebob, &curr_rate); + err = spec->get_freq(bebob, &curr_rate); if (err < 0) goto end; if (rate == 0) @@ -365,11 +366,11 @@ int snd_bebob_stream_start_duplex(struct snd_bebob *bebob, * If establishing connections at first, Yamaha GO46 (and maybe * TerraTek X24) don't generate sound. */ - err = snd_bebob_stream_set_rate(bebob, rate); + err = spec->set_freq(bebob, rate); if (err < 0) goto end; snd_ctl_notify(bebob->card, SNDRV_CTL_EVENT_MASK_VALUE, - bebob->ctl_id_freq); + spec->ctl_id_freq);
err = make_both_connections(bebob, rate); if (err < 0)
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@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);
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.
- 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.
- 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.
- 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@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.o obj-m += snd-bebob.obebob_maudio.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)
end: return err; }err = snd_bebob_maudio_special_add_controls(bebob);
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);
This commit allows this driver to support all of models which Focusrite produces with BeBoB chipset. They are: - Saffire - Saffire LE - SaffirePro 10 - SaffirePro26
This commit adds Focusrite specific operations, quirks for its four functionalities.
1. Switching source of clock 2. Change sampling frequency 3. Check clock synchronization 4. Get metering information
The driver uses these functionalities to read/write specific address by async transaction.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/Kconfig | 1 + sound/firewire/bebob/Makefile | 2 +- sound/firewire/bebob/bebob.c | 34 ++- sound/firewire/bebob/bebob.h | 4 + sound/firewire/bebob/bebob_focusrite.c | 390 +++++++++++++++++++++++++++++++++ 5 files changed, 429 insertions(+), 2 deletions(-) create mode 100644 sound/firewire/bebob/bebob_focusrite.c
diff --git a/sound/firewire/Kconfig b/sound/firewire/Kconfig index 427def7..b8d7fb5 100644 --- a/sound/firewire/Kconfig +++ b/sound/firewire/Kconfig @@ -82,6 +82,7 @@ config SND_BEBOB * BridgeCo RDAudio1/Audio5 * M-Audio Firewire410/AudioPhile/Solo/Ozonic/NRV10 * M-Audio Firewire1814/ProjectMix IO/ProfireLightBridge + * Focusrite Saffire/Saffire LE/SaffirePro10 IO/SaffirePro26 IO
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 2e87d02..96f8e92 100644 --- a/sound/firewire/bebob/Makefile +++ b/sound/firewire/bebob/Makefile @@ -1,5 +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_maudio.o bebob_focusrite.o \ bebob.o obj-m += snd-bebob.o diff --git a/sound/firewire/bebob/bebob.c b/sound/firewire/bebob/bebob.c index 576d801..6382b49 100644 --- a/sound/firewire/bebob/bebob.c +++ b/sound/firewire/bebob/bebob.c @@ -52,10 +52,12 @@ static unsigned int devices_used; #define VEN_PHONIC 0x00001496 #define VEN_MAUDIO1 0x00000d6c #define VEN_MAUDIO2 0x000007f5 +#define VEN_FOCUSRITE 0x0000130e
#define MODEL_MAUDIO_AUDIOPHILE_BOTH 0x00010060 #define MODEL_MAUDIO_FW1814 0x00010071 #define MODEL_MAUDIO_PROJECTMIX 0x00010091 +#define MODEL_FOCUSRITE_SAFFIRE_BOTH 0x00000000
static int name_device(struct snd_bebob *bebob, unsigned int vendor_id) @@ -157,6 +159,21 @@ check_audiophile_booted(struct fw_unit *unit) return true; }
+static const struct snd_bebob_spec * +get_saffire_spec(struct fw_unit *unit) +{ + char name[24] = {0}; + + if (fw_csr_string(unit->directory, CSR_MODEL, name, sizeof(name)) < 0) + return NULL; + + if (strcmp(name, "SaffireLE") == 0) + return &saffire_le_spec; + else + return &saffire_spec; +} + + static int snd_bebob_probe(struct fw_unit *unit, const struct ieee1394_device_id *entry) @@ -179,7 +196,15 @@ snd_bebob_probe(struct fw_unit *unit, goto end; }
- spec = (const struct snd_bebob_spec *)entry->driver_data; + if (entry->model_id == MODEL_FOCUSRITE_SAFFIRE_BOTH) + spec = get_saffire_spec(unit); + else + spec = (const struct snd_bebob_spec *)entry->driver_data; + + if (spec == NULL) { + err = -EIO; + goto end; + }
/* if needed, load firmware and exit */ if ((spec->load) && @@ -362,6 +387,13 @@ static const struct ieee1394_device_id snd_bebob_id_table[] = { maudio_special_spec), /* M-Audio, ProFireLightbridge */ SND_BEBOB_DEV_ENTRY(VEN_MAUDIO1, 0x000100a1, spec_normal), + /* Focusrite, SaffirePro 26 I/O */ + SND_BEBOB_DEV_ENTRY(VEN_FOCUSRITE, 0x00000003, saffirepro_26_spec), + /* Focusrite, SaffirePro 10 I/O */ + SND_BEBOB_DEV_ENTRY(VEN_FOCUSRITE, 0x00000006, saffirepro_10_spec), + /* Focusrite, Saffire(no label and LE) */ + SND_BEBOB_DEV_ENTRY(VEN_FOCUSRITE, MODEL_FOCUSRITE_SAFFIRE_BOTH, + saffire_spec), {} }; MODULE_DEVICE_TABLE(ieee1394, snd_bebob_id_table); diff --git a/sound/firewire/bebob/bebob.h b/sound/firewire/bebob/bebob.h index fb9d99d..90b37eb 100644 --- a/sound/firewire/bebob/bebob.h +++ b/sound/firewire/bebob/bebob.h @@ -234,6 +234,10 @@ 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; +extern struct snd_bebob_spec saffirepro_26_spec; +extern struct snd_bebob_spec saffirepro_10_spec; +extern struct snd_bebob_spec saffire_le_spec; +extern struct snd_bebob_spec saffire_spec;
#define SND_BEBOB_DEV_ENTRY(vendor, model, private_data) \ { \ diff --git a/sound/firewire/bebob/bebob_focusrite.c b/sound/firewire/bebob/bebob_focusrite.c new file mode 100644 index 0000000..c4c7805 --- /dev/null +++ b/sound/firewire/bebob/bebob_focusrite.c @@ -0,0 +1,390 @@ +/* + * bebob_focusrite.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" + +#define ANA_IN "Analog In" +#define DIG_IN "Digital In" +#define ANA_OUT "Analog Out" +#define DIG_OUT "Digital Out" +#define STM_IN "Stream In" + +#define SAFFIRE_ADDRESS_BASE 0x000100000000 + +#define SAFFIRE_OFFSET_CLOCK_SOURCE 0x0000000000f8 +#define SAFFIREPRO_OFFSET_CLOCK_SOURCE 0x000000000174 + +/* whether sync to external device or not */ +#define SAFFIRE_OFFSET_CLOCK_SYNC_EXT 0x00000000013c +#define SAFFIRE_LE_OFFSET_CLOCK_SYNC_EXT 0x000000000432 +#define SAFFIREPRO_OFFSET_CLOCK_SYNC_EXT 0x000000000164 + +#define SAFFIRE_CLOCK_SOURCE_INTERNAL 0 +#define SAFFIRE_CLOCK_SOURCE_SPDIF 1 + +/* '1' is absent, why... */ +#define SAFFIREPRO_CLOCK_SOURCE_INTERNAL 0 +#define SAFFIREPRO_CLOCK_SOURCE_SPDIF 2 +#define SAFFIREPRO_CLOCK_SOURCE_ADAT1 3 +#define SAFFIREPRO_CLOCK_SOURCE_ADAT2 4 +#define SAFFIREPRO_CLOCK_SOURCE_WORDCLOCK 5 + +/* S/PDIF, ADAT1, ADAT2 is enabled or not. three quadlets */ +#define SAFFIREPRO_ENABLE_DIG_IFACES 0x0000000001a4 + +/* saffirepro has its own parameter for sampling frequency */ +#define SAFFIREPRO_RATE_NOREBOOT 0x0000000001cc +/* index is the value for this register */ +const static unsigned int rates[] = { + [0] = 0, + [1] = 44100, + [2] = 48000, + [3] = 88200, + [4] = 96000, + [5] = 176400, + [6] = 192000 +}; + +/* saffire(no label)/saffire LE has metering */ +#define SAFFIRE_OFFSET_METER 0x000000000100 +#define SAFFIRE_LE_OFFSET_METER 0x000000000168 + +static inline int +saffire_read_block(struct snd_bebob *bebob, u64 offset, + u32 *buf, unsigned int size) +{ + unsigned int i; + int err; + __be32 *tmp = (__be32 *)buf; + + err = snd_fw_transaction(bebob->unit, TCODE_READ_BLOCK_REQUEST, + SAFFIRE_ADDRESS_BASE + offset, + tmp, size, 0); + if (err < 0) + goto end; + + for (i = 0; i < size / sizeof(u32); i++) + buf[i] = be32_to_cpu(tmp[i]); +end: + return err; +} + +static inline int +saffire_read_quad(struct snd_bebob *bebob, u64 offset, u32 *value) +{ + int err; + __be32 tmp; + + err = snd_fw_transaction(bebob->unit, TCODE_READ_QUADLET_REQUEST, + SAFFIRE_ADDRESS_BASE + offset, + &tmp, sizeof(u32), 0); + if (err < 0) + goto end; + + *value = be32_to_cpu(tmp); +end: + return err; +} + +static inline int +saffire_write_quad(struct snd_bebob *bebob, u64 offset, u32 value) +{ + value = cpu_to_be32(value); + + return snd_fw_transaction(bebob->unit, TCODE_WRITE_QUADLET_REQUEST, + SAFFIRE_ADDRESS_BASE + offset, + &value, sizeof(u32), 0); +} + +static char *saffirepro_26_clk_src_labels[] = { + SND_BEBOB_CLOCK_INTERNAL, "S/PDIF", "ADAT1", "ADAT2", "Word Clock" +}; + +static char *saffirepro_10_clk_src_labels[] = { + SND_BEBOB_CLOCK_INTERNAL, "S/PDIF", "Word Clock" +}; +static int +saffirepro_both_clk_freq_get(struct snd_bebob *bebob, unsigned int *rate) +{ + u32 id; + int err; + + err = saffire_read_quad(bebob, SAFFIREPRO_RATE_NOREBOOT, &id); + if (err < 0) + goto end; + if (id >= ARRAY_SIZE(rates)) { + err = -EIO; + goto end; + } + + *rate = rates[id]; +end: + return err; +} +static int +saffirepro_both_clk_freq_set(struct snd_bebob *bebob, unsigned int rate) +{ + u32 id; + bool flag; + + flag = false; + for (id = 0; id < ARRAY_SIZE(rates); id++) { + if (rates[id] == rate) + flag = true; + } + if (!flag) + return -EINVAL; + + return saffire_write_quad(bebob, SAFFIREPRO_RATE_NOREBOOT, id); +} +static int +saffirepro_both_clk_src_get(struct snd_bebob *bebob, unsigned int *id) +{ + int err; + u32 value; + + err = saffire_read_quad(bebob, SAFFIREPRO_OFFSET_CLOCK_SOURCE, &value); + if (err < 0) + goto end; + + if (bebob->spec->clock->labels == saffirepro_10_clk_src_labels) { + if (value == SAFFIREPRO_CLOCK_SOURCE_WORDCLOCK) + *id = 2; + else if (value == SAFFIREPRO_CLOCK_SOURCE_SPDIF) + *id = 1; + } else if (value > 1) + *id = value - 1; +end: + return err; +} +static int +saffirepro_both_clk_src_set(struct snd_bebob *bebob, unsigned int id) +{ + int err; + u32 values[3]; + + if (bebob->spec->clock->labels == saffirepro_10_clk_src_labels) { + if (id == 2) + id = 5; + else if (id == 1) + id = 2; + } else if (id > 0) + id++; + + /* if requesting digital input, check whether it's enabled or not */ + /* TODO: consider to add switch for these digital inputs */ + if ((id > 1) && (id < 5)) { + err = saffire_read_block(bebob, SAFFIREPRO_ENABLE_DIG_IFACES, + values, sizeof(values)); + if (err < 0) + goto end; + if (values[id - 2] == 0) + goto end; + } + + err = saffire_write_quad(bebob, SAFFIREPRO_OFFSET_CLOCK_SOURCE, id); +end: + return err; +} +static int +saffirepro_both_clk_synced(struct snd_bebob *bebob, bool *synced) +{ + unsigned int clock; + u32 value; + int err; + + err = saffirepro_both_clk_src_get(bebob, &clock); + if (err < 0) + goto end; + /* internal */ + if (clock == 0) { + *synced = true; + goto end; + } + + err = saffire_read_quad(bebob, + SAFFIREPRO_OFFSET_CLOCK_SYNC_EXT, &value); + if (err < 0) + goto end; + + *synced = (value & 0x01); +end: + return err; +} + +struct snd_bebob_spec saffire_le_spec; +static char *saffire_both_clk_src_labels[] = { + SND_BEBOB_CLOCK_INTERNAL, "S/PDIF" +}; +static int +saffire_both_clk_src_get(struct snd_bebob *bebob, unsigned int *id) +{ + int err; + u32 value; + + err = saffire_read_quad(bebob, SAFFIRE_OFFSET_CLOCK_SOURCE, &value); + if (err < 0) + goto end; + + *id = 0xff & value; +end: + return err; +}; +static int +saffire_both_clk_src_set(struct snd_bebob *bebob, unsigned int id) +{ + return saffire_write_quad(bebob, SAFFIRE_OFFSET_CLOCK_SOURCE, id); +}; +static int +saffire_both_clk_synced(struct snd_bebob *bebob, bool *synced) +{ + int err; + u64 offset; + u32 value; + + err = saffire_both_clk_src_get(bebob, &value); + if (err < 0) + goto end; + /* internal */ + if (value == 0) { + *synced = true; + goto end; + } + + if (bebob->spec == &saffire_le_spec) + offset = SAFFIRE_LE_OFFSET_CLOCK_SYNC_EXT; + else + offset = SAFFIRE_OFFSET_CLOCK_SYNC_EXT; + + err = saffire_read_quad(bebob, offset, &value); + if (err < 0) + goto end; + + *synced = (0x01 & value); +end: + return err; +} +static char *saffire_le_meter_labels[] = { + ANA_IN, ANA_IN, DIG_IN, + ANA_OUT, ANA_OUT, ANA_OUT, ANA_OUT, + STM_IN, STM_IN +}; +#define SWAP(a, b) \ + tmp = a; \ + a = b; \ + b = tmp +static int +saffire_le_meter_get(struct snd_bebob *bebob, u32 *buf, unsigned int size) +{ + int err; + u32 tmp; + + if (size < sizeof(saffire_le_meter_labels) * sizeof(u32)) + return -EIO; + + err = saffire_read_block(bebob, SAFFIRE_LE_OFFSET_METER, buf, size); + if (err < 0) + goto end; + + SWAP(buf[1], buf[3]); + SWAP(buf[2], buf[3]); + SWAP(buf[3], buf[4]); + + SWAP(buf[7], buf[10]); + SWAP(buf[8], buf[10]); + SWAP(buf[9], buf[11]); + SWAP(buf[11], buf[12]); + + SWAP(buf[15], buf[16]); + +end: + return err; +} +static char *saffire_meter_labels[] = { + ANA_IN, ANA_IN, + STM_IN, STM_IN, STM_IN, STM_IN, STM_IN, +}; +static int +saffire_meter_get(struct snd_bebob *bebob, u32 *buf, unsigned int size) +{ + return saffire_read_block(bebob, SAFFIRE_OFFSET_METER, buf, size); +} + +/* Saffire Pro 26 I/O */ +static struct snd_bebob_clock_spec saffirepro_26_clk_spec = { + .num = ARRAY_SIZE(saffirepro_26_clk_src_labels), + .labels = saffirepro_26_clk_src_labels, + .get_src = &saffirepro_both_clk_src_get, + .set_src = &saffirepro_both_clk_src_set, + .get_freq = &saffirepro_both_clk_freq_get, + .set_freq = &saffirepro_both_clk_freq_set, + .synced = &saffirepro_both_clk_synced +}; +struct snd_bebob_spec saffirepro_26_spec = { + .load = NULL, + .clock = &saffirepro_26_clk_spec, + .meter = NULL +}; + +/* Saffire Pro 10 I/O */ +static struct snd_bebob_clock_spec saffirepro_10_clk_spec = { + .num = ARRAY_SIZE(saffirepro_10_clk_src_labels), + .labels = saffirepro_10_clk_src_labels, + .get_src = &saffirepro_both_clk_src_get, + .set_src = &saffirepro_both_clk_src_set, + .get_freq = &saffirepro_both_clk_freq_get, + .set_freq = &saffirepro_both_clk_freq_set, + .synced = &saffirepro_both_clk_synced +}; +struct snd_bebob_spec saffirepro_10_spec = { + .load = NULL, + .clock = &saffirepro_10_clk_spec, + .meter = NULL +}; + +struct snd_bebob_clock_spec saffire_both_clk_spec= { + .num = ARRAY_SIZE(saffire_both_clk_src_labels), + .labels = saffire_both_clk_src_labels, + .get_src = &saffire_both_clk_src_get, + .set_src = &saffire_both_clk_src_set, + .get_freq = &snd_bebob_stream_get_rate, + .set_freq = &snd_bebob_stream_set_rate, + .synced = &saffire_both_clk_synced +}; + +/* Saffire LE */ +struct snd_bebob_meter_spec saffire_le_meter_spec = { + .num = ARRAY_SIZE(saffire_le_meter_labels), + .labels = saffire_le_meter_labels, + .get = &saffire_le_meter_get, +}; +struct snd_bebob_spec saffire_le_spec = { + .load = NULL, + .clock = &saffire_both_clk_spec, + .meter = &saffire_le_meter_spec +}; +/* Saffire */ +struct snd_bebob_meter_spec saffire_meter_spec = { + .num = ARRAY_SIZE(saffire_meter_labels), + .labels = saffire_meter_labels, + .get = &saffire_meter_get, +}; +struct snd_bebob_spec saffire_spec ={ + .load = NULL, + .clock = &saffire_both_clk_spec, + .meter = &saffire_meter_spec +};
This commit allows this driver to support all of models which Terratec produced with BeBoB chipset. They are: - PHASE 24 FW - PHASE X24 FW - PHASE 88 Rack FW
This commit adds Terratec specific operations, quirks for three functionalities.
1. Switching source of clock 2. Change sampling frequency 3. Check clock synchronization
The driver uses these functionalities with AV/C vendor specific commands and AV/C Audio Subunit commands.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/Kconfig | 1 + sound/firewire/bebob/Makefile | 2 +- sound/firewire/bebob/bebob.c | 9 +++ sound/firewire/bebob/bebob.h | 2 + sound/firewire/bebob/bebob_terratec.c | 143 ++++++++++++++++++++++++++++++++++ 5 files changed, 156 insertions(+), 1 deletion(-) create mode 100644 sound/firewire/bebob/bebob_terratec.c
diff --git a/sound/firewire/Kconfig b/sound/firewire/Kconfig index b8d7fb5..502dce9 100644 --- a/sound/firewire/Kconfig +++ b/sound/firewire/Kconfig @@ -83,6 +83,7 @@ config SND_BEBOB * M-Audio Firewire410/AudioPhile/Solo/Ozonic/NRV10 * M-Audio Firewire1814/ProjectMix IO/ProfireLightBridge * Focusrite Saffire/Saffire LE/SaffirePro10 IO/SaffirePro26 IO + * TerraTec PHASE 24 FW/PHASE X24 FW/PHASE 88 Rack FW
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 96f8e92..2fefcec 100644 --- a/sound/firewire/bebob/Makefile +++ b/sound/firewire/bebob/Makefile @@ -1,5 +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_focusrite.o \ + bebob_maudio.o bebob_focusrite.o bebob_terratec.o \ bebob.o obj-m += snd-bebob.o diff --git a/sound/firewire/bebob/bebob.c b/sound/firewire/bebob/bebob.c index 6382b49..7c5576d 100644 --- a/sound/firewire/bebob/bebob.c +++ b/sound/firewire/bebob/bebob.c @@ -53,6 +53,7 @@ static unsigned int devices_used; #define VEN_MAUDIO1 0x00000d6c #define VEN_MAUDIO2 0x000007f5 #define VEN_FOCUSRITE 0x0000130e +#define VEN_TERRATEC 0x00000aac
#define MODEL_MAUDIO_AUDIOPHILE_BOTH 0x00010060 #define MODEL_MAUDIO_FW1814 0x00010071 @@ -92,6 +93,8 @@ name_device(struct snd_bebob *bebob, unsigned int vendor_id) strcpy(vendor, "Phonic"); else if ((vendor_id == VEN_MAUDIO1) || (vendor_id == VEN_MAUDIO2)) strcpy(vendor, "M-Audio"); + else if (vendor_id == VEN_TERRATEC) + strcpy(vendor, "Terratec");
/* get model name */ err = fw_csr_string(bebob->unit->directory, CSR_MODEL, @@ -394,6 +397,12 @@ static const struct ieee1394_device_id snd_bebob_id_table[] = { /* Focusrite, Saffire(no label and LE) */ SND_BEBOB_DEV_ENTRY(VEN_FOCUSRITE, MODEL_FOCUSRITE_SAFFIRE_BOTH, saffire_spec), + /* TerraTecElectronic GmbH, PHASE 88 Rack FW */ + SND_BEBOB_DEV_ENTRY(VEN_TERRATEC, 0x00000003, phase88_rack_spec), + /* TerraTecElectronic GmbH, PHASE 24 FW */ + SND_BEBOB_DEV_ENTRY(VEN_TERRATEC, 0x00000004, phase24_series_spec), + /* TerraTecElectronic GmbH, Phase X24 FW */ + SND_BEBOB_DEV_ENTRY(VEN_TERRATEC, 0x00000007, phase24_series_spec), {} }; MODULE_DEVICE_TABLE(ieee1394, snd_bebob_id_table); diff --git a/sound/firewire/bebob/bebob.h b/sound/firewire/bebob/bebob.h index 90b37eb..6a92f43 100644 --- a/sound/firewire/bebob/bebob.h +++ b/sound/firewire/bebob/bebob.h @@ -238,6 +238,8 @@ extern struct snd_bebob_spec saffirepro_26_spec; extern struct snd_bebob_spec saffirepro_10_spec; extern struct snd_bebob_spec saffire_le_spec; extern struct snd_bebob_spec saffire_spec; +extern struct snd_bebob_spec phase88_rack_spec; +extern struct snd_bebob_spec phase24_series_spec;
#define SND_BEBOB_DEV_ENTRY(vendor, model, private_data) \ { \ diff --git a/sound/firewire/bebob/bebob_terratec.c b/sound/firewire/bebob/bebob_terratec.c new file mode 100644 index 0000000..149507a --- /dev/null +++ b/sound/firewire/bebob/bebob_terratec.c @@ -0,0 +1,143 @@ +/* + * bebob_terratec.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" + +static char *phase88_rack_clk_src_labels[] = { + SND_BEBOB_CLOCK_INTERNAL, "Digital In", "Word Clock" +}; +static int +phase88_rack_clk_src_get(struct snd_bebob *bebob, unsigned int *id) +{ + unsigned int enable_ext, enable_word; + int err; + + err = avc_audio_get_selector(bebob->unit, 0, 0, &enable_ext); + if (err < 0) + goto end; + err = avc_audio_get_selector(bebob->unit, 0, 0, &enable_word); + if (err < 0) + goto end; + + *id = (enable_ext & 0x01) || ((enable_word & 0x01) << 1); +end: + return err; +} +static int +phase88_rack_clk_src_set(struct snd_bebob *bebob, unsigned int id) +{ + unsigned int enable_ext, enable_word; + int err; + + enable_ext = id & 0x01; + enable_word = (id >> 1) & 0x01; + + err = avc_audio_set_selector(bebob->unit, 0, 9, enable_ext); + if (err < 0) + goto end; + err = avc_audio_set_selector(bebob->unit, 0, 8, enable_word); + if (err < 0) + goto end; +end: + return err; +} + +static int +phase88_rack_clk_synced(struct snd_bebob *bebob, bool *synced) +{ + int err; + u8 *buf; + + buf = kmalloc(8, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + buf[0] = 0x01; /* STATUS */ + buf[1] = 0xff; /* UNIT */ + buf[2] = 0x00; /* Vendor Dependent */ + buf[3] = 0x00; /* Company ID high */ + buf[4] = 0x0a; /* Company ID middle */ + buf[5] = 0xac; /* Company ID low */ + buf[6] = 0x21; /* subfunction */ + buf[7] = 0xff; /* the state */ + + /* do transaction and check buf[1-6] are the same against command */ + err = fcp_avc_transaction(bebob->unit, buf, 8, buf, 8, + BIT(1) | BIT(2) | BIT(3) | BIT(4) | + BIT(5) | BIT(6)); + if (err < 0) + goto end; + /* IMPLEMENTED/STABLE is OK */ + if ((err < 6) || (buf[0] != 0x0c)) { + dev_err(&bebob->unit->device, + "fail to execute Terratek command: %02X\n", + buf[0]); + err = -EIO; + goto end; + } + + *synced = (buf[7] = 0x01); +end: + kfree(buf); + return err; +} + +static char *phase24_series_clk_src_labels[] = { + SND_BEBOB_CLOCK_INTERNAL, "Digital In" +}; +static int +phase24_series_clk_src_get(struct snd_bebob *bebob, unsigned int *id) +{ + return avc_audio_get_selector(bebob->unit, 0, 4, id); +} +static int +phase24_series_clk_src_set(struct snd_bebob *bebob, unsigned int id) +{ + return avc_audio_set_selector(bebob->unit, 0, 4, id); +} + +/* PHASE 88 Rack FW */ +struct snd_bebob_clock_spec phase88_rack_clk = { + .num = ARRAY_SIZE(phase88_rack_clk_src_labels), + .labels = phase88_rack_clk_src_labels, + .get_src = &phase88_rack_clk_src_get, + .set_src = &phase88_rack_clk_src_set, + .get_freq = &snd_bebob_stream_get_rate, + .set_freq = &snd_bebob_stream_set_rate, + .synced = &phase88_rack_clk_synced +}; +struct snd_bebob_spec phase88_rack_spec = { + .load = NULL, + .clock = &phase88_rack_clk, + .meter = NULL +}; + +/* 'PHASE 24 FW' and 'PHASE X24 FW' */ +struct snd_bebob_clock_spec phase24_series_clk = { + .num = ARRAY_SIZE(phase24_series_clk_src_labels), + .labels = phase24_series_clk_src_labels, + .get_src = &phase24_series_clk_src_get, + .set_src = &phase24_series_clk_src_set, + .get_freq = &snd_bebob_stream_get_rate, + .set_freq = &snd_bebob_stream_set_rate, + .synced = NULL +}; +struct snd_bebob_spec phase24_series_spec = { + .load = NULL, + .clock = &phase24_series_clk, + .meter = NULL +};
This commit allows this driver to support all of models which Yamaha produced with BeBoB chipset. They are: - GO44 - GO46
This commit adds Yamaha specific operations, quirks for three functionalities.
1. Switching source of clock 2. Detecting signal in digital input 3. Check clock synchronization
The driver uses these functionalities with AV/C vendor specific commands and AV/C Audio Subunit commands.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/Kconfig | 1 + sound/firewire/bebob/Makefile | 1 + sound/firewire/bebob/bebob.c | 7 ++ sound/firewire/bebob/bebob.h | 1 + sound/firewire/bebob/bebob_yamaha.c | 176 ++++++++++++++++++++++++++++++++++++ 5 files changed, 186 insertions(+) create mode 100644 sound/firewire/bebob/bebob_yamaha.c
diff --git a/sound/firewire/Kconfig b/sound/firewire/Kconfig index 502dce9..08ecc18 100644 --- a/sound/firewire/Kconfig +++ b/sound/firewire/Kconfig @@ -84,6 +84,7 @@ config SND_BEBOB * M-Audio Firewire1814/ProjectMix IO/ProfireLightBridge * Focusrite Saffire/Saffire LE/SaffirePro10 IO/SaffirePro26 IO * TerraTec PHASE 24 FW/PHASE X24 FW/PHASE 88 Rack FW + * Yamaha GO44/GO46
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 2fefcec..a81540b 100644 --- a/sound/firewire/bebob/Makefile +++ b/sound/firewire/bebob/Makefile @@ -1,5 +1,6 @@ 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_focusrite.o bebob_terratec.o \ + bebob_yamaha.o \ bebob.o obj-m += snd-bebob.o diff --git a/sound/firewire/bebob/bebob.c b/sound/firewire/bebob/bebob.c index 7c5576d..d871fae 100644 --- a/sound/firewire/bebob/bebob.c +++ b/sound/firewire/bebob/bebob.c @@ -54,6 +54,7 @@ static unsigned int devices_used; #define VEN_MAUDIO2 0x000007f5 #define VEN_FOCUSRITE 0x0000130e #define VEN_TERRATEC 0x00000aac +#define VEN_YAMAHA 0x0000a0de
#define MODEL_MAUDIO_AUDIOPHILE_BOTH 0x00010060 #define MODEL_MAUDIO_FW1814 0x00010071 @@ -95,6 +96,8 @@ name_device(struct snd_bebob *bebob, unsigned int vendor_id) strcpy(vendor, "M-Audio"); else if (vendor_id == VEN_TERRATEC) strcpy(vendor, "Terratec"); + else if (vendor_id == VEN_YAMAHA) + strcpy(vendor, "YAMAHA");
/* get model name */ err = fw_csr_string(bebob->unit->directory, CSR_MODEL, @@ -403,6 +406,10 @@ static const struct ieee1394_device_id snd_bebob_id_table[] = { SND_BEBOB_DEV_ENTRY(VEN_TERRATEC, 0x00000004, phase24_series_spec), /* TerraTecElectronic GmbH, Phase X24 FW */ SND_BEBOB_DEV_ENTRY(VEN_TERRATEC, 0x00000007, phase24_series_spec), + /* Yamaha, GO44 */ + SND_BEBOB_DEV_ENTRY(VEN_YAMAHA, 0x0010000b, yamaha_go_spec), + /* YAMAHA, GO46 */ + SND_BEBOB_DEV_ENTRY(VEN_YAMAHA, 0x0010000c, yamaha_go_spec), {} }; MODULE_DEVICE_TABLE(ieee1394, snd_bebob_id_table); diff --git a/sound/firewire/bebob/bebob.h b/sound/firewire/bebob/bebob.h index 6a92f43..b3211b6 100644 --- a/sound/firewire/bebob/bebob.h +++ b/sound/firewire/bebob/bebob.h @@ -240,6 +240,7 @@ extern struct snd_bebob_spec saffire_le_spec; extern struct snd_bebob_spec saffire_spec; extern struct snd_bebob_spec phase88_rack_spec; extern struct snd_bebob_spec phase24_series_spec; +extern struct snd_bebob_spec yamaha_go_spec;
#define SND_BEBOB_DEV_ENTRY(vendor, model, private_data) \ { \ diff --git a/sound/firewire/bebob/bebob_yamaha.c b/sound/firewire/bebob/bebob_yamaha.c new file mode 100644 index 0000000..fc8a44f --- /dev/null +++ b/sound/firewire/bebob/bebob_yamaha.c @@ -0,0 +1,176 @@ +/* + * bebob_yamaha.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: + * Yamaha GO44 is not considered to be used as stand-alone mixer. So any streams + * must be accompanied. If changing the state, a LED on the device starts to + * blink and its sync status is false. In this state, the device sounds nothing + * even if streaming. To start streaming at the current sampling rate is only + * way to revocer this state. GO46 is better for stand-alone mixer. + * + * Both of them have a capability to change its sampling rate up to 192.0kHz. + * At 192.0kHz, the device reports 4 PCM-in, 1 MIDI-in, 6 PCM-out, 1 MIDI-out. + * But Yamaha's driver reduce 2 PCM-in, 1 MIDI-in, 2 PCM-out, 1 MIDI-out to use + * 'Extended Stream Format Information Command - Single Request' in 'Additional + * AVC commands' defined by BridgeCo. + * This ALSA driver don't do this because a bit tiresome. Then isochronous + * streaming with many asynchronous transactions brings sounds with noises. + * Unfortunately current 'ffado-mixer' generated many asynchronous transaction + * to observe device's state, mainly check cmp connection and signal format. I + * reccomend users to close ffado-mixer at 192.0kHz if mixer is needless. + */ + +static int +detect_dig_in(struct snd_bebob *bebob, unsigned int *detect) +{ + int err; + u8 *buf; + + buf = kmalloc(12, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + /* This is a vendor dependent command */ + buf[0] = 0x01; /* STATUS */ + buf[1] = 0xff; /* UNIT */ + buf[2] = 0x00; /* Vendor Dependent command */ + buf[3] = 0x00; /* Company ID high */ + buf[4] = 0x07; /* Company ID middle */ + buf[5] = 0xf5; /* Company ID low */ + buf[6] = 0x00; /* Unknown Subfunction*/ + buf[7] = 0x00; /* Unknown */ + buf[8] = 0x01; /* Unknown */ + buf[9] = 0x00; /* Unknown */ + buf[10] = 0x00; /* Unknown */ + buf[11] = 0x00; /* Unknown */ + + /* do transaction and check buf[1-6] 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)); + if (err < 0) + goto end; + /* IMPLEMENTED/STABLE is OK */ + if ((err < 6) || (buf[0] != 0x0c)){ + dev_err(&bebob->unit->device, + "failed to detect clock source 0x%02X\n", + buf[0]); + err = -EIO; + goto end; + } + + /* when digital clock input exists, 10th byte is 0x01 */ + *detect = (buf[9] > 0); + err = 0; +end: + return err; +} + +static int +get_sync_status(struct snd_bebob *bebob, bool *sync) +{ + u8 *buf; + int err; + + buf = kmalloc(8, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + buf[0] = 0x01; /* AV/C STATUS */ + buf[1] = 0xFF; /* UNIT */ + buf[2] = 0x00; /* Vendor Specific Command */ + buf[3] = 0x01; /* Company ID high */ + buf[4] = 0x02; /* Company ID middle */ + buf[5] = 0x03; /* Company ID low */ + buf[6] = 0x21; /* unknown subfunction */ + buf[7] = 0xff; /* status */ + + err = fcp_avc_transaction(bebob->unit, buf, 8, buf, 8, + BIT(1) | BIT(2) | BIT(3) | BIT(4) | + BIT(5) | BIT(6)); + if (err < 0) + goto end; + if ((err < 6) || (buf[0] != 0x0c)) { + dev_err(&bebob->unit->device, + "failed to get sync status\n"); + err = -EIO; + goto end; + } + + /* 0x00 if losing sync */ + *sync = (buf[7] != 0x00); + err = 0; +end: + kfree(buf); + return err; +} + +static char *clk_src_labels[] = {SND_BEBOB_CLOCK_INTERNAL, "SPDIF"}; +static int +clk_src_set(struct snd_bebob *bebob, unsigned int id) +{ + int err; + unsigned int detect; + + if (id > 0) { + err = detect_dig_in(bebob, &detect); + if ((err < 0) || (detect == 0)) + return -EIO; + } + + err = avc_audio_set_selector(bebob->unit, 0, 4, id); + if (err < 0) + goto end; + + /* + * Yamaha BeBob returns 'IN TRANSITION' status just after returning to + * internal clock + */ + if (id == 0) + msleep(1500); + +end: + return err; +} +static int +clk_src_get(struct snd_bebob *bebob, unsigned int *id) +{ + return avc_audio_get_selector(bebob->unit, 0, 4, id); +} +static int +clk_synced(struct snd_bebob *bebob, bool *synced) +{ + return get_sync_status(bebob, synced); +} + +static struct snd_bebob_clock_spec clock_spec = { + .num = ARRAY_SIZE(clk_src_labels), + .labels = clk_src_labels, + .get_src = &clk_src_get, + .set_src = &clk_src_set, + .get_freq = &snd_bebob_stream_get_rate, + .set_freq = &snd_bebob_stream_set_rate, + .synced = &clk_synced +}; +struct snd_bebob_spec yamaha_go_spec = { + .load = NULL, + .clock = &clock_spec, + .meter = NULL +};
This commit allows this driver to support FIREBOX/FIREPOD which PreSonus produced with BeBoB chipset.
This commit adds FIREBOX specific operations for switching source of clock with AV/C Signal Cource command. FIREPOD is not tested so use normal spec.
Signed-off-by: Takashi Sakamoto o-takashi@sakamocchi.jp --- sound/firewire/Kconfig | 1 + sound/firewire/bebob/Makefile | 2 +- sound/firewire/bebob/bebob.c | 7 ++++ sound/firewire/bebob/bebob.h | 1 + sound/firewire/bebob/bebob_presonus.c | 75 +++++++++++++++++++++++++++++++++++ 5 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 sound/firewire/bebob/bebob_presonus.c
diff --git a/sound/firewire/Kconfig b/sound/firewire/Kconfig index 08ecc18..3879a9b 100644 --- a/sound/firewire/Kconfig +++ b/sound/firewire/Kconfig @@ -85,6 +85,7 @@ config SND_BEBOB * Focusrite Saffire/Saffire LE/SaffirePro10 IO/SaffirePro26 IO * TerraTec PHASE 24 FW/PHASE X24 FW/PHASE 88 Rack FW * Yamaha GO44/GO46 + * PreSonus FIREBOX/FirePod
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 a81540b..4510460 100644 --- a/sound/firewire/bebob/Makefile +++ b/sound/firewire/bebob/Makefile @@ -1,6 +1,6 @@ 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_focusrite.o bebob_terratec.o \ - bebob_yamaha.o \ + bebob_yamaha.o bebob_presonus.o \ bebob.o obj-m += snd-bebob.o diff --git a/sound/firewire/bebob/bebob.c b/sound/firewire/bebob/bebob.c index d871fae..fdda63e 100644 --- a/sound/firewire/bebob/bebob.c +++ b/sound/firewire/bebob/bebob.c @@ -55,6 +55,7 @@ static unsigned int devices_used; #define VEN_FOCUSRITE 0x0000130e #define VEN_TERRATEC 0x00000aac #define VEN_YAMAHA 0x0000a0de +#define VEN_PRESONUS 0x00000a92
#define MODEL_MAUDIO_AUDIOPHILE_BOTH 0x00010060 #define MODEL_MAUDIO_FW1814 0x00010071 @@ -98,6 +99,8 @@ name_device(struct snd_bebob *bebob, unsigned int vendor_id) strcpy(vendor, "Terratec"); else if (vendor_id == VEN_YAMAHA) strcpy(vendor, "YAMAHA"); + else if (vendor_id == VEN_PRESONUS) + strcpy(vendor, "Presonus");
/* get model name */ err = fw_csr_string(bebob->unit->directory, CSR_MODEL, @@ -410,6 +413,10 @@ static const struct ieee1394_device_id snd_bebob_id_table[] = { SND_BEBOB_DEV_ENTRY(VEN_YAMAHA, 0x0010000b, yamaha_go_spec), /* YAMAHA, GO46 */ SND_BEBOB_DEV_ENTRY(VEN_YAMAHA, 0x0010000c, yamaha_go_spec), + /* Presonus, FireBox */ + SND_BEBOB_DEV_ENTRY(VEN_PRESONUS, 0x00010000, presonus_firebox_spec), + /* PreSonus FirePod */ + SND_BEBOB_DEV_ENTRY(VEN_PRESONUS, 0x00010066, 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 b3211b6..1f562d4 100644 --- a/sound/firewire/bebob/bebob.h +++ b/sound/firewire/bebob/bebob.h @@ -241,6 +241,7 @@ extern struct snd_bebob_spec saffire_spec; extern struct snd_bebob_spec phase88_rack_spec; extern struct snd_bebob_spec phase24_series_spec; extern struct snd_bebob_spec yamaha_go_spec; +extern struct snd_bebob_spec presonus_firebox_spec;
#define SND_BEBOB_DEV_ENTRY(vendor, model, private_data) \ { \ diff --git a/sound/firewire/bebob/bebob_presonus.c b/sound/firewire/bebob/bebob_presonus.c new file mode 100644 index 0000000..cf419b3 --- /dev/null +++ b/sound/firewire/bebob/bebob_presonus.c @@ -0,0 +1,75 @@ +/* + * bebob_presonus.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" + +/* FIREBOX specific controls */ +static char *firebox_clk_src_labels[] = { + SND_BEBOB_CLOCK_INTERNAL, "Digital Coaxial" +}; +static int +firebox_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, 0x05); + if (err < 0) + goto end; + + if ((stype != 0x1f) && (sid != 0x07) && (pid != 0x83)) + *id = 0; + else + *id = 1; +end: + return err; +} +static int +firebox_clk_src_set(struct snd_bebob *bebob, unsigned int id) +{ + unsigned int stype, sid, pid; + + if (id != 1) { + stype = 0x1f; + sid = 0x07; + pid = 0x83; + } else { + stype = 0x0c; + sid = 0x00; + pid = 0x01; + } + + return avc_ccm_set_sig_src(bebob->unit, + stype, sid, pid, 0x0c, 0x00, 0x05); +} + +/* FIREBOX specification */ +static struct snd_bebob_clock_spec firebox_clk_spec = { + .num = ARRAY_SIZE(firebox_clk_src_labels), + .labels = firebox_clk_src_labels, + .get_src = &firebox_clk_src_get, + .set_src = &firebox_clk_src_set, + .get_freq = &snd_bebob_stream_get_rate, + .set_freq = &snd_bebob_stream_set_rate, + .synced = NULL +}; +struct snd_bebob_spec presonus_firebox_spec = { + .load = NULL, + .clock = &firebox_clk_spec, + .meter = NULL +};
Hi Takashi,
On 11/23/2013 07:09 AM, Takashi Sakamoto wrote:
In previous series of patch, I showed enhancement of current firewire-lib. So I should show the way to use new functions in each driver side. This driver is under development but functionalities for streaming seems to be fixed. I'm happy to receive your comments.
I have tried this series (incl. the bug fix you recommend) on a FA-66. While I see some output in alsa-info, pulseaudio didn't pick it up. I also noted that some logs in the dmesg. I am not sure what is supposed to work already or not.
Here are my logs
http://www.monom.org/misc/alsa-firewire/2013-11-30
cheers, daniel
Hi Daniel,
Thanks for your testing.
At first, I describe our system to test. I use Ubuntu 13.10. You use Fedora 19.
(Dec 01 2013 02:55), Daniel Wagner wrote:
I have tried this series (incl. the bug fix you recommend) on a FA-66. While I see some output in alsa-info, pulseaudio didn't pick it up. I also noted that some logs in the dmesg. I am not sure what is supposed to work already or not.
Here are my logs
1.pulseaudio PulseAudio can automatically load 'module-alsa-card' with 'module-udev-detect' (utilizing sysfs/dbus). But for snd-bebob, this mecanism doesn't work because PulseAudio cannot decide profiles for the card even if success to detect.
For this problem, I have a need to commit some codes for PulseAudio but this may be my future work.
Well, when I test with PulseAudio, I always directly load 'module-alsa-sink' or 'module-alsa-source' by these lines: $ pactl load-module module-alsa-sink device=hw:X,X $ pactl load-module module-alsa-source device=hw:X,X
See in detail: http://www.freedesktop.org/wiki/Software/PulseAudio/Documentation/User/Modul...
2.dmesg Hm. I want to know which asynchronous transaction is failed. [ 446.796563] snd-bebob fw1.0: transaction failed: no ack [ 446.838567] snd-bebob fw1.0: transaction failed: no ack
Would you investigate which transactions cannot get 'no ack'?
Thank you
Takashi Sakamoto
Hi Takashi,
On 12/01/2013 05:42 AM, Takashi Sakamoto wrote:
Hi Daniel,
Thanks for your testing.
No problem.
At first, I describe our system to test. I use Ubuntu 13.10. You use Fedora 19.
Be aware I have a lot of own build libraries flying around (e.g. libiec61883, libraw1394, ...)
(Dec 01 2013 02:55), Daniel Wagner wrote:
I have tried this series (incl. the bug fix you recommend) on a FA-66. While I see some output in alsa-info, pulseaudio didn't pick it up. I also noted that some logs in the dmesg. I am not sure what is supposed to work already or not.
Here are my logs
1.pulseaudio PulseAudio can automatically load 'module-alsa-card' with 'module-udev-detect' (utilizing sysfs/dbus). But for snd-bebob, this mecanism doesn't work because PulseAudio cannot decide profiles for the card even if success to detect.
For this problem, I have a need to commit some codes for PulseAudio but this may be my future work.
Well, when I test with PulseAudio, I always directly load 'module-alsa-sink' or 'module-alsa-source' by these lines: $ pactl load-module module-alsa-sink device=hw:X,X $ pactl load-module module-alsa-source device=hw:X,X
I got the FA-66 now working (playback) with explicit loading of the modules.
2.dmesg Hm. I want to know which asynchronous transaction is failed. [ 446.796563] snd-bebob fw1.0: transaction failed: no ack [ 446.838567] snd-bebob fw1.0: transaction failed: no ack
I was not able to reproduce this so far. Note that I a have crapy firewire card. So that might just be the main reason why we see these.
Would you investigate which transactions cannot get 'no ack'?
I have uploaded some two logs. The first one is for the fa-66 (fw-fa66.txt) and the second one is for the Audio5 (fw-a5.txt).
There seems to be an issue with the Audio5, though I haven't looked into it yet (quite busy, sorry) For that device I even got a serial console to it) Maybe you see the problem already from the log.
http://www.monom.org/misc/alsa-firewire/2013-12-05/
cheers, daniel
Hi Daniel,
I'm sorry to be late for reply but I was hard to prepare series of patch for Fireworks driver.
I have uploaded some two logs. The first one is for the fa-66 (fw-fa66.txt) and the second one is for the Audio5 (fw-a5.txt).
There seems to be an issue with the Audio5, though I haven't looked into it yet (quite busy, sorry) For that device I even got a serial console to it) Maybe you see the problem already from the log.
In the log of Audio5, there were much lock transaction for iPCR[0]/oPCR[0]. This means that snd-bebob tried to establish connection several times to start transmitting/receiving streams.
I guess that the module-alsa-sink of PulseAudio retried snd_pcm_prepare() several times due to get err. When snd_pcm_prepare() failed, snd_bebob_stream_start_duplex() is failed inner snd-bebob. Establishing connections is done by make_both_connections() so we should notice the codes after this function.
There are four possibilities, two of them are related to start_stream() and the others are related to amdtp_stream_wait_callback(). But the former generates log output. So amdtp_stream_wait_callback() causes this issue;
This function return false when snd-bebob cannot receive AMDTP in-packets within 100 msec after establishing connection. Please see amdtp.c in detail. I programmed this to prevent snd-bebob from waiting forever to receive in-packets.
So I guess you can playback/capture with your device when you expand the time (200 msec or more). Or there are some bugs of snd-bebob to handle your device.
Would you test and get logs again after expand waiting time? Then please use aplay/arecord to test. I believe simple tools help our investigating.
And I hope you to give me an advice for my headache. As long as I investigated, snd-bebob can support 64 device and models (I count the devices to which the same expansion card can apply). But there are some devices which I can't identify its vendor_id/model_id.
[Identified devices and models] * Edirol FA-66/FA-101 * PreSonus FIREBOX/FIREPOD * BridgeCo RDAudio1/Audio5 * Mackie Onyx 820/1220/1620/1640 (Firewire I/O Card) * Mackie d.2 (Firewire Option) * Stanton FinalScratch 2 (ScratchAmp) * Tascam IF-FW DM * Apogee Rosetta 200/400 (X-FireWire card) * Apogee DA/AD/DD-16X (X-FireWire card) * ESI Quotafire610 * AcousticReality eARMasterOne * CME MatrixKFW * Phonic HB 12/12 MkII/12 Universal * Phonic HB 18/18 MkII/18 Universal * Phonic HB 24/24 MkII/24 Universal * Phonic FireFly 202/302 * Lynx Aurora 8/16 (LT-FW) * ICON FireXon * PrismSound Orpheus/ADA-8XR * Yamaha GO44/GO46 * TerraTec PHASE 24 FW/PHASE X24 FW/PHASE 88 Rack FW * Terratec EWS MIC2/EWS MIC4 * Terratec Aureon 7.1 Firewire * Focusrite Saffire/Saffire LE/SaffirePro10 IO/SaffirePro26 IO * M-Audio Firewire410/AudioPhile/Solo/Ozonic/NRV10 * M-Audio Firewire1814/ProjectMix IO/ProfireLightBridge
[Unidentified devices and models] * PreSonus, Inspire 1394 * Mackie, Digital X Bus x.200 * Mackie, Digital X Bus x.400 * CME, UF400e * Infrasonic, DewX * Infrasonic, Windy6 * Rolf Spuler, Firewire Guitar
Do you have any idea to identify them?
Thank you
Takashi Sakamoto o-takashi@sakamocchi.jp
Hi Daniel,
This function return false when snd-bebob cannot receive AMDTP in-packets within 100 msec after establishing connection.
I'm sorry but this is wrong. It's not after establishing connection. It's after starting isochronous receive context on OHCI1394 controller.
Regards
Takashi Sakamoto
(2013年12月11日 21:06), Takashi Sakamoto wrote:
Hi Daniel,
I'm sorry to be late for reply but I was hard to prepare series of patch for Fireworks driver.
I have uploaded some two logs. The first one is for the fa-66 (fw-fa66.txt) and the second one is for the Audio5 (fw-a5.txt).
There seems to be an issue with the Audio5, though I haven't looked into it yet (quite busy, sorry) For that device I even got a serial console to it) Maybe you see the problem already from the log.
In the log of Audio5, there were much lock transaction for iPCR[0]/oPCR[0]. This means that snd-bebob tried to establish connection several times to start transmitting/receiving streams.
I guess that the module-alsa-sink of PulseAudio retried snd_pcm_prepare() several times due to get err. When snd_pcm_prepare() failed, snd_bebob_stream_start_duplex() is failed inner snd-bebob. Establishing connections is done by make_both_connections() so we should notice the codes after this function.
There are four possibilities, two of them are related to start_stream() and the others are related to amdtp_stream_wait_callback(). But the former generates log output. So amdtp_stream_wait_callback() causes this issue;
This function return false when snd-bebob cannot receive AMDTP in-packets within 100 msec after establishing connection. Please see amdtp.c in detail. I programmed this to prevent snd-bebob from waiting forever to receive in-packets.
So I guess you can playback/capture with your device when you expand the time (200 msec or more). Or there are some bugs of snd-bebob to handle your device.
Would you test and get logs again after expand waiting time? Then please use aplay/arecord to test. I believe simple tools help our investigating.
And I hope you to give me an advice for my headache. As long as I investigated, snd-bebob can support 64 device and models (I count the devices to which the same expansion card can apply). But there are some devices which I can't identify its vendor_id/model_id.
[Identified devices and models]
- Edirol FA-66/FA-101
- PreSonus FIREBOX/FIREPOD
- BridgeCo RDAudio1/Audio5
- Mackie Onyx 820/1220/1620/1640 (Firewire I/O Card)
- Mackie d.2 (Firewire Option)
- Stanton FinalScratch 2 (ScratchAmp)
- Tascam IF-FW DM
- Apogee Rosetta 200/400 (X-FireWire card)
- Apogee DA/AD/DD-16X (X-FireWire card)
- ESI Quotafire610
- AcousticReality eARMasterOne
- CME MatrixKFW
- Phonic HB 12/12 MkII/12 Universal
- Phonic HB 18/18 MkII/18 Universal
- Phonic HB 24/24 MkII/24 Universal
- Phonic FireFly 202/302
- Lynx Aurora 8/16 (LT-FW)
- ICON FireXon
- PrismSound Orpheus/ADA-8XR
- Yamaha GO44/GO46
- TerraTec PHASE 24 FW/PHASE X24 FW/PHASE 88 Rack FW
- Terratec EWS MIC2/EWS MIC4
- Terratec Aureon 7.1 Firewire
- Focusrite Saffire/Saffire LE/SaffirePro10 IO/SaffirePro26 IO
- M-Audio Firewire410/AudioPhile/Solo/Ozonic/NRV10
- M-Audio Firewire1814/ProjectMix IO/ProfireLightBridge
[Unidentified devices and models]
- PreSonus, Inspire 1394
- Mackie, Digital X Bus x.200
- Mackie, Digital X Bus x.400
- CME, UF400e
- Infrasonic, DewX
- Infrasonic, Windy6
- Rolf Spuler, Firewire Guitar
Do you have any idea to identify them?
Thank you
Takashi Sakamoto o-takashi@sakamocchi.jp
Hi Takashi,
On 12/11/2013 01:06 PM, Takashi Sakamoto wrote:
I'm sorry to be late for reply but I was hard to prepare series of patch for Fireworks driver.
No problemo. I am also slow as a heavy droged slug :)
I have uploaded some two logs. The first one is for the fa-66 (fw-fa66.txt) and the second one is for the Audio5 (fw-a5.txt).
There seems to be an issue with the Audio5, though I haven't looked into it yet (quite busy, sorry) For that device I even got a serial console to it) Maybe you see the problem already from the log.
In the log of Audio5, there were much lock transaction for iPCR[0]/oPCR[0]. This means that snd-bebob tried to establish connection several times to start transmitting/receiving streams.
IIRC, I loaded the ALSA module by issuing
$ pactl load-module module-alsa-sink device=hw:X,X
I guess that the module-alsa-sink of PulseAudio retried snd_pcm_prepare() several times due to get err. When snd_pcm_prepare() failed, snd_bebob_stream_start_duplex() is failed inner snd-bebob. Establishing connections is done by make_both_connections() so we should notice the codes after this function.
There are four possibilities, two of them are related to start_stream() and the others are related to amdtp_stream_wait_callback(). But the former generates log output. So amdtp_stream_wait_callback() causes this issue;
This function return false when snd-bebob cannot receive AMDTP in-packets within 100 msec after establishing connection. Please see amdtp.c in detail. I programmed this to prevent snd-bebob from waiting forever to receive in-packets.
So I guess you can playback/capture with your device when you expand the time (200 msec or more). Or there are some bugs of snd-bebob to handle your device.
The A5 is basically the SDK from BridgeCo. It is surprising to see that this device shows these kind of generic errors.
Would you test and get logs again after expand waiting time? Then please use aplay/arecord to test. I believe simple tools help our investigating.
I'll give it a try coming week. Sorry for my really slow debugging/testing cycles.
And I hope you to give me an advice for my headache. As long as I investigated, snd-bebob can support 64 device and models (I count the devices to which the same expansion card can apply). But there are some devices which I can't identify its vendor_id/model_id.
You mean the vendor_id/model_id is not unique?
[Identified devices and models]
- Edirol FA-66/FA-101
- PreSonus FIREBOX/FIREPOD
- BridgeCo RDAudio1/Audio5
- Mackie Onyx 820/1220/1620/1640 (Firewire I/O Card)
- Mackie d.2 (Firewire Option)
- Stanton FinalScratch 2 (ScratchAmp)
- Tascam IF-FW DM
- Apogee Rosetta 200/400 (X-FireWire card)
- Apogee DA/AD/DD-16X (X-FireWire card)
- ESI Quotafire610
- AcousticReality eARMasterOne
- CME MatrixKFW
- Phonic HB 12/12 MkII/12 Universal
- Phonic HB 18/18 MkII/18 Universal
- Phonic HB 24/24 MkII/24 Universal
- Phonic FireFly 202/302
- Lynx Aurora 8/16 (LT-FW)
- ICON FireXon
- PrismSound Orpheus/ADA-8XR
- Yamaha GO44/GO46
- TerraTec PHASE 24 FW/PHASE X24 FW/PHASE 88 Rack FW
- Terratec EWS MIC2/EWS MIC4
- Terratec Aureon 7.1 Firewire
- Focusrite Saffire/Saffire LE/SaffirePro10 IO/SaffirePro26 IO
- M-Audio Firewire410/AudioPhile/Solo/Ozonic/NRV10
- M-Audio Firewire1814/ProjectMix IO/ProfireLightBridge
[Unidentified devices and models]
- PreSonus, Inspire 1394
- Mackie, Digital X Bus x.200
- Mackie, Digital X Bus x.400
- CME, UF400e
- Infrasonic, DewX
- Infrasonic, Windy6
- Rolf Spuler, Firewire Guitar
Do you have any idea to identify them?
If the vendor/model id is not unique, maybe a more heuristic approach is needed, e.g. by reading the number of in/out streams which makes the device unique. But as you see, I don't have a good idea here.
cheers, daniel
participants (2)
-
Daniel Wagner
-
Takashi Sakamoto