Commit | Line | Data |
---|---|---|
58579c05 TS |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
3 | * dice-extension.c - a part of driver for DICE based devices | |
4 | * | |
5 | * Copyright (c) 2018 Takashi Sakamoto | |
6 | */ | |
7 | ||
8 | #include "dice.h" | |
9 | ||
10 | /* For TCD2210/2220, TCAT defines extension of application protocol. */ | |
11 | ||
12 | #define DICE_EXT_APP_SPACE 0xffffe0200000uLL | |
13 | ||
14 | #define DICE_EXT_APP_CAPS_OFFSET 0x00 | |
15 | #define DICE_EXT_APP_CAPS_SIZE 0x04 | |
16 | #define DICE_EXT_APP_CMD_OFFSET 0x08 | |
17 | #define DICE_EXT_APP_CMD_SIZE 0x0c | |
18 | #define DICE_EXT_APP_MIXER_OFFSET 0x10 | |
19 | #define DICE_EXT_APP_MIXER_SIZE 0x14 | |
20 | #define DICE_EXT_APP_PEAK_OFFSET 0x18 | |
21 | #define DICE_EXT_APP_PEAK_SIZE 0x1c | |
22 | #define DICE_EXT_APP_ROUTER_OFFSET 0x20 | |
23 | #define DICE_EXT_APP_ROUTER_SIZE 0x24 | |
24 | #define DICE_EXT_APP_STREAM_OFFSET 0x28 | |
25 | #define DICE_EXT_APP_STREAM_SIZE 0x2c | |
26 | #define DICE_EXT_APP_CURRENT_OFFSET 0x30 | |
27 | #define DICE_EXT_APP_CURRENT_SIZE 0x34 | |
28 | #define DICE_EXT_APP_STANDALONE_OFFSET 0x38 | |
29 | #define DICE_EXT_APP_STANDALONE_SIZE 0x3c | |
30 | #define DICE_EXT_APP_APPLICATION_OFFSET 0x40 | |
31 | #define DICE_EXT_APP_APPLICATION_SIZE 0x44 | |
32 | ||
33 | #define EXT_APP_STREAM_TX_NUMBER 0x0000 | |
34 | #define EXT_APP_STREAM_RX_NUMBER 0x0004 | |
35 | #define EXT_APP_STREAM_ENTRIES 0x0008 | |
36 | #define EXT_APP_STREAM_ENTRY_SIZE 0x010c | |
37 | #define EXT_APP_NUMBER_AUDIO 0x0000 | |
38 | #define EXT_APP_NUMBER_MIDI 0x0004 | |
39 | #define EXT_APP_NAMES 0x0008 | |
40 | #define EXT_APP_NAMES_SIZE 256 | |
41 | #define EXT_APP_AC3 0x0108 | |
42 | ||
43 | #define EXT_APP_CONFIG_LOW_ROUTER 0x0000 | |
44 | #define EXT_APP_CONFIG_LOW_STREAM 0x1000 | |
45 | #define EXT_APP_CONFIG_MIDDLE_ROUTER 0x2000 | |
46 | #define EXT_APP_CONFIG_MIDDLE_STREAM 0x3000 | |
47 | #define EXT_APP_CONFIG_HIGH_ROUTER 0x4000 | |
48 | #define EXT_APP_CONFIG_HIGH_STREAM 0x5000 | |
49 | ||
50 | static inline int read_transaction(struct snd_dice *dice, u64 section_addr, | |
51 | u32 offset, void *buf, size_t len) | |
52 | { | |
53 | return snd_fw_transaction(dice->unit, | |
54 | len == 4 ? TCODE_READ_QUADLET_REQUEST : | |
55 | TCODE_READ_BLOCK_REQUEST, | |
56 | section_addr + offset, buf, len, 0); | |
57 | } | |
58 | ||
59 | static int read_stream_entries(struct snd_dice *dice, u64 section_addr, | |
60 | u32 base_offset, unsigned int stream_count, | |
61 | unsigned int mode, | |
62 | unsigned int pcm_channels[MAX_STREAMS][3], | |
63 | unsigned int midi_ports[MAX_STREAMS]) | |
64 | { | |
65 | u32 entry_offset; | |
66 | __be32 reg[2]; | |
67 | int err; | |
68 | int i; | |
69 | ||
70 | for (i = 0; i < stream_count; ++i) { | |
71 | entry_offset = base_offset + i * EXT_APP_STREAM_ENTRY_SIZE; | |
72 | err = read_transaction(dice, section_addr, | |
73 | entry_offset + EXT_APP_NUMBER_AUDIO, | |
74 | reg, sizeof(reg)); | |
75 | if (err < 0) | |
76 | return err; | |
77 | pcm_channels[i][mode] = be32_to_cpu(reg[0]); | |
78 | midi_ports[i] = max(midi_ports[i], be32_to_cpu(reg[1])); | |
79 | } | |
80 | ||
81 | return 0; | |
82 | } | |
83 | ||
84 | static int detect_stream_formats(struct snd_dice *dice, u64 section_addr) | |
85 | { | |
86 | u32 base_offset; | |
87 | __be32 reg[2]; | |
88 | unsigned int stream_count; | |
89 | int mode; | |
90 | int err = 0; | |
91 | ||
92 | for (mode = 0; mode < SND_DICE_RATE_MODE_COUNT; ++mode) { | |
93 | unsigned int cap; | |
94 | ||
95 | /* | |
96 | * Some models report stream formats at highest mode, however | |
97 | * they don't support the mode. Check clock capabilities. | |
98 | */ | |
99 | if (mode == 2) { | |
100 | cap = CLOCK_CAP_RATE_176400 | CLOCK_CAP_RATE_192000; | |
101 | } else if (mode == 1) { | |
102 | cap = CLOCK_CAP_RATE_88200 | CLOCK_CAP_RATE_96000; | |
103 | } else { | |
104 | cap = CLOCK_CAP_RATE_32000 | CLOCK_CAP_RATE_44100 | | |
105 | CLOCK_CAP_RATE_48000; | |
106 | } | |
107 | if (!(cap & dice->clock_caps)) | |
108 | continue; | |
109 | ||
110 | base_offset = 0x2000 * mode + 0x1000; | |
111 | ||
112 | err = read_transaction(dice, section_addr, | |
113 | base_offset + EXT_APP_STREAM_TX_NUMBER, | |
114 | ®, sizeof(reg)); | |
115 | if (err < 0) | |
116 | break; | |
117 | ||
118 | base_offset += EXT_APP_STREAM_ENTRIES; | |
119 | stream_count = be32_to_cpu(reg[0]); | |
120 | err = read_stream_entries(dice, section_addr, base_offset, | |
121 | stream_count, mode, | |
122 | dice->tx_pcm_chs, | |
123 | dice->tx_midi_ports); | |
124 | if (err < 0) | |
125 | break; | |
126 | ||
127 | base_offset += stream_count * EXT_APP_STREAM_ENTRY_SIZE; | |
128 | stream_count = be32_to_cpu(reg[1]); | |
129 | err = read_stream_entries(dice, section_addr, base_offset, | |
130 | stream_count, | |
131 | mode, dice->rx_pcm_chs, | |
132 | dice->rx_midi_ports); | |
133 | if (err < 0) | |
134 | break; | |
135 | } | |
136 | ||
137 | return err; | |
138 | } | |
139 | ||
140 | int snd_dice_detect_extension_formats(struct snd_dice *dice) | |
141 | { | |
142 | __be32 *pointers; | |
143 | unsigned int i; | |
144 | u64 section_addr; | |
145 | int err; | |
146 | ||
147 | pointers = kmalloc_array(9, sizeof(__be32) * 2, GFP_KERNEL); | |
148 | if (pointers == NULL) | |
149 | return -ENOMEM; | |
150 | ||
151 | err = snd_fw_transaction(dice->unit, TCODE_READ_BLOCK_REQUEST, | |
152 | DICE_EXT_APP_SPACE, pointers, | |
153 | 9 * sizeof(__be32) * 2, 0); | |
154 | if (err < 0) | |
155 | goto end; | |
156 | ||
157 | /* Check two of them for offset have the same value or not. */ | |
158 | for (i = 0; i < 9; ++i) { | |
159 | int j; | |
160 | ||
161 | for (j = i + 1; j < 9; ++j) { | |
3e2dc6bd TS |
162 | if (pointers[i * 2] == pointers[j * 2]) { |
163 | // Fallback to limited functionality. | |
164 | err = -ENXIO; | |
58579c05 | 165 | goto end; |
3e2dc6bd | 166 | } |
58579c05 TS |
167 | } |
168 | } | |
169 | ||
170 | section_addr = DICE_EXT_APP_SPACE + be32_to_cpu(pointers[12]) * 4; | |
171 | err = detect_stream_formats(dice, section_addr); | |
172 | end: | |
173 | kfree(pointers); | |
174 | return err; | |
175 | } |