| // SPDX-License-Identifier: GPL-2.0-or-later |
| /********************************************************************* |
| * |
| * 2002/06/30 Karsten Wiese: |
| * removed kernel-version dependencies. |
| * ripped from linux kernel 2.4.18 (OSS Implementation) by me. |
| * In the OSS Version, this file is compiled to a separate MODULE, |
| * that is used by the pinnacle and the classic driver. |
| * since there is no classic driver for alsa yet (i dont have a classic |
| * & writing one blindfold is difficult) this file's object is statically |
| * linked into the pinnacle-driver-module for now. look for the string |
| * "uncomment this to make this a module again" |
| * to do guess what. |
| * |
| * the following is a copy of the 2.4.18 OSS FREE file-heading comment: |
| * |
| * msnd.c - Driver Base |
| * |
| * Turtle Beach MultiSound Sound Card Driver for Linux |
| * |
| * Copyright (C) 1998 Andrew Veliath |
| * |
| ********************************************************************/ |
| |
| #include <linux/kernel.h> |
| #include <linux/sched/signal.h> |
| #include <linux/types.h> |
| #include <linux/interrupt.h> |
| #include <linux/io.h> |
| #include <linux/fs.h> |
| #include <linux/delay.h> |
| #include <linux/module.h> |
| |
| #include <sound/core.h> |
| #include <sound/initval.h> |
| #include <sound/pcm.h> |
| #include <sound/pcm_params.h> |
| |
| #include "msnd.h" |
| |
| #define LOGNAME "msnd" |
| |
| |
| void snd_msnd_init_queue(void __iomem *base, int start, int size) |
| { |
| writew(PCTODSP_BASED(start), base + JQS_wStart); |
| writew(PCTODSP_OFFSET(size) - 1, base + JQS_wSize); |
| writew(0, base + JQS_wHead); |
| writew(0, base + JQS_wTail); |
| } |
| EXPORT_SYMBOL(snd_msnd_init_queue); |
| |
| static int snd_msnd_wait_TXDE(struct snd_msnd *dev) |
| { |
| unsigned int io = dev->io; |
| int timeout = 1000; |
| |
| while (timeout-- > 0) |
| if (inb(io + HP_ISR) & HPISR_TXDE) |
| return 0; |
| |
| return -EIO; |
| } |
| |
| static int snd_msnd_wait_HC0(struct snd_msnd *dev) |
| { |
| unsigned int io = dev->io; |
| int timeout = 1000; |
| |
| while (timeout-- > 0) |
| if (!(inb(io + HP_CVR) & HPCVR_HC)) |
| return 0; |
| |
| return -EIO; |
| } |
| |
| int snd_msnd_send_dsp_cmd(struct snd_msnd *dev, u8 cmd) |
| { |
| unsigned long flags; |
| |
| spin_lock_irqsave(&dev->lock, flags); |
| if (snd_msnd_wait_HC0(dev) == 0) { |
| outb(cmd, dev->io + HP_CVR); |
| spin_unlock_irqrestore(&dev->lock, flags); |
| return 0; |
| } |
| spin_unlock_irqrestore(&dev->lock, flags); |
| |
| dev_dbg(dev->card->dev, LOGNAME ": Send DSP command timeout\n"); |
| |
| return -EIO; |
| } |
| EXPORT_SYMBOL(snd_msnd_send_dsp_cmd); |
| |
| int snd_msnd_send_word(struct snd_msnd *dev, unsigned char high, |
| unsigned char mid, unsigned char low) |
| { |
| unsigned int io = dev->io; |
| |
| if (snd_msnd_wait_TXDE(dev) == 0) { |
| outb(high, io + HP_TXH); |
| outb(mid, io + HP_TXM); |
| outb(low, io + HP_TXL); |
| return 0; |
| } |
| |
| dev_dbg(dev->card->dev, LOGNAME ": Send host word timeout\n"); |
| |
| return -EIO; |
| } |
| EXPORT_SYMBOL(snd_msnd_send_word); |
| |
| int snd_msnd_upload_host(struct snd_msnd *dev, const u8 *bin, int len) |
| { |
| int i; |
| |
| if (len % 3 != 0) { |
| dev_err(dev->card->dev, LOGNAME |
| ": Upload host data not multiple of 3!\n"); |
| return -EINVAL; |
| } |
| |
| for (i = 0; i < len; i += 3) |
| if (snd_msnd_send_word(dev, bin[i], bin[i + 1], bin[i + 2])) |
| return -EIO; |
| |
| inb(dev->io + HP_RXL); |
| inb(dev->io + HP_CVR); |
| |
| return 0; |
| } |
| EXPORT_SYMBOL(snd_msnd_upload_host); |
| |
| int snd_msnd_enable_irq(struct snd_msnd *dev) |
| { |
| unsigned long flags; |
| |
| if (dev->irq_ref++) |
| return 0; |
| |
| dev_dbg(dev->card->dev, LOGNAME ": Enabling IRQ\n"); |
| |
| spin_lock_irqsave(&dev->lock, flags); |
| if (snd_msnd_wait_TXDE(dev) == 0) { |
| outb(inb(dev->io + HP_ICR) | HPICR_TREQ, dev->io + HP_ICR); |
| if (dev->type == msndClassic) |
| outb(dev->irqid, dev->io + HP_IRQM); |
| |
| outb(inb(dev->io + HP_ICR) & ~HPICR_TREQ, dev->io + HP_ICR); |
| outb(inb(dev->io + HP_ICR) | HPICR_RREQ, dev->io + HP_ICR); |
| enable_irq(dev->irq); |
| snd_msnd_init_queue(dev->DSPQ, dev->dspq_data_buff, |
| dev->dspq_buff_size); |
| spin_unlock_irqrestore(&dev->lock, flags); |
| return 0; |
| } |
| spin_unlock_irqrestore(&dev->lock, flags); |
| |
| dev_dbg(dev->card->dev, LOGNAME ": Enable IRQ failed\n"); |
| |
| return -EIO; |
| } |
| EXPORT_SYMBOL(snd_msnd_enable_irq); |
| |
| int snd_msnd_disable_irq(struct snd_msnd *dev) |
| { |
| unsigned long flags; |
| |
| if (--dev->irq_ref > 0) |
| return 0; |
| |
| if (dev->irq_ref < 0) |
| dev_dbg(dev->card->dev, LOGNAME ": IRQ ref count is %d\n", |
| dev->irq_ref); |
| |
| dev_dbg(dev->card->dev, LOGNAME ": Disabling IRQ\n"); |
| |
| spin_lock_irqsave(&dev->lock, flags); |
| if (snd_msnd_wait_TXDE(dev) == 0) { |
| outb(inb(dev->io + HP_ICR) & ~HPICR_RREQ, dev->io + HP_ICR); |
| if (dev->type == msndClassic) |
| outb(HPIRQ_NONE, dev->io + HP_IRQM); |
| disable_irq(dev->irq); |
| spin_unlock_irqrestore(&dev->lock, flags); |
| return 0; |
| } |
| spin_unlock_irqrestore(&dev->lock, flags); |
| |
| dev_dbg(dev->card->dev, LOGNAME ": Disable IRQ failed\n"); |
| |
| return -EIO; |
| } |
| EXPORT_SYMBOL(snd_msnd_disable_irq); |
| |
| static inline long get_play_delay_jiffies(struct snd_msnd *chip, long size) |
| { |
| long tmp = (size * HZ * chip->play_sample_size) / 8; |
| return tmp / (chip->play_sample_rate * chip->play_channels); |
| } |
| |
| static void snd_msnd_dsp_write_flush(struct snd_msnd *chip) |
| { |
| if (!(chip->mode & FMODE_WRITE) || !test_bit(F_WRITING, &chip->flags)) |
| return; |
| set_bit(F_WRITEFLUSH, &chip->flags); |
| /* interruptible_sleep_on_timeout( |
| &chip->writeflush, |
| get_play_delay_jiffies(&chip, chip->DAPF.len));*/ |
| clear_bit(F_WRITEFLUSH, &chip->flags); |
| if (!signal_pending(current)) |
| schedule_timeout_interruptible( |
| get_play_delay_jiffies(chip, chip->play_period_bytes)); |
| clear_bit(F_WRITING, &chip->flags); |
| } |
| |
| void snd_msnd_dsp_halt(struct snd_msnd *chip, struct file *file) |
| { |
| if ((file ? file->f_mode : chip->mode) & FMODE_READ) { |
| clear_bit(F_READING, &chip->flags); |
| snd_msnd_send_dsp_cmd(chip, HDEX_RECORD_STOP); |
| snd_msnd_disable_irq(chip); |
| if (file) { |
| dev_dbg(chip->card->dev, LOGNAME |
| ": Stopping read for %p\n", file); |
| chip->mode &= ~FMODE_READ; |
| } |
| clear_bit(F_AUDIO_READ_INUSE, &chip->flags); |
| } |
| if ((file ? file->f_mode : chip->mode) & FMODE_WRITE) { |
| if (test_bit(F_WRITING, &chip->flags)) { |
| snd_msnd_dsp_write_flush(chip); |
| snd_msnd_send_dsp_cmd(chip, HDEX_PLAY_STOP); |
| } |
| snd_msnd_disable_irq(chip); |
| if (file) { |
| dev_dbg(chip->card->dev, |
| LOGNAME ": Stopping write for %p\n", file); |
| chip->mode &= ~FMODE_WRITE; |
| } |
| clear_bit(F_AUDIO_WRITE_INUSE, &chip->flags); |
| } |
| } |
| EXPORT_SYMBOL(snd_msnd_dsp_halt); |
| |
| |
| int snd_msnd_DARQ(struct snd_msnd *chip, int bank) |
| { |
| int /*size, n,*/ timeout = 3; |
| u16 wTmp; |
| /* void *DAQD; */ |
| |
| /* Increment the tail and check for queue wrap */ |
| wTmp = readw(chip->DARQ + JQS_wTail) + PCTODSP_OFFSET(DAQDS__size); |
| if (wTmp > readw(chip->DARQ + JQS_wSize)) |
| wTmp = 0; |
| while (wTmp == readw(chip->DARQ + JQS_wHead) && timeout--) |
| udelay(1); |
| |
| if (chip->capturePeriods == 2) { |
| void __iomem *pDAQ = chip->mappedbase + DARQ_DATA_BUFF + |
| bank * DAQDS__size + DAQDS_wStart; |
| unsigned short offset = 0x3000 + chip->capturePeriodBytes; |
| |
| if (readw(pDAQ) != PCTODSP_BASED(0x3000)) |
| offset = 0x3000; |
| writew(PCTODSP_BASED(offset), pDAQ); |
| } |
| |
| writew(wTmp, chip->DARQ + JQS_wTail); |
| |
| #if 0 |
| /* Get our digital audio queue struct */ |
| DAQD = bank * DAQDS__size + chip->mappedbase + DARQ_DATA_BUFF; |
| |
| /* Get length of data */ |
| size = readw(DAQD + DAQDS_wSize); |
| |
| /* Read data from the head (unprotected bank 1 access okay |
| since this is only called inside an interrupt) */ |
| outb(HPBLKSEL_1, chip->io + HP_BLKS); |
| n = msnd_fifo_write(&chip->DARF, |
| (char *)(chip->base + bank * DAR_BUFF_SIZE), |
| size, 0); |
| if (n <= 0) { |
| outb(HPBLKSEL_0, chip->io + HP_BLKS); |
| return n; |
| } |
| outb(HPBLKSEL_0, chip->io + HP_BLKS); |
| #endif |
| |
| return 1; |
| } |
| EXPORT_SYMBOL(snd_msnd_DARQ); |
| |
| int snd_msnd_DAPQ(struct snd_msnd *chip, int start) |
| { |
| u16 DAPQ_tail; |
| int protect = start, nbanks = 0; |
| void __iomem *DAQD; |
| static int play_banks_submitted; |
| /* unsigned long flags; |
| spin_lock_irqsave(&chip->lock, flags); not necessary */ |
| |
| DAPQ_tail = readw(chip->DAPQ + JQS_wTail); |
| while (DAPQ_tail != readw(chip->DAPQ + JQS_wHead) || start) { |
| int bank_num = DAPQ_tail / PCTODSP_OFFSET(DAQDS__size); |
| |
| if (start) { |
| start = 0; |
| play_banks_submitted = 0; |
| } |
| |
| /* Get our digital audio queue struct */ |
| DAQD = bank_num * DAQDS__size + chip->mappedbase + |
| DAPQ_DATA_BUFF; |
| |
| /* Write size of this bank */ |
| writew(chip->play_period_bytes, DAQD + DAQDS_wSize); |
| if (play_banks_submitted < 3) |
| ++play_banks_submitted; |
| else if (chip->playPeriods == 2) { |
| unsigned short offset = chip->play_period_bytes; |
| |
| if (readw(DAQD + DAQDS_wStart) != PCTODSP_BASED(0x0)) |
| offset = 0; |
| |
| writew(PCTODSP_BASED(offset), DAQD + DAQDS_wStart); |
| } |
| ++nbanks; |
| |
| /* Then advance the tail */ |
| DAPQ_tail = (++bank_num % 3) * PCTODSP_OFFSET(DAQDS__size); |
| writew(DAPQ_tail, chip->DAPQ + JQS_wTail); |
| /* Tell the DSP to play the bank */ |
| snd_msnd_send_dsp_cmd(chip, HDEX_PLAY_START); |
| if (protect) |
| if (2 == bank_num) |
| break; |
| } |
| /* spin_unlock_irqrestore(&chip->lock, flags); not necessary */ |
| return nbanks; |
| } |
| EXPORT_SYMBOL(snd_msnd_DAPQ); |
| |
| static void snd_msnd_play_reset_queue(struct snd_msnd *chip, |
| unsigned int pcm_periods, |
| unsigned int pcm_count) |
| { |
| int n; |
| void __iomem *pDAQ = chip->mappedbase + DAPQ_DATA_BUFF; |
| |
| chip->last_playbank = -1; |
| chip->playLimit = pcm_count * (pcm_periods - 1); |
| chip->playPeriods = pcm_periods; |
| writew(PCTODSP_OFFSET(0 * DAQDS__size), chip->DAPQ + JQS_wHead); |
| writew(PCTODSP_OFFSET(0 * DAQDS__size), chip->DAPQ + JQS_wTail); |
| |
| chip->play_period_bytes = pcm_count; |
| |
| for (n = 0; n < pcm_periods; ++n, pDAQ += DAQDS__size) { |
| writew(PCTODSP_BASED((u32)(pcm_count * n)), |
| pDAQ + DAQDS_wStart); |
| writew(0, pDAQ + DAQDS_wSize); |
| writew(1, pDAQ + DAQDS_wFormat); |
| writew(chip->play_sample_size, pDAQ + DAQDS_wSampleSize); |
| writew(chip->play_channels, pDAQ + DAQDS_wChannels); |
| writew(chip->play_sample_rate, pDAQ + DAQDS_wSampleRate); |
| writew(HIMT_PLAY_DONE * 0x100 + n, pDAQ + DAQDS_wIntMsg); |
| writew(n, pDAQ + DAQDS_wFlags); |
| } |
| } |
| |
| static void snd_msnd_capture_reset_queue(struct snd_msnd *chip, |
| unsigned int pcm_periods, |
| unsigned int pcm_count) |
| { |
| int n; |
| void __iomem *pDAQ; |
| /* unsigned long flags; */ |
| |
| /* snd_msnd_init_queue(chip->DARQ, DARQ_DATA_BUFF, DARQ_BUFF_SIZE); */ |
| |
| chip->last_recbank = 2; |
| chip->captureLimit = pcm_count * (pcm_periods - 1); |
| chip->capturePeriods = pcm_periods; |
| writew(PCTODSP_OFFSET(0 * DAQDS__size), chip->DARQ + JQS_wHead); |
| writew(PCTODSP_OFFSET(chip->last_recbank * DAQDS__size), |
| chip->DARQ + JQS_wTail); |
| |
| #if 0 /* Critical section: bank 1 access. this is how the OSS driver does it:*/ |
| spin_lock_irqsave(&chip->lock, flags); |
| outb(HPBLKSEL_1, chip->io + HP_BLKS); |
| memset_io(chip->mappedbase, 0, DAR_BUFF_SIZE * 3); |
| outb(HPBLKSEL_0, chip->io + HP_BLKS); |
| spin_unlock_irqrestore(&chip->lock, flags); |
| #endif |
| |
| chip->capturePeriodBytes = pcm_count; |
| dev_dbg(chip->card->dev, "%s() %i\n", __func__, pcm_count); |
| |
| pDAQ = chip->mappedbase + DARQ_DATA_BUFF; |
| |
| for (n = 0; n < pcm_periods; ++n, pDAQ += DAQDS__size) { |
| u32 tmp = pcm_count * n; |
| |
| writew(PCTODSP_BASED(tmp + 0x3000), pDAQ + DAQDS_wStart); |
| writew(pcm_count, pDAQ + DAQDS_wSize); |
| writew(1, pDAQ + DAQDS_wFormat); |
| writew(chip->capture_sample_size, pDAQ + DAQDS_wSampleSize); |
| writew(chip->capture_channels, pDAQ + DAQDS_wChannels); |
| writew(chip->capture_sample_rate, pDAQ + DAQDS_wSampleRate); |
| writew(HIMT_RECORD_DONE * 0x100 + n, pDAQ + DAQDS_wIntMsg); |
| writew(n, pDAQ + DAQDS_wFlags); |
| } |
| } |
| |
| static const struct snd_pcm_hardware snd_msnd_playback = { |
| .info = SNDRV_PCM_INFO_MMAP_IOMEM | |
| SNDRV_PCM_INFO_INTERLEAVED | |
| SNDRV_PCM_INFO_MMAP_VALID | |
| SNDRV_PCM_INFO_BATCH, |
| .formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE, |
| .rates = SNDRV_PCM_RATE_8000_48000, |
| .rate_min = 8000, |
| .rate_max = 48000, |
| .channels_min = 1, |
| .channels_max = 2, |
| .buffer_bytes_max = 0x3000, |
| .period_bytes_min = 0x40, |
| .period_bytes_max = 0x1800, |
| .periods_min = 2, |
| .periods_max = 3, |
| .fifo_size = 0, |
| }; |
| |
| static const struct snd_pcm_hardware snd_msnd_capture = { |
| .info = SNDRV_PCM_INFO_MMAP_IOMEM | |
| SNDRV_PCM_INFO_INTERLEAVED | |
| SNDRV_PCM_INFO_MMAP_VALID | |
| SNDRV_PCM_INFO_BATCH, |
| .formats = SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE, |
| .rates = SNDRV_PCM_RATE_8000_48000, |
| .rate_min = 8000, |
| .rate_max = 48000, |
| .channels_min = 1, |
| .channels_max = 2, |
| .buffer_bytes_max = 0x3000, |
| .period_bytes_min = 0x40, |
| .period_bytes_max = 0x1800, |
| .periods_min = 2, |
| .periods_max = 3, |
| .fifo_size = 0, |
| }; |
| |
| |
| static int snd_msnd_playback_open(struct snd_pcm_substream *substream) |
| { |
| struct snd_pcm_runtime *runtime = substream->runtime; |
| struct snd_msnd *chip = snd_pcm_substream_chip(substream); |
| |
| set_bit(F_AUDIO_WRITE_INUSE, &chip->flags); |
| clear_bit(F_WRITING, &chip->flags); |
| snd_msnd_enable_irq(chip); |
| |
| runtime->dma_area = (__force void *)chip->mappedbase; |
| runtime->dma_addr = chip->base; |
| runtime->dma_bytes = 0x3000; |
| |
| chip->playback_substream = substream; |
| runtime->hw = snd_msnd_playback; |
| return 0; |
| } |
| |
| static int snd_msnd_playback_close(struct snd_pcm_substream *substream) |
| { |
| struct snd_msnd *chip = snd_pcm_substream_chip(substream); |
| |
| snd_msnd_disable_irq(chip); |
| clear_bit(F_AUDIO_WRITE_INUSE, &chip->flags); |
| return 0; |
| } |
| |
| |
| static int snd_msnd_playback_hw_params(struct snd_pcm_substream *substream, |
| struct snd_pcm_hw_params *params) |
| { |
| int i; |
| struct snd_msnd *chip = snd_pcm_substream_chip(substream); |
| void __iomem *pDAQ = chip->mappedbase + DAPQ_DATA_BUFF; |
| |
| chip->play_sample_size = snd_pcm_format_width(params_format(params)); |
| chip->play_channels = params_channels(params); |
| chip->play_sample_rate = params_rate(params); |
| |
| for (i = 0; i < 3; ++i, pDAQ += DAQDS__size) { |
| writew(chip->play_sample_size, pDAQ + DAQDS_wSampleSize); |
| writew(chip->play_channels, pDAQ + DAQDS_wChannels); |
| writew(chip->play_sample_rate, pDAQ + DAQDS_wSampleRate); |
| } |
| /* dont do this here: |
| * snd_msnd_calibrate_adc(chip->play_sample_rate); |
| */ |
| |
| return 0; |
| } |
| |
| static int snd_msnd_playback_prepare(struct snd_pcm_substream *substream) |
| { |
| struct snd_msnd *chip = snd_pcm_substream_chip(substream); |
| unsigned int pcm_size = snd_pcm_lib_buffer_bytes(substream); |
| unsigned int pcm_count = snd_pcm_lib_period_bytes(substream); |
| unsigned int pcm_periods = pcm_size / pcm_count; |
| |
| snd_msnd_play_reset_queue(chip, pcm_periods, pcm_count); |
| chip->playDMAPos = 0; |
| return 0; |
| } |
| |
| static int snd_msnd_playback_trigger(struct snd_pcm_substream *substream, |
| int cmd) |
| { |
| struct snd_msnd *chip = snd_pcm_substream_chip(substream); |
| int result = 0; |
| |
| if (cmd == SNDRV_PCM_TRIGGER_START) { |
| dev_dbg(chip->card->dev, "%s(START)\n", __func__); |
| chip->banksPlayed = 0; |
| set_bit(F_WRITING, &chip->flags); |
| snd_msnd_DAPQ(chip, 1); |
| } else if (cmd == SNDRV_PCM_TRIGGER_STOP) { |
| dev_dbg(chip->card->dev, "%s(STOP)\n", __func__); |
| /* interrupt diagnostic, comment this out later */ |
| clear_bit(F_WRITING, &chip->flags); |
| snd_msnd_send_dsp_cmd(chip, HDEX_PLAY_STOP); |
| } else { |
| dev_dbg(chip->card->dev, "%s(?????)\n", __func__); |
| result = -EINVAL; |
| } |
| |
| dev_dbg(chip->card->dev, "%s() ENDE\n", __func__); |
| return result; |
| } |
| |
| static snd_pcm_uframes_t |
| snd_msnd_playback_pointer(struct snd_pcm_substream *substream) |
| { |
| struct snd_msnd *chip = snd_pcm_substream_chip(substream); |
| |
| return bytes_to_frames(substream->runtime, chip->playDMAPos); |
| } |
| |
| |
| static const struct snd_pcm_ops snd_msnd_playback_ops = { |
| .open = snd_msnd_playback_open, |
| .close = snd_msnd_playback_close, |
| .hw_params = snd_msnd_playback_hw_params, |
| .prepare = snd_msnd_playback_prepare, |
| .trigger = snd_msnd_playback_trigger, |
| .pointer = snd_msnd_playback_pointer, |
| .mmap = snd_pcm_lib_mmap_iomem, |
| }; |
| |
| static int snd_msnd_capture_open(struct snd_pcm_substream *substream) |
| { |
| struct snd_pcm_runtime *runtime = substream->runtime; |
| struct snd_msnd *chip = snd_pcm_substream_chip(substream); |
| |
| set_bit(F_AUDIO_READ_INUSE, &chip->flags); |
| snd_msnd_enable_irq(chip); |
| runtime->dma_area = (__force void *)chip->mappedbase + 0x3000; |
| runtime->dma_addr = chip->base + 0x3000; |
| runtime->dma_bytes = 0x3000; |
| memset(runtime->dma_area, 0, runtime->dma_bytes); |
| chip->capture_substream = substream; |
| runtime->hw = snd_msnd_capture; |
| return 0; |
| } |
| |
| static int snd_msnd_capture_close(struct snd_pcm_substream *substream) |
| { |
| struct snd_msnd *chip = snd_pcm_substream_chip(substream); |
| |
| snd_msnd_disable_irq(chip); |
| clear_bit(F_AUDIO_READ_INUSE, &chip->flags); |
| return 0; |
| } |
| |
| static int snd_msnd_capture_prepare(struct snd_pcm_substream *substream) |
| { |
| struct snd_msnd *chip = snd_pcm_substream_chip(substream); |
| unsigned int pcm_size = snd_pcm_lib_buffer_bytes(substream); |
| unsigned int pcm_count = snd_pcm_lib_period_bytes(substream); |
| unsigned int pcm_periods = pcm_size / pcm_count; |
| |
| snd_msnd_capture_reset_queue(chip, pcm_periods, pcm_count); |
| chip->captureDMAPos = 0; |
| return 0; |
| } |
| |
| static int snd_msnd_capture_trigger(struct snd_pcm_substream *substream, |
| int cmd) |
| { |
| struct snd_msnd *chip = snd_pcm_substream_chip(substream); |
| |
| if (cmd == SNDRV_PCM_TRIGGER_START) { |
| chip->last_recbank = -1; |
| set_bit(F_READING, &chip->flags); |
| if (snd_msnd_send_dsp_cmd(chip, HDEX_RECORD_START) == 0) |
| return 0; |
| |
| clear_bit(F_READING, &chip->flags); |
| } else if (cmd == SNDRV_PCM_TRIGGER_STOP) { |
| clear_bit(F_READING, &chip->flags); |
| snd_msnd_send_dsp_cmd(chip, HDEX_RECORD_STOP); |
| return 0; |
| } |
| return -EINVAL; |
| } |
| |
| |
| static snd_pcm_uframes_t |
| snd_msnd_capture_pointer(struct snd_pcm_substream *substream) |
| { |
| struct snd_pcm_runtime *runtime = substream->runtime; |
| struct snd_msnd *chip = snd_pcm_substream_chip(substream); |
| |
| return bytes_to_frames(runtime, chip->captureDMAPos); |
| } |
| |
| |
| static int snd_msnd_capture_hw_params(struct snd_pcm_substream *substream, |
| struct snd_pcm_hw_params *params) |
| { |
| int i; |
| struct snd_msnd *chip = snd_pcm_substream_chip(substream); |
| void __iomem *pDAQ = chip->mappedbase + DARQ_DATA_BUFF; |
| |
| chip->capture_sample_size = snd_pcm_format_width(params_format(params)); |
| chip->capture_channels = params_channels(params); |
| chip->capture_sample_rate = params_rate(params); |
| |
| for (i = 0; i < 3; ++i, pDAQ += DAQDS__size) { |
| writew(chip->capture_sample_size, pDAQ + DAQDS_wSampleSize); |
| writew(chip->capture_channels, pDAQ + DAQDS_wChannels); |
| writew(chip->capture_sample_rate, pDAQ + DAQDS_wSampleRate); |
| } |
| return 0; |
| } |
| |
| |
| static const struct snd_pcm_ops snd_msnd_capture_ops = { |
| .open = snd_msnd_capture_open, |
| .close = snd_msnd_capture_close, |
| .hw_params = snd_msnd_capture_hw_params, |
| .prepare = snd_msnd_capture_prepare, |
| .trigger = snd_msnd_capture_trigger, |
| .pointer = snd_msnd_capture_pointer, |
| .mmap = snd_pcm_lib_mmap_iomem, |
| }; |
| |
| |
| int snd_msnd_pcm(struct snd_card *card, int device) |
| { |
| struct snd_msnd *chip = card->private_data; |
| struct snd_pcm *pcm; |
| int err; |
| |
| err = snd_pcm_new(card, "MSNDPINNACLE", device, 1, 1, &pcm); |
| if (err < 0) |
| return err; |
| |
| snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_msnd_playback_ops); |
| snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_msnd_capture_ops); |
| |
| pcm->private_data = chip; |
| strcpy(pcm->name, "Hurricane"); |
| |
| return 0; |
| } |
| EXPORT_SYMBOL(snd_msnd_pcm); |
| |
| MODULE_DESCRIPTION("Common routines for Turtle Beach Multisound drivers"); |
| MODULE_LICENSE("GPL"); |
| |