162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * dice-extension.c - a part of driver for DICE based devices
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (c) 2018 Takashi Sakamoto
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#include "dice.h"
962306a36Sopenharmony_ci
1062306a36Sopenharmony_ci/* For TCD2210/2220, TCAT defines extension of application protocol. */
1162306a36Sopenharmony_ci
1262306a36Sopenharmony_ci#define DICE_EXT_APP_SPACE		0xffffe0200000uLL
1362306a36Sopenharmony_ci
1462306a36Sopenharmony_ci#define DICE_EXT_APP_CAPS_OFFSET	0x00
1562306a36Sopenharmony_ci#define DICE_EXT_APP_CAPS_SIZE		0x04
1662306a36Sopenharmony_ci#define DICE_EXT_APP_CMD_OFFSET		0x08
1762306a36Sopenharmony_ci#define DICE_EXT_APP_CMD_SIZE		0x0c
1862306a36Sopenharmony_ci#define DICE_EXT_APP_MIXER_OFFSET	0x10
1962306a36Sopenharmony_ci#define DICE_EXT_APP_MIXER_SIZE		0x14
2062306a36Sopenharmony_ci#define DICE_EXT_APP_PEAK_OFFSET	0x18
2162306a36Sopenharmony_ci#define DICE_EXT_APP_PEAK_SIZE		0x1c
2262306a36Sopenharmony_ci#define DICE_EXT_APP_ROUTER_OFFSET	0x20
2362306a36Sopenharmony_ci#define DICE_EXT_APP_ROUTER_SIZE	0x24
2462306a36Sopenharmony_ci#define DICE_EXT_APP_STREAM_OFFSET	0x28
2562306a36Sopenharmony_ci#define DICE_EXT_APP_STREAM_SIZE	0x2c
2662306a36Sopenharmony_ci#define DICE_EXT_APP_CURRENT_OFFSET	0x30
2762306a36Sopenharmony_ci#define DICE_EXT_APP_CURRENT_SIZE	0x34
2862306a36Sopenharmony_ci#define DICE_EXT_APP_STANDALONE_OFFSET	0x38
2962306a36Sopenharmony_ci#define DICE_EXT_APP_STANDALONE_SIZE	0x3c
3062306a36Sopenharmony_ci#define DICE_EXT_APP_APPLICATION_OFFSET	0x40
3162306a36Sopenharmony_ci#define DICE_EXT_APP_APPLICATION_SIZE	0x44
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_ci#define EXT_APP_STREAM_TX_NUMBER	0x0000
3462306a36Sopenharmony_ci#define EXT_APP_STREAM_RX_NUMBER	0x0004
3562306a36Sopenharmony_ci#define EXT_APP_STREAM_ENTRIES		0x0008
3662306a36Sopenharmony_ci#define EXT_APP_STREAM_ENTRY_SIZE	0x010c
3762306a36Sopenharmony_ci#define  EXT_APP_NUMBER_AUDIO		0x0000
3862306a36Sopenharmony_ci#define  EXT_APP_NUMBER_MIDI		0x0004
3962306a36Sopenharmony_ci#define  EXT_APP_NAMES			0x0008
4062306a36Sopenharmony_ci#define   EXT_APP_NAMES_SIZE		256
4162306a36Sopenharmony_ci#define  EXT_APP_AC3			0x0108
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_ci#define EXT_APP_CONFIG_LOW_ROUTER	0x0000
4462306a36Sopenharmony_ci#define EXT_APP_CONFIG_LOW_STREAM	0x1000
4562306a36Sopenharmony_ci#define EXT_APP_CONFIG_MIDDLE_ROUTER	0x2000
4662306a36Sopenharmony_ci#define EXT_APP_CONFIG_MIDDLE_STREAM	0x3000
4762306a36Sopenharmony_ci#define EXT_APP_CONFIG_HIGH_ROUTER	0x4000
4862306a36Sopenharmony_ci#define EXT_APP_CONFIG_HIGH_STREAM	0x5000
4962306a36Sopenharmony_ci
5062306a36Sopenharmony_cistatic inline int read_transaction(struct snd_dice *dice, u64 section_addr,
5162306a36Sopenharmony_ci				   u32 offset, void *buf, size_t len)
5262306a36Sopenharmony_ci{
5362306a36Sopenharmony_ci	return snd_fw_transaction(dice->unit,
5462306a36Sopenharmony_ci				  len == 4 ? TCODE_READ_QUADLET_REQUEST :
5562306a36Sopenharmony_ci					     TCODE_READ_BLOCK_REQUEST,
5662306a36Sopenharmony_ci				  section_addr + offset, buf, len, 0);
5762306a36Sopenharmony_ci}
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_cistatic int read_stream_entries(struct snd_dice *dice, u64 section_addr,
6062306a36Sopenharmony_ci			       u32 base_offset, unsigned int stream_count,
6162306a36Sopenharmony_ci			       unsigned int mode,
6262306a36Sopenharmony_ci			       unsigned int pcm_channels[MAX_STREAMS][3],
6362306a36Sopenharmony_ci			       unsigned int midi_ports[MAX_STREAMS])
6462306a36Sopenharmony_ci{
6562306a36Sopenharmony_ci	u32 entry_offset;
6662306a36Sopenharmony_ci	__be32 reg[2];
6762306a36Sopenharmony_ci	int err;
6862306a36Sopenharmony_ci	int i;
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_ci	for (i = 0; i < stream_count; ++i) {
7162306a36Sopenharmony_ci		entry_offset = base_offset + i * EXT_APP_STREAM_ENTRY_SIZE;
7262306a36Sopenharmony_ci		err = read_transaction(dice, section_addr,
7362306a36Sopenharmony_ci				    entry_offset + EXT_APP_NUMBER_AUDIO,
7462306a36Sopenharmony_ci				    reg, sizeof(reg));
7562306a36Sopenharmony_ci		if (err < 0)
7662306a36Sopenharmony_ci			return err;
7762306a36Sopenharmony_ci		pcm_channels[i][mode] = be32_to_cpu(reg[0]);
7862306a36Sopenharmony_ci		midi_ports[i] = max(midi_ports[i], be32_to_cpu(reg[1]));
7962306a36Sopenharmony_ci	}
8062306a36Sopenharmony_ci
8162306a36Sopenharmony_ci	return 0;
8262306a36Sopenharmony_ci}
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_cistatic int detect_stream_formats(struct snd_dice *dice, u64 section_addr)
8562306a36Sopenharmony_ci{
8662306a36Sopenharmony_ci	u32 base_offset;
8762306a36Sopenharmony_ci	__be32 reg[2];
8862306a36Sopenharmony_ci	unsigned int stream_count;
8962306a36Sopenharmony_ci	int mode;
9062306a36Sopenharmony_ci	int err = 0;
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_ci	for (mode = 0; mode < SND_DICE_RATE_MODE_COUNT; ++mode) {
9362306a36Sopenharmony_ci		unsigned int cap;
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_ci		/*
9662306a36Sopenharmony_ci		 * Some models report stream formats at highest mode, however
9762306a36Sopenharmony_ci		 * they don't support the mode. Check clock capabilities.
9862306a36Sopenharmony_ci		 */
9962306a36Sopenharmony_ci		if (mode == 2) {
10062306a36Sopenharmony_ci			cap = CLOCK_CAP_RATE_176400 | CLOCK_CAP_RATE_192000;
10162306a36Sopenharmony_ci		} else if (mode == 1) {
10262306a36Sopenharmony_ci			cap = CLOCK_CAP_RATE_88200 | CLOCK_CAP_RATE_96000;
10362306a36Sopenharmony_ci		} else {
10462306a36Sopenharmony_ci			cap = CLOCK_CAP_RATE_32000 | CLOCK_CAP_RATE_44100 |
10562306a36Sopenharmony_ci			      CLOCK_CAP_RATE_48000;
10662306a36Sopenharmony_ci		}
10762306a36Sopenharmony_ci		if (!(cap & dice->clock_caps))
10862306a36Sopenharmony_ci			continue;
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_ci		base_offset = 0x2000 * mode + 0x1000;
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_ci		err = read_transaction(dice, section_addr,
11362306a36Sopenharmony_ci				       base_offset + EXT_APP_STREAM_TX_NUMBER,
11462306a36Sopenharmony_ci				       &reg, sizeof(reg));
11562306a36Sopenharmony_ci		if (err < 0)
11662306a36Sopenharmony_ci			break;
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_ci		base_offset += EXT_APP_STREAM_ENTRIES;
11962306a36Sopenharmony_ci		stream_count = be32_to_cpu(reg[0]);
12062306a36Sopenharmony_ci		err = read_stream_entries(dice, section_addr, base_offset,
12162306a36Sopenharmony_ci					  stream_count, mode,
12262306a36Sopenharmony_ci					  dice->tx_pcm_chs,
12362306a36Sopenharmony_ci					  dice->tx_midi_ports);
12462306a36Sopenharmony_ci		if (err < 0)
12562306a36Sopenharmony_ci			break;
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_ci		base_offset += stream_count * EXT_APP_STREAM_ENTRY_SIZE;
12862306a36Sopenharmony_ci		stream_count = be32_to_cpu(reg[1]);
12962306a36Sopenharmony_ci		err = read_stream_entries(dice, section_addr, base_offset,
13062306a36Sopenharmony_ci					  stream_count,
13162306a36Sopenharmony_ci					  mode, dice->rx_pcm_chs,
13262306a36Sopenharmony_ci					  dice->rx_midi_ports);
13362306a36Sopenharmony_ci		if (err < 0)
13462306a36Sopenharmony_ci			break;
13562306a36Sopenharmony_ci	}
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_ci	return err;
13862306a36Sopenharmony_ci}
13962306a36Sopenharmony_ci
14062306a36Sopenharmony_ciint snd_dice_detect_extension_formats(struct snd_dice *dice)
14162306a36Sopenharmony_ci{
14262306a36Sopenharmony_ci	__be32 *pointers;
14362306a36Sopenharmony_ci	unsigned int i;
14462306a36Sopenharmony_ci	u64 section_addr;
14562306a36Sopenharmony_ci	int err;
14662306a36Sopenharmony_ci
14762306a36Sopenharmony_ci	pointers = kmalloc_array(9, sizeof(__be32) * 2, GFP_KERNEL);
14862306a36Sopenharmony_ci	if (pointers == NULL)
14962306a36Sopenharmony_ci		return -ENOMEM;
15062306a36Sopenharmony_ci
15162306a36Sopenharmony_ci	err = snd_fw_transaction(dice->unit, TCODE_READ_BLOCK_REQUEST,
15262306a36Sopenharmony_ci				 DICE_EXT_APP_SPACE, pointers,
15362306a36Sopenharmony_ci				 9 * sizeof(__be32) * 2, 0);
15462306a36Sopenharmony_ci	if (err < 0)
15562306a36Sopenharmony_ci		goto end;
15662306a36Sopenharmony_ci
15762306a36Sopenharmony_ci	/* Check two of them for offset have the same value or not. */
15862306a36Sopenharmony_ci	for (i = 0; i < 9; ++i) {
15962306a36Sopenharmony_ci		int j;
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_ci		for (j = i + 1; j < 9; ++j) {
16262306a36Sopenharmony_ci			if (pointers[i * 2] == pointers[j * 2]) {
16362306a36Sopenharmony_ci				// Fallback to limited functionality.
16462306a36Sopenharmony_ci				err = -ENXIO;
16562306a36Sopenharmony_ci				goto end;
16662306a36Sopenharmony_ci			}
16762306a36Sopenharmony_ci		}
16862306a36Sopenharmony_ci	}
16962306a36Sopenharmony_ci
17062306a36Sopenharmony_ci	section_addr = DICE_EXT_APP_SPACE + be32_to_cpu(pointers[12]) * 4;
17162306a36Sopenharmony_ci	err = detect_stream_formats(dice, section_addr);
17262306a36Sopenharmony_ciend:
17362306a36Sopenharmony_ci	kfree(pointers);
17462306a36Sopenharmony_ci	return err;
17562306a36Sopenharmony_ci}
176