1// SPDX-License-Identifier: GPL-2.0-only
2/*
3 * dice_proc.c - a part of driver for Dice based devices
4 *
5 * Copyright (c) Clemens Ladisch
6 * Copyright (c) 2014 Takashi Sakamoto
7 */
8
9#include "dice.h"
10
11static int dice_proc_read_mem(struct snd_dice *dice, void *buffer,
12			      unsigned int offset_q, unsigned int quadlets)
13{
14	unsigned int i;
15	int err;
16
17	err = snd_fw_transaction(dice->unit, TCODE_READ_BLOCK_REQUEST,
18				 DICE_PRIVATE_SPACE + 4 * offset_q,
19				 buffer, 4 * quadlets, 0);
20	if (err < 0)
21		return err;
22
23	for (i = 0; i < quadlets; ++i)
24		be32_to_cpus(&((u32 *)buffer)[i]);
25
26	return 0;
27}
28
29static const char *str_from_array(const char *const strs[], unsigned int count,
30				  unsigned int i)
31{
32	if (i < count)
33		return strs[i];
34
35	return "(unknown)";
36}
37
38static void dice_proc_fixup_string(char *s, unsigned int size)
39{
40	unsigned int i;
41
42	for (i = 0; i < size; i += 4)
43		cpu_to_le32s((u32 *)(s + i));
44
45	for (i = 0; i < size - 2; ++i) {
46		if (s[i] == '\0')
47			return;
48		if (s[i] == '\\' && s[i + 1] == '\\') {
49			s[i + 2] = '\0';
50			return;
51		}
52	}
53	s[size - 1] = '\0';
54}
55
56static void dice_proc_read(struct snd_info_entry *entry,
57			   struct snd_info_buffer *buffer)
58{
59	static const char *const section_names[5] = {
60		"global", "tx", "rx", "ext_sync", "unused2"
61	};
62	static const char *const clock_sources[] = {
63		"aes1", "aes2", "aes3", "aes4", "aes", "adat", "tdif",
64		"wc", "arx1", "arx2", "arx3", "arx4", "internal"
65	};
66	static const char *const rates[] = {
67		"32000", "44100", "48000", "88200", "96000", "176400", "192000",
68		"any low", "any mid", "any high", "none"
69	};
70	struct snd_dice *dice = entry->private_data;
71	u32 sections[ARRAY_SIZE(section_names) * 2];
72	struct {
73		u32 number;
74		u32 size;
75	} tx_rx_header;
76	union {
77		struct {
78			u32 owner_hi, owner_lo;
79			u32 notification;
80			char nick_name[NICK_NAME_SIZE];
81			u32 clock_select;
82			u32 enable;
83			u32 status;
84			u32 extended_status;
85			u32 sample_rate;
86			u32 version;
87			u32 clock_caps;
88			char clock_source_names[CLOCK_SOURCE_NAMES_SIZE];
89		} global;
90		struct {
91			u32 iso;
92			u32 number_audio;
93			u32 number_midi;
94			u32 speed;
95			char names[TX_NAMES_SIZE];
96			u32 ac3_caps;
97			u32 ac3_enable;
98		} tx;
99		struct {
100			u32 iso;
101			u32 seq_start;
102			u32 number_audio;
103			u32 number_midi;
104			char names[RX_NAMES_SIZE];
105			u32 ac3_caps;
106			u32 ac3_enable;
107		} rx;
108		struct {
109			u32 clock_source;
110			u32 locked;
111			u32 rate;
112			u32 adat_user_data;
113		} ext_sync;
114	} buf;
115	unsigned int quadlets, stream, i;
116
117	if (dice_proc_read_mem(dice, sections, 0, ARRAY_SIZE(sections)) < 0)
118		return;
119	snd_iprintf(buffer, "sections:\n");
120	for (i = 0; i < ARRAY_SIZE(section_names); ++i)
121		snd_iprintf(buffer, "  %s: offset %u, size %u\n",
122			    section_names[i],
123			    sections[i * 2], sections[i * 2 + 1]);
124
125	quadlets = min_t(u32, sections[1], sizeof(buf.global) / 4);
126	if (dice_proc_read_mem(dice, &buf.global, sections[0], quadlets) < 0)
127		return;
128	snd_iprintf(buffer, "global:\n");
129	snd_iprintf(buffer, "  owner: %04x:%04x%08x\n",
130		    buf.global.owner_hi >> 16,
131		    buf.global.owner_hi & 0xffff, buf.global.owner_lo);
132	snd_iprintf(buffer, "  notification: %08x\n", buf.global.notification);
133	dice_proc_fixup_string(buf.global.nick_name, NICK_NAME_SIZE);
134	snd_iprintf(buffer, "  nick name: %s\n", buf.global.nick_name);
135	snd_iprintf(buffer, "  clock select: %s %s\n",
136		    str_from_array(clock_sources, ARRAY_SIZE(clock_sources),
137				   buf.global.clock_select & CLOCK_SOURCE_MASK),
138		    str_from_array(rates, ARRAY_SIZE(rates),
139				   (buf.global.clock_select & CLOCK_RATE_MASK)
140				   >> CLOCK_RATE_SHIFT));
141	snd_iprintf(buffer, "  enable: %u\n", buf.global.enable);
142	snd_iprintf(buffer, "  status: %slocked %s\n",
143		    buf.global.status & STATUS_SOURCE_LOCKED ? "" : "un",
144		    str_from_array(rates, ARRAY_SIZE(rates),
145				   (buf.global.status &
146				    STATUS_NOMINAL_RATE_MASK)
147				   >> CLOCK_RATE_SHIFT));
148	snd_iprintf(buffer, "  ext status: %08x\n", buf.global.extended_status);
149	snd_iprintf(buffer, "  sample rate: %u\n", buf.global.sample_rate);
150	if (quadlets >= 90) {
151		snd_iprintf(buffer, "  version: %u.%u.%u.%u\n",
152			    (buf.global.version >> 24) & 0xff,
153			    (buf.global.version >> 16) & 0xff,
154			    (buf.global.version >>  8) & 0xff,
155			    (buf.global.version >>  0) & 0xff);
156		snd_iprintf(buffer, "  clock caps:");
157		for (i = 0; i <= 6; ++i)
158			if (buf.global.clock_caps & (1 << i))
159				snd_iprintf(buffer, " %s", rates[i]);
160		for (i = 0; i <= 12; ++i)
161			if (buf.global.clock_caps & (1 << (16 + i)))
162				snd_iprintf(buffer, " %s", clock_sources[i]);
163		snd_iprintf(buffer, "\n");
164		dice_proc_fixup_string(buf.global.clock_source_names,
165				       CLOCK_SOURCE_NAMES_SIZE);
166		snd_iprintf(buffer, "  clock source names: %s\n",
167			    buf.global.clock_source_names);
168	}
169
170	if (dice_proc_read_mem(dice, &tx_rx_header, sections[2], 2) < 0)
171		return;
172	quadlets = min_t(u32, tx_rx_header.size, sizeof(buf.tx) / 4);
173	for (stream = 0; stream < tx_rx_header.number; ++stream) {
174		if (dice_proc_read_mem(dice, &buf.tx, sections[2] + 2 +
175				       stream * tx_rx_header.size,
176				       quadlets) < 0)
177			break;
178		snd_iprintf(buffer, "tx %u:\n", stream);
179		snd_iprintf(buffer, "  iso channel: %d\n", (int)buf.tx.iso);
180		snd_iprintf(buffer, "  audio channels: %u\n",
181			    buf.tx.number_audio);
182		snd_iprintf(buffer, "  midi ports: %u\n", buf.tx.number_midi);
183		snd_iprintf(buffer, "  speed: S%u\n", 100u << buf.tx.speed);
184		if (quadlets >= 68) {
185			dice_proc_fixup_string(buf.tx.names, TX_NAMES_SIZE);
186			snd_iprintf(buffer, "  names: %s\n", buf.tx.names);
187		}
188		if (quadlets >= 70) {
189			snd_iprintf(buffer, "  ac3 caps: %08x\n",
190				    buf.tx.ac3_caps);
191			snd_iprintf(buffer, "  ac3 enable: %08x\n",
192				    buf.tx.ac3_enable);
193		}
194	}
195
196	if (dice_proc_read_mem(dice, &tx_rx_header, sections[4], 2) < 0)
197		return;
198	quadlets = min_t(u32, tx_rx_header.size, sizeof(buf.rx) / 4);
199	for (stream = 0; stream < tx_rx_header.number; ++stream) {
200		if (dice_proc_read_mem(dice, &buf.rx, sections[4] + 2 +
201				       stream * tx_rx_header.size,
202				       quadlets) < 0)
203			break;
204		snd_iprintf(buffer, "rx %u:\n", stream);
205		snd_iprintf(buffer, "  iso channel: %d\n", (int)buf.rx.iso);
206		snd_iprintf(buffer, "  sequence start: %u\n", buf.rx.seq_start);
207		snd_iprintf(buffer, "  audio channels: %u\n",
208			    buf.rx.number_audio);
209		snd_iprintf(buffer, "  midi ports: %u\n", buf.rx.number_midi);
210		if (quadlets >= 68) {
211			dice_proc_fixup_string(buf.rx.names, RX_NAMES_SIZE);
212			snd_iprintf(buffer, "  names: %s\n", buf.rx.names);
213		}
214		if (quadlets >= 70) {
215			snd_iprintf(buffer, "  ac3 caps: %08x\n",
216				    buf.rx.ac3_caps);
217			snd_iprintf(buffer, "  ac3 enable: %08x\n",
218				    buf.rx.ac3_enable);
219		}
220	}
221
222	quadlets = min_t(u32, sections[7], sizeof(buf.ext_sync) / 4);
223	if (quadlets >= 4) {
224		if (dice_proc_read_mem(dice, &buf.ext_sync,
225				       sections[6], 4) < 0)
226			return;
227		snd_iprintf(buffer, "ext status:\n");
228		snd_iprintf(buffer, "  clock source: %s\n",
229			    str_from_array(clock_sources,
230					   ARRAY_SIZE(clock_sources),
231					   buf.ext_sync.clock_source));
232		snd_iprintf(buffer, "  locked: %u\n", buf.ext_sync.locked);
233		snd_iprintf(buffer, "  rate: %s\n",
234			    str_from_array(rates, ARRAY_SIZE(rates),
235					   buf.ext_sync.rate));
236		snd_iprintf(buffer, "  adat user data: ");
237		if (buf.ext_sync.adat_user_data & ADAT_USER_DATA_NO_DATA)
238			snd_iprintf(buffer, "-\n");
239		else
240			snd_iprintf(buffer, "%x\n",
241				    buf.ext_sync.adat_user_data);
242	}
243}
244
245static void dice_proc_read_formation(struct snd_info_entry *entry,
246				     struct snd_info_buffer *buffer)
247{
248	static const char *const rate_labels[] = {
249		[SND_DICE_RATE_MODE_LOW]	= "low",
250		[SND_DICE_RATE_MODE_MIDDLE]	= "middle",
251		[SND_DICE_RATE_MODE_HIGH]	= "high",
252	};
253	struct snd_dice *dice = entry->private_data;
254	int i, j;
255
256	snd_iprintf(buffer, "Output stream from unit:\n");
257	for (i = 0; i < SND_DICE_RATE_MODE_COUNT; ++i)
258		snd_iprintf(buffer, "\t%s", rate_labels[i]);
259	snd_iprintf(buffer, "\tMIDI\n");
260	for (i = 0; i < MAX_STREAMS; ++i) {
261		snd_iprintf(buffer, "Tx %u:", i);
262		for (j = 0; j < SND_DICE_RATE_MODE_COUNT; ++j)
263			snd_iprintf(buffer, "\t%u", dice->tx_pcm_chs[i][j]);
264		snd_iprintf(buffer, "\t%u\n", dice->tx_midi_ports[i]);
265	}
266
267	snd_iprintf(buffer, "Input stream to unit:\n");
268	for (i = 0; i < SND_DICE_RATE_MODE_COUNT; ++i)
269		snd_iprintf(buffer, "\t%s", rate_labels[i]);
270	snd_iprintf(buffer, "\n");
271	for (i = 0; i < MAX_STREAMS; ++i) {
272		snd_iprintf(buffer, "Rx %u:", i);
273		for (j = 0; j < SND_DICE_RATE_MODE_COUNT; ++j)
274			snd_iprintf(buffer, "\t%u", dice->rx_pcm_chs[i][j]);
275		snd_iprintf(buffer, "\t%u\n", dice->rx_midi_ports[i]);
276	}
277}
278
279static void add_node(struct snd_dice *dice, struct snd_info_entry *root,
280		     const char *name,
281		     void (*op)(struct snd_info_entry *entry,
282				struct snd_info_buffer *buffer))
283{
284	struct snd_info_entry *entry;
285
286	entry = snd_info_create_card_entry(dice->card, name, root);
287	if (entry)
288		snd_info_set_text_ops(entry, dice, op);
289}
290
291void snd_dice_create_proc(struct snd_dice *dice)
292{
293	struct snd_info_entry *root;
294
295	/*
296	 * All nodes are automatically removed at snd_card_disconnect(),
297	 * by following to link list.
298	 */
299	root = snd_info_create_card_entry(dice->card, "firewire",
300					  dice->card->proc_root);
301	if (!root)
302		return;
303	root->mode = S_IFDIR | 0555;
304
305	add_node(dice, root, "dice", dice_proc_read);
306	add_node(dice, root, "formation", dice_proc_read_formation);
307}
308