File Index
Files and directories
Sign in
FILE

dice-transaction.c

File preview with syntax highlighting.

linux-6.19.14/sound/firewire/dice/dice-transaction.c C
  1// SPDX-License-Identifier: GPL-2.0-only
  2/*
  3 * dice_transaction.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 u64 get_subaddr(struct snd_dice *dice, enum snd_dice_addr_type type,
 12		       u64 offset)
 13{
 14	switch (type) {
 15	case SND_DICE_ADDR_TYPE_TX:
 16		offset += dice->tx_offset;
 17		break;
 18	case SND_DICE_ADDR_TYPE_RX:
 19		offset += dice->rx_offset;
 20		break;
 21	case SND_DICE_ADDR_TYPE_SYNC:
 22		offset += dice->sync_offset;
 23		break;
 24	case SND_DICE_ADDR_TYPE_RSRV:
 25		offset += dice->rsrv_offset;
 26		break;
 27	case SND_DICE_ADDR_TYPE_GLOBAL:
 28	default:
 29		offset += dice->global_offset;
 30		break;
 31	}
 32	offset += DICE_PRIVATE_SPACE;
 33	return offset;
 34}
 35
 36int snd_dice_transaction_write(struct snd_dice *dice,
 37			       enum snd_dice_addr_type type,
 38			       unsigned int offset, void *buf, unsigned int len)
 39{
 40	return snd_fw_transaction(dice->unit,
 41				  (len == 4) ? TCODE_WRITE_QUADLET_REQUEST :
 42					       TCODE_WRITE_BLOCK_REQUEST,
 43				  get_subaddr(dice, type, offset), buf, len, 0);
 44}
 45
 46int snd_dice_transaction_read(struct snd_dice *dice,
 47			      enum snd_dice_addr_type type, unsigned int offset,
 48			      void *buf, unsigned int len)
 49{
 50	return snd_fw_transaction(dice->unit,
 51				  (len == 4) ? TCODE_READ_QUADLET_REQUEST :
 52					       TCODE_READ_BLOCK_REQUEST,
 53				  get_subaddr(dice, type, offset), buf, len, 0);
 54}
 55
 56static unsigned int get_clock_info(struct snd_dice *dice, __be32 *info)
 57{
 58	return snd_dice_transaction_read_global(dice, GLOBAL_CLOCK_SELECT,
 59						info, 4);
 60}
 61
 62int snd_dice_transaction_get_clock_source(struct snd_dice *dice,
 63					  unsigned int *source)
 64{
 65	__be32 info;
 66	int err;
 67
 68	err = get_clock_info(dice, &info);
 69	if (err >= 0)
 70		*source = be32_to_cpu(info) & CLOCK_SOURCE_MASK;
 71
 72	return err;
 73}
 74
 75int snd_dice_transaction_get_rate(struct snd_dice *dice, unsigned int *rate)
 76{
 77	__be32 info;
 78	unsigned int index;
 79	int err;
 80
 81	err = get_clock_info(dice, &info);
 82	if (err < 0)
 83		goto end;
 84
 85	index = (be32_to_cpu(info) & CLOCK_RATE_MASK) >> CLOCK_RATE_SHIFT;
 86	if (index >= SND_DICE_RATES_COUNT) {
 87		err = -ENOSYS;
 88		goto end;
 89	}
 90
 91	*rate = snd_dice_rates[index];
 92end:
 93	return err;
 94}
 95
 96int snd_dice_transaction_set_enable(struct snd_dice *dice)
 97{
 98	__be32 value;
 99	int err = 0;
100
101	if (dice->global_enabled)
102		goto end;
103
104	value = cpu_to_be32(1);
105	err = snd_fw_transaction(dice->unit, TCODE_WRITE_QUADLET_REQUEST,
106				 get_subaddr(dice, SND_DICE_ADDR_TYPE_GLOBAL,
107					     GLOBAL_ENABLE),
108				 &value, 4,
109				 FW_FIXED_GENERATION | dice->owner_generation);
110	if (err < 0)
111		goto end;
112
113	dice->global_enabled = true;
114end:
115	return err;
116}
117
118void snd_dice_transaction_clear_enable(struct snd_dice *dice)
119{
120	__be32 value;
121
122	value = 0;
123	snd_fw_transaction(dice->unit, TCODE_WRITE_QUADLET_REQUEST,
124			   get_subaddr(dice, SND_DICE_ADDR_TYPE_GLOBAL,
125				       GLOBAL_ENABLE),
126			   &value, 4, FW_QUIET |
127			   FW_FIXED_GENERATION | dice->owner_generation);
128
129	dice->global_enabled = false;
130}
131
132static void dice_notification(struct fw_card *card, struct fw_request *request,
133			      int tcode, int destination, int source,
134			      int generation, unsigned long long offset,
135			      void *data, size_t length, void *callback_data)
136{
137	struct snd_dice *dice = callback_data;
138	u32 bits;
139
140	if (tcode != TCODE_WRITE_QUADLET_REQUEST) {
141		fw_send_response(card, request, RCODE_TYPE_ERROR);
142		return;
143	}
144	if ((offset & 3) != 0) {
145		fw_send_response(card, request, RCODE_ADDRESS_ERROR);
146		return;
147	}
148
149	bits = be32_to_cpup(data);
150
151	scoped_guard(spinlock_irqsave, &dice->lock) {
152		dice->notification_bits |= bits;
153	}
154
155	fw_send_response(card, request, RCODE_COMPLETE);
156
157	if (bits & NOTIFY_CLOCK_ACCEPTED)
158		complete(&dice->clock_accepted);
159	wake_up(&dice->hwdep_wait);
160}
161
162static int register_notification_address(struct snd_dice *dice, bool retry)
163{
164	struct fw_device *device = fw_parent_device(dice->unit);
165	__be64 *buffer;
166	unsigned int retries;
167	int err;
168
169	retries = (retry) ? 3 : 0;
170
171	buffer = kmalloc(2 * 8, GFP_KERNEL);
172	if (!buffer)
173		return -ENOMEM;
174
175	for (;;) {
176		buffer[0] = cpu_to_be64(OWNER_NO_OWNER);
177		buffer[1] = cpu_to_be64(
178			((u64)device->card->node_id << OWNER_NODE_SHIFT) |
179			dice->notification_handler.offset);
180
181		dice->owner_generation = device->generation;
182		smp_rmb(); /* node_id vs. generation */
183		err = snd_fw_transaction(dice->unit, TCODE_LOCK_COMPARE_SWAP,
184					 get_subaddr(dice,
185						     SND_DICE_ADDR_TYPE_GLOBAL,
186						     GLOBAL_OWNER),
187					 buffer, 2 * 8,
188					 FW_FIXED_GENERATION |
189							dice->owner_generation);
190		if (err == 0) {
191			/* success */
192			if (buffer[0] == cpu_to_be64(OWNER_NO_OWNER))
193				break;
194			/* The address seems to be already registered. */
195			if (buffer[0] == buffer[1])
196				break;
197
198			dev_err(&dice->unit->device,
199				"device is already in use\n");
200			err = -EBUSY;
201		}
202		if (err != -EAGAIN || retries-- > 0)
203			break;
204
205		msleep(20);
206	}
207
208	kfree(buffer);
209
210	if (err < 0)
211		dice->owner_generation = -1;
212
213	return err;
214}
215
216static void unregister_notification_address(struct snd_dice *dice)
217{
218	struct fw_device *device = fw_parent_device(dice->unit);
219	__be64 *buffer;
220
221	buffer = kmalloc(2 * 8, GFP_KERNEL);
222	if (buffer == NULL)
223		return;
224
225	buffer[0] = cpu_to_be64(
226		((u64)device->card->node_id << OWNER_NODE_SHIFT) |
227		dice->notification_handler.offset);
228	buffer[1] = cpu_to_be64(OWNER_NO_OWNER);
229	snd_fw_transaction(dice->unit, TCODE_LOCK_COMPARE_SWAP,
230			   get_subaddr(dice, SND_DICE_ADDR_TYPE_GLOBAL,
231				       GLOBAL_OWNER),
232			   buffer, 2 * 8, FW_QUIET |
233			   FW_FIXED_GENERATION | dice->owner_generation);
234
235	kfree(buffer);
236
237	dice->owner_generation = -1;
238}
239
240void snd_dice_transaction_destroy(struct snd_dice *dice)
241{
242	struct fw_address_handler *handler = &dice->notification_handler;
243
244	if (handler->callback_data == NULL)
245		return;
246
247	unregister_notification_address(dice);
248
249	fw_core_remove_address_handler(handler);
250	handler->callback_data = NULL;
251}
252
253int snd_dice_transaction_reinit(struct snd_dice *dice)
254{
255	struct fw_address_handler *handler = &dice->notification_handler;
256
257	if (handler->callback_data == NULL)
258		return -EINVAL;
259
260	return register_notification_address(dice, false);
261}
262
263static int get_subaddrs(struct snd_dice *dice)
264{
265	static const int min_values[10] = {
266		10, 0x60 / 4,
267		10, 0x18 / 4,
268		10, 0x18 / 4,
269		0, 0,
270		0, 0,
271	};
272	__be32 *pointers;
273	__be32 version;
274	u32 data;
275	unsigned int i;
276	int err;
277
278	pointers = kmalloc_array(ARRAY_SIZE(min_values), sizeof(__be32),
279				 GFP_KERNEL);
280	if (pointers == NULL)
281		return -ENOMEM;
282
283	/*
284	 * Check that the sub address spaces exist and are located inside the
285	 * private address space.  The minimum values are chosen so that all
286	 * minimally required registers are included.
287	 */
288	err = snd_fw_transaction(dice->unit, TCODE_READ_BLOCK_REQUEST,
289				 DICE_PRIVATE_SPACE, pointers,
290				 sizeof(__be32) * ARRAY_SIZE(min_values), 0);
291	if (err < 0)
292		goto end;
293
294	for (i = 0; i < ARRAY_SIZE(min_values); ++i) {
295		data = be32_to_cpu(pointers[i]);
296		if (data < min_values[i] || data >= 0x40000) {
297			err = -ENODEV;
298			goto end;
299		}
300	}
301
302	if (be32_to_cpu(pointers[1]) > 0x18) {
303		/*
304		 * Check that the implemented DICE driver specification major
305		 * version number matches.
306		 */
307		err = snd_fw_transaction(dice->unit, TCODE_READ_QUADLET_REQUEST,
308				DICE_PRIVATE_SPACE +
309				be32_to_cpu(pointers[0]) * 4 + GLOBAL_VERSION,
310				&version, sizeof(version), 0);
311		if (err < 0)
312			goto end;
313
314		if ((version & cpu_to_be32(0xff000000)) !=
315						cpu_to_be32(0x01000000)) {
316			dev_err(&dice->unit->device,
317				"unknown DICE version: 0x%08x\n",
318				be32_to_cpu(version));
319			err = -ENODEV;
320			goto end;
321		}
322
323		/* Set up later. */
324		dice->clock_caps = 1;
325	}
326
327	dice->global_offset = be32_to_cpu(pointers[0]) * 4;
328	dice->tx_offset = be32_to_cpu(pointers[2]) * 4;
329	dice->rx_offset = be32_to_cpu(pointers[4]) * 4;
330
331	/* Old firmware doesn't support these fields. */
332	if (pointers[7])
333		dice->sync_offset = be32_to_cpu(pointers[6]) * 4;
334	if (pointers[9])
335		dice->rsrv_offset = be32_to_cpu(pointers[8]) * 4;
336end:
337	kfree(pointers);
338	return err;
339}
340
341int snd_dice_transaction_init(struct snd_dice *dice)
342{
343	struct fw_address_handler *handler = &dice->notification_handler;
344	int err;
345
346	err = get_subaddrs(dice);
347	if (err < 0)
348		return err;
349
350	/* Allocation callback in address space over host controller */
351	handler->length = 4;
352	handler->address_callback = dice_notification;
353	handler->callback_data = dice;
354	err = fw_core_add_address_handler(handler, &fw_high_memory_region);
355	if (err < 0) {
356		handler->callback_data = NULL;
357		return err;
358	}
359
360	/* Register the address space */
361	err = register_notification_address(dice, true);
362	if (err < 0) {
363		fw_core_remove_address_handler(handler);
364		handler->callback_data = NULL;
365	}
366
367	return err;
368}
~/ Read-only file preview.