diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2014-06-08 11:31:16 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2014-06-08 11:31:16 -0700 |
commit | 3f17ea6dea8ba5668873afa54628a91aaa3fb1c0 (patch) | |
tree | afbeb2accd4c2199ddd705ae943995b143a0af02 /sound/firewire/fcp.c | |
parent | 1860e379875dfe7271c649058aeddffe5afd9d0d (diff) | |
parent | 1a5700bc2d10cd379a795fd2bb377a190af5acd4 (diff) |
Merge branch 'next' (accumulated 3.16 merge window patches) into master
Now that 3.15 is released, this merges the 'next' branch into 'master',
bringing us to the normal situation where my 'master' branch is the
merge window.
* accumulated work in next: (6809 commits)
ufs: sb mutex merge + mutex_destroy
powerpc: update comments for generic idle conversion
cris: update comments for generic idle conversion
idle: remove cpu_idle() forward declarations
nbd: zero from and len fields in NBD_CMD_DISCONNECT.
mm: convert some level-less printks to pr_*
MAINTAINERS: adi-buildroot-devel is moderated
MAINTAINERS: add linux-api for review of API/ABI changes
mm/kmemleak-test.c: use pr_fmt for logging
fs/dlm/debug_fs.c: replace seq_printf by seq_puts
fs/dlm/lockspace.c: convert simple_str to kstr
fs/dlm/config.c: convert simple_str to kstr
mm: mark remap_file_pages() syscall as deprecated
mm: memcontrol: remove unnecessary memcg argument from soft limit functions
mm: memcontrol: clean up memcg zoneinfo lookup
mm/memblock.c: call kmemleak directly from memblock_(alloc|free)
mm/mempool.c: update the kmemleak stack trace for mempool allocations
lib/radix-tree.c: update the kmemleak stack trace for radix tree allocations
mm: introduce kmemleak_update_trace()
mm/kmemleak.c: use %u to print ->checksum
...
Diffstat (limited to 'sound/firewire/fcp.c')
-rw-r--r-- | sound/firewire/fcp.c | 191 |
1 files changed, 182 insertions, 9 deletions
diff --git a/sound/firewire/fcp.c b/sound/firewire/fcp.c index 860c08073c59..0619597e3a3f 100644 --- a/sound/firewire/fcp.c +++ b/sound/firewire/fcp.c @@ -10,12 +10,14 @@ #include <linux/firewire-constants.h> #include <linux/list.h> #include <linux/module.h> +#include <linux/slab.h> #include <linux/sched.h> #include <linux/spinlock.h> #include <linux/wait.h> #include <linux/delay.h> #include "fcp.h" #include "lib.h" +#include "amdtp.h" #define CTS_AVC 0x00 @@ -23,6 +25,158 @@ #define ERROR_DELAY_MS 5 #define FCP_TIMEOUT_MS 125 +int avc_general_set_sig_fmt(struct fw_unit *unit, unsigned int rate, + enum avc_general_plug_dir dir, + unsigned short pid) +{ + unsigned int sfc; + u8 *buf; + bool flag; + int err; + + flag = false; + for (sfc = 0; sfc < CIP_SFC_COUNT; sfc++) { + if (amdtp_rate_table[sfc] == rate) { + flag = true; + break; + } + } + if (!flag) + return -EINVAL; + + buf = kzalloc(8, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + buf[0] = 0x00; /* AV/C CONTROL */ + buf[1] = 0xff; /* UNIT */ + if (dir == AVC_GENERAL_PLUG_DIR_IN) + buf[2] = 0x19; /* INPUT PLUG SIGNAL FORMAT */ + else + buf[2] = 0x18; /* OUTPUT PLUG SIGNAL FORMAT */ + buf[3] = 0xff & pid; /* plug id */ + buf[4] = 0x90; /* EOH_1, Form_1, FMT. AM824 */ + buf[5] = 0x07 & sfc; /* FDF-hi. AM824, frequency */ + buf[6] = 0xff; /* FDF-mid. AM824, SYT hi (not used)*/ + buf[7] = 0xff; /* FDF-low. AM824, SYT lo (not used) */ + + /* do transaction and check buf[1-5] are the same against command */ + err = fcp_avc_transaction(unit, buf, 8, buf, 8, + BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5)); + if (err >= 0 && err < 8) + err = -EIO; + else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ + err = -ENOSYS; + else if (buf[0] == 0x0a) /* REJECTED */ + err = -EINVAL; + if (err < 0) + goto end; + + err = 0; +end: + kfree(buf); + return err; +} +EXPORT_SYMBOL(avc_general_set_sig_fmt); + +int avc_general_get_sig_fmt(struct fw_unit *unit, unsigned int *rate, + enum avc_general_plug_dir dir, + unsigned short pid) +{ + unsigned int sfc; + u8 *buf; + int err; + + buf = kzalloc(8, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + buf[0] = 0x01; /* AV/C STATUS */ + buf[1] = 0xff; /* Unit */ + if (dir == AVC_GENERAL_PLUG_DIR_IN) + buf[2] = 0x19; /* INPUT PLUG SIGNAL FORMAT */ + else + buf[2] = 0x18; /* OUTPUT PLUG SIGNAL FORMAT */ + buf[3] = 0xff & pid; /* plug id */ + buf[4] = 0x90; /* EOH_1, Form_1, FMT. AM824 */ + buf[5] = 0xff; /* FDF-hi. AM824, frequency */ + buf[6] = 0xff; /* FDF-mid. AM824, SYT hi (not used) */ + buf[7] = 0xff; /* FDF-low. AM824, SYT lo (not used) */ + + /* do transaction and check buf[1-4] are the same against command */ + err = fcp_avc_transaction(unit, buf, 8, buf, 8, + BIT(1) | BIT(2) | BIT(3) | BIT(4)); + if (err >= 0 && err < 8) + err = -EIO; + else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ + err = -ENOSYS; + else if (buf[0] == 0x0a) /* REJECTED */ + err = -EINVAL; + else if (buf[0] == 0x0b) /* IN TRANSITION */ + err = -EAGAIN; + if (err < 0) + goto end; + + /* check sfc field and pick up rate */ + sfc = 0x07 & buf[5]; + if (sfc >= CIP_SFC_COUNT) { + err = -EAGAIN; /* also in transition */ + goto end; + } + + *rate = amdtp_rate_table[sfc]; + err = 0; +end: + kfree(buf); + return err; +} +EXPORT_SYMBOL(avc_general_get_sig_fmt); + +int avc_general_get_plug_info(struct fw_unit *unit, unsigned int subunit_type, + unsigned int subunit_id, unsigned int subfunction, + u8 info[AVC_PLUG_INFO_BUF_BYTES]) +{ + u8 *buf; + int err; + + /* extended subunit in spec.4.2 is not supported */ + if ((subunit_type == 0x1E) || (subunit_id == 5)) + return -EINVAL; + + buf = kzalloc(8, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + buf[0] = 0x01; /* AV/C STATUS */ + /* UNIT or Subunit, Functionblock */ + buf[1] = ((subunit_type & 0x1f) << 3) | (subunit_id & 0x7); + buf[2] = 0x02; /* PLUG INFO */ + buf[3] = 0xff & subfunction; + + err = fcp_avc_transaction(unit, buf, 8, buf, 8, BIT(1) | BIT(2)); + if (err >= 0 && err < 8) + err = -EIO; + else if (buf[0] == 0x08) /* NOT IMPLEMENTED */ + err = -ENOSYS; + else if (buf[0] == 0x0a) /* REJECTED */ + err = -EINVAL; + else if (buf[0] == 0x0b) /* IN TRANSITION */ + err = -EAGAIN; + if (err < 0) + goto end; + + info[0] = buf[4]; + info[1] = buf[5]; + info[2] = buf[6]; + info[3] = buf[7]; + + err = 0; +end: + kfree(buf); + return err; +} +EXPORT_SYMBOL(avc_general_get_plug_info); + static DEFINE_SPINLOCK(transactions_lock); static LIST_HEAD(transactions); @@ -30,6 +184,7 @@ enum fcp_state { STATE_PENDING, STATE_BUS_RESET, STATE_COMPLETE, + STATE_DEFERRED, }; struct fcp_transaction { @@ -40,6 +195,7 @@ struct fcp_transaction { unsigned int response_match_bytes; enum fcp_state state; wait_queue_head_t wait; + bool deferrable; }; /** @@ -62,8 +218,6 @@ struct fcp_transaction { * * @command and @response can point to the same buffer. * - * Asynchronous operation (INTERIM, NOTIFY) is not supported at the moment. - * * Returns the actual size of the response frame, or a negative error code. */ int fcp_avc_transaction(struct fw_unit *unit, @@ -81,6 +235,9 @@ int fcp_avc_transaction(struct fw_unit *unit, t.state = STATE_PENDING; init_waitqueue_head(&t.wait); + if (*(const u8 *)command == 0x00 || *(const u8 *)command == 0x03) + t.deferrable = true; + spin_lock_irq(&transactions_lock); list_add_tail(&t.list, &transactions); spin_unlock_irq(&transactions_lock); @@ -93,11 +250,21 @@ int fcp_avc_transaction(struct fw_unit *unit, (void *)command, command_size, 0); if (ret < 0) break; - +deferred: wait_event_timeout(t.wait, t.state != STATE_PENDING, msecs_to_jiffies(FCP_TIMEOUT_MS)); - if (t.state == STATE_COMPLETE) { + if (t.state == STATE_DEFERRED) { + /* + * 'AV/C General Specification' define no time limit + * on command completion once an INTERIM response has + * been sent. but we promise to finish this function + * for a caller. Here we use FCP_TIMEOUT_MS for next + * interval. This is not in the specification. + */ + t.state = STATE_PENDING; + goto deferred; + } else if (t.state == STATE_COMPLETE) { ret = t.response_size; break; } else if (t.state == STATE_BUS_RESET) { @@ -132,7 +299,8 @@ void fcp_bus_reset(struct fw_unit *unit) spin_lock_irq(&transactions_lock); list_for_each_entry(t, &transactions, list) { if (t->unit == unit && - t->state == STATE_PENDING) { + (t->state == STATE_PENDING || + t->state == STATE_DEFERRED)) { t->state = STATE_BUS_RESET; wake_up(&t->wait); } @@ -186,10 +354,15 @@ static void fcp_response(struct fw_card *card, struct fw_request *request, if (t->state == STATE_PENDING && is_matching_response(t, data, length)) { - t->state = STATE_COMPLETE; - t->response_size = min((unsigned int)length, - t->response_size); - memcpy(t->response_buffer, data, t->response_size); + if (t->deferrable && *(const u8 *)data == 0x0f) { + t->state = STATE_DEFERRED; + } else { + t->state = STATE_COMPLETE; + t->response_size = min_t(unsigned int, length, + t->response_size); + memcpy(t->response_buffer, data, + t->response_size); + } wake_up(&t->wait); } } |