// SPDX-License-Identifier: GPL-2.0-only /* * tascam-stream.c - a part of driver for TASCAM FireWire series * * Copyright (c) 2015 Takashi Sakamoto */ #include #include "tascam.h" #define CLOCK_STATUS_MASK 0xffff0000 #define CLOCK_CONFIG_MASK 0x0000ffff #define CALLBACK_TIMEOUT 500 static int get_clock(struct snd_tscm *tscm, u32 *data) { int trial = 0; __be32 reg; int err; while (trial++ < 5) { err = snd_fw_transaction(tscm->unit, TCODE_READ_QUADLET_REQUEST, TSCM_ADDR_BASE + TSCM_OFFSET_CLOCK_STATUS, ®, sizeof(reg), 0); if (err < 0) return err; *data = be32_to_cpu(reg); if (*data & CLOCK_STATUS_MASK) break; // In intermediate state after changing clock status. msleep(50); } // Still in the intermediate state. if (trial >= 5) return -EAGAIN; return 0; } static int set_clock(struct snd_tscm *tscm, unsigned int rate, enum snd_tscm_clock clock) { u32 data; __be32 reg; int err; err = get_clock(tscm, &data); if (err < 0) return err; data &= CLOCK_CONFIG_MASK; if (rate > 0) { data &= 0x000000ff; /* Base rate. */ if ((rate % 44100) == 0) { data |= 0x00000100; /* Multiplier. */ if (rate / 44100 == 2) data |= 0x00008000; } else if ((rate % 48000) == 0) { data |= 0x00000200; /* Multiplier. */ if (rate / 48000 == 2) data |= 0x00008000; } else { return -EAGAIN; } } if (clock != INT_MAX) { data &= 0x0000ff00; data |= clock + 1; } reg = cpu_to_be32(data); err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, TSCM_ADDR_BASE + TSCM_OFFSET_CLOCK_STATUS, ®, sizeof(reg), 0); if (err < 0) return err; if (data & 0x00008000) reg = cpu_to_be32(0x0000001a); else reg = cpu_to_be32(0x0000000d); return snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, TSCM_ADDR_BASE + TSCM_OFFSET_MULTIPLEX_MODE, ®, sizeof(reg), 0); } int snd_tscm_stream_get_rate(struct snd_tscm *tscm, unsigned int *rate) { u32 data; int err; err = get_clock(tscm, &data); if (err < 0) return err; data = (data & 0xff000000) >> 24; /* Check base rate. */ if ((data & 0x0f) == 0x01) *rate = 44100; else if ((data & 0x0f) == 0x02) *rate = 48000; else return -EAGAIN; /* Check multiplier. */ if ((data & 0xf0) == 0x80) *rate *= 2; else if ((data & 0xf0) != 0x00) return -EAGAIN; return err; } int snd_tscm_stream_get_clock(struct snd_tscm *tscm, enum snd_tscm_clock *clock) { u32 data; int err; err = get_clock(tscm, &data); if (err < 0) return err; *clock = ((data & 0x00ff0000) >> 16) - 1; if (*clock < 0 || *clock > SND_TSCM_CLOCK_ADAT) return -EIO; return 0; } static int enable_data_channels(struct snd_tscm *tscm) { __be32 reg; u32 data; unsigned int i; int err; data = 0; for (i = 0; i < tscm->spec->pcm_capture_analog_channels; ++i) data |= BIT(i); if (tscm->spec->has_adat) data |= 0x0000ff00; if (tscm->spec->has_spdif) data |= 0x00030000; reg = cpu_to_be32(data); err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, TSCM_ADDR_BASE + TSCM_OFFSET_TX_PCM_CHANNELS, ®, sizeof(reg), 0); if (err < 0) return err; data = 0; for (i = 0; i < tscm->spec->pcm_playback_analog_channels; ++i) data |= BIT(i); if (tscm->spec->has_adat) data |= 0x0000ff00; if (tscm->spec->has_spdif) data |= 0x00030000; reg = cpu_to_be32(data); return snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, TSCM_ADDR_BASE + TSCM_OFFSET_RX_PCM_CHANNELS, ®, sizeof(reg), 0); } static int set_stream_formats(struct snd_tscm *tscm, unsigned int rate) { __be32 reg; int err; // Set an option for unknown purpose. reg = cpu_to_be32(0x00200000); err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, TSCM_ADDR_BASE + TSCM_OFFSET_SET_OPTION, ®, sizeof(reg), 0); if (err < 0) return err; return enable_data_channels(tscm); } static void finish_session(struct snd_tscm *tscm) { __be32 reg; reg = 0; snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, TSCM_ADDR_BASE + TSCM_OFFSET_START_STREAMING, ®, sizeof(reg), 0); reg = 0; snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, TSCM_ADDR_BASE + TSCM_OFFSET_ISOC_RX_ON, ®, sizeof(reg), 0); // Unregister channels. reg = cpu_to_be32(0x00000000); snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, TSCM_ADDR_BASE + TSCM_OFFSET_ISOC_TX_CH, ®, sizeof(reg), 0); reg = cpu_to_be32(0x00000000); snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, TSCM_ADDR_BASE + TSCM_OFFSET_UNKNOWN, ®, sizeof(reg), 0); reg = cpu_to_be32(0x00000000); snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, TSCM_ADDR_BASE + TSCM_OFFSET_ISOC_RX_CH, ®, sizeof(reg), 0); } static int begin_session(struct snd_tscm *tscm) { __be32 reg; int err; // Register the isochronous channel for transmitting stream. reg = cpu_to_be32(tscm->tx_resources.channel); err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, TSCM_ADDR_BASE + TSCM_OFFSET_ISOC_TX_CH, ®, sizeof(reg), 0); if (err < 0) return err; // Unknown. reg = cpu_to_be32(0x00000002); err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, TSCM_ADDR_BASE + TSCM_OFFSET_UNKNOWN, ®, sizeof(reg), 0); if (err < 0) return err; // Register the isochronous channel for receiving stream. reg = cpu_to_be32(tscm->rx_resources.channel); err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, TSCM_ADDR_BASE + TSCM_OFFSET_ISOC_RX_CH, ®, sizeof(reg), 0); if (err < 0) return err; reg = cpu_to_be32(0x00000001); err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, TSCM_ADDR_BASE + TSCM_OFFSET_START_STREAMING, ®, sizeof(reg), 0); if (err < 0) return err; reg = cpu_to_be32(0x00000001); err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, TSCM_ADDR_BASE + TSCM_OFFSET_ISOC_RX_ON, ®, sizeof(reg), 0); if (err < 0) return err; // Set an option for unknown purpose. reg = cpu_to_be32(0x00002000); err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, TSCM_ADDR_BASE + TSCM_OFFSET_SET_OPTION, ®, sizeof(reg), 0); if (err < 0) return err; // Start multiplexing PCM samples on packets. reg = cpu_to_be32(0x00000001); return snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST, TSCM_ADDR_BASE + TSCM_OFFSET_ISOC_TX_ON, ®, sizeof(reg), 0); } static int keep_resources(struct snd_tscm *tscm, unsigned int rate, struct amdtp_stream *stream) { struct fw_iso_resources *resources; int err; if (stream == &tscm->tx_stream) resources = &tscm->tx_resources; else resources = &tscm->rx_resources; err = amdtp_tscm_set_parameters(stream, rate); if (err < 0) return err; return fw_iso_resources_allocate(resources, amdtp_stream_get_max_payload(stream), fw_parent_device(tscm->unit)->max_speed); } static int init_stream(struct snd_tscm *tscm, struct amdtp_stream *s) { struct fw_iso_resources *resources; enum amdtp_stream_direction dir; unsigned int pcm_channels; int err; if (s == &tscm->tx_stream) { resources = &tscm->tx_resources; dir = AMDTP_IN_STREAM; pcm_channels = tscm->spec->pcm_capture_analog_channels; } else { resources = &tscm->rx_resources; dir = AMDTP_OUT_STREAM; pcm_channels = tscm->spec->pcm_playback_analog_channels; } if (tscm->spec->has_adat) pcm_channels += 8; if (tscm->spec->has_spdif) pcm_channels += 2; err = fw_iso_resources_init(resources, tscm->unit); if (err < 0) return err; err = amdtp_tscm_init(s, tscm->unit, dir, pcm_channels); if (err < 0) fw_iso_resources_free(resources); return err; } static void destroy_stream(struct snd_tscm *tscm, struct amdtp_stream *s) { amdtp_stream_destroy(s); if (s == &tscm->tx_stream) fw_iso_resources_destroy(&tscm->tx_resources); else fw_iso_resources_destroy(&tscm->rx_resources); } int snd_tscm_stream_init_duplex(struct snd_tscm *tscm) { int err; err = init_stream(tscm, &tscm->tx_stream); if (err < 0) return err; err = init_stream(tscm, &tscm->rx_stream); if (err < 0) { destroy_stream(tscm, &tscm->tx_stream); return err; } err = amdtp_domain_init(&tscm->domain); if (err < 0) { destroy_stream(tscm, &tscm->tx_stream); destroy_stream(tscm, &tscm->rx_stream); } return err; } // At bus reset, streaming is stopped and some registers are clear. void snd_tscm_stream_update_duplex(struct snd_tscm *tscm) { amdtp_domain_stop(&tscm->domain); amdtp_stream_pcm_abort(&tscm->tx_stream); amdtp_stream_pcm_abort(&tscm->rx_stream); } // This function should be called before starting streams or after stopping // streams. void snd_tscm_stream_destroy_duplex(struct snd_tscm *tscm) { amdtp_domain_destroy(&tscm->domain); destroy_stream(tscm, &tscm->rx_stream); destroy_stream(tscm, &tscm->tx_stream); } int snd_tscm_stream_reserve_duplex(struct snd_tscm *tscm, unsigned int rate, unsigned int frames_per_period, unsigned int frames_per_buffer) { unsigned int curr_rate; int err; err = snd_tscm_stream_get_rate(tscm, &curr_rate); if (err < 0) return err; if (tscm->substreams_counter == 0 || rate != curr_rate) { amdtp_domain_stop(&tscm->domain); finish_session(tscm); fw_iso_resources_free(&tscm->tx_resources); fw_iso_resources_free(&tscm->rx_resources); err = set_clock(tscm, rate, INT_MAX); if (err < 0) return err; err = keep_resources(tscm, rate, &tscm->tx_stream); if (err < 0) return err; err = keep_resources(tscm, rate, &tscm->rx_stream); if (err < 0) { fw_iso_resources_free(&tscm->tx_resources); return err; } err = amdtp_domain_set_events_per_period(&tscm->domain, frames_per_period, frames_per_buffer); if (err < 0) { fw_iso_resources_free(&tscm->tx_resources); fw_iso_resources_free(&tscm->rx_resources); return err; } } return 0; } int snd_tscm_stream_start_duplex(struct snd_tscm *tscm, unsigned int rate) { unsigned int generation = tscm->rx_resources.generation; int err; if (tscm->substreams_counter == 0) return 0; if (amdtp_streaming_error(&tscm->rx_stream) || amdtp_streaming_error(&tscm->tx_stream)) { amdtp_domain_stop(&tscm->domain); finish_session(tscm); } if (generation != fw_parent_device(tscm->unit)->card->generation) { err = fw_iso_resources_update(&tscm->tx_resources); if (err < 0) goto error; err = fw_iso_resources_update(&tscm->rx_resources); if (err < 0) goto error; } if (!amdtp_stream_running(&tscm->rx_stream)) { int spd = fw_parent_device(tscm->unit)->max_speed; err = set_stream_formats(tscm, rate); if (err < 0) goto error; err = begin_session(tscm); if (err < 0) goto error; err = amdtp_domain_add_stream(&tscm->domain, &tscm->rx_stream, tscm->rx_resources.channel, spd); if (err < 0) goto error; err = amdtp_domain_add_stream(&tscm->domain, &tscm->tx_stream, tscm->tx_resources.channel, spd); if (err < 0) goto error; err = amdtp_domain_start(&tscm->domain, 0); if (err < 0) return err; if (!amdtp_stream_wait_callback(&tscm->rx_stream, CALLBACK_TIMEOUT) || !amdtp_stream_wait_callback(&tscm->tx_stream, CALLBACK_TIMEOUT)) { err = -ETIMEDOUT; goto error; } } return 0; error: amdtp_domain_stop(&tscm->domain); finish_session(tscm); return err; } void snd_tscm_stream_stop_duplex(struct snd_tscm *tscm) { if (tscm->substreams_counter == 0) { amdtp_domain_stop(&tscm->domain); finish_session(tscm); fw_iso_resources_free(&tscm->tx_resources); fw_iso_resources_free(&tscm->rx_resources); } } void snd_tscm_stream_lock_changed(struct snd_tscm *tscm) { tscm->dev_lock_changed = true; wake_up(&tscm->hwdep_wait); } int snd_tscm_stream_lock_try(struct snd_tscm *tscm) { int err; spin_lock_irq(&tscm->lock); /* user land lock this */ if (tscm->dev_lock_count < 0) { err = -EBUSY; goto end; } /* this is the first time */ if (tscm->dev_lock_count++ == 0) snd_tscm_stream_lock_changed(tscm); err = 0; end: spin_unlock_irq(&tscm->lock); return err; } void snd_tscm_stream_lock_release(struct snd_tscm *tscm) { spin_lock_irq(&tscm->lock); if (WARN_ON(tscm->dev_lock_count <= 0)) goto end; if (--tscm->dev_lock_count == 0) snd_tscm_stream_lock_changed(tscm); end: spin_unlock_irq(&tscm->lock); }