es1938.c

来自「Linux Kernel 2.6.9 for OMAP1710」· C语言 代码 · 共 1,732 行 · 第 1/4 页

C
1,732
字号
/* *  Driver for ESS Solo-1 (ES1938, ES1946, ES1969) soundcard *  Copyright (c) by Jaromir Koutek <miri@punknet.cz>, *                   Jaroslav Kysela <perex@suse.cz>, *                   Thomas Sailer <sailer@ife.ee.ethz.ch>, *                   Abramo Bagnara <abramo@alsa-project.org>, *                   Markus Gruber <gruber@eikon.tum.de> *  * Rewritten from sonicvibes.c source. * *  TODO: *    Rewrite better spinlocks * * *   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:  - Capture data is written unaligned starting from dma_base + 1 so I need to    disable mmap and to add a copy callback.  - After several cycle of the following:    while : ; do arecord -d1 -f cd -t raw | aplay -f cd ; done    a "playback write error (DMA or IRQ trouble?)" may happen.    This is due to playback interrupts not generated.    I suspect a timing issue.  - Sometimes the interrupt handler is invoked wrongly during playback.    This generates some harmless "Unexpected hw_pointer: wrong interrupt    acknowledge".    I've seen that using small period sizes.    Reproducible with:    mpg123 test.mp3 &    hdparm -t -T /dev/hda*/#include <sound/driver.h>#include <linux/init.h>#include <linux/interrupt.h>#include <linux/pci.h>#include <linux/slab.h>#include <linux/gameport.h>#include <linux/moduleparam.h>#include <linux/delay.h>#include <sound/core.h>#include <sound/control.h>#include <sound/pcm.h>#include <sound/opl3.h>#include <sound/mpu401.h>#include <sound/initval.h>#include <asm/io.h>MODULE_AUTHOR("Jaromir Koutek <miri@punknet.cz>");MODULE_DESCRIPTION("ESS Solo-1");MODULE_LICENSE("GPL");MODULE_SUPPORTED_DEVICE("{{ESS,ES1938},"                "{ESS,ES1946},"                "{ESS,ES1969},"		"{TerraTec,128i PCI}}");#ifndef PCI_VENDOR_ID_ESS#define PCI_VENDOR_ID_ESS		0x125d#endif#ifndef PCI_DEVICE_ID_ESS_ES1938#define PCI_DEVICE_ID_ESS_ES1938	0x1969#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 */static int boot_devs;module_param_array(index, int, boot_devs, 0444);MODULE_PARM_DESC(index, "Index value for ESS Solo-1 soundcard.");module_param_array(id, charp, boot_devs, 0444);MODULE_PARM_DESC(id, "ID string for ESS Solo-1 soundcard.");module_param_array(enable, bool, boot_devs, 0444);MODULE_PARM_DESC(enable, "Enable ESS Solo-1 soundcard.");#define SLIO_REG(chip, x) ((chip)->io_port + ESSIO_REG_##x)#define SLDM_REG(chip, x) ((chip)->ddma_port + ESSDM_REG_##x)#define SLSB_REG(chip, x) ((chip)->sb_port + ESSSB_REG_##x)#define SL_PCI_LEGACYCONTROL		0x40#define SL_PCI_CONFIG			0x50#define SL_PCI_DDMACONTROL		0x60#define ESSIO_REG_AUDIO2DMAADDR		0#define ESSIO_REG_AUDIO2DMACOUNT	4#define ESSIO_REG_AUDIO2MODE		6#define ESSIO_REG_IRQCONTROL		7#define ESSDM_REG_DMAADDR		0x00#define ESSDM_REG_DMACOUNT		0x04#define ESSDM_REG_DMACOMMAND		0x08#define ESSDM_REG_DMASTATUS		0x08#define ESSDM_REG_DMAMODE		0x0b#define ESSDM_REG_DMACLEAR		0x0d#define ESSDM_REG_DMAMASK		0x0f#define ESSSB_REG_FMLOWADDR		0x00#define ESSSB_REG_FMHIGHADDR		0x02#define ESSSB_REG_MIXERADDR		0x04#define ESSSB_REG_MIXERDATA		0x05#define ESSSB_IREG_AUDIO1		0x14#define ESSSB_IREG_MICMIX		0x1a#define ESSSB_IREG_RECSRC		0x1c#define ESSSB_IREG_MASTER		0x32#define ESSSB_IREG_FM			0x36#define ESSSB_IREG_AUXACD		0x38#define ESSSB_IREG_AUXB			0x3a#define ESSSB_IREG_PCSPEAKER		0x3c#define ESSSB_IREG_LINE			0x3e#define ESSSB_IREG_SPATCONTROL		0x50#define ESSSB_IREG_SPATLEVEL		0x52#define ESSSB_IREG_MASTER_LEFT		0x60#define ESSSB_IREG_MASTER_RIGHT		0x62#define ESSSB_IREG_MPU401CONTROL	0x64#define ESSSB_IREG_MICMIXRECORD		0x68#define ESSSB_IREG_AUDIO2RECORD		0x69#define ESSSB_IREG_AUXACDRECORD		0x6a#define ESSSB_IREG_FMRECORD		0x6b#define ESSSB_IREG_AUXBRECORD		0x6c#define ESSSB_IREG_MONO			0x6d#define ESSSB_IREG_LINERECORD		0x6e#define ESSSB_IREG_MONORECORD		0x6f#define ESSSB_IREG_AUDIO2SAMPLE		0x70#define ESSSB_IREG_AUDIO2MODE		0x71#define ESSSB_IREG_AUDIO2FILTER		0x72#define ESSSB_IREG_AUDIO2TCOUNTL	0x74#define ESSSB_IREG_AUDIO2TCOUNTH	0x76#define ESSSB_IREG_AUDIO2CONTROL1	0x78#define ESSSB_IREG_AUDIO2CONTROL2	0x7a#define ESSSB_IREG_AUDIO2		0x7c#define ESSSB_REG_RESET			0x06#define ESSSB_REG_READDATA		0x0a#define ESSSB_REG_WRITEDATA		0x0c#define ESSSB_REG_READSTATUS		0x0c#define ESSSB_REG_STATUS		0x0e#define ESS_CMD_EXTSAMPLERATE		0xa1#define ESS_CMD_FILTERDIV		0xa2#define ESS_CMD_DMACNTRELOADL		0xa4#define ESS_CMD_DMACNTRELOADH		0xa5#define ESS_CMD_ANALOGCONTROL		0xa8#define ESS_CMD_IRQCONTROL		0xb1#define ESS_CMD_DRQCONTROL		0xb2#define ESS_CMD_RECLEVEL		0xb4#define ESS_CMD_SETFORMAT		0xb6#define ESS_CMD_SETFORMAT2		0xb7#define ESS_CMD_DMACONTROL		0xb8#define ESS_CMD_DMATYPE			0xb9#define ESS_CMD_OFFSETLEFT		0xba	#define ESS_CMD_OFFSETRIGHT		0xbb#define ESS_CMD_READREG			0xc0#define ESS_CMD_ENABLEEXT		0xc6#define ESS_CMD_PAUSEDMA		0xd0#define ESS_CMD_ENABLEAUDIO1		0xd1#define ESS_CMD_STOPAUDIO1		0xd3#define ESS_CMD_AUDIO1STATUS		0xd8#define ESS_CMD_CONTDMA			0xd4#define ESS_CMD_TESTIRQ			0xf2#define ESS_RECSRC_MIC		0#define ESS_RECSRC_AUXACD	2#define ESS_RECSRC_AUXB		5#define ESS_RECSRC_LINE		6#define ESS_RECSRC_NONE		7#define DAC1 0x01#define ADC1 0x02#define DAC2 0x04/* */typedef struct _snd_es1938 es1938_t;#define SAVED_REG_SIZE	32 /* max. number of registers to save */struct _snd_es1938 {	int irq;	unsigned long io_port;	unsigned long sb_port;	unsigned long vc_port;	unsigned long mpu_port;	unsigned long game_port;	unsigned long ddma_port;	unsigned char irqmask;	unsigned char revision;	snd_kcontrol_t *hw_volume;	snd_kcontrol_t *hw_switch;	snd_kcontrol_t *master_volume;	snd_kcontrol_t *master_switch;	struct pci_dev *pci;	snd_card_t *card;	snd_pcm_t *pcm;	snd_pcm_substream_t *capture_substream;	snd_pcm_substream_t *playback1_substream;	snd_pcm_substream_t *playback2_substream;	snd_kmixer_t *mixer;	snd_rawmidi_t *rmidi;	unsigned int dma1_size;	unsigned int dma2_size;	unsigned int dma1_start;	unsigned int dma2_start;	unsigned int dma1_shift;	unsigned int dma2_shift;	unsigned int active;	spinlock_t reg_lock;	spinlock_t mixer_lock;        snd_info_entry_t *proc_entry;#if defined(CONFIG_GAMEPORT) || (defined(MODULE) && defined(CONFIG_GAMEPORT_MODULE))	struct gameport gameport;#endif#ifdef CONFIG_PM	unsigned char saved_regs[SAVED_REG_SIZE];#endif};static irqreturn_t snd_es1938_interrupt(int irq, void *dev_id, struct pt_regs *regs);static struct pci_device_id snd_es1938_ids[] = {        { 0x125d, 0x1969, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0, },   /* Solo-1 */	{ 0, }};MODULE_DEVICE_TABLE(pci, snd_es1938_ids);#define RESET_LOOP_TIMEOUT	0x10000#define WRITE_LOOP_TIMEOUT	0x10000#define GET_LOOP_TIMEOUT	0x01000#undef REG_DEBUG/* ----------------------------------------------------------------- * Write to a mixer register * -----------------------------------------------------------------*/static void snd_es1938_mixer_write(es1938_t *chip, unsigned char reg, unsigned char val){	unsigned long flags;	spin_lock_irqsave(&chip->mixer_lock, flags);	outb(reg, SLSB_REG(chip, MIXERADDR));	outb(val, SLSB_REG(chip, MIXERDATA));	spin_unlock_irqrestore(&chip->mixer_lock, flags);#ifdef REG_DEBUG	snd_printk("Mixer reg %02x set to %02x\n", reg, val);#endif}/* ----------------------------------------------------------------- * Read from a mixer register * -----------------------------------------------------------------*/static int snd_es1938_mixer_read(es1938_t *chip, unsigned char reg){	int data;	unsigned long flags;	spin_lock_irqsave(&chip->mixer_lock, flags);	outb(reg, SLSB_REG(chip, MIXERADDR));	data = inb(SLSB_REG(chip, MIXERDATA));	spin_unlock_irqrestore(&chip->mixer_lock, flags);#ifdef REG_DEBUG	snd_printk("Mixer reg %02x now is %02x\n", reg, data);#endif	return data;}/* ----------------------------------------------------------------- * Write to some bits of a mixer register (return old value) * -----------------------------------------------------------------*/static int snd_es1938_mixer_bits(es1938_t *chip, unsigned char reg, unsigned char mask, unsigned char val){	unsigned long flags;	unsigned char old, new, oval;	spin_lock_irqsave(&chip->mixer_lock, flags);	outb(reg, SLSB_REG(chip, MIXERADDR));	old = inb(SLSB_REG(chip, MIXERDATA));	oval = old & mask;	if (val != oval) {		new = (old & ~mask) | (val & mask);		outb(new, SLSB_REG(chip, MIXERDATA));#ifdef REG_DEBUG		snd_printk("Mixer reg %02x was %02x, set to %02x\n", reg, old, new);#endif	}	spin_unlock_irqrestore(&chip->mixer_lock, flags);	return oval;}/* ----------------------------------------------------------------- * Write command to Controller Registers * -----------------------------------------------------------------*/static void snd_es1938_write_cmd(es1938_t *chip, unsigned char cmd){	int i;	unsigned char v;	for (i = 0; i < WRITE_LOOP_TIMEOUT; i++) {		if (!(v = inb(SLSB_REG(chip, READSTATUS)) & 0x80)) {			outb(cmd, SLSB_REG(chip, WRITEDATA));			return;		}	}	printk("snd_es1938_write_cmd timeout (0x02%x/0x02%x)\n", cmd, v);}/* ----------------------------------------------------------------- * Read the Read Data Buffer * -----------------------------------------------------------------*/static int snd_es1938_get_byte(es1938_t *chip){	int i;	unsigned char v;	for (i = GET_LOOP_TIMEOUT; i; i--)		if ((v = inb(SLSB_REG(chip, STATUS))) & 0x80)			return inb(SLSB_REG(chip, READDATA));	snd_printk("get_byte timeout: status 0x02%x\n", v);	return -ENODEV;}/* ----------------------------------------------------------------- * Write value cmd register * -----------------------------------------------------------------*/static void snd_es1938_write(es1938_t *chip, unsigned char reg, unsigned char val){	unsigned long flags;	spin_lock_irqsave(&chip->reg_lock, flags);	snd_es1938_write_cmd(chip, reg);	snd_es1938_write_cmd(chip, val);	spin_unlock_irqrestore(&chip->reg_lock, flags);#ifdef REG_DEBUG	snd_printk("Reg %02x set to %02x\n", reg, val);#endif}/* ----------------------------------------------------------------- * Read data from cmd register and return it * -----------------------------------------------------------------*/static unsigned char snd_es1938_read(es1938_t *chip, unsigned char reg){	unsigned char val;	unsigned long flags;	spin_lock_irqsave(&chip->reg_lock, flags);	snd_es1938_write_cmd(chip, ESS_CMD_READREG);	snd_es1938_write_cmd(chip, reg);	val = snd_es1938_get_byte(chip);	spin_unlock_irqrestore(&chip->reg_lock, flags);#ifdef REG_DEBUG	snd_printk("Reg %02x now is %02x\n", reg, val);#endif	return val;}/* ----------------------------------------------------------------- * Write data to cmd register and return old value * -----------------------------------------------------------------*/static int snd_es1938_bits(es1938_t *chip, unsigned char reg, unsigned char mask, unsigned char val){	unsigned long flags;	unsigned char old, new, oval;	spin_lock_irqsave(&chip->reg_lock, flags);	snd_es1938_write_cmd(chip, ESS_CMD_READREG);	snd_es1938_write_cmd(chip, reg);	old = snd_es1938_get_byte(chip);	oval = old & mask;	if (val != oval) {		snd_es1938_write_cmd(chip, reg);		new = (old & ~mask) | (val & mask);		snd_es1938_write_cmd(chip, new);#ifdef REG_DEBUG		snd_printk("Reg %02x was %02x, set to %02x\n", reg, old, new);#endif	}	spin_unlock_irqrestore(&chip->reg_lock, flags);	return oval;}/* -------------------------------------------------------------------- * Reset the chip * --------------------------------------------------------------------*/static void snd_es1938_reset(es1938_t *chip){	int i;	outb(3, SLSB_REG(chip, RESET));	inb(SLSB_REG(chip, RESET));	outb(0, SLSB_REG(chip, RESET));	for (i = 0; i < RESET_LOOP_TIMEOUT; i++) {		if (inb(SLSB_REG(chip, STATUS)) & 0x80) {			if (inb(SLSB_REG(chip, READDATA)) == 0xaa)				goto __next;		}	}	snd_printk("ESS Solo-1 reset failed\n");     __next:	snd_es1938_write_cmd(chip, ESS_CMD_ENABLEEXT);	/* Demand transfer DMA: 4 bytes per DMA request */	snd_es1938_write(chip, ESS_CMD_DMATYPE, 2);	/* Change behaviour of register A1	   4x oversampling	   2nd channel DAC asynchronous */                                                      	snd_es1938_mixer_write(chip, ESSSB_IREG_AUDIO2MODE, 0x32);	/* enable/select DMA channel and IRQ channel */	snd_es1938_bits(chip, ESS_CMD_IRQCONTROL, 0xf0, 0x50);

⌨️ 快捷键说明

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