linux/sound/firewire/fireworks/fireworks_stream.c
Takashi Sakamoto 697022391e ALSA: fireworks/firewire-lib: Add a quirk for wrong dbs in tx packets
One of Fireworks firmware, named  as 'AudioFire9', seems to transmit
packets with wrong value of dbs. It's always 0x11 but actual size of
data block is different.

This commit adds a flag for this quirk and some codes to calculate
correct size.

Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2014-05-26 14:25:00 +02:00

357 lines
7.9 KiB
C

/*
* fireworks_stream.c - a part of driver for Fireworks based devices
*
* Copyright (c) 2013-2014 Takashi Sakamoto
*
* Licensed under the terms of the GNU General Public License, version 2.
*/
#include "./fireworks.h"
#define CALLBACK_TIMEOUT 100
static unsigned int freq_table[] = {
/* multiplier mode 0 */
[0] = 32000,
[1] = 44100,
[2] = 48000,
/* multiplier mode 1 */
[3] = 88200,
[4] = 96000,
/* multiplier mode 2 */
[5] = 176400,
[6] = 192000,
};
static inline unsigned int
get_multiplier_mode_with_index(unsigned int index)
{
return ((int)index - 1) / 2;
}
int snd_efw_get_multiplier_mode(unsigned int sampling_rate, unsigned int *mode)
{
unsigned int i;
for (i = 0; i < ARRAY_SIZE(freq_table); i++) {
if (freq_table[i] == sampling_rate) {
*mode = get_multiplier_mode_with_index(i);
return 0;
}
}
return -EINVAL;
}
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)
goto end;
err = amdtp_stream_init(stream, efw->unit, s_dir, CIP_BLOCKING);
if (err < 0) {
amdtp_stream_destroy(stream);
cmp_connection_destroy(conn);
}
end:
return err;
}
static void
stop_stream(struct snd_efw *efw, struct amdtp_stream *stream)
{
amdtp_stream_pcm_abort(stream);
amdtp_stream_stop(stream);
if (stream == &efw->tx_stream)
cmp_connection_break(&efw->out_conn);
else
cmp_connection_break(&efw->in_conn);
}
static int
start_stream(struct snd_efw *efw, struct amdtp_stream *stream,
unsigned int sampling_rate)
{
struct cmp_connection *conn;
unsigned int mode, pcm_channels, midi_ports;
int err;
err = snd_efw_get_multiplier_mode(sampling_rate, &mode);
if (err < 0)
goto end;
if (stream == &efw->tx_stream) {
conn = &efw->out_conn;
pcm_channels = efw->pcm_capture_channels[mode];
midi_ports = efw->midi_out_ports;
} else {
conn = &efw->in_conn;
pcm_channels = efw->pcm_playback_channels[mode];
midi_ports = efw->midi_in_ports;
}
amdtp_stream_set_parameters(stream, sampling_rate,
pcm_channels, midi_ports);
/* establish connection via CMP */
err = cmp_connection_establish(conn,
amdtp_stream_get_max_payload(stream));
if (err < 0)
goto end;
/* start amdtp stream */
err = amdtp_stream_start(stream,
conn->resources.channel,
conn->speed);
if (err < 0) {
stop_stream(efw, stream);
goto end;
}
/* wait first callback */
if (!amdtp_stream_wait_callback(stream, CALLBACK_TIMEOUT)) {
stop_stream(efw, stream);
err = -ETIMEDOUT;
}
end:
return err;
}
static void
destroy_stream(struct snd_efw *efw, struct amdtp_stream *stream)
{
stop_stream(efw, 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
get_sync_mode(struct snd_efw *efw, enum cip_flags *sync_mode)
{
enum snd_efw_clock_source clock_source;
int err;
err = snd_efw_command_get_clock_source(efw, &clock_source);
if (err < 0)
return err;
if (clock_source == SND_EFW_CLOCK_SOURCE_SYTMATCH)
return -ENOSYS;
*sync_mode = CIP_SYNC_TO_DEVICE;
return 0;
}
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)
goto end;
/* 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;
/* AudioFire9 always reports wrong dbs. */
if (efw->is_af9)
efw->tx_stream.flags |= CIP_WRONG_DBS;
err = init_stream(efw, &efw->rx_stream);
if (err < 0) {
destroy_stream(efw, &efw->tx_stream);
goto end;
}
/* 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);
}
end:
return err;
}
int snd_efw_stream_start_duplex(struct snd_efw *efw, int rate)
{
struct amdtp_stream *master, *slave;
atomic_t *slave_substreams;
enum cip_flags sync_mode;
unsigned int curr_rate;
int err = 0;
mutex_lock(&efw->mutex);
/* Need no substreams */
if ((atomic_read(&efw->playback_substreams) == 0) &&
(atomic_read(&efw->capture_substreams) == 0))
goto end;
err = get_sync_mode(efw, &sync_mode);
if (err < 0)
goto end;
if (sync_mode == CIP_SYNC_TO_DEVICE) {
master = &efw->tx_stream;
slave = &efw->rx_stream;
slave_substreams = &efw->playback_substreams;
} else {
master = &efw->rx_stream;
slave = &efw->tx_stream;
slave_substreams = &efw->capture_substreams;
}
/*
* Considering JACK/FFADO streaming:
* TODO: This can be removed hwdep functionality becomes popular.
*/
err = check_connection_used_by_others(efw, master);
if (err < 0)
goto end;
/* packet queueing error */
if (amdtp_streaming_error(slave))
stop_stream(efw, slave);
if (amdtp_streaming_error(master))
stop_stream(efw, master);
/* stop streams if rate is different */
err = snd_efw_command_get_sampling_rate(efw, &curr_rate);
if (err < 0)
goto end;
if (rate == 0)
rate = curr_rate;
if (rate != curr_rate) {
stop_stream(efw, slave);
stop_stream(efw, master);
}
/* master should be always running */
if (!amdtp_stream_running(master)) {
amdtp_stream_set_sync(sync_mode, master, slave);
efw->master = master;
err = snd_efw_command_set_sampling_rate(efw, rate);
if (err < 0)
goto end;
err = start_stream(efw, master, rate);
if (err < 0) {
dev_err(&efw->unit->device,
"fail to start AMDTP master stream:%d\n", err);
goto end;
}
}
/* start slave if needed */
if (atomic_read(slave_substreams) > 0 && !amdtp_stream_running(slave)) {
err = start_stream(efw, slave, rate);
if (err < 0) {
dev_err(&efw->unit->device,
"fail to start AMDTP slave stream:%d\n", err);
stop_stream(efw, master);
}
}
end:
mutex_unlock(&efw->mutex);
return err;
}
void snd_efw_stream_stop_duplex(struct snd_efw *efw)
{
struct amdtp_stream *master, *slave;
atomic_t *master_substreams, *slave_substreams;
mutex_lock(&efw->mutex);
if (efw->master == &efw->rx_stream) {
slave = &efw->tx_stream;
master = &efw->rx_stream;
slave_substreams = &efw->capture_substreams;
master_substreams = &efw->playback_substreams;
} else {
slave = &efw->rx_stream;
master = &efw->tx_stream;
slave_substreams = &efw->playback_substreams;
master_substreams = &efw->capture_substreams;
}
if (atomic_read(slave_substreams) == 0) {
stop_stream(efw, slave);
if (atomic_read(master_substreams) == 0)
stop_stream(efw, master);
}
mutex_unlock(&efw->mutex);
}
void snd_efw_stream_update_duplex(struct snd_efw *efw)
{
if ((cmp_connection_update(&efw->out_conn) < 0) ||
(cmp_connection_update(&efw->in_conn) < 0)) {
mutex_lock(&efw->mutex);
stop_stream(efw, &efw->rx_stream);
stop_stream(efw, &efw->tx_stream);
mutex_unlock(&efw->mutex);
} else {
amdtp_stream_update(&efw->rx_stream);
amdtp_stream_update(&efw->tx_stream);
}
}
void snd_efw_stream_destroy_duplex(struct snd_efw *efw)
{
mutex_lock(&efw->mutex);
destroy_stream(efw, &efw->rx_stream);
destroy_stream(efw, &efw->tx_stream);
mutex_unlock(&efw->mutex);
}