root/sound/firewire/motu/motu-stream.c
// SPDX-License-Identifier: GPL-2.0-only
/*
 * motu-stream.c - a part of driver for MOTU FireWire series
 *
 * Copyright (c) 2015-2017 Takashi Sakamoto <o-takashi@sakamocchi.jp>
 */

#include "motu.h"

#define READY_TIMEOUT_MS        200

#define ISOC_COMM_CONTROL_OFFSET                0x0b00
#define  ISOC_COMM_CONTROL_MASK                 0xffff0000
#define  CHANGE_RX_ISOC_COMM_STATE              0x80000000
#define  RX_ISOC_COMM_IS_ACTIVATED              0x40000000
#define  RX_ISOC_COMM_CHANNEL_MASK              0x3f000000
#define  RX_ISOC_COMM_CHANNEL_SHIFT             24
#define  CHANGE_TX_ISOC_COMM_STATE              0x00800000
#define  TX_ISOC_COMM_IS_ACTIVATED              0x00400000
#define  TX_ISOC_COMM_CHANNEL_MASK              0x003f0000
#define  TX_ISOC_COMM_CHANNEL_SHIFT             16

#define PACKET_FORMAT_OFFSET                    0x0b10
#define  TX_PACKET_EXCLUDE_DIFFERED_DATA_CHUNKS 0x00000080
#define  RX_PACKET_EXCLUDE_DIFFERED_DATA_CHUNKS 0x00000040
#define  TX_PACKET_TRANSMISSION_SPEED_MASK      0x0000000f

static int keep_resources(struct snd_motu *motu, unsigned int rate,
                          struct amdtp_stream *stream)
{
        struct fw_iso_resources *resources;
        struct snd_motu_packet_format *packet_format;
        unsigned int midi_ports = 0;
        int err;

        if (stream == &motu->rx_stream) {
                resources = &motu->rx_resources;
                packet_format = &motu->rx_packet_formats;

                if ((motu->spec->flags & SND_MOTU_SPEC_RX_MIDI_2ND_Q) ||
                    (motu->spec->flags & SND_MOTU_SPEC_RX_MIDI_3RD_Q))
                        midi_ports = 1;
        } else {
                resources = &motu->tx_resources;
                packet_format = &motu->tx_packet_formats;

                if ((motu->spec->flags & SND_MOTU_SPEC_TX_MIDI_2ND_Q) ||
                    (motu->spec->flags & SND_MOTU_SPEC_TX_MIDI_3RD_Q))
                        midi_ports = 1;
        }

        err = amdtp_motu_set_parameters(stream, rate, midi_ports,
                                        packet_format);
        if (err < 0)
                return err;

        return fw_iso_resources_allocate(resources,
                                amdtp_stream_get_max_payload(stream),
                                fw_parent_device(motu->unit)->max_speed);
}

static int begin_session(struct snd_motu *motu)
{
        __be32 reg;
        u32 data;
        int err;

        // Configure the unit to start isochronous communication.
        err = snd_motu_transaction_read(motu, ISOC_COMM_CONTROL_OFFSET, &reg,
                                        sizeof(reg));
        if (err < 0)
                return err;
        data = be32_to_cpu(reg) & ~ISOC_COMM_CONTROL_MASK;

        data |= CHANGE_RX_ISOC_COMM_STATE | RX_ISOC_COMM_IS_ACTIVATED |
                (motu->rx_resources.channel << RX_ISOC_COMM_CHANNEL_SHIFT) |
                CHANGE_TX_ISOC_COMM_STATE | TX_ISOC_COMM_IS_ACTIVATED |
                (motu->tx_resources.channel << TX_ISOC_COMM_CHANNEL_SHIFT);

        reg = cpu_to_be32(data);
        return snd_motu_transaction_write(motu, ISOC_COMM_CONTROL_OFFSET, &reg,
                                          sizeof(reg));
}

static void finish_session(struct snd_motu *motu)
{
        __be32 reg;
        u32 data;
        int err;

        err = snd_motu_protocol_switch_fetching_mode(motu, false);
        if (err < 0)
                return;

        err = snd_motu_transaction_read(motu, ISOC_COMM_CONTROL_OFFSET, &reg,
                                        sizeof(reg));
        if (err < 0)
                return;
        data = be32_to_cpu(reg);

        data &= ~(RX_ISOC_COMM_IS_ACTIVATED | TX_ISOC_COMM_IS_ACTIVATED);
        data |= CHANGE_RX_ISOC_COMM_STATE | CHANGE_TX_ISOC_COMM_STATE;

        reg = cpu_to_be32(data);
        snd_motu_transaction_write(motu, ISOC_COMM_CONTROL_OFFSET, &reg,
                                   sizeof(reg));
}

int snd_motu_stream_cache_packet_formats(struct snd_motu *motu)
{
        int err;

        err = snd_motu_protocol_cache_packet_formats(motu);
        if (err < 0)
                return err;

        if (motu->spec->flags & SND_MOTU_SPEC_TX_MIDI_2ND_Q) {
                motu->tx_packet_formats.midi_flag_offset = 4;
                motu->tx_packet_formats.midi_byte_offset = 6;
        } else if (motu->spec->flags & SND_MOTU_SPEC_TX_MIDI_3RD_Q) {
                motu->tx_packet_formats.midi_flag_offset = 8;
                motu->tx_packet_formats.midi_byte_offset = 7;
        }

        if (motu->spec->flags & SND_MOTU_SPEC_RX_MIDI_2ND_Q) {
                motu->rx_packet_formats.midi_flag_offset = 4;
                motu->rx_packet_formats.midi_byte_offset = 6;
        } else if (motu->spec->flags & SND_MOTU_SPEC_RX_MIDI_3RD_Q) {
                motu->rx_packet_formats.midi_flag_offset = 8;
                motu->rx_packet_formats.midi_byte_offset = 7;
        }

        return 0;
}

int snd_motu_stream_reserve_duplex(struct snd_motu *motu, unsigned int rate,
                                   unsigned int frames_per_period,
                                   unsigned int frames_per_buffer)
{
        unsigned int curr_rate;
        int err;

        err = snd_motu_protocol_get_clock_rate(motu, &curr_rate);
        if (err < 0)
                return err;
        if (rate == 0)
                rate = curr_rate;

        if (motu->substreams_counter == 0 || curr_rate != rate) {
                amdtp_domain_stop(&motu->domain);
                finish_session(motu);

                fw_iso_resources_free(&motu->tx_resources);
                fw_iso_resources_free(&motu->rx_resources);

                kfree(motu->cache.event_offsets);
                motu->cache.event_offsets = NULL;

                err = snd_motu_protocol_set_clock_rate(motu, rate);
                if (err < 0) {
                        dev_err(&motu->unit->device,
                                "fail to set sampling rate: %d\n", err);
                        return err;
                }

                err = snd_motu_stream_cache_packet_formats(motu);
                if (err < 0)
                        return err;

                err = keep_resources(motu, rate, &motu->tx_stream);
                if (err < 0)
                        return err;

                err = keep_resources(motu, rate, &motu->rx_stream);
                if (err < 0) {
                        fw_iso_resources_free(&motu->tx_resources);
                        return err;
                }

                err = amdtp_domain_set_events_per_period(&motu->domain,
                                        frames_per_period, frames_per_buffer);
                if (err < 0) {
                        fw_iso_resources_free(&motu->tx_resources);
                        fw_iso_resources_free(&motu->rx_resources);
                        return err;
                }

                motu->cache.size = motu->tx_stream.syt_interval * frames_per_buffer;
                motu->cache.event_offsets = kcalloc(motu->cache.size, sizeof(*motu->cache.event_offsets),
                                                  GFP_KERNEL);
                if (!motu->cache.event_offsets) {
                        fw_iso_resources_free(&motu->tx_resources);
                        fw_iso_resources_free(&motu->rx_resources);
                        return -ENOMEM;
                }
        }

        return 0;
}

static int ensure_packet_formats(struct snd_motu *motu)
{
        __be32 reg;
        u32 data;
        int err;

        err = snd_motu_transaction_read(motu, PACKET_FORMAT_OFFSET, &reg,
                                        sizeof(reg));
        if (err < 0)
                return err;
        data = be32_to_cpu(reg);

        data &= ~(TX_PACKET_EXCLUDE_DIFFERED_DATA_CHUNKS |
                  RX_PACKET_EXCLUDE_DIFFERED_DATA_CHUNKS|
                  TX_PACKET_TRANSMISSION_SPEED_MASK);
        if (motu->spec->tx_fixed_pcm_chunks[0] == motu->tx_packet_formats.pcm_chunks[0])
                data |= TX_PACKET_EXCLUDE_DIFFERED_DATA_CHUNKS;
        if (motu->spec->rx_fixed_pcm_chunks[0] == motu->rx_packet_formats.pcm_chunks[0])
                data |= RX_PACKET_EXCLUDE_DIFFERED_DATA_CHUNKS;
        data |= fw_parent_device(motu->unit)->max_speed;

        reg = cpu_to_be32(data);
        return snd_motu_transaction_write(motu, PACKET_FORMAT_OFFSET, &reg,
                                          sizeof(reg));
}

int snd_motu_stream_start_duplex(struct snd_motu *motu)
{
        unsigned int generation = motu->rx_resources.generation;
        int err = 0;

        if (motu->substreams_counter == 0)
                return 0;

        if (amdtp_streaming_error(&motu->rx_stream) ||
            amdtp_streaming_error(&motu->tx_stream)) {
                amdtp_domain_stop(&motu->domain);
                finish_session(motu);
        }

        if (generation != fw_parent_device(motu->unit)->card->generation) {
                err = fw_iso_resources_update(&motu->rx_resources);
                if (err < 0)
                        return err;

                err = fw_iso_resources_update(&motu->tx_resources);
                if (err < 0)
                        return err;
        }

        if (!amdtp_stream_running(&motu->rx_stream)) {
                int spd = fw_parent_device(motu->unit)->max_speed;

                err = ensure_packet_formats(motu);
                if (err < 0)
                        return err;

                if (motu->spec->flags & SND_MOTU_SPEC_REGISTER_DSP) {
                        err = snd_motu_register_dsp_message_parser_init(motu);
                        if (err < 0)
                                return err;
                } else if (motu->spec->flags & SND_MOTU_SPEC_COMMAND_DSP) {
                        err = snd_motu_command_dsp_message_parser_init(motu, motu->tx_stream.sfc);
                        if (err < 0)
                                return err;
                }

                err = begin_session(motu);
                if (err < 0) {
                        dev_err(&motu->unit->device,
                                "fail to start isochronous comm: %d\n", err);
                        goto stop_streams;
                }

                err = amdtp_domain_add_stream(&motu->domain, &motu->tx_stream,
                                              motu->tx_resources.channel, spd);
                if (err < 0)
                        goto stop_streams;

                err = amdtp_domain_add_stream(&motu->domain, &motu->rx_stream,
                                              motu->rx_resources.channel, spd);
                if (err < 0)
                        goto stop_streams;

                motu->cache.tail = 0;
                motu->cache.tx_cycle_count = UINT_MAX;
                motu->cache.head = 0;
                motu->cache.rx_cycle_count = UINT_MAX;

                // NOTE: The device requires both of replay; the sequence of the number of data
                // blocks per packet, and the sequence of source packet header per data block as
                // presentation time.
                err = amdtp_domain_start(&motu->domain, 0, true, false);
                if (err < 0)
                        goto stop_streams;

                if (!amdtp_domain_wait_ready(&motu->domain, READY_TIMEOUT_MS)) {
                        err = -ETIMEDOUT;
                        goto stop_streams;
                }

                err = snd_motu_protocol_switch_fetching_mode(motu, true);
                if (err < 0) {
                        dev_err(&motu->unit->device,
                                "fail to enable frame fetching: %d\n", err);
                        goto stop_streams;
                }
        }

        return 0;

stop_streams:
        amdtp_domain_stop(&motu->domain);
        finish_session(motu);
        return err;
}

void snd_motu_stream_stop_duplex(struct snd_motu *motu)
{
        if (motu->substreams_counter == 0) {
                amdtp_domain_stop(&motu->domain);
                finish_session(motu);

                fw_iso_resources_free(&motu->tx_resources);
                fw_iso_resources_free(&motu->rx_resources);

                kfree(motu->cache.event_offsets);
                motu->cache.event_offsets = NULL;
        }
}

static int init_stream(struct snd_motu *motu, struct amdtp_stream *s)
{
        struct fw_iso_resources *resources;
        enum amdtp_stream_direction dir;
        int err;

        if (s == &motu->tx_stream) {
                resources = &motu->tx_resources;
                dir = AMDTP_IN_STREAM;
        } else {
                resources = &motu->rx_resources;
                dir = AMDTP_OUT_STREAM;
        }

        err = fw_iso_resources_init(resources, motu->unit);
        if (err < 0)
                return err;

        err = amdtp_motu_init(s, motu->unit, dir, motu->spec, &motu->cache);
        if (err < 0)
                fw_iso_resources_destroy(resources);

        return err;
}

static void destroy_stream(struct snd_motu *motu, struct amdtp_stream *s)
{
        amdtp_stream_destroy(s);

        if (s == &motu->tx_stream)
                fw_iso_resources_destroy(&motu->tx_resources);
        else
                fw_iso_resources_destroy(&motu->rx_resources);
}

int snd_motu_stream_init_duplex(struct snd_motu *motu)
{
        int err;

        err = init_stream(motu, &motu->tx_stream);
        if (err < 0)
                return err;

        err = init_stream(motu, &motu->rx_stream);
        if (err < 0) {
                destroy_stream(motu, &motu->tx_stream);
                return err;
        }

        err = amdtp_domain_init(&motu->domain);
        if (err < 0) {
                destroy_stream(motu, &motu->tx_stream);
                destroy_stream(motu, &motu->rx_stream);
        }

        return err;
}

// This function should be called before starting streams or after stopping
// streams.
void snd_motu_stream_destroy_duplex(struct snd_motu *motu)
{
        amdtp_domain_destroy(&motu->domain);

        destroy_stream(motu, &motu->rx_stream);
        destroy_stream(motu, &motu->tx_stream);

        motu->substreams_counter = 0;
}

static void motu_lock_changed(struct snd_motu *motu)
{
        motu->dev_lock_changed = true;
        wake_up(&motu->hwdep_wait);
}

int snd_motu_stream_lock_try(struct snd_motu *motu)
{
        guard(spinlock_irq)(&motu->lock);

        if (motu->dev_lock_count < 0)
                return -EBUSY;

        if (motu->dev_lock_count++ == 0)
                motu_lock_changed(motu);
        return 0;
}

void snd_motu_stream_lock_release(struct snd_motu *motu)
{
        guard(spinlock_irq)(&motu->lock);

        if (WARN_ON(motu->dev_lock_count <= 0))
                return;

        if (--motu->dev_lock_count == 0)
                motu_lock_changed(motu);
}