162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * dice_transaction.c - a part of driver for Dice based devices
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (c) Clemens Ladisch
662306a36Sopenharmony_ci * Copyright (c) 2014 Takashi Sakamoto
762306a36Sopenharmony_ci */
862306a36Sopenharmony_ci
962306a36Sopenharmony_ci#include "dice.h"
1062306a36Sopenharmony_ci
1162306a36Sopenharmony_cistatic u64 get_subaddr(struct snd_dice *dice, enum snd_dice_addr_type type,
1262306a36Sopenharmony_ci		       u64 offset)
1362306a36Sopenharmony_ci{
1462306a36Sopenharmony_ci	switch (type) {
1562306a36Sopenharmony_ci	case SND_DICE_ADDR_TYPE_TX:
1662306a36Sopenharmony_ci		offset += dice->tx_offset;
1762306a36Sopenharmony_ci		break;
1862306a36Sopenharmony_ci	case SND_DICE_ADDR_TYPE_RX:
1962306a36Sopenharmony_ci		offset += dice->rx_offset;
2062306a36Sopenharmony_ci		break;
2162306a36Sopenharmony_ci	case SND_DICE_ADDR_TYPE_SYNC:
2262306a36Sopenharmony_ci		offset += dice->sync_offset;
2362306a36Sopenharmony_ci		break;
2462306a36Sopenharmony_ci	case SND_DICE_ADDR_TYPE_RSRV:
2562306a36Sopenharmony_ci		offset += dice->rsrv_offset;
2662306a36Sopenharmony_ci		break;
2762306a36Sopenharmony_ci	case SND_DICE_ADDR_TYPE_GLOBAL:
2862306a36Sopenharmony_ci	default:
2962306a36Sopenharmony_ci		offset += dice->global_offset;
3062306a36Sopenharmony_ci		break;
3162306a36Sopenharmony_ci	}
3262306a36Sopenharmony_ci	offset += DICE_PRIVATE_SPACE;
3362306a36Sopenharmony_ci	return offset;
3462306a36Sopenharmony_ci}
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_ciint snd_dice_transaction_write(struct snd_dice *dice,
3762306a36Sopenharmony_ci			       enum snd_dice_addr_type type,
3862306a36Sopenharmony_ci			       unsigned int offset, void *buf, unsigned int len)
3962306a36Sopenharmony_ci{
4062306a36Sopenharmony_ci	return snd_fw_transaction(dice->unit,
4162306a36Sopenharmony_ci				  (len == 4) ? TCODE_WRITE_QUADLET_REQUEST :
4262306a36Sopenharmony_ci					       TCODE_WRITE_BLOCK_REQUEST,
4362306a36Sopenharmony_ci				  get_subaddr(dice, type, offset), buf, len, 0);
4462306a36Sopenharmony_ci}
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_ciint snd_dice_transaction_read(struct snd_dice *dice,
4762306a36Sopenharmony_ci			      enum snd_dice_addr_type type, unsigned int offset,
4862306a36Sopenharmony_ci			      void *buf, unsigned int len)
4962306a36Sopenharmony_ci{
5062306a36Sopenharmony_ci	return snd_fw_transaction(dice->unit,
5162306a36Sopenharmony_ci				  (len == 4) ? TCODE_READ_QUADLET_REQUEST :
5262306a36Sopenharmony_ci					       TCODE_READ_BLOCK_REQUEST,
5362306a36Sopenharmony_ci				  get_subaddr(dice, type, offset), buf, len, 0);
5462306a36Sopenharmony_ci}
5562306a36Sopenharmony_ci
5662306a36Sopenharmony_cistatic unsigned int get_clock_info(struct snd_dice *dice, __be32 *info)
5762306a36Sopenharmony_ci{
5862306a36Sopenharmony_ci	return snd_dice_transaction_read_global(dice, GLOBAL_CLOCK_SELECT,
5962306a36Sopenharmony_ci						info, 4);
6062306a36Sopenharmony_ci}
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_ciint snd_dice_transaction_get_clock_source(struct snd_dice *dice,
6362306a36Sopenharmony_ci					  unsigned int *source)
6462306a36Sopenharmony_ci{
6562306a36Sopenharmony_ci	__be32 info;
6662306a36Sopenharmony_ci	int err;
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_ci	err = get_clock_info(dice, &info);
6962306a36Sopenharmony_ci	if (err >= 0)
7062306a36Sopenharmony_ci		*source = be32_to_cpu(info) & CLOCK_SOURCE_MASK;
7162306a36Sopenharmony_ci
7262306a36Sopenharmony_ci	return err;
7362306a36Sopenharmony_ci}
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_ciint snd_dice_transaction_get_rate(struct snd_dice *dice, unsigned int *rate)
7662306a36Sopenharmony_ci{
7762306a36Sopenharmony_ci	__be32 info;
7862306a36Sopenharmony_ci	unsigned int index;
7962306a36Sopenharmony_ci	int err;
8062306a36Sopenharmony_ci
8162306a36Sopenharmony_ci	err = get_clock_info(dice, &info);
8262306a36Sopenharmony_ci	if (err < 0)
8362306a36Sopenharmony_ci		goto end;
8462306a36Sopenharmony_ci
8562306a36Sopenharmony_ci	index = (be32_to_cpu(info) & CLOCK_RATE_MASK) >> CLOCK_RATE_SHIFT;
8662306a36Sopenharmony_ci	if (index >= SND_DICE_RATES_COUNT) {
8762306a36Sopenharmony_ci		err = -ENOSYS;
8862306a36Sopenharmony_ci		goto end;
8962306a36Sopenharmony_ci	}
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_ci	*rate = snd_dice_rates[index];
9262306a36Sopenharmony_ciend:
9362306a36Sopenharmony_ci	return err;
9462306a36Sopenharmony_ci}
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_ciint snd_dice_transaction_set_enable(struct snd_dice *dice)
9762306a36Sopenharmony_ci{
9862306a36Sopenharmony_ci	__be32 value;
9962306a36Sopenharmony_ci	int err = 0;
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_ci	if (dice->global_enabled)
10262306a36Sopenharmony_ci		goto end;
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_ci	value = cpu_to_be32(1);
10562306a36Sopenharmony_ci	err = snd_fw_transaction(dice->unit, TCODE_WRITE_QUADLET_REQUEST,
10662306a36Sopenharmony_ci				 get_subaddr(dice, SND_DICE_ADDR_TYPE_GLOBAL,
10762306a36Sopenharmony_ci					     GLOBAL_ENABLE),
10862306a36Sopenharmony_ci				 &value, 4,
10962306a36Sopenharmony_ci				 FW_FIXED_GENERATION | dice->owner_generation);
11062306a36Sopenharmony_ci	if (err < 0)
11162306a36Sopenharmony_ci		goto end;
11262306a36Sopenharmony_ci
11362306a36Sopenharmony_ci	dice->global_enabled = true;
11462306a36Sopenharmony_ciend:
11562306a36Sopenharmony_ci	return err;
11662306a36Sopenharmony_ci}
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_civoid snd_dice_transaction_clear_enable(struct snd_dice *dice)
11962306a36Sopenharmony_ci{
12062306a36Sopenharmony_ci	__be32 value;
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_ci	value = 0;
12362306a36Sopenharmony_ci	snd_fw_transaction(dice->unit, TCODE_WRITE_QUADLET_REQUEST,
12462306a36Sopenharmony_ci			   get_subaddr(dice, SND_DICE_ADDR_TYPE_GLOBAL,
12562306a36Sopenharmony_ci				       GLOBAL_ENABLE),
12662306a36Sopenharmony_ci			   &value, 4, FW_QUIET |
12762306a36Sopenharmony_ci			   FW_FIXED_GENERATION | dice->owner_generation);
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_ci	dice->global_enabled = false;
13062306a36Sopenharmony_ci}
13162306a36Sopenharmony_ci
13262306a36Sopenharmony_cistatic void dice_notification(struct fw_card *card, struct fw_request *request,
13362306a36Sopenharmony_ci			      int tcode, int destination, int source,
13462306a36Sopenharmony_ci			      int generation, unsigned long long offset,
13562306a36Sopenharmony_ci			      void *data, size_t length, void *callback_data)
13662306a36Sopenharmony_ci{
13762306a36Sopenharmony_ci	struct snd_dice *dice = callback_data;
13862306a36Sopenharmony_ci	u32 bits;
13962306a36Sopenharmony_ci	unsigned long flags;
14062306a36Sopenharmony_ci
14162306a36Sopenharmony_ci	if (tcode != TCODE_WRITE_QUADLET_REQUEST) {
14262306a36Sopenharmony_ci		fw_send_response(card, request, RCODE_TYPE_ERROR);
14362306a36Sopenharmony_ci		return;
14462306a36Sopenharmony_ci	}
14562306a36Sopenharmony_ci	if ((offset & 3) != 0) {
14662306a36Sopenharmony_ci		fw_send_response(card, request, RCODE_ADDRESS_ERROR);
14762306a36Sopenharmony_ci		return;
14862306a36Sopenharmony_ci	}
14962306a36Sopenharmony_ci
15062306a36Sopenharmony_ci	bits = be32_to_cpup(data);
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_ci	spin_lock_irqsave(&dice->lock, flags);
15362306a36Sopenharmony_ci	dice->notification_bits |= bits;
15462306a36Sopenharmony_ci	spin_unlock_irqrestore(&dice->lock, flags);
15562306a36Sopenharmony_ci
15662306a36Sopenharmony_ci	fw_send_response(card, request, RCODE_COMPLETE);
15762306a36Sopenharmony_ci
15862306a36Sopenharmony_ci	if (bits & NOTIFY_CLOCK_ACCEPTED)
15962306a36Sopenharmony_ci		complete(&dice->clock_accepted);
16062306a36Sopenharmony_ci	wake_up(&dice->hwdep_wait);
16162306a36Sopenharmony_ci}
16262306a36Sopenharmony_ci
16362306a36Sopenharmony_cistatic int register_notification_address(struct snd_dice *dice, bool retry)
16462306a36Sopenharmony_ci{
16562306a36Sopenharmony_ci	struct fw_device *device = fw_parent_device(dice->unit);
16662306a36Sopenharmony_ci	__be64 *buffer;
16762306a36Sopenharmony_ci	unsigned int retries;
16862306a36Sopenharmony_ci	int err;
16962306a36Sopenharmony_ci
17062306a36Sopenharmony_ci	retries = (retry) ? 3 : 0;
17162306a36Sopenharmony_ci
17262306a36Sopenharmony_ci	buffer = kmalloc(2 * 8, GFP_KERNEL);
17362306a36Sopenharmony_ci	if (!buffer)
17462306a36Sopenharmony_ci		return -ENOMEM;
17562306a36Sopenharmony_ci
17662306a36Sopenharmony_ci	for (;;) {
17762306a36Sopenharmony_ci		buffer[0] = cpu_to_be64(OWNER_NO_OWNER);
17862306a36Sopenharmony_ci		buffer[1] = cpu_to_be64(
17962306a36Sopenharmony_ci			((u64)device->card->node_id << OWNER_NODE_SHIFT) |
18062306a36Sopenharmony_ci			dice->notification_handler.offset);
18162306a36Sopenharmony_ci
18262306a36Sopenharmony_ci		dice->owner_generation = device->generation;
18362306a36Sopenharmony_ci		smp_rmb(); /* node_id vs. generation */
18462306a36Sopenharmony_ci		err = snd_fw_transaction(dice->unit, TCODE_LOCK_COMPARE_SWAP,
18562306a36Sopenharmony_ci					 get_subaddr(dice,
18662306a36Sopenharmony_ci						     SND_DICE_ADDR_TYPE_GLOBAL,
18762306a36Sopenharmony_ci						     GLOBAL_OWNER),
18862306a36Sopenharmony_ci					 buffer, 2 * 8,
18962306a36Sopenharmony_ci					 FW_FIXED_GENERATION |
19062306a36Sopenharmony_ci							dice->owner_generation);
19162306a36Sopenharmony_ci		if (err == 0) {
19262306a36Sopenharmony_ci			/* success */
19362306a36Sopenharmony_ci			if (buffer[0] == cpu_to_be64(OWNER_NO_OWNER))
19462306a36Sopenharmony_ci				break;
19562306a36Sopenharmony_ci			/* The address seems to be already registered. */
19662306a36Sopenharmony_ci			if (buffer[0] == buffer[1])
19762306a36Sopenharmony_ci				break;
19862306a36Sopenharmony_ci
19962306a36Sopenharmony_ci			dev_err(&dice->unit->device,
20062306a36Sopenharmony_ci				"device is already in use\n");
20162306a36Sopenharmony_ci			err = -EBUSY;
20262306a36Sopenharmony_ci		}
20362306a36Sopenharmony_ci		if (err != -EAGAIN || retries-- > 0)
20462306a36Sopenharmony_ci			break;
20562306a36Sopenharmony_ci
20662306a36Sopenharmony_ci		msleep(20);
20762306a36Sopenharmony_ci	}
20862306a36Sopenharmony_ci
20962306a36Sopenharmony_ci	kfree(buffer);
21062306a36Sopenharmony_ci
21162306a36Sopenharmony_ci	if (err < 0)
21262306a36Sopenharmony_ci		dice->owner_generation = -1;
21362306a36Sopenharmony_ci
21462306a36Sopenharmony_ci	return err;
21562306a36Sopenharmony_ci}
21662306a36Sopenharmony_ci
21762306a36Sopenharmony_cistatic void unregister_notification_address(struct snd_dice *dice)
21862306a36Sopenharmony_ci{
21962306a36Sopenharmony_ci	struct fw_device *device = fw_parent_device(dice->unit);
22062306a36Sopenharmony_ci	__be64 *buffer;
22162306a36Sopenharmony_ci
22262306a36Sopenharmony_ci	buffer = kmalloc(2 * 8, GFP_KERNEL);
22362306a36Sopenharmony_ci	if (buffer == NULL)
22462306a36Sopenharmony_ci		return;
22562306a36Sopenharmony_ci
22662306a36Sopenharmony_ci	buffer[0] = cpu_to_be64(
22762306a36Sopenharmony_ci		((u64)device->card->node_id << OWNER_NODE_SHIFT) |
22862306a36Sopenharmony_ci		dice->notification_handler.offset);
22962306a36Sopenharmony_ci	buffer[1] = cpu_to_be64(OWNER_NO_OWNER);
23062306a36Sopenharmony_ci	snd_fw_transaction(dice->unit, TCODE_LOCK_COMPARE_SWAP,
23162306a36Sopenharmony_ci			   get_subaddr(dice, SND_DICE_ADDR_TYPE_GLOBAL,
23262306a36Sopenharmony_ci				       GLOBAL_OWNER),
23362306a36Sopenharmony_ci			   buffer, 2 * 8, FW_QUIET |
23462306a36Sopenharmony_ci			   FW_FIXED_GENERATION | dice->owner_generation);
23562306a36Sopenharmony_ci
23662306a36Sopenharmony_ci	kfree(buffer);
23762306a36Sopenharmony_ci
23862306a36Sopenharmony_ci	dice->owner_generation = -1;
23962306a36Sopenharmony_ci}
24062306a36Sopenharmony_ci
24162306a36Sopenharmony_civoid snd_dice_transaction_destroy(struct snd_dice *dice)
24262306a36Sopenharmony_ci{
24362306a36Sopenharmony_ci	struct fw_address_handler *handler = &dice->notification_handler;
24462306a36Sopenharmony_ci
24562306a36Sopenharmony_ci	if (handler->callback_data == NULL)
24662306a36Sopenharmony_ci		return;
24762306a36Sopenharmony_ci
24862306a36Sopenharmony_ci	unregister_notification_address(dice);
24962306a36Sopenharmony_ci
25062306a36Sopenharmony_ci	fw_core_remove_address_handler(handler);
25162306a36Sopenharmony_ci	handler->callback_data = NULL;
25262306a36Sopenharmony_ci}
25362306a36Sopenharmony_ci
25462306a36Sopenharmony_ciint snd_dice_transaction_reinit(struct snd_dice *dice)
25562306a36Sopenharmony_ci{
25662306a36Sopenharmony_ci	struct fw_address_handler *handler = &dice->notification_handler;
25762306a36Sopenharmony_ci
25862306a36Sopenharmony_ci	if (handler->callback_data == NULL)
25962306a36Sopenharmony_ci		return -EINVAL;
26062306a36Sopenharmony_ci
26162306a36Sopenharmony_ci	return register_notification_address(dice, false);
26262306a36Sopenharmony_ci}
26362306a36Sopenharmony_ci
26462306a36Sopenharmony_cistatic int get_subaddrs(struct snd_dice *dice)
26562306a36Sopenharmony_ci{
26662306a36Sopenharmony_ci	static const int min_values[10] = {
26762306a36Sopenharmony_ci		10, 0x60 / 4,
26862306a36Sopenharmony_ci		10, 0x18 / 4,
26962306a36Sopenharmony_ci		10, 0x18 / 4,
27062306a36Sopenharmony_ci		0, 0,
27162306a36Sopenharmony_ci		0, 0,
27262306a36Sopenharmony_ci	};
27362306a36Sopenharmony_ci	__be32 *pointers;
27462306a36Sopenharmony_ci	__be32 version;
27562306a36Sopenharmony_ci	u32 data;
27662306a36Sopenharmony_ci	unsigned int i;
27762306a36Sopenharmony_ci	int err;
27862306a36Sopenharmony_ci
27962306a36Sopenharmony_ci	pointers = kmalloc_array(ARRAY_SIZE(min_values), sizeof(__be32),
28062306a36Sopenharmony_ci				 GFP_KERNEL);
28162306a36Sopenharmony_ci	if (pointers == NULL)
28262306a36Sopenharmony_ci		return -ENOMEM;
28362306a36Sopenharmony_ci
28462306a36Sopenharmony_ci	/*
28562306a36Sopenharmony_ci	 * Check that the sub address spaces exist and are located inside the
28662306a36Sopenharmony_ci	 * private address space.  The minimum values are chosen so that all
28762306a36Sopenharmony_ci	 * minimally required registers are included.
28862306a36Sopenharmony_ci	 */
28962306a36Sopenharmony_ci	err = snd_fw_transaction(dice->unit, TCODE_READ_BLOCK_REQUEST,
29062306a36Sopenharmony_ci				 DICE_PRIVATE_SPACE, pointers,
29162306a36Sopenharmony_ci				 sizeof(__be32) * ARRAY_SIZE(min_values), 0);
29262306a36Sopenharmony_ci	if (err < 0)
29362306a36Sopenharmony_ci		goto end;
29462306a36Sopenharmony_ci
29562306a36Sopenharmony_ci	for (i = 0; i < ARRAY_SIZE(min_values); ++i) {
29662306a36Sopenharmony_ci		data = be32_to_cpu(pointers[i]);
29762306a36Sopenharmony_ci		if (data < min_values[i] || data >= 0x40000) {
29862306a36Sopenharmony_ci			err = -ENODEV;
29962306a36Sopenharmony_ci			goto end;
30062306a36Sopenharmony_ci		}
30162306a36Sopenharmony_ci	}
30262306a36Sopenharmony_ci
30362306a36Sopenharmony_ci	if (be32_to_cpu(pointers[1]) > 0x18) {
30462306a36Sopenharmony_ci		/*
30562306a36Sopenharmony_ci		 * Check that the implemented DICE driver specification major
30662306a36Sopenharmony_ci		 * version number matches.
30762306a36Sopenharmony_ci		 */
30862306a36Sopenharmony_ci		err = snd_fw_transaction(dice->unit, TCODE_READ_QUADLET_REQUEST,
30962306a36Sopenharmony_ci				DICE_PRIVATE_SPACE +
31062306a36Sopenharmony_ci				be32_to_cpu(pointers[0]) * 4 + GLOBAL_VERSION,
31162306a36Sopenharmony_ci				&version, sizeof(version), 0);
31262306a36Sopenharmony_ci		if (err < 0)
31362306a36Sopenharmony_ci			goto end;
31462306a36Sopenharmony_ci
31562306a36Sopenharmony_ci		if ((version & cpu_to_be32(0xff000000)) !=
31662306a36Sopenharmony_ci						cpu_to_be32(0x01000000)) {
31762306a36Sopenharmony_ci			dev_err(&dice->unit->device,
31862306a36Sopenharmony_ci				"unknown DICE version: 0x%08x\n",
31962306a36Sopenharmony_ci				be32_to_cpu(version));
32062306a36Sopenharmony_ci			err = -ENODEV;
32162306a36Sopenharmony_ci			goto end;
32262306a36Sopenharmony_ci		}
32362306a36Sopenharmony_ci
32462306a36Sopenharmony_ci		/* Set up later. */
32562306a36Sopenharmony_ci		dice->clock_caps = 1;
32662306a36Sopenharmony_ci	}
32762306a36Sopenharmony_ci
32862306a36Sopenharmony_ci	dice->global_offset = be32_to_cpu(pointers[0]) * 4;
32962306a36Sopenharmony_ci	dice->tx_offset = be32_to_cpu(pointers[2]) * 4;
33062306a36Sopenharmony_ci	dice->rx_offset = be32_to_cpu(pointers[4]) * 4;
33162306a36Sopenharmony_ci
33262306a36Sopenharmony_ci	/* Old firmware doesn't support these fields. */
33362306a36Sopenharmony_ci	if (pointers[7])
33462306a36Sopenharmony_ci		dice->sync_offset = be32_to_cpu(pointers[6]) * 4;
33562306a36Sopenharmony_ci	if (pointers[9])
33662306a36Sopenharmony_ci		dice->rsrv_offset = be32_to_cpu(pointers[8]) * 4;
33762306a36Sopenharmony_ciend:
33862306a36Sopenharmony_ci	kfree(pointers);
33962306a36Sopenharmony_ci	return err;
34062306a36Sopenharmony_ci}
34162306a36Sopenharmony_ci
34262306a36Sopenharmony_ciint snd_dice_transaction_init(struct snd_dice *dice)
34362306a36Sopenharmony_ci{
34462306a36Sopenharmony_ci	struct fw_address_handler *handler = &dice->notification_handler;
34562306a36Sopenharmony_ci	int err;
34662306a36Sopenharmony_ci
34762306a36Sopenharmony_ci	err = get_subaddrs(dice);
34862306a36Sopenharmony_ci	if (err < 0)
34962306a36Sopenharmony_ci		return err;
35062306a36Sopenharmony_ci
35162306a36Sopenharmony_ci	/* Allocation callback in address space over host controller */
35262306a36Sopenharmony_ci	handler->length = 4;
35362306a36Sopenharmony_ci	handler->address_callback = dice_notification;
35462306a36Sopenharmony_ci	handler->callback_data = dice;
35562306a36Sopenharmony_ci	err = fw_core_add_address_handler(handler, &fw_high_memory_region);
35662306a36Sopenharmony_ci	if (err < 0) {
35762306a36Sopenharmony_ci		handler->callback_data = NULL;
35862306a36Sopenharmony_ci		return err;
35962306a36Sopenharmony_ci	}
36062306a36Sopenharmony_ci
36162306a36Sopenharmony_ci	/* Register the address space */
36262306a36Sopenharmony_ci	err = register_notification_address(dice, true);
36362306a36Sopenharmony_ci	if (err < 0) {
36462306a36Sopenharmony_ci		fw_core_remove_address_handler(handler);
36562306a36Sopenharmony_ci		handler->callback_data = NULL;
36662306a36Sopenharmony_ci	}
36762306a36Sopenharmony_ci
36862306a36Sopenharmony_ci	return err;
36962306a36Sopenharmony_ci}
370