diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2014-06-04 09:08:25 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2014-06-04 09:08:25 -0700 |
commit | b77279bc2e81545b20824da701b349272a78e4e7 (patch) | |
tree | d8f3a8ddf544cf201f8bdcb587cf360571487e5c /sound/firewire/bebob/bebob_pcm.c | |
parent | 15b588303155b22edd559672905db8e59a44ef9a (diff) | |
parent | 16088cb6c02d0b766b9b8d7edff98da7f1c93205 (diff) |
Merge tag 'sound-3.16-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/tiwai/sound into next
Pull sound updates from Takashi Iwai:
"At this time, majority of changes come from ASoC world while we got a
few new drivers in other places for FireWire and USB. There have been
lots of ASoC core cleanups / refactoring, but very little visible to
external users.
ASoC:
- Support for specifying aux CODECs in DT
- Removal of the deprecated mux and enum macros
- More moves towards full componentisation
- Removal of some unused I/O code
- Lots of cleanups, fixes and enhancements to the davinci, Freescale,
Haswell and Realtek drivers
- Several drivers exposed directly in Kconfig for use with
simple-card
- GPIO descriptor support for jacks
- More updates and fixes to the Freescale SSI, Intel and rsnd drivers
- New drivers for Cirrus CS42L56, Realtek RT5639, RT5642 and RT5651
and ST STA350, Analog Devices ADAU1361, ADAU1381, ADAU1761 and
ADAU1781, and Realtek RT5677
HD-audio:
- Clean up Dell headset quirks
- Noise fixes for Dell and Sony laptops
- Thinkpad T440 dock fix
- Realtek codec updates (ALC293,ALC233,ALC3235)
- Tegra HD-audio HDMI support
FireWire-audio:
- FireWire audio stack enhancement (AMDTP, MIDI), support for
incoming isochronous stream and duplex streams with timestamp
synchronization
- BeBoB-based devices support
- Fireworks-based device support
USB-audio:
- Behringer BCD2000 USB device support
Misc:
- Clean up of a few old drivers, atmel, fm801, etc"
* tag 'sound-3.16-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/tiwai/sound: (480 commits)
ASoC: Fix wrong argument for card remove callbacks
ASoC: free jack GPIOs before the sound card is freed
ALSA: firewire-lib: Remove a comment about restriction of asynchronous operation
ASoC: cache: Fix error code when not using ASoC level cache
ALSA: hda/realtek - Fix COEF widget NID for ALC260 replacer fixup
ALSA: hda/realtek - Correction of fixup codes for PB V7900 laptop
ALSA: firewire-lib: Use IEC 61883-6 compliant labels for Raw Audio data
ASoC: add RT5677 CODEC driver
ASoC: intel: The Baytrail/MAX98090 driver depends on I2C
ASoC: rt5640: Add the function "get_clk_info" to RL6231 shared support
ASoC: rt5640: Add the function of the PLL clock calculation to RL6231 shared support
ASoC: rt5640: Add RL6231 class device shared support for RT5640, RT5645 and RT5651
ASoC: cache: Fix possible ZERO_SIZE_PTR pointer dereferencing error.
ASoC: Add helper functions to cast from DAPM context to CODEC/platform
ALSA: bebob: sizeof() vs ARRAY_SIZE() typo
ASoC: wm9713: correct mono out PGA sources
ALSA: synth: emux: soundfont.c: Cleaning up memory leak
ASoC: fsl: Remove dependencies of boards for SND_SOC_EUKREA_TLV320
ASoC: fsl-ssi: Use regmap
ASoC: fsl-ssi: reorder and document fsl_ssi_private
...
Diffstat (limited to 'sound/firewire/bebob/bebob_pcm.c')
-rw-r--r-- | sound/firewire/bebob/bebob_pcm.c | 378 |
1 files changed, 378 insertions, 0 deletions
diff --git a/sound/firewire/bebob/bebob_pcm.c b/sound/firewire/bebob/bebob_pcm.c new file mode 100644 index 00000000000..4a55561ed4e --- /dev/null +++ b/sound/firewire/bebob/bebob_pcm.c @@ -0,0 +1,378 @@ +/* + * bebob_pcm.c - a part of driver for BeBoB based devices + * + * Copyright (c) 2013-2014 Takashi Sakamoto + * + * Licensed under the terms of the GNU General Public License, version 2. + */ + +#include "./bebob.h" + +static int +hw_rule_rate(struct snd_pcm_hw_params *params, struct snd_pcm_hw_rule *rule) +{ + struct snd_bebob_stream_formation *formations = rule->private; + struct snd_interval *r = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_RATE); + const struct snd_interval *c = + hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_CHANNELS); + struct snd_interval t = { + .min = UINT_MAX, .max = 0, .integer = 1 + }; + unsigned int i; + + for (i = 0; i < SND_BEBOB_STRM_FMT_ENTRIES; i++) { + /* entry is invalid */ + if (formations[i].pcm == 0) + continue; + + if (!snd_interval_test(c, formations[i].pcm)) + continue; + + t.min = min(t.min, snd_bebob_rate_table[i]); + t.max = max(t.max, snd_bebob_rate_table[i]); + + } + return snd_interval_refine(r, &t); +} + +static int +hw_rule_channels(struct snd_pcm_hw_params *params, struct snd_pcm_hw_rule *rule) +{ + struct snd_bebob_stream_formation *formations = rule->private; + struct snd_interval *c = + hw_param_interval(params, SNDRV_PCM_HW_PARAM_CHANNELS); + const struct snd_interval *r = + hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_RATE); + struct snd_interval t = { + .min = UINT_MAX, .max = 0, .integer = 1 + }; + + unsigned int i; + + for (i = 0; i < SND_BEBOB_STRM_FMT_ENTRIES; i++) { + /* entry is invalid */ + if (formations[i].pcm == 0) + continue; + + if (!snd_interval_test(r, snd_bebob_rate_table[i])) + continue; + + t.min = min(t.min, formations[i].pcm); + t.max = max(t.max, formations[i].pcm); + } + + return snd_interval_refine(c, &t); +} + +static void +limit_channels_and_rates(struct snd_pcm_hardware *hw, + struct snd_bebob_stream_formation *formations) +{ + unsigned int i; + + hw->channels_min = UINT_MAX; + hw->channels_max = 0; + + hw->rate_min = UINT_MAX; + hw->rate_max = 0; + hw->rates = 0; + + for (i = 0; i < SND_BEBOB_STRM_FMT_ENTRIES; i++) { + /* entry has no PCM channels */ + if (formations[i].pcm == 0) + continue; + + hw->channels_min = min(hw->channels_min, formations[i].pcm); + hw->channels_max = max(hw->channels_max, formations[i].pcm); + + hw->rate_min = min(hw->rate_min, snd_bebob_rate_table[i]); + hw->rate_max = max(hw->rate_max, snd_bebob_rate_table[i]); + hw->rates |= snd_pcm_rate_to_rate_bit(snd_bebob_rate_table[i]); + } +} + +static void +limit_period_and_buffer(struct snd_pcm_hardware *hw) +{ + hw->periods_min = 2; /* SNDRV_PCM_INFO_BATCH */ + hw->periods_max = UINT_MAX; + + hw->period_bytes_min = 4 * hw->channels_max; /* bytes for a frame */ + + /* Just to prevent from allocating much pages. */ + hw->period_bytes_max = hw->period_bytes_min * 2048; + hw->buffer_bytes_max = hw->period_bytes_max * hw->periods_min; +} + +static int +pcm_init_hw_params(struct snd_bebob *bebob, + struct snd_pcm_substream *substream) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + struct amdtp_stream *s; + struct snd_bebob_stream_formation *formations; + int err; + + runtime->hw.info = SNDRV_PCM_INFO_BATCH | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_JOINT_DUPLEX | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID; + + if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { + runtime->hw.formats = AMDTP_IN_PCM_FORMAT_BITS; + s = &bebob->tx_stream; + formations = bebob->tx_stream_formations; + } else { + runtime->hw.formats = AMDTP_OUT_PCM_FORMAT_BITS; + s = &bebob->rx_stream; + formations = bebob->rx_stream_formations; + } + + limit_channels_and_rates(&runtime->hw, formations); + limit_period_and_buffer(&runtime->hw); + + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS, + hw_rule_channels, formations, + SNDRV_PCM_HW_PARAM_RATE, -1); + if (err < 0) + goto end; + + err = snd_pcm_hw_rule_add(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, + hw_rule_rate, formations, + SNDRV_PCM_HW_PARAM_CHANNELS, -1); + if (err < 0) + goto end; + + err = amdtp_stream_add_pcm_hw_constraints(s, runtime); +end: + return err; +} + +static int +pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_bebob *bebob = substream->private_data; + struct snd_bebob_rate_spec *spec = bebob->spec->rate; + unsigned int sampling_rate; + bool internal; + int err; + + err = snd_bebob_stream_lock_try(bebob); + if (err < 0) + goto end; + + err = pcm_init_hw_params(bebob, substream); + if (err < 0) + goto err_locked; + + err = snd_bebob_stream_check_internal_clock(bebob, &internal); + if (err < 0) + goto err_locked; + + /* + * When source of clock is internal or any PCM stream are running, + * the available sampling rate is limited at current sampling rate. + */ + if (!internal || + amdtp_stream_pcm_running(&bebob->tx_stream) || + amdtp_stream_pcm_running(&bebob->rx_stream)) { + err = spec->get(bebob, &sampling_rate); + if (err < 0) { + dev_err(&bebob->unit->device, + "fail to get sampling rate: %d\n", err); + goto err_locked; + } + + substream->runtime->hw.rate_min = sampling_rate; + substream->runtime->hw.rate_max = sampling_rate; + } + + snd_pcm_set_sync(substream); +end: + return err; +err_locked: + snd_bebob_stream_lock_release(bebob); + return err; +} + +static int +pcm_close(struct snd_pcm_substream *substream) +{ + struct snd_bebob *bebob = substream->private_data; + snd_bebob_stream_lock_release(bebob); + return 0; +} + +static int +pcm_capture_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_bebob *bebob = substream->private_data; + + if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) + atomic_inc(&bebob->capture_substreams); + amdtp_stream_set_pcm_format(&bebob->tx_stream, + params_format(hw_params)); + return snd_pcm_lib_alloc_vmalloc_buffer(substream, + params_buffer_bytes(hw_params)); +} +static int +pcm_playback_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_bebob *bebob = substream->private_data; + + if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) + atomic_inc(&bebob->playback_substreams); + amdtp_stream_set_pcm_format(&bebob->rx_stream, + params_format(hw_params)); + return snd_pcm_lib_alloc_vmalloc_buffer(substream, + params_buffer_bytes(hw_params)); +} + +static int +pcm_capture_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_bebob *bebob = substream->private_data; + + if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN) + atomic_dec(&bebob->capture_substreams); + + snd_bebob_stream_stop_duplex(bebob); + + return snd_pcm_lib_free_vmalloc_buffer(substream); +} +static int +pcm_playback_hw_free(struct snd_pcm_substream *substream) +{ + struct snd_bebob *bebob = substream->private_data; + + if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN) + atomic_dec(&bebob->playback_substreams); + + snd_bebob_stream_stop_duplex(bebob); + + return snd_pcm_lib_free_vmalloc_buffer(substream); +} + +static int +pcm_capture_prepare(struct snd_pcm_substream *substream) +{ + struct snd_bebob *bebob = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + + err = snd_bebob_stream_start_duplex(bebob, runtime->rate); + if (err >= 0) + amdtp_stream_pcm_prepare(&bebob->tx_stream); + + return err; +} +static int +pcm_playback_prepare(struct snd_pcm_substream *substream) +{ + struct snd_bebob *bebob = substream->private_data; + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + + err = snd_bebob_stream_start_duplex(bebob, runtime->rate); + if (err >= 0) + amdtp_stream_pcm_prepare(&bebob->rx_stream); + + return err; +} + +static int +pcm_capture_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_bebob *bebob = substream->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + amdtp_stream_pcm_trigger(&bebob->tx_stream, substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + amdtp_stream_pcm_trigger(&bebob->tx_stream, NULL); + break; + default: + return -EINVAL; + } + + return 0; +} +static int +pcm_playback_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_bebob *bebob = substream->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + amdtp_stream_pcm_trigger(&bebob->rx_stream, substream); + break; + case SNDRV_PCM_TRIGGER_STOP: + amdtp_stream_pcm_trigger(&bebob->rx_stream, NULL); + break; + default: + return -EINVAL; + } + + return 0; +} + +static snd_pcm_uframes_t +pcm_capture_pointer(struct snd_pcm_substream *sbstrm) +{ + struct snd_bebob *bebob = sbstrm->private_data; + return amdtp_stream_pcm_pointer(&bebob->tx_stream); +} +static snd_pcm_uframes_t +pcm_playback_pointer(struct snd_pcm_substream *sbstrm) +{ + struct snd_bebob *bebob = sbstrm->private_data; + return amdtp_stream_pcm_pointer(&bebob->rx_stream); +} + +static const struct snd_pcm_ops pcm_capture_ops = { + .open = pcm_open, + .close = pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = pcm_capture_hw_params, + .hw_free = pcm_capture_hw_free, + .prepare = pcm_capture_prepare, + .trigger = pcm_capture_trigger, + .pointer = pcm_capture_pointer, + .page = snd_pcm_lib_get_vmalloc_page, +}; +static const struct snd_pcm_ops pcm_playback_ops = { + .open = pcm_open, + .close = pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = pcm_playback_hw_params, + .hw_free = pcm_playback_hw_free, + .prepare = pcm_playback_prepare, + .trigger = pcm_playback_trigger, + .pointer = pcm_playback_pointer, + .page = snd_pcm_lib_get_vmalloc_page, + .mmap = snd_pcm_lib_mmap_vmalloc, +}; + +int snd_bebob_create_pcm_devices(struct snd_bebob *bebob) +{ + struct snd_pcm *pcm; + int err; + + err = snd_pcm_new(bebob->card, bebob->card->driver, 0, 1, 1, &pcm); + if (err < 0) + goto end; + + pcm->private_data = bebob; + snprintf(pcm->name, sizeof(pcm->name), + "%s PCM", bebob->card->shortname); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &pcm_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &pcm_capture_ops); +end: + return err; +} |