162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * miscellaneous helper functions
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (c) Clemens Ladisch <clemens@ladisch.de>
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#include <linux/delay.h>
962306a36Sopenharmony_ci#include <linux/device.h>
1062306a36Sopenharmony_ci#include <linux/firewire.h>
1162306a36Sopenharmony_ci#include <linux/module.h>
1262306a36Sopenharmony_ci#include <linux/slab.h>
1362306a36Sopenharmony_ci#include "lib.h"
1462306a36Sopenharmony_ci
1562306a36Sopenharmony_ci#define ERROR_RETRY_DELAY_MS	20
1662306a36Sopenharmony_ci
1762306a36Sopenharmony_ci/**
1862306a36Sopenharmony_ci * snd_fw_transaction - send a request and wait for its completion
1962306a36Sopenharmony_ci * @unit: the driver's unit on the target device
2062306a36Sopenharmony_ci * @tcode: the transaction code
2162306a36Sopenharmony_ci * @offset: the address in the target's address space
2262306a36Sopenharmony_ci * @buffer: input/output data
2362306a36Sopenharmony_ci * @length: length of @buffer
2462306a36Sopenharmony_ci * @flags: use %FW_FIXED_GENERATION and add the generation value to attempt the
2562306a36Sopenharmony_ci *         request only in that generation; use %FW_QUIET to suppress error
2662306a36Sopenharmony_ci *         messages
2762306a36Sopenharmony_ci *
2862306a36Sopenharmony_ci * Submits an asynchronous request to the target device, and waits for the
2962306a36Sopenharmony_ci * response.  The node ID and the current generation are derived from @unit.
3062306a36Sopenharmony_ci * On a bus reset or an error, the transaction is retried a few times.
3162306a36Sopenharmony_ci * Returns zero on success, or a negative error code.
3262306a36Sopenharmony_ci */
3362306a36Sopenharmony_ciint snd_fw_transaction(struct fw_unit *unit, int tcode,
3462306a36Sopenharmony_ci		       u64 offset, void *buffer, size_t length,
3562306a36Sopenharmony_ci		       unsigned int flags)
3662306a36Sopenharmony_ci{
3762306a36Sopenharmony_ci	struct fw_device *device = fw_parent_device(unit);
3862306a36Sopenharmony_ci	int generation, rcode, tries = 0;
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_ci	generation = flags & FW_GENERATION_MASK;
4162306a36Sopenharmony_ci	for (;;) {
4262306a36Sopenharmony_ci		if (!(flags & FW_FIXED_GENERATION)) {
4362306a36Sopenharmony_ci			generation = device->generation;
4462306a36Sopenharmony_ci			smp_rmb(); /* node_id vs. generation */
4562306a36Sopenharmony_ci		}
4662306a36Sopenharmony_ci		rcode = fw_run_transaction(device->card, tcode,
4762306a36Sopenharmony_ci					   device->node_id, generation,
4862306a36Sopenharmony_ci					   device->max_speed, offset,
4962306a36Sopenharmony_ci					   buffer, length);
5062306a36Sopenharmony_ci
5162306a36Sopenharmony_ci		if (rcode == RCODE_COMPLETE)
5262306a36Sopenharmony_ci			return 0;
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_ci		if (rcode == RCODE_GENERATION && (flags & FW_FIXED_GENERATION))
5562306a36Sopenharmony_ci			return -EAGAIN;
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_ci		if (rcode_is_permanent_error(rcode) || ++tries >= 3) {
5862306a36Sopenharmony_ci			if (!(flags & FW_QUIET))
5962306a36Sopenharmony_ci				dev_err(&unit->device,
6062306a36Sopenharmony_ci					"transaction failed: %s\n",
6162306a36Sopenharmony_ci					fw_rcode_string(rcode));
6262306a36Sopenharmony_ci			return -EIO;
6362306a36Sopenharmony_ci		}
6462306a36Sopenharmony_ci
6562306a36Sopenharmony_ci		msleep(ERROR_RETRY_DELAY_MS);
6662306a36Sopenharmony_ci	}
6762306a36Sopenharmony_ci}
6862306a36Sopenharmony_ciEXPORT_SYMBOL(snd_fw_transaction);
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_ciMODULE_DESCRIPTION("FireWire audio helper functions");
7162306a36Sopenharmony_ciMODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>");
7262306a36Sopenharmony_ciMODULE_LICENSE("GPL");
73