linux/sound/firewire/fireworks/fireworks_stream.c
Takashi Sakamoto 0ca37273ee ALSA: fireworks: add support for Loud Onyx 1200f quirk
Loud Technologies shipped Onyx 1200f 2008 in its Mackie brand and
already discontinued. The model uses component of Fireworks board
module as its communication and DSP function.

The latest firmware (v4.6.0) has a quirk that tx packet includes wrong
value (0x1f) in its dbs field at middle and higher sampling transfer
frequency. It brings ALSA fireworks driver discontinuity of data block
counter.

This commit fixes it by assuming it as a quirk of firmware version
4.6.0.

$ cd linux-firewire-tools/src
$ python crpp < /sys/bus/firewire/devices/fw1/config_rom
               ROM header and bus information block
               -----------------------------------------------------------------
400  0404b9ef  bus_info_length 4, crc_length 4, crc 47599
404  31333934  bus_name "1394"
408  e064a212  irmc 1, cmc 1, isc 1, bmc 0, pmc 0, cyc_clk_acc 100,
               max_rec 10 (2048), max_rom 2, gen 1, spd 2 (S400)
40c  000ff209  company_id 000ff2     |
410  62550ce0  device_id 0962550ce0  | EUI-64 000ff20962550ce0

               root directory
               -----------------------------------------------------------------
414  0008088e  directory_length 8, crc 2190
418  03000ff2  vendor
41c  8100000f  --> descriptor leaf at 458
420  1701200f  model
424  81000018  --> descriptor leaf at 484
428  0c008380  node capabilities
42c  8d000003  --> eui-64 leaf at 438
430  d1000005  --> unit directory at 444
434  08000ff2  (immediate value)

               eui-64 leaf at 438
               -----------------------------------------------------------------
438  000281ae  leaf_length 2, crc 33198
43c  000ff209  company_id 000ff2     |
440  62550ce0  device_id 0962550ce0  | EUI-64 000ff20962550ce0

               unit directory at 444
               -----------------------------------------------------------------
444  00045d94  directory_length 4, crc 23956
448  1200a02d  specifier id: 1394 TA
44c  13010000  version
450  1701200f  model
454  8100000c  --> descriptor leaf at 484

               descriptor leaf at 458
               -----------------------------------------------------------------
458  000a199d  leaf_length 10, crc 6557
45c  00000000  textual descriptor
460  00000000  minimal ASCII
464  4d61636b  "Mack"
468  69650000  "ie"
46c  00000000
470  00000000
474  00000000
478  00000000
47c  00000000
480  00000000

               descriptor leaf at 484
               -----------------------------------------------------------------
484  000a0964  leaf_length 10, crc 2404
488  00000000  textual descriptor
48c  00000000  minimal ASCII
490  4f6e7978  "Onyx"
494  20313230  " 120"
498  30460000  "0F"
49c  00000000
4a0  00000000
4a4  00000000
4a8  00000000
4ac  00000000

Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
Link: https://lore.kernel.org/r/20211111103015.7498-1-o-takashi@sakamocchi.jp
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2021-11-11 14:02:02 +01:00

378 lines
8.8 KiB
C

// SPDX-License-Identifier: GPL-2.0-only
/*
* fireworks_stream.c - a part of driver for Fireworks based devices
*
* Copyright (c) 2013-2014 Takashi Sakamoto
*/
#include "./fireworks.h"
#define READY_TIMEOUT_MS 1000
static int init_stream(struct snd_efw *efw, struct amdtp_stream *stream)
{
struct cmp_connection *conn;
enum cmp_direction c_dir;
enum amdtp_stream_direction s_dir;
int err;
if (stream == &efw->tx_stream) {
conn = &efw->out_conn;
c_dir = CMP_OUTPUT;
s_dir = AMDTP_IN_STREAM;
} else {
conn = &efw->in_conn;
c_dir = CMP_INPUT;
s_dir = AMDTP_OUT_STREAM;
}
err = cmp_connection_init(conn, efw->unit, c_dir, 0);
if (err < 0)
return err;
err = amdtp_am824_init(stream, efw->unit, s_dir, CIP_BLOCKING | CIP_UNAWARE_SYT);
if (err < 0) {
amdtp_stream_destroy(stream);
cmp_connection_destroy(conn);
return err;
}
if (stream == &efw->tx_stream) {
// Fireworks transmits NODATA packets with TAG0.
efw->tx_stream.flags |= CIP_EMPTY_WITH_TAG0;
// Fireworks has its own meaning for dbc.
efw->tx_stream.flags |= CIP_DBC_IS_END_EVENT;
// Fireworks reset dbc at bus reset.
efw->tx_stream.flags |= CIP_SKIP_DBC_ZERO_CHECK;
// But Recent firmwares starts packets with non-zero dbc.
// Driver version 5.7.6 installs firmware version 5.7.3.
if (efw->is_fireworks3 &&
(efw->firmware_version == 0x5070000 ||
efw->firmware_version == 0x5070300 ||
efw->firmware_version == 0x5080000))
efw->tx_stream.flags |= CIP_UNALIGHED_DBC;
// AudioFire9 always reports wrong dbs. Onyx 1200F with the latest firmware (v4.6.0)
// also report wrong dbs at 88.2 kHz or greater.
if (efw->is_af9 || efw->firmware_version == 0x4060000)
efw->tx_stream.flags |= CIP_WRONG_DBS;
// Firmware version 5.5 reports fixed interval for dbc.
if (efw->firmware_version == 0x5050000)
efw->tx_stream.ctx_data.tx.dbc_interval = 8;
}
return err;
}
static int start_stream(struct snd_efw *efw, struct amdtp_stream *stream,
unsigned int rate)
{
struct cmp_connection *conn;
int err;
if (stream == &efw->tx_stream)
conn = &efw->out_conn;
else
conn = &efw->in_conn;
// Establish connection via CMP.
err = cmp_connection_establish(conn);
if (err < 0)
return err;
// Start amdtp stream.
err = amdtp_domain_add_stream(&efw->domain, stream,
conn->resources.channel, conn->speed);
if (err < 0) {
cmp_connection_break(conn);
return err;
}
return 0;
}
// This function should be called before starting the stream or after stopping
// the streams.
static void destroy_stream(struct snd_efw *efw, struct amdtp_stream *stream)
{
amdtp_stream_destroy(stream);
if (stream == &efw->tx_stream)
cmp_connection_destroy(&efw->out_conn);
else
cmp_connection_destroy(&efw->in_conn);
}
static int
check_connection_used_by_others(struct snd_efw *efw, struct amdtp_stream *s)
{
struct cmp_connection *conn;
bool used;
int err;
if (s == &efw->tx_stream)
conn = &efw->out_conn;
else
conn = &efw->in_conn;
err = cmp_connection_check_used(conn, &used);
if ((err >= 0) && used && !amdtp_stream_running(s)) {
dev_err(&efw->unit->device,
"Connection established by others: %cPCR[%d]\n",
(conn->direction == CMP_OUTPUT) ? 'o' : 'i',
conn->pcr_index);
err = -EBUSY;
}
return err;
}
int snd_efw_stream_init_duplex(struct snd_efw *efw)
{
int err;
err = init_stream(efw, &efw->tx_stream);
if (err < 0)
return err;
err = init_stream(efw, &efw->rx_stream);
if (err < 0) {
destroy_stream(efw, &efw->tx_stream);
return err;
}
err = amdtp_domain_init(&efw->domain);
if (err < 0) {
destroy_stream(efw, &efw->tx_stream);
destroy_stream(efw, &efw->rx_stream);
return err;
}
// set IEC61883 compliant mode (actually not fully compliant...).
err = snd_efw_command_set_tx_mode(efw, SND_EFW_TRANSPORT_MODE_IEC61883);
if (err < 0) {
destroy_stream(efw, &efw->tx_stream);
destroy_stream(efw, &efw->rx_stream);
}
return err;
}
static int keep_resources(struct snd_efw *efw, struct amdtp_stream *stream,
unsigned int rate, unsigned int mode)
{
unsigned int pcm_channels;
unsigned int midi_ports;
struct cmp_connection *conn;
int err;
if (stream == &efw->tx_stream) {
pcm_channels = efw->pcm_capture_channels[mode];
midi_ports = efw->midi_out_ports;
conn = &efw->out_conn;
} else {
pcm_channels = efw->pcm_playback_channels[mode];
midi_ports = efw->midi_in_ports;
conn = &efw->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_efw_stream_reserve_duplex(struct snd_efw *efw, 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(efw, &efw->rx_stream);
if (err < 0)
return err;
// stop streams if rate is different.
err = snd_efw_command_get_sampling_rate(efw, &curr_rate);
if (err < 0)
return err;
if (rate == 0)
rate = curr_rate;
if (rate != curr_rate) {
amdtp_domain_stop(&efw->domain);
cmp_connection_break(&efw->out_conn);
cmp_connection_break(&efw->in_conn);
cmp_connection_release(&efw->out_conn);
cmp_connection_release(&efw->in_conn);
}
if (efw->substreams_counter == 0 || rate != curr_rate) {
unsigned int mode;
err = snd_efw_command_set_sampling_rate(efw, rate);
if (err < 0)
return err;
err = snd_efw_get_multiplier_mode(rate, &mode);
if (err < 0)
return err;
err = keep_resources(efw, &efw->tx_stream, rate, mode);
if (err < 0)
return err;
err = keep_resources(efw, &efw->rx_stream, rate, mode);
if (err < 0) {
cmp_connection_release(&efw->in_conn);
return err;
}
err = amdtp_domain_set_events_per_period(&efw->domain,
frames_per_period, frames_per_buffer);
if (err < 0) {
cmp_connection_release(&efw->in_conn);
cmp_connection_release(&efw->out_conn);
return err;
}
}
return 0;
}
int snd_efw_stream_start_duplex(struct snd_efw *efw)
{
unsigned int rate;
int err = 0;
// Need no substreams.
if (efw->substreams_counter == 0)
return -EIO;
if (amdtp_streaming_error(&efw->rx_stream) ||
amdtp_streaming_error(&efw->tx_stream)) {
amdtp_domain_stop(&efw->domain);
cmp_connection_break(&efw->out_conn);
cmp_connection_break(&efw->in_conn);
}
err = snd_efw_command_get_sampling_rate(efw, &rate);
if (err < 0)
return err;
if (!amdtp_stream_running(&efw->rx_stream)) {
unsigned int tx_init_skip_cycles;
// Audiofire 2/4 skip an isochronous cycle several thousands after starting
// packet transmission.
if (efw->is_fireworks3 && !efw->is_af9)
tx_init_skip_cycles = 6000;
else
tx_init_skip_cycles = 0;
err = start_stream(efw, &efw->rx_stream, rate);
if (err < 0)
goto error;
err = start_stream(efw, &efw->tx_stream, rate);
if (err < 0)
goto error;
// NOTE: The device ignores presentation time expressed by the value of syt field
// of CIP header in received packets. The sequence of the number of data blocks per
// packet is important for media clock recovery.
err = amdtp_domain_start(&efw->domain, tx_init_skip_cycles, true, false);
if (err < 0)
goto error;
if (!amdtp_domain_wait_ready(&efw->domain, READY_TIMEOUT_MS)) {
err = -ETIMEDOUT;
goto error;
}
}
return 0;
error:
amdtp_domain_stop(&efw->domain);
cmp_connection_break(&efw->out_conn);
cmp_connection_break(&efw->in_conn);
return err;
}
void snd_efw_stream_stop_duplex(struct snd_efw *efw)
{
if (efw->substreams_counter == 0) {
amdtp_domain_stop(&efw->domain);
cmp_connection_break(&efw->out_conn);
cmp_connection_break(&efw->in_conn);
cmp_connection_release(&efw->out_conn);
cmp_connection_release(&efw->in_conn);
}
}
void snd_efw_stream_update_duplex(struct snd_efw *efw)
{
amdtp_domain_stop(&efw->domain);
cmp_connection_break(&efw->out_conn);
cmp_connection_break(&efw->in_conn);
amdtp_stream_pcm_abort(&efw->rx_stream);
amdtp_stream_pcm_abort(&efw->tx_stream);
}
void snd_efw_stream_destroy_duplex(struct snd_efw *efw)
{
amdtp_domain_destroy(&efw->domain);
destroy_stream(efw, &efw->rx_stream);
destroy_stream(efw, &efw->tx_stream);
}
void snd_efw_stream_lock_changed(struct snd_efw *efw)
{
efw->dev_lock_changed = true;
wake_up(&efw->hwdep_wait);
}
int snd_efw_stream_lock_try(struct snd_efw *efw)
{
int err;
spin_lock_irq(&efw->lock);
/* user land lock this */
if (efw->dev_lock_count < 0) {
err = -EBUSY;
goto end;
}
/* this is the first time */
if (efw->dev_lock_count++ == 0)
snd_efw_stream_lock_changed(efw);
err = 0;
end:
spin_unlock_irq(&efw->lock);
return err;
}
void snd_efw_stream_lock_release(struct snd_efw *efw)
{
spin_lock_irq(&efw->lock);
if (WARN_ON(efw->dev_lock_count <= 0))
goto end;
if (--efw->dev_lock_count == 0)
snd_efw_stream_lock_changed(efw);
end:
spin_unlock_irq(&efw->lock);
}