| /* |
| * TC Applied Technologies Digital Interface Communications Engine driver |
| * |
| * Copyright (c) Clemens Ladisch <clemens@ladisch.de> |
| * Licensed under the terms of the GNU General Public License, version 2. |
| */ |
| |
| #include <linux/compat.h> |
| #include <linux/delay.h> |
| #include <linux/device.h> |
| #include <linux/firewire.h> |
| #include <linux/firewire-constants.h> |
| #include <linux/module.h> |
| #include <linux/mod_devicetable.h> |
| #include <linux/mutex.h> |
| #include <linux/slab.h> |
| #include <linux/spinlock.h> |
| #include <linux/wait.h> |
| #include <sound/control.h> |
| #include <sound/core.h> |
| #include <sound/firewire.h> |
| #include <sound/hwdep.h> |
| #include <sound/initval.h> |
| #include <sound/pcm.h> |
| #include <sound/pcm_params.h> |
| #include "amdtp.h" |
| #include "iso-resources.h" |
| #include "lib.h" |
| #include "dice-interface.h" |
| |
| |
| struct dice { |
| struct snd_card *card; |
| struct fw_unit *unit; |
| spinlock_t lock; |
| struct mutex mutex; |
| unsigned int global_offset; |
| unsigned int rx_offset; |
| struct fw_address_handler notification_handler; |
| int owner_generation; |
| int dev_lock_count; /* > 0 driver, < 0 userspace */ |
| bool dev_lock_changed; |
| bool global_enabled; |
| wait_queue_head_t hwdep_wait; |
| u32 notification_bits; |
| struct snd_pcm_substream *pcm; |
| struct fw_iso_resources resources; |
| struct amdtp_out_stream stream; |
| }; |
| |
| MODULE_DESCRIPTION("DICE driver"); |
| MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>"); |
| MODULE_LICENSE("GPL v2"); |
| |
| static const unsigned int dice_rates[] = { |
| [0] = 32000, |
| [1] = 44100, |
| [2] = 48000, |
| [3] = 88200, |
| [4] = 96000, |
| [5] = 176400, |
| [6] = 192000, |
| }; |
| |
| static void dice_lock_changed(struct dice *dice) |
| { |
| dice->dev_lock_changed = true; |
| wake_up(&dice->hwdep_wait); |
| } |
| |
| static int dice_try_lock(struct dice *dice) |
| { |
| int err; |
| |
| spin_lock_irq(&dice->lock); |
| |
| if (dice->dev_lock_count < 0) { |
| err = -EBUSY; |
| goto out; |
| } |
| |
| if (dice->dev_lock_count++ == 0) |
| dice_lock_changed(dice); |
| err = 0; |
| |
| out: |
| spin_unlock_irq(&dice->lock); |
| |
| return err; |
| } |
| |
| static void dice_unlock(struct dice *dice) |
| { |
| spin_lock_irq(&dice->lock); |
| |
| if (WARN_ON(dice->dev_lock_count <= 0)) |
| goto out; |
| |
| if (--dice->dev_lock_count == 0) |
| dice_lock_changed(dice); |
| |
| out: |
| spin_unlock_irq(&dice->lock); |
| } |
| |
| static inline u64 global_address(struct dice *dice, unsigned int offset) |
| { |
| return DICE_PRIVATE_SPACE + dice->global_offset + offset; |
| } |
| |
| // TODO: rx index |
| static inline u64 rx_address(struct dice *dice, unsigned int offset) |
| { |
| return DICE_PRIVATE_SPACE + dice->rx_offset + offset; |
| } |
| |
| static int dice_owner_set(struct dice *dice) |
| { |
| struct fw_device *device = fw_parent_device(dice->unit); |
| __be64 *buffer; |
| int rcode, err, errors = 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 */ |
| rcode = fw_run_transaction(device->card, |
| TCODE_LOCK_COMPARE_SWAP, |
| device->node_id, |
| dice->owner_generation, |
| device->max_speed, |
| global_address(dice, GLOBAL_OWNER), |
| buffer, 2 * 8); |
| |
| if (rcode == RCODE_COMPLETE) { |
| if (buffer[0] == cpu_to_be64(OWNER_NO_OWNER)) { |
| err = 0; |
| } else { |
| dev_err(&dice->unit->device, |
| "device is already in use\n"); |
| err = -EBUSY; |
| } |
| break; |
| } |
| if (rcode_is_permanent_error(rcode) || ++errors >= 3) { |
| dev_err(&dice->unit->device, |
| "setting device owner failed: %s\n", |
| fw_rcode_string(rcode)); |
| err = -EIO; |
| break; |
| } |
| msleep(20); |
| } |
| |
| kfree(buffer); |
| |
| return err; |
| } |
| |
| static int dice_owner_update(struct dice *dice) |
| { |
| struct fw_device *device = fw_parent_device(dice->unit); |
| __be64 *buffer; |
| int rcode, err, errors = 0; |
| |
| if (dice->owner_generation == -1) |
| return 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 */ |
| rcode = fw_run_transaction(device->card, |
| TCODE_LOCK_COMPARE_SWAP, |
| device->node_id, |
| dice->owner_generation, |
| device->max_speed, |
| global_address(dice, GLOBAL_OWNER), |
| buffer, 2 * 8); |
| |
| if (rcode == RCODE_COMPLETE) { |
| if (buffer[0] == cpu_to_be64(OWNER_NO_OWNER)) { |
| err = 0; |
| } else { |
| dev_err(&dice->unit->device, |
| "device is already in use\n"); |
| err = -EBUSY; |
| } |
| break; |
| } |
| if (rcode == RCODE_GENERATION) { |
| err = 0; /* try again later */ |
| break; |
| } |
| if (rcode_is_permanent_error(rcode) || ++errors >= 3) { |
| dev_err(&dice->unit->device, |
| "setting device owner failed: %s\n", |
| fw_rcode_string(rcode)); |
| err = -EIO; |
| break; |
| } |
| msleep(20); |
| } |
| |
| kfree(buffer); |
| |
| if (err < 0) |
| dice->owner_generation = -1; |
| |
| return err; |
| } |
| |
| static void dice_owner_clear(struct dice *dice) |
| { |
| struct fw_device *device = fw_parent_device(dice->unit); |
| __be64 *buffer; |
| int rcode, errors = 0; |
| |
| buffer = kmalloc(2 * 8, GFP_KERNEL); |
| if (!buffer) |
| return; |
| |
| for (;;) { |
| 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); |
| |
| rcode = fw_run_transaction(device->card, |
| TCODE_LOCK_COMPARE_SWAP, |
| device->node_id, |
| dice->owner_generation, |
| device->max_speed, |
| global_address(dice, GLOBAL_OWNER), |
| buffer, 2 * 8); |
| |
| if (rcode == RCODE_COMPLETE) |
| break; |
| if (rcode == RCODE_GENERATION) |
| break; |
| if (rcode_is_permanent_error(rcode) || ++errors >= 3) { |
| dev_err(&dice->unit->device, |
| "clearing device owner failed: %s\n", |
| fw_rcode_string(rcode)); |
| break; |
| } |
| msleep(20); |
| } |
| |
| kfree(buffer); |
| |
| dice->owner_generation = -1; |
| } |
| |
| static int dice_enable_set(struct dice *dice) |
| { |
| struct fw_device *device = fw_parent_device(dice->unit); |
| __be32 value; |
| int rcode, err, errors = 0; |
| |
| value = cpu_to_be32(1); |
| for (;;) { |
| rcode = fw_run_transaction(device->card, |
| TCODE_WRITE_QUADLET_REQUEST, |
| device->node_id, |
| dice->owner_generation, |
| device->max_speed, |
| global_address(dice, GLOBAL_ENABLE), |
| &value, 4); |
| if (rcode == RCODE_COMPLETE) { |
| dice->global_enabled = true; |
| err = 0; |
| break; |
| } |
| if (rcode == RCODE_GENERATION) { |
| err = -EAGAIN; |
| break; |
| } |
| if (rcode_is_permanent_error(rcode) || ++errors >= 3) { |
| dev_err(&dice->unit->device, |
| "device enabling failed: %s\n", |
| fw_rcode_string(rcode)); |
| err = -EIO; |
| break; |
| } |
| msleep(20); |
| } |
| |
| return err; |
| } |
| |
| static void dice_enable_clear(struct dice *dice) |
| { |
| struct fw_device *device = fw_parent_device(dice->unit); |
| __be32 value; |
| int rcode, errors = 0; |
| |
| value = 0; |
| for (;;) { |
| rcode = fw_run_transaction(device->card, |
| TCODE_WRITE_QUADLET_REQUEST, |
| device->node_id, |
| dice->owner_generation, |
| device->max_speed, |
| global_address(dice, GLOBAL_ENABLE), |
| &value, 4); |
| if (rcode == RCODE_COMPLETE || |
| rcode == RCODE_GENERATION) |
| break; |
| if (rcode_is_permanent_error(rcode) || ++errors >= 3) { |
| dev_err(&dice->unit->device, |
| "device disabling failed: %s\n", |
| fw_rcode_string(rcode)); |
| break; |
| } |
| msleep(20); |
| } |
| 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 dice *dice = callback_data; |
| 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; |
| } |
| spin_lock_irqsave(&dice->lock, flags); |
| dice->notification_bits |= be32_to_cpup(data); |
| spin_unlock_irqrestore(&dice->lock, flags); |
| fw_send_response(card, request, RCODE_COMPLETE); |
| wake_up(&dice->hwdep_wait); |
| } |
| |
| static int dice_open(struct snd_pcm_substream *substream) |
| { |
| static const struct snd_pcm_hardware hardware = { |
| .info = SNDRV_PCM_INFO_MMAP | |
| SNDRV_PCM_INFO_MMAP_VALID | |
| SNDRV_PCM_INFO_BATCH | |
| SNDRV_PCM_INFO_INTERLEAVED | |
| SNDRV_PCM_INFO_BLOCK_TRANSFER, |
| .formats = AMDTP_OUT_PCM_FORMAT_BITS, |
| .buffer_bytes_max = 16 * 1024 * 1024, |
| .period_bytes_min = 1, |
| .period_bytes_max = UINT_MAX, |
| .periods_min = 1, |
| .periods_max = UINT_MAX, |
| }; |
| struct dice *dice = substream->private_data; |
| struct snd_pcm_runtime *runtime = substream->runtime; |
| __be32 clock_sel, number_audio, number_midi; |
| unsigned int rate; |
| int err; |
| |
| err = dice_try_lock(dice); |
| if (err < 0) |
| goto error; |
| |
| err = snd_fw_transaction(dice->unit, TCODE_READ_QUADLET_REQUEST, |
| global_address(dice, GLOBAL_CLOCK_SELECT), |
| &clock_sel, 4); |
| if (err < 0) |
| goto err_lock; |
| rate = (be32_to_cpu(clock_sel) & CLOCK_RATE_MASK) >> CLOCK_RATE_SHIFT; |
| if (rate >= ARRAY_SIZE(dice_rates)) { |
| err = -ENXIO; |
| goto err_lock; |
| } |
| rate = dice_rates[rate]; |
| |
| err = snd_fw_transaction(dice->unit, TCODE_READ_QUADLET_REQUEST, |
| rx_address(dice, RX_NUMBER_AUDIO), |
| &number_audio, 4); |
| if (err < 0) |
| goto err_lock; |
| err = snd_fw_transaction(dice->unit, TCODE_READ_QUADLET_REQUEST, |
| rx_address(dice, RX_NUMBER_MIDI), |
| &number_midi, 4); |
| if (err < 0) |
| goto err_lock; |
| |
| runtime->hw = hardware; |
| |
| runtime->hw.rates = snd_pcm_rate_to_rate_bit(rate); |
| snd_pcm_limit_hw_rates(runtime); |
| |
| runtime->hw.channels_min = be32_to_cpu(number_audio); |
| runtime->hw.channels_max = be32_to_cpu(number_audio); |
| |
| amdtp_out_stream_set_rate(&dice->stream, rate); |
| amdtp_out_stream_set_pcm(&dice->stream, be32_to_cpu(number_audio)); |
| amdtp_out_stream_set_midi(&dice->stream, be32_to_cpu(number_midi)); |
| |
| err = snd_pcm_hw_constraint_minmax(runtime, |
| SNDRV_PCM_HW_PARAM_PERIOD_TIME, |
| 5000, 8192000); |
| if (err < 0) |
| goto err_lock; |
| |
| err = snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24); |
| if (err < 0) |
| goto err_lock; |
| |
| return 0; |
| |
| err_lock: |
| dice_unlock(dice); |
| error: |
| return err; |
| } |
| |
| static int dice_close(struct snd_pcm_substream *substream) |
| { |
| struct dice *dice = substream->private_data; |
| |
| dice_unlock(dice); |
| |
| return 0; |
| } |
| |
| static int dice_stream_start_packets(struct dice *dice) |
| { |
| int err; |
| |
| if (amdtp_out_stream_running(&dice->stream)) |
| return 0; |
| |
| err = amdtp_out_stream_start(&dice->stream, dice->resources.channel, |
| fw_parent_device(dice->unit)->max_speed); |
| if (err < 0) |
| return err; |
| |
| err = dice_enable_set(dice); |
| if (err < 0) { |
| amdtp_out_stream_stop(&dice->stream); |
| return err; |
| } |
| |
| return 0; |
| } |
| |
| static int dice_stream_start(struct dice *dice) |
| { |
| __be32 channel; |
| int err; |
| |
| if (!dice->resources.allocated) { |
| err = fw_iso_resources_allocate(&dice->resources, |
| amdtp_out_stream_get_max_payload(&dice->stream), |
| fw_parent_device(dice->unit)->max_speed); |
| if (err < 0) |
| goto error; |
| |
| channel = cpu_to_be32(dice->resources.channel); |
| err = snd_fw_transaction(dice->unit, |
| TCODE_WRITE_QUADLET_REQUEST, |
| rx_address(dice, RX_ISOCHRONOUS), |
| &channel, 4); |
| if (err < 0) |
| goto err_resources; |
| } |
| |
| err = dice_stream_start_packets(dice); |
| if (err < 0) |
| goto err_rx_channel; |
| |
| return 0; |
| |
| err_rx_channel: |
| channel = cpu_to_be32((u32)-1); |
| snd_fw_transaction(dice->unit, TCODE_WRITE_QUADLET_REQUEST, |
| rx_address(dice, RX_ISOCHRONOUS), &channel, 4); |
| err_resources: |
| fw_iso_resources_free(&dice->resources); |
| error: |
| return err; |
| } |
| |
| static void dice_stream_stop_packets(struct dice *dice) |
| { |
| if (amdtp_out_stream_running(&dice->stream)) { |
| dice_enable_clear(dice); |
| amdtp_out_stream_stop(&dice->stream); |
| } |
| } |
| |
| static void dice_stream_stop(struct dice *dice) |
| { |
| __be32 channel; |
| |
| dice_stream_stop_packets(dice); |
| |
| if (!dice->resources.allocated) |
| return; |
| |
| channel = cpu_to_be32((u32)-1); |
| snd_fw_transaction(dice->unit, TCODE_WRITE_QUADLET_REQUEST, |
| rx_address(dice, RX_ISOCHRONOUS), &channel, 4); |
| |
| fw_iso_resources_free(&dice->resources); |
| } |
| |
| static int dice_hw_params(struct snd_pcm_substream *substream, |
| struct snd_pcm_hw_params *hw_params) |
| { |
| struct dice *dice = substream->private_data; |
| int err; |
| |
| mutex_lock(&dice->mutex); |
| dice_stream_stop(dice); |
| mutex_unlock(&dice->mutex); |
| |
| err = snd_pcm_lib_alloc_vmalloc_buffer(substream, |
| params_buffer_bytes(hw_params)); |
| if (err < 0) |
| goto error; |
| |
| amdtp_out_stream_set_pcm_format(&dice->stream, |
| params_format(hw_params)); |
| |
| return 0; |
| |
| error: |
| return err; |
| } |
| |
| static int dice_hw_free(struct snd_pcm_substream *substream) |
| { |
| struct dice *dice = substream->private_data; |
| |
| mutex_lock(&dice->mutex); |
| dice_stream_stop(dice); |
| mutex_unlock(&dice->mutex); |
| |
| return snd_pcm_lib_free_vmalloc_buffer(substream); |
| } |
| |
| static int dice_prepare(struct snd_pcm_substream *substream) |
| { |
| struct dice *dice = substream->private_data; |
| int err; |
| |
| mutex_lock(&dice->mutex); |
| |
| if (amdtp_out_streaming_error(&dice->stream)) |
| dice_stream_stop_packets(dice); |
| |
| err = dice_stream_start(dice); |
| if (err < 0) { |
| mutex_unlock(&dice->mutex); |
| return err; |
| } |
| |
| mutex_unlock(&dice->mutex); |
| |
| amdtp_out_stream_pcm_prepare(&dice->stream); |
| |
| return 0; |
| } |
| |
| static int dice_trigger(struct snd_pcm_substream *substream, int cmd) |
| { |
| struct dice *dice = substream->private_data; |
| struct snd_pcm_substream *pcm; |
| |
| switch (cmd) { |
| case SNDRV_PCM_TRIGGER_START: |
| pcm = substream; |
| break; |
| case SNDRV_PCM_TRIGGER_STOP: |
| pcm = NULL; |
| break; |
| default: |
| return -EINVAL; |
| } |
| amdtp_out_stream_pcm_trigger(&dice->stream, pcm); |
| |
| return 0; |
| } |
| |
| static snd_pcm_uframes_t dice_pointer(struct snd_pcm_substream *substream) |
| { |
| struct dice *dice = substream->private_data; |
| |
| return amdtp_out_stream_pcm_pointer(&dice->stream); |
| } |
| |
| static int dice_create_pcm(struct dice *dice) |
| { |
| static struct snd_pcm_ops ops = { |
| .open = dice_open, |
| .close = dice_close, |
| .ioctl = snd_pcm_lib_ioctl, |
| .hw_params = dice_hw_params, |
| .hw_free = dice_hw_free, |
| .prepare = dice_prepare, |
| .trigger = dice_trigger, |
| .pointer = dice_pointer, |
| .page = snd_pcm_lib_get_vmalloc_page, |
| .mmap = snd_pcm_lib_mmap_vmalloc, |
| }; |
| struct snd_pcm *pcm; |
| int err; |
| |
| err = snd_pcm_new(dice->card, "DICE", 0, 1, 0, &pcm); |
| if (err < 0) |
| return err; |
| pcm->private_data = dice; |
| strcpy(pcm->name, dice->card->shortname); |
| dice->pcm = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; |
| dice->pcm->ops = &ops; |
| |
| return 0; |
| } |
| |
| static long dice_hwdep_read(struct snd_hwdep *hwdep, char __user *buf, |
| long count, loff_t *offset) |
| { |
| struct dice *dice = hwdep->private_data; |
| DEFINE_WAIT(wait); |
| union snd_firewire_event event; |
| |
| spin_lock_irq(&dice->lock); |
| |
| while (!dice->dev_lock_changed && dice->notification_bits == 0) { |
| prepare_to_wait(&dice->hwdep_wait, &wait, TASK_INTERRUPTIBLE); |
| spin_unlock_irq(&dice->lock); |
| schedule(); |
| finish_wait(&dice->hwdep_wait, &wait); |
| if (signal_pending(current)) |
| return -ERESTARTSYS; |
| spin_lock_irq(&dice->lock); |
| } |
| |
| memset(&event, 0, sizeof(event)); |
| if (dice->dev_lock_changed) { |
| event.lock_status.type = SNDRV_FIREWIRE_EVENT_LOCK_STATUS; |
| event.lock_status.status = dice->dev_lock_count > 0; |
| dice->dev_lock_changed = false; |
| |
| count = min(count, (long)sizeof(event.lock_status)); |
| } else { |
| event.dice_notification.type = SNDRV_FIREWIRE_EVENT_DICE_NOTIFICATION; |
| event.dice_notification.notification = dice->notification_bits; |
| dice->notification_bits = 0; |
| |
| count = min(count, (long)sizeof(event.dice_notification)); |
| } |
| |
| spin_unlock_irq(&dice->lock); |
| |
| if (copy_to_user(buf, &event, count)) |
| return -EFAULT; |
| |
| return count; |
| } |
| |
| static unsigned int dice_hwdep_poll(struct snd_hwdep *hwdep, struct file *file, |
| poll_table *wait) |
| { |
| struct dice *dice = hwdep->private_data; |
| unsigned int events; |
| |
| poll_wait(file, &dice->hwdep_wait, wait); |
| |
| spin_lock_irq(&dice->lock); |
| if (dice->dev_lock_changed || dice->notification_bits != 0) |
| events = POLLIN | POLLRDNORM; |
| else |
| events = 0; |
| spin_unlock_irq(&dice->lock); |
| |
| return events; |
| } |
| |
| static int dice_hwdep_get_info(struct dice *dice, void __user *arg) |
| { |
| struct fw_device *dev = fw_parent_device(dice->unit); |
| struct snd_firewire_get_info info; |
| |
| memset(&info, 0, sizeof(info)); |
| info.type = SNDRV_FIREWIRE_TYPE_DICE; |
| info.card = dev->card->index; |
| *(__be32 *)&info.guid[0] = cpu_to_be32(dev->config_rom[3]); |
| *(__be32 *)&info.guid[4] = cpu_to_be32(dev->config_rom[4]); |
| strlcpy(info.device_name, dev_name(&dev->device), |
| sizeof(info.device_name)); |
| |
| if (copy_to_user(arg, &info, sizeof(info))) |
| return -EFAULT; |
| |
| return 0; |
| } |
| |
| static int dice_hwdep_lock(struct dice *dice) |
| { |
| int err; |
| |
| spin_lock_irq(&dice->lock); |
| |
| if (dice->dev_lock_count == 0) { |
| dice->dev_lock_count = -1; |
| err = 0; |
| } else { |
| err = -EBUSY; |
| } |
| |
| spin_unlock_irq(&dice->lock); |
| |
| return err; |
| } |
| |
| static int dice_hwdep_unlock(struct dice *dice) |
| { |
| int err; |
| |
| spin_lock_irq(&dice->lock); |
| |
| if (dice->dev_lock_count == -1) { |
| dice->dev_lock_count = 0; |
| err = 0; |
| } else { |
| err = -EBADFD; |
| } |
| |
| spin_unlock_irq(&dice->lock); |
| |
| return err; |
| } |
| |
| static int dice_hwdep_release(struct snd_hwdep *hwdep, struct file *file) |
| { |
| struct dice *dice = hwdep->private_data; |
| |
| spin_lock_irq(&dice->lock); |
| if (dice->dev_lock_count == -1) |
| dice->dev_lock_count = 0; |
| spin_unlock_irq(&dice->lock); |
| |
| return 0; |
| } |
| |
| static int dice_hwdep_ioctl(struct snd_hwdep *hwdep, struct file *file, |
| unsigned int cmd, unsigned long arg) |
| { |
| struct dice *dice = hwdep->private_data; |
| |
| switch (cmd) { |
| case SNDRV_FIREWIRE_IOCTL_GET_INFO: |
| return dice_hwdep_get_info(dice, (void __user *)arg); |
| case SNDRV_FIREWIRE_IOCTL_LOCK: |
| return dice_hwdep_lock(dice); |
| case SNDRV_FIREWIRE_IOCTL_UNLOCK: |
| return dice_hwdep_unlock(dice); |
| default: |
| return -ENOIOCTLCMD; |
| } |
| } |
| |
| #ifdef CONFIG_COMPAT |
| static int dice_hwdep_compat_ioctl(struct snd_hwdep *hwdep, struct file *file, |
| unsigned int cmd, unsigned long arg) |
| { |
| return dice_hwdep_ioctl(hwdep, file, cmd, |
| (unsigned long)compat_ptr(arg)); |
| } |
| #else |
| #define dice_hwdep_compat_ioctl NULL |
| #endif |
| |
| static int dice_create_hwdep(struct dice *dice) |
| { |
| static const struct snd_hwdep_ops ops = { |
| .read = dice_hwdep_read, |
| .release = dice_hwdep_release, |
| .poll = dice_hwdep_poll, |
| .ioctl = dice_hwdep_ioctl, |
| .ioctl_compat = dice_hwdep_compat_ioctl, |
| }; |
| struct snd_hwdep *hwdep; |
| int err; |
| |
| err = snd_hwdep_new(dice->card, "DICE", 0, &hwdep); |
| if (err < 0) |
| return err; |
| strcpy(hwdep->name, "DICE"); |
| hwdep->iface = SNDRV_HWDEP_IFACE_FW_DICE; |
| hwdep->ops = ops; |
| hwdep->private_data = dice; |
| hwdep->exclusive = true; |
| |
| return 0; |
| } |
| |
| static void dice_card_free(struct snd_card *card) |
| { |
| struct dice *dice = card->private_data; |
| |
| amdtp_out_stream_destroy(&dice->stream); |
| fw_core_remove_address_handler(&dice->notification_handler); |
| mutex_destroy(&dice->mutex); |
| } |
| |
| static int dice_init_offsets(struct dice *dice) |
| { |
| __be32 pointers[6]; |
| unsigned int global_size, rx_size; |
| int err; |
| |
| err = snd_fw_transaction(dice->unit, TCODE_READ_BLOCK_REQUEST, |
| DICE_PRIVATE_SPACE, &pointers, 6 * 4); |
| if (err < 0) |
| return err; |
| |
| dice->global_offset = be32_to_cpu(pointers[0]) * 4; |
| global_size = be32_to_cpu(pointers[1]); |
| dice->rx_offset = be32_to_cpu(pointers[4]) * 4; |
| rx_size = be32_to_cpu(pointers[5]); |
| |
| /* some sanity checks to ensure that we actually have a DICE */ |
| if (dice->global_offset < 10 * 4 || global_size < 0x168 / 4 || |
| dice->rx_offset < 10 * 4 || rx_size < 0x120 / 4) { |
| dev_err(&dice->unit->device, "invalid register pointers\n"); |
| return -ENXIO; |
| } |
| |
| return 0; |
| } |
| |
| static void dice_card_strings(struct dice *dice) |
| { |
| struct snd_card *card = dice->card; |
| struct fw_device *dev = fw_parent_device(dice->unit); |
| char vendor[32], model[32]; |
| unsigned int i; |
| int err; |
| |
| strcpy(card->driver, "DICE"); |
| |
| strcpy(card->shortname, "DICE"); |
| BUILD_BUG_ON(NICK_NAME_SIZE < sizeof(card->shortname)); |
| err = snd_fw_transaction(dice->unit, TCODE_READ_BLOCK_REQUEST, |
| global_address(dice, GLOBAL_NICK_NAME), |
| card->shortname, sizeof(card->shortname)); |
| if (err >= 0) { |
| /* DICE strings are returned in "always-wrong" endianness */ |
| BUILD_BUG_ON(sizeof(card->shortname) % 4 != 0); |
| for (i = 0; i < sizeof(card->shortname); i += 4) |
| swab32s((u32 *)&card->shortname[i]); |
| card->shortname[sizeof(card->shortname) - 1] = '\0'; |
| } |
| |
| strcpy(vendor, "?"); |
| fw_csr_string(dev->config_rom + 5, CSR_VENDOR, vendor, sizeof(vendor)); |
| strcpy(model, "?"); |
| fw_csr_string(dice->unit->directory, CSR_MODEL, model, sizeof(model)); |
| snprintf(card->longname, sizeof(card->longname), |
| "%s %s, GUID %08x%08x at %s, S%d", |
| vendor, model, dev->config_rom[3], dev->config_rom[4], |
| dev_name(&dice->unit->device), 100 << dev->max_speed); |
| |
| strcpy(card->mixername, "DICE"); |
| } |
| |
| static int dice_probe(struct fw_unit *unit, const struct ieee1394_device_id *id) |
| { |
| struct snd_card *card; |
| struct dice *dice; |
| __be32 clock_sel; |
| int err; |
| |
| err = snd_card_create(-1, NULL, THIS_MODULE, sizeof(*dice), &card); |
| if (err < 0) |
| return err; |
| snd_card_set_dev(card, &unit->device); |
| |
| dice = card->private_data; |
| dice->card = card; |
| spin_lock_init(&dice->lock); |
| mutex_init(&dice->mutex); |
| dice->unit = unit; |
| init_waitqueue_head(&dice->hwdep_wait); |
| |
| err = dice_init_offsets(dice); |
| if (err < 0) |
| goto err_mutex; |
| |
| dice->notification_handler.length = 4; |
| dice->notification_handler.address_callback = dice_notification; |
| dice->notification_handler.callback_data = dice; |
| err = fw_core_add_address_handler(&dice->notification_handler, |
| &fw_high_memory_region); |
| if (err < 0) |
| goto err_mutex; |
| |
| err = fw_iso_resources_init(&dice->resources, unit); |
| if (err < 0) |
| goto err_notification_handler; |
| dice->resources.channels_mask = 0x00000000ffffffffuLL; |
| |
| err = amdtp_out_stream_init(&dice->stream, unit, CIP_BLOCKING); |
| if (err < 0) |
| goto err_resources; |
| |
| err = dice_owner_set(dice); |
| if (err < 0) |
| goto err_stream; |
| |
| card->private_free = dice_card_free; |
| |
| dice_card_strings(dice); |
| |
| err = snd_fw_transaction(unit, TCODE_READ_QUADLET_REQUEST, |
| global_address(dice, GLOBAL_CLOCK_SELECT), |
| &clock_sel, 4); |
| if (err < 0) |
| goto error; |
| clock_sel &= cpu_to_be32(~CLOCK_SOURCE_MASK); |
| clock_sel |= cpu_to_be32(CLOCK_SOURCE_ARX1); |
| err = snd_fw_transaction(unit, TCODE_WRITE_QUADLET_REQUEST, |
| global_address(dice, GLOBAL_CLOCK_SELECT), |
| &clock_sel, 4); |
| if (err < 0) |
| goto error; |
| |
| err = dice_create_pcm(dice); |
| if (err < 0) |
| goto error; |
| |
| err = dice_create_hwdep(dice); |
| if (err < 0) |
| goto error; |
| |
| err = snd_card_register(card); |
| if (err < 0) |
| goto error; |
| |
| dev_set_drvdata(&unit->device, dice); |
| |
| return 0; |
| |
| err_stream: |
| amdtp_out_stream_destroy(&dice->stream); |
| err_resources: |
| fw_iso_resources_destroy(&dice->resources); |
| err_notification_handler: |
| fw_core_remove_address_handler(&dice->notification_handler); |
| err_mutex: |
| mutex_destroy(&dice->mutex); |
| error: |
| snd_card_free(card); |
| return err; |
| } |
| |
| static void dice_remove(struct fw_unit *unit) |
| { |
| struct dice *dice = dev_get_drvdata(&unit->device); |
| |
| mutex_lock(&dice->mutex); |
| |
| amdtp_out_stream_pcm_abort(&dice->stream); |
| |
| snd_card_disconnect(dice->card); |
| |
| dice_stream_stop(dice); |
| dice_owner_clear(dice); |
| |
| mutex_unlock(&dice->mutex); |
| |
| snd_card_free_when_closed(dice->card); |
| } |
| |
| static void dice_bus_reset(struct fw_unit *unit) |
| { |
| struct dice *dice = dev_get_drvdata(&unit->device); |
| |
| mutex_lock(&dice->mutex); |
| |
| /* |
| * On a bus reset, the DICE firmware disables streaming and then goes |
| * off contemplating its own navel for hundreds of milliseconds before |
| * it can react to any of our attempts to reenable streaming. This |
| * means that we lose synchronization anyway, so we force our streams |
| * to stop so that the application can restart them in an orderly |
| * manner. |
| */ |
| amdtp_out_stream_pcm_abort(&dice->stream); |
| dice_stream_stop_packets(dice); |
| |
| dice_owner_update(dice); |
| |
| fw_iso_resources_update(&dice->resources); |
| |
| mutex_unlock(&dice->mutex); |
| } |
| |
| #define TC_OUI 0x000166 |
| #define DICE_INTERFACE 0x000001 |
| |
| static const struct ieee1394_device_id dice_id_table[] = { |
| { |
| .match_flags = IEEE1394_MATCH_SPECIFIER_ID | |
| IEEE1394_MATCH_VERSION, |
| .specifier_id = TC_OUI, |
| .version = DICE_INTERFACE, |
| }, |
| { } |
| }; |
| MODULE_DEVICE_TABLE(ieee1394, dice_id_table); |
| |
| static struct fw_driver dice_driver = { |
| .driver = { |
| .owner = THIS_MODULE, |
| .name = KBUILD_MODNAME, |
| .bus = &fw_bus_type, |
| }, |
| .probe = dice_probe, |
| .update = dice_bus_reset, |
| .remove = dice_remove, |
| .id_table = dice_id_table, |
| }; |
| |
| static int __init alsa_dice_init(void) |
| { |
| return driver_register(&dice_driver.driver); |
| } |
| |
| static void __exit alsa_dice_exit(void) |
| { |
| driver_unregister(&dice_driver.driver); |
| } |
| |
| module_init(alsa_dice_init); |
| module_exit(alsa_dice_exit); |