Logo Search packages:      
Sourcecode: alsa-driver version File versions  Download package

ctpcm.c

/**
 * 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 <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;
      runtime->private_data = apcm;
      runtime->private_free = ct_atc_pcm_free_substream;
      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)
            return -ENOMEM;

      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) {
            printk(KERN_ERR "ctxfi: 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->private_data = apcm;
      runtime->private_free = ct_atc_pcm_free_substream;
      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)
            return -ENOMEM;

      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) {
            printk(KERN_ERR "ctxfi: 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,
};

/* Create ALSA pcm device */
int ct_alsa_pcm_create(struct ct_atc *atc,
                   enum CTALSADEVS device,
                   const char *device_name)
{
      struct snd_pcm *pcm;
      int err;
      int playback_count, capture_count;

      playback_count = (IEC958 == device) ? 1 : 8;
      capture_count = (FRONT == device) ? 1 : 0;
      err = snd_pcm_new(atc->card, "ctxfi", device,
                    playback_count, capture_count, &pcm);
      if (err < 0) {
            printk(KERN_ERR "ctxfi: 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);

#ifdef CONFIG_PM
      atc->pcms[device] = pcm;
#endif

      return 0;
}

Generated by  Doxygen 1.6.0   Back to index