summaryrefslogtreecommitdiff
path: root/sound/firewire/dice/dice-stream.c
diff options
context:
space:
mode:
Diffstat (limited to 'sound/firewire/dice/dice-stream.c')
-rw-r--r--sound/firewire/dice/dice-stream.c283
1 files changed, 215 insertions, 68 deletions
diff --git a/sound/firewire/dice/dice-stream.c b/sound/firewire/dice/dice-stream.c
index 928a255bfc35..c3c892c5c7ff 100644
--- a/sound/firewire/dice/dice-stream.c
+++ b/sound/firewire/dice/dice-stream.c
@@ -30,13 +30,43 @@ const unsigned int snd_dice_rates[SND_DICE_RATES_COUNT] = {
[6] = 192000,
};
+int snd_dice_stream_get_rate_mode(struct snd_dice *dice, unsigned int rate,
+ enum snd_dice_rate_mode *mode)
+{
+ /* Corresponding to each entry in snd_dice_rates. */
+ static const enum snd_dice_rate_mode modes[] = {
+ [0] = SND_DICE_RATE_MODE_LOW,
+ [1] = SND_DICE_RATE_MODE_LOW,
+ [2] = SND_DICE_RATE_MODE_LOW,
+ [3] = SND_DICE_RATE_MODE_MIDDLE,
+ [4] = SND_DICE_RATE_MODE_MIDDLE,
+ [5] = SND_DICE_RATE_MODE_HIGH,
+ [6] = SND_DICE_RATE_MODE_HIGH,
+ };
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(snd_dice_rates); i++) {
+ if (!(dice->clock_caps & BIT(i)))
+ continue;
+ if (snd_dice_rates[i] != rate)
+ continue;
+
+ *mode = modes[i];
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
/*
* 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)
+static int ensure_phase_lock(struct snd_dice *dice, unsigned int rate)
{
__be32 reg, nominal;
+ u32 data;
+ int i;
int err;
err = snd_dice_transaction_read_global(dice, GLOBAL_CLOCK_SELECT,
@@ -44,9 +74,21 @@ static int ensure_phase_lock(struct snd_dice *dice)
if (err < 0)
return err;
+ data = be32_to_cpu(reg);
+
+ data &= ~CLOCK_RATE_MASK;
+ for (i = 0; i < ARRAY_SIZE(snd_dice_rates); ++i) {
+ if (snd_dice_rates[i] == rate)
+ break;
+ }
+ if (i == ARRAY_SIZE(snd_dice_rates))
+ return -EINVAL;
+ data |= i << CLOCK_RATE_SHIFT;
+
if (completion_done(&dice->clock_accepted))
reinit_completion(&dice->clock_accepted);
+ reg = cpu_to_be32(data);
err = snd_dice_transaction_write_global(dice, GLOBAL_CLOCK_SELECT,
&reg, sizeof(reg));
if (err < 0)
@@ -192,6 +234,7 @@ static int start_streams(struct snd_dice *dice, enum amdtp_stream_direction dir,
unsigned int rate, struct reg_params *params)
{
__be32 reg[2];
+ enum snd_dice_rate_mode mode;
unsigned int i, pcm_chs, midi_ports;
struct amdtp_stream *streams;
struct fw_iso_resources *resources;
@@ -206,12 +249,23 @@ static int start_streams(struct snd_dice *dice, enum amdtp_stream_direction dir,
resources = dice->rx_resources;
}
+ err = snd_dice_stream_get_rate_mode(dice, rate, &mode);
+ if (err < 0)
+ return err;
+
for (i = 0; i < params->count; i++) {
+ unsigned int pcm_cache;
+ unsigned int midi_cache;
+
if (dir == AMDTP_IN_STREAM) {
+ pcm_cache = dice->tx_pcm_chs[i][mode];
+ midi_cache = dice->tx_midi_ports[i];
err = snd_dice_transaction_read_tx(dice,
params->size * i + TX_NUMBER_AUDIO,
reg, sizeof(reg));
} else {
+ pcm_cache = dice->rx_pcm_chs[i][mode];
+ midi_cache = dice->rx_midi_ports[i];
err = snd_dice_transaction_read_rx(dice,
params->size * i + RX_NUMBER_AUDIO,
reg, sizeof(reg));
@@ -221,6 +275,14 @@ static int start_streams(struct snd_dice *dice, enum amdtp_stream_direction dir,
pcm_chs = be32_to_cpu(reg[0]);
midi_ports = be32_to_cpu(reg[1]);
+ /* These are important for developer of this driver. */
+ if (pcm_chs != pcm_cache || midi_ports != midi_cache) {
+ dev_info(&dice->unit->device,
+ "cache mismatch: pcm: %u:%u, midi: %u:%u\n",
+ pcm_chs, pcm_cache, midi_ports, midi_cache);
+ return -EPROTO;
+ }
+
err = keep_resources(dice, dir, i, rate, pcm_chs, midi_ports);
if (err < 0)
return err;
@@ -256,6 +318,68 @@ static int start_streams(struct snd_dice *dice, enum amdtp_stream_direction dir,
return err;
}
+static int start_duplex_streams(struct snd_dice *dice, unsigned int rate)
+{
+ struct reg_params tx_params, rx_params;
+ int i;
+ int err;
+
+ err = get_register_params(dice, &tx_params, &rx_params);
+ if (err < 0)
+ return err;
+
+ /* Stop transmission. */
+ stop_streams(dice, AMDTP_IN_STREAM, &tx_params);
+ stop_streams(dice, AMDTP_OUT_STREAM, &rx_params);
+ snd_dice_transaction_clear_enable(dice);
+ release_resources(dice);
+
+ err = ensure_phase_lock(dice, rate);
+ if (err < 0) {
+ dev_err(&dice->unit->device, "fail to ensure phase lock\n");
+ return err;
+ }
+
+ /* Likely to have changed stream formats. */
+ err = get_register_params(dice, &tx_params, &rx_params);
+ if (err < 0)
+ return err;
+
+ /* Start both streams. */
+ err = start_streams(dice, AMDTP_IN_STREAM, rate, &tx_params);
+ if (err < 0)
+ goto error;
+ err = start_streams(dice, AMDTP_OUT_STREAM, rate, &rx_params);
+ if (err < 0)
+ goto error;
+
+ err = snd_dice_transaction_set_enable(dice);
+ if (err < 0) {
+ dev_err(&dice->unit->device, "fail to enable interface\n");
+ goto error;
+ }
+
+ for (i = 0; i < MAX_STREAMS; i++) {
+ if ((i < tx_params.count &&
+ !amdtp_stream_wait_callback(&dice->tx_stream[i],
+ CALLBACK_TIMEOUT)) ||
+ (i < rx_params.count &&
+ !amdtp_stream_wait_callback(&dice->rx_stream[i],
+ CALLBACK_TIMEOUT))) {
+ err = -ETIMEDOUT;
+ goto error;
+ }
+ }
+
+ return 0;
+error:
+ stop_streams(dice, AMDTP_IN_STREAM, &tx_params);
+ stop_streams(dice, AMDTP_OUT_STREAM, &rx_params);
+ snd_dice_transaction_clear_enable(dice);
+ release_resources(dice);
+ return err;
+}
+
/*
* MEMO: After this function, there're two states of streams:
* - None streams are running.
@@ -265,17 +389,13 @@ int snd_dice_stream_start_duplex(struct snd_dice *dice, unsigned int rate)
{
unsigned int curr_rate;
unsigned int i;
- struct reg_params tx_params, rx_params;
- bool need_to_start;
+ enum snd_dice_rate_mode mode;
int err;
if (dice->substreams_counter == 0)
return -EIO;
- err = get_register_params(dice, &tx_params, &rx_params);
- if (err < 0)
- return err;
-
+ /* Check sampling transmission frequency. */
err = snd_dice_transaction_get_rate(dice, &curr_rate);
if (err < 0) {
dev_err(&dice->unit->device,
@@ -285,72 +405,36 @@ int snd_dice_stream_start_duplex(struct snd_dice *dice, unsigned int rate)
if (rate == 0)
rate = curr_rate;
if (rate != curr_rate)
- return -EINVAL;
+ goto restart;
- /* Judge to need to restart streams. */
- for (i = 0; i < MAX_STREAMS; i++) {
- if (i < tx_params.count) {
- if (amdtp_streaming_error(&dice->tx_stream[i]) ||
- !amdtp_stream_running(&dice->tx_stream[i]))
- break;
- }
- if (i < rx_params.count) {
- if (amdtp_streaming_error(&dice->rx_stream[i]) ||
- !amdtp_stream_running(&dice->rx_stream[i]))
- break;
- }
+ /* Check error of packet streaming. */
+ for (i = 0; i < MAX_STREAMS; ++i) {
+ if (amdtp_streaming_error(&dice->tx_stream[i]))
+ break;
+ if (amdtp_streaming_error(&dice->rx_stream[i]))
+ break;
}
- need_to_start = (i < MAX_STREAMS);
-
- if (need_to_start) {
- /* Stop transmission. */
- snd_dice_transaction_clear_enable(dice);
- stop_streams(dice, AMDTP_IN_STREAM, &tx_params);
- stop_streams(dice, AMDTP_OUT_STREAM, &rx_params);
- release_resources(dice);
-
- err = ensure_phase_lock(dice);
- if (err < 0) {
- dev_err(&dice->unit->device,
- "fail to ensure phase lock\n");
- return err;
- }
+ if (i < MAX_STREAMS)
+ goto restart;
- /* Start both streams. */
- err = start_streams(dice, AMDTP_IN_STREAM, rate, &tx_params);
- if (err < 0)
- goto error;
- err = start_streams(dice, AMDTP_OUT_STREAM, rate, &rx_params);
- if (err < 0)
- goto error;
-
- err = snd_dice_transaction_set_enable(dice);
- if (err < 0) {
- dev_err(&dice->unit->device,
- "fail to enable interface\n");
- goto error;
- }
-
- for (i = 0; i < MAX_STREAMS; i++) {
- if ((i < tx_params.count &&
- !amdtp_stream_wait_callback(&dice->tx_stream[i],
- CALLBACK_TIMEOUT)) ||
- (i < rx_params.count &&
- !amdtp_stream_wait_callback(&dice->rx_stream[i],
- CALLBACK_TIMEOUT))) {
- err = -ETIMEDOUT;
- goto error;
- }
- }
+ /* Check required streams are running or not. */
+ err = snd_dice_stream_get_rate_mode(dice, rate, &mode);
+ if (err < 0)
+ return err;
+ for (i = 0; i < MAX_STREAMS; ++i) {
+ if (dice->tx_pcm_chs[i][mode] > 0 &&
+ !amdtp_stream_running(&dice->tx_stream[i]))
+ break;
+ if (dice->rx_pcm_chs[i][mode] > 0 &&
+ !amdtp_stream_running(&dice->rx_stream[i]))
+ break;
}
+ if (i < MAX_STREAMS)
+ goto restart;
- return err;
-error:
- snd_dice_transaction_clear_enable(dice);
- stop_streams(dice, AMDTP_IN_STREAM, &tx_params);
- stop_streams(dice, AMDTP_OUT_STREAM, &rx_params);
- release_resources(dice);
- return err;
+ return 0;
+restart:
+ return start_duplex_streams(dice, rate);
}
/*
@@ -484,6 +568,69 @@ void snd_dice_stream_update_duplex(struct snd_dice *dice)
}
}
+int snd_dice_stream_detect_current_formats(struct snd_dice *dice)
+{
+ unsigned int rate;
+ enum snd_dice_rate_mode mode;
+ __be32 reg[2];
+ struct reg_params tx_params, rx_params;
+ int i;
+ int err;
+
+ /* If extended protocol is available, detect detail spec. */
+ err = snd_dice_detect_extension_formats(dice);
+ if (err >= 0)
+ return err;
+
+ /*
+ * Available stream format is restricted at current mode of sampling
+ * clock.
+ */
+ err = snd_dice_transaction_get_rate(dice, &rate);
+ if (err < 0)
+ return err;
+
+ err = snd_dice_stream_get_rate_mode(dice, rate, &mode);
+ if (err < 0)
+ return err;
+
+ /*
+ * Just after owning the unit (GLOBAL_OWNER), the unit can return
+ * invalid stream formats. Selecting clock parameters have an effect
+ * for the unit to refine it.
+ */
+ err = ensure_phase_lock(dice, rate);
+ if (err < 0)
+ return err;
+
+ err = get_register_params(dice, &tx_params, &rx_params);
+ if (err < 0)
+ return err;
+
+ for (i = 0; i < tx_params.count; ++i) {
+ err = snd_dice_transaction_read_tx(dice,
+ tx_params.size * i + TX_NUMBER_AUDIO,
+ reg, sizeof(reg));
+ if (err < 0)
+ return err;
+ dice->tx_pcm_chs[i][mode] = be32_to_cpu(reg[0]);
+ dice->tx_midi_ports[i] = max_t(unsigned int,
+ be32_to_cpu(reg[1]), dice->tx_midi_ports[i]);
+ }
+ for (i = 0; i < rx_params.count; ++i) {
+ err = snd_dice_transaction_read_rx(dice,
+ rx_params.size * i + RX_NUMBER_AUDIO,
+ reg, sizeof(reg));
+ if (err < 0)
+ return err;
+ dice->rx_pcm_chs[i][mode] = be32_to_cpu(reg[0]);
+ dice->rx_midi_ports[i] = max_t(unsigned int,
+ be32_to_cpu(reg[1]), dice->rx_midi_ports[i]);
+ }
+
+ return 0;
+}
+
static void dice_lock_changed(struct snd_dice *dice)
{
dice->dev_lock_changed = true;