⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 als4000.c

📁 linux 内核源代码
💻 C
📖 第 1 页 / 共 2 页
字号:
/* *  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 by Andreas Mohr <hw7oshyuv3001@sneakemail.com> * *  Framework borrowed from Massimo Piccioni's card-als100.c. * * *  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., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA * * 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. Pretty verbose chip docu (ALS4000a.PDF) *  can be found on the ALSA web site. * *  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: * - Proper shared IRQ handling? * - power management? (card can do voice wakeup according to datasheet!!) */#include <sound/driver.h>#include <asm/io.h>#include <linux/init.h>#include <linux/pci.h>#include <linux/slab.h>#include <linux/gameport.h>#include <linux/moduleparam.h>#include <linux/dma-mapping.h>#include <sound/core.h>#include <sound/pcm.h>#include <sound/rawmidi.h>#include <sound/mpu401.h>#include <sound/opl3.h>#include <sound/sb.h>#include <sound/initval.h>MODULE_AUTHOR("Bart Hartgers <bart@etpmod.phys.tue.nl>");MODULE_DESCRIPTION("Avance Logic ALS4000");MODULE_LICENSE("GPL");MODULE_SUPPORTED_DEVICE("{{Avance Logic,ALS4000}}");#if defined(CONFIG_GAMEPORT) || (defined(MODULE) && defined(CONFIG_GAMEPORT_MODULE))#define SUPPORT_JOYSTICK 1#endifstatic int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;	/* Index 0-MAX */static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;	/* ID for this card */static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;	/* Enable this card */#ifdef SUPPORT_JOYSTICKstatic int joystick_port[SNDRV_CARDS];#endifmodule_param_array(index, int, NULL, 0444);MODULE_PARM_DESC(index, "Index value for ALS4000 soundcard.");module_param_array(id, charp, NULL, 0444);MODULE_PARM_DESC(id, "ID string for ALS4000 soundcard.");module_param_array(enable, bool, NULL, 0444);MODULE_PARM_DESC(enable, "Enable ALS4000 soundcard.");#ifdef SUPPORT_JOYSTICKmodule_param_array(joystick_port, int, NULL, 0444);MODULE_PARM_DESC(joystick_port, "Joystick port address for ALS4000 soundcard. (0 = disabled)");#endifstruct snd_card_als4000 {	/* most frequent access first */	unsigned long gcr;	struct pci_dev *pci;	struct snd_sb *chip;#ifdef SUPPORT_JOYSTICK	struct gameport *gameport;#endif};static struct pci_device_id snd_als4000_ids[] = {	{ 0x4005, 0x4000, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0, },   /* ALS4000 */	{ 0, }};MODULE_DEVICE_TABLE(pci, snd_als4000_ids);static inline void snd_als4000_gcr_write_addr(unsigned long port, u32 reg, u32 val){	outb(reg, port+0x0c);	outl(val, port+0x08);}static inline void snd_als4000_gcr_write(struct snd_sb *sb, u32 reg, u32 val){	snd_als4000_gcr_write_addr(sb->alt_port, reg, val);}	static inline u32 snd_als4000_gcr_read_addr(unsigned long port, u32 reg){	outb(reg, port+0x0c);	return inl(port+0x08);}static inline u32 snd_als4000_gcr_read(struct snd_sb *sb, u32 reg){	return snd_als4000_gcr_read_addr(sb->alt_port, reg);}static void snd_als4000_set_rate(struct snd_sb *chip, unsigned int rate){	if (!(chip->mode & SB_RATE_LOCK)) {		snd_sbdsp_command(chip, SB_DSP_SAMPLE_RATE_OUT);		snd_sbdsp_command(chip, rate>>8);		snd_sbdsp_command(chip, rate);	}}static inline void snd_als4000_set_capture_dma(struct snd_sb *chip,					       dma_addr_t addr, unsigned size){	snd_als4000_gcr_write(chip, 0xa2, addr);	snd_als4000_gcr_write(chip, 0xa3, (size-1));}static inline void snd_als4000_set_playback_dma(struct snd_sb *chip,						dma_addr_t addr, unsigned size){	snd_als4000_gcr_write(chip, 0x91, addr);	snd_als4000_gcr_write(chip, 0x92, (size-1)|0x180000);}#define ALS4000_FORMAT_SIGNED	(1<<0)#define ALS4000_FORMAT_16BIT	(1<<1)#define ALS4000_FORMAT_STEREO	(1<<2)static int 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;}/* structure for setting up playback */static const struct {	unsigned char dsp_cmd, dma_on, dma_off, format;} playback_cmd_vals[]={/* ALS4000_FORMAT_U8_MONO */{ SB_DSP4_OUT8_AI, SB_DSP_DMA8_ON, SB_DSP_DMA8_OFF, SB_DSP4_MODE_UNS_MONO },/* ALS4000_FORMAT_S8_MONO */	{ SB_DSP4_OUT8_AI, SB_DSP_DMA8_ON, SB_DSP_DMA8_OFF, SB_DSP4_MODE_SIGN_MONO },/* ALS4000_FORMAT_U16L_MONO */{ SB_DSP4_OUT16_AI, SB_DSP_DMA16_ON, SB_DSP_DMA16_OFF, SB_DSP4_MODE_UNS_MONO },/* ALS4000_FORMAT_S16L_MONO */{ SB_DSP4_OUT16_AI, SB_DSP_DMA16_ON, SB_DSP_DMA16_OFF, SB_DSP4_MODE_SIGN_MONO },/* ALS4000_FORMAT_U8_STEREO */{ SB_DSP4_OUT8_AI, SB_DSP_DMA8_ON, SB_DSP_DMA8_OFF, SB_DSP4_MODE_UNS_STEREO },/* ALS4000_FORMAT_S8_STEREO */	{ SB_DSP4_OUT8_AI, SB_DSP_DMA8_ON, SB_DSP_DMA8_OFF, SB_DSP4_MODE_SIGN_STEREO },/* ALS4000_FORMAT_U16L_STEREO */{ SB_DSP4_OUT16_AI, SB_DSP_DMA16_ON, SB_DSP_DMA16_OFF, SB_DSP4_MODE_UNS_STEREO },/* ALS4000_FORMAT_S16L_STEREO */{ SB_DSP4_OUT16_AI, SB_DSP_DMA16_ON, SB_DSP_DMA16_OFF, SB_DSP4_MODE_SIGN_STEREO },};#define playback_cmd(chip) (playback_cmd_vals[(chip)->playback_format])/* structure for setting up capture */enum { CMD_WIDTH8=0x04, CMD_SIGNED=0x10, CMD_MONO=0x80, CMD_STEREO=0xA0 };static const unsigned char capture_cmd_vals[]={CMD_WIDTH8|CMD_MONO,			/* ALS4000_FORMAT_U8_MONO */CMD_WIDTH8|CMD_SIGNED|CMD_MONO,		/* ALS4000_FORMAT_S8_MONO */	CMD_MONO,				/* ALS4000_FORMAT_U16L_MONO */CMD_SIGNED|CMD_MONO,			/* ALS4000_FORMAT_S16L_MONO */CMD_WIDTH8|CMD_STEREO,			/* ALS4000_FORMAT_U8_STEREO */CMD_WIDTH8|CMD_SIGNED|CMD_STEREO,	/* ALS4000_FORMAT_S8_STEREO */	CMD_STEREO,				/* ALS4000_FORMAT_U16L_STEREO */CMD_SIGNED|CMD_STEREO,			/* ALS4000_FORMAT_S16L_STEREO */};	#define capture_cmd(chip) (capture_cmd_vals[(chip)->capture_format])static int snd_als4000_hw_params(struct snd_pcm_substream *substream,				 struct snd_pcm_hw_params *hw_params){	return snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(hw_params));}static int snd_als4000_hw_free(struct snd_pcm_substream *substream){	snd_pcm_lib_free_pages(substream);	return 0;}static int snd_als4000_capture_prepare(struct snd_pcm_substream *substream){	struct snd_sb *chip = snd_pcm_substream_chip(substream);	struct snd_pcm_runtime *runtime = substream->runtime;	unsigned long size;	unsigned count;	chip->capture_format = snd_als4000_get_format(runtime);			size = snd_pcm_lib_buffer_bytes(substream);	count = snd_pcm_lib_period_bytes(substream);		if (chip->capture_format & ALS4000_FORMAT_16BIT)		count >>=1;	count--;	spin_lock_irq(&chip->reg_lock);	snd_als4000_set_rate(chip, runtime->rate);	snd_als4000_set_capture_dma(chip, runtime->dma_addr, size);	spin_unlock_irq(&chip->reg_lock);	spin_lock_irq(&chip->mixer_lock);	snd_sbmixer_write(chip, 0xdc, count);	snd_sbmixer_write(chip, 0xdd, count>>8);	spin_unlock_irq(&chip->mixer_lock);	return 0;}static int snd_als4000_playback_prepare(struct snd_pcm_substream *substream){	struct snd_sb *chip = snd_pcm_substream_chip(substream);	struct snd_pcm_runtime *runtime = substream->runtime;	unsigned long size;	unsigned count;	chip->playback_format = snd_als4000_get_format(runtime);		size = snd_pcm_lib_buffer_bytes(substream);	count = snd_pcm_lib_period_bytes(substream);		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);	snd_sbdsp_command(chip, count>>8);	snd_sbdsp_command(chip, playback_cmd(chip).dma_off);		spin_unlock_irq(&chip->reg_lock);		return 0;}static int snd_als4000_capture_trigger(struct snd_pcm_substream *substream, int cmd){	struct snd_sb *chip = snd_pcm_substream_chip(substream);	int result = 0;		spin_lock(&chip->mixer_lock);	switch (cmd) {	case SNDRV_PCM_TRIGGER_START:	case SNDRV_PCM_TRIGGER_RESUME:		chip->mode |= SB_RATE_LOCK_CAPTURE;		snd_sbmixer_write(chip, 0xde, capture_cmd(chip));		break;	case SNDRV_PCM_TRIGGER_STOP:	case SNDRV_PCM_TRIGGER_SUSPEND:		chip->mode &= ~SB_RATE_LOCK_CAPTURE;		snd_sbmixer_write(chip, 0xde, 0);		break;	default:		result = -EINVAL;		break;	}	spin_unlock(&chip->mixer_lock);	return result;}static int 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;}static snd_pcm_uframes_t snd_als4000_capture_pointer(struct snd_pcm_substream *substream){	struct snd_sb *chip = snd_pcm_substream_chip(substream);	unsigned int result;	spin_lock(&chip->reg_lock);		result = snd_als4000_gcr_read(chip, 0xa4) & 0xffff;	spin_unlock(&chip->reg_lock);	return bytes_to_frames( substream->runtime, result );}static snd_pcm_uframes_t snd_als4000_playback_pointer(struct snd_pcm_substream *substream){	struct snd_sb *chip = snd_pcm_substream_chip(substream);	unsigned result;	spin_lock(&chip->reg_lock);		result = snd_als4000_gcr_read(chip, 0xa0) & 0xffff;	spin_unlock(&chip->reg_lock);	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 + 0x0e). 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. * 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 gcr_status;	unsigned sb_status;	/* find out which bit of the ALS4000 produced the interrupt */	gcr_status = inb(chip->alt_port + 0xe);	if ((gcr_status & 0x80) && (chip->playback_substream)) /* playback */		snd_pcm_period_elapsed(chip->playback_substream);	if ((gcr_status & 0x40) && (chip->capture_substream)) /* capturing */		snd_pcm_period_elapsed(chip->capture_substream);	if ((gcr_status & 0x10) && (chip->rmidi)) /* MPU401 interrupt */		snd_mpu401_uart_interrupt(irq, chip->rmidi->private_data);	/* release the gcr */	outb(gcr_status, chip->alt_port + 0xe);		spin_lock(&chip->mixer_lock);	sb_status = snd_sbmixer_read(chip, SB_DSP4_IRQSTATUS);	spin_unlock(&chip->mixer_lock);		if (sb_status & SB_IRQTYPE_8BIT) 		snd_sb_ack_8bit(chip);	if (sb_status & SB_IRQTYPE_16BIT) 		snd_sb_ack_16bit(chip);	if (sb_status & SB_IRQTYPE_MPUIN)		inb(chip->mpu_port);	if (sb_status & 0x20)		inb(SBP(chip, RESET));	return IRQ_HANDLED;}/*****************************************************************/static struct snd_pcm_hardware snd_als4000_playback ={	.info =			(SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED |				 SNDRV_PCM_INFO_MMAP_VALID),	.formats =		SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_U8 |				SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_U16_LE,	/* formats */	.rates =		SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000,	.rate_min =		4000,	.rate_max =		48000,	.channels_min =		1,	.channels_max =		2,	.buffer_bytes_max =	65536,

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -