// SPDX-License-Identifier: GPL-2.0-or-later /* * card-als4000.c - driver for Avance Logic ALS4000 based soundcards. * Copyright (C) 2000 by Bart Hartgers <bart@etpmod.phys.tue.nl>, * Jaroslav Kysela <perex@perex.cz> * Copyright (C) 2002, 2008 by Andreas Mohr <hw7oshyuv3001@sneakemail.com> * * Framework borrowed from Massimo Piccioni's card-als100.c. * * NOTES * * Since Avance does not provide any meaningful documentation, and I * bought an ALS4000 based soundcard, I was forced to base this driver * on reverse engineering. * * Note: this is no longer true (thank you!): * pretty verbose chip docu (ALS4000a.PDF) can be found on the ALSA web site. * Page numbers stated anywhere below with the "SPECS_PAGE:" tag * refer to: ALS4000a.PDF specs Ver 1.0, May 28th, 1998. * * The ALS4000 seems to be the PCI-cousin of the ALS100. It contains an * ALS100-like SB DSP/mixer, an OPL3 synth, a MPU401 and a gameport * interface. These subsystems can be mapped into ISA io-port space, * using the PCI-interface. In addition, the PCI-bit provides DMA and IRQ * services to the subsystems. * * While ALS4000 is very similar to a SoundBlaster, the differences in * DMA and capturing require more changes to the SoundBlaster than * desirable, so I made this separate driver. * * The ALS4000 can do real full duplex playback/capture. * * FMDAC: * - 0x4f -> port 0x14 * - port 0x15 |= 1 * * Enable/disable 3D sound: * - 0x50 -> port 0x14 * - change bit 6 (0x40) of port 0x15 * * Set QSound: * - 0xdb -> port 0x14 * - set port 0x15: * 0x3e (mode 3), 0x3c (mode 2), 0x3a (mode 1), 0x38 (mode 0) * * Set KSound: * - value -> some port 0x0c0d * * ToDo: * - by default, don't enable legacy game and use PCI game I/O * - power management? (card can do voice wakeup according to datasheet!!)
*/
staticint snd_als4000_get_format(struct snd_pcm_runtime *runtime)
{ int result;
result = 0; if (snd_pcm_format_signed(runtime->format))
result |= ALS4000_FORMAT_SIGNED; if (snd_pcm_format_physical_width(runtime->format) == 16)
result |= ALS4000_FORMAT_16BIT; if (runtime->channels > 1)
result |= ALS4000_FORMAT_STEREO; return result;
}
if (chip->playback_format & ALS4000_FORMAT_16BIT)
count >>= 1;
count--;
/* FIXME: from second playback on, there's a lot more clicks and pops * involved here than on first playback. Fiddling with * tons of different settings didn't help (DMA, speaker on/off, * reordering, ...). Something seems to get enabled on playback * that I haven't found out how to disable again, which then causes
* the switching pops to reach the speakers the next time here. */
spin_lock_irq(&chip->reg_lock);
snd_als4000_set_rate(chip, runtime->rate);
snd_als4000_set_playback_dma(chip, runtime->dma_addr, size);
/* SPEAKER_ON not needed, since dma_on seems to also enable speaker */ /* snd_sbdsp_command(chip, SB_DSP_SPEAKER_ON); */
snd_sbdsp_command(chip, playback_cmd(chip).dsp_cmd);
snd_sbdsp_command(chip, playback_cmd(chip).format);
snd_sbdsp_command(chip, count & 0xff);
snd_sbdsp_command(chip, count >> 8);
snd_sbdsp_command(chip, playback_cmd(chip).dma_off);
spin_unlock_irq(&chip->reg_lock);
return 0;
}
staticint snd_als4000_capture_trigger(struct snd_pcm_substream *substream, int cmd)
{ struct snd_sb *chip = snd_pcm_substream_chip(substream); int result = 0;
/* FIXME race condition in here!!! chip->mode non-atomic update gets consistently protected by reg_lock always, _except_ for this place!! Probably need to take reg_lock as outer (or inner??) lock, too. (or serialize both lock operations? probably not, though... - racy?)
*/
spin_lock(&chip->mixer_lock); switch (cmd) { case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_RESUME:
chip->mode |= SB_RATE_LOCK_CAPTURE;
snd_als4_cr_write(chip, ALS4K_CR1E_FIFO2_CONTROL,
capture_cmd(chip)); break; case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_SUSPEND:
chip->mode &= ~SB_RATE_LOCK_CAPTURE;
snd_als4_cr_write(chip, ALS4K_CR1E_FIFO2_CONTROL,
capture_cmd(chip)); break; default:
result = -EINVAL; break;
}
spin_unlock(&chip->mixer_lock); return result;
}
staticint snd_als4000_playback_trigger(struct snd_pcm_substream *substream, int cmd)
{ struct snd_sb *chip = snd_pcm_substream_chip(substream); int result = 0;
spin_lock(&chip->reg_lock); switch (cmd) { case SNDRV_PCM_TRIGGER_START: case SNDRV_PCM_TRIGGER_RESUME:
chip->mode |= SB_RATE_LOCK_PLAYBACK;
snd_sbdsp_command(chip, playback_cmd(chip).dma_on); break; case SNDRV_PCM_TRIGGER_STOP: case SNDRV_PCM_TRIGGER_SUSPEND:
snd_sbdsp_command(chip, playback_cmd(chip).dma_off);
chip->mode &= ~SB_RATE_LOCK_PLAYBACK; break; default:
result = -EINVAL; break;
}
spin_unlock(&chip->reg_lock); return result;
}
spin_lock(&chip->reg_lock);
result = snd_als4k_gcr_read(chip, ALS4K_GCRA4_FIFO2_CURRENT_ADDR);
spin_unlock(&chip->reg_lock);
result &= 0xffff; return bytes_to_frames( substream->runtime, result );
}
spin_lock(&chip->reg_lock);
result = snd_als4k_gcr_read(chip, ALS4K_GCRA0_FIFO1_CURRENT_ADDR);
spin_unlock(&chip->reg_lock);
result &= 0xffff; return bytes_to_frames( substream->runtime, result );
}
/* FIXME: this IRQ routine doesn't really support IRQ sharing (we always * return IRQ_HANDLED no matter whether we actually had an IRQ flag or not). * ALS4000a.PDF writes that while ACKing IRQ in PCI block will *not* ACK * the IRQ in the SB core, ACKing IRQ in SB block *will* ACK the PCI IRQ * register (alt_port + ALS4K_IOB_0E_IRQTYPE_SB_CR1E_MPU). Probably something * could be optimized here to query/write one register only... * And even if both registers need to be queried, then there's still the * question of whether it's actually correct to ACK PCI IRQ before reading * SB IRQ like we do now, since ALS4000a.PDF mentions that PCI IRQ will *clear* * SB IRQ status. * (hmm, SPECS_PAGE: 38 mentions it the other way around!) * And do we *really* need the lock here for *reading* SB_DSP4_IRQSTATUS??
* */ static irqreturn_t snd_als4000_interrupt(int irq, void *dev_id)
{ struct snd_sb *chip = dev_id; unsigned pci_irqstatus; unsigned sb_irqstatus;
/* find out which bit of the ALS4000 PCI block produced the interrupt,
SPECS_PAGE: 38, 5 */
pci_irqstatus = snd_als4k_iobase_readb(chip->alt_port,
ALS4K_IOB_0E_IRQTYPE_SB_CR1E_MPU); if ((pci_irqstatus & ALS4K_IOB_0E_SB_DMA_IRQ)
&& (chip->playback_substream)) /* playback */
snd_pcm_period_elapsed(chip->playback_substream); if ((pci_irqstatus & ALS4K_IOB_0E_CR1E_IRQ)
&& (chip->capture_substream)) /* capturing */
snd_pcm_period_elapsed(chip->capture_substream); if ((pci_irqstatus & ALS4K_IOB_0E_MPU_IRQ)
&& (chip->rmidi)) /* MPU401 interrupt */
snd_mpu401_uart_interrupt(irq, chip->rmidi->private_data); /* ACK the PCI block IRQ */
snd_als4k_iobase_writeb(chip->alt_port,
ALS4K_IOB_0E_IRQTYPE_SB_CR1E_MPU, pci_irqstatus);
staticvoid snd_als4000_configure(struct snd_sb *chip)
{
u8 tmp; int i;
/* do some more configuration */
spin_lock_irq(&chip->mixer_lock);
tmp = snd_als4_cr_read(chip, ALS4K_CR0_SB_CONFIG);
snd_als4_cr_write(chip, ALS4K_CR0_SB_CONFIG,
tmp|ALS4K_CR0_MX80_81_REG_WRITE_ENABLE); /* always select DMA channel 0, since we do not actually use DMA
* SPECS_PAGE: 19/20 */
snd_sbmixer_write(chip, SB_DSP4_DMASETUP, SB_DMASETUP_DMA0);
snd_als4_cr_write(chip, ALS4K_CR0_SB_CONFIG,
tmp & ~ALS4K_CR0_MX80_81_REG_WRITE_ENABLE);
spin_unlock_irq(&chip->mixer_lock);
/* SPECS_PAGE: 39 */ for (i = ALS4K_GCR91_DMA0_ADDR; i <= ALS4K_GCR96_DMA3_MODE_COUNT; ++i)
snd_als4k_gcr_write(chip, i, 0); /* enable burst mode to prevent dropouts during high PCI bus usage */
snd_als4k_gcr_write(chip, ALS4K_GCR99_DMA_EMULATION_CTRL,
(snd_als4k_gcr_read(chip, ALS4K_GCR99_DMA_EMULATION_CTRL) & ~0x07) | 0x04);
spin_unlock_irq(&chip->reg_lock);
}
#ifdef SUPPORT_JOYSTICK staticint snd_als4000_create_gameport(struct snd_card_als4000 *acard, int dev)
{ struct gameport *gp; struct resource *r; int io_port;
if (joystick_port[dev] == 0) return -ENODEV;
if (joystick_port[dev] == 1) { /* auto-detect */ for (io_port = 0x200; io_port <= 0x218; io_port += 8) {
r = devm_request_region(&acard->pci->dev, io_port, 8, "ALS4000 gameport"); if (r) break;
}
} else {
io_port = joystick_port[dev];
r = devm_request_region(&acard->pci->dev, io_port, 8, "ALS4000 gameport");
}
/* check, if we can restrict PCI DMA transfers to 24 bits */ if (dma_set_mask_and_coherent(&pci->dev, DMA_BIT_MASK(24))) {
dev_err(&pci->dev, "architecture does not support 24bit PCI busmaster DMA\n"); return -ENXIO;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.