Path: blob/master/sound/firewire/tascam/tascam-stream.c
52065 views
// SPDX-License-Identifier: GPL-2.0-only1/*2* tascam-stream.c - a part of driver for TASCAM FireWire series3*4* Copyright (c) 2015 Takashi Sakamoto5*/67#include <linux/delay.h>8#include "tascam.h"910#define CLOCK_STATUS_MASK 0xffff000011#define CLOCK_CONFIG_MASK 0x0000ffff1213#define READY_TIMEOUT_MS 40001415static int get_clock(struct snd_tscm *tscm, u32 *data)16{17int trial = 0;18__be32 reg;19int err;2021while (trial++ < 5) {22err = snd_fw_transaction(tscm->unit, TCODE_READ_QUADLET_REQUEST,23TSCM_ADDR_BASE + TSCM_OFFSET_CLOCK_STATUS,24®, sizeof(reg), 0);25if (err < 0)26return err;2728*data = be32_to_cpu(reg);29if (*data & CLOCK_STATUS_MASK)30break;3132// In intermediate state after changing clock status.33msleep(50);34}3536// Still in the intermediate state.37if (trial >= 5)38return -EAGAIN;3940return 0;41}4243static int set_clock(struct snd_tscm *tscm, unsigned int rate,44enum snd_tscm_clock clock)45{46u32 data;47__be32 reg;48int err;4950err = get_clock(tscm, &data);51if (err < 0)52return err;53data &= CLOCK_CONFIG_MASK;5455if (rate > 0) {56data &= 0x000000ff;57/* Base rate. */58if ((rate % 44100) == 0) {59data |= 0x00000100;60/* Multiplier. */61if (rate / 44100 == 2)62data |= 0x00008000;63} else if ((rate % 48000) == 0) {64data |= 0x00000200;65/* Multiplier. */66if (rate / 48000 == 2)67data |= 0x00008000;68} else {69return -EAGAIN;70}71}7273if (clock != INT_MAX) {74data &= 0x0000ff00;75data |= clock + 1;76}7778reg = cpu_to_be32(data);7980err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST,81TSCM_ADDR_BASE + TSCM_OFFSET_CLOCK_STATUS,82®, sizeof(reg), 0);83if (err < 0)84return err;8586if (data & 0x00008000)87reg = cpu_to_be32(0x0000001a);88else89reg = cpu_to_be32(0x0000000d);9091return snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST,92TSCM_ADDR_BASE + TSCM_OFFSET_MULTIPLEX_MODE,93®, sizeof(reg), 0);94}9596int snd_tscm_stream_get_rate(struct snd_tscm *tscm, unsigned int *rate)97{98u32 data;99int err;100101err = get_clock(tscm, &data);102if (err < 0)103return err;104105data = (data & 0xff000000) >> 24;106107/* Check base rate. */108if ((data & 0x0f) == 0x01)109*rate = 44100;110else if ((data & 0x0f) == 0x02)111*rate = 48000;112else113return -EAGAIN;114115/* Check multiplier. */116if ((data & 0xf0) == 0x80)117*rate *= 2;118else if ((data & 0xf0) != 0x00)119return -EAGAIN;120121return err;122}123124int snd_tscm_stream_get_clock(struct snd_tscm *tscm, enum snd_tscm_clock *clock)125{126u32 data;127int err;128129err = get_clock(tscm, &data);130if (err < 0)131return err;132133*clock = ((data & 0x00ff0000) >> 16) - 1;134if (*clock < 0 || *clock > SND_TSCM_CLOCK_ADAT)135return -EIO;136137return 0;138}139140static int enable_data_channels(struct snd_tscm *tscm)141{142__be32 reg;143u32 data;144unsigned int i;145int err;146147data = 0;148for (i = 0; i < tscm->spec->pcm_capture_analog_channels; ++i)149data |= BIT(i);150if (tscm->spec->has_adat)151data |= 0x0000ff00;152if (tscm->spec->has_spdif)153data |= 0x00030000;154155reg = cpu_to_be32(data);156err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST,157TSCM_ADDR_BASE + TSCM_OFFSET_TX_PCM_CHANNELS,158®, sizeof(reg), 0);159if (err < 0)160return err;161162data = 0;163for (i = 0; i < tscm->spec->pcm_playback_analog_channels; ++i)164data |= BIT(i);165if (tscm->spec->has_adat)166data |= 0x0000ff00;167if (tscm->spec->has_spdif)168data |= 0x00030000;169170reg = cpu_to_be32(data);171return snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST,172TSCM_ADDR_BASE + TSCM_OFFSET_RX_PCM_CHANNELS,173®, sizeof(reg), 0);174}175176static int set_stream_formats(struct snd_tscm *tscm, unsigned int rate)177{178__be32 reg;179int err;180181// Set an option for unknown purpose.182reg = cpu_to_be32(0x00200000);183err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST,184TSCM_ADDR_BASE + TSCM_OFFSET_SET_OPTION,185®, sizeof(reg), 0);186if (err < 0)187return err;188189return enable_data_channels(tscm);190}191192static void finish_session(struct snd_tscm *tscm)193{194__be32 reg;195196reg = 0;197snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST,198TSCM_ADDR_BASE + TSCM_OFFSET_START_STREAMING,199®, sizeof(reg), 0);200201reg = 0;202snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST,203TSCM_ADDR_BASE + TSCM_OFFSET_ISOC_RX_ON,204®, sizeof(reg), 0);205206// Unregister channels.207reg = cpu_to_be32(0x00000000);208snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST,209TSCM_ADDR_BASE + TSCM_OFFSET_ISOC_TX_CH,210®, sizeof(reg), 0);211reg = cpu_to_be32(0x00000000);212snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST,213TSCM_ADDR_BASE + TSCM_OFFSET_UNKNOWN,214®, sizeof(reg), 0);215reg = cpu_to_be32(0x00000000);216snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST,217TSCM_ADDR_BASE + TSCM_OFFSET_ISOC_RX_CH,218®, sizeof(reg), 0);219}220221static int begin_session(struct snd_tscm *tscm)222{223__be32 reg;224int err;225226// Register the isochronous channel for transmitting stream.227reg = cpu_to_be32(tscm->tx_resources.channel);228err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST,229TSCM_ADDR_BASE + TSCM_OFFSET_ISOC_TX_CH,230®, sizeof(reg), 0);231if (err < 0)232return err;233234// Unknown.235reg = cpu_to_be32(0x00000002);236err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST,237TSCM_ADDR_BASE + TSCM_OFFSET_UNKNOWN,238®, sizeof(reg), 0);239if (err < 0)240return err;241242// Register the isochronous channel for receiving stream.243reg = cpu_to_be32(tscm->rx_resources.channel);244err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST,245TSCM_ADDR_BASE + TSCM_OFFSET_ISOC_RX_CH,246®, sizeof(reg), 0);247if (err < 0)248return err;249250reg = cpu_to_be32(0x00000001);251err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST,252TSCM_ADDR_BASE + TSCM_OFFSET_START_STREAMING,253®, sizeof(reg), 0);254if (err < 0)255return err;256257reg = cpu_to_be32(0x00000001);258err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST,259TSCM_ADDR_BASE + TSCM_OFFSET_ISOC_RX_ON,260®, sizeof(reg), 0);261if (err < 0)262return err;263264// Set an option for unknown purpose.265reg = cpu_to_be32(0x00002000);266err = snd_fw_transaction(tscm->unit, TCODE_WRITE_QUADLET_REQUEST,267TSCM_ADDR_BASE + TSCM_OFFSET_SET_OPTION,268®, sizeof(reg), 0);269if (err < 0)270return err;271272// Start multiplexing PCM samples on packets.273reg = cpu_to_be32(0x00000001);274return snd_fw_transaction(tscm->unit,275TCODE_WRITE_QUADLET_REQUEST,276TSCM_ADDR_BASE + TSCM_OFFSET_ISOC_TX_ON,277®, sizeof(reg), 0);278}279280static int keep_resources(struct snd_tscm *tscm, unsigned int rate,281struct amdtp_stream *stream)282{283struct fw_iso_resources *resources;284int speed;285int err;286287if (stream == &tscm->tx_stream) {288resources = &tscm->tx_resources;289speed = fw_parent_device(tscm->unit)->max_speed;290} else {291resources = &tscm->rx_resources;292speed = SCODE_400;293}294295err = amdtp_tscm_set_parameters(stream, rate);296if (err < 0)297return err;298299return fw_iso_resources_allocate(resources, amdtp_stream_get_max_payload(stream), speed);300}301302static int init_stream(struct snd_tscm *tscm, struct amdtp_stream *s)303{304struct fw_iso_resources *resources;305enum amdtp_stream_direction dir;306unsigned int pcm_channels;307int err;308309if (s == &tscm->tx_stream) {310resources = &tscm->tx_resources;311dir = AMDTP_IN_STREAM;312pcm_channels = tscm->spec->pcm_capture_analog_channels;313} else {314resources = &tscm->rx_resources;315dir = AMDTP_OUT_STREAM;316pcm_channels = tscm->spec->pcm_playback_analog_channels;317}318319if (tscm->spec->has_adat)320pcm_channels += 8;321if (tscm->spec->has_spdif)322pcm_channels += 2;323324err = fw_iso_resources_init(resources, tscm->unit);325if (err < 0)326return err;327328err = amdtp_tscm_init(s, tscm->unit, dir, pcm_channels);329if (err < 0)330fw_iso_resources_free(resources);331332return err;333}334335static void destroy_stream(struct snd_tscm *tscm, struct amdtp_stream *s)336{337amdtp_stream_destroy(s);338339if (s == &tscm->tx_stream)340fw_iso_resources_destroy(&tscm->tx_resources);341else342fw_iso_resources_destroy(&tscm->rx_resources);343}344345int snd_tscm_stream_init_duplex(struct snd_tscm *tscm)346{347int err;348349err = init_stream(tscm, &tscm->tx_stream);350if (err < 0)351return err;352353err = init_stream(tscm, &tscm->rx_stream);354if (err < 0) {355destroy_stream(tscm, &tscm->tx_stream);356return err;357}358359err = amdtp_domain_init(&tscm->domain);360if (err < 0) {361destroy_stream(tscm, &tscm->tx_stream);362destroy_stream(tscm, &tscm->rx_stream);363}364365return err;366}367368// At bus reset, streaming is stopped and some registers are clear.369void snd_tscm_stream_update_duplex(struct snd_tscm *tscm)370{371amdtp_domain_stop(&tscm->domain);372373amdtp_stream_pcm_abort(&tscm->tx_stream);374amdtp_stream_pcm_abort(&tscm->rx_stream);375}376377// This function should be called before starting streams or after stopping378// streams.379void snd_tscm_stream_destroy_duplex(struct snd_tscm *tscm)380{381amdtp_domain_destroy(&tscm->domain);382383destroy_stream(tscm, &tscm->rx_stream);384destroy_stream(tscm, &tscm->tx_stream);385}386387int snd_tscm_stream_reserve_duplex(struct snd_tscm *tscm, unsigned int rate,388unsigned int frames_per_period,389unsigned int frames_per_buffer)390{391unsigned int curr_rate;392int err;393394err = snd_tscm_stream_get_rate(tscm, &curr_rate);395if (err < 0)396return err;397398if (tscm->substreams_counter == 0 || rate != curr_rate) {399amdtp_domain_stop(&tscm->domain);400401finish_session(tscm);402403fw_iso_resources_free(&tscm->tx_resources);404fw_iso_resources_free(&tscm->rx_resources);405406err = set_clock(tscm, rate, INT_MAX);407if (err < 0)408return err;409410err = keep_resources(tscm, rate, &tscm->tx_stream);411if (err < 0)412return err;413414err = keep_resources(tscm, rate, &tscm->rx_stream);415if (err < 0) {416fw_iso_resources_free(&tscm->tx_resources);417return err;418}419420err = amdtp_domain_set_events_per_period(&tscm->domain,421frames_per_period, frames_per_buffer);422if (err < 0) {423fw_iso_resources_free(&tscm->tx_resources);424fw_iso_resources_free(&tscm->rx_resources);425return err;426}427428tscm->need_long_tx_init_skip = (rate != curr_rate);429}430431return 0;432}433434int snd_tscm_stream_start_duplex(struct snd_tscm *tscm, unsigned int rate)435{436unsigned int generation = tscm->rx_resources.generation;437int err;438439if (tscm->substreams_counter == 0)440return 0;441442if (amdtp_streaming_error(&tscm->rx_stream) ||443amdtp_streaming_error(&tscm->tx_stream)) {444amdtp_domain_stop(&tscm->domain);445finish_session(tscm);446}447448if (generation != fw_parent_device(tscm->unit)->card->generation) {449err = fw_iso_resources_update(&tscm->tx_resources);450if (err < 0)451goto error;452453err = fw_iso_resources_update(&tscm->rx_resources);454if (err < 0)455goto error;456}457458if (!amdtp_stream_running(&tscm->rx_stream)) {459unsigned int tx_init_skip_cycles;460461err = set_stream_formats(tscm, rate);462if (err < 0)463goto error;464465err = begin_session(tscm);466if (err < 0)467goto error;468469err = amdtp_domain_add_stream(&tscm->domain, &tscm->rx_stream, tscm->rx_resources.channel,470fw_parent_device(tscm->unit)->max_speed);471if (err < 0)472goto error;473474err = amdtp_domain_add_stream(&tscm->domain, &tscm->tx_stream, tscm->tx_resources.channel,475SCODE_400);476if (err < 0)477goto error;478479if (tscm->need_long_tx_init_skip)480tx_init_skip_cycles = 16000;481else482tx_init_skip_cycles = 0;483484// MEMO: Just after starting packet streaming, it transfers packets without any485// event. Enough after receiving the sequence of packets, it multiplexes events into486// the packet. However, just after changing sampling transfer frequency, it stops487// multiplexing during packet transmission. Enough after, it restarts multiplexing488// again. The device ignores presentation time expressed by the value of syt field489// of CIP header in received packets. The sequence of the number of data blocks per490// packet is important for media clock recovery.491err = amdtp_domain_start(&tscm->domain, tx_init_skip_cycles, true, true);492if (err < 0)493goto error;494495if (!amdtp_domain_wait_ready(&tscm->domain, READY_TIMEOUT_MS)) {496err = -ETIMEDOUT;497goto error;498}499}500501return 0;502error:503amdtp_domain_stop(&tscm->domain);504finish_session(tscm);505506return err;507}508509void snd_tscm_stream_stop_duplex(struct snd_tscm *tscm)510{511if (tscm->substreams_counter == 0) {512amdtp_domain_stop(&tscm->domain);513finish_session(tscm);514515fw_iso_resources_free(&tscm->tx_resources);516fw_iso_resources_free(&tscm->rx_resources);517518tscm->need_long_tx_init_skip = false;519}520}521522void snd_tscm_stream_lock_changed(struct snd_tscm *tscm)523{524tscm->dev_lock_changed = true;525wake_up(&tscm->hwdep_wait);526}527528int snd_tscm_stream_lock_try(struct snd_tscm *tscm)529{530guard(spinlock_irq)(&tscm->lock);531532/* user land lock this */533if (tscm->dev_lock_count < 0)534return -EBUSY;535536/* this is the first time */537if (tscm->dev_lock_count++ == 0)538snd_tscm_stream_lock_changed(tscm);539return 0;540}541542void snd_tscm_stream_lock_release(struct snd_tscm *tscm)543{544guard(spinlock_irq)(&tscm->lock);545546if (WARN_ON(tscm->dev_lock_count <= 0))547return;548if (--tscm->dev_lock_count == 0)549snd_tscm_stream_lock_changed(tscm);550}551552553