Path: blob/master/sound/firewire/tascam/tascam-stream.c
26451 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 err;285286if (stream == &tscm->tx_stream)287resources = &tscm->tx_resources;288else289resources = &tscm->rx_resources;290291err = amdtp_tscm_set_parameters(stream, rate);292if (err < 0)293return err;294295return fw_iso_resources_allocate(resources,296amdtp_stream_get_max_payload(stream),297fw_parent_device(tscm->unit)->max_speed);298}299300static int init_stream(struct snd_tscm *tscm, struct amdtp_stream *s)301{302struct fw_iso_resources *resources;303enum amdtp_stream_direction dir;304unsigned int pcm_channels;305int err;306307if (s == &tscm->tx_stream) {308resources = &tscm->tx_resources;309dir = AMDTP_IN_STREAM;310pcm_channels = tscm->spec->pcm_capture_analog_channels;311} else {312resources = &tscm->rx_resources;313dir = AMDTP_OUT_STREAM;314pcm_channels = tscm->spec->pcm_playback_analog_channels;315}316317if (tscm->spec->has_adat)318pcm_channels += 8;319if (tscm->spec->has_spdif)320pcm_channels += 2;321322err = fw_iso_resources_init(resources, tscm->unit);323if (err < 0)324return err;325326err = amdtp_tscm_init(s, tscm->unit, dir, pcm_channels);327if (err < 0)328fw_iso_resources_free(resources);329330return err;331}332333static void destroy_stream(struct snd_tscm *tscm, struct amdtp_stream *s)334{335amdtp_stream_destroy(s);336337if (s == &tscm->tx_stream)338fw_iso_resources_destroy(&tscm->tx_resources);339else340fw_iso_resources_destroy(&tscm->rx_resources);341}342343int snd_tscm_stream_init_duplex(struct snd_tscm *tscm)344{345int err;346347err = init_stream(tscm, &tscm->tx_stream);348if (err < 0)349return err;350351err = init_stream(tscm, &tscm->rx_stream);352if (err < 0) {353destroy_stream(tscm, &tscm->tx_stream);354return err;355}356357err = amdtp_domain_init(&tscm->domain);358if (err < 0) {359destroy_stream(tscm, &tscm->tx_stream);360destroy_stream(tscm, &tscm->rx_stream);361}362363return err;364}365366// At bus reset, streaming is stopped and some registers are clear.367void snd_tscm_stream_update_duplex(struct snd_tscm *tscm)368{369amdtp_domain_stop(&tscm->domain);370371amdtp_stream_pcm_abort(&tscm->tx_stream);372amdtp_stream_pcm_abort(&tscm->rx_stream);373}374375// This function should be called before starting streams or after stopping376// streams.377void snd_tscm_stream_destroy_duplex(struct snd_tscm *tscm)378{379amdtp_domain_destroy(&tscm->domain);380381destroy_stream(tscm, &tscm->rx_stream);382destroy_stream(tscm, &tscm->tx_stream);383}384385int snd_tscm_stream_reserve_duplex(struct snd_tscm *tscm, unsigned int rate,386unsigned int frames_per_period,387unsigned int frames_per_buffer)388{389unsigned int curr_rate;390int err;391392err = snd_tscm_stream_get_rate(tscm, &curr_rate);393if (err < 0)394return err;395396if (tscm->substreams_counter == 0 || rate != curr_rate) {397amdtp_domain_stop(&tscm->domain);398399finish_session(tscm);400401fw_iso_resources_free(&tscm->tx_resources);402fw_iso_resources_free(&tscm->rx_resources);403404err = set_clock(tscm, rate, INT_MAX);405if (err < 0)406return err;407408err = keep_resources(tscm, rate, &tscm->tx_stream);409if (err < 0)410return err;411412err = keep_resources(tscm, rate, &tscm->rx_stream);413if (err < 0) {414fw_iso_resources_free(&tscm->tx_resources);415return err;416}417418err = amdtp_domain_set_events_per_period(&tscm->domain,419frames_per_period, frames_per_buffer);420if (err < 0) {421fw_iso_resources_free(&tscm->tx_resources);422fw_iso_resources_free(&tscm->rx_resources);423return err;424}425426tscm->need_long_tx_init_skip = (rate != curr_rate);427}428429return 0;430}431432int snd_tscm_stream_start_duplex(struct snd_tscm *tscm, unsigned int rate)433{434unsigned int generation = tscm->rx_resources.generation;435int err;436437if (tscm->substreams_counter == 0)438return 0;439440if (amdtp_streaming_error(&tscm->rx_stream) ||441amdtp_streaming_error(&tscm->tx_stream)) {442amdtp_domain_stop(&tscm->domain);443finish_session(tscm);444}445446if (generation != fw_parent_device(tscm->unit)->card->generation) {447err = fw_iso_resources_update(&tscm->tx_resources);448if (err < 0)449goto error;450451err = fw_iso_resources_update(&tscm->rx_resources);452if (err < 0)453goto error;454}455456if (!amdtp_stream_running(&tscm->rx_stream)) {457int spd = fw_parent_device(tscm->unit)->max_speed;458unsigned int tx_init_skip_cycles;459460err = set_stream_formats(tscm, rate);461if (err < 0)462goto error;463464err = begin_session(tscm);465if (err < 0)466goto error;467468err = amdtp_domain_add_stream(&tscm->domain, &tscm->rx_stream,469tscm->rx_resources.channel, spd);470if (err < 0)471goto error;472473err = amdtp_domain_add_stream(&tscm->domain, &tscm->tx_stream,474tscm->tx_resources.channel, spd);475if (err < 0)476goto error;477478if (tscm->need_long_tx_init_skip)479tx_init_skip_cycles = 16000;480else481tx_init_skip_cycles = 0;482483// MEMO: Just after starting packet streaming, it transfers packets without any484// event. Enough after receiving the sequence of packets, it multiplexes events into485// the packet. However, just after changing sampling transfer frequency, it stops486// multiplexing during packet transmission. Enough after, it restarts multiplexing487// again. The device ignores presentation time expressed by the value of syt field488// of CIP header in received packets. The sequence of the number of data blocks per489// packet is important for media clock recovery.490err = amdtp_domain_start(&tscm->domain, tx_init_skip_cycles, true, true);491if (err < 0)492goto error;493494if (!amdtp_domain_wait_ready(&tscm->domain, READY_TIMEOUT_MS)) {495err = -ETIMEDOUT;496goto error;497}498}499500return 0;501error:502amdtp_domain_stop(&tscm->domain);503finish_session(tscm);504505return err;506}507508void snd_tscm_stream_stop_duplex(struct snd_tscm *tscm)509{510if (tscm->substreams_counter == 0) {511amdtp_domain_stop(&tscm->domain);512finish_session(tscm);513514fw_iso_resources_free(&tscm->tx_resources);515fw_iso_resources_free(&tscm->rx_resources);516517tscm->need_long_tx_init_skip = false;518}519}520521void snd_tscm_stream_lock_changed(struct snd_tscm *tscm)522{523tscm->dev_lock_changed = true;524wake_up(&tscm->hwdep_wait);525}526527int snd_tscm_stream_lock_try(struct snd_tscm *tscm)528{529int err;530531spin_lock_irq(&tscm->lock);532533/* user land lock this */534if (tscm->dev_lock_count < 0) {535err = -EBUSY;536goto end;537}538539/* this is the first time */540if (tscm->dev_lock_count++ == 0)541snd_tscm_stream_lock_changed(tscm);542err = 0;543end:544spin_unlock_irq(&tscm->lock);545return err;546}547548void snd_tscm_stream_lock_release(struct snd_tscm *tscm)549{550spin_lock_irq(&tscm->lock);551552if (WARN_ON(tscm->dev_lock_count <= 0))553goto end;554if (--tscm->dev_lock_count == 0)555snd_tscm_stream_lock_changed(tscm);556end:557spin_unlock_irq(&tscm->lock);558}559560561