2014-11-28 18:59:14 +03:00
|
|
|
/*
|
|
|
|
* dice_stream.c - a part of driver for DICE based devices
|
|
|
|
*
|
|
|
|
* Copyright (c) Clemens Ladisch <clemens@ladisch.de>
|
|
|
|
* Copyright (c) 2014 Takashi Sakamoto <o-takashi@sakamocchi.jp>
|
|
|
|
*
|
|
|
|
* Licensed under the terms of the GNU General Public License, version 2.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "dice.h"
|
|
|
|
|
2014-12-08 18:10:35 +03:00
|
|
|
#define CALLBACK_TIMEOUT 200
|
2016-02-08 16:54:20 +03:00
|
|
|
#define NOTIFICATION_TIMEOUT_MS (2 * MSEC_PER_SEC)
|
2014-12-08 18:10:35 +03:00
|
|
|
|
2016-03-10 15:44:28 +03:00
|
|
|
struct reg_params {
|
|
|
|
unsigned int count;
|
|
|
|
unsigned int size;
|
|
|
|
};
|
|
|
|
|
2014-11-28 18:59:14 +03:00
|
|
|
const unsigned int snd_dice_rates[SND_DICE_RATES_COUNT] = {
|
|
|
|
/* mode 0 */
|
|
|
|
[0] = 32000,
|
|
|
|
[1] = 44100,
|
|
|
|
[2] = 48000,
|
|
|
|
/* mode 1 */
|
|
|
|
[3] = 88200,
|
|
|
|
[4] = 96000,
|
|
|
|
/* mode 2 */
|
|
|
|
[5] = 176400,
|
|
|
|
[6] = 192000,
|
|
|
|
};
|
|
|
|
|
2016-02-08 16:54:20 +03:00
|
|
|
/*
|
|
|
|
* This operation has an effect to synchronize GLOBAL_STATUS/GLOBAL_SAMPLE_RATE
|
|
|
|
* to GLOBAL_STATUS. Especially, just after powering on, these are different.
|
|
|
|
*/
|
|
|
|
static int ensure_phase_lock(struct snd_dice *dice)
|
|
|
|
{
|
2016-02-11 14:18:38 +03:00
|
|
|
__be32 reg, nominal;
|
2016-02-08 16:54:20 +03:00
|
|
|
int err;
|
|
|
|
|
|
|
|
err = snd_dice_transaction_read_global(dice, GLOBAL_CLOCK_SELECT,
|
|
|
|
®, sizeof(reg));
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
|
|
|
|
if (completion_done(&dice->clock_accepted))
|
|
|
|
reinit_completion(&dice->clock_accepted);
|
|
|
|
|
|
|
|
err = snd_dice_transaction_write_global(dice, GLOBAL_CLOCK_SELECT,
|
|
|
|
®, sizeof(reg));
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
|
|
|
|
if (wait_for_completion_timeout(&dice->clock_accepted,
|
2016-02-11 14:18:38 +03:00
|
|
|
msecs_to_jiffies(NOTIFICATION_TIMEOUT_MS)) == 0) {
|
|
|
|
/*
|
|
|
|
* Old versions of Dice firmware transfer no notification when
|
|
|
|
* the same clock status as current one is set. In this case,
|
|
|
|
* just check current clock status.
|
|
|
|
*/
|
|
|
|
err = snd_dice_transaction_read_global(dice, GLOBAL_STATUS,
|
|
|
|
&nominal, sizeof(nominal));
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
if (!(be32_to_cpu(nominal) & STATUS_SOURCE_LOCKED))
|
|
|
|
return -ETIMEDOUT;
|
|
|
|
}
|
2016-02-08 16:54:20 +03:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2016-03-10 15:44:28 +03:00
|
|
|
static int get_register_params(struct snd_dice *dice,
|
|
|
|
struct reg_params *tx_params,
|
|
|
|
struct reg_params *rx_params)
|
2014-11-28 18:59:14 +03:00
|
|
|
{
|
2016-03-07 16:35:43 +03:00
|
|
|
__be32 reg[2];
|
2014-11-28 18:59:14 +03:00
|
|
|
int err;
|
|
|
|
|
2016-03-07 16:35:43 +03:00
|
|
|
err = snd_dice_transaction_read_tx(dice, TX_NUMBER, reg, sizeof(reg));
|
2014-11-28 18:59:14 +03:00
|
|
|
if (err < 0)
|
2016-03-07 16:35:43 +03:00
|
|
|
return err;
|
2016-03-10 15:44:28 +03:00
|
|
|
tx_params->count =
|
|
|
|
min_t(unsigned int, be32_to_cpu(reg[0]), MAX_STREAMS);
|
|
|
|
tx_params->size = be32_to_cpu(reg[1]) * 4;
|
2014-11-28 18:59:14 +03:00
|
|
|
|
2016-03-07 16:35:43 +03:00
|
|
|
err = snd_dice_transaction_read_rx(dice, RX_NUMBER, reg, sizeof(reg));
|
2014-12-08 18:10:35 +03:00
|
|
|
if (err < 0)
|
2016-03-07 16:35:43 +03:00
|
|
|
return err;
|
2016-03-10 15:44:28 +03:00
|
|
|
rx_params->count =
|
|
|
|
min_t(unsigned int, be32_to_cpu(reg[0]), MAX_STREAMS);
|
|
|
|
rx_params->size = be32_to_cpu(reg[1]) * 4;
|
2016-03-07 16:35:43 +03:00
|
|
|
|
|
|
|
return 0;
|
2014-11-28 18:59:14 +03:00
|
|
|
}
|
|
|
|
|
2016-03-07 16:35:43 +03:00
|
|
|
static void release_resources(struct snd_dice *dice)
|
2014-11-28 18:59:14 +03:00
|
|
|
{
|
2016-03-07 16:35:43 +03:00
|
|
|
unsigned int i;
|
|
|
|
|
|
|
|
for (i = 0; i < MAX_STREAMS; i++) {
|
|
|
|
if (amdtp_stream_running(&dice->tx_stream[i])) {
|
|
|
|
amdtp_stream_pcm_abort(&dice->tx_stream[i]);
|
|
|
|
amdtp_stream_stop(&dice->tx_stream[i]);
|
|
|
|
}
|
|
|
|
if (amdtp_stream_running(&dice->rx_stream[i])) {
|
|
|
|
amdtp_stream_pcm_abort(&dice->rx_stream[i]);
|
|
|
|
amdtp_stream_stop(&dice->rx_stream[i]);
|
|
|
|
}
|
2014-11-28 18:59:15 +03:00
|
|
|
|
2016-03-07 16:35:43 +03:00
|
|
|
fw_iso_resources_free(&dice->tx_resources[i]);
|
|
|
|
fw_iso_resources_free(&dice->rx_resources[i]);
|
|
|
|
}
|
2014-11-28 18:59:14 +03:00
|
|
|
}
|
|
|
|
|
2016-03-07 16:35:43 +03:00
|
|
|
static void stop_streams(struct snd_dice *dice, enum amdtp_stream_direction dir,
|
2016-03-10 15:44:28 +03:00
|
|
|
struct reg_params *params)
|
2014-11-28 18:59:14 +03:00
|
|
|
{
|
2016-03-07 16:35:43 +03:00
|
|
|
__be32 reg;
|
|
|
|
unsigned int i;
|
|
|
|
|
2016-03-10 15:44:28 +03:00
|
|
|
for (i = 0; i < params->count; i++) {
|
2016-03-07 16:35:43 +03:00
|
|
|
reg = cpu_to_be32((u32)-1);
|
|
|
|
if (dir == AMDTP_IN_STREAM) {
|
|
|
|
snd_dice_transaction_write_tx(dice,
|
2016-03-10 15:44:28 +03:00
|
|
|
params->size * i + TX_ISOCHRONOUS,
|
|
|
|
®, sizeof(reg));
|
2016-03-07 16:35:43 +03:00
|
|
|
} else {
|
|
|
|
snd_dice_transaction_write_rx(dice,
|
2016-03-10 15:44:28 +03:00
|
|
|
params->size * i + RX_ISOCHRONOUS,
|
|
|
|
®, sizeof(reg));
|
2016-03-07 16:35:43 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static int keep_resources(struct snd_dice *dice,
|
|
|
|
enum amdtp_stream_direction dir, unsigned int index,
|
|
|
|
unsigned int rate, unsigned int pcm_chs,
|
|
|
|
unsigned int midi_ports)
|
|
|
|
{
|
|
|
|
struct amdtp_stream *stream;
|
2014-12-08 18:10:36 +03:00
|
|
|
struct fw_iso_resources *resources;
|
ALSA: firewire-lib: add an argument for Dice's dual wire mode
In IEC 61883-6, one data block represents one event. In ALSA, the event is
one PCM frame. Therefore, when processing one data block, current
implementation counts one PCM frame.
On the other hand, Dice platform has a quirk called as 'dual wire' at
higher sampling rate. In detail, see comment of commit 6eb6c81eee2a
("ALSA: dice: Split stream functionality into a file").
Currently, to handle this quirk, AMDTP stream structure has a
'double_pcm_frames' member. When this is enabled, two PCM frames are
counted. Each driver set this flag by accessing the structure member
directly.
In future commit, some members related to AM824 data block will be moved
to specific structure, to separate packet streaming layer and data block
processing layer. The access will be limited by opaque pointer.
For this reason, this commit adds an argument into
amdtp_stream_set_parameter() to set the flag.
Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2015-09-19 05:21:50 +03:00
|
|
|
bool double_pcm_frames;
|
2016-03-07 16:35:43 +03:00
|
|
|
unsigned int i;
|
2014-12-08 18:10:35 +03:00
|
|
|
int err;
|
2014-11-28 18:59:14 +03:00
|
|
|
|
2016-03-07 16:35:43 +03:00
|
|
|
if (dir == AMDTP_IN_STREAM) {
|
|
|
|
stream = &dice->tx_stream[index];
|
|
|
|
resources = &dice->tx_resources[index];
|
2014-12-08 18:10:36 +03:00
|
|
|
} else {
|
2016-03-07 16:35:43 +03:00
|
|
|
stream = &dice->rx_stream[index];
|
|
|
|
resources = &dice->rx_resources[index];
|
2014-12-08 18:10:36 +03:00
|
|
|
}
|
2014-11-28 18:59:14 +03:00
|
|
|
|
2014-12-08 18:10:35 +03:00
|
|
|
/*
|
|
|
|
* At 176.4/192.0 kHz, Dice has a quirk to transfer two PCM frames in
|
|
|
|
* one data block of AMDTP packet. Thus sampling transfer frequency is
|
|
|
|
* a half of PCM sampling frequency, i.e. PCM frames at 192.0 kHz are
|
|
|
|
* transferred on AMDTP packets at 96 kHz. Two successive samples of a
|
|
|
|
* channel are stored consecutively in the packet. This quirk is called
|
|
|
|
* as 'Dual Wire'.
|
|
|
|
* For this quirk, blocking mode is required and PCM buffer size should
|
|
|
|
* be aligned to SYT_INTERVAL.
|
|
|
|
*/
|
2016-02-08 16:54:19 +03:00
|
|
|
double_pcm_frames = rate > 96000;
|
ALSA: firewire-lib: add an argument for Dice's dual wire mode
In IEC 61883-6, one data block represents one event. In ALSA, the event is
one PCM frame. Therefore, when processing one data block, current
implementation counts one PCM frame.
On the other hand, Dice platform has a quirk called as 'dual wire' at
higher sampling rate. In detail, see comment of commit 6eb6c81eee2a
("ALSA: dice: Split stream functionality into a file").
Currently, to handle this quirk, AMDTP stream structure has a
'double_pcm_frames' member. When this is enabled, two PCM frames are
counted. Each driver set this flag by accessing the structure member
directly.
In future commit, some members related to AM824 data block will be moved
to specific structure, to separate packet streaming layer and data block
processing layer. The access will be limited by opaque pointer.
For this reason, this commit adds an argument into
amdtp_stream_set_parameter() to set the flag.
Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2015-09-19 05:21:50 +03:00
|
|
|
if (double_pcm_frames) {
|
2014-12-08 18:10:35 +03:00
|
|
|
rate /= 2;
|
|
|
|
pcm_chs *= 2;
|
|
|
|
}
|
2014-11-28 18:59:14 +03:00
|
|
|
|
2015-09-19 05:21:56 +03:00
|
|
|
err = amdtp_am824_set_parameters(stream, rate, pcm_chs, midi_ports,
|
|
|
|
double_pcm_frames);
|
2015-09-19 05:21:49 +03:00
|
|
|
if (err < 0)
|
2016-03-07 16:35:43 +03:00
|
|
|
return err;
|
2015-09-19 05:21:49 +03:00
|
|
|
|
ALSA: firewire-lib: add an argument for Dice's dual wire mode
In IEC 61883-6, one data block represents one event. In ALSA, the event is
one PCM frame. Therefore, when processing one data block, current
implementation counts one PCM frame.
On the other hand, Dice platform has a quirk called as 'dual wire' at
higher sampling rate. In detail, see comment of commit 6eb6c81eee2a
("ALSA: dice: Split stream functionality into a file").
Currently, to handle this quirk, AMDTP stream structure has a
'double_pcm_frames' member. When this is enabled, two PCM frames are
counted. Each driver set this flag by accessing the structure member
directly.
In future commit, some members related to AM824 data block will be moved
to specific structure, to separate packet streaming layer and data block
processing layer. The access will be limited by opaque pointer.
For this reason, this commit adds an argument into
amdtp_stream_set_parameter() to set the flag.
Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
2015-09-19 05:21:50 +03:00
|
|
|
if (double_pcm_frames) {
|
2014-12-08 18:10:35 +03:00
|
|
|
pcm_chs /= 2;
|
2014-11-28 18:59:14 +03:00
|
|
|
|
2014-12-08 18:10:35 +03:00
|
|
|
for (i = 0; i < pcm_chs; i++) {
|
2015-09-19 05:21:58 +03:00
|
|
|
amdtp_am824_set_pcm_position(stream, i, i * 2);
|
|
|
|
amdtp_am824_set_pcm_position(stream, i + pcm_chs,
|
|
|
|
i * 2 + 1);
|
2014-12-08 18:10:35 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-03-07 16:35:43 +03:00
|
|
|
return fw_iso_resources_allocate(resources,
|
|
|
|
amdtp_stream_get_max_payload(stream),
|
|
|
|
fw_parent_device(dice->unit)->max_speed);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int start_streams(struct snd_dice *dice, enum amdtp_stream_direction dir,
|
2016-03-10 15:44:28 +03:00
|
|
|
unsigned int rate, struct reg_params *params)
|
2016-03-07 16:35:43 +03:00
|
|
|
{
|
|
|
|
__be32 reg[2];
|
|
|
|
unsigned int i, pcm_chs, midi_ports;
|
|
|
|
struct amdtp_stream *streams;
|
|
|
|
struct fw_iso_resources *resources;
|
2017-01-03 06:44:43 +03:00
|
|
|
struct fw_device *fw_dev = fw_parent_device(dice->unit);
|
2016-03-07 16:35:43 +03:00
|
|
|
int err = 0;
|
|
|
|
|
|
|
|
if (dir == AMDTP_IN_STREAM) {
|
|
|
|
streams = dice->tx_stream;
|
|
|
|
resources = dice->tx_resources;
|
|
|
|
} else {
|
|
|
|
streams = dice->rx_stream;
|
|
|
|
resources = dice->rx_resources;
|
|
|
|
}
|
|
|
|
|
2016-03-10 15:44:28 +03:00
|
|
|
for (i = 0; i < params->count; i++) {
|
2016-03-07 16:35:43 +03:00
|
|
|
if (dir == AMDTP_IN_STREAM) {
|
|
|
|
err = snd_dice_transaction_read_tx(dice,
|
2016-03-10 15:44:28 +03:00
|
|
|
params->size * i + TX_NUMBER_AUDIO,
|
|
|
|
reg, sizeof(reg));
|
2016-03-07 16:35:43 +03:00
|
|
|
} else {
|
|
|
|
err = snd_dice_transaction_read_rx(dice,
|
2016-03-10 15:44:28 +03:00
|
|
|
params->size * i + RX_NUMBER_AUDIO,
|
|
|
|
reg, sizeof(reg));
|
2016-03-07 16:35:43 +03:00
|
|
|
}
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
pcm_chs = be32_to_cpu(reg[0]);
|
|
|
|
midi_ports = be32_to_cpu(reg[1]);
|
|
|
|
|
|
|
|
err = keep_resources(dice, dir, i, rate, pcm_chs, midi_ports);
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
|
|
|
|
reg[0] = cpu_to_be32(resources[i].channel);
|
|
|
|
if (dir == AMDTP_IN_STREAM) {
|
|
|
|
err = snd_dice_transaction_write_tx(dice,
|
2016-03-10 15:44:28 +03:00
|
|
|
params->size * i + TX_ISOCHRONOUS,
|
|
|
|
reg, sizeof(reg[0]));
|
2016-03-07 16:35:43 +03:00
|
|
|
} else {
|
|
|
|
err = snd_dice_transaction_write_rx(dice,
|
2016-03-10 15:44:28 +03:00
|
|
|
params->size * i + RX_ISOCHRONOUS,
|
|
|
|
reg, sizeof(reg[0]));
|
2016-03-07 16:35:43 +03:00
|
|
|
}
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
|
2017-01-03 06:44:43 +03:00
|
|
|
if (dir == AMDTP_IN_STREAM) {
|
|
|
|
reg[0] = cpu_to_be32(fw_dev->max_speed);
|
|
|
|
err = snd_dice_transaction_write_tx(dice,
|
|
|
|
params->size * i + TX_SPEED,
|
|
|
|
reg, sizeof(reg[0]));
|
|
|
|
if (err < 0)
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2016-03-07 16:35:43 +03:00
|
|
|
err = amdtp_stream_start(&streams[i], resources[i].channel,
|
2017-01-03 06:44:43 +03:00
|
|
|
fw_dev->max_speed);
|
2016-03-07 16:35:43 +03:00
|
|
|
if (err < 0)
|
|
|
|
return err;
|
2014-12-08 18:10:35 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2016-03-07 16:35:43 +03:00
|
|
|
/*
|
|
|
|
* MEMO: After this function, there're two states of streams:
|
|
|
|
* - None streams are running.
|
|
|
|
* - All streams are running.
|
|
|
|
*/
|
2014-12-08 18:10:36 +03:00
|
|
|
int snd_dice_stream_start_duplex(struct snd_dice *dice, unsigned int rate)
|
2014-12-08 18:10:35 +03:00
|
|
|
{
|
|
|
|
unsigned int curr_rate;
|
2016-03-07 16:35:43 +03:00
|
|
|
unsigned int i;
|
2016-03-10 15:44:28 +03:00
|
|
|
struct reg_params tx_params, rx_params;
|
2016-03-07 16:35:43 +03:00
|
|
|
bool need_to_start;
|
|
|
|
int err;
|
2014-12-08 18:10:36 +03:00
|
|
|
|
|
|
|
if (dice->substreams_counter == 0)
|
2016-03-07 16:35:43 +03:00
|
|
|
return -EIO;
|
2014-12-08 18:10:35 +03:00
|
|
|
|
2016-03-10 15:44:28 +03:00
|
|
|
err = get_register_params(dice, &tx_params, &rx_params);
|
2016-03-07 16:35:43 +03:00
|
|
|
if (err < 0)
|
|
|
|
return err;
|
2014-12-08 18:10:35 +03:00
|
|
|
|
|
|
|
err = snd_dice_transaction_get_rate(dice, &curr_rate);
|
|
|
|
if (err < 0) {
|
|
|
|
dev_err(&dice->unit->device,
|
|
|
|
"fail to get sampling rate\n");
|
2016-03-07 16:35:43 +03:00
|
|
|
return err;
|
2014-12-08 18:10:35 +03:00
|
|
|
}
|
2014-12-08 18:10:39 +03:00
|
|
|
if (rate == 0)
|
|
|
|
rate = curr_rate;
|
2016-03-07 16:35:43 +03:00
|
|
|
if (rate != curr_rate)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
/* Judge to need to restart streams. */
|
|
|
|
for (i = 0; i < MAX_STREAMS; i++) {
|
2016-03-10 15:44:28 +03:00
|
|
|
if (i < tx_params.count) {
|
2016-03-07 16:35:43 +03:00
|
|
|
if (amdtp_streaming_error(&dice->tx_stream[i]) ||
|
|
|
|
!amdtp_stream_running(&dice->tx_stream[i]))
|
|
|
|
break;
|
|
|
|
}
|
2016-03-10 15:44:28 +03:00
|
|
|
if (i < rx_params.count) {
|
2016-03-07 16:35:43 +03:00
|
|
|
if (amdtp_streaming_error(&dice->rx_stream[i]) ||
|
|
|
|
!amdtp_stream_running(&dice->rx_stream[i]))
|
|
|
|
break;
|
|
|
|
}
|
2016-02-08 16:54:16 +03:00
|
|
|
}
|
2016-03-07 16:35:43 +03:00
|
|
|
need_to_start = (i < MAX_STREAMS);
|
2014-12-08 18:10:35 +03:00
|
|
|
|
2016-03-07 16:35:43 +03:00
|
|
|
if (need_to_start) {
|
|
|
|
/* Stop transmission. */
|
2014-12-08 18:10:35 +03:00
|
|
|
snd_dice_transaction_clear_enable(dice);
|
2016-03-10 15:44:28 +03:00
|
|
|
stop_streams(dice, AMDTP_IN_STREAM, &tx_params);
|
|
|
|
stop_streams(dice, AMDTP_OUT_STREAM, &rx_params);
|
2016-03-07 16:35:43 +03:00
|
|
|
release_resources(dice);
|
2014-12-08 18:10:35 +03:00
|
|
|
|
2016-02-08 16:54:20 +03:00
|
|
|
err = ensure_phase_lock(dice);
|
2014-12-08 18:10:35 +03:00
|
|
|
if (err < 0) {
|
|
|
|
dev_err(&dice->unit->device,
|
2016-02-08 16:54:20 +03:00
|
|
|
"fail to ensure phase lock\n");
|
2016-03-07 16:35:43 +03:00
|
|
|
return err;
|
2014-12-08 18:10:35 +03:00
|
|
|
}
|
|
|
|
|
2014-12-08 18:10:36 +03:00
|
|
|
/* Start both streams. */
|
2016-03-10 15:44:28 +03:00
|
|
|
err = start_streams(dice, AMDTP_IN_STREAM, rate, &tx_params);
|
2016-03-07 16:35:43 +03:00
|
|
|
if (err < 0)
|
|
|
|
goto error;
|
2016-03-10 15:44:28 +03:00
|
|
|
err = start_streams(dice, AMDTP_OUT_STREAM, rate, &rx_params);
|
2016-03-07 16:35:43 +03:00
|
|
|
if (err < 0)
|
|
|
|
goto error;
|
|
|
|
|
2014-12-08 18:10:35 +03:00
|
|
|
err = snd_dice_transaction_set_enable(dice);
|
|
|
|
if (err < 0) {
|
|
|
|
dev_err(&dice->unit->device,
|
|
|
|
"fail to enable interface\n");
|
2016-03-07 16:35:43 +03:00
|
|
|
goto error;
|
2014-12-08 18:10:35 +03:00
|
|
|
}
|
|
|
|
|
2016-03-07 16:35:43 +03:00
|
|
|
for (i = 0; i < MAX_STREAMS; i++) {
|
2016-03-10 15:44:28 +03:00
|
|
|
if ((i < tx_params.count &&
|
2016-03-07 16:35:43 +03:00
|
|
|
!amdtp_stream_wait_callback(&dice->tx_stream[i],
|
|
|
|
CALLBACK_TIMEOUT)) ||
|
2016-03-10 15:44:28 +03:00
|
|
|
(i < rx_params.count &&
|
2016-03-07 16:35:43 +03:00
|
|
|
!amdtp_stream_wait_callback(&dice->rx_stream[i],
|
|
|
|
CALLBACK_TIMEOUT))) {
|
|
|
|
err = -ETIMEDOUT;
|
|
|
|
goto error;
|
|
|
|
}
|
2014-12-08 18:10:35 +03:00
|
|
|
}
|
|
|
|
}
|
2016-03-07 16:35:43 +03:00
|
|
|
|
|
|
|
return err;
|
|
|
|
error:
|
|
|
|
snd_dice_transaction_clear_enable(dice);
|
2016-03-10 15:44:28 +03:00
|
|
|
stop_streams(dice, AMDTP_IN_STREAM, &tx_params);
|
|
|
|
stop_streams(dice, AMDTP_OUT_STREAM, &rx_params);
|
2016-03-07 16:35:43 +03:00
|
|
|
release_resources(dice);
|
2014-12-08 18:10:35 +03:00
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2016-03-07 16:35:43 +03:00
|
|
|
/*
|
|
|
|
* MEMO: After this function, there're two states of streams:
|
|
|
|
* - None streams are running.
|
|
|
|
* - All streams are running.
|
|
|
|
*/
|
2014-12-08 18:10:36 +03:00
|
|
|
void snd_dice_stream_stop_duplex(struct snd_dice *dice)
|
2014-12-08 18:10:35 +03:00
|
|
|
{
|
2016-03-10 15:44:28 +03:00
|
|
|
struct reg_params tx_params, rx_params;
|
2016-03-07 16:35:43 +03:00
|
|
|
|
2014-12-08 18:10:36 +03:00
|
|
|
if (dice->substreams_counter > 0)
|
|
|
|
return;
|
|
|
|
|
2014-12-08 18:10:35 +03:00
|
|
|
snd_dice_transaction_clear_enable(dice);
|
2014-12-08 18:10:36 +03:00
|
|
|
|
2016-03-10 15:44:28 +03:00
|
|
|
if (get_register_params(dice, &tx_params, &rx_params) == 0) {
|
|
|
|
stop_streams(dice, AMDTP_IN_STREAM, &tx_params);
|
|
|
|
stop_streams(dice, AMDTP_OUT_STREAM, &rx_params);
|
2016-03-07 16:35:43 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
release_resources(dice);
|
2014-11-28 18:59:14 +03:00
|
|
|
}
|
|
|
|
|
2016-03-07 16:35:43 +03:00
|
|
|
static int init_stream(struct snd_dice *dice, enum amdtp_stream_direction dir,
|
|
|
|
unsigned int index)
|
2014-11-28 18:59:14 +03:00
|
|
|
{
|
2016-03-07 16:35:43 +03:00
|
|
|
struct amdtp_stream *stream;
|
2014-12-08 18:10:36 +03:00
|
|
|
struct fw_iso_resources *resources;
|
2016-03-07 16:35:43 +03:00
|
|
|
int err;
|
2014-12-08 18:10:36 +03:00
|
|
|
|
2016-03-07 16:35:43 +03:00
|
|
|
if (dir == AMDTP_IN_STREAM) {
|
|
|
|
stream = &dice->tx_stream[index];
|
|
|
|
resources = &dice->tx_resources[index];
|
2014-12-08 18:10:36 +03:00
|
|
|
} else {
|
2016-03-07 16:35:43 +03:00
|
|
|
stream = &dice->rx_stream[index];
|
|
|
|
resources = &dice->rx_resources[index];
|
2014-12-08 18:10:36 +03:00
|
|
|
}
|
2014-11-28 18:59:14 +03:00
|
|
|
|
2014-12-08 18:10:36 +03:00
|
|
|
err = fw_iso_resources_init(resources, dice->unit);
|
2014-11-28 18:59:14 +03:00
|
|
|
if (err < 0)
|
|
|
|
goto end;
|
2014-12-08 18:10:36 +03:00
|
|
|
resources->channels_mask = 0x00000000ffffffffuLL;
|
2014-11-28 18:59:14 +03:00
|
|
|
|
2015-09-19 05:21:55 +03:00
|
|
|
err = amdtp_am824_init(stream, dice->unit, dir, CIP_BLOCKING);
|
2014-12-08 18:10:36 +03:00
|
|
|
if (err < 0) {
|
|
|
|
amdtp_stream_destroy(stream);
|
|
|
|
fw_iso_resources_destroy(resources);
|
|
|
|
}
|
|
|
|
end:
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2015-02-21 17:54:59 +03:00
|
|
|
/*
|
|
|
|
* This function should be called before starting streams or after stopping
|
|
|
|
* streams.
|
|
|
|
*/
|
2016-03-07 16:35:43 +03:00
|
|
|
static void destroy_stream(struct snd_dice *dice,
|
|
|
|
enum amdtp_stream_direction dir,
|
|
|
|
unsigned int index)
|
2014-12-08 18:10:36 +03:00
|
|
|
{
|
2016-03-07 16:35:43 +03:00
|
|
|
struct amdtp_stream *stream;
|
2015-02-21 17:54:59 +03:00
|
|
|
struct fw_iso_resources *resources;
|
2014-12-08 18:10:36 +03:00
|
|
|
|
2016-03-07 16:35:43 +03:00
|
|
|
if (dir == AMDTP_IN_STREAM) {
|
|
|
|
stream = &dice->tx_stream[index];
|
|
|
|
resources = &dice->tx_resources[index];
|
|
|
|
} else {
|
|
|
|
stream = &dice->rx_stream[index];
|
|
|
|
resources = &dice->rx_resources[index];
|
|
|
|
}
|
2015-02-21 17:54:59 +03:00
|
|
|
|
|
|
|
amdtp_stream_destroy(stream);
|
|
|
|
fw_iso_resources_destroy(resources);
|
2014-12-08 18:10:36 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
int snd_dice_stream_init_duplex(struct snd_dice *dice)
|
|
|
|
{
|
2016-03-07 16:35:43 +03:00
|
|
|
int i, err;
|
2014-12-08 18:10:36 +03:00
|
|
|
|
2016-03-07 16:35:43 +03:00
|
|
|
for (i = 0; i < MAX_STREAMS; i++) {
|
|
|
|
err = init_stream(dice, AMDTP_IN_STREAM, i);
|
|
|
|
if (err < 0) {
|
|
|
|
for (; i >= 0; i--)
|
|
|
|
destroy_stream(dice, AMDTP_OUT_STREAM, i);
|
|
|
|
goto end;
|
|
|
|
}
|
|
|
|
}
|
2014-11-28 18:59:14 +03:00
|
|
|
|
2016-03-07 16:35:43 +03:00
|
|
|
for (i = 0; i < MAX_STREAMS; i++) {
|
|
|
|
err = init_stream(dice, AMDTP_OUT_STREAM, i);
|
|
|
|
if (err < 0) {
|
|
|
|
for (; i >= 0; i--)
|
|
|
|
destroy_stream(dice, AMDTP_OUT_STREAM, i);
|
|
|
|
for (i = 0; i < MAX_STREAMS; i++)
|
|
|
|
destroy_stream(dice, AMDTP_IN_STREAM, i);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2014-11-28 18:59:14 +03:00
|
|
|
end:
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2014-12-08 18:10:36 +03:00
|
|
|
void snd_dice_stream_destroy_duplex(struct snd_dice *dice)
|
2014-11-28 18:59:14 +03:00
|
|
|
{
|
2016-03-28 02:29:32 +03:00
|
|
|
unsigned int i;
|
2014-12-08 18:10:36 +03:00
|
|
|
|
2016-03-28 02:29:32 +03:00
|
|
|
for (i = 0; i < MAX_STREAMS; i++) {
|
|
|
|
destroy_stream(dice, AMDTP_IN_STREAM, i);
|
|
|
|
destroy_stream(dice, AMDTP_OUT_STREAM, i);
|
2016-03-07 16:35:43 +03:00
|
|
|
}
|
2014-11-28 18:59:14 +03:00
|
|
|
}
|
|
|
|
|
2014-12-08 18:10:36 +03:00
|
|
|
void snd_dice_stream_update_duplex(struct snd_dice *dice)
|
2014-11-28 18:59:14 +03:00
|
|
|
{
|
2016-03-10 15:44:28 +03:00
|
|
|
struct reg_params tx_params, rx_params;
|
2016-03-07 16:35:43 +03:00
|
|
|
|
2014-11-28 18:59:14 +03:00
|
|
|
/*
|
|
|
|
* On a bus reset, the DICE firmware disables streaming and then goes
|
|
|
|
* off contemplating its own navel for hundreds of milliseconds before
|
|
|
|
* it can react to any of our attempts to reenable streaming. This
|
|
|
|
* means that we lose synchronization anyway, so we force our streams
|
|
|
|
* to stop so that the application can restart them in an orderly
|
|
|
|
* manner.
|
|
|
|
*/
|
|
|
|
dice->global_enabled = false;
|
|
|
|
|
2016-03-10 15:44:28 +03:00
|
|
|
if (get_register_params(dice, &tx_params, &rx_params) == 0) {
|
|
|
|
stop_streams(dice, AMDTP_IN_STREAM, &tx_params);
|
|
|
|
stop_streams(dice, AMDTP_OUT_STREAM, &rx_params);
|
2016-03-07 16:35:43 +03:00
|
|
|
}
|
2014-11-28 18:59:14 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
static void dice_lock_changed(struct snd_dice *dice)
|
|
|
|
{
|
|
|
|
dice->dev_lock_changed = true;
|
|
|
|
wake_up(&dice->hwdep_wait);
|
|
|
|
}
|
|
|
|
|
|
|
|
int snd_dice_stream_lock_try(struct snd_dice *dice)
|
|
|
|
{
|
|
|
|
int err;
|
|
|
|
|
|
|
|
spin_lock_irq(&dice->lock);
|
|
|
|
|
|
|
|
if (dice->dev_lock_count < 0) {
|
|
|
|
err = -EBUSY;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (dice->dev_lock_count++ == 0)
|
|
|
|
dice_lock_changed(dice);
|
|
|
|
err = 0;
|
|
|
|
out:
|
|
|
|
spin_unlock_irq(&dice->lock);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
void snd_dice_stream_lock_release(struct snd_dice *dice)
|
|
|
|
{
|
|
|
|
spin_lock_irq(&dice->lock);
|
|
|
|
|
|
|
|
if (WARN_ON(dice->dev_lock_count <= 0))
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
if (--dice->dev_lock_count == 0)
|
|
|
|
dice_lock_changed(dice);
|
|
|
|
out:
|
|
|
|
spin_unlock_irq(&dice->lock);
|
|
|
|
}
|