I-ONIX FW810s was shipped in Lexicon brand of HARMAN International industries, Inc 2009. The model uses TCD2220 ASIC as its communication engine. TCAT general protocol is supported, its extension isn't. This patch adds support for the model with hard-coded stream formats. $ python3 ~/git/linux-firewire-utils/src/crpp < /sys/bus/firewire/devices/fw1/config_rom ROM header and bus information block ----------------------------------------------------------------- 400 04042b91 bus_info_length 4, crc_length 4, crc 11153 404 31333934 bus_name "1394" 408 e0008102 irmc 1, cmc 1, isc 1, bmc 0, cyc_clk_acc 0, max_rec 8 (512) 40c 000fd720 company_id 000fd7 | 410 007d7ecf device_id 20007d7ecf | EUI-64 000fd720007d7ecf root directory ----------------------------------------------------------------- 414 00064c2d directory_length 6, crc 19501 418 03000fd7 vendor 41c 8100000a --> descriptor leaf at 444 420 17000001 model 424 8100000d --> descriptor leaf at 458 428 0c0087c0 node capabilities per IEEE 1394 42c d1000001 --> unit directory at 430 unit directory at 430 ----------------------------------------------------------------- 430 000438f2 directory_length 4, crc 14578 434 12000fd7 specifier id 438 13000001 version 43c 17000001 model 440 8100000d --> descriptor leaf at 474 descriptor leaf at 444 ----------------------------------------------------------------- 444 000489d5 leaf_length 4, crc 35285 448 00000000 textual descriptor 44c 00000000 minimal ASCII 450 4c657869 "Lexi" 454 636f6e00 "con" descriptor leaf at 458 ----------------------------------------------------------------- 458 0006594b leaf_length 6, crc 22859 45c 00000000 textual descriptor 460 00000000 minimal ASCII 464 492d4f4e "I-ON" 468 49585f46 "IX_F" 46c 57383130 "W810" 470 53000000 "S" descriptor leaf at 474 ----------------------------------------------------------------- 474 0006594b leaf_length 6, crc 22859 478 00000000 textual descriptor 47c 00000000 minimal ASCII 480 492d4f4e "I-ON" 484 49585f46 "IX_F" 488 57383130 "W810" 48c 53000000 "S" Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp> Link: https://lore.kernel.org/r/20210115035623.148580-1-o-takashi@sakamocchi.jp Signed-off-by: Takashi Iwai <tiwai@suse.de>
435 lines
11 KiB
C
435 lines
11 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* TC Applied Technologies Digital Interface Communications Engine driver
|
|
*
|
|
* Copyright (c) Clemens Ladisch <clemens@ladisch.de>
|
|
*/
|
|
|
|
#include "dice.h"
|
|
|
|
MODULE_DESCRIPTION("DICE driver");
|
|
MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>");
|
|
MODULE_LICENSE("GPL v2");
|
|
|
|
#define OUI_WEISS 0x001c6a
|
|
#define OUI_LOUD 0x000ff2
|
|
#define OUI_FOCUSRITE 0x00130e
|
|
#define OUI_TCELECTRONIC 0x000166
|
|
#define OUI_ALESIS 0x000595
|
|
#define OUI_MAUDIO 0x000d6c
|
|
#define OUI_MYTEK 0x001ee8
|
|
#define OUI_SSL 0x0050c2 // Actually ID reserved by IEEE.
|
|
#define OUI_PRESONUS 0x000a92
|
|
#define OUI_HARMAN 0x000fd7
|
|
|
|
#define DICE_CATEGORY_ID 0x04
|
|
#define WEISS_CATEGORY_ID 0x00
|
|
#define LOUD_CATEGORY_ID 0x10
|
|
#define HARMAN_CATEGORY_ID 0x20
|
|
|
|
#define MODEL_ALESIS_IO_BOTH 0x000001
|
|
|
|
static int check_dice_category(struct fw_unit *unit)
|
|
{
|
|
struct fw_device *device = fw_parent_device(unit);
|
|
struct fw_csr_iterator it;
|
|
int key, val, vendor = -1, model = -1;
|
|
unsigned int category;
|
|
|
|
/*
|
|
* Check that GUID and unit directory are constructed according to DICE
|
|
* rules, i.e., that the specifier ID is the GUID's OUI, and that the
|
|
* GUID chip ID consists of the 8-bit category ID, the 10-bit product
|
|
* ID, and a 22-bit serial number.
|
|
*/
|
|
fw_csr_iterator_init(&it, unit->directory);
|
|
while (fw_csr_iterator_next(&it, &key, &val)) {
|
|
switch (key) {
|
|
case CSR_SPECIFIER_ID:
|
|
vendor = val;
|
|
break;
|
|
case CSR_MODEL:
|
|
model = val;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (vendor == OUI_WEISS)
|
|
category = WEISS_CATEGORY_ID;
|
|
else if (vendor == OUI_LOUD)
|
|
category = LOUD_CATEGORY_ID;
|
|
else if (vendor == OUI_HARMAN)
|
|
category = HARMAN_CATEGORY_ID;
|
|
else
|
|
category = DICE_CATEGORY_ID;
|
|
if (device->config_rom[3] != ((vendor << 8) | category) ||
|
|
device->config_rom[4] >> 22 != model)
|
|
return -ENODEV;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int check_clock_caps(struct snd_dice *dice)
|
|
{
|
|
__be32 value;
|
|
int err;
|
|
|
|
/* some very old firmwares don't tell about their clock support */
|
|
if (dice->clock_caps > 0) {
|
|
err = snd_dice_transaction_read_global(dice,
|
|
GLOBAL_CLOCK_CAPABILITIES,
|
|
&value, 4);
|
|
if (err < 0)
|
|
return err;
|
|
dice->clock_caps = be32_to_cpu(value);
|
|
} else {
|
|
/* this should be supported by any device */
|
|
dice->clock_caps = CLOCK_CAP_RATE_44100 |
|
|
CLOCK_CAP_RATE_48000 |
|
|
CLOCK_CAP_SOURCE_ARX1 |
|
|
CLOCK_CAP_SOURCE_INTERNAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void dice_card_strings(struct snd_dice *dice)
|
|
{
|
|
struct snd_card *card = dice->card;
|
|
struct fw_device *dev = fw_parent_device(dice->unit);
|
|
char vendor[32], model[32];
|
|
unsigned int i;
|
|
int err;
|
|
|
|
strcpy(card->driver, "DICE");
|
|
|
|
strcpy(card->shortname, "DICE");
|
|
BUILD_BUG_ON(NICK_NAME_SIZE < sizeof(card->shortname));
|
|
err = snd_dice_transaction_read_global(dice, GLOBAL_NICK_NAME,
|
|
card->shortname,
|
|
sizeof(card->shortname));
|
|
if (err >= 0) {
|
|
/* DICE strings are returned in "always-wrong" endianness */
|
|
BUILD_BUG_ON(sizeof(card->shortname) % 4 != 0);
|
|
for (i = 0; i < sizeof(card->shortname); i += 4)
|
|
swab32s((u32 *)&card->shortname[i]);
|
|
card->shortname[sizeof(card->shortname) - 1] = '\0';
|
|
}
|
|
|
|
strcpy(vendor, "?");
|
|
fw_csr_string(dev->config_rom + 5, CSR_VENDOR, vendor, sizeof(vendor));
|
|
strcpy(model, "?");
|
|
fw_csr_string(dice->unit->directory, CSR_MODEL, model, sizeof(model));
|
|
snprintf(card->longname, sizeof(card->longname),
|
|
"%s %s (serial %u) at %s, S%d",
|
|
vendor, model, dev->config_rom[4] & 0x3fffff,
|
|
dev_name(&dice->unit->device), 100 << dev->max_speed);
|
|
|
|
strcpy(card->mixername, "DICE");
|
|
}
|
|
|
|
static void dice_card_free(struct snd_card *card)
|
|
{
|
|
struct snd_dice *dice = card->private_data;
|
|
|
|
snd_dice_stream_destroy_duplex(dice);
|
|
snd_dice_transaction_destroy(dice);
|
|
}
|
|
|
|
static void do_registration(struct work_struct *work)
|
|
{
|
|
struct snd_dice *dice = container_of(work, struct snd_dice, dwork.work);
|
|
int err;
|
|
|
|
if (dice->registered)
|
|
return;
|
|
|
|
err = snd_card_new(&dice->unit->device, -1, NULL, THIS_MODULE, 0,
|
|
&dice->card);
|
|
if (err < 0)
|
|
return;
|
|
dice->card->private_free = dice_card_free;
|
|
dice->card->private_data = dice;
|
|
|
|
err = snd_dice_transaction_init(dice);
|
|
if (err < 0)
|
|
goto error;
|
|
|
|
err = check_clock_caps(dice);
|
|
if (err < 0)
|
|
goto error;
|
|
|
|
dice_card_strings(dice);
|
|
|
|
err = dice->detect_formats(dice);
|
|
if (err < 0)
|
|
goto error;
|
|
|
|
err = snd_dice_stream_init_duplex(dice);
|
|
if (err < 0)
|
|
goto error;
|
|
|
|
snd_dice_create_proc(dice);
|
|
|
|
err = snd_dice_create_pcm(dice);
|
|
if (err < 0)
|
|
goto error;
|
|
|
|
err = snd_dice_create_midi(dice);
|
|
if (err < 0)
|
|
goto error;
|
|
|
|
err = snd_dice_create_hwdep(dice);
|
|
if (err < 0)
|
|
goto error;
|
|
|
|
err = snd_card_register(dice->card);
|
|
if (err < 0)
|
|
goto error;
|
|
|
|
dice->registered = true;
|
|
|
|
return;
|
|
error:
|
|
snd_card_free(dice->card);
|
|
dev_info(&dice->unit->device,
|
|
"Sound card registration failed: %d\n", err);
|
|
}
|
|
|
|
static int dice_probe(struct fw_unit *unit,
|
|
const struct ieee1394_device_id *entry)
|
|
{
|
|
struct snd_dice *dice;
|
|
int err;
|
|
|
|
if (!entry->driver_data && entry->vendor_id != OUI_SSL) {
|
|
err = check_dice_category(unit);
|
|
if (err < 0)
|
|
return -ENODEV;
|
|
}
|
|
|
|
/* Allocate this independent of sound card instance. */
|
|
dice = devm_kzalloc(&unit->device, sizeof(struct snd_dice), GFP_KERNEL);
|
|
if (!dice)
|
|
return -ENOMEM;
|
|
dice->unit = fw_unit_get(unit);
|
|
dev_set_drvdata(&unit->device, dice);
|
|
|
|
if (!entry->driver_data) {
|
|
dice->detect_formats = snd_dice_stream_detect_current_formats;
|
|
} else {
|
|
dice->detect_formats =
|
|
(snd_dice_detect_formats_t)entry->driver_data;
|
|
}
|
|
|
|
spin_lock_init(&dice->lock);
|
|
mutex_init(&dice->mutex);
|
|
init_completion(&dice->clock_accepted);
|
|
init_waitqueue_head(&dice->hwdep_wait);
|
|
|
|
/* Allocate and register this sound card later. */
|
|
INIT_DEFERRABLE_WORK(&dice->dwork, do_registration);
|
|
snd_fw_schedule_registration(unit, &dice->dwork);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void dice_remove(struct fw_unit *unit)
|
|
{
|
|
struct snd_dice *dice = dev_get_drvdata(&unit->device);
|
|
|
|
/*
|
|
* Confirm to stop the work for registration before the sound card is
|
|
* going to be released. The work is not scheduled again because bus
|
|
* reset handler is not called anymore.
|
|
*/
|
|
cancel_delayed_work_sync(&dice->dwork);
|
|
|
|
if (dice->registered) {
|
|
// Block till all of ALSA character devices are released.
|
|
snd_card_free(dice->card);
|
|
}
|
|
|
|
mutex_destroy(&dice->mutex);
|
|
fw_unit_put(dice->unit);
|
|
}
|
|
|
|
static void dice_bus_reset(struct fw_unit *unit)
|
|
{
|
|
struct snd_dice *dice = dev_get_drvdata(&unit->device);
|
|
|
|
/* Postpone a workqueue for deferred registration. */
|
|
if (!dice->registered)
|
|
snd_fw_schedule_registration(unit, &dice->dwork);
|
|
|
|
/* The handler address register becomes initialized. */
|
|
snd_dice_transaction_reinit(dice);
|
|
|
|
/*
|
|
* After registration, userspace can start packet streaming, then this
|
|
* code block works fine.
|
|
*/
|
|
if (dice->registered) {
|
|
mutex_lock(&dice->mutex);
|
|
snd_dice_stream_update_duplex(dice);
|
|
mutex_unlock(&dice->mutex);
|
|
}
|
|
}
|
|
|
|
#define DICE_INTERFACE 0x000001
|
|
|
|
static const struct ieee1394_device_id dice_id_table[] = {
|
|
/* M-Audio Profire 2626 has a different value in version field. */
|
|
{
|
|
.match_flags = IEEE1394_MATCH_VENDOR_ID |
|
|
IEEE1394_MATCH_MODEL_ID,
|
|
.vendor_id = OUI_MAUDIO,
|
|
.model_id = 0x000010,
|
|
.driver_data = (kernel_ulong_t)snd_dice_detect_extension_formats,
|
|
},
|
|
/* M-Audio Profire 610 has a different value in version field. */
|
|
{
|
|
.match_flags = IEEE1394_MATCH_VENDOR_ID |
|
|
IEEE1394_MATCH_MODEL_ID,
|
|
.vendor_id = OUI_MAUDIO,
|
|
.model_id = 0x000011,
|
|
.driver_data = (kernel_ulong_t)snd_dice_detect_extension_formats,
|
|
},
|
|
/* TC Electronic Konnekt 24D. */
|
|
{
|
|
.match_flags = IEEE1394_MATCH_VENDOR_ID |
|
|
IEEE1394_MATCH_MODEL_ID,
|
|
.vendor_id = OUI_TCELECTRONIC,
|
|
.model_id = 0x000020,
|
|
.driver_data = (kernel_ulong_t)snd_dice_detect_tcelectronic_formats,
|
|
},
|
|
/* TC Electronic Konnekt 8. */
|
|
{
|
|
.match_flags = IEEE1394_MATCH_VENDOR_ID |
|
|
IEEE1394_MATCH_MODEL_ID,
|
|
.vendor_id = OUI_TCELECTRONIC,
|
|
.model_id = 0x000021,
|
|
.driver_data = (kernel_ulong_t)snd_dice_detect_tcelectronic_formats,
|
|
},
|
|
/* TC Electronic Studio Konnekt 48. */
|
|
{
|
|
.match_flags = IEEE1394_MATCH_VENDOR_ID |
|
|
IEEE1394_MATCH_MODEL_ID,
|
|
.vendor_id = OUI_TCELECTRONIC,
|
|
.model_id = 0x000022,
|
|
.driver_data = (kernel_ulong_t)snd_dice_detect_tcelectronic_formats,
|
|
},
|
|
/* TC Electronic Konnekt Live. */
|
|
{
|
|
.match_flags = IEEE1394_MATCH_VENDOR_ID |
|
|
IEEE1394_MATCH_MODEL_ID,
|
|
.vendor_id = OUI_TCELECTRONIC,
|
|
.model_id = 0x000023,
|
|
.driver_data = (kernel_ulong_t)snd_dice_detect_tcelectronic_formats,
|
|
},
|
|
/* TC Electronic Desktop Konnekt 6. */
|
|
{
|
|
.match_flags = IEEE1394_MATCH_VENDOR_ID |
|
|
IEEE1394_MATCH_MODEL_ID,
|
|
.vendor_id = OUI_TCELECTRONIC,
|
|
.model_id = 0x000024,
|
|
.driver_data = (kernel_ulong_t)snd_dice_detect_tcelectronic_formats,
|
|
},
|
|
/* TC Electronic Impact Twin. */
|
|
{
|
|
.match_flags = IEEE1394_MATCH_VENDOR_ID |
|
|
IEEE1394_MATCH_MODEL_ID,
|
|
.vendor_id = OUI_TCELECTRONIC,
|
|
.model_id = 0x000027,
|
|
.driver_data = (kernel_ulong_t)snd_dice_detect_tcelectronic_formats,
|
|
},
|
|
/* TC Electronic Digital Konnekt x32. */
|
|
{
|
|
.match_flags = IEEE1394_MATCH_VENDOR_ID |
|
|
IEEE1394_MATCH_MODEL_ID,
|
|
.vendor_id = OUI_TCELECTRONIC,
|
|
.model_id = 0x000030,
|
|
.driver_data = (kernel_ulong_t)snd_dice_detect_tcelectronic_formats,
|
|
},
|
|
/* Alesis iO14/iO26. */
|
|
{
|
|
.match_flags = IEEE1394_MATCH_VENDOR_ID |
|
|
IEEE1394_MATCH_MODEL_ID,
|
|
.vendor_id = OUI_ALESIS,
|
|
.model_id = MODEL_ALESIS_IO_BOTH,
|
|
.driver_data = (kernel_ulong_t)snd_dice_detect_alesis_formats,
|
|
},
|
|
// Alesis MasterControl.
|
|
{
|
|
.match_flags = IEEE1394_MATCH_VENDOR_ID |
|
|
IEEE1394_MATCH_MODEL_ID,
|
|
.vendor_id = OUI_ALESIS,
|
|
.model_id = 0x000002,
|
|
.driver_data = (kernel_ulong_t)snd_dice_detect_alesis_mastercontrol_formats,
|
|
},
|
|
/* Mytek Stereo 192 DSD-DAC. */
|
|
{
|
|
.match_flags = IEEE1394_MATCH_VENDOR_ID |
|
|
IEEE1394_MATCH_MODEL_ID,
|
|
.vendor_id = OUI_MYTEK,
|
|
.model_id = 0x000002,
|
|
.driver_data = (kernel_ulong_t)snd_dice_detect_mytek_formats,
|
|
},
|
|
// Solid State Logic, Duende Classic and Mini.
|
|
// NOTE: each field of GUID in config ROM is not compliant to standard
|
|
// DICE scheme.
|
|
{
|
|
.match_flags = IEEE1394_MATCH_VENDOR_ID |
|
|
IEEE1394_MATCH_MODEL_ID,
|
|
.vendor_id = OUI_SSL,
|
|
.model_id = 0x000070,
|
|
},
|
|
// Presonus FireStudio.
|
|
{
|
|
.match_flags = IEEE1394_MATCH_VENDOR_ID |
|
|
IEEE1394_MATCH_MODEL_ID,
|
|
.vendor_id = OUI_PRESONUS,
|
|
.model_id = 0x000008,
|
|
.driver_data = (kernel_ulong_t)snd_dice_detect_presonus_formats,
|
|
},
|
|
// Lexicon I-ONYX FW810S.
|
|
{
|
|
.match_flags = IEEE1394_MATCH_VENDOR_ID |
|
|
IEEE1394_MATCH_MODEL_ID,
|
|
.vendor_id = OUI_HARMAN,
|
|
.model_id = 0x000001,
|
|
.driver_data = (kernel_ulong_t)snd_dice_detect_harman_formats,
|
|
},
|
|
{
|
|
.match_flags = IEEE1394_MATCH_VERSION,
|
|
.version = DICE_INTERFACE,
|
|
},
|
|
{ }
|
|
};
|
|
MODULE_DEVICE_TABLE(ieee1394, dice_id_table);
|
|
|
|
static struct fw_driver dice_driver = {
|
|
.driver = {
|
|
.owner = THIS_MODULE,
|
|
.name = KBUILD_MODNAME,
|
|
.bus = &fw_bus_type,
|
|
},
|
|
.probe = dice_probe,
|
|
.update = dice_bus_reset,
|
|
.remove = dice_remove,
|
|
.id_table = dice_id_table,
|
|
};
|
|
|
|
static int __init alsa_dice_init(void)
|
|
{
|
|
return driver_register(&dice_driver.driver);
|
|
}
|
|
|
|
static void __exit alsa_dice_exit(void)
|
|
{
|
|
driver_unregister(&dice_driver.driver);
|
|
}
|
|
|
|
module_init(alsa_dice_init);
|
|
module_exit(alsa_dice_exit);
|