root/sound/firewire/dice/dice-extension.c
// SPDX-License-Identifier: GPL-2.0
/*
 * dice-extension.c - a part of driver for DICE based devices
 *
 * Copyright (c) 2018 Takashi Sakamoto
 */

#include "dice.h"

/* For TCD2210/2220, TCAT defines extension of application protocol. */

#define DICE_EXT_APP_SPACE              0xffffe0200000uLL

#define DICE_EXT_APP_CAPS_OFFSET        0x00
#define DICE_EXT_APP_CAPS_SIZE          0x04
#define DICE_EXT_APP_CMD_OFFSET         0x08
#define DICE_EXT_APP_CMD_SIZE           0x0c
#define DICE_EXT_APP_MIXER_OFFSET       0x10
#define DICE_EXT_APP_MIXER_SIZE         0x14
#define DICE_EXT_APP_PEAK_OFFSET        0x18
#define DICE_EXT_APP_PEAK_SIZE          0x1c
#define DICE_EXT_APP_ROUTER_OFFSET      0x20
#define DICE_EXT_APP_ROUTER_SIZE        0x24
#define DICE_EXT_APP_STREAM_OFFSET      0x28
#define DICE_EXT_APP_STREAM_SIZE        0x2c
#define DICE_EXT_APP_CURRENT_OFFSET     0x30
#define DICE_EXT_APP_CURRENT_SIZE       0x34
#define DICE_EXT_APP_STANDALONE_OFFSET  0x38
#define DICE_EXT_APP_STANDALONE_SIZE    0x3c
#define DICE_EXT_APP_APPLICATION_OFFSET 0x40
#define DICE_EXT_APP_APPLICATION_SIZE   0x44

#define EXT_APP_STREAM_TX_NUMBER        0x0000
#define EXT_APP_STREAM_RX_NUMBER        0x0004
#define EXT_APP_STREAM_ENTRIES          0x0008
#define EXT_APP_STREAM_ENTRY_SIZE       0x010c
#define  EXT_APP_NUMBER_AUDIO           0x0000
#define  EXT_APP_NUMBER_MIDI            0x0004
#define  EXT_APP_NAMES                  0x0008
#define   EXT_APP_NAMES_SIZE            256
#define  EXT_APP_AC3                    0x0108

#define EXT_APP_CONFIG_LOW_ROUTER       0x0000
#define EXT_APP_CONFIG_LOW_STREAM       0x1000
#define EXT_APP_CONFIG_MIDDLE_ROUTER    0x2000
#define EXT_APP_CONFIG_MIDDLE_STREAM    0x3000
#define EXT_APP_CONFIG_HIGH_ROUTER      0x4000
#define EXT_APP_CONFIG_HIGH_STREAM      0x5000

static inline int read_transaction(struct snd_dice *dice, u64 section_addr,
                                   u32 offset, void *buf, size_t len)
{
        return snd_fw_transaction(dice->unit,
                                  len == 4 ? TCODE_READ_QUADLET_REQUEST :
                                             TCODE_READ_BLOCK_REQUEST,
                                  section_addr + offset, buf, len, 0);
}

static int read_stream_entries(struct snd_dice *dice, u64 section_addr,
                               u32 base_offset, unsigned int stream_count,
                               unsigned int mode,
                               unsigned int pcm_channels[MAX_STREAMS][3],
                               unsigned int midi_ports[MAX_STREAMS])
{
        u32 entry_offset;
        __be32 reg[2];
        int err;
        int i;

        for (i = 0; i < stream_count; ++i) {
                entry_offset = base_offset + i * EXT_APP_STREAM_ENTRY_SIZE;
                err = read_transaction(dice, section_addr,
                                    entry_offset + EXT_APP_NUMBER_AUDIO,
                                    reg, sizeof(reg));
                if (err < 0)
                        return err;
                pcm_channels[i][mode] = be32_to_cpu(reg[0]);
                midi_ports[i] = max(midi_ports[i], be32_to_cpu(reg[1]));
        }

        return 0;
}

static int detect_stream_formats(struct snd_dice *dice, u64 section_addr)
{
        u32 base_offset;
        __be32 reg[2];
        unsigned int stream_count;
        int mode;
        int err = 0;

        for (mode = 0; mode < SND_DICE_RATE_MODE_COUNT; ++mode) {
                unsigned int cap;

                /*
                 * Some models report stream formats at highest mode, however
                 * they don't support the mode. Check clock capabilities.
                 */
                if (mode == 2) {
                        cap = CLOCK_CAP_RATE_176400 | CLOCK_CAP_RATE_192000;
                } else if (mode == 1) {
                        cap = CLOCK_CAP_RATE_88200 | CLOCK_CAP_RATE_96000;
                } else {
                        cap = CLOCK_CAP_RATE_32000 | CLOCK_CAP_RATE_44100 |
                              CLOCK_CAP_RATE_48000;
                }
                if (!(cap & dice->clock_caps))
                        continue;

                base_offset = 0x2000 * mode + 0x1000;

                err = read_transaction(dice, section_addr,
                                       base_offset + EXT_APP_STREAM_TX_NUMBER,
                                       &reg, sizeof(reg));
                if (err < 0)
                        break;

                base_offset += EXT_APP_STREAM_ENTRIES;
                stream_count = min_t(unsigned int, be32_to_cpu(reg[0]), MAX_STREAMS);
                err = read_stream_entries(dice, section_addr, base_offset,
                                          stream_count, mode,
                                          dice->tx_pcm_chs,
                                          dice->tx_midi_ports);
                if (err < 0)
                        break;

                base_offset += stream_count * EXT_APP_STREAM_ENTRY_SIZE;
                stream_count = min_t(unsigned int, be32_to_cpu(reg[1]), MAX_STREAMS);
                err = read_stream_entries(dice, section_addr, base_offset,
                                          stream_count,
                                          mode, dice->rx_pcm_chs,
                                          dice->rx_midi_ports);
                if (err < 0)
                        break;
        }

        return err;
}

int snd_dice_detect_extension_formats(struct snd_dice *dice)
{
        __be32 *pointers;
        unsigned int i;
        u64 section_addr;
        int err;

        pointers = kmalloc_array(9, sizeof(__be32) * 2, GFP_KERNEL);
        if (pointers == NULL)
                return -ENOMEM;

        err = snd_fw_transaction(dice->unit, TCODE_READ_BLOCK_REQUEST,
                                 DICE_EXT_APP_SPACE, pointers,
                                 9 * sizeof(__be32) * 2, 0);
        if (err < 0)
                goto end;

        /* Check two of them for offset have the same value or not. */
        for (i = 0; i < 9; ++i) {
                int j;

                for (j = i + 1; j < 9; ++j) {
                        if (pointers[i * 2] == pointers[j * 2]) {
                                // Fallback to limited functionality.
                                err = -ENXIO;
                                goto end;
                        }
                }
        }

        section_addr = DICE_EXT_APP_SPACE + be32_to_cpu(pointers[12]) * 4;
        err = detect_stream_formats(dice, section_addr);
end:
        kfree(pointers);
        return err;
}