/* * Linux driver for TerraTec DMX 6Fire USB * * ALSA PCM interface. * * Author: Torsten Schenk * Created: Jan 01, 2011 * Copyright: (C) Torsten Schenk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. */ //TODO URL: http://linux.die.net/man/1/iecset http://alsa.opensrc.org/DigitalOut http://lxr.free-electrons.com/source/sound/pci/hda/hda_codec.c#L2705 http://www.epanorama.net/documents/audio/spdif.html http://www.cs.fsu.edu/~baker/devices/lxr/http/source/linux/include/sound/control.h#L169 #include "pcm.h" #include "chip.h" #include "substream.h" #include "urbs.h" enum { MAX_BUFSIZE = 1024 * 1024, MIN_PERIODS = 2, MAX_PERIODS = 64 }; static const struct snd_pcm_hardware pcm_hw = { .info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_BATCH, .channels_min = 1, .period_bytes_max = MAX_BUFSIZE / MIN_PERIODS, .periods_min = MIN_PERIODS, .buffer_bytes_max = MAX_BUFSIZE, .periods_max = MAX_PERIODS }; static int usb6fire_pcm_rule_period_size(struct snd_pcm_hw_params *params, struct snd_pcm_hw_rule *rule) { struct snd_interval *sri = hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); struct snd_interval *psi = hw_param_interval(params, SNDRV_PCM_HW_PARAM_PERIOD_SIZE); struct sfire_chip *chip = rule->private; struct urbs_runtime *urbs_rt = chip->urbs; unsigned int min_size; if (chip->shutdown || !urbs_rt) return -EPIPE; min_size = urbs_rt->get_min_period_size(urbs_rt, sri->max); if (!min_size) return -EINVAL; if (psi->min != min_size) { psi->min = min_size; return 1; } else return 0; } static int usb6fire_pcm_open(struct snd_pcm_substream *alsa_sub) { struct snd_pcm_runtime *alsa_rt = alsa_sub->runtime; struct sfire_chip *chip = snd_pcm_substream_chip(alsa_sub); struct pcm_runtime *rt = chip->pcm; struct substream_runtime *sub_rt = chip->substream; struct urbs_runtime *urbs_rt = chip->urbs; struct substream_descriptor desc; if (chip->shutdown || !rt || !sub_rt || !urbs_rt) return -EPIPE; desc = sub_rt->get_descriptor(sub_rt, alsa_sub); mutex_lock(&rt->lock); alsa_rt->hw = pcm_hw; alsa_rt->hw.channels_max = desc.max_channels; alsa_rt->hw.rate_min = desc.rate_min; alsa_rt->hw.rate_max = desc.rate_max; alsa_rt->hw.rates = desc.rate_bits; alsa_rt->hw.formats = desc.formats; if (urbs_rt->rate) { if (urbs_rt->rate < desc.rate_min || urbs_rt->rate > desc.rate_max) return -EBUSY; alsa_rt->hw.rate_min = urbs_rt->rate; alsa_rt->hw.rate_max = urbs_rt->rate; alsa_rt->hw.rates = snd_pcm_rate_to_rate_bit(urbs_rt->rate); } snd_pcm_hw_rule_add(alsa_sub->runtime, 0, SNDRV_PCM_HW_PARAM_PERIOD_TIME, usb6fire_pcm_rule_period_size, chip, SNDRV_PCM_HW_PARAM_RATE, -1); mutex_unlock(&rt->lock); return 0; } static int usb6fire_pcm_close(struct snd_pcm_substream *alsa_sub) { struct sfire_chip *chip = snd_pcm_substream_chip(alsa_sub); struct pcm_runtime *rt = chip->pcm; struct substream_runtime *sub_rt = chip->substream; struct urbs_runtime *urbs_rt = chip->urbs; struct substream_descriptor desc; int ret; if (chip->shutdown || !rt || !sub_rt || !urbs_rt) return 0; desc = sub_rt->get_descriptor(sub_rt, alsa_sub); mutex_lock(&rt->lock); ret = urbs_rt->set_state(urbs_rt, &desc, URBS_SUBSTATE_DISABLED); mutex_unlock(&rt->lock); return ret; } static int usb6fire_pcm_prepare(struct snd_pcm_substream *alsa_sub) { struct sfire_chip *chip = snd_pcm_substream_chip(alsa_sub); struct pcm_runtime *rt = chip->pcm; struct substream_runtime *sub_rt = chip->substream; struct urbs_runtime *urbs_rt = chip->urbs; struct substream_descriptor desc; int ret; if (chip->shutdown || !rt || !sub_rt || !urbs_rt) return -EPIPE; desc = sub_rt->get_descriptor(sub_rt, alsa_sub); mutex_lock(&rt->lock); ret = urbs_rt->set_state(urbs_rt, &desc, URBS_SUBSTATE_ENABLED); mutex_unlock(&rt->lock); return ret; } static int usb6fire_pcm_trigger(struct snd_pcm_substream *alsa_sub, int cmd) { struct sfire_chip *chip = snd_pcm_substream_chip(alsa_sub); struct pcm_runtime *rt = chip->pcm; struct substream_runtime *sub_rt = chip->substream; struct urbs_runtime *urbs_rt = chip->urbs; struct substream_descriptor desc; if (chip->shutdown || !rt || !sub_rt || !urbs_rt) return -EPIPE; desc = sub_rt->get_descriptor(sub_rt, alsa_sub); switch (cmd) { case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: return urbs_rt->set_active(urbs_rt, &desc, true); case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_PAUSE_PUSH: return urbs_rt->set_active(urbs_rt, &desc, false); default: return -EINVAL; } } static snd_pcm_uframes_t usb6fire_pcm_pointer( struct snd_pcm_substream *alsa_sub) { struct sfire_chip *chip = snd_pcm_substream_chip(alsa_sub); struct pcm_runtime *rt = chip->pcm; struct substream_runtime *sub_rt = chip->substream; struct urbs_runtime *urbs_rt = chip->urbs; struct substream_descriptor desc; if (chip->shutdown || !rt || !sub_rt || !urbs_rt) return -EPIPE; desc = sub_rt->get_descriptor(sub_rt, alsa_sub); return urbs_rt->get_pointer(urbs_rt, &desc); } static int usb6fire_pcm_hw_params(struct snd_pcm_substream *alsa_sub, struct snd_pcm_hw_params *hw_params) { return snd_pcm_lib_malloc_pages(alsa_sub, params_buffer_bytes(hw_params)); } static struct snd_pcm_ops pcm_ops = { .open = usb6fire_pcm_open, .close = usb6fire_pcm_close, .ioctl = snd_pcm_lib_ioctl, .hw_params = usb6fire_pcm_hw_params, .hw_free = snd_pcm_lib_free_pages, .prepare = usb6fire_pcm_prepare, .trigger = usb6fire_pcm_trigger, .pointer = usb6fire_pcm_pointer, }; int usb6fire_pcm_init(struct sfire_chip *chip) { struct pcm_runtime *rt = kzalloc(sizeof(struct pcm_runtime), GFP_KERNEL); struct substream_runtime *sub_rt = chip->substream; int ret; int i; if (!rt) return -ENOMEM; rt->chip = chip; mutex_init(&rt->lock); for (i = 0; i < sub_rt->n_devices; i++) { sub_rt->devices[i]->private_data = chip; snd_pcm_set_ops(sub_rt->devices[i], SNDRV_PCM_STREAM_PLAYBACK, &pcm_ops); snd_pcm_set_ops(sub_rt->devices[i], SNDRV_PCM_STREAM_CAPTURE, &pcm_ops); ret = snd_pcm_lib_preallocate_pages_for_all(sub_rt->devices[i], SNDRV_DMA_TYPE_CONTINUOUS, snd_dma_continuous_data(GFP_KERNEL), MAX_BUFSIZE, MAX_BUFSIZE); if (ret) { kfree(rt); snd_printk(KERN_ERR PREFIX "error preallocating pcm buffers.\n"); return ret; } } chip->pcm = rt; return 0; } void usb6fire_pcm_abort(struct sfire_chip *chip) {} void usb6fire_pcm_destroy(struct sfire_chip *chip) { kfree(chip->pcm); chip->pcm = NULL; }