📄 es1938.c
字号:
/* * Driver for ESS Solo-1 (ES1938, ES1946, ES1969) soundcard * Copyright (c) by Jaromir Koutek <miri@punknet.cz>, * Jaroslav Kysela <perex@perex.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 <linux/dma-mapping.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 <sound/tlv.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}}");#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 */module_param_array(index, int, NULL, 0444);MODULE_PARM_DESC(index, "Index value for ESS Solo-1 soundcard.");module_param_array(id, charp, NULL, 0444);MODULE_PARM_DESC(id, "ID string for ESS Solo-1 soundcard.");module_param_array(enable, bool, NULL, 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/* */#define SAVED_REG_SIZE 32 /* max. number of registers to save */struct 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; struct snd_kcontrol *hw_volume; struct snd_kcontrol *hw_switch; struct snd_kcontrol *master_volume; struct snd_kcontrol *master_switch; struct pci_dev *pci; struct snd_card *card; struct snd_pcm *pcm; struct snd_pcm_substream *capture_substream; struct snd_pcm_substream *playback1_substream; struct snd_pcm_substream *playback2_substream; struct snd_rawmidi *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; struct snd_info_entry *proc_entry;#ifdef SUPPORT_JOYSTICK 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);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(struct es1938 *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(KERN_DEBUG "Mixer reg %02x set to %02x\n", reg, val);#endif}/* ----------------------------------------------------------------- * Read from a mixer register * -----------------------------------------------------------------*/static int snd_es1938_mixer_read(struct es1938 *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(KERN_DEBUG "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(struct es1938 *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(KERN_DEBUG "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(struct es1938 *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(KERN_ERR "snd_es1938_write_cmd timeout (0x02%x/0x02%x)\n", cmd, v);}/* ----------------------------------------------------------------- * Read the Read Data Buffer * -----------------------------------------------------------------*/static int snd_es1938_get_byte(struct es1938 *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(KERN_ERR "get_byte timeout: status 0x02%x\n", v); return -ENODEV;}/* ----------------------------------------------------------------- * Write value cmd register * -----------------------------------------------------------------*/static void snd_es1938_write(struct es1938 *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(KERN_DEBUG "Reg %02x set to %02x\n", reg, val);#endif}/* ----------------------------------------------------------------- * Read data from cmd register and return it * -----------------------------------------------------------------*/static unsigned char snd_es1938_read(struct es1938 *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(KERN_DEBUG "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(struct es1938 *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(KERN_DEBUG "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(struct es1938 *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(KERN_ERR "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); snd_es1938_bits(chip, ESS_CMD_DRQCONTROL, 0xf0, 0x50); snd_es1938_write_cmd(chip, ESS_CMD_ENABLEAUDIO1); /* Set spatializer parameters to recommended values */ snd_es1938_mixer_write(chip, 0x54, 0x8f); snd_es1938_mixer_write(chip, 0x56, 0x95); snd_es1938_mixer_write(chip, 0x58, 0x94); snd_es1938_mixer_write(chip, 0x5a, 0x80);}/* -------------------------------------------------------------------- * Reset the FIFOs * --------------------------------------------------------------------*/static void snd_es1938_reset_fifo(struct es1938 *chip){ outb(2, SLSB_REG(chip, RESET)); outb(0, SLSB_REG(chip, RESET));}static struct snd_ratnum clocks[2] = { { .num = 793800, .den_min = 1, .den_max = 128, .den_step = 1, }, { .num = 768000, .den_min = 1, .den_max = 128, .den_step = 1, }};static struct snd_pcm_hw_constraint_ratnums hw_constraints_clocks = { .nrats = 2, .rats = clocks,};
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -