diff options
Diffstat (limited to 'sound/firewire/fireworks/fireworks_transaction.c')
| -rw-r--r-- | sound/firewire/fireworks/fireworks_transaction.c | 326 | 
1 files changed, 326 insertions, 0 deletions
diff --git a/sound/firewire/fireworks/fireworks_transaction.c b/sound/firewire/fireworks/fireworks_transaction.c new file mode 100644 index 00000000000..255dabc6fc3 --- /dev/null +++ b/sound/firewire/fireworks/fireworks_transaction.c @@ -0,0 +1,326 @@ +/* + * fireworks_transaction.c - a part of driver for Fireworks based devices + * + * Copyright (c) 2013-2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +/* + * Fireworks have its own transaction. The transaction can be delivered by AV/C + * Vendor Specific command frame or usual asynchronous transaction. At least, + * Windows driver and firmware version 5.5 or later don't use AV/C command. + * + * Transaction substance: + *  At first, 6 data exist. Following to the data, parameters for each command + *  exist. All of the parameters are 32 bit alighed to big endian. + *   data[0]:	Length of transaction substance + *   data[1]:	Transaction version + *   data[2]:	Sequence number. This is incremented by the device + *   data[3]:	Transaction category + *   data[4]:	Transaction command + *   data[5]:	Return value in response. + *   data[6-]:	Parameters + * + * Transaction address: + *  command:	0xecc000000000 + *  response:	0xecc080000000 (default) + * + * I note that the address for response can be changed by command. But this + * module uses the default address. + */ +#include "./fireworks.h" + +#define MEMORY_SPACE_EFW_COMMAND	0xecc000000000ULL +#define MEMORY_SPACE_EFW_RESPONSE	0xecc080000000ULL + +#define ERROR_RETRIES 3 +#define ERROR_DELAY_MS 5 +#define EFC_TIMEOUT_MS 125 + +static DEFINE_SPINLOCK(instances_lock); +static struct snd_efw *instances[SNDRV_CARDS] = SNDRV_DEFAULT_PTR; + +static DEFINE_SPINLOCK(transaction_queues_lock); +static LIST_HEAD(transaction_queues); + +enum transaction_queue_state { +	STATE_PENDING, +	STATE_BUS_RESET, +	STATE_COMPLETE +}; + +struct transaction_queue { +	struct list_head list; +	struct fw_unit *unit; +	void *buf; +	unsigned int size; +	u32 seqnum; +	enum transaction_queue_state state; +	wait_queue_head_t wait; +}; + +int snd_efw_transaction_cmd(struct fw_unit *unit, +			    const void *cmd, unsigned int size) +{ +	return snd_fw_transaction(unit, TCODE_WRITE_BLOCK_REQUEST, +				  MEMORY_SPACE_EFW_COMMAND, +				  (void *)cmd, size, 0); +} + +int snd_efw_transaction_run(struct fw_unit *unit, +			    const void *cmd, unsigned int cmd_size, +			    void *resp, unsigned int resp_size) +{ +	struct transaction_queue t; +	unsigned int tries; +	int ret; + +	t.unit = unit; +	t.buf = resp; +	t.size = resp_size; +	t.seqnum = be32_to_cpu(((struct snd_efw_transaction *)cmd)->seqnum) + 1; +	t.state = STATE_PENDING; +	init_waitqueue_head(&t.wait); + +	spin_lock_irq(&transaction_queues_lock); +	list_add_tail(&t.list, &transaction_queues); +	spin_unlock_irq(&transaction_queues_lock); + +	tries = 0; +	do { +		ret = snd_efw_transaction_cmd(t.unit, (void *)cmd, cmd_size); +		if (ret < 0) +			break; + +		wait_event_timeout(t.wait, t.state != STATE_PENDING, +				   msecs_to_jiffies(EFC_TIMEOUT_MS)); + +		if (t.state == STATE_COMPLETE) { +			ret = t.size; +			break; +		} else if (t.state == STATE_BUS_RESET) { +			msleep(ERROR_DELAY_MS); +		} else if (++tries >= ERROR_RETRIES) { +			dev_err(&t.unit->device, "EFW transaction timed out\n"); +			ret = -EIO; +			break; +		} +	} while (1); + +	spin_lock_irq(&transaction_queues_lock); +	list_del(&t.list); +	spin_unlock_irq(&transaction_queues_lock); + +	return ret; +} + +static void +copy_resp_to_buf(struct snd_efw *efw, void *data, size_t length, int *rcode) +{ +	size_t capacity, till_end; +	struct snd_efw_transaction *t; + +	spin_lock_irq(&efw->lock); + +	t = (struct snd_efw_transaction *)data; +	length = min_t(size_t, t->length * sizeof(t->length), length); + +	if (efw->push_ptr < efw->pull_ptr) +		capacity = (unsigned int)(efw->pull_ptr - efw->push_ptr); +	else +		capacity = snd_efw_resp_buf_size - +			   (unsigned int)(efw->push_ptr - efw->pull_ptr); + +	/* confirm enough space for this response */ +	if (capacity < length) { +		*rcode = RCODE_CONFLICT_ERROR; +		goto end; +	} + +	/* copy to ring buffer */ +	while (length > 0) { +		till_end = snd_efw_resp_buf_size - +			   (unsigned int)(efw->push_ptr - efw->resp_buf); +		till_end = min_t(unsigned int, length, till_end); + +		memcpy(efw->push_ptr, data, till_end); + +		efw->push_ptr += till_end; +		if (efw->push_ptr >= efw->resp_buf + snd_efw_resp_buf_size) +			efw->push_ptr -= snd_efw_resp_buf_size; + +		length -= till_end; +		data += till_end; +	} + +	/* for hwdep */ +	efw->resp_queues++; +	wake_up(&efw->hwdep_wait); + +	*rcode = RCODE_COMPLETE; +end: +	spin_unlock_irq(&efw->lock); +} + +static void +handle_resp_for_user(struct fw_card *card, int generation, int source, +		     void *data, size_t length, int *rcode) +{ +	struct fw_device *device; +	struct snd_efw *efw; +	unsigned int i; + +	spin_lock_irq(&instances_lock); + +	for (i = 0; i < SNDRV_CARDS; i++) { +		efw = instances[i]; +		if (efw == NULL) +			continue; +		device = fw_parent_device(efw->unit); +		if ((device->card != card) || +		    (device->generation != generation)) +			continue; +		smp_rmb();	/* node id vs. generation */ +		if (device->node_id != source) +			continue; + +		break; +	} +	if (i == SNDRV_CARDS) +		goto end; + +	copy_resp_to_buf(efw, data, length, rcode); +end: +	spin_unlock_irq(&instances_lock); +} + +static void +handle_resp_for_kernel(struct fw_card *card, int generation, int source, +		       void *data, size_t length, int *rcode, u32 seqnum) +{ +	struct fw_device *device; +	struct transaction_queue *t; +	unsigned long flags; + +	spin_lock_irqsave(&transaction_queues_lock, flags); +	list_for_each_entry(t, &transaction_queues, list) { +		device = fw_parent_device(t->unit); +		if ((device->card != card) || +		    (device->generation != generation)) +			continue; +		smp_rmb();	/* node_id vs. generation */ +		if (device->node_id != source) +			continue; + +		if ((t->state == STATE_PENDING) && (t->seqnum == seqnum)) { +			t->state = STATE_COMPLETE; +			t->size = min_t(unsigned int, length, t->size); +			memcpy(t->buf, data, t->size); +			wake_up(&t->wait); +			*rcode = RCODE_COMPLETE; +		} +	} +	spin_unlock_irqrestore(&transaction_queues_lock, flags); +} + +static void +efw_response(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) +{ +	int rcode, dummy; +	u32 seqnum; + +	rcode = RCODE_TYPE_ERROR; +	if (length < sizeof(struct snd_efw_transaction)) { +		rcode = RCODE_DATA_ERROR; +		goto end; +	} else if (offset != MEMORY_SPACE_EFW_RESPONSE) { +		rcode = RCODE_ADDRESS_ERROR; +		goto end; +	} + +	seqnum = be32_to_cpu(((struct snd_efw_transaction *)data)->seqnum); +	if (seqnum > SND_EFW_TRANSACTION_USER_SEQNUM_MAX + 1) { +		handle_resp_for_kernel(card, generation, source, +				       data, length, &rcode, seqnum); +		if (snd_efw_resp_buf_debug) +			handle_resp_for_user(card, generation, source, +					     data, length, &dummy); +	} else { +		handle_resp_for_user(card, generation, source, +				     data, length, &rcode); +	} +end: +	fw_send_response(card, request, rcode); +} + +void snd_efw_transaction_add_instance(struct snd_efw *efw) +{ +	unsigned int i; + +	spin_lock_irq(&instances_lock); + +	for (i = 0; i < SNDRV_CARDS; i++) { +		if (instances[i] != NULL) +			continue; +		instances[i] = efw; +		break; +	} + +	spin_unlock_irq(&instances_lock); +} + +void snd_efw_transaction_remove_instance(struct snd_efw *efw) +{ +	unsigned int i; + +	spin_lock_irq(&instances_lock); + +	for (i = 0; i < SNDRV_CARDS; i++) { +		if (instances[i] != efw) +			continue; +		instances[i] = NULL; +	} + +	spin_unlock_irq(&instances_lock); +} + +void snd_efw_transaction_bus_reset(struct fw_unit *unit) +{ +	struct transaction_queue *t; + +	spin_lock_irq(&transaction_queues_lock); +	list_for_each_entry(t, &transaction_queues, list) { +		if ((t->unit == unit) && +		    (t->state == STATE_PENDING)) { +			t->state = STATE_BUS_RESET; +			wake_up(&t->wait); +		} +	} +	spin_unlock_irq(&transaction_queues_lock); +} + +static struct fw_address_handler resp_register_handler = { +	.length = SND_EFW_RESPONSE_MAXIMUM_BYTES, +	.address_callback = efw_response +}; + +int snd_efw_transaction_register(void) +{ +	static const struct fw_address_region resp_register_region = { +		.start	= MEMORY_SPACE_EFW_RESPONSE, +		.end	= MEMORY_SPACE_EFW_RESPONSE + +			  SND_EFW_RESPONSE_MAXIMUM_BYTES +	}; +	return fw_core_add_address_handler(&resp_register_handler, +					   &resp_register_region); +} + +void snd_efw_transaction_unregister(void) +{ +	WARN_ON(!list_empty(&transaction_queues)); +	fw_core_remove_address_handler(&resp_register_handler); +}  | 
