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