/** * 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 /* 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_S8 | SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE | SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_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 = 1, .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 | SNDRV_PCM_FMTBIT_U16_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 = 1, .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_S8 | SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE | SNDRV_PCM_FMTBIT_S24_3LE | SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_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); 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; spin_lock_init(&apcm->timer_lock); apcm->stop_timer = 0; 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; } 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) { return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params)); } static int ct_pcm_hw_free(struct snd_pcm_substream *substream) { /* Free snd-allocated pages */ return snd_pcm_lib_free_pages(substream); } static void ct_pcm_timer_callback(unsigned long data) { struct ct_atc_pcm *apcm = (struct ct_atc_pcm *)data; struct snd_pcm_substream *substream = apcm->substream; struct snd_pcm_runtime *runtime = substream->runtime; unsigned int period_size = runtime->period_size; unsigned int buffer_size = runtime->buffer_size; unsigned long flags; unsigned int position = 0, dist = 0, interval = 0; position = substream->ops->pointer(substream); dist = (position + buffer_size - apcm->position) % buffer_size; if ((dist >= period_size) || (position/period_size != apcm->position/period_size)) { apcm->interrupt(apcm); apcm->position = position; } /* Add extra HZ*5/1000 to avoid overrun issue when recording * at 8kHz in 8-bit format or at 88kHz in 24-bit format. */ interval = ((period_size - (position % period_size)) * HZ + (runtime->rate - 1)) / runtime->rate + HZ * 5 / 1000; spin_lock_irqsave(&apcm->timer_lock, flags); apcm->timer.expires = jiffies + interval; if (!apcm->stop_timer) add_timer(&apcm->timer); spin_unlock_irqrestore(&apcm->timer_lock, flags); } static int ct_pcm_timer_prepare(struct ct_atc_pcm *apcm) { unsigned long flags; spin_lock_irqsave(&apcm->timer_lock, flags); if (timer_pending(&apcm->timer)) { /* The timer has already been started. */ spin_unlock_irqrestore(&apcm->timer_lock, flags); return 0; } init_timer(&apcm->timer); apcm->timer.data = (unsigned long)apcm; apcm->timer.function = ct_pcm_timer_callback; spin_unlock_irqrestore(&apcm->timer_lock, flags); apcm->position = 0; return 0; } static int ct_pcm_timer_start(struct ct_atc_pcm *apcm) { struct snd_pcm_runtime *runtime = apcm->substream->runtime; unsigned long flags; spin_lock_irqsave(&apcm->timer_lock, flags); if (timer_pending(&apcm->timer)) { /* The timer has already been started. */ spin_unlock_irqrestore(&apcm->timer_lock, flags); return 0; } apcm->timer.expires = jiffies + (runtime->period_size * HZ + (runtime->rate - 1)) / runtime->rate; apcm->stop_timer = 0; add_timer(&apcm->timer); spin_unlock_irqrestore(&apcm->timer_lock, flags); return 0; } static int ct_pcm_timer_stop(struct ct_atc_pcm *apcm) { unsigned long flags; spin_lock_irqsave(&apcm->timer_lock, flags); apcm->stop_timer = 1; del_timer(&apcm->timer); spin_unlock_irqrestore(&apcm->timer_lock, flags); try_to_del_timer_sync(&apcm->timer); return 0; } 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; } ct_pcm_timer_prepare(apcm); 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); ct_pcm_timer_start(apcm); break; case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_SUSPEND: case SNDRV_PCM_TRIGGER_PAUSE_PUSH: ct_pcm_timer_stop(apcm); 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); 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; spin_lock_init(&apcm->timer_lock); apcm->started = 0; apcm->stop_timer = 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; } 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; } ct_pcm_timer_prepare(apcm); 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); ct_pcm_timer_start(apcm); break; case SNDRV_PCM_TRIGGER_STOP: ct_pcm_timer_stop(apcm); atc->pcm_capture_stop(atc, apcm); break; default: ct_pcm_timer_stop(apcm); 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); 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, }; /* 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, }; /* 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; char name[128]; strncpy(name, device_name, sizeof(name)); playback_count = (IEC958 == device) ? 1 : 8; capture_count = (FRONT == device) ? 1 : 0; err = snd_pcm_new(atc->card, name, 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; strcpy(pcm->name, device_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, snd_dma_pci_data(atc->pci), 128*1024, 128*1024); return 0; }