diff options
Diffstat (limited to 'sound/firewire/dice/dice-transaction.c')
-rw-r--r-- | sound/firewire/dice/dice-transaction.c | 387 |
1 files changed, 387 insertions, 0 deletions
diff --git a/sound/firewire/dice/dice-transaction.c b/sound/firewire/dice/dice-transaction.c new file mode 100644 index 000000000000..a9b98e087dce --- /dev/null +++ b/sound/firewire/dice/dice-transaction.c @@ -0,0 +1,387 @@ +/* + * dice_transaction.c - a part of driver for Dice based devices + * + * Copyright (c) Clemens Ladisch + * Copyright (c) 2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "dice.h" + +#define NOTIFICATION_TIMEOUT_MS 100 + +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); +} + +static int set_clock_info(struct snd_dice *dice, + unsigned int rate, unsigned int source) +{ + unsigned int retries = 3; + unsigned int i; + __be32 info; + u32 mask; + u32 clock; + int err; +retry: + err = get_clock_info(dice, &info); + if (err < 0) + goto end; + + clock = be32_to_cpu(info); + if (source != UINT_MAX) { + mask = CLOCK_SOURCE_MASK; + clock &= ~mask; + clock |= source; + } + if (rate != UINT_MAX) { + for (i = 0; i < ARRAY_SIZE(snd_dice_rates); i++) { + if (snd_dice_rates[i] == rate) + break; + } + if (i == ARRAY_SIZE(snd_dice_rates)) { + err = -EINVAL; + goto end; + } + + mask = CLOCK_RATE_MASK; + clock &= ~mask; + clock |= i << CLOCK_RATE_SHIFT; + } + info = cpu_to_be32(clock); + + if (completion_done(&dice->clock_accepted)) + reinit_completion(&dice->clock_accepted); + + err = snd_dice_transaction_write_global(dice, GLOBAL_CLOCK_SELECT, + &info, 4); + if (err < 0) + goto end; + + /* Timeout means it's invalid request, probably bus reset occurred. */ + if (wait_for_completion_timeout(&dice->clock_accepted, + msecs_to_jiffies(NOTIFICATION_TIMEOUT_MS)) == 0) { + if (retries-- == 0) { + err = -ETIMEDOUT; + goto end; + } + + err = snd_dice_transaction_reinit(dice); + if (err < 0) + goto end; + + msleep(500); /* arbitrary */ + goto retry; + } +end: + return err; +} + +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_set_clock_source(struct snd_dice *dice, + unsigned int source) +{ + return set_clock_info(dice, UINT_MAX, source); +} + +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_rate(struct snd_dice *dice, unsigned int rate) +{ + return set_clock_info(dice, rate, UINT_MAX); +} + +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); +} + +int snd_dice_transaction_init(struct snd_dice *dice) +{ + struct fw_address_handler *handler = &dice->notification_handler; + __be32 *pointers; + int err; + + /* Use the same way which dice_interface_check() does. */ + pointers = kmalloc(sizeof(__be32) * 10, GFP_KERNEL); + if (pointers == NULL) + return -ENOMEM; + + /* Get offsets for sub-addresses */ + err = snd_fw_transaction(dice->unit, TCODE_READ_BLOCK_REQUEST, + DICE_PRIVATE_SPACE, + pointers, sizeof(__be32) * 10, 0); + if (err < 0) + goto end; + + /* 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; + goto end; + } + + /* Register the address space */ + err = register_notification_address(dice, true); + if (err < 0) { + fw_core_remove_address_handler(handler); + handler->callback_data = NULL; + goto end; + } + + 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; + dice->sync_offset = be32_to_cpu(pointers[6]) * 4; + dice->rsrv_offset = be32_to_cpu(pointers[8]) * 4; + + /* Set up later. */ + if (be32_to_cpu(pointers[1]) * 4 >= GLOBAL_CLOCK_CAPABILITIES + 4) + dice->clock_caps = 1; +end: + kfree(pointers); + return err; +} |