mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
384 lines
10 KiB
384 lines
10 KiB
// SPDX-License-Identifier: GPL-2.0-only |
|
/* |
|
* oxfw.c - a part of driver for OXFW970/971 based devices |
|
* |
|
* Copyright (c) Clemens Ladisch <[email protected]> |
|
*/ |
|
|
|
#include "oxfw.h" |
|
|
|
#define OXFORD_FIRMWARE_ID_ADDRESS (CSR_REGISTER_BASE + 0x50000) |
|
/* 0x970?vvvv or 0x971?vvvv, where vvvv = firmware version */ |
|
|
|
#define OXFORD_HARDWARE_ID_ADDRESS (CSR_REGISTER_BASE + 0x90020) |
|
#define OXFORD_HARDWARE_ID_OXFW970 0x39443841 |
|
#define OXFORD_HARDWARE_ID_OXFW971 0x39373100 |
|
|
|
#define VENDOR_LOUD 0x000ff2 |
|
#define VENDOR_GRIFFIN 0x001292 |
|
#define VENDOR_BEHRINGER 0x001564 |
|
#define VENDOR_LACIE 0x00d04b |
|
#define VENDOR_TASCAM 0x00022e |
|
#define OUI_STANTON 0x001260 |
|
#define OUI_APOGEE 0x0003db |
|
|
|
#define MODEL_SATELLITE 0x00200f |
|
#define MODEL_SCS1M 0x001000 |
|
#define MODEL_DUET_FW 0x01dddd |
|
|
|
#define SPECIFIER_1394TA 0x00a02d |
|
#define VERSION_AVC 0x010001 |
|
|
|
MODULE_DESCRIPTION("Oxford Semiconductor FW970/971 driver"); |
|
MODULE_AUTHOR("Clemens Ladisch <[email protected]>"); |
|
MODULE_LICENSE("GPL v2"); |
|
MODULE_ALIAS("snd-firewire-speakers"); |
|
MODULE_ALIAS("snd-scs1x"); |
|
|
|
struct compat_info { |
|
const char *driver_name; |
|
const char *vendor_name; |
|
const char *model_name; |
|
}; |
|
|
|
static bool detect_loud_models(struct fw_unit *unit) |
|
{ |
|
const char *const models[] = { |
|
"Onyxi", |
|
"Onyx-i", |
|
"Onyx 1640i", |
|
"d.Pro", |
|
"U.420"}; |
|
char model[32]; |
|
int err; |
|
|
|
err = fw_csr_string(unit->directory, CSR_MODEL, |
|
model, sizeof(model)); |
|
if (err < 0) |
|
return false; |
|
|
|
return match_string(models, ARRAY_SIZE(models), model) >= 0; |
|
} |
|
|
|
static int name_card(struct snd_oxfw *oxfw, const struct ieee1394_device_id *entry) |
|
{ |
|
struct fw_device *fw_dev = fw_parent_device(oxfw->unit); |
|
const struct compat_info *info; |
|
char vendor[24]; |
|
char model[32]; |
|
const char *d, *v, *m; |
|
u32 firmware; |
|
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(oxfw->unit->directory, CSR_MODEL, |
|
model, sizeof(model)); |
|
if (err < 0) |
|
goto end; |
|
|
|
err = snd_fw_transaction(oxfw->unit, TCODE_READ_QUADLET_REQUEST, |
|
OXFORD_FIRMWARE_ID_ADDRESS, &firmware, 4, 0); |
|
if (err < 0) |
|
goto end; |
|
be32_to_cpus(&firmware); |
|
|
|
if (firmware >> 20 == 0x970) |
|
oxfw->quirks |= SND_OXFW_QUIRK_JUMBO_PAYLOAD; |
|
|
|
/* to apply card definitions */ |
|
if (entry->vendor_id == VENDOR_GRIFFIN || entry->vendor_id == VENDOR_LACIE) { |
|
info = (const struct compat_info *)entry->driver_data; |
|
d = info->driver_name; |
|
v = info->vendor_name; |
|
m = info->model_name; |
|
} else { |
|
d = "OXFW"; |
|
v = vendor; |
|
m = model; |
|
} |
|
|
|
strcpy(oxfw->card->driver, d); |
|
strcpy(oxfw->card->mixername, m); |
|
strcpy(oxfw->card->shortname, m); |
|
|
|
snprintf(oxfw->card->longname, sizeof(oxfw->card->longname), |
|
"%s %s (OXFW%x %04x), GUID %08x%08x at %s, S%d", |
|
v, m, firmware >> 20, firmware & 0xffff, |
|
fw_dev->config_rom[3], fw_dev->config_rom[4], |
|
dev_name(&oxfw->unit->device), 100 << fw_dev->max_speed); |
|
end: |
|
return err; |
|
} |
|
|
|
static void oxfw_card_free(struct snd_card *card) |
|
{ |
|
struct snd_oxfw *oxfw = card->private_data; |
|
|
|
if (oxfw->has_output || oxfw->has_input) |
|
snd_oxfw_stream_destroy_duplex(oxfw); |
|
|
|
mutex_destroy(&oxfw->mutex); |
|
fw_unit_put(oxfw->unit); |
|
} |
|
|
|
static int detect_quirks(struct snd_oxfw *oxfw, const struct ieee1394_device_id *entry) |
|
{ |
|
struct fw_device *fw_dev = fw_parent_device(oxfw->unit); |
|
struct fw_csr_iterator it; |
|
int key, val; |
|
int vendor, model; |
|
|
|
/* |
|
* Add ALSA control elements for two models to keep compatibility to |
|
* old firewire-speaker module. |
|
*/ |
|
if (entry->vendor_id == VENDOR_GRIFFIN) |
|
return snd_oxfw_add_spkr(oxfw, false); |
|
if (entry->vendor_id == VENDOR_LACIE) |
|
return snd_oxfw_add_spkr(oxfw, true); |
|
|
|
/* |
|
* Stanton models supports asynchronous transactions for unique MIDI |
|
* messages. |
|
*/ |
|
if (entry->vendor_id == OUI_STANTON) { |
|
oxfw->quirks |= SND_OXFW_QUIRK_SCS_TRANSACTION; |
|
if (entry->model_id == MODEL_SCS1M) |
|
oxfw->quirks |= SND_OXFW_QUIRK_BLOCKING_TRANSMISSION; |
|
|
|
// No physical MIDI ports. |
|
oxfw->midi_input_ports = 0; |
|
oxfw->midi_output_ports = 0; |
|
|
|
return snd_oxfw_scs1x_add(oxfw); |
|
} |
|
|
|
if (entry->vendor_id == OUI_APOGEE && entry->model_id == MODEL_DUET_FW) |
|
oxfw->quirks |= SND_OXFW_QUIRK_BLOCKING_TRANSMISSION; |
|
|
|
/* |
|
* TASCAM FireOne has physical control and requires a pair of additional |
|
* MIDI ports. |
|
*/ |
|
if (entry->vendor_id == VENDOR_TASCAM) { |
|
oxfw->midi_input_ports++; |
|
oxfw->midi_output_ports++; |
|
return 0; |
|
} |
|
|
|
/* Seek from Root Directory of Config ROM. */ |
|
vendor = model = 0; |
|
fw_csr_iterator_init(&it, fw_dev->config_rom + 5); |
|
while (fw_csr_iterator_next(&it, &key, &val)) { |
|
if (key == CSR_VENDOR) |
|
vendor = val; |
|
else if (key == CSR_MODEL) |
|
model = val; |
|
} |
|
|
|
/* |
|
* Mackie Onyx Satellite with base station has a quirk to report a wrong |
|
* value in 'dbs' field of CIP header against its format information. |
|
*/ |
|
if (vendor == VENDOR_LOUD && model == MODEL_SATELLITE) |
|
oxfw->quirks |= SND_OXFW_QUIRK_WRONG_DBS; |
|
|
|
return 0; |
|
} |
|
|
|
static int oxfw_probe(struct fw_unit *unit, const struct ieee1394_device_id *entry) |
|
{ |
|
struct snd_card *card; |
|
struct snd_oxfw *oxfw; |
|
int err; |
|
|
|
if (entry->vendor_id == VENDOR_LOUD && entry->model_id == 0 && !detect_loud_models(unit)) |
|
return -ENODEV; |
|
|
|
err = snd_card_new(&unit->device, -1, NULL, THIS_MODULE, sizeof(*oxfw), &card); |
|
if (err < 0) |
|
return err; |
|
card->private_free = oxfw_card_free; |
|
|
|
oxfw = card->private_data; |
|
oxfw->unit = fw_unit_get(unit); |
|
dev_set_drvdata(&unit->device, oxfw); |
|
oxfw->card = card; |
|
|
|
mutex_init(&oxfw->mutex); |
|
spin_lock_init(&oxfw->lock); |
|
init_waitqueue_head(&oxfw->hwdep_wait); |
|
|
|
err = name_card(oxfw, entry); |
|
if (err < 0) |
|
goto error; |
|
|
|
err = snd_oxfw_stream_discover(oxfw); |
|
if (err < 0) |
|
goto error; |
|
|
|
err = detect_quirks(oxfw, entry); |
|
if (err < 0) |
|
goto error; |
|
|
|
if (oxfw->has_output || oxfw->has_input) { |
|
err = snd_oxfw_stream_init_duplex(oxfw); |
|
if (err < 0) |
|
goto error; |
|
|
|
err = snd_oxfw_create_pcm(oxfw); |
|
if (err < 0) |
|
goto error; |
|
|
|
snd_oxfw_proc_init(oxfw); |
|
|
|
err = snd_oxfw_create_midi(oxfw); |
|
if (err < 0) |
|
goto error; |
|
|
|
err = snd_oxfw_create_hwdep(oxfw); |
|
if (err < 0) |
|
goto error; |
|
} |
|
|
|
err = snd_card_register(card); |
|
if (err < 0) |
|
goto error; |
|
|
|
return 0; |
|
error: |
|
snd_card_free(card); |
|
return err; |
|
} |
|
|
|
static void oxfw_bus_reset(struct fw_unit *unit) |
|
{ |
|
struct snd_oxfw *oxfw = dev_get_drvdata(&unit->device); |
|
|
|
fcp_bus_reset(oxfw->unit); |
|
|
|
if (oxfw->has_output || oxfw->has_input) { |
|
mutex_lock(&oxfw->mutex); |
|
snd_oxfw_stream_update_duplex(oxfw); |
|
mutex_unlock(&oxfw->mutex); |
|
} |
|
|
|
if (oxfw->quirks & SND_OXFW_QUIRK_SCS_TRANSACTION) |
|
snd_oxfw_scs1x_update(oxfw); |
|
} |
|
|
|
static void oxfw_remove(struct fw_unit *unit) |
|
{ |
|
struct snd_oxfw *oxfw = dev_get_drvdata(&unit->device); |
|
|
|
// Block till all of ALSA character devices are released. |
|
snd_card_free(oxfw->card); |
|
} |
|
|
|
static const struct compat_info griffin_firewave = { |
|
.driver_name = "FireWave", |
|
.vendor_name = "Griffin", |
|
.model_name = "FireWave", |
|
}; |
|
|
|
static const struct compat_info lacie_speakers = { |
|
.driver_name = "FWSpeakers", |
|
.vendor_name = "LaCie", |
|
.model_name = "FireWire Speakers", |
|
}; |
|
|
|
#define OXFW_DEV_ENTRY(vendor, model, data) \ |
|
{ \ |
|
.match_flags = IEEE1394_MATCH_VENDOR_ID | \ |
|
IEEE1394_MATCH_MODEL_ID | \ |
|
IEEE1394_MATCH_SPECIFIER_ID | \ |
|
IEEE1394_MATCH_VERSION, \ |
|
.vendor_id = vendor, \ |
|
.model_id = model, \ |
|
.specifier_id = SPECIFIER_1394TA, \ |
|
.version = VERSION_AVC, \ |
|
.driver_data = (kernel_ulong_t)data, \ |
|
} |
|
|
|
static const struct ieee1394_device_id oxfw_id_table[] = { |
|
// |
|
// OXFW970 devices: |
|
// Initial firmware has a quirk to postpone isoc packet transmission during finishing async |
|
// transaction. As a result, several isochronous cycles are skipped to transfer the packets |
|
// and the audio data frames which should have been transferred during the cycles are put |
|
// into packet at the first isoc cycle after the postpone. Furthermore, the value of SYT |
|
// field in CIP header is not reliable as synchronization timing, |
|
// |
|
OXFW_DEV_ENTRY(VENDOR_GRIFFIN, 0x00f970, &griffin_firewave), |
|
OXFW_DEV_ENTRY(VENDOR_LACIE, 0x00f970, &lacie_speakers), |
|
// Behringer,F-Control Audio 202. The value of SYT field is not reliable at all. |
|
OXFW_DEV_ENTRY(VENDOR_BEHRINGER, 0x00fc22, NULL), |
|
// Loud Technologies, Tapco Link.FireWire 4x6. The value of SYT field is always 0xffff. |
|
OXFW_DEV_ENTRY(VENDOR_LOUD, 0x000460, NULL), |
|
// Loud Technologies, Mackie Onyx Satellite. Although revised version of firmware is |
|
// installed to avoid the postpone, the value of SYT field is always 0xffff. |
|
OXFW_DEV_ENTRY(VENDOR_LOUD, MODEL_SATELLITE, NULL), |
|
// Miglia HarmonyAudio. Not yet identified. |
|
|
|
// |
|
// OXFW971 devices: |
|
// The value of SYT field in CIP header is enough reliable. Both of blocking and non-blocking |
|
// transmission methods are available. |
|
// |
|
// Any Mackie(Loud) models (name string/model id): |
|
// Onyx-i series (former models): 0x081216 |
|
// Onyx 1640i: 0x001640 |
|
// d.2 pro/d.4 pro (built-in card): Unknown |
|
// U.420: Unknown |
|
// U.420d: Unknown |
|
{ |
|
.match_flags = IEEE1394_MATCH_VENDOR_ID | |
|
IEEE1394_MATCH_SPECIFIER_ID | |
|
IEEE1394_MATCH_VERSION, |
|
.vendor_id = VENDOR_LOUD, |
|
.model_id = 0, |
|
.specifier_id = SPECIFIER_1394TA, |
|
.version = VERSION_AVC, |
|
}, |
|
// TASCAM, FireOne. |
|
OXFW_DEV_ENTRY(VENDOR_TASCAM, 0x800007, NULL), |
|
// Stanton, Stanton Controllers & Systems 1 Mixer (SCS.1m). |
|
OXFW_DEV_ENTRY(OUI_STANTON, MODEL_SCS1M, NULL), |
|
// Stanton, Stanton Controllers & Systems 1 Deck (SCS.1d). |
|
OXFW_DEV_ENTRY(OUI_STANTON, 0x002000, NULL), |
|
// APOGEE, duet FireWire. |
|
OXFW_DEV_ENTRY(OUI_APOGEE, MODEL_DUET_FW, NULL), |
|
{ } |
|
}; |
|
MODULE_DEVICE_TABLE(ieee1394, oxfw_id_table); |
|
|
|
static struct fw_driver oxfw_driver = { |
|
.driver = { |
|
.owner = THIS_MODULE, |
|
.name = KBUILD_MODNAME, |
|
.bus = &fw_bus_type, |
|
}, |
|
.probe = oxfw_probe, |
|
.update = oxfw_bus_reset, |
|
.remove = oxfw_remove, |
|
.id_table = oxfw_id_table, |
|
}; |
|
|
|
static int __init snd_oxfw_init(void) |
|
{ |
|
return driver_register(&oxfw_driver.driver); |
|
} |
|
|
|
static void __exit snd_oxfw_exit(void) |
|
{ |
|
driver_unregister(&oxfw_driver.driver); |
|
} |
|
|
|
module_init(snd_oxfw_init); |
|
module_exit(snd_oxfw_exit);
|
|
|