Add the rt linux 4.1.3-rt3 as base
[kvmfornfv.git] / kernel / sound / firewire / dice / dice-transaction.c
diff --git a/kernel/sound/firewire/dice/dice-transaction.c b/kernel/sound/firewire/dice/dice-transaction.c
new file mode 100644 (file)
index 0000000..aee7461
--- /dev/null
@@ -0,0 +1,382 @@
+/*
+ * 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_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;
+}