| // 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, | |
| ®, sizeof(reg)); | |
| if (err < 0) | |
| break; | |
| base_offset += EXT_APP_STREAM_ENTRIES; | |
| stream_count = be32_to_cpu(reg[0]); | |
| 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 = be32_to_cpu(reg[1]); | |
| 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; | |
| } |