| 
									
										
										
										
											2014-04-25 22:45:01 +09:00
										 |  |  | /*
 | 
					
						
							|  |  |  |  * 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 | 
					
						
							| 
									
										
										
										
											2014-06-04 15:25:34 +09:00
										 |  |  |  * 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. | 
					
						
							| 
									
										
										
										
											2014-04-25 22:45:01 +09:00
										 |  |  |  * | 
					
						
							|  |  |  |  * Transaction substance: | 
					
						
							| 
									
										
										
										
											2014-06-04 15:25:34 +09:00
										 |  |  |  *  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. | 
					
						
							| 
									
										
										
										
											2014-04-25 22:45:01 +09:00
										 |  |  |  *   data[0]:	Length of transaction substance | 
					
						
							|  |  |  |  *   data[1]:	Transaction version | 
					
						
							|  |  |  |  *   data[2]:	Sequence number. This is incremented by the device | 
					
						
							| 
									
										
										
										
											2014-06-04 15:25:34 +09:00
										 |  |  |  *   data[3]:	Transaction category | 
					
						
							|  |  |  |  *   data[4]:	Transaction command | 
					
						
							|  |  |  |  *   data[5]:	Return value in response. | 
					
						
							|  |  |  |  *   data[6-]:	Parameters | 
					
						
							| 
									
										
										
										
											2014-04-25 22:45:01 +09:00
										 |  |  |  * | 
					
						
							|  |  |  |  * 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"
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-05-28 00:14:42 +09:00
										 |  |  | #define MEMORY_SPACE_EFW_COMMAND	0xecc000000000ULL
 | 
					
						
							|  |  |  | #define MEMORY_SPACE_EFW_RESPONSE	0xecc080000000ULL
 | 
					
						
							| 
									
										
										
										
											2014-04-25 22:45:01 +09:00
										 |  |  | 
 | 
					
						
							|  |  |  | #define ERROR_RETRIES 3
 | 
					
						
							|  |  |  | #define ERROR_DELAY_MS 5
 | 
					
						
							|  |  |  | #define EFC_TIMEOUT_MS 125
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-04-25 22:45:13 +09:00
										 |  |  | static DEFINE_SPINLOCK(instances_lock); | 
					
						
							|  |  |  | static struct snd_efw *instances[SNDRV_CARDS] = SNDRV_DEFAULT_PTR; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-04-25 22:45:01 +09:00
										 |  |  | 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; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-04-25 22:45:13 +09:00
										 |  |  | 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); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-04-25 22:45:01 +09:00
										 |  |  | 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 { | 
					
						
							| 
									
										
										
										
											2014-04-25 22:45:13 +09:00
										 |  |  | 		ret = snd_efw_transaction_cmd(t.unit, (void *)cmd, cmd_size); | 
					
						
							| 
									
										
										
										
											2014-04-25 22:45:01 +09:00
										 |  |  | 		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 | 
					
						
							| 
									
										
										
										
											2014-04-25 22:45:13 +09:00
										 |  |  | copy_resp_to_buf(struct snd_efw *efw, void *data, size_t length, int *rcode) | 
					
						
							| 
									
										
										
										
											2014-04-25 22:45:01 +09:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2014-04-25 22:45:13 +09:00
										 |  |  | 	size_t capacity, till_end; | 
					
						
							|  |  |  | 	struct snd_efw_transaction *t; | 
					
						
							| 
									
										
										
										
											2014-04-25 22:45:01 +09:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-04-25 22:45:13 +09:00
										 |  |  | 	spin_lock_irq(&efw->lock); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	t = (struct snd_efw_transaction *)data; | 
					
						
							| 
									
										
										
										
											2015-01-08 00:31:16 +09:00
										 |  |  | 	length = min_t(size_t, be32_to_cpu(t->length) * sizeof(u32), length); | 
					
						
							| 
									
										
										
										
											2014-04-25 22:45:13 +09:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	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; | 
					
						
							| 
									
										
										
										
											2014-04-25 22:45:01 +09:00
										 |  |  | 		goto end; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-04-25 22:45:13 +09:00
										 |  |  | 	/* 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) | 
					
						
							| 
									
										
										
										
											2014-06-04 15:25:33 +09:00
										 |  |  | 			efw->push_ptr -= snd_efw_resp_buf_size; | 
					
						
							| 
									
										
										
										
											2014-04-25 22:45:13 +09:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		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; | 
					
						
							| 
									
										
										
										
											2014-04-25 22:45:01 +09:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	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); | 
					
						
							| 
									
										
										
										
											2014-04-25 22:45:13 +09:00
										 |  |  | 			*rcode = RCODE_COMPLETE; | 
					
						
							| 
									
										
										
										
											2014-04-25 22:45:01 +09:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	spin_unlock_irqrestore(&transaction_queues_lock, flags); | 
					
						
							| 
									
										
										
										
											2014-04-25 22:45:13 +09:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 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); | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2014-04-25 22:45:01 +09:00
										 |  |  | end: | 
					
						
							|  |  |  | 	fw_send_response(card, request, rcode); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-04-25 22:45:13 +09:00
										 |  |  | 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); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-04-25 22:45:01 +09:00
										 |  |  | 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); | 
					
						
							|  |  |  | } |