/* ********************************************************************** * cardwi.c - PCM input HAL for emu10k1 driver * Copyright 1999, 2000 Creative Labs, Inc. * ********************************************************************** * * Date Author Summary of changes * ---- ------ ------------------ * October 20, 1999 Bertrand Lee base code release * ********************************************************************** * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the Free * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, * USA. * ********************************************************************** */ #include #include "hwaccess.h" #include "timer.h" #include "recmgr.h" #include "audio.h" #include "cardwi.h" /** * query_format - returns a valid sound format * * This function will return a valid sound format as close * to the requested one as possible. */ static void query_format(int recsrc, struct wave_format *wave_fmt) { switch (recsrc) { case WAVERECORD_AC97: if ((wave_fmt->channels != 1) && (wave_fmt->channels != 2)) wave_fmt->channels = 2; if (wave_fmt->samplingrate >= (0xBB80 + 0xAC44) / 2) wave_fmt->samplingrate = 0xBB80; else if (wave_fmt->samplingrate >= (0xAC44 + 0x7D00) / 2) wave_fmt->samplingrate = 0xAC44; else if (wave_fmt->samplingrate >= (0x7D00 + 0x5DC0) / 2) wave_fmt->samplingrate = 0x7D00; else if (wave_fmt->samplingrate >= (0x5DC0 + 0x5622) / 2) wave_fmt->samplingrate = 0x5DC0; else if (wave_fmt->samplingrate >= (0x5622 + 0x3E80) / 2) wave_fmt->samplingrate = 0x5622; else if (wave_fmt->samplingrate >= (0x3E80 + 0x2B11) / 2) wave_fmt->samplingrate = 0x3E80; else if (wave_fmt->samplingrate >= (0x2B11 + 0x1F40) / 2) wave_fmt->samplingrate = 0x2B11; else wave_fmt->samplingrate = 0x1F40; switch (wave_fmt->id) { case AFMT_S16_LE: wave_fmt->bitsperchannel = 16; break; case AFMT_U8: wave_fmt->bitsperchannel = 8; break; default: wave_fmt->id = AFMT_S16_LE; wave_fmt->bitsperchannel = 16; break; } break; /* these can't be changed from the original values */ case WAVERECORD_MIC: case WAVERECORD_FX: break; default: BUG(); break; } wave_fmt->bytesperchannel = wave_fmt->bitsperchannel >> 3; wave_fmt->bytespersample = wave_fmt->channels * wave_fmt->bytesperchannel; wave_fmt->bytespersec = wave_fmt->bytespersample * wave_fmt->samplingrate; wave_fmt->bytespervoicesample = wave_fmt->bytespersample; } static int alloc_buffer(struct emu10k1_card *card, struct wavein_buffer *buffer) { buffer->addr = pci_alloc_consistent(card->pci_dev, buffer->size * buffer->cov, &buffer->dma_handle); if (buffer->addr == NULL) return -1; return 0; } static void free_buffer(struct emu10k1_card *card, struct wavein_buffer *buffer) { if (buffer->addr != NULL) pci_free_consistent(card->pci_dev, buffer->size * buffer->cov, buffer->addr, buffer->dma_handle); } int emu10k1_wavein_open(struct emu10k1_wavedevice *wave_dev) { struct emu10k1_card *card = wave_dev->card; struct wiinst *wiinst = wave_dev->wiinst; struct wiinst **wiinst_tmp = NULL; u16 delay; unsigned long flags; DPF(2, "emu10k1_wavein_open()\n"); switch (wiinst->recsrc) { case WAVERECORD_AC97: wiinst_tmp = &card->wavein.ac97; break; case WAVERECORD_MIC: wiinst_tmp = &card->wavein.mic; break; case WAVERECORD_FX: wiinst_tmp = &card->wavein.fx; break; default: BUG(); break; } spin_lock_irqsave(&card->lock, flags); if (*wiinst_tmp != NULL) { spin_unlock_irqrestore(&card->lock, flags); return -1; } *wiinst_tmp = wiinst; spin_unlock_irqrestore(&card->lock, flags); /* handle 8 bit recording */ if (wiinst->format.bytesperchannel == 1) { if (wiinst->buffer.size > 0x8000) { wiinst->buffer.size = 0x8000; wiinst->buffer.sizeregval = 0x1f; } else wiinst->buffer.sizeregval += 4; wiinst->buffer.cov = 2; } else wiinst->buffer.cov = 1; if (alloc_buffer(card, &wiinst->buffer) < 0) { ERROR(); return -1; } emu10k1_set_record_src(card, wiinst); emu10k1_reset_record(card, &wiinst->buffer); wiinst->buffer.hw_pos = 0; wiinst->buffer.pos = 0; wiinst->buffer.bytestocopy = 0; delay = (48000 * wiinst->buffer.fragment_size) / wiinst->format.bytespersec; emu10k1_timer_install(card, &wiinst->timer, delay / 2); wiinst->state = WAVE_STATE_OPEN; return 0; } void emu10k1_wavein_close(struct emu10k1_wavedevice *wave_dev) { struct emu10k1_card *card = wave_dev->card; struct wiinst *wiinst = wave_dev->wiinst; unsigned long flags; DPF(2, "emu10k1_wavein_close()\n"); emu10k1_wavein_stop(wave_dev); emu10k1_timer_uninstall(card, &wiinst->timer); free_buffer(card, &wiinst->buffer); spin_lock_irqsave(&card->lock, flags); switch (wave_dev->wiinst->recsrc) { case WAVERECORD_AC97: card->wavein.ac97 = NULL; break; case WAVERECORD_MIC: card->wavein.mic = NULL; break; case WAVERECORD_FX: card->wavein.fx = NULL; break; default: BUG(); break; } spin_unlock_irqrestore(&card->lock, flags); wiinst->state = WAVE_STATE_CLOSED; } void emu10k1_wavein_start(struct emu10k1_wavedevice *wave_dev) { struct emu10k1_card *card = wave_dev->card; struct wiinst *wiinst = wave_dev->wiinst; DPF(2, "emu10k1_wavein_start()\n"); emu10k1_start_record(card, &wiinst->buffer); emu10k1_timer_enable(wave_dev->card, &wiinst->timer); wiinst->state |= WAVE_STATE_STARTED; } void emu10k1_wavein_stop(struct emu10k1_wavedevice *wave_dev) { struct emu10k1_card *card = wave_dev->card; struct wiinst *wiinst = wave_dev->wiinst; DPF(2, "emu10k1_wavein_stop()\n"); if (!(wiinst->state & WAVE_STATE_STARTED)) return; emu10k1_timer_disable(card, &wiinst->timer); emu10k1_stop_record(card, &wiinst->buffer); wiinst->state &= ~WAVE_STATE_STARTED; } int emu10k1_wavein_setformat(struct emu10k1_wavedevice *wave_dev, struct wave_format *format) { struct emu10k1_card *card = wave_dev->card; struct wiinst *wiinst = wave_dev->wiinst; u16 delay; DPF(2, "emu10k1_wavein_setformat()\n"); if (wiinst->state & WAVE_STATE_STARTED) return -1; query_format(wiinst->recsrc, format); if ((wiinst->format.samplingrate != format->samplingrate) || (wiinst->format.bitsperchannel != format->bitsperchannel) || (wiinst->format.channels != format->channels)) { wiinst->format = *format; if (wiinst->state == WAVE_STATE_CLOSED) return 0; wiinst->buffer.size *= wiinst->buffer.cov; if (wiinst->format.bytesperchannel == 1) { wiinst->buffer.cov = 2; wiinst->buffer.size /= wiinst->buffer.cov; } else wiinst->buffer.cov = 1; emu10k1_timer_uninstall(card, &wiinst->timer); delay = (48000 * wiinst->buffer.fragment_size) / wiinst->format.bytespersec; emu10k1_timer_install(card, &wiinst->timer, delay / 2); } return 0; } void emu10k1_wavein_getxfersize(struct wiinst *wiinst, u32 * size) { struct wavein_buffer *buffer = &wiinst->buffer; *size = buffer->bytestocopy; if (wiinst->mmapped) return; if (*size > buffer->size) { *size = buffer->size; buffer->pos = buffer->hw_pos; buffer->bytestocopy = buffer->size; DPF(1, "buffer overrun\n"); } } static void copy_block(u8 __user *dst, u8 * src, u32 str, u32 len, u8 cov) { if (cov == 1) __copy_to_user(dst, src + str, len); else { u8 byte; u32 i; src += 1 + 2 * str; for (i = 0; i < len; i++) { byte = src[2 * i] ^ 0x80; __copy_to_user(dst + i, &byte, 1); } } } void emu10k1_wavein_xferdata(struct wiinst *wiinst, u8 __user *data, u32 * size) { struct wavein_buffer *buffer = &wiinst->buffer; u32 sizetocopy, sizetocopy_now, start; unsigned long flags; sizetocopy = min_t(u32, buffer->size, *size); *size = sizetocopy; if (!sizetocopy) return; spin_lock_irqsave(&wiinst->lock, flags); start = buffer->pos; buffer->pos += sizetocopy; buffer->pos %= buffer->size; buffer->bytestocopy -= sizetocopy; sizetocopy_now = buffer->size - start; spin_unlock_irqrestore(&wiinst->lock, flags); if (sizetocopy > sizetocopy_now) { sizetocopy -= sizetocopy_now; copy_block(data, buffer->addr, start, sizetocopy_now, buffer->cov); copy_block(data + sizetocopy_now, buffer->addr, 0, sizetocopy, buffer->cov); } else { copy_block(data, buffer->addr, start, sizetocopy, buffer->cov); } } void emu10k1_wavein_update(struct emu10k1_card *card, struct wiinst *wiinst) { u32 hw_pos; u32 diff; /* There is no actual start yet */ if (!(wiinst->state & WAVE_STATE_STARTED)) { hw_pos = wiinst->buffer.hw_pos; } else { /* hw_pos in byte units */ hw_pos = sblive_readptr(card, wiinst->buffer.idxreg, 0) / wiinst->buffer.cov; } diff = (wiinst->buffer.size + hw_pos - wiinst->buffer.hw_pos) % wiinst->buffer.size; wiinst->total_recorded += diff; wiinst->buffer.bytestocopy += diff; wiinst->buffer.hw_pos = hw_pos; }