370 lines
		
	
	
		
			8.6 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			370 lines
		
	
	
		
			8.6 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| // SPDX-License-Identifier: GPL-2.0-only
 | |
| /*
 | |
|  * dice_transaction.c - a part of driver for Dice based devices
 | |
|  *
 | |
|  * Copyright (c) Clemens Ladisch
 | |
|  * Copyright (c) 2014 Takashi Sakamoto
 | |
|  */
 | |
| 
 | |
| #include "dice.h"
 | |
| 
 | |
| static u64 get_subaddr(struct snd_dice *dice, enum snd_dice_addr_type type,
 | |
| 		       u64 offset)
 | |
| {
 | |
| 	switch (type) {
 | |
| 	case SND_DICE_ADDR_TYPE_TX:
 | |
| 		offset += dice->tx_offset;
 | |
| 		break;
 | |
| 	case SND_DICE_ADDR_TYPE_RX:
 | |
| 		offset += dice->rx_offset;
 | |
| 		break;
 | |
| 	case SND_DICE_ADDR_TYPE_SYNC:
 | |
| 		offset += dice->sync_offset;
 | |
| 		break;
 | |
| 	case SND_DICE_ADDR_TYPE_RSRV:
 | |
| 		offset += dice->rsrv_offset;
 | |
| 		break;
 | |
| 	case SND_DICE_ADDR_TYPE_GLOBAL:
 | |
| 	default:
 | |
| 		offset += dice->global_offset;
 | |
| 		break;
 | |
| 	}
 | |
| 	offset += DICE_PRIVATE_SPACE;
 | |
| 	return offset;
 | |
| }
 | |
| 
 | |
| int snd_dice_transaction_write(struct snd_dice *dice,
 | |
| 			       enum snd_dice_addr_type type,
 | |
| 			       unsigned int offset, void *buf, unsigned int len)
 | |
| {
 | |
| 	return snd_fw_transaction(dice->unit,
 | |
| 				  (len == 4) ? TCODE_WRITE_QUADLET_REQUEST :
 | |
| 					       TCODE_WRITE_BLOCK_REQUEST,
 | |
| 				  get_subaddr(dice, type, offset), buf, len, 0);
 | |
| }
 | |
| 
 | |
| int snd_dice_transaction_read(struct snd_dice *dice,
 | |
| 			      enum snd_dice_addr_type type, unsigned int offset,
 | |
| 			      void *buf, unsigned int len)
 | |
| {
 | |
| 	return snd_fw_transaction(dice->unit,
 | |
| 				  (len == 4) ? TCODE_READ_QUADLET_REQUEST :
 | |
| 					       TCODE_READ_BLOCK_REQUEST,
 | |
| 				  get_subaddr(dice, type, offset), buf, len, 0);
 | |
| }
 | |
| 
 | |
| static unsigned int get_clock_info(struct snd_dice *dice, __be32 *info)
 | |
| {
 | |
| 	return snd_dice_transaction_read_global(dice, GLOBAL_CLOCK_SELECT,
 | |
| 						info, 4);
 | |
| }
 | |
| 
 | |
| int snd_dice_transaction_get_clock_source(struct snd_dice *dice,
 | |
| 					  unsigned int *source)
 | |
| {
 | |
| 	__be32 info;
 | |
| 	int err;
 | |
| 
 | |
| 	err = get_clock_info(dice, &info);
 | |
| 	if (err >= 0)
 | |
| 		*source = be32_to_cpu(info) & CLOCK_SOURCE_MASK;
 | |
| 
 | |
| 	return err;
 | |
| }
 | |
| 
 | |
| int snd_dice_transaction_get_rate(struct snd_dice *dice, unsigned int *rate)
 | |
| {
 | |
| 	__be32 info;
 | |
| 	unsigned int index;
 | |
| 	int err;
 | |
| 
 | |
| 	err = get_clock_info(dice, &info);
 | |
| 	if (err < 0)
 | |
| 		goto end;
 | |
| 
 | |
| 	index = (be32_to_cpu(info) & CLOCK_RATE_MASK) >> CLOCK_RATE_SHIFT;
 | |
| 	if (index >= SND_DICE_RATES_COUNT) {
 | |
| 		err = -ENOSYS;
 | |
| 		goto end;
 | |
| 	}
 | |
| 
 | |
| 	*rate = snd_dice_rates[index];
 | |
| end:
 | |
| 	return err;
 | |
| }
 | |
| 
 | |
| int snd_dice_transaction_set_enable(struct snd_dice *dice)
 | |
| {
 | |
| 	__be32 value;
 | |
| 	int err = 0;
 | |
| 
 | |
| 	if (dice->global_enabled)
 | |
| 		goto end;
 | |
| 
 | |
| 	value = cpu_to_be32(1);
 | |
| 	err = snd_fw_transaction(dice->unit, TCODE_WRITE_QUADLET_REQUEST,
 | |
| 				 get_subaddr(dice, SND_DICE_ADDR_TYPE_GLOBAL,
 | |
| 					     GLOBAL_ENABLE),
 | |
| 				 &value, 4,
 | |
| 				 FW_FIXED_GENERATION | dice->owner_generation);
 | |
| 	if (err < 0)
 | |
| 		goto end;
 | |
| 
 | |
| 	dice->global_enabled = true;
 | |
| end:
 | |
| 	return err;
 | |
| }
 | |
| 
 | |
| void snd_dice_transaction_clear_enable(struct snd_dice *dice)
 | |
| {
 | |
| 	__be32 value;
 | |
| 
 | |
| 	value = 0;
 | |
| 	snd_fw_transaction(dice->unit, TCODE_WRITE_QUADLET_REQUEST,
 | |
| 			   get_subaddr(dice, SND_DICE_ADDR_TYPE_GLOBAL,
 | |
| 				       GLOBAL_ENABLE),
 | |
| 			   &value, 4, FW_QUIET |
 | |
| 			   FW_FIXED_GENERATION | dice->owner_generation);
 | |
| 
 | |
| 	dice->global_enabled = false;
 | |
| }
 | |
| 
 | |
| static void dice_notification(struct fw_card *card, struct fw_request *request,
 | |
| 			      int tcode, int destination, int source,
 | |
| 			      int generation, unsigned long long offset,
 | |
| 			      void *data, size_t length, void *callback_data)
 | |
| {
 | |
| 	struct snd_dice *dice = callback_data;
 | |
| 	u32 bits;
 | |
| 	unsigned long flags;
 | |
| 
 | |
| 	if (tcode != TCODE_WRITE_QUADLET_REQUEST) {
 | |
| 		fw_send_response(card, request, RCODE_TYPE_ERROR);
 | |
| 		return;
 | |
| 	}
 | |
| 	if ((offset & 3) != 0) {
 | |
| 		fw_send_response(card, request, RCODE_ADDRESS_ERROR);
 | |
| 		return;
 | |
| 	}
 | |
| 
 | |
| 	bits = be32_to_cpup(data);
 | |
| 
 | |
| 	spin_lock_irqsave(&dice->lock, flags);
 | |
| 	dice->notification_bits |= bits;
 | |
| 	spin_unlock_irqrestore(&dice->lock, flags);
 | |
| 
 | |
| 	fw_send_response(card, request, RCODE_COMPLETE);
 | |
| 
 | |
| 	if (bits & NOTIFY_CLOCK_ACCEPTED)
 | |
| 		complete(&dice->clock_accepted);
 | |
| 	wake_up(&dice->hwdep_wait);
 | |
| }
 | |
| 
 | |
| static int register_notification_address(struct snd_dice *dice, bool retry)
 | |
| {
 | |
| 	struct fw_device *device = fw_parent_device(dice->unit);
 | |
| 	__be64 *buffer;
 | |
| 	unsigned int retries;
 | |
| 	int err;
 | |
| 
 | |
| 	retries = (retry) ? 3 : 0;
 | |
| 
 | |
| 	buffer = kmalloc(2 * 8, GFP_KERNEL);
 | |
| 	if (!buffer)
 | |
| 		return -ENOMEM;
 | |
| 
 | |
| 	for (;;) {
 | |
| 		buffer[0] = cpu_to_be64(OWNER_NO_OWNER);
 | |
| 		buffer[1] = cpu_to_be64(
 | |
| 			((u64)device->card->node_id << OWNER_NODE_SHIFT) |
 | |
| 			dice->notification_handler.offset);
 | |
| 
 | |
| 		dice->owner_generation = device->generation;
 | |
| 		smp_rmb(); /* node_id vs. generation */
 | |
| 		err = snd_fw_transaction(dice->unit, TCODE_LOCK_COMPARE_SWAP,
 | |
| 					 get_subaddr(dice,
 | |
| 						     SND_DICE_ADDR_TYPE_GLOBAL,
 | |
| 						     GLOBAL_OWNER),
 | |
| 					 buffer, 2 * 8,
 | |
| 					 FW_FIXED_GENERATION |
 | |
| 							dice->owner_generation);
 | |
| 		if (err == 0) {
 | |
| 			/* success */
 | |
| 			if (buffer[0] == cpu_to_be64(OWNER_NO_OWNER))
 | |
| 				break;
 | |
| 			/* The address seems to be already registered. */
 | |
| 			if (buffer[0] == buffer[1])
 | |
| 				break;
 | |
| 
 | |
| 			dev_err(&dice->unit->device,
 | |
| 				"device is already in use\n");
 | |
| 			err = -EBUSY;
 | |
| 		}
 | |
| 		if (err != -EAGAIN || retries-- > 0)
 | |
| 			break;
 | |
| 
 | |
| 		msleep(20);
 | |
| 	}
 | |
| 
 | |
| 	kfree(buffer);
 | |
| 
 | |
| 	if (err < 0)
 | |
| 		dice->owner_generation = -1;
 | |
| 
 | |
| 	return err;
 | |
| }
 | |
| 
 | |
| static void unregister_notification_address(struct snd_dice *dice)
 | |
| {
 | |
| 	struct fw_device *device = fw_parent_device(dice->unit);
 | |
| 	__be64 *buffer;
 | |
| 
 | |
| 	buffer = kmalloc(2 * 8, GFP_KERNEL);
 | |
| 	if (buffer == NULL)
 | |
| 		return;
 | |
| 
 | |
| 	buffer[0] = cpu_to_be64(
 | |
| 		((u64)device->card->node_id << OWNER_NODE_SHIFT) |
 | |
| 		dice->notification_handler.offset);
 | |
| 	buffer[1] = cpu_to_be64(OWNER_NO_OWNER);
 | |
| 	snd_fw_transaction(dice->unit, TCODE_LOCK_COMPARE_SWAP,
 | |
| 			   get_subaddr(dice, SND_DICE_ADDR_TYPE_GLOBAL,
 | |
| 				       GLOBAL_OWNER),
 | |
| 			   buffer, 2 * 8, FW_QUIET |
 | |
| 			   FW_FIXED_GENERATION | dice->owner_generation);
 | |
| 
 | |
| 	kfree(buffer);
 | |
| 
 | |
| 	dice->owner_generation = -1;
 | |
| }
 | |
| 
 | |
| void snd_dice_transaction_destroy(struct snd_dice *dice)
 | |
| {
 | |
| 	struct fw_address_handler *handler = &dice->notification_handler;
 | |
| 
 | |
| 	if (handler->callback_data == NULL)
 | |
| 		return;
 | |
| 
 | |
| 	unregister_notification_address(dice);
 | |
| 
 | |
| 	fw_core_remove_address_handler(handler);
 | |
| 	handler->callback_data = NULL;
 | |
| }
 | |
| 
 | |
| int snd_dice_transaction_reinit(struct snd_dice *dice)
 | |
| {
 | |
| 	struct fw_address_handler *handler = &dice->notification_handler;
 | |
| 
 | |
| 	if (handler->callback_data == NULL)
 | |
| 		return -EINVAL;
 | |
| 
 | |
| 	return register_notification_address(dice, false);
 | |
| }
 | |
| 
 | |
| static int get_subaddrs(struct snd_dice *dice)
 | |
| {
 | |
| 	static const int min_values[10] = {
 | |
| 		10, 0x60 / 4,
 | |
| 		10, 0x18 / 4,
 | |
| 		10, 0x18 / 4,
 | |
| 		0, 0,
 | |
| 		0, 0,
 | |
| 	};
 | |
| 	__be32 *pointers;
 | |
| 	__be32 version;
 | |
| 	u32 data;
 | |
| 	unsigned int i;
 | |
| 	int err;
 | |
| 
 | |
| 	pointers = kmalloc_array(ARRAY_SIZE(min_values), sizeof(__be32),
 | |
| 				 GFP_KERNEL);
 | |
| 	if (pointers == NULL)
 | |
| 		return -ENOMEM;
 | |
| 
 | |
| 	/*
 | |
| 	 * Check that the sub address spaces exist and are located inside the
 | |
| 	 * private address space.  The minimum values are chosen so that all
 | |
| 	 * minimally required registers are included.
 | |
| 	 */
 | |
| 	err = snd_fw_transaction(dice->unit, TCODE_READ_BLOCK_REQUEST,
 | |
| 				 DICE_PRIVATE_SPACE, pointers,
 | |
| 				 sizeof(__be32) * ARRAY_SIZE(min_values), 0);
 | |
| 	if (err < 0)
 | |
| 		goto end;
 | |
| 
 | |
| 	for (i = 0; i < ARRAY_SIZE(min_values); ++i) {
 | |
| 		data = be32_to_cpu(pointers[i]);
 | |
| 		if (data < min_values[i] || data >= 0x40000) {
 | |
| 			err = -ENODEV;
 | |
| 			goto end;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if (be32_to_cpu(pointers[1]) > 0x18) {
 | |
| 		/*
 | |
| 		 * Check that the implemented DICE driver specification major
 | |
| 		 * version number matches.
 | |
| 		 */
 | |
| 		err = snd_fw_transaction(dice->unit, TCODE_READ_QUADLET_REQUEST,
 | |
| 				DICE_PRIVATE_SPACE +
 | |
| 				be32_to_cpu(pointers[0]) * 4 + GLOBAL_VERSION,
 | |
| 				&version, sizeof(version), 0);
 | |
| 		if (err < 0)
 | |
| 			goto end;
 | |
| 
 | |
| 		if ((version & cpu_to_be32(0xff000000)) !=
 | |
| 						cpu_to_be32(0x01000000)) {
 | |
| 			dev_err(&dice->unit->device,
 | |
| 				"unknown DICE version: 0x%08x\n",
 | |
| 				be32_to_cpu(version));
 | |
| 			err = -ENODEV;
 | |
| 			goto end;
 | |
| 		}
 | |
| 
 | |
| 		/* Set up later. */
 | |
| 		dice->clock_caps = 1;
 | |
| 	}
 | |
| 
 | |
| 	dice->global_offset = be32_to_cpu(pointers[0]) * 4;
 | |
| 	dice->tx_offset = be32_to_cpu(pointers[2]) * 4;
 | |
| 	dice->rx_offset = be32_to_cpu(pointers[4]) * 4;
 | |
| 
 | |
| 	/* Old firmware doesn't support these fields. */
 | |
| 	if (pointers[7])
 | |
| 		dice->sync_offset = be32_to_cpu(pointers[6]) * 4;
 | |
| 	if (pointers[9])
 | |
| 		dice->rsrv_offset = be32_to_cpu(pointers[8]) * 4;
 | |
| end:
 | |
| 	kfree(pointers);
 | |
| 	return err;
 | |
| }
 | |
| 
 | |
| int snd_dice_transaction_init(struct snd_dice *dice)
 | |
| {
 | |
| 	struct fw_address_handler *handler = &dice->notification_handler;
 | |
| 	int err;
 | |
| 
 | |
| 	err = get_subaddrs(dice);
 | |
| 	if (err < 0)
 | |
| 		return err;
 | |
| 
 | |
| 	/* Allocation callback in address space over host controller */
 | |
| 	handler->length = 4;
 | |
| 	handler->address_callback = dice_notification;
 | |
| 	handler->callback_data = dice;
 | |
| 	err = fw_core_add_address_handler(handler, &fw_high_memory_region);
 | |
| 	if (err < 0) {
 | |
| 		handler->callback_data = NULL;
 | |
| 		return err;
 | |
| 	}
 | |
| 
 | |
| 	/* Register the address space */
 | |
| 	err = register_notification_address(dice, true);
 | |
| 	if (err < 0) {
 | |
| 		fw_core_remove_address_handler(handler);
 | |
| 		handler->callback_data = NULL;
 | |
| 	}
 | |
| 
 | |
| 	return err;
 | |
| }
 |