Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,6 @@
# SPDX-License-Identifier: GPL-2.0
snd-bebob-y := bebob_command.o bebob_stream.o bebob_proc.o bebob_midi.o \
bebob_pcm.o bebob_hwdep.o bebob_terratec.o \
bebob_yamaha_terratec.o bebob_focusrite.o bebob_maudio.o \
bebob.o
obj-$(CONFIG_SND_BEBOB) += snd-bebob.o
+510
View File
@@ -0,0 +1,510 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* bebob.c - a part of driver for BeBoB based devices
*
* Copyright (c) 2013-2014 Takashi Sakamoto
*/
/*
* BeBoB is 'BridgeCo enhanced Breakout Box'. This is installed to firewire
* devices with DM1000/DM1100/DM1500 chipset. It gives common way for host
* system to handle BeBoB based devices.
*/
#include "bebob.h"
MODULE_DESCRIPTION("BridgeCo BeBoB driver");
MODULE_AUTHOR("Takashi Sakamoto <o-takashi@sakamocchi.jp>");
MODULE_LICENSE("GPL");
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 DECLARE_BITMAP(devices_used, SNDRV_CARDS);
/* Offsets from information register. */
#define INFO_OFFSET_BEBOB_VERSION 0x08
#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_PRESONUS 0x00000a92
#define VEN_BRIDGECO 0x000007f5
#define VEN_MACKIE 0x00000ff2
#define VEN_STANTON 0x00001260
#define VEN_TASCAM 0x0000022e
#define VEN_BEHRINGER 0x00001564
#define VEN_APOGEE 0x000003db
#define VEN_ESI 0x00000f1b
#define VEN_CME 0x0000000a
#define VEN_PHONIC 0x00001496
#define VEN_LYNX 0x000019e5
#define VEN_ICON 0x00001a9e
#define VEN_PRISMSOUND 0x00001198
#define VEN_TERRATEC 0x00000aac
#define VEN_YAMAHA 0x0000a0de
#define VEN_FOCUSRITE 0x0000130e
#define VEN_MAUDIO 0x00000d6c
#define VEN_DIGIDESIGN 0x00a07e
#define OUI_SHOUYO 0x002327
#define MODEL_FOCUSRITE_SAFFIRE_BOTH 0x00000000
#define MODEL_MAUDIO_AUDIOPHILE_BOTH 0x00010060
#define MODEL_MAUDIO_FW1814 0x00010071
#define MODEL_MAUDIO_PROJECTMIX 0x00010091
#define MODEL_MAUDIO_PROFIRELIGHTBRIDGE 0x000100a1
static int
name_device(struct snd_bebob *bebob)
{
struct fw_device *fw_dev = fw_parent_device(bebob->unit);
char vendor[24] = {0};
char model[32] = {0};
u32 hw_id;
u32 data[2] = {0};
u32 revision;
int err;
/* get vendor name from root directory */
err = fw_csr_string(fw_dev->config_rom + 5, CSR_VENDOR,
vendor, sizeof(vendor));
if (err < 0)
goto end;
/* get model name from unit directory */
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->unit, INFO_OFFSET_HW_MODEL_ID,
&hw_id);
if (err < 0)
goto end;
/* get hardware revision */
err = snd_bebob_read_quad(bebob->unit, INFO_OFFSET_HW_MODEL_REVISION,
&revision);
if (err < 0)
goto end;
/* get GUID */
err = snd_bebob_read_block(bebob->unit, INFO_OFFSET_GUID,
data, sizeof(data));
if (err < 0)
goto end;
strscpy(bebob->card->driver, "BeBoB");
strscpy(bebob->card->shortname, model);
strscpy(bebob->card->mixername, model);
snprintf(bebob->card->longname, sizeof(bebob->card->longname),
"%s %s (id:%d, rev:%d), GUID %08x%08x at %s, S%d",
vendor, model, hw_id, revision,
data[0], data[1], dev_name(&bebob->unit->device),
100 << fw_dev->max_speed);
end:
return err;
}
static void
bebob_card_free(struct snd_card *card)
{
struct snd_bebob *bebob = card->private_data;
scoped_guard(mutex, &devices_mutex) {
clear_bit(bebob->card_index, devices_used);
}
snd_bebob_stream_destroy_duplex(bebob);
mutex_destroy(&bebob->mutex);
fw_unit_put(bebob->unit);
}
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 bool
check_audiophile_booted(struct fw_unit *unit)
{
char name[28] = {0};
if (fw_csr_string(unit->directory, CSR_MODEL, name, sizeof(name)) < 0)
return false;
return strncmp(name, "FW Audiophile Bootloader", 24) != 0;
}
static int detect_quirks(struct snd_bebob *bebob, const struct ieee1394_device_id *entry)
{
if (entry->vendor_id == VEN_MAUDIO) {
switch (entry->model_id) {
case MODEL_MAUDIO_PROFIRELIGHTBRIDGE:
// M-Audio ProFire Lightbridge has a quirk to transfer packets with
// discontinuous cycle or data block counter in early stage of packet
// streaming. The cycle span from the first packet with event is variable.
bebob->quirks |= SND_BEBOB_QUIRK_INITIAL_DISCONTINUOUS_DBC;
break;
case MODEL_MAUDIO_FW1814:
case MODEL_MAUDIO_PROJECTMIX:
// At high sampling rate, M-Audio special firmware transmits empty packet
// with the value of dbc incremented by 8.
bebob->quirks |= SND_BEBOB_QUIRK_WRONG_DBC;
break;
default:
break;
}
}
return 0;
}
static int bebob_probe(struct fw_unit *unit, const struct ieee1394_device_id *entry)
{
unsigned int card_index;
struct snd_card *card;
struct snd_bebob *bebob;
const struct snd_bebob_spec *spec;
int err;
if (entry->vendor_id == VEN_FOCUSRITE &&
entry->model_id == MODEL_FOCUSRITE_SAFFIRE_BOTH)
spec = get_saffire_spec(unit);
else if (entry->vendor_id == VEN_MAUDIO &&
entry->model_id == MODEL_MAUDIO_AUDIOPHILE_BOTH &&
!check_audiophile_booted(unit))
spec = NULL;
else
spec = (const struct snd_bebob_spec *)entry->driver_data;
if (spec == NULL) {
// To boot up M-Audio models.
if (entry->vendor_id == VEN_MAUDIO || entry->vendor_id == VEN_BRIDGECO)
return snd_bebob_maudio_load_firmware(unit);
else
return -ENODEV;
}
scoped_guard(mutex, &devices_mutex) {
for (card_index = 0; card_index < SNDRV_CARDS; card_index++) {
if (!test_bit(card_index, devices_used) && enable[card_index])
break;
}
if (card_index >= SNDRV_CARDS)
return -ENOENT;
err = snd_card_new(&unit->device, index[card_index], id[card_index], THIS_MODULE,
sizeof(*bebob), &card);
if (err < 0)
return err;
card->private_free = bebob_card_free;
set_bit(card_index, devices_used);
}
bebob = card->private_data;
bebob->unit = fw_unit_get(unit);
dev_set_drvdata(&unit->device, bebob);
bebob->card = card;
bebob->card_index = card_index;
bebob->spec = spec;
mutex_init(&bebob->mutex);
spin_lock_init(&bebob->lock);
init_waitqueue_head(&bebob->hwdep_wait);
err = name_device(bebob);
if (err < 0)
goto error;
err = detect_quirks(bebob, entry);
if (err < 0)
goto error;
if (bebob->spec == &maudio_special_spec) {
if (entry->model_id == MODEL_MAUDIO_FW1814)
err = snd_bebob_maudio_special_discover(bebob, true);
else
err = snd_bebob_maudio_special_discover(bebob, false);
} else {
err = snd_bebob_stream_discover(bebob);
}
if (err < 0)
goto error;
err = snd_bebob_stream_init_duplex(bebob);
if (err < 0)
goto error;
snd_bebob_proc_init(bebob);
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_pcm_devices(bebob);
if (err < 0)
goto error;
err = snd_bebob_create_hwdep_device(bebob);
if (err < 0)
goto error;
err = snd_card_register(card);
if (err < 0)
goto error;
if (entry->vendor_id == VEN_MAUDIO &&
(entry->model_id == MODEL_MAUDIO_FW1814 || entry->model_id == MODEL_MAUDIO_PROJECTMIX)) {
// This is a workaround. This bus reset seems to have an effect to make devices
// correctly handling transactions. Without this, the devices have gap_count
// mismatch. This causes much failure of transaction.
//
// Just after registration, user-land application receive signals from dbus and
// starts I/Os. To avoid I/Os till the future bus reset, registration is done in
// next update().
fw_schedule_bus_reset(fw_parent_device(bebob->unit)->card, false, true);
}
return 0;
error:
snd_card_free(card);
return err;
}
/*
* This driver doesn't update streams in bus reset handler.
*
* DM1000/ DM1100/DM1500 chipsets with BeBoB firmware transfer packets with
* discontinued counter at bus reset. This discontinuity is immediately
* detected in packet streaming layer, then it sets XRUN to PCM substream.
*
* ALSA PCM applications can know the XRUN by getting -EPIPE from PCM operation.
* Then, they can recover the PCM substream by executing ioctl(2) with
* SNDRV_PCM_IOCTL_PREPARE. 'struct snd_pcm_ops.prepare' is called and drivers
* restart packet streaming.
*
* The above processing may be executed before this bus-reset handler is
* executed. When this handler updates streams with current isochronous
* channels, the streams already have the current ones.
*/
static void
bebob_update(struct fw_unit *unit)
{
struct snd_bebob *bebob = dev_get_drvdata(&unit->device);
if (bebob == NULL)
return;
fcp_bus_reset(bebob->unit);
}
static void bebob_remove(struct fw_unit *unit)
{
struct snd_bebob *bebob = dev_get_drvdata(&unit->device);
if (bebob == NULL)
return;
// Block till all of ALSA character devices are released.
snd_card_free(bebob->card);
}
static const struct snd_bebob_rate_spec normal_rate_spec = {
.get = &snd_bebob_stream_get_rate,
.set = &snd_bebob_stream_set_rate
};
static const struct snd_bebob_spec spec_normal = {
.clock = NULL,
.rate = &normal_rate_spec,
.meter = NULL
};
#define SPECIFIER_1394TA 0x00a02d
// The immediate entry for version in unit directory differs depending on models:
// * 0x010001
// * 0x014001
#define SND_BEBOB_DEV_ENTRY(vendor, model, data) \
{ \
.match_flags = IEEE1394_MATCH_VENDOR_ID | \
IEEE1394_MATCH_MODEL_ID | \
IEEE1394_MATCH_SPECIFIER_ID, \
.vendor_id = vendor, \
.model_id = model, \
.specifier_id = SPECIFIER_1394TA, \
.driver_data = (kernel_ulong_t)data \
}
static const struct ieee1394_device_id bebob_id_table[] = {
/* Edirol, FA-66 */
SND_BEBOB_DEV_ENTRY(VEN_EDIROL, 0x00010049, &spec_normal),
/* Edirol, FA-101 */
SND_BEBOB_DEV_ENTRY(VEN_EDIROL, 0x00010048, &spec_normal),
/* Presonus, FIREBOX */
SND_BEBOB_DEV_ENTRY(VEN_PRESONUS, 0x00010000, &spec_normal),
/* PreSonus, FIREPOD/FP10 */
SND_BEBOB_DEV_ENTRY(VEN_PRESONUS, 0x00010066, &spec_normal),
/* PreSonus, Inspire1394 */
SND_BEBOB_DEV_ENTRY(VEN_PRESONUS, 0x00010001, &spec_normal),
/* BridgeCo, RDAudio1 */
SND_BEBOB_DEV_ENTRY(VEN_BRIDGECO, 0x00010048, &spec_normal),
/* BridgeCo, Audio5 */
SND_BEBOB_DEV_ENTRY(VEN_BRIDGECO, 0x00010049, &spec_normal),
/* Mackie, Onyx 1220/1620/1640 (Firewire I/O Card) */
SND_BEBOB_DEV_ENTRY(VEN_MACKIE, 0x00010065, &spec_normal),
// Mackie, d.2 (optional Firewire card with DM1000).
SND_BEBOB_DEV_ENTRY(VEN_MACKIE, 0x00010067, &spec_normal),
/* Stanton, ScratchAmp */
SND_BEBOB_DEV_ENTRY(VEN_STANTON, 0x00000001, &spec_normal),
/* Tascam, IF-FW DM */
SND_BEBOB_DEV_ENTRY(VEN_TASCAM, 0x00010067, &spec_normal),
/* Behringer, XENIX UFX 1204 */
SND_BEBOB_DEV_ENTRY(VEN_BEHRINGER, 0x00001204, &spec_normal),
/* Behringer, XENIX UFX 1604 */
SND_BEBOB_DEV_ENTRY(VEN_BEHRINGER, 0x00001604, &spec_normal),
/* Behringer, Digital Mixer X32 series (X-UF Card) */
SND_BEBOB_DEV_ENTRY(VEN_BEHRINGER, 0x00000006, &spec_normal),
/* Behringer, F-Control Audio 1616 */
SND_BEBOB_DEV_ENTRY(VEN_BEHRINGER, 0x001616, &spec_normal),
/* Behringer, F-Control Audio 610 */
SND_BEBOB_DEV_ENTRY(VEN_BEHRINGER, 0x000610, &spec_normal),
/* Apogee Electronics, Rosetta 200/400 (X-FireWire card) */
/* Apogee Electronics, DA/AD/DD-16X (X-FireWire card) */
SND_BEBOB_DEV_ENTRY(VEN_APOGEE, 0x00010048, &spec_normal),
/* Apogee Electronics, Ensemble */
SND_BEBOB_DEV_ENTRY(VEN_APOGEE, 0x01eeee, &spec_normal),
/* ESI, Quatafire610 */
SND_BEBOB_DEV_ENTRY(VEN_ESI, 0x00010064, &spec_normal),
/* CME, MatrixKFW */
SND_BEBOB_DEV_ENTRY(VEN_CME, 0x00030000, &spec_normal),
// Phonic Helix Board 12 FireWire MkII.
SND_BEBOB_DEV_ENTRY(VEN_PHONIC, 0x00050000, &spec_normal),
// Phonic Helix Board 18 FireWire MkII.
SND_BEBOB_DEV_ENTRY(VEN_PHONIC, 0x00060000, &spec_normal),
// Phonic Helix Board 24 FireWire MkII.
SND_BEBOB_DEV_ENTRY(VEN_PHONIC, 0x00070000, &spec_normal),
// Phonic FireFly 808 FireWire.
SND_BEBOB_DEV_ENTRY(VEN_PHONIC, 0x00080000, &spec_normal),
// Phonic FireFly 202, 302, 808 Universal.
// Phinic Helix Board 12/18/24 FireWire, 12/18/24 Universal
SND_BEBOB_DEV_ENTRY(VEN_PHONIC, 0x00000000, &spec_normal),
/* Lynx, Aurora 8/16 (LT-FW) */
SND_BEBOB_DEV_ENTRY(VEN_LYNX, 0x00000001, &spec_normal),
/* ICON, FireXon */
SND_BEBOB_DEV_ENTRY(VEN_ICON, 0x00000001, &spec_normal),
/* PrismSound, Orpheus */
SND_BEBOB_DEV_ENTRY(VEN_PRISMSOUND, 0x00010048, &spec_normal),
/* PrismSound, ADA-8XR */
SND_BEBOB_DEV_ENTRY(VEN_PRISMSOUND, 0x0000ada8, &spec_normal),
/* TerraTec Electronic GmbH, PHASE 88 Rack FW */
SND_BEBOB_DEV_ENTRY(VEN_TERRATEC, 0x00000003, &phase88_rack_spec),
/* TerraTec Electronic GmbH, PHASE 24 FW */
SND_BEBOB_DEV_ENTRY(VEN_TERRATEC, 0x00000004, &yamaha_terratec_spec),
/* TerraTec Electronic GmbH, Phase X24 FW */
SND_BEBOB_DEV_ENTRY(VEN_TERRATEC, 0x00000007, &yamaha_terratec_spec),
/* TerraTec Electronic GmbH, EWS MIC2/MIC8 */
SND_BEBOB_DEV_ENTRY(VEN_TERRATEC, 0x00000005, &spec_normal),
// Terratec Electronic GmbH, Aureon 7.1 Firewire.
// AcousticReality, eAR Master One, Eroica, Figaro, and Ciaccona. Perhaps Terratec OEM.
SND_BEBOB_DEV_ENTRY(VEN_TERRATEC, 0x00000002, &spec_normal),
/* Yamaha, GO44 */
SND_BEBOB_DEV_ENTRY(VEN_YAMAHA, 0x0010000b, &yamaha_terratec_spec),
/* YAMAHA, GO46 */
SND_BEBOB_DEV_ENTRY(VEN_YAMAHA, 0x0010000c, &yamaha_terratec_spec),
/* 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, 0x000006, &saffirepro_10_spec),
/* Focusrite, Saffire(no label and LE) */
SND_BEBOB_DEV_ENTRY(VEN_FOCUSRITE, MODEL_FOCUSRITE_SAFFIRE_BOTH,
&saffire_spec),
// M-Audio, Firewire 410. The vendor field is left as BridgeCo. AG.
SND_BEBOB_DEV_ENTRY(VEN_BRIDGECO, 0x00010058, NULL),
SND_BEBOB_DEV_ENTRY(VEN_BRIDGECO, 0x00010046, &maudio_fw410_spec),
/* M-Audio, Firewire Audiophile */
SND_BEBOB_DEV_ENTRY(VEN_MAUDIO, MODEL_MAUDIO_AUDIOPHILE_BOTH,
&maudio_audiophile_spec),
/* M-Audio, Firewire Solo */
SND_BEBOB_DEV_ENTRY(VEN_MAUDIO, 0x00010062, &maudio_solo_spec),
/* M-Audio, Ozonic */
SND_BEBOB_DEV_ENTRY(VEN_MAUDIO, 0x0000000a, &maudio_ozonic_spec),
/* M-Audio NRV10 */
SND_BEBOB_DEV_ENTRY(VEN_MAUDIO, 0x00010081, &maudio_nrv10_spec),
/* M-Audio, ProFireLightbridge */
SND_BEBOB_DEV_ENTRY(VEN_MAUDIO, MODEL_MAUDIO_PROFIRELIGHTBRIDGE, &spec_normal),
/* Firewire 1814 */
SND_BEBOB_DEV_ENTRY(VEN_MAUDIO, 0x00010070, NULL), /* bootloader */
SND_BEBOB_DEV_ENTRY(VEN_MAUDIO, MODEL_MAUDIO_FW1814,
&maudio_special_spec),
/* M-Audio ProjectMix */
SND_BEBOB_DEV_ENTRY(VEN_MAUDIO, MODEL_MAUDIO_PROJECTMIX,
&maudio_special_spec),
/* Digidesign Mbox 2 Pro */
SND_BEBOB_DEV_ENTRY(VEN_DIGIDESIGN, 0x0000a9, &spec_normal),
// Toneweal FW66.
SND_BEBOB_DEV_ENTRY(OUI_SHOUYO, 0x020002, &spec_normal),
/* IDs are unknown but able to be supported */
/* Apogee, Mini-ME Firewire */
/* Apogee, Mini-DAC Firewire */
/* Cakawalk, Sonar Power Studio 66 */
/* CME, UF400e */
/* ESI, Quotafire XL */
/* Infrasonic, DewX */
/* Infrasonic, Windy6 */
/* Mackie, Digital X Bus x.200 */
/* Mackie, Digital X Bus x.400 */
/* Rolf Spuler, Firewire Guitar */
{}
};
MODULE_DEVICE_TABLE(ieee1394, bebob_id_table);
static struct fw_driver bebob_driver = {
.driver = {
.owner = THIS_MODULE,
.name = KBUILD_MODNAME,
.bus = &fw_bus_type,
},
.probe = bebob_probe,
.update = bebob_update,
.remove = bebob_remove,
.id_table = bebob_id_table,
};
static int __init
snd_bebob_init(void)
{
return driver_register(&bebob_driver.driver);
}
static void __exit
snd_bebob_exit(void)
{
driver_unregister(&bebob_driver.driver);
}
module_init(snd_bebob_init);
module_exit(snd_bebob_exit);
+255
View File
@@ -0,0 +1,255 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* bebob.h - a part of driver for BeBoB based devices
*
* Copyright (c) 2013-2014 Takashi Sakamoto
*/
#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/delay.h>
#include <linux/slab.h>
#include <linux/sched/signal.h>
#include <sound/core.h>
#include <sound/initval.h>
#include <sound/info.h>
#include <sound/rawmidi.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/firewire.h>
#include <sound/hwdep.h>
#include "../lib.h"
#include "../fcp.h"
#include "../packets-buffer.h"
#include "../iso-resources.h"
#include "../amdtp-am824.h"
#include "../cmp.h"
/* basic register addresses on DM1000/DM1100/DM1500 */
#define BEBOB_ADDR_REG_INFO 0xffffc8020000ULL
#define BEBOB_ADDR_REG_REQ 0xffffc8021000ULL
struct snd_bebob;
#define SND_BEBOB_STRM_FMT_ENTRIES 7
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];
/* device specific operations */
enum snd_bebob_clock_type {
SND_BEBOB_CLOCK_TYPE_INTERNAL = 0,
SND_BEBOB_CLOCK_TYPE_EXTERNAL,
SND_BEBOB_CLOCK_TYPE_SYT,
};
struct snd_bebob_clock_spec {
unsigned int num;
const char *const *labels;
const enum snd_bebob_clock_type *types;
int (*get)(struct snd_bebob *bebob, unsigned int *id);
};
struct snd_bebob_rate_spec {
int (*get)(struct snd_bebob *bebob, unsigned int *rate);
int (*set)(struct snd_bebob *bebob, unsigned int rate);
};
struct snd_bebob_meter_spec {
unsigned int num;
const char *const *labels;
int (*get)(struct snd_bebob *bebob, u32 *target, unsigned int size);
};
struct snd_bebob_spec {
const struct snd_bebob_clock_spec *clock;
const struct snd_bebob_rate_spec *rate;
const struct snd_bebob_meter_spec *meter;
};
enum snd_bebob_quirk {
SND_BEBOB_QUIRK_INITIAL_DISCONTINUOUS_DBC = (1 << 0),
SND_BEBOB_QUIRK_WRONG_DBC = (1 << 1),
};
struct snd_bebob {
struct snd_card *card;
struct fw_unit *unit;
int card_index;
struct mutex mutex;
spinlock_t lock;
const struct snd_bebob_spec *spec;
unsigned int quirks; // Combination of snd_bebob_quirk enumerations.
unsigned int midi_input_ports;
unsigned int midi_output_ports;
struct amdtp_stream tx_stream;
struct amdtp_stream rx_stream;
struct cmp_connection out_conn;
struct cmp_connection in_conn;
unsigned int substreams_counter;
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];
int sync_input_plug;
/* for uapi */
int dev_lock_count;
bool dev_lock_changed;
wait_queue_head_t hwdep_wait;
/* for M-Audio special devices */
void *maudio_special_quirk;
struct amdtp_domain domain;
};
static inline int
snd_bebob_read_block(struct fw_unit *unit, u64 addr, void *buf, int size)
{
return snd_fw_transaction(unit, TCODE_READ_BLOCK_REQUEST,
BEBOB_ADDR_REG_INFO + addr,
buf, size, 0);
}
static inline int
snd_bebob_read_quad(struct fw_unit *unit, u64 addr, u32 *buf)
{
return snd_fw_transaction(unit, TCODE_READ_QUADLET_REQUEST,
BEBOB_ADDR_REG_INFO + addr,
(void *)buf, sizeof(u32), 0);
}
/* AV/C Audio Subunit Specification 1.0 (Oct 2000, 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);
/*
* AVC command extensions, AV/C Unit and Subunit, Revision 17
* (Nov 2003, BridgeCo)
*/
#define AVC_BRIDGECO_ADDR_BYTES 6
enum avc_bridgeco_plug_dir {
AVC_BRIDGECO_PLUG_DIR_IN = 0x00,
AVC_BRIDGECO_PLUG_DIR_OUT = 0x01
};
enum avc_bridgeco_plug_mode {
AVC_BRIDGECO_PLUG_MODE_UNIT = 0x00,
AVC_BRIDGECO_PLUG_MODE_SUBUNIT = 0x01,
AVC_BRIDGECO_PLUG_MODE_FUNCTION_BLOCK = 0x02
};
enum avc_bridgeco_plug_unit {
AVC_BRIDGECO_PLUG_UNIT_ISOC = 0x00,
AVC_BRIDGECO_PLUG_UNIT_EXT = 0x01,
AVC_BRIDGECO_PLUG_UNIT_ASYNC = 0x02
};
enum avc_bridgeco_plug_type {
AVC_BRIDGECO_PLUG_TYPE_ISOC = 0x00,
AVC_BRIDGECO_PLUG_TYPE_ASYNC = 0x01,
AVC_BRIDGECO_PLUG_TYPE_MIDI = 0x02,
AVC_BRIDGECO_PLUG_TYPE_SYNC = 0x03,
AVC_BRIDGECO_PLUG_TYPE_ANA = 0x04,
AVC_BRIDGECO_PLUG_TYPE_DIG = 0x05,
AVC_BRIDGECO_PLUG_TYPE_ADDITION = 0x06
};
static inline void
avc_bridgeco_fill_unit_addr(u8 buf[AVC_BRIDGECO_ADDR_BYTES],
enum avc_bridgeco_plug_dir dir,
enum avc_bridgeco_plug_unit unit,
unsigned int pid)
{
buf[0] = 0xff; /* Unit */
buf[1] = dir;
buf[2] = AVC_BRIDGECO_PLUG_MODE_UNIT;
buf[3] = unit;
buf[4] = 0xff & pid;
buf[5] = 0xff; /* reserved */
}
static inline void
avc_bridgeco_fill_msu_addr(u8 buf[AVC_BRIDGECO_ADDR_BYTES],
enum avc_bridgeco_plug_dir dir,
unsigned int pid)
{
buf[0] = 0x60; /* Music subunit */
buf[1] = dir;
buf[2] = AVC_BRIDGECO_PLUG_MODE_SUBUNIT;
buf[3] = 0xff & pid;
buf[4] = 0xff; /* reserved */
buf[5] = 0xff; /* reserved */
}
int avc_bridgeco_get_plug_ch_pos(struct fw_unit *unit,
u8 addr[AVC_BRIDGECO_ADDR_BYTES],
u8 *buf, unsigned int len);
int avc_bridgeco_get_plug_type(struct fw_unit *unit,
u8 addr[AVC_BRIDGECO_ADDR_BYTES],
enum avc_bridgeco_plug_type *type);
int avc_bridgeco_get_plug_ch_count(struct fw_unit *unit, u8 addr[AVC_BRIDGECO_ADDR_BYTES],
unsigned int *ch_count);
int avc_bridgeco_get_plug_section_type(struct fw_unit *unit,
u8 addr[AVC_BRIDGECO_ADDR_BYTES],
unsigned int id, u8 *type);
int avc_bridgeco_get_plug_input(struct fw_unit *unit,
u8 addr[AVC_BRIDGECO_ADDR_BYTES],
u8 input[7]);
int avc_bridgeco_get_plug_strm_fmt(struct fw_unit *unit,
u8 addr[AVC_BRIDGECO_ADDR_BYTES], u8 *buf,
unsigned int *len, unsigned int eid);
/* 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_get_clock_src(struct snd_bebob *bebob,
enum snd_bebob_clock_type *src);
int snd_bebob_stream_discover(struct snd_bebob *bebob);
int snd_bebob_stream_init_duplex(struct snd_bebob *bebob);
int snd_bebob_stream_reserve_duplex(struct snd_bebob *bebob, unsigned int rate,
unsigned int frames_per_period,
unsigned int frames_per_buffer);
int snd_bebob_stream_start_duplex(struct snd_bebob *bebob);
void snd_bebob_stream_stop_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_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);
/* model specific operations */
extern const struct snd_bebob_spec phase88_rack_spec;
extern const struct snd_bebob_spec yamaha_terratec_spec;
extern const struct snd_bebob_spec saffirepro_26_spec;
extern const struct snd_bebob_spec saffirepro_10_spec;
extern const struct snd_bebob_spec saffire_le_spec;
extern const struct snd_bebob_spec saffire_spec;
extern const struct snd_bebob_spec maudio_fw410_spec;
extern const struct snd_bebob_spec maudio_audiophile_spec;
extern const struct snd_bebob_spec maudio_solo_spec;
extern const struct snd_bebob_spec maudio_ozonic_spec;
extern const struct snd_bebob_spec maudio_nrv10_spec;
extern const struct snd_bebob_spec maudio_special_spec;
int snd_bebob_maudio_special_discover(struct snd_bebob *bebob, bool is1814);
int snd_bebob_maudio_load_firmware(struct fw_unit *unit);
#endif
@@ -0,0 +1,331 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* bebob_command.c - driver for BeBoB based devices
*
* Copyright (c) 2013-2014 Takashi Sakamoto
*/
#include "./bebob.h"
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 */
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)
;
else if (err < 9)
err = -EIO;
else if (buf[0] == 0x08) /* NOT IMPLEMENTED */
err = -ENOSYS;
else if (buf[0] == 0x0a) /* REJECTED */
err = -EINVAL;
else
err = 0;
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 */
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)
;
else if (err < 9)
err = -EIO;
else if (buf[0] == 0x08) /* NOT IMPLEMENTED */
err = -ENOSYS;
else if (buf[0] == 0x0a) /* REJECTED */
err = -EINVAL;
else if (buf[0] == 0x0b) /* IN TRANSITION */
err = -EAGAIN;
if (err < 0)
goto end;
*num = buf[7];
err = 0;
end:
kfree(buf);
return err;
}
static inline void
avc_bridgeco_fill_extension_addr(u8 *buf, u8 *addr)
{
buf[1] = addr[0];
memcpy(buf + 4, addr + 1, 5);
}
static inline void
avc_bridgeco_fill_plug_info_extension_command(u8 *buf, u8 *addr,
unsigned int itype)
{
buf[0] = 0x01; /* AV/C STATUS */
buf[2] = 0x02; /* AV/C GENERAL PLUG INFO */
buf[3] = 0xc0; /* BridgeCo extension */
avc_bridgeco_fill_extension_addr(buf, addr);
buf[9] = itype; /* info type */
}
int avc_bridgeco_get_plug_type(struct fw_unit *unit,
u8 addr[AVC_BRIDGECO_ADDR_BYTES],
enum avc_bridgeco_plug_type *type)
{
u8 *buf;
int err;
buf = kzalloc(12, GFP_KERNEL);
if (buf == NULL)
return -ENOMEM;
/* Info type is 'plug type'. */
avc_bridgeco_fill_plug_info_extension_command(buf, addr, 0x00);
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)
;
else if (err < 11)
err = -EIO;
else if (buf[0] == 0x08) /* NOT IMPLEMENTED */
err = -ENOSYS;
else if (buf[0] == 0x0a) /* REJECTED */
err = -EINVAL;
else if (buf[0] == 0x0b) /* IN TRANSITION */
err = -EAGAIN;
if (err < 0)
goto end;
*type = buf[10];
err = 0;
end:
kfree(buf);
return err;
}
int avc_bridgeco_get_plug_ch_count(struct fw_unit *unit, u8 addr[AVC_BRIDGECO_ADDR_BYTES],
unsigned int *ch_count)
{
u8 *buf;
int err;
buf = kzalloc(12, GFP_KERNEL);
if (buf == NULL)
return -ENOMEM;
// Info type is 'plug type'.
avc_bridgeco_fill_plug_info_extension_command(buf, addr, 0x02);
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)
;
else if (err < 11)
err = -EIO;
else if (buf[0] == 0x08) // NOT IMPLEMENTED
err = -ENOSYS;
else if (buf[0] == 0x0a) // REJECTED
err = -EINVAL;
else if (buf[0] == 0x0b) // IN TRANSITION
err = -EAGAIN;
if (err < 0)
goto end;
*ch_count = buf[10];
err = 0;
end:
kfree(buf);
return err;
}
int avc_bridgeco_get_plug_ch_pos(struct fw_unit *unit,
u8 addr[AVC_BRIDGECO_ADDR_BYTES],
u8 *buf, unsigned int len)
{
int err;
/* Info type is 'channel position'. */
avc_bridgeco_fill_plug_info_extension_command(buf, addr, 0x03);
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)
;
else if (err < 11)
err = -EIO;
else if (buf[0] == 0x08) /* NOT IMPLEMENTED */
err = -ENOSYS;
else if (buf[0] == 0x0a) /* REJECTED */
err = -EINVAL;
else if (buf[0] == 0x0b) /* IN TRANSITION */
err = -EAGAIN;
if (err < 0)
goto end;
/* Pick up specific data. */
memmove(buf, buf + 10, err - 10);
err = 0;
end:
return err;
}
int avc_bridgeco_get_plug_section_type(struct fw_unit *unit,
u8 addr[AVC_BRIDGECO_ADDR_BYTES],
unsigned int id, u8 *type)
{
u8 *buf;
int err;
/* section info includes charactors but this module don't need it */
buf = kzalloc(12, GFP_KERNEL);
if (buf == NULL)
return -ENOMEM;
/* Info type is 'section info'. */
avc_bridgeco_fill_plug_info_extension_command(buf, addr, 0x07);
buf[10] = 0xff & ++id; /* section id */
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)
;
else if (err < 12)
err = -EIO;
else if (buf[0] == 0x08) /* NOT IMPLEMENTED */
err = -ENOSYS;
else if (buf[0] == 0x0a) /* REJECTED */
err = -EINVAL;
else if (buf[0] == 0x0b) /* IN TRANSITION */
err = -EAGAIN;
if (err < 0)
goto end;
*type = buf[11];
err = 0;
end:
kfree(buf);
return err;
}
int avc_bridgeco_get_plug_input(struct fw_unit *unit,
u8 addr[AVC_BRIDGECO_ADDR_BYTES], u8 input[7])
{
int err;
u8 *buf;
buf = kzalloc(18, GFP_KERNEL);
if (buf == NULL)
return -ENOMEM;
/* Info type is 'plug input'. */
avc_bridgeco_fill_plug_info_extension_command(buf, addr, 0x05);
err = fcp_avc_transaction(unit, buf, 16, buf, 16,
BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) |
BIT(6) | BIT(7));
if (err < 0)
;
else if (err < 16)
err = -EIO;
else if (buf[0] == 0x08) /* NOT IMPLEMENTED */
err = -ENOSYS;
else if (buf[0] == 0x0a) /* REJECTED */
err = -EINVAL;
else if (buf[0] == 0x0b) /* IN TRANSITION */
err = -EAGAIN;
if (err < 0)
goto end;
memcpy(input, buf + 10, 5);
err = 0;
end:
kfree(buf);
return err;
}
int avc_bridgeco_get_plug_strm_fmt(struct fw_unit *unit,
u8 addr[AVC_BRIDGECO_ADDR_BYTES], u8 *buf,
unsigned int *len, unsigned int eid)
{
int err;
/* check given buffer */
if ((buf == NULL) || (*len < 12)) {
err = -EINVAL;
goto end;
}
buf[0] = 0x01; /* AV/C STATUS */
buf[2] = 0x2f; /* AV/C STREAM FORMAT SUPPORT */
buf[3] = 0xc1; /* Bridgeco extension - List Request */
avc_bridgeco_fill_extension_addr(buf, addr);
buf[10] = 0xff & eid; /* Entry ID */
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)
;
else if (err < 12)
err = -EIO;
else if (buf[0] == 0x08) /* NOT IMPLEMENTED */
err = -ENOSYS;
else if (buf[0] == 0x0a) /* REJECTED */
err = -EINVAL;
else if (buf[0] == 0x0b) /* IN TRANSITION */
err = -EAGAIN;
else if (buf[10] != eid)
err = -EIO;
if (err < 0)
goto end;
/* Pick up 'stream format info'. */
memmove(buf, buf + 11, err - 11);
*len = err - 11;
err = 0;
end:
return err;
}
@@ -0,0 +1,322 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* bebob_focusrite.c - a part of driver for BeBoB based devices
*
* Copyright (c) 2013-2014 Takashi Sakamoto
*/
#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 0x000100000000ULL
#define SAFFIRE_OFFSET_CLOCK_SOURCE 0x00f8
#define SAFFIREPRO_OFFSET_CLOCK_SOURCE 0x0174
/* whether sync to external device or not */
#define SAFFIRE_OFFSET_CLOCK_SYNC_EXT 0x013c
#define SAFFIRE_LE_OFFSET_CLOCK_SYNC_EXT 0x0432
#define SAFFIREPRO_OFFSET_CLOCK_SYNC_EXT 0x0164
#define SAFFIRE_CLOCK_SOURCE_INTERNAL 0
#define SAFFIRE_CLOCK_SOURCE_SPDIF 1
/* clock sources as returned from register of Saffire Pro 10 and 26 */
#define SAFFIREPRO_CLOCK_SOURCE_SELECT_MASK 0x000000ff
#define SAFFIREPRO_CLOCK_SOURCE_DETECT_MASK 0x0000ff00
#define SAFFIREPRO_CLOCK_SOURCE_INTERNAL 0
#define SAFFIREPRO_CLOCK_SOURCE_SKIP 1 /* never used on hardware */
#define SAFFIREPRO_CLOCK_SOURCE_SPDIF 2
#define SAFFIREPRO_CLOCK_SOURCE_ADAT1 3 /* not used on s.pro. 10 */
#define SAFFIREPRO_CLOCK_SOURCE_ADAT2 4 /* not used on s.pro. 10 */
#define SAFFIREPRO_CLOCK_SOURCE_WORDCLOCK 5
#define SAFFIREPRO_CLOCK_SOURCE_COUNT 6
/* S/PDIF, ADAT1, ADAT2 is enabled or not. three quadlets */
#define SAFFIREPRO_ENABLE_DIG_IFACES 0x01a4
/* saffirepro has its own parameter for sampling frequency */
#define SAFFIREPRO_RATE_NOREBOOT 0x01cc
/* index is the value for this register */
static const 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 0x0100
#define SAFFIRE_LE_OFFSET_METER 0x0168
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(__be32), 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)
{
__be32 data = cpu_to_be32(value);
return snd_fw_transaction(bebob->unit, TCODE_WRITE_QUADLET_REQUEST,
SAFFIRE_ADDRESS_BASE + offset,
&data, sizeof(__be32), 0);
}
static const enum snd_bebob_clock_type saffirepro_10_clk_src_types[] = {
SND_BEBOB_CLOCK_TYPE_INTERNAL,
SND_BEBOB_CLOCK_TYPE_EXTERNAL, /* S/PDIF */
SND_BEBOB_CLOCK_TYPE_EXTERNAL, /* Word Clock */
};
static const enum snd_bebob_clock_type saffirepro_26_clk_src_types[] = {
SND_BEBOB_CLOCK_TYPE_INTERNAL,
SND_BEBOB_CLOCK_TYPE_EXTERNAL, /* S/PDIF */
SND_BEBOB_CLOCK_TYPE_EXTERNAL, /* ADAT1 */
SND_BEBOB_CLOCK_TYPE_EXTERNAL, /* ADAT2 */
SND_BEBOB_CLOCK_TYPE_EXTERNAL, /* Word Clock */
};
/* Value maps between registers and labels for SaffirePro 10/26. */
static const signed char saffirepro_clk_maps[][SAFFIREPRO_CLOCK_SOURCE_COUNT] = {
/* SaffirePro 10 */
[0] = {
[SAFFIREPRO_CLOCK_SOURCE_INTERNAL] = 0,
[SAFFIREPRO_CLOCK_SOURCE_SKIP] = -1, /* not supported */
[SAFFIREPRO_CLOCK_SOURCE_SPDIF] = 1,
[SAFFIREPRO_CLOCK_SOURCE_ADAT1] = -1, /* not supported */
[SAFFIREPRO_CLOCK_SOURCE_ADAT2] = -1, /* not supported */
[SAFFIREPRO_CLOCK_SOURCE_WORDCLOCK] = 2,
},
/* SaffirePro 26 */
[1] = {
[SAFFIREPRO_CLOCK_SOURCE_INTERNAL] = 0,
[SAFFIREPRO_CLOCK_SOURCE_SKIP] = -1, /* not supported */
[SAFFIREPRO_CLOCK_SOURCE_SPDIF] = 1,
[SAFFIREPRO_CLOCK_SOURCE_ADAT1] = 2,
[SAFFIREPRO_CLOCK_SOURCE_ADAT2] = 3,
[SAFFIREPRO_CLOCK_SOURCE_WORDCLOCK] = 4,
}
};
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;
else
*rate = rates[id];
end:
return err;
}
static int
saffirepro_both_clk_freq_set(struct snd_bebob *bebob, unsigned int rate)
{
u32 id;
for (id = 0; id < ARRAY_SIZE(rates); id++) {
if (rates[id] == rate)
break;
}
if (id == ARRAY_SIZE(rates))
return -EINVAL;
return saffire_write_quad(bebob, SAFFIREPRO_RATE_NOREBOOT, id);
}
/*
* query hardware for current clock source, return our internally
* used clock index in *id, depending on hardware.
*/
static int
saffirepro_both_clk_src_get(struct snd_bebob *bebob, unsigned int *id)
{
int err;
u32 value; /* clock source read from hw register */
const signed char *map;
err = saffire_read_quad(bebob, SAFFIREPRO_OFFSET_CLOCK_SOURCE, &value);
if (err < 0)
goto end;
/* depending on hardware, use a different mapping */
if (bebob->spec->clock->types == saffirepro_10_clk_src_types)
map = saffirepro_clk_maps[0];
else
map = saffirepro_clk_maps[1];
/* In a case that this driver cannot handle the value of register. */
value &= SAFFIREPRO_CLOCK_SOURCE_SELECT_MASK;
if (value >= SAFFIREPRO_CLOCK_SOURCE_COUNT || map[value] < 0) {
err = -EIO;
goto end;
}
*id = (unsigned int)map[value];
end:
return err;
}
const struct snd_bebob_spec saffire_le_spec;
static const enum snd_bebob_clock_type saffire_both_clk_src_types[] = {
SND_BEBOB_CLOCK_TYPE_INTERNAL,
SND_BEBOB_CLOCK_TYPE_EXTERNAL,
};
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)
*id = 0xff & value;
return err;
};
static const char *const saffire_le_meter_labels[] = {
ANA_IN, ANA_IN, DIG_IN,
ANA_OUT, ANA_OUT, ANA_OUT, ANA_OUT,
STM_IN, STM_IN
};
static const char *const 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)
{
const struct snd_bebob_meter_spec *spec = bebob->spec->meter;
unsigned int channels;
u64 offset;
int err;
if (spec->labels == saffire_le_meter_labels)
offset = SAFFIRE_LE_OFFSET_METER;
else
offset = SAFFIRE_OFFSET_METER;
channels = spec->num * 2;
if (size < channels * sizeof(u32))
return -EIO;
err = saffire_read_block(bebob, offset, buf, size);
if (err >= 0 && spec->labels == saffire_le_meter_labels) {
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]);
}
return err;
}
static const struct snd_bebob_rate_spec saffirepro_both_rate_spec = {
.get = &saffirepro_both_clk_freq_get,
.set = &saffirepro_both_clk_freq_set,
};
/* Saffire Pro 26 I/O */
static const struct snd_bebob_clock_spec saffirepro_26_clk_spec = {
.num = ARRAY_SIZE(saffirepro_26_clk_src_types),
.types = saffirepro_26_clk_src_types,
.get = &saffirepro_both_clk_src_get,
};
const struct snd_bebob_spec saffirepro_26_spec = {
.clock = &saffirepro_26_clk_spec,
.rate = &saffirepro_both_rate_spec,
.meter = NULL
};
/* Saffire Pro 10 I/O */
static const struct snd_bebob_clock_spec saffirepro_10_clk_spec = {
.num = ARRAY_SIZE(saffirepro_10_clk_src_types),
.types = saffirepro_10_clk_src_types,
.get = &saffirepro_both_clk_src_get,
};
const struct snd_bebob_spec saffirepro_10_spec = {
.clock = &saffirepro_10_clk_spec,
.rate = &saffirepro_both_rate_spec,
.meter = NULL
};
static const struct snd_bebob_rate_spec saffire_both_rate_spec = {
.get = &snd_bebob_stream_get_rate,
.set = &snd_bebob_stream_set_rate,
};
static const struct snd_bebob_clock_spec saffire_both_clk_spec = {
.num = ARRAY_SIZE(saffire_both_clk_src_types),
.types = saffire_both_clk_src_types,
.get = &saffire_both_clk_src_get,
};
/* Saffire LE */
static const struct snd_bebob_meter_spec saffire_le_meter_spec = {
.num = ARRAY_SIZE(saffire_le_meter_labels),
.labels = saffire_le_meter_labels,
.get = &saffire_meter_get,
};
const struct snd_bebob_spec saffire_le_spec = {
.clock = &saffire_both_clk_spec,
.rate = &saffire_both_rate_spec,
.meter = &saffire_le_meter_spec
};
/* Saffire */
static const struct snd_bebob_meter_spec saffire_meter_spec = {
.num = ARRAY_SIZE(saffire_meter_labels),
.labels = saffire_meter_labels,
.get = &saffire_meter_get,
};
const struct snd_bebob_spec saffire_spec = {
.clock = &saffire_both_clk_spec,
.rate = &saffire_both_rate_spec,
.meter = &saffire_meter_spec
};
@@ -0,0 +1,177 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* bebob_hwdep.c - a part of driver for BeBoB based devices
*
* Copyright (c) 2013-2014 Takashi Sakamoto
*/
/*
* This codes give three functionality.
*
* 1.get firewire node infomation
* 2.get notification about starting/stopping stream
* 3.lock/unlock stream
*/
#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));
count = min_t(long, count, sizeof(event.lock_status));
event.lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS;
event.lock_status.status = (bebob->dev_lock_count > 0);
bebob->dev_lock_changed = false;
spin_unlock_irq(&bebob->lock);
if (copy_to_user(buf, &event, count))
return -EFAULT;
return count;
}
static __poll_t
hwdep_poll(struct snd_hwdep *hwdep, struct file *file, poll_table *wait)
{
struct snd_bebob *bebob = hwdep->private_data;
poll_wait(file, &bebob->hwdep_wait, wait);
guard(spinlock_irq)(&bebob->lock);
if (bebob->dev_lock_changed)
return EPOLLIN | EPOLLRDNORM;
else
return 0;
}
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]);
strscpy(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)
{
guard(spinlock_irq)(&bebob->lock);
if (bebob->dev_lock_count == 0) {
bebob->dev_lock_count = -1;
return 0;
} else {
return -EBUSY;
}
}
static int
hwdep_unlock(struct snd_bebob *bebob)
{
guard(spinlock_irq)(&bebob->lock);
if (bebob->dev_lock_count == -1) {
bebob->dev_lock_count = 0;
return 0;
} else {
return -EBADFD;
}
}
static int
hwdep_release(struct snd_hwdep *hwdep, struct file *file)
{
struct snd_bebob *bebob = hwdep->private_data;
guard(spinlock_irq)(&bebob->lock);
if (bebob->dev_lock_count == -1)
bebob->dev_lock_count = 0;
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
int snd_bebob_create_hwdep_device(struct snd_bebob *bebob)
{
static const struct snd_hwdep_ops ops = {
.read = hwdep_read,
.release = hwdep_release,
.poll = hwdep_poll,
.ioctl = hwdep_ioctl,
.ioctl_compat = hwdep_compat_ioctl,
};
struct snd_hwdep *hwdep;
int err;
err = snd_hwdep_new(bebob->card, "BeBoB", 0, &hwdep);
if (err < 0)
goto end;
strscpy(hwdep->name, "BeBoB");
hwdep->iface = SNDRV_HWDEP_IFACE_FW_BEBOB;
hwdep->ops = ops;
hwdep->private_data = bebob;
hwdep->exclusive = true;
end:
return err;
}
@@ -0,0 +1,785 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* bebob_maudio.c - a part of driver for BeBoB based devices
*
* Copyright (c) 2013-2014 Takashi Sakamoto
*/
#include "./bebob.h"
#include <sound/control.h>
/*
* Just powering on, Firewire 410/Audiophile/1814 and ProjectMix I/O wait to
* download firmware blob. To enable these devices, drivers should upload
* firmware blob and send a command to initialize configuration to factory
* settings when completing uploading. Then these devices generate bus reset
* and are recognized as new devices with the firmware.
*
* But with firmware version 5058 or later, the firmware is stored to flash
* memory in the device and drivers can tell bootloader to load the firmware
* by sending a cue. This cue must be sent one time.
*
* 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 reason, Audiophile cannot be used as standalone
* mixer.
*
* Firewire 1814 and ProjectMix I/O uses special firmware. It will be freezed
* when receiving any commands which the firmware can't understand. These
* devices utilize completely different system to control. It is some
* write-transaction directly into a certain address. All of addresses for mixer
* functionality is between 0xffc700700000 to 0xffc70070009c.
*/
/* Offset from information register */
#define INFO_OFFSET_SW_DATE 0x20
/* Bootloader Protocol Version 1 */
#define MAUDIO_BOOTLOADER_CUE1 0x00000001
/*
* Initializing configuration to factory settings (= 0x1101), (swapped in line),
* Command code is zero (= 0x00),
* the number of operands is zero (= 0x00)(at least significant byte)
*/
#define MAUDIO_BOOTLOADER_CUE2 0x01110000
/* padding */
#define MAUDIO_BOOTLOADER_CUE3 0x00000000
#define MAUDIO_SPECIFIC_ADDRESS 0xffc700000000ULL
#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"
struct special_params {
bool is1814;
unsigned int clk_src;
unsigned int dig_in_fmt;
unsigned int dig_out_fmt;
unsigned int clk_lock;
struct snd_ctl_elem_id *ctl_id_sync;
};
/*
* For some M-Audio devices, this module just send cue to load firmware. After
* loading, the device generates bus reset and newly detected.
*
* If we make any transactions to load firmware, the operation may failed.
*/
int snd_bebob_maudio_load_firmware(struct fw_unit *unit)
{
struct fw_device *device = fw_parent_device(unit);
int err, rcode;
u64 date;
__le32 *cues;
/* check date of software used to build */
err = snd_bebob_read_block(unit, INFO_OFFSET_SW_DATE,
&date, sizeof(u64));
if (err < 0)
return err;
/*
* firmware version 5058 or later has date later than "20070401", but
* 'date' is not null-terminated.
*/
if (date < 0x3230303730343031LL) {
dev_err(&unit->device,
"Use firmware version 5058 or later\n");
return -ENXIO;
}
cues = kmalloc_array(3, sizeof(*cues), GFP_KERNEL);
if (!cues)
return -ENOMEM;
cues[0] = cpu_to_le32(MAUDIO_BOOTLOADER_CUE1);
cues[1] = cpu_to_le32(MAUDIO_BOOTLOADER_CUE2);
cues[2] = cpu_to_le32(MAUDIO_BOOTLOADER_CUE3);
rcode = fw_run_transaction(device->card, TCODE_WRITE_BLOCK_REQUEST,
device->node_id, device->generation,
device->max_speed, BEBOB_ADDR_REG_REQ,
cues, 3 * sizeof(*cues));
kfree(cues);
if (rcode != RCODE_COMPLETE) {
dev_err(&unit->device,
"Failed to send a cue to load firmware\n");
err = -EIO;
}
return err;
}
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);
}
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 as SFC of FDF in CIP header */
*sync = (buf[size - 2] != 0xff);
end:
kfree(buf);
return err;
}
/*
* dig_fmt: 0x00:S/PDIF, 0x01:ADAT
* clk_lock: 0x00:unlock, 0x01:lock
*/
static int
avc_maudio_set_special_clk(struct snd_bebob *bebob, unsigned int clk_src,
unsigned int dig_in_fmt, unsigned int dig_out_fmt,
unsigned int clk_lock)
{
struct special_params *params = bebob->maudio_special_quirk;
int err;
u8 *buf;
if (amdtp_stream_running(&bebob->rx_stream) ||
amdtp_stream_running(&bebob->tx_stream))
return -EBUSY;
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 */
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) && (err < 10))
err = -EIO;
else if (buf[0] == 0x08) /* NOT IMPLEMENTED */
err = -ENOSYS;
else if (buf[0] == 0x0a) /* REJECTED */
err = -EINVAL;
if (err < 0)
goto end;
params->clk_src = buf[6];
params->dig_in_fmt = buf[7];
params->dig_out_fmt = buf[8];
params->clk_lock = buf[9];
if (params->ctl_id_sync)
snd_ctl_notify(bebob->card, SNDRV_CTL_EVENT_MASK_VALUE,
params->ctl_id_sync);
err = 0;
end:
kfree(buf);
return err;
}
static void
special_stream_formation_set(struct snd_bebob *bebob)
{
static const unsigned int ch_table[2][2][3] = {
/* AMDTP_OUT_STREAM */
{ { 6, 6, 4 }, /* SPDIF */
{ 12, 8, 4 } }, /* ADAT */
/* AMDTP_IN_STREAM */
{ { 10, 10, 2 }, /* SPDIF */
{ 16, 12, 2 } } /* ADAT */
};
struct special_params *params = bebob->maudio_special_quirk;
unsigned int i, max;
max = SND_BEBOB_STRM_FMT_ENTRIES - 1;
if (!params->is1814)
max -= 2;
for (i = 0; i < max; i++) {
bebob->tx_stream_formations[i + 1].pcm =
ch_table[AMDTP_IN_STREAM][params->dig_in_fmt][i / 2];
bebob->tx_stream_formations[i + 1].midi = 1;
bebob->rx_stream_formations[i + 1].pcm =
ch_table[AMDTP_OUT_STREAM][params->dig_out_fmt][i / 2];
bebob->rx_stream_formations[i + 1].midi = 1;
}
}
static int add_special_controls(struct snd_bebob *bebob);
int
snd_bebob_maudio_special_discover(struct snd_bebob *bebob, bool is1814)
{
struct special_params *params;
int err;
params = devm_kzalloc(&bebob->card->card_dev,
sizeof(struct special_params), GFP_KERNEL);
if (!params)
return -ENOMEM;
guard(mutex)(&bebob->mutex);
bebob->maudio_special_quirk = (void *)params;
params->is1814 = is1814;
/* initialize these parameters because driver is not allowed to ask */
bebob->rx_stream.context = ERR_PTR(-1);
bebob->tx_stream.context = ERR_PTR(-1);
err = avc_maudio_set_special_clk(bebob, 0x03, 0x00, 0x00, 0x00);
if (err < 0) {
dev_err(&bebob->unit->device,
"fail to initialize clock params: %d\n", err);
return err;
}
err = add_special_controls(bebob);
if (err < 0)
return err;
special_stream_formation_set(bebob);
if (params->is1814) {
bebob->midi_input_ports = 1;
bebob->midi_output_ports = 1;
} else {
bebob->midi_input_ports = 2;
bebob->midi_output_ports = 2;
}
return err;
}
/* Input plug shows actual rate. Output plug is needless for this purpose. */
static int special_get_rate(struct snd_bebob *bebob, unsigned int *rate)
{
int err, trials;
trials = 0;
do {
err = avc_general_get_sig_fmt(bebob->unit, rate,
AVC_GENERAL_PLUG_DIR_IN, 0);
} while (err == -EAGAIN && ++trials < 3);
return err;
}
static int special_set_rate(struct snd_bebob *bebob, unsigned int rate)
{
struct special_params *params = bebob->maudio_special_quirk;
int err;
err = avc_general_set_sig_fmt(bebob->unit, rate,
AVC_GENERAL_PLUG_DIR_OUT, 0);
if (err < 0)
goto end;
/*
* Just after changing sampling rate for output, a followed command
* for input is easy to fail. This is a workaround fot this issue.
*/
msleep(100);
err = avc_general_set_sig_fmt(bebob->unit, rate,
AVC_GENERAL_PLUG_DIR_IN, 0);
if (err < 0)
goto end;
if (params->ctl_id_sync)
snd_ctl_notify(bebob->card, SNDRV_CTL_EVENT_MASK_VALUE,
params->ctl_id_sync);
end:
return err;
}
/* Clock source control for special firmware */
static const enum snd_bebob_clock_type special_clk_types[] = {
SND_BEBOB_CLOCK_TYPE_INTERNAL, /* With digital mute */
SND_BEBOB_CLOCK_TYPE_EXTERNAL, /* SPDIF/ADAT */
SND_BEBOB_CLOCK_TYPE_EXTERNAL, /* Word Clock */
SND_BEBOB_CLOCK_TYPE_INTERNAL,
};
static int special_clk_get(struct snd_bebob *bebob, unsigned int *id)
{
struct special_params *params = bebob->maudio_special_quirk;
*id = params->clk_src;
return 0;
}
static int special_clk_ctl_info(struct snd_kcontrol *kctl,
struct snd_ctl_elem_info *einf)
{
static const char *const special_clk_labels[] = {
"Internal with Digital Mute",
"Digital",
"Word Clock",
"Internal"
};
return snd_ctl_enum_info(einf, 1, ARRAY_SIZE(special_clk_types),
special_clk_labels);
}
static int special_clk_ctl_get(struct snd_kcontrol *kctl,
struct snd_ctl_elem_value *uval)
{
struct snd_bebob *bebob = snd_kcontrol_chip(kctl);
struct special_params *params = bebob->maudio_special_quirk;
uval->value.enumerated.item[0] = params->clk_src;
return 0;
}
static int special_clk_ctl_put(struct snd_kcontrol *kctl,
struct snd_ctl_elem_value *uval)
{
struct snd_bebob *bebob = snd_kcontrol_chip(kctl);
struct special_params *params = bebob->maudio_special_quirk;
int err, id;
id = uval->value.enumerated.item[0];
if (id >= ARRAY_SIZE(special_clk_types))
return -EINVAL;
guard(mutex)(&bebob->mutex);
err = avc_maudio_set_special_clk(bebob, id,
params->dig_in_fmt,
params->dig_out_fmt,
params->clk_lock);
if (err >= 0)
err = 1;
return err;
}
static const struct snd_kcontrol_new special_clk_ctl = {
.name = "Clock Source",
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
.info = special_clk_ctl_info,
.get = special_clk_ctl_get,
.put = special_clk_ctl_put
};
/* Clock synchronization control for special firmware */
static int special_sync_ctl_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 special_sync_ctl_get(struct snd_kcontrol *kctl,
struct snd_ctl_elem_value *uval)
{
struct snd_bebob *bebob = snd_kcontrol_chip(kctl);
int err;
bool synced = 0;
err = check_clk_sync(bebob, METER_SIZE_SPECIAL, &synced);
if (err >= 0)
uval->value.integer.value[0] = synced;
return 0;
}
static const struct snd_kcontrol_new special_sync_ctl = {
.name = "Sync Status",
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.access = SNDRV_CTL_ELEM_ACCESS_READ,
.info = special_sync_ctl_info,
.get = special_sync_ctl_get,
};
/* Digital input interface control for special firmware */
static const char *const special_dig_in_iface_labels[] = {
"S/PDIF Optical", "S/PDIF Coaxial", "ADAT Optical"
};
static int special_dig_in_iface_ctl_info(struct snd_kcontrol *kctl,
struct snd_ctl_elem_info *einf)
{
return snd_ctl_enum_info(einf, 1,
ARRAY_SIZE(special_dig_in_iface_labels),
special_dig_in_iface_labels);
}
static int special_dig_in_iface_ctl_get(struct snd_kcontrol *kctl,
struct snd_ctl_elem_value *uval)
{
struct snd_bebob *bebob = snd_kcontrol_chip(kctl);
struct special_params *params = bebob->maudio_special_quirk;
unsigned int dig_in_iface;
int err, val;
guard(mutex)(&bebob->mutex);
err = avc_audio_get_selector(bebob->unit, 0x00, 0x04,
&dig_in_iface);
if (err < 0) {
dev_err(&bebob->unit->device,
"fail to get digital input interface: %d\n", err);
return err;
}
/* encoded id for user value */
val = (params->dig_in_fmt << 1) | (dig_in_iface & 0x01);
/* for ADAT Optical */
if (val > 2)
val = 2;
uval->value.enumerated.item[0] = val;
return 0;
}
static int special_dig_in_iface_ctl_set(struct snd_kcontrol *kctl,
struct snd_ctl_elem_value *uval)
{
struct snd_bebob *bebob = snd_kcontrol_chip(kctl);
struct special_params *params = bebob->maudio_special_quirk;
unsigned int id, dig_in_fmt, dig_in_iface;
int err;
id = uval->value.enumerated.item[0];
if (id >= ARRAY_SIZE(special_dig_in_iface_labels))
return -EINVAL;
/* decode user value */
dig_in_fmt = (id >> 1) & 0x01;
dig_in_iface = id & 0x01;
guard(mutex)(&bebob->mutex);
err = avc_maudio_set_special_clk(bebob,
params->clk_src,
dig_in_fmt,
params->dig_out_fmt,
params->clk_lock);
if (err < 0)
return err;
/* For ADAT, optical interface is only available. */
if (params->dig_in_fmt > 0)
return 1;
/* For S/PDIF, optical/coaxial interfaces are selectable. */
err = avc_audio_set_selector(bebob->unit, 0x00, 0x04, dig_in_iface);
if (err < 0)
dev_err(&bebob->unit->device,
"fail to set digital input interface: %d\n", err);
special_stream_formation_set(bebob);
return 1;
}
static const struct snd_kcontrol_new special_dig_in_iface_ctl = {
.name = "Digital Input Interface",
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
.info = special_dig_in_iface_ctl_info,
.get = special_dig_in_iface_ctl_get,
.put = special_dig_in_iface_ctl_set
};
/* Digital output interface control for special firmware */
static const char *const special_dig_out_iface_labels[] = {
"S/PDIF Optical and Coaxial", "ADAT Optical"
};
static int special_dig_out_iface_ctl_info(struct snd_kcontrol *kctl,
struct snd_ctl_elem_info *einf)
{
return snd_ctl_enum_info(einf, 1,
ARRAY_SIZE(special_dig_out_iface_labels),
special_dig_out_iface_labels);
}
static int special_dig_out_iface_ctl_get(struct snd_kcontrol *kctl,
struct snd_ctl_elem_value *uval)
{
struct snd_bebob *bebob = snd_kcontrol_chip(kctl);
struct special_params *params = bebob->maudio_special_quirk;
guard(mutex)(&bebob->mutex);
uval->value.enumerated.item[0] = params->dig_out_fmt;
return 0;
}
static int special_dig_out_iface_ctl_set(struct snd_kcontrol *kctl,
struct snd_ctl_elem_value *uval)
{
struct snd_bebob *bebob = snd_kcontrol_chip(kctl);
struct special_params *params = bebob->maudio_special_quirk;
unsigned int id;
int err;
id = uval->value.enumerated.item[0];
if (id >= ARRAY_SIZE(special_dig_out_iface_labels))
return -EINVAL;
guard(mutex)(&bebob->mutex);
err = avc_maudio_set_special_clk(bebob,
params->clk_src,
params->dig_in_fmt,
id, params->clk_lock);
if (err >= 0) {
special_stream_formation_set(bebob);
err = 1;
}
return err;
}
static const struct snd_kcontrol_new special_dig_out_iface_ctl = {
.name = "Digital Output Interface",
.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
.access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
.info = special_dig_out_iface_ctl_info,
.get = special_dig_out_iface_ctl_get,
.put = special_dig_out_iface_ctl_set
};
static int add_special_controls(struct snd_bebob *bebob)
{
struct snd_kcontrol *kctl;
struct special_params *params = bebob->maudio_special_quirk;
int err;
kctl = snd_ctl_new1(&special_clk_ctl, bebob);
err = snd_ctl_add(bebob->card, kctl);
if (err < 0)
goto end;
kctl = snd_ctl_new1(&special_sync_ctl, bebob);
err = snd_ctl_add(bebob->card, kctl);
if (err < 0)
goto end;
params->ctl_id_sync = &kctl->id;
kctl = snd_ctl_new1(&special_dig_in_iface_ctl, bebob);
err = snd_ctl_add(bebob->card, kctl);
if (err < 0)
goto end;
kctl = snd_ctl_new1(&special_dig_out_iface_ctl, bebob);
err = snd_ctl_add(bebob->card, kctl);
end:
return err;
}
/* Hardware metering for special firmware */
static const char *const 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)
{
__be16 *buf;
unsigned int i, c, channels;
int err;
channels = ARRAY_SIZE(special_meter_labels) * 2;
if (size < channels * sizeof(u32))
return -EINVAL;
/* omit last 4 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;
/* Its format is u16 and some channels are unknown. */
i = 0;
for (c = 2; c < channels + 2; c++)
target[i++] = be16_to_cpu(buf[c]) << 16;
end:
kfree(buf);
return err;
}
/* last 4 bytes are omitted because it's clock info. */
static const char *const fw410_meter_labels[] = {
ANA_IN, DIG_IN,
ANA_OUT, ANA_OUT, ANA_OUT, ANA_OUT, DIG_OUT,
HP_OUT
};
static const char *const audiophile_meter_labels[] = {
ANA_IN, DIG_IN,
ANA_OUT, ANA_OUT, DIG_OUT,
HP_OUT, AUX_OUT,
};
static const char *const solo_meter_labels[] = {
ANA_IN, DIG_IN,
STRM_IN, STRM_IN,
ANA_OUT, DIG_OUT
};
/* no clock info */
static const char *const ozonic_meter_labels[] = {
ANA_IN, ANA_IN,
STRM_IN, STRM_IN,
ANA_OUT, ANA_OUT
};
/* TODO: need testers. these positions are based on authour's assumption */
static const char *const 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
normal_meter_get(struct snd_bebob *bebob, u32 *buf, unsigned int size)
{
const struct snd_bebob_meter_spec *spec = bebob->spec->meter;
unsigned int c, channels;
int err;
channels = spec->num * 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++)
be32_to_cpus(&buf[c]);
/* swap stream channels because inverted */
if (spec->labels == solo_meter_labels) {
swap(buf[4], buf[6]);
swap(buf[5], buf[7]);
}
end:
return err;
}
/* for special customized devices */
static const struct snd_bebob_rate_spec special_rate_spec = {
.get = &special_get_rate,
.set = &special_set_rate,
};
static const struct snd_bebob_clock_spec special_clk_spec = {
.num = ARRAY_SIZE(special_clk_types),
.types = special_clk_types,
.get = &special_clk_get,
};
static const struct snd_bebob_meter_spec special_meter_spec = {
.num = ARRAY_SIZE(special_meter_labels),
.labels = special_meter_labels,
.get = &special_meter_get
};
const struct snd_bebob_spec maudio_special_spec = {
.clock = &special_clk_spec,
.rate = &special_rate_spec,
.meter = &special_meter_spec
};
/* Firewire 410 specification */
static const struct snd_bebob_rate_spec usual_rate_spec = {
.get = &snd_bebob_stream_get_rate,
.set = &snd_bebob_stream_set_rate,
};
static const struct snd_bebob_meter_spec fw410_meter_spec = {
.num = ARRAY_SIZE(fw410_meter_labels),
.labels = fw410_meter_labels,
.get = &normal_meter_get
};
const struct snd_bebob_spec maudio_fw410_spec = {
.clock = NULL,
.rate = &usual_rate_spec,
.meter = &fw410_meter_spec
};
/* Firewire Audiophile specification */
static const struct snd_bebob_meter_spec audiophile_meter_spec = {
.num = ARRAY_SIZE(audiophile_meter_labels),
.labels = audiophile_meter_labels,
.get = &normal_meter_get
};
const struct snd_bebob_spec maudio_audiophile_spec = {
.clock = NULL,
.rate = &usual_rate_spec,
.meter = &audiophile_meter_spec
};
/* Firewire Solo specification */
static const struct snd_bebob_meter_spec solo_meter_spec = {
.num = ARRAY_SIZE(solo_meter_labels),
.labels = solo_meter_labels,
.get = &normal_meter_get
};
const struct snd_bebob_spec maudio_solo_spec = {
.clock = NULL,
.rate = &usual_rate_spec,
.meter = &solo_meter_spec
};
/* Ozonic specification */
static const struct snd_bebob_meter_spec ozonic_meter_spec = {
.num = ARRAY_SIZE(ozonic_meter_labels),
.labels = ozonic_meter_labels,
.get = &normal_meter_get
};
const struct snd_bebob_spec maudio_ozonic_spec = {
.clock = NULL,
.rate = &usual_rate_spec,
.meter = &ozonic_meter_spec
};
/* NRV10 specification */
static const struct snd_bebob_meter_spec nrv10_meter_spec = {
.num = ARRAY_SIZE(nrv10_meter_labels),
.labels = nrv10_meter_labels,
.get = &normal_meter_get
};
const struct snd_bebob_spec maudio_nrv10_spec = {
.clock = NULL,
.rate = &usual_rate_spec,
.meter = &nrv10_meter_spec
};
@@ -0,0 +1,140 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* bebob_midi.c - a part of driver for BeBoB based devices
*
* Copyright (c) 2013-2014 Takashi Sakamoto
*/
#include "bebob.h"
static int midi_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)
return err;
scoped_guard(mutex, &bebob->mutex) {
err = snd_bebob_stream_reserve_duplex(bebob, 0, 0, 0);
if (err >= 0) {
++bebob->substreams_counter;
err = snd_bebob_stream_start_duplex(bebob);
if (err < 0)
--bebob->substreams_counter;
}
}
if (err < 0)
snd_bebob_stream_lock_release(bebob);
return err;
}
static int midi_close(struct snd_rawmidi_substream *substream)
{
struct snd_bebob *bebob = substream->rmidi->private_data;
scoped_guard(mutex, &bebob->mutex) {
bebob->substreams_counter--;
snd_bebob_stream_stop_duplex(bebob);
}
snd_bebob_stream_lock_release(bebob);
return 0;
}
static void midi_capture_trigger(struct snd_rawmidi_substream *substrm, int up)
{
struct snd_bebob *bebob = substrm->rmidi->private_data;
guard(spinlock_irqsave)(&bebob->lock);
if (up)
amdtp_am824_midi_trigger(&bebob->tx_stream,
substrm->number, substrm);
else
amdtp_am824_midi_trigger(&bebob->tx_stream,
substrm->number, NULL);
}
static void midi_playback_trigger(struct snd_rawmidi_substream *substrm, int up)
{
struct snd_bebob *bebob = substrm->rmidi->private_data;
guard(spinlock_irqsave)(&bebob->lock);
if (up)
amdtp_am824_midi_trigger(&bebob->rx_stream,
substrm->number, substrm);
else
amdtp_am824_midi_trigger(&bebob->rx_stream,
substrm->number, NULL);
}
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) {
scnprintf(subs->name, sizeof(subs->name),
"%s MIDI %d",
bebob->card->shortname, subs->number + 1);
}
}
int snd_bebob_create_midi_devices(struct snd_bebob *bebob)
{
static const struct snd_rawmidi_ops capture_ops = {
.open = midi_open,
.close = midi_close,
.trigger = midi_capture_trigger,
};
static const struct snd_rawmidi_ops playback_ops = {
.open = midi_open,
.close = midi_close,
.trigger = midi_playback_trigger,
};
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,
&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,
&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;
}
@@ -0,0 +1,368 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* bebob_pcm.c - a part of driver for BeBoB based devices
*
* Copyright (c) 2013-2014 Takashi Sakamoto
*/
#include "./bebob.h"
static int
hw_rule_rate(struct snd_pcm_hw_params *params, struct snd_pcm_hw_rule *rule)
{
struct snd_bebob_stream_formation *formations = rule->private;
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_stream_formation *formations = rule->private;
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 void
limit_channels_and_rates(struct snd_pcm_hardware *hw,
struct snd_bebob_stream_formation *formations)
{
unsigned int i;
hw->channels_min = UINT_MAX;
hw->channels_max = 0;
hw->rate_min = UINT_MAX;
hw->rate_max = 0;
hw->rates = 0;
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);
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]);
}
}
static int
pcm_init_hw_params(struct snd_bebob *bebob,
struct snd_pcm_substream *substream)
{
struct snd_pcm_runtime *runtime = substream->runtime;
struct amdtp_stream *s;
struct snd_bebob_stream_formation *formations;
int err;
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
runtime->hw.formats = AM824_IN_PCM_FORMAT_BITS;
s = &bebob->tx_stream;
formations = bebob->tx_stream_formations;
} else {
runtime->hw.formats = AM824_OUT_PCM_FORMAT_BITS;
s = &bebob->rx_stream;
formations = bebob->rx_stream_formations;
}
limit_channels_and_rates(&runtime->hw, formations);
err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
hw_rule_channels, formations,
SNDRV_PCM_HW_PARAM_RATE, -1);
if (err < 0)
goto end;
err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
hw_rule_rate, formations,
SNDRV_PCM_HW_PARAM_CHANNELS, -1);
if (err < 0)
goto end;
err = amdtp_am824_add_pcm_hw_constraints(s, runtime);
end:
return err;
}
static int pcm_open(struct snd_pcm_substream *substream)
{
struct snd_bebob *bebob = substream->private_data;
const struct snd_bebob_rate_spec *spec = bebob->spec->rate;
struct amdtp_domain *d = &bebob->domain;
enum snd_bebob_clock_type src;
int err;
err = snd_bebob_stream_lock_try(bebob);
if (err < 0)
return err;
err = pcm_init_hw_params(bebob, substream);
if (err < 0)
goto err_locked;
err = snd_bebob_stream_get_clock_src(bebob, &src);
if (err < 0)
goto err_locked;
scoped_guard(mutex, &bebob->mutex) {
// When source of clock is not internal or any stream is reserved for
// transmission of PCM frames, the available sampling rate is limited
// at current one.
if (src == SND_BEBOB_CLOCK_TYPE_EXTERNAL ||
(bebob->substreams_counter > 0 && d->events_per_period > 0)) {
unsigned int frames_per_period = d->events_per_period;
unsigned int frames_per_buffer = d->events_per_buffer;
unsigned int sampling_rate;
err = spec->get(bebob, &sampling_rate);
if (err < 0) {
dev_err(&bebob->unit->device,
"fail to get sampling rate: %d\n", err);
goto err_locked;
}
substream->runtime->hw.rate_min = sampling_rate;
substream->runtime->hw.rate_max = sampling_rate;
if (frames_per_period > 0) {
err = snd_pcm_hw_constraint_minmax(substream->runtime,
SNDRV_PCM_HW_PARAM_PERIOD_SIZE,
frames_per_period, frames_per_period);
if (err < 0)
goto err_locked;
err = snd_pcm_hw_constraint_minmax(substream->runtime,
SNDRV_PCM_HW_PARAM_BUFFER_SIZE,
frames_per_buffer, frames_per_buffer);
if (err < 0)
goto err_locked;
}
}
}
snd_pcm_set_sync(substream);
return 0;
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;
}
static int pcm_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *hw_params)
{
struct snd_bebob *bebob = substream->private_data;
int err = 0;
if (substream->runtime->state == SNDRV_PCM_STATE_OPEN) {
unsigned int rate = params_rate(hw_params);
unsigned int frames_per_period = params_period_size(hw_params);
unsigned int frames_per_buffer = params_buffer_size(hw_params);
guard(mutex)(&bebob->mutex);
err = snd_bebob_stream_reserve_duplex(bebob, rate,
frames_per_period, frames_per_buffer);
if (err >= 0)
++bebob->substreams_counter;
}
return err;
}
static int pcm_hw_free(struct snd_pcm_substream *substream)
{
struct snd_bebob *bebob = substream->private_data;
guard(mutex)(&bebob->mutex);
if (substream->runtime->state != SNDRV_PCM_STATE_OPEN)
bebob->substreams_counter--;
snd_bebob_stream_stop_duplex(bebob);
return 0;
}
static int
pcm_capture_prepare(struct snd_pcm_substream *substream)
{
struct snd_bebob *bebob = substream->private_data;
int err;
err = snd_bebob_stream_start_duplex(bebob);
if (err >= 0)
amdtp_stream_pcm_prepare(&bebob->tx_stream);
return err;
}
static int
pcm_playback_prepare(struct snd_pcm_substream *substream)
{
struct snd_bebob *bebob = substream->private_data;
int err;
err = snd_bebob_stream_start_duplex(bebob);
if (err >= 0)
amdtp_stream_pcm_prepare(&bebob->rx_stream);
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_domain_stream_pcm_pointer(&bebob->domain,
&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_domain_stream_pcm_pointer(&bebob->domain,
&bebob->rx_stream);
}
static int pcm_capture_ack(struct snd_pcm_substream *substream)
{
struct snd_bebob *bebob = substream->private_data;
return amdtp_domain_stream_pcm_ack(&bebob->domain, &bebob->tx_stream);
}
static int pcm_playback_ack(struct snd_pcm_substream *substream)
{
struct snd_bebob *bebob = substream->private_data;
return amdtp_domain_stream_pcm_ack(&bebob->domain, &bebob->rx_stream);
}
int snd_bebob_create_pcm_devices(struct snd_bebob *bebob)
{
static const struct snd_pcm_ops capture_ops = {
.open = pcm_open,
.close = pcm_close,
.hw_params = pcm_hw_params,
.hw_free = pcm_hw_free,
.prepare = pcm_capture_prepare,
.trigger = pcm_capture_trigger,
.pointer = pcm_capture_pointer,
.ack = pcm_capture_ack,
};
static const struct snd_pcm_ops playback_ops = {
.open = pcm_open,
.close = pcm_close,
.hw_params = pcm_hw_params,
.hw_free = pcm_hw_free,
.prepare = pcm_playback_prepare,
.trigger = pcm_playback_trigger,
.pointer = pcm_playback_pointer,
.ack = pcm_playback_ack,
};
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;
pcm->nonatomic = true;
snprintf(pcm->name, sizeof(pcm->name),
"%s PCM", bebob->card->shortname);
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &playback_ops);
snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &capture_ops);
snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_VMALLOC, NULL, 0, 0);
end:
return err;
}
@@ -0,0 +1,189 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* bebob_proc.c - a part of driver for BeBoB based devices
*
* Copyright (c) 2013-2014 Takashi Sakamoto
*/
#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;
info = kzalloc_obj(struct hw_info);
if (info == NULL)
return;
if (snd_bebob_read_block(bebob->unit, 0,
info, sizeof(struct hw_info)) < 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);
}
static void
proc_read_meters(struct snd_info_entry *entry,
struct snd_info_buffer *buffer)
{
struct snd_bebob *bebob = entry->private_data;
const struct snd_bebob_meter_spec *spec = bebob->spec->meter;
u32 *buf;
unsigned int i, c, channels, size;
if (spec == NULL)
return;
channels = spec->num * 2;
size = channels * sizeof(u32);
buf = kmalloc(size, GFP_KERNEL);
if (buf == NULL)
return;
if (spec->get(bebob, buf, size) < 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);
}
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 < SND_BEBOB_STRM_FMT_ENTRIES; i++) {
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 < SND_BEBOB_STRM_FMT_ENTRIES; i++) {
snd_iprintf(buffer,
"\t%d\t%d\t%d\n", snd_bebob_rate_table[i],
formation[i].pcm, formation[i].midi);
}
}
static void
proc_read_clock(struct snd_info_entry *entry,
struct snd_info_buffer *buffer)
{
static const char *const clk_labels[] = {
"Internal",
"External",
"SYT-Match",
};
struct snd_bebob *bebob = entry->private_data;
const struct snd_bebob_rate_spec *rate_spec = bebob->spec->rate;
const struct snd_bebob_clock_spec *clk_spec = bebob->spec->clock;
enum snd_bebob_clock_type src;
unsigned int rate;
if (rate_spec->get(bebob, &rate) >= 0)
snd_iprintf(buffer, "Sampling rate: %d\n", rate);
if (snd_bebob_stream_get_clock_src(bebob, &src) >= 0) {
if (clk_spec)
snd_iprintf(buffer, "Clock Source: %s\n",
clk_labels[src]);
else
snd_iprintf(buffer, "Clock Source: %s (MSU-dest: %d)\n",
clk_labels[src], bebob->sync_input_plug);
}
}
static void
add_node(struct snd_bebob *bebob, struct snd_info_entry *root, const char *name,
void (*op)(struct snd_info_entry *e, struct snd_info_buffer *b))
{
struct snd_info_entry *entry;
entry = snd_info_create_card_entry(bebob->card, name, root);
if (entry)
snd_info_set_text_ops(entry, bebob, op);
}
void snd_bebob_proc_init(struct snd_bebob *bebob)
{
struct snd_info_entry *root;
/*
* All nodes are automatically removed at snd_card_disconnect(),
* by following to link list.
*/
root = snd_info_create_card_entry(bebob->card, "firewire",
bebob->card->proc_root);
if (root == NULL)
return;
root->mode = S_IFDIR | 0555;
add_node(bebob, root, "clock", proc_read_clock);
add_node(bebob, root, "firmware", proc_read_hw_info);
add_node(bebob, root, "formation", proc_read_formation);
if (bebob->spec->meter != NULL)
add_node(bebob, root, "meter", proc_read_meters);
}
@@ -0,0 +1,987 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* bebob_stream.c - a part of driver for BeBoB based devices
*
* Copyright (c) 2013-2014 Takashi Sakamoto
*/
#include "./bebob.h"
#define READY_TIMEOUT_MS 4000
/*
* NOTE;
* For BeBoB streams, Both of input and output CMP connection are important.
*
* For most devices, each CMP connection starts to transmit/receive a
* corresponding stream. But for a few devices, both of CMP connection needs
* to start transmitting stream. An example 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] = 32000,
[1] = 44100,
[2] = 48000,
[3] = 88200,
[4] = 96000,
[5] = 176400,
[6] = 192000,
};
/*
* See: Table 51: Extended Stream Format Info ‘Sampling Frequency’
* in Additional AVC commands (Nov 2003, BridgeCo)
*/
static const unsigned int bridgeco_freq_table[] = {
[0] = 0x02,
[1] = 0x03,
[2] = 0x04,
[3] = 0x0a,
[4] = 0x05,
[5] = 0x06,
[6] = 0x07,
};
static int
get_formation_index(unsigned int rate, unsigned int *index)
{
unsigned int i;
for (i = 0; i < ARRAY_SIZE(snd_bebob_rate_table); i++) {
if (snd_bebob_rate_table[i] == rate) {
*index = i;
return 0;
}
}
return -EINVAL;
}
int
snd_bebob_stream_get_rate(struct snd_bebob *bebob, unsigned int *curr_rate)
{
unsigned int tx_rate, rx_rate, trials;
int err;
trials = 0;
do {
err = avc_general_get_sig_fmt(bebob->unit, &tx_rate,
AVC_GENERAL_PLUG_DIR_OUT, 0);
} while (err == -EAGAIN && ++trials < 3);
if (err < 0)
goto end;
trials = 0;
do {
err = avc_general_get_sig_fmt(bebob->unit, &rx_rate,
AVC_GENERAL_PLUG_DIR_IN, 0);
} while (err == -EAGAIN && ++trials < 3);
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 = avc_general_set_sig_fmt(bebob->unit, rx_rate,
AVC_GENERAL_PLUG_DIR_IN, 0);
end:
return err;
}
int
snd_bebob_stream_set_rate(struct snd_bebob *bebob, unsigned int rate)
{
int err;
err = avc_general_set_sig_fmt(bebob->unit, rate,
AVC_GENERAL_PLUG_DIR_OUT, 0);
if (err < 0)
goto end;
err = avc_general_set_sig_fmt(bebob->unit, rate,
AVC_GENERAL_PLUG_DIR_IN, 0);
if (err < 0)
goto end;
/*
* Some devices need a bit time for transition.
* 300msec is got by some experiments.
*/
msleep(300);
end:
return err;
}
int snd_bebob_stream_get_clock_src(struct snd_bebob *bebob,
enum snd_bebob_clock_type *src)
{
const struct snd_bebob_clock_spec *clk_spec = bebob->spec->clock;
u8 addr[AVC_BRIDGECO_ADDR_BYTES], input[7];
unsigned int id;
enum avc_bridgeco_plug_type type;
int err = 0;
/* 1.The device has its own operation to switch source of clock */
if (clk_spec) {
err = clk_spec->get(bebob, &id);
if (err < 0) {
dev_err(&bebob->unit->device,
"fail to get clock source: %d\n", err);
goto end;
}
if (id >= clk_spec->num) {
dev_err(&bebob->unit->device,
"clock source %d out of range 0..%d\n",
id, clk_spec->num - 1);
err = -EIO;
goto end;
}
*src = clk_spec->types[id];
goto end;
}
/*
* 2.The device don't support to switch source of clock then assumed
* to use internal clock always
*/
if (bebob->sync_input_plug < 0) {
*src = SND_BEBOB_CLOCK_TYPE_INTERNAL;
goto end;
}
/*
* 3.The device supports to switch source of clock by an usual way.
* Let's check input for 'Music Sub Unit Sync Input' plug.
*/
avc_bridgeco_fill_msu_addr(addr, AVC_BRIDGECO_PLUG_DIR_IN,
bebob->sync_input_plug);
err = avc_bridgeco_get_plug_input(bebob->unit, addr, input);
if (err < 0) {
dev_err(&bebob->unit->device,
"fail to get an input for MSU in plug %d: %d\n",
bebob->sync_input_plug, err);
goto end;
}
/*
* If there are no input plugs, all of fields are 0xff.
* Here check the first field. This field is used for direction.
*/
if (input[0] == 0xff) {
*src = SND_BEBOB_CLOCK_TYPE_INTERNAL;
goto end;
}
/* The source from any output plugs is for one purpose only. */
if (input[0] == AVC_BRIDGECO_PLUG_DIR_OUT) {
/*
* In BeBoB architecture, the source from music subunit may
* bypass from oPCR[0]. This means that this source gives
* synchronization to IEEE 1394 cycle start packet.
*/
if (input[1] == AVC_BRIDGECO_PLUG_MODE_SUBUNIT &&
input[2] == 0x0c) {
*src = SND_BEBOB_CLOCK_TYPE_INTERNAL;
goto end;
}
/* The source from any input units is for several purposes. */
} else if (input[1] == AVC_BRIDGECO_PLUG_MODE_UNIT) {
if (input[2] == AVC_BRIDGECO_PLUG_UNIT_ISOC) {
if (input[3] == 0x00) {
/*
* This source comes from iPCR[0]. This means
* that presentation timestamp calculated by
* SYT series of the received packets. In
* short, this driver is the master of
* synchronization.
*/
*src = SND_BEBOB_CLOCK_TYPE_SYT;
goto end;
} else {
/*
* This source comes from iPCR[1-29]. This
* means that the synchronization stream is not
* the Audio/MIDI compound stream.
*/
*src = SND_BEBOB_CLOCK_TYPE_EXTERNAL;
goto end;
}
} else if (input[2] == AVC_BRIDGECO_PLUG_UNIT_EXT) {
/* Check type of this plug. */
avc_bridgeco_fill_unit_addr(addr,
AVC_BRIDGECO_PLUG_DIR_IN,
AVC_BRIDGECO_PLUG_UNIT_EXT,
input[3]);
err = avc_bridgeco_get_plug_type(bebob->unit, addr,
&type);
if (err < 0)
goto end;
if (type == AVC_BRIDGECO_PLUG_TYPE_DIG) {
/*
* SPDIF/ADAT or sometimes (not always) word
* clock.
*/
*src = SND_BEBOB_CLOCK_TYPE_EXTERNAL;
goto end;
} else if (type == AVC_BRIDGECO_PLUG_TYPE_SYNC) {
/* Often word clock. */
*src = SND_BEBOB_CLOCK_TYPE_EXTERNAL;
goto end;
} else if (type == AVC_BRIDGECO_PLUG_TYPE_ADDITION) {
/*
* Not standard.
* Mostly, additional internal clock.
*/
*src = SND_BEBOB_CLOCK_TYPE_INTERNAL;
goto end;
}
}
}
/* Not supported. */
err = -EIO;
end:
return err;
}
static int map_data_channels(struct snd_bebob *bebob, struct amdtp_stream *s)
{
unsigned int sec, sections, ch, channels;
unsigned int pcm, midi, location;
unsigned int stm_pos, sec_loc, pos;
u8 *buf, addr[AVC_BRIDGECO_ADDR_BYTES], type;
enum avc_bridgeco_plug_dir dir;
int err;
/*
* The length of return value of this command cannot be expected. Here
* use the maximum length of FCP.
*/
buf = kzalloc(256, GFP_KERNEL);
if (buf == NULL)
return -ENOMEM;
if (s == &bebob->tx_stream)
dir = AVC_BRIDGECO_PLUG_DIR_OUT;
else
dir = AVC_BRIDGECO_PLUG_DIR_IN;
avc_bridgeco_fill_unit_addr(addr, dir, AVC_BRIDGECO_PLUG_UNIT_ISOC, 0);
err = avc_bridgeco_get_plug_ch_pos(bebob->unit, addr, buf, 256);
if (err < 0) {
dev_err(&bebob->unit->device,
"fail to get channel position for isoc %s plug 0: %d\n",
(dir == AVC_BRIDGECO_PLUG_DIR_IN) ? "in" : "out",
err);
goto end;
}
pos = 0;
/* positions in I/O buffer */
pcm = 0;
midi = 0;
/* the number of sections in AMDTP packet */
sections = buf[pos++];
for (sec = 0; sec < sections; sec++) {
/* type of this section */
avc_bridgeco_fill_unit_addr(addr, dir,
AVC_BRIDGECO_PLUG_UNIT_ISOC, 0);
err = avc_bridgeco_get_plug_section_type(bebob->unit, addr,
sec, &type);
if (err < 0) {
dev_err(&bebob->unit->device,
"fail to get section type for isoc %s plug 0: %d\n",
(dir == AVC_BRIDGECO_PLUG_DIR_IN) ? "in" :
"out",
err);
goto end;
}
/* NoType */
if (type == 0xff) {
err = -ENOSYS;
goto end;
}
/* the number of channels in this section */
channels = buf[pos++];
for (ch = 0; ch < channels; ch++) {
/* position of this channel in AMDTP packet */
stm_pos = buf[pos++] - 1;
/* location of this channel in this section */
sec_loc = buf[pos++] - 1;
/*
* Basically the number of location is within the
* number of channels in this section. But some models
* of M-Audio don't follow this. Its location for MIDI
* is the position of MIDI channels in AMDTP packet.
*/
if (sec_loc >= channels)
sec_loc = ch;
switch (type) {
/* for MIDI conformant data channel */
case 0x0a:
/* AMDTP_MAX_CHANNELS_FOR_MIDI is 1. */
if ((midi > 0) && (stm_pos != midi)) {
err = -ENOSYS;
goto end;
}
amdtp_am824_set_midi_position(s, stm_pos);
midi = stm_pos;
break;
/* for PCM data channel */
case 0x01: /* Headphone */
case 0x02: /* Microphone */
case 0x03: /* Line */
case 0x04: /* SPDIF */
case 0x05: /* ADAT */
case 0x06: /* TDIF */
case 0x07: /* MADI */
/* for undefined/changeable signal */
case 0x08: /* Analog */
case 0x09: /* Digital */
default:
location = pcm + sec_loc;
if (location >= AM824_MAX_CHANNELS_FOR_PCM) {
err = -ENOSYS;
goto end;
}
amdtp_am824_set_pcm_position(s, location,
stm_pos);
break;
}
}
if (type != 0x0a)
pcm += channels;
else
midi += channels;
}
end:
kfree(buf);
return err;
}
static int
check_connection_used_by_others(struct snd_bebob *bebob, struct amdtp_stream *s)
{
struct cmp_connection *conn;
bool used;
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 && !amdtp_stream_running(s)) {
dev_err(&bebob->unit->device,
"Connection established by others: %cPCR[%d]\n",
(conn->direction == CMP_OUTPUT) ? 'o' : 'i',
conn->pcr_index);
err = -EBUSY;
}
return err;
}
static void break_both_connections(struct snd_bebob *bebob)
{
cmp_connection_break(&bebob->in_conn);
cmp_connection_break(&bebob->out_conn);
}
static int start_stream(struct snd_bebob *bebob, struct amdtp_stream *stream)
{
struct cmp_connection *conn;
int err = 0;
if (stream == &bebob->rx_stream)
conn = &bebob->in_conn;
else
conn = &bebob->out_conn;
// channel mapping.
if (bebob->maudio_special_quirk == NULL) {
err = map_data_channels(bebob, stream);
if (err < 0)
return err;
}
err = cmp_connection_establish(conn);
if (err < 0)
return err;
return amdtp_domain_add_stream(&bebob->domain, stream,
conn->resources.channel, conn->speed);
}
static int init_stream(struct snd_bebob *bebob, struct amdtp_stream *stream)
{
unsigned int flags = CIP_BLOCKING;
enum amdtp_stream_direction dir_stream;
struct cmp_connection *conn;
enum cmp_direction dir_conn;
int err;
if (stream == &bebob->tx_stream) {
dir_stream = AMDTP_IN_STREAM;
conn = &bebob->out_conn;
dir_conn = CMP_OUTPUT;
} else {
dir_stream = AMDTP_OUT_STREAM;
conn = &bebob->in_conn;
dir_conn = CMP_INPUT;
}
if (stream == &bebob->tx_stream) {
if (bebob->quirks & SND_BEBOB_QUIRK_WRONG_DBC)
flags |= CIP_EMPTY_HAS_WRONG_DBC;
}
err = cmp_connection_init(conn, bebob->unit, dir_conn, 0);
if (err < 0)
return err;
err = amdtp_am824_init(stream, bebob->unit, dir_stream, flags);
if (err < 0) {
cmp_connection_destroy(conn);
return err;
}
return 0;
}
static void destroy_stream(struct snd_bebob *bebob, struct amdtp_stream *stream)
{
amdtp_stream_destroy(stream);
if (stream == &bebob->tx_stream)
cmp_connection_destroy(&bebob->out_conn);
else
cmp_connection_destroy(&bebob->in_conn);
}
int snd_bebob_stream_init_duplex(struct snd_bebob *bebob)
{
int err;
err = init_stream(bebob, &bebob->tx_stream);
if (err < 0)
return err;
err = init_stream(bebob, &bebob->rx_stream);
if (err < 0) {
destroy_stream(bebob, &bebob->tx_stream);
return err;
}
err = amdtp_domain_init(&bebob->domain);
if (err < 0) {
destroy_stream(bebob, &bebob->tx_stream);
destroy_stream(bebob, &bebob->rx_stream);
}
return err;
}
static int keep_resources(struct snd_bebob *bebob, struct amdtp_stream *stream,
unsigned int rate, unsigned int index)
{
unsigned int pcm_channels;
unsigned int midi_ports;
struct cmp_connection *conn;
int err;
if (stream == &bebob->tx_stream) {
pcm_channels = bebob->tx_stream_formations[index].pcm;
midi_ports = bebob->midi_input_ports;
conn = &bebob->out_conn;
} else {
pcm_channels = bebob->rx_stream_formations[index].pcm;
midi_ports = bebob->midi_output_ports;
conn = &bebob->in_conn;
}
err = amdtp_am824_set_parameters(stream, rate, pcm_channels, midi_ports, false);
if (err < 0)
return err;
return cmp_connection_reserve(conn, amdtp_stream_get_max_payload(stream));
}
int snd_bebob_stream_reserve_duplex(struct snd_bebob *bebob, unsigned int rate,
unsigned int frames_per_period,
unsigned int frames_per_buffer)
{
unsigned int curr_rate;
int err;
// Considering JACK/FFADO streaming:
// TODO: This can be removed hwdep functionality becomes popular.
err = check_connection_used_by_others(bebob, &bebob->rx_stream);
if (err < 0)
return err;
err = bebob->spec->rate->get(bebob, &curr_rate);
if (err < 0)
return err;
if (rate == 0)
rate = curr_rate;
if (curr_rate != rate) {
amdtp_domain_stop(&bebob->domain);
break_both_connections(bebob);
cmp_connection_release(&bebob->out_conn);
cmp_connection_release(&bebob->in_conn);
}
if (bebob->substreams_counter == 0 || curr_rate != rate) {
unsigned int index;
// NOTE:
// If establishing connections at first, Yamaha GO46
// (and maybe Terratec X24) don't generate sound.
//
// For firmware customized by M-Audio, refer to next NOTE.
err = bebob->spec->rate->set(bebob, rate);
if (err < 0) {
dev_err(&bebob->unit->device,
"fail to set sampling rate: %d\n",
err);
return err;
}
err = get_formation_index(rate, &index);
if (err < 0)
return err;
err = keep_resources(bebob, &bebob->tx_stream, rate, index);
if (err < 0)
return err;
err = keep_resources(bebob, &bebob->rx_stream, rate, index);
if (err < 0) {
cmp_connection_release(&bebob->out_conn);
return err;
}
err = amdtp_domain_set_events_per_period(&bebob->domain,
frames_per_period, frames_per_buffer);
if (err < 0) {
cmp_connection_release(&bebob->out_conn);
cmp_connection_release(&bebob->in_conn);
return err;
}
}
return 0;
}
int snd_bebob_stream_start_duplex(struct snd_bebob *bebob)
{
int err;
// Need no substreams.
if (bebob->substreams_counter == 0)
return -EIO;
// packet queueing error or detecting discontinuity
if (amdtp_streaming_error(&bebob->rx_stream) ||
amdtp_streaming_error(&bebob->tx_stream)) {
amdtp_domain_stop(&bebob->domain);
break_both_connections(bebob);
}
if (!amdtp_stream_running(&bebob->rx_stream)) {
enum snd_bebob_clock_type src;
unsigned int curr_rate;
unsigned int tx_init_skip_cycles;
if (bebob->maudio_special_quirk) {
err = bebob->spec->rate->get(bebob, &curr_rate);
if (err < 0)
return err;
}
err = snd_bebob_stream_get_clock_src(bebob, &src);
if (err < 0)
return err;
err = start_stream(bebob, &bebob->rx_stream);
if (err < 0)
goto error;
err = start_stream(bebob, &bebob->tx_stream);
if (err < 0)
goto error;
if (!(bebob->quirks & SND_BEBOB_QUIRK_INITIAL_DISCONTINUOUS_DBC))
tx_init_skip_cycles = 0;
else
tx_init_skip_cycles = 16000;
// MEMO: Some devices start packet transmission long enough after establishment of
// CMP connection. In the early stage of packet streaming, any device transfers
// NODATA packets. After several hundred cycles, it begins to multiplex event into
// the packet with adequate value of syt field in CIP header. Some devices are
// strictly to generate any discontinuity in the sequence of tx packet when they
// receives inadequate sequence of value in syt field of CIP header. In the case,
// the request to break CMP connection is often corrupted, then any transaction
// results in unrecoverable error, sometimes generate bus-reset.
err = amdtp_domain_start(&bebob->domain, tx_init_skip_cycles, true, false);
if (err < 0)
goto error;
// NOTE:
// The firmware customized by M-Audio uses these commands to
// start transmitting stream. This is not usual way.
if (bebob->maudio_special_quirk) {
err = bebob->spec->rate->set(bebob, curr_rate);
if (err < 0) {
dev_err(&bebob->unit->device,
"fail to ensure sampling rate: %d\n",
err);
goto error;
}
}
// Some devices postpone start of transmission mostly for 1 sec after receives
// packets firstly.
if (!amdtp_domain_wait_ready(&bebob->domain, READY_TIMEOUT_MS)) {
err = -ETIMEDOUT;
goto error;
}
}
return 0;
error:
amdtp_domain_stop(&bebob->domain);
break_both_connections(bebob);
return err;
}
void snd_bebob_stream_stop_duplex(struct snd_bebob *bebob)
{
if (bebob->substreams_counter == 0) {
amdtp_domain_stop(&bebob->domain);
break_both_connections(bebob);
cmp_connection_release(&bebob->out_conn);
cmp_connection_release(&bebob->in_conn);
}
}
/*
* This function should be called before starting streams or after stopping
* streams.
*/
void snd_bebob_stream_destroy_duplex(struct snd_bebob *bebob)
{
amdtp_domain_destroy(&bebob->domain);
destroy_stream(bebob, &bebob->tx_stream);
destroy_stream(bebob, &bebob->rx_stream);
}
/*
* See: Table 50: Extended Stream Format Info Format Hierarchy Level 2’
* in Additional AVC commands (Nov 2003, BridgeCo)
* Also 'Clause 12 AM824 sequence adaption layers' in IEC 61883-6:2005
*/
static int
parse_stream_formation(u8 *buf, unsigned int len,
struct snd_bebob_stream_formation *formation)
{
unsigned int i, e, channels, format;
/*
* this module can support a hierarchy combination that:
* Root: Audio and Music (0x90)
* Level 1: AM824 Compound (0x40)
*/
if ((buf[0] != 0x90) || (buf[1] != 0x40))
return -ENOSYS;
/* check sampling rate */
for (i = 0; i < ARRAY_SIZE(bridgeco_freq_table); i++) {
if (buf[2] == bridgeco_freq_table[i])
break;
}
if (i == ARRAY_SIZE(bridgeco_freq_table))
return -ENOSYS;
/* Avoid double count by different entries for the same rate. */
memset(&formation[i], 0, sizeof(struct snd_bebob_stream_formation));
for (e = 0; e < buf[4]; e++) {
channels = buf[5 + e * 2];
format = buf[6 + e * 2];
switch (format) {
/* IEC 60958 Conformant, currently handled as MBLA */
case 0x00:
/* Multi bit linear audio */
case 0x06: /* Raw */
formation[i].pcm += channels;
break;
/* MIDI Conformant */
case 0x0d:
formation[i].midi += channels;
break;
/* IEC 61937-3 to 7 */
case 0x01:
case 0x02:
case 0x03:
case 0x04:
case 0x05:
/* Multi bit linear audio */
case 0x07: /* DVD-Audio */
case 0x0c: /* High Precision */
/* One Bit Audio */
case 0x08: /* (Plain) Raw */
case 0x09: /* (Plain) SACD */
case 0x0a: /* (Encoded) Raw */
case 0x0b: /* (Encoded) SACD */
/* Synchronization Stream (Stereo Raw audio) */
case 0x40:
/* Don't care */
case 0xff:
default:
return -ENOSYS; /* not supported */
}
}
if (formation[i].pcm > AM824_MAX_CHANNELS_FOR_PCM ||
formation[i].midi > AM824_MAX_CHANNELS_FOR_MIDI)
return -ENOSYS;
return 0;
}
static int fill_stream_formations(struct snd_bebob *bebob, u8 addr[AVC_BRIDGECO_ADDR_BYTES],
enum avc_bridgeco_plug_dir plug_dir, unsigned int plug_id,
struct snd_bebob_stream_formation *formations)
{
enum avc_bridgeco_plug_type plug_type;
u8 *buf;
unsigned int len, eid;
int err;
avc_bridgeco_fill_unit_addr(addr, plug_dir, AVC_BRIDGECO_PLUG_UNIT_ISOC, plug_id);
err = avc_bridgeco_get_plug_type(bebob->unit, addr, &plug_type);
if (err < 0) {
dev_err(&bebob->unit->device,
"Fail to get type for isoc %d plug 0: %d\n", plug_dir, err);
return err;
} else if (plug_type != AVC_BRIDGECO_PLUG_TYPE_ISOC)
return -ENXIO;
buf = kmalloc(FORMAT_MAXIMUM_LENGTH, GFP_KERNEL);
if (buf == NULL)
return -ENOMEM;
for (eid = 0; eid < SND_BEBOB_STRM_FMT_ENTRIES; ++eid) {
avc_bridgeco_fill_unit_addr(addr, plug_dir, AVC_BRIDGECO_PLUG_UNIT_ISOC, plug_id);
len = FORMAT_MAXIMUM_LENGTH;
err = avc_bridgeco_get_plug_strm_fmt(bebob->unit, addr, buf, &len, eid);
// No entries remained.
if (err == -EINVAL && eid > 0) {
err = 0;
break;
} else if (err < 0) {
dev_err(&bebob->unit->device,
"fail to get stream format %d for isoc %d plug %d:%d\n",
eid, plug_dir, plug_id, err);
break;
}
err = parse_stream_formation(buf, len, formations);
if (err < 0)
break;
}
kfree(buf);
return err;
}
static int detect_midi_ports(struct snd_bebob *bebob,
const struct snd_bebob_stream_formation *formats,
u8 addr[AVC_BRIDGECO_ADDR_BYTES], enum avc_bridgeco_plug_dir plug_dir,
unsigned int plug_count, unsigned int *midi_ports)
{
int i;
int err = 0;
*midi_ports = 0;
/// Detect the number of available MIDI ports when packet has MIDI conformant data channel.
for (i = 0; i < SND_BEBOB_STRM_FMT_ENTRIES; ++i) {
if (formats[i].midi > 0)
break;
}
if (i >= SND_BEBOB_STRM_FMT_ENTRIES)
return 0;
for (i = 0; i < plug_count; ++i) {
enum avc_bridgeco_plug_type plug_type;
unsigned int ch_count;
avc_bridgeco_fill_unit_addr(addr, plug_dir, AVC_BRIDGECO_PLUG_UNIT_EXT, i);
err = avc_bridgeco_get_plug_type(bebob->unit, addr, &plug_type);
if (err < 0) {
dev_err(&bebob->unit->device,
"fail to get type for external %d plug %d: %d\n",
plug_dir, i, err);
break;
} else if (plug_type != AVC_BRIDGECO_PLUG_TYPE_MIDI) {
continue;
}
err = avc_bridgeco_get_plug_ch_count(bebob->unit, addr, &ch_count);
if (err < 0)
break;
// Yamaha GO44, GO46, Terratec Phase 24, Phase x24 reports 0 for the number of
// channels in external output plug 3 (MIDI type) even if it has a pair of physical
// MIDI jacks. As a workaround, assume it as one.
if (ch_count == 0)
ch_count = 1;
*midi_ports += ch_count;
}
return err;
}
static int
seek_msu_sync_input_plug(struct snd_bebob *bebob)
{
u8 plugs[AVC_PLUG_INFO_BUF_BYTES], addr[AVC_BRIDGECO_ADDR_BYTES];
unsigned int i;
enum avc_bridgeco_plug_type type;
int err;
/* Get the number of Music Sub Unit for both direction. */
err = avc_general_get_plug_info(bebob->unit, 0x0c, 0x00, 0x00, plugs);
if (err < 0) {
dev_err(&bebob->unit->device,
"fail to get info for MSU in/out plugs: %d\n",
err);
goto end;
}
/* seek destination plugs for 'MSU sync input' */
bebob->sync_input_plug = -1;
for (i = 0; i < plugs[0]; i++) {
avc_bridgeco_fill_msu_addr(addr, AVC_BRIDGECO_PLUG_DIR_IN, i);
err = avc_bridgeco_get_plug_type(bebob->unit, addr, &type);
if (err < 0) {
dev_err(&bebob->unit->device,
"fail to get type for MSU in plug %d: %d\n",
i, err);
goto end;
}
if (type == AVC_BRIDGECO_PLUG_TYPE_SYNC) {
bebob->sync_input_plug = i;
break;
}
}
end:
return err;
}
int snd_bebob_stream_discover(struct snd_bebob *bebob)
{
const struct snd_bebob_clock_spec *clk_spec = bebob->spec->clock;
u8 plugs[AVC_PLUG_INFO_BUF_BYTES], addr[AVC_BRIDGECO_ADDR_BYTES];
int err;
/* the number of plugs for isoc in/out, ext in/out */
err = avc_general_get_plug_info(bebob->unit, 0x1f, 0x07, 0x00, plugs);
if (err < 0) {
dev_err(&bebob->unit->device,
"fail to get info for isoc/external in/out plugs: %d\n",
err);
goto end;
}
/*
* This module supports at least one isoc input plug and one isoc
* output plug.
*/
if ((plugs[0] == 0) || (plugs[1] == 0)) {
err = -ENOSYS;
goto end;
}
err = fill_stream_formations(bebob, addr, AVC_BRIDGECO_PLUG_DIR_IN, 0,
bebob->rx_stream_formations);
if (err < 0)
goto end;
err = fill_stream_formations(bebob, addr, AVC_BRIDGECO_PLUG_DIR_OUT, 0,
bebob->tx_stream_formations);
if (err < 0)
goto end;
err = detect_midi_ports(bebob, bebob->tx_stream_formations, addr, AVC_BRIDGECO_PLUG_DIR_IN,
plugs[2], &bebob->midi_input_ports);
if (err < 0)
goto end;
err = detect_midi_ports(bebob, bebob->rx_stream_formations, addr, AVC_BRIDGECO_PLUG_DIR_OUT,
plugs[3], &bebob->midi_output_ports);
if (err < 0)
goto end;
/* for check source of clock later */
if (!clk_spec)
err = seek_msu_sync_input_plug(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)
{
guard(spinlock_irq)(&bebob->lock);
/* user land lock this */
if (bebob->dev_lock_count < 0)
return -EBUSY;
/* this is the first time */
if (bebob->dev_lock_count++ == 0)
snd_bebob_stream_lock_changed(bebob);
return 0;
}
void snd_bebob_stream_lock_release(struct snd_bebob *bebob)
{
guard(spinlock_irq)(&bebob->lock);
if (WARN_ON(bebob->dev_lock_count <= 0))
return;
if (--bebob->dev_lock_count == 0)
snd_bebob_stream_lock_changed(bebob);
}
@@ -0,0 +1,53 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* bebob_terratec.c - a part of driver for BeBoB based devices
*
* Copyright (c) 2013-2014 Takashi Sakamoto
*/
#include "./bebob.h"
static const enum snd_bebob_clock_type phase88_rack_clk_src_types[] = {
SND_BEBOB_CLOCK_TYPE_INTERNAL,
SND_BEBOB_CLOCK_TYPE_EXTERNAL, /* S/PDIF */
SND_BEBOB_CLOCK_TYPE_EXTERNAL, /* 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, 9, &enable_ext);
if (err < 0)
goto end;
err = avc_audio_get_selector(bebob->unit, 0, 8, &enable_word);
if (err < 0)
goto end;
if (enable_ext == 0)
*id = 0;
else if (enable_word == 0)
*id = 1;
else
*id = 2;
end:
return err;
}
static const struct snd_bebob_rate_spec phase_series_rate_spec = {
.get = &snd_bebob_stream_get_rate,
.set = &snd_bebob_stream_set_rate,
};
/* PHASE 88 Rack FW */
static const struct snd_bebob_clock_spec phase88_rack_clk = {
.num = ARRAY_SIZE(phase88_rack_clk_src_types),
.types = phase88_rack_clk_src_types,
.get = &phase88_rack_clk_src_get,
};
const struct snd_bebob_spec phase88_rack_spec = {
.clock = &phase88_rack_clk,
.rate = &phase_series_rate_spec,
.meter = NULL
};
@@ -0,0 +1,64 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* bebob_yamaha.c - a part of driver for BeBoB based devices
*
* Copyright (c) 2013-2014 Takashi Sakamoto
*/
#include "./bebob.h"
/*
* NOTE:
* Yamaha GO44 is not designed 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 recover 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
* recommend users to close ffado-mixer at 192.0kHz if mixer is needless.
*
* Terratec PHASE 24 FW and PHASE X24 FW are internally the same as
* Yamaha GO 44 and GO 46. Yamaha and Terratec had cooperated for these models.
*/
static const enum snd_bebob_clock_type clk_src_types[] = {
SND_BEBOB_CLOCK_TYPE_INTERNAL,
SND_BEBOB_CLOCK_TYPE_EXTERNAL, /* S/PDIF */
};
static int
clk_src_get(struct snd_bebob *bebob, unsigned int *id)
{
int err;
err = avc_audio_get_selector(bebob->unit, 0, 4, id);
if (err < 0)
return err;
if (*id >= ARRAY_SIZE(clk_src_types))
return -EIO;
return 0;
}
static const struct snd_bebob_clock_spec clock_spec = {
.num = ARRAY_SIZE(clk_src_types),
.types = clk_src_types,
.get = &clk_src_get,
};
static const struct snd_bebob_rate_spec rate_spec = {
.get = &snd_bebob_stream_get_rate,
.set = &snd_bebob_stream_set_rate,
};
const struct snd_bebob_spec yamaha_terratec_spec = {
.clock = &clock_spec,
.rate = &rate_spec,
.meter = NULL
};