| /* |
| ********************************************************************** |
| * 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 <linux/poll.h> |
| #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; |
| } |