mirror of
https://github.com/torvalds/linux.git
synced 2024-12-22 10:56:40 +00:00
8fc01fc067
This commit allows this driver to handle devices with non SYT-Match sampling clock source. When sampling clock source is SYT-Match mode, devices handle 'presentation timestamp' in received packets and generates sampling clock according to the information. In this case, driver is synchronization master and must transfer correct value in SYT field of each packets in outgoing stream, then the outgoing stream is a master stream. On the other hand, non SYT-Match mode, devices do this. So drivers must pick up the value in SYT field of incoming packets and use the value for outgoing stream. Currently firewire-lib module achieve this work. Furthermore, without SYT-Match and internal clock source, the sampling rate should be fixed for the other devices connected to the handled device. This commit add a restriction of sampling rate at this situation. With these implementations, this driver has no need to set clock source. This commit remove set function. Signed-off-by: Takashi Sakamoto <o-takashi@sakamocchi.jp> Acked-by: Clemens Ladisch <clemens@ladisch.de> Signed-off-by: Takashi Iwai <tiwai@suse.de>
383 lines
8.9 KiB
C
383 lines
8.9 KiB
C
/*
|
|
* 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;
|
|
}
|