/* * TC Applied Technologies Digital Interface Communications Engine driver * * Copyright (c) Clemens Ladisch * Licensed under the terms of the GNU General Public License, version 2. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "amdtp.h" #include "iso-resources.h" #include "lib.h" #define DICE_PRIVATE_SPACE 0xffffe0000000uLL /* offset from DICE_PRIVATE_SPACE; offsets and sizes in quadlets */ #define DICE_GLOBAL_OFFSET 0x00 #define DICE_GLOBAL_SIZE 0x04 #define DICE_TX_OFFSET 0x08 #define DICE_TX_SIZE 0x0c #define DICE_RX_OFFSET 0x10 #define DICE_RX_SIZE 0x14 /* pointed to by DICE_GLOBAL_OFFSET */ #define GLOBAL_OWNER 0x000 #define OWNER_NO_OWNER 0xffff000000000000uLL #define OWNER_NODE_SHIFT 48 #define GLOBAL_NOTIFICATION 0x008 #define NOTIFY_RX_CFG_CHG 0x00000001 #define NOTIFY_TX_CFG_CHG 0x00000002 #define NOTIFY_DUP_ISOC 0x00000004 #define NOTIFY_BW_ERR 0x00000008 #define NOTIFY_LOCK_CHG 0x00000010 #define NOTIFY_CLOCK_ACCEPTED 0x00000020 #define NOTIFY_INTERFACE_CHG 0x00000040 #define NOTIFY_MESSAGE 0x00100000 #define GLOBAL_NICK_NAME 0x00c #define NICK_NAME_SIZE 64 #define GLOBAL_CLOCK_SELECT 0x04c #define CLOCK_SOURCE_MASK 0x000000ff #define CLOCK_SOURCE_AES1 0x00000000 #define CLOCK_SOURCE_AES2 0x00000001 #define CLOCK_SOURCE_AES3 0x00000002 #define CLOCK_SOURCE_AES4 0x00000003 #define CLOCK_SOURCE_AES_ANY 0x00000004 #define CLOCK_SOURCE_ADAT 0x00000005 #define CLOCK_SOURCE_TDIF 0x00000006 #define CLOCK_SOURCE_WC 0x00000007 #define CLOCK_SOURCE_ARX1 0x00000008 #define CLOCK_SOURCE_ARX2 0x00000009 #define CLOCK_SOURCE_ARX3 0x0000000a #define CLOCK_SOURCE_ARX4 0x0000000b #define CLOCK_SOURCE_INTERNAL 0x0000000c #define CLOCK_RATE_MASK 0x0000ff00 #define CLOCK_RATE_32000 0x00000000 #define CLOCK_RATE_44100 0x00000100 #define CLOCK_RATE_48000 0x00000200 #define CLOCK_RATE_88200 0x00000300 #define CLOCK_RATE_96000 0x00000400 #define CLOCK_RATE_176400 0x00000500 #define CLOCK_RATE_192000 0x00000600 #define CLOCK_RATE_ANY_LOW 0x00000700 #define CLOCK_RATE_ANY_MID 0x00000800 #define CLOCK_RATE_ANY_HIGH 0x00000900 #define CLOCK_RATE_NONE 0x00000a00 #define CLOCK_RATE_SHIFT 8 #define GLOBAL_ENABLE 0x050 #define ENABLE 0x00000001 #define GLOBAL_STATUS 0x054 #define STATUS_SOURCE_LOCKED 0x00000001 #define STATUS_RATE_CONFLICT 0x00000002 #define STATUS_NOMINAL_RATE_MASK 0x0000ff00 #define GLOBAL_EXTENDED_STATUS 0x058 #define EXT_STATUS_AES1_LOCKED 0x00000001 #define EXT_STATUS_AES2_LOCKED 0x00000002 #define EXT_STATUS_AES3_LOCKED 0x00000004 #define EXT_STATUS_AES4_LOCKED 0x00000008 #define EXT_STATUS_ADAT_LOCKED 0x00000010 #define EXT_STATUS_TDIF_LOCKED 0x00000020 #define EXT_STATUS_ARX1_LOCKED 0x00000040 #define EXT_STATUS_ARX2_LOCKED 0x00000080 #define EXT_STATUS_ARX3_LOCKED 0x00000100 #define EXT_STATUS_ARX4_LOCKED 0x00000200 #define EXT_STATUS_WC_LOCKED 0x00000400 #define EXT_STATUS_AES1_SLIP 0x00010000 #define EXT_STATUS_AES2_SLIP 0x00020000 #define EXT_STATUS_AES3_SLIP 0x00040000 #define EXT_STATUS_AES4_SLIP 0x00080000 #define EXT_STATUS_ADAT_SLIP 0x00100000 #define EXT_STATUS_TDIF_SLIP 0x00200000 #define EXT_STATUS_ARX1_SLIP 0x00400000 #define EXT_STATUS_ARX2_SLIP 0x00800000 #define EXT_STATUS_ARX3_SLIP 0x01000000 #define EXT_STATUS_ARX4_SLIP 0x02000000 #define EXT_STATUS_WC_SLIP 0x04000000 #define GLOBAL_SAMPLE_RATE 0x05c #define GLOBAL_VERSION 0x060 #define GLOBAL_CLOCK_CAPABILITIES 0x064 #define CLOCK_CAP_RATE_32000 0x00000001 #define CLOCK_CAP_RATE_44100 0x00000002 #define CLOCK_CAP_RATE_48000 0x00000004 #define CLOCK_CAP_RATE_88200 0x00000008 #define CLOCK_CAP_RATE_96000 0x00000010 #define CLOCK_CAP_RATE_176400 0x00000020 #define CLOCK_CAP_RATE_192000 0x00000040 #define CLOCK_CAP_SOURCE_AES1 0x00010000 #define CLOCK_CAP_SOURCE_AES2 0x00020000 #define CLOCK_CAP_SOURCE_AES3 0x00040000 #define CLOCK_CAP_SOURCE_AES4 0x00080000 #define CLOCK_CAP_SOURCE_AES_ANY 0x00100000 #define CLOCK_CAP_SOURCE_ADAT 0x00200000 #define CLOCK_CAP_SOURCE_TDIF 0x00400000 #define CLOCK_CAP_SOURCE_WC 0x00800000 #define CLOCK_CAP_SOURCE_ARX1 0x01000000 #define CLOCK_CAP_SOURCE_ARX2 0x02000000 #define CLOCK_CAP_SOURCE_ARX3 0x04000000 #define CLOCK_CAP_SOURCE_ARX4 0x08000000 #define CLOCK_CAP_SOURCE_INTERNAL 0x10000000 #define GLOBAL_CLOCK_SOURCE_NAMES 0x068 #define CLOCK_SOURCE_NAMES_SIZE 256 /* pointed to by DICE_TX_OFFSET */ #define TX_NUMBER 0x000 #define TX_SIZE 0x004 /* repeated TX_NUMBER times, offset by TX_SIZE quadlets */ #define TX_ISOCHRONOUS 0x008 #define TX_NUMBER_AUDIO 0x00c #define TX_NUMBER_MIDI 0x010 #define TX_SPEED 0x014 #define TX_NAMES 0x018 #define TX_NAMES_SIZE 256 #define TX_AC3_CAPABILITIES 0x118 #define TX_AC3_ENABLE 0x11c /* pointed to by DICE_RX_OFFSET */ #define RX_NUMBER 0x000 #define RX_SIZE 0x004 /* repeated RX_NUMBER times, offset by RX_SIZE quadlets */ #define RX_ISOCHRONOUS 0x008 #define RX_SEQ_START 0x00c #define RX_NUMBER_AUDIO 0x010 #define RX_NUMBER_MIDI 0x014 #define RX_NAMES 0x018 #define RX_NAMES_SIZE 256 #define RX_AC3_CAPABILITIES 0x118 #define RX_AC3_ENABLE 0x11c #define FIRMWARE_LOAD_SPACE 0xffffe0100000uLL /* offset from FIRMWARE_LOAD_SPACE */ #define FIRMWARE_VERSION 0x000 #define FIRMWARE_OPCODE 0x004 #define OPCODE_MASK 0x00000fff #define OPCODE_GET_IMAGE_DESC 0x00000000 #define OPCODE_DELETE_IMAGE 0x00000001 #define OPCODE_CREATE_IMAGE 0x00000002 #define OPCODE_UPLOAD 0x00000003 #define OPCODE_UPLOAD_STAT 0x00000004 #define OPCODE_RESET_IMAGE 0x00000005 #define OPCODE_TEST_ACTION 0x00000006 #define OPCODE_GET_RUNNING_IMAGE_VINFO 0x0000000a #define OPCODE_EXECUTE 0x80000000 #define FIRMWARE_RETURN_STATUS 0x008 #define FIRMWARE_PROGRESS 0x00c #define PROGRESS_CURR_MASK 0x00000fff #define PROGRESS_MAX_MASK 0x00fff000 #define PROGRESS_TOUT_MASK 0x0f000000 #define PROGRESS_FLAG 0x80000000 #define FIRMWARE_CAPABILITIES 0x010 #define FL_CAP_AUTOERASE 0x00000001 #define FL_CAP_PROGRESS 0x00000002 #define FIRMWARE_DATA 0x02c #define TEST_CMD_POKE 0x00000001 #define TEST_CMD_PEEK 0x00000002 #define CMD_GET_AVS_CNT 0x00000003 #define CMD_CLR_AVS_CNT 0x00000004 #define CMD_SET_MODE 0x00000005 #define CMD_SET_MIDIBP 0x00000006 #define CMD_GET_AVSPHASE 0x00000007 #define CMD_ENABLE_BNC_SYNC 0x00000008 #define CMD_PULSE_BNC_SYNC 0x00000009 #define CMD_EMUL_SLOW_CMD 0x0000000a #define FIRMWARE_TEST_DELAY 0xfd8 #define FIRMWARE_TEST_BUF 0xfdc /* EAP */ #define EAP_PRIVATE_SPACE 0xffffe0200000uLL #define EAP_CAPABILITY_OFFSET 0x000 #define EAP_CAPABILITY_SIZE 0x004 /* ... */ #define EAP_ROUTER_CAPS 0x000 #define ROUTER_EXPOSED 0x00000001 #define ROUTER_READ_ONLY 0x00000002 #define ROUTER_FLASH 0x00000004 #define MAX_ROUTES_MASK 0xffff0000 #define EAP_MIXER_CAPS 0x004 #define MIXER_EXPOSED 0x00000001 #define MIXER_READ_ONLY 0x00000002 #define MIXER_FLASH 0x00000004 #define MIXER_IN_DEV_MASK 0x000000f0 #define MIXER_OUT_DEV_MASK 0x00000f00 #define MIXER_INPUTS_MASK 0x00ff0000 #define MIXER_OUTPUTS_MASK 0xff000000 #define EAP_GENERAL_CAPS 0x008 #define GENERAL_STREAM_CONFIG 0x00000001 #define GENERAL_FLASH 0x00000002 #define GENERAL_PEAK 0x00000004 #define GENERAL_MAX_TX_STREAMS_MASK 0x000000f0 #define GENERAL_MAX_RX_STREAMS_MASK 0x00000f00 #define GENERAL_STREAM_CONFIG_FLASH 0x00001000 #define GENERAL_CHIP_MASK 0x00ff0000 #define GENERAL_CHIP_DICE_II 0x00000000 #define GENERAL_CHIP_DICE_MINI 0x00010000 #define GENERAL_CHIP_DICE_JR 0x00020000 struct dice { struct snd_card *card; struct fw_unit *unit; struct mutex mutex; unsigned int global_offset; unsigned int rx_offset; struct fw_address_handler notification_handler; int owner_generation; bool global_enabled; bool stream_running; struct snd_pcm_substream *pcm; struct fw_iso_resources resources; struct amdtp_out_stream stream; }; MODULE_DESCRIPTION("DICE driver"); MODULE_AUTHOR("Clemens Ladisch "); 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 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(ENABLE); 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; 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; } dev_dbg(&dice->unit->device, "notification: %08x\n", be32_to_cpup(data)); fw_send_response(card, request, RCODE_COMPLETE); } 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 = snd_fw_transaction(dice->unit, TCODE_READ_QUADLET_REQUEST, global_address(dice, GLOBAL_CLOCK_SELECT), &clock_sel, 4); if (err < 0) return err; rate = (be32_to_cpu(clock_sel) & CLOCK_RATE_MASK) >> CLOCK_RATE_SHIFT; if (rate >= ARRAY_SIZE(dice_rates)) return -ENXIO; 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) return err; err = snd_fw_transaction(dice->unit, TCODE_READ_QUADLET_REQUEST, rx_address(dice, RX_NUMBER_MIDI), &number_midi, 4); if (err < 0) return err; 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) return err; err = snd_pcm_hw_constraint_msbits(runtime, 0, 32, 24); if (err < 0) return err; return 0; } static int dice_close(struct snd_pcm_substream *substream) { return 0; } static int dice_stream_start_packets(struct dice *dice) { int err; if (dice->stream_running) 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; } dice->stream_running = true; 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 (!dice->stream_running) return; dice_enable_clear(dice); amdtp_out_stream_stop(&dice->stream); dice->stream_running = false; } 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; } // TODO: implement these static long dice_hwdep_read(struct snd_hwdep *hwdep, char __user *buf, long count, loff_t *offset) { return -EIO; } static int dice_hwdep_open(struct snd_hwdep *hwdep, struct file *file) { return -EIO; } static int dice_hwdep_release(struct snd_hwdep *hwdep, struct file *file) { return 0; } static unsigned int dice_hwdep_poll(struct snd_hwdep *hwdep, struct file *file, poll_table *wait) { return POLLERR | POLLHUP; } static int dice_hwdep_ioctl(struct snd_hwdep *hwdep, struct file *file, unsigned int cmd, unsigned long arg) { return -EIO; } static int dice_create_hwdep(struct dice *dice) { static const struct snd_hwdep_ops ops = { .read = dice_hwdep_read, .open = dice_hwdep_open, .release = dice_hwdep_release, .poll = dice_hwdep_poll, .ioctl = dice_hwdep_ioctl, .ioctl_compat = dice_hwdep_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; mutex_init(&dice->mutex); dice->unit = unit; 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_NONBLOCKING); 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); snd_card_disconnect(dice->card); mutex_lock(&dice->mutex); amdtp_out_stream_pcm_abort(&dice->stream); 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);