mirror of
https://github.com/torvalds/linux.git
synced 2026-06-03 20:14:06 +02:00
System suspend cannot work for msnd today because the PCM trigger paths reject SNDRV_PCM_TRIGGER_SUSPEND, and the driver has only refcounted IRQ helpers. Add the small helpers needed by the PM callbacks and restore master volume from the cached ALSA mixer state when the DSP is reinitialized. Signed-off-by: Cássio Gabriel <cassiogabrielcontato@gmail.com> Link: https://patch.msgid.link/20260409-msnd-pm-support-v1-1-2abef720d0e7@gmail.com Signed-off-by: Takashi Iwai <tiwai@suse.de>
698 lines
19 KiB
C
698 lines
19 KiB
C
// 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)
|
|
{
|
|
guard(spinlock_irqsave)(&dev->lock);
|
|
if (snd_msnd_wait_HC0(dev) == 0) {
|
|
outb(cmd, dev->io + HP_CVR);
|
|
return 0;
|
|
}
|
|
|
|
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);
|
|
|
|
static int __snd_msnd_enable_irq(struct snd_msnd *dev)
|
|
{
|
|
dev_dbg(dev->card->dev, LOGNAME ": Enabling IRQ\n");
|
|
|
|
guard(spinlock_irqsave)(&dev->lock);
|
|
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);
|
|
return 0;
|
|
}
|
|
|
|
dev_dbg(dev->card->dev, LOGNAME ": Enable IRQ failed\n");
|
|
|
|
return -EIO;
|
|
}
|
|
|
|
static int __snd_msnd_disable_irq(struct snd_msnd *dev)
|
|
{
|
|
dev_dbg(dev->card->dev, LOGNAME ": Disabling IRQ\n");
|
|
|
|
guard(spinlock_irqsave)(&dev->lock);
|
|
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);
|
|
return 0;
|
|
}
|
|
|
|
dev_dbg(dev->card->dev, LOGNAME ": Disable IRQ failed\n");
|
|
|
|
return -EIO;
|
|
}
|
|
|
|
int snd_msnd_enable_irq(struct snd_msnd *dev)
|
|
{
|
|
if (dev->irq_ref++)
|
|
return 0;
|
|
|
|
return __snd_msnd_enable_irq(dev);
|
|
}
|
|
EXPORT_SYMBOL(snd_msnd_enable_irq);
|
|
|
|
int snd_msnd_disable_irq(struct snd_msnd *dev)
|
|
{
|
|
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);
|
|
|
|
return __snd_msnd_disable_irq(dev);
|
|
}
|
|
EXPORT_SYMBOL(snd_msnd_disable_irq);
|
|
|
|
int snd_msnd_force_irq(struct snd_msnd *dev, bool enable)
|
|
{
|
|
if (!dev->irq_ref)
|
|
return 0;
|
|
|
|
return enable ? __snd_msnd_enable_irq(dev) :
|
|
__snd_msnd_disable_irq(dev);
|
|
}
|
|
EXPORT_SYMBOL(snd_msnd_force_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;
|
|
|
|
/* 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:*/
|
|
scoped_guard(spinlock_irqsave, &chip->lock) {
|
|
outb(HPBLKSEL_1, chip->io + HP_BLKS);
|
|
memset_io(chip->mappedbase, 0, DAR_BUFF_SIZE * 3);
|
|
outb(HPBLKSEL_0, chip->io + HP_BLKS);
|
|
}
|
|
#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);
|
|
|
|
switch (cmd) {
|
|
case 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);
|
|
break;
|
|
case SNDRV_PCM_TRIGGER_STOP:
|
|
case SNDRV_PCM_TRIGGER_SUSPEND:
|
|
dev_dbg(chip->card->dev, "%s(STOP)\n", __func__);
|
|
clear_bit(F_WRITING, &chip->flags);
|
|
snd_msnd_send_dsp_cmd(chip, HDEX_PLAY_STOP);
|
|
break;
|
|
default:
|
|
dev_dbg(chip->card->dev, "%s(?????)\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
dev_dbg(chip->card->dev, "%s() ENDE\n", __func__);
|
|
return 0;
|
|
}
|
|
|
|
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);
|
|
|
|
switch (cmd) {
|
|
case 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);
|
|
break;
|
|
case SNDRV_PCM_TRIGGER_STOP:
|
|
case SNDRV_PCM_TRIGGER_SUSPEND:
|
|
clear_bit(F_READING, &chip->flags);
|
|
snd_msnd_send_dsp_cmd(chip, HDEX_RECORD_STOP);
|
|
return 0;
|
|
default:
|
|
break;
|
|
}
|
|
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;
|
|
strscpy(pcm->name, "Hurricane");
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(snd_msnd_pcm);
|
|
|
|
MODULE_DESCRIPTION("Common routines for Turtle Beach Multisound drivers");
|
|
MODULE_LICENSE("GPL");
|