summaryrefslogtreecommitdiffstats
path: root/kernel/sound/pci/ctxfi/ctpcm.c
diff options
context:
space:
mode:
Diffstat (limited to 'kernel/sound/pci/ctxfi/ctpcm.c')
-rw-r--r--kernel/sound/pci/ctxfi/ctpcm.c488
1 files changed, 488 insertions, 0 deletions
diff --git a/kernel/sound/pci/ctxfi/ctpcm.c b/kernel/sound/pci/ctxfi/ctpcm.c
new file mode 100644
index 000000000..d86c474ca
--- /dev/null
+++ b/kernel/sound/pci/ctxfi/ctpcm.c
@@ -0,0 +1,488 @@
+/**
+ * Copyright (C) 2008, Creative Technology Ltd. All Rights Reserved.
+ *
+ * This source file is released under GPL v2 license (no other versions).
+ * See the COPYING file included in the main directory of this source
+ * distribution for the license terms and conditions.
+ *
+ * @File ctpcm.c
+ *
+ * @Brief
+ * This file contains the definition of the pcm device functions.
+ *
+ * @Author Liu Chun
+ * @Date Apr 2 2008
+ *
+ */
+
+#include "ctpcm.h"
+#include "cttimer.h"
+#include <linux/slab.h>
+#include <sound/pcm.h>
+
+/* Hardware descriptions for playback */
+static struct snd_pcm_hardware ct_pcm_playback_hw = {
+ .info = (SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_PAUSE),
+ .formats = (SNDRV_PCM_FMTBIT_U8 |
+ SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S24_3LE |
+ SNDRV_PCM_FMTBIT_S32_LE |
+ SNDRV_PCM_FMTBIT_FLOAT_LE),
+ .rates = (SNDRV_PCM_RATE_CONTINUOUS |
+ SNDRV_PCM_RATE_8000_192000),
+ .rate_min = 8000,
+ .rate_max = 192000,
+ .channels_min = 1,
+ .channels_max = 2,
+ .buffer_bytes_max = (128*1024),
+ .period_bytes_min = (64),
+ .period_bytes_max = (128*1024),
+ .periods_min = 2,
+ .periods_max = 1024,
+ .fifo_size = 0,
+};
+
+static struct snd_pcm_hardware ct_spdif_passthru_playback_hw = {
+ .info = (SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_PAUSE),
+ .formats = SNDRV_PCM_FMTBIT_S16_LE,
+ .rates = (SNDRV_PCM_RATE_48000 |
+ SNDRV_PCM_RATE_44100 |
+ SNDRV_PCM_RATE_32000),
+ .rate_min = 32000,
+ .rate_max = 48000,
+ .channels_min = 2,
+ .channels_max = 2,
+ .buffer_bytes_max = (128*1024),
+ .period_bytes_min = (64),
+ .period_bytes_max = (128*1024),
+ .periods_min = 2,
+ .periods_max = 1024,
+ .fifo_size = 0,
+};
+
+/* Hardware descriptions for capture */
+static struct snd_pcm_hardware ct_pcm_capture_hw = {
+ .info = (SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER |
+ SNDRV_PCM_INFO_PAUSE |
+ SNDRV_PCM_INFO_MMAP_VALID),
+ .formats = (SNDRV_PCM_FMTBIT_U8 |
+ SNDRV_PCM_FMTBIT_S16_LE |
+ SNDRV_PCM_FMTBIT_S24_3LE |
+ SNDRV_PCM_FMTBIT_S32_LE |
+ SNDRV_PCM_FMTBIT_FLOAT_LE),
+ .rates = (SNDRV_PCM_RATE_CONTINUOUS |
+ SNDRV_PCM_RATE_8000_96000),
+ .rate_min = 8000,
+ .rate_max = 96000,
+ .channels_min = 1,
+ .channels_max = 2,
+ .buffer_bytes_max = (128*1024),
+ .period_bytes_min = (384),
+ .period_bytes_max = (64*1024),
+ .periods_min = 2,
+ .periods_max = 1024,
+ .fifo_size = 0,
+};
+
+static void ct_atc_pcm_interrupt(struct ct_atc_pcm *atc_pcm)
+{
+ struct ct_atc_pcm *apcm = atc_pcm;
+
+ if (!apcm->substream)
+ return;
+
+ snd_pcm_period_elapsed(apcm->substream);
+}
+
+static void ct_atc_pcm_free_substream(struct snd_pcm_runtime *runtime)
+{
+ struct ct_atc_pcm *apcm = runtime->private_data;
+ struct ct_atc *atc = snd_pcm_substream_chip(apcm->substream);
+
+ atc->pcm_release_resources(atc, apcm);
+ ct_timer_instance_free(apcm->timer);
+ kfree(apcm);
+ runtime->private_data = NULL;
+}
+
+/* pcm playback operations */
+static int ct_pcm_playback_open(struct snd_pcm_substream *substream)
+{
+ struct ct_atc *atc = snd_pcm_substream_chip(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct ct_atc_pcm *apcm;
+ int err;
+
+ apcm = kzalloc(sizeof(*apcm), GFP_KERNEL);
+ if (!apcm)
+ return -ENOMEM;
+
+ apcm->substream = substream;
+ apcm->interrupt = ct_atc_pcm_interrupt;
+ if (IEC958 == substream->pcm->device) {
+ runtime->hw = ct_spdif_passthru_playback_hw;
+ atc->spdif_out_passthru(atc, 1);
+ } else {
+ runtime->hw = ct_pcm_playback_hw;
+ if (FRONT == substream->pcm->device)
+ runtime->hw.channels_max = 8;
+ }
+
+ err = snd_pcm_hw_constraint_integer(runtime,
+ SNDRV_PCM_HW_PARAM_PERIODS);
+ if (err < 0) {
+ kfree(apcm);
+ return err;
+ }
+ err = snd_pcm_hw_constraint_minmax(runtime,
+ SNDRV_PCM_HW_PARAM_BUFFER_BYTES,
+ 1024, UINT_MAX);
+ if (err < 0) {
+ kfree(apcm);
+ return err;
+ }
+
+ apcm->timer = ct_timer_instance_new(atc->timer, apcm);
+ if (!apcm->timer) {
+ kfree(apcm);
+ return -ENOMEM;
+ }
+ runtime->private_data = apcm;
+ runtime->private_free = ct_atc_pcm_free_substream;
+
+ return 0;
+}
+
+static int ct_pcm_playback_close(struct snd_pcm_substream *substream)
+{
+ struct ct_atc *atc = snd_pcm_substream_chip(substream);
+
+ /* TODO: Notify mixer inactive. */
+ if (IEC958 == substream->pcm->device)
+ atc->spdif_out_passthru(atc, 0);
+
+ /* The ct_atc_pcm object will be freed by runtime->private_free */
+
+ return 0;
+}
+
+static int ct_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *hw_params)
+{
+ struct ct_atc *atc = snd_pcm_substream_chip(substream);
+ struct ct_atc_pcm *apcm = substream->runtime->private_data;
+ int err;
+
+ err = snd_pcm_lib_malloc_pages(substream,
+ params_buffer_bytes(hw_params));
+ if (err < 0)
+ return err;
+ /* clear previous resources */
+ atc->pcm_release_resources(atc, apcm);
+ return err;
+}
+
+static int ct_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+ struct ct_atc *atc = snd_pcm_substream_chip(substream);
+ struct ct_atc_pcm *apcm = substream->runtime->private_data;
+
+ /* clear previous resources */
+ atc->pcm_release_resources(atc, apcm);
+ /* Free snd-allocated pages */
+ return snd_pcm_lib_free_pages(substream);
+}
+
+
+static int ct_pcm_playback_prepare(struct snd_pcm_substream *substream)
+{
+ int err;
+ struct ct_atc *atc = snd_pcm_substream_chip(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct ct_atc_pcm *apcm = runtime->private_data;
+
+ if (IEC958 == substream->pcm->device)
+ err = atc->spdif_passthru_playback_prepare(atc, apcm);
+ else
+ err = atc->pcm_playback_prepare(atc, apcm);
+
+ if (err < 0) {
+ dev_err(atc->card->dev,
+ "Preparing pcm playback failed!!!\n");
+ return err;
+ }
+
+ return 0;
+}
+
+static int
+ct_pcm_playback_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct ct_atc *atc = snd_pcm_substream_chip(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct ct_atc_pcm *apcm = runtime->private_data;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ atc->pcm_playback_start(atc, apcm);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ atc->pcm_playback_stop(atc, apcm);
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static snd_pcm_uframes_t
+ct_pcm_playback_pointer(struct snd_pcm_substream *substream)
+{
+ unsigned long position;
+ struct ct_atc *atc = snd_pcm_substream_chip(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct ct_atc_pcm *apcm = runtime->private_data;
+
+ /* Read out playback position */
+ position = atc->pcm_playback_position(atc, apcm);
+ position = bytes_to_frames(runtime, position);
+ if (position >= runtime->buffer_size)
+ position = 0;
+ return position;
+}
+
+/* pcm capture operations */
+static int ct_pcm_capture_open(struct snd_pcm_substream *substream)
+{
+ struct ct_atc *atc = snd_pcm_substream_chip(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct ct_atc_pcm *apcm;
+ int err;
+
+ apcm = kzalloc(sizeof(*apcm), GFP_KERNEL);
+ if (!apcm)
+ return -ENOMEM;
+
+ apcm->started = 0;
+ apcm->substream = substream;
+ apcm->interrupt = ct_atc_pcm_interrupt;
+ runtime->hw = ct_pcm_capture_hw;
+ runtime->hw.rate_max = atc->rsr * atc->msr;
+
+ err = snd_pcm_hw_constraint_integer(runtime,
+ SNDRV_PCM_HW_PARAM_PERIODS);
+ if (err < 0) {
+ kfree(apcm);
+ return err;
+ }
+ err = snd_pcm_hw_constraint_minmax(runtime,
+ SNDRV_PCM_HW_PARAM_BUFFER_BYTES,
+ 1024, UINT_MAX);
+ if (err < 0) {
+ kfree(apcm);
+ return err;
+ }
+
+ apcm->timer = ct_timer_instance_new(atc->timer, apcm);
+ if (!apcm->timer) {
+ kfree(apcm);
+ return -ENOMEM;
+ }
+ runtime->private_data = apcm;
+ runtime->private_free = ct_atc_pcm_free_substream;
+
+ return 0;
+}
+
+static int ct_pcm_capture_close(struct snd_pcm_substream *substream)
+{
+ /* The ct_atc_pcm object will be freed by runtime->private_free */
+ /* TODO: Notify mixer inactive. */
+ return 0;
+}
+
+static int ct_pcm_capture_prepare(struct snd_pcm_substream *substream)
+{
+ int err;
+ struct ct_atc *atc = snd_pcm_substream_chip(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct ct_atc_pcm *apcm = runtime->private_data;
+
+ err = atc->pcm_capture_prepare(atc, apcm);
+ if (err < 0) {
+ dev_err(atc->card->dev,
+ "Preparing pcm capture failed!!!\n");
+ return err;
+ }
+
+ return 0;
+}
+
+static int
+ct_pcm_capture_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct ct_atc *atc = snd_pcm_substream_chip(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct ct_atc_pcm *apcm = runtime->private_data;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ atc->pcm_capture_start(atc, apcm);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ atc->pcm_capture_stop(atc, apcm);
+ break;
+ default:
+ atc->pcm_capture_stop(atc, apcm);
+ break;
+ }
+
+ return 0;
+}
+
+static snd_pcm_uframes_t
+ct_pcm_capture_pointer(struct snd_pcm_substream *substream)
+{
+ unsigned long position;
+ struct ct_atc *atc = snd_pcm_substream_chip(substream);
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct ct_atc_pcm *apcm = runtime->private_data;
+
+ /* Read out playback position */
+ position = atc->pcm_capture_position(atc, apcm);
+ position = bytes_to_frames(runtime, position);
+ if (position >= runtime->buffer_size)
+ position = 0;
+ return position;
+}
+
+/* PCM operators for playback */
+static struct snd_pcm_ops ct_pcm_playback_ops = {
+ .open = ct_pcm_playback_open,
+ .close = ct_pcm_playback_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = ct_pcm_hw_params,
+ .hw_free = ct_pcm_hw_free,
+ .prepare = ct_pcm_playback_prepare,
+ .trigger = ct_pcm_playback_trigger,
+ .pointer = ct_pcm_playback_pointer,
+ .page = snd_pcm_sgbuf_ops_page,
+};
+
+/* PCM operators for capture */
+static struct snd_pcm_ops ct_pcm_capture_ops = {
+ .open = ct_pcm_capture_open,
+ .close = ct_pcm_capture_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = ct_pcm_hw_params,
+ .hw_free = ct_pcm_hw_free,
+ .prepare = ct_pcm_capture_prepare,
+ .trigger = ct_pcm_capture_trigger,
+ .pointer = ct_pcm_capture_pointer,
+ .page = snd_pcm_sgbuf_ops_page,
+};
+
+static const struct snd_pcm_chmap_elem surround_map[] = {
+ { .channels = 1,
+ .map = { SNDRV_CHMAP_MONO } },
+ { .channels = 2,
+ .map = { SNDRV_CHMAP_RL, SNDRV_CHMAP_RR } },
+ { }
+};
+
+static const struct snd_pcm_chmap_elem clfe_map[] = {
+ { .channels = 1,
+ .map = { SNDRV_CHMAP_MONO } },
+ { .channels = 2,
+ .map = { SNDRV_CHMAP_FC, SNDRV_CHMAP_LFE } },
+ { }
+};
+
+static const struct snd_pcm_chmap_elem side_map[] = {
+ { .channels = 1,
+ .map = { SNDRV_CHMAP_MONO } },
+ { .channels = 2,
+ .map = { SNDRV_CHMAP_SL, SNDRV_CHMAP_SR } },
+ { }
+};
+
+/* Create ALSA pcm device */
+int ct_alsa_pcm_create(struct ct_atc *atc,
+ enum CTALSADEVS device,
+ const char *device_name)
+{
+ struct snd_pcm *pcm;
+ const struct snd_pcm_chmap_elem *map;
+ int chs;
+ int err;
+ int playback_count, capture_count;
+
+ playback_count = (IEC958 == device) ? 1 : 256;
+ capture_count = (FRONT == device) ? 1 : 0;
+ err = snd_pcm_new(atc->card, "ctxfi", device,
+ playback_count, capture_count, &pcm);
+ if (err < 0) {
+ dev_err(atc->card->dev, "snd_pcm_new failed!! Err=%d\n",
+ err);
+ return err;
+ }
+
+ pcm->private_data = atc;
+ pcm->info_flags = 0;
+ pcm->dev_subclass = SNDRV_PCM_SUBCLASS_GENERIC_MIX;
+ strlcpy(pcm->name, device_name, sizeof(pcm->name));
+
+ snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &ct_pcm_playback_ops);
+
+ if (FRONT == device)
+ snd_pcm_set_ops(pcm,
+ SNDRV_PCM_STREAM_CAPTURE, &ct_pcm_capture_ops);
+
+ snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV_SG,
+ snd_dma_pci_data(atc->pci), 128*1024, 128*1024);
+
+ chs = 2;
+ switch (device) {
+ case FRONT:
+ chs = 8;
+ map = snd_pcm_std_chmaps;
+ break;
+ case SURROUND:
+ map = surround_map;
+ break;
+ case CLFE:
+ map = clfe_map;
+ break;
+ case SIDE:
+ map = side_map;
+ break;
+ default:
+ map = snd_pcm_std_chmaps;
+ break;
+ }
+ err = snd_pcm_add_chmap_ctls(pcm, SNDRV_PCM_STREAM_PLAYBACK, map, chs,
+ 0, NULL);
+ if (err < 0)
+ return err;
+
+#ifdef CONFIG_PM_SLEEP
+ atc->pcms[device] = pcm;
+#endif
+
+ return 0;
+}