mirror of
https://github.com/torvalds/linux.git
synced 2024-12-21 10:31:54 +00:00
a5990dc5b9
Currently the PCM resources are allocated only once and ever in prepare callback, assuming that the PCM parameters are never changed. But it's not true. This patch adds the call of atc->pcm_release_resources() at hw_params and hw_free callbacks to assure that the PCM setup is done correctly for each h/w parameter changes. Signed-off-by: Takashi Iwai <tiwai@suse.de>
427 lines
11 KiB
C
427 lines
11 KiB
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 (NULL == 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 (NULL == 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 (NULL == 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);
|
|
|
|
return 0;
|
|
}
|