| // 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; |
| } |