📄 azt3328.c
字号:
/* * azt3328.c - driver for Aztech AZF3328 based soundcards (e.g. PCI168). * Copyright (C) 2002, 2005, 2006, 2007 by Andreas Mohr <andi AT lisas.de> * * Framework borrowed from Bart Hartgers's als4000.c. * Driver developed on PCI168 AP(W) version (PCI rev. 10, subsystem ID 1801), * found in a Fujitsu-Siemens PC ("Cordant", aluminum case). * Other versions are: * PCI168 A(W), sub ID 1800 * PCI168 A/AP, sub ID 8000 * Please give me feedback in case you try my driver with one of these!! * * GPL LICENSE * 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 Aztech does not provide any chipset documentation, * even on repeated request to various addresses, * and the answer that was finally given was negative * (and I was stupid enough to manage to get hold of a PCI168 soundcard * in the first place >:-P}), * I was forced to base this driver on reverse engineering * (3 weeks' worth of evenings filled with driver work). * (and no, I did NOT go the easy way: to pick up a SB PCI128 for 9 Euros) * * The AZF3328 chip (note: AZF3328, *not* AZT3328, that's just the driver name * for compatibility reasons) has the following features: * * - builtin AC97 conformant codec (SNR over 80dB) * Note that "conformant" != "compliant"!! this chip's mixer register layout * *differs* from the standard AC97 layout: * they chose to not implement the headphone register (which is not a * problem since it's merely optional), yet when doing this, they committed * the grave sin of letting other registers follow immediately instead of * keeping a headphone dummy register, thereby shifting the mixer register * addresses illegally. So far unfortunately it looks like the very flexible * ALSA AC97 support is still not enough to easily compensate for such a * grave layout violation despite all tweaks and quirks mechanisms it offers. * - builtin genuine OPL3 * - full duplex 16bit playback/record at independent sampling rate * - MPU401 (+ legacy address support) FIXME: how to enable legacy addr?? * - game port (legacy address support) * - builtin 3D enhancement (said to be YAMAHA Ymersion) * - builtin DirectInput support, helps reduce CPU overhead (interrupt-driven * features supported) * - built-in General DirectX timer having a 20 bits counter * with 1us resolution (see below!) * - I2S serial port for external DAC * - supports 33MHz PCI spec 2.1, PCI power management 1.0, compliant with ACPI * - supports hardware volume control * - single chip low cost solution (128 pin QFP) * - supports programmable Sub-vendor and Sub-system ID * required for Microsoft's logo compliance (FIXME: where?) * - PCI168 AP(W) card: power amplifier with 4 Watts/channel at 4 Ohms * * Note that this driver now is actually *better* than the Windows driver, * since it additionally supports the card's 1MHz DirectX timer - just try * the following snd-seq module parameters etc.: * - options snd-seq seq_default_timer_class=2 seq_default_timer_sclass=0 * seq_default_timer_card=0 seq_client_load=1 seq_default_timer_device=0 * seq_default_timer_subdevice=0 seq_default_timer_resolution=1000000 * - "timidity -iAv -B2,8 -Os -EFreverb=0" * - "pmidi -p 128:0 jazz.mid" * * Certain PCI versions of this card are susceptible to DMA traffic underruns * in some systems (resulting in sound crackling/clicking/popping), * probably because they don't have a DMA FIFO buffer or so. * Overview (PCI ID/PCI subID/PCI rev.): * - no DMA crackling on SiS735: 0x50DC/0x1801/16 * - unknown performance: 0x50DC/0x1801/10 * (well, it's not bad on an Athlon 1800 with now very optimized IRQ handler) * * Crackling happens with VIA chipsets or, in my case, an SiS735, which is * supposed to be very fast and supposed to get rid of crackling much * better than a VIA, yet ironically I still get crackling, like many other * people with the same chipset. * Possible remedies: * - plug card into a different PCI slot, preferrably one that isn't shared * too much (this helps a lot, but not completely!) * - get rid of PCI VGA card, use AGP instead * - upgrade or downgrade BIOS * - fiddle with PCI latency settings (setpci -v -s BUSID latency_timer=XX) * Not too helpful. * - Disable ACPI/power management/"Auto Detect RAM/PCI Clk" in BIOS * * BUGS * - full-duplex might *still* be problematic, not fully tested recently * - (non-bug) "Bass/Treble or 3D settings don't work" - they do get evaluated * if you set PCM output switch to "pre 3D" instead of "post 3D". * If this can't be set, then get a mixer application that Isn't Stupid (tm) * (e.g. kmix, gamix) - unfortunately several are!! * * TODO * - test MPU401 MIDI playback etc. * - add some power micro-management (disable various units of the card * as long as they're unused). However this requires I/O ports which I * haven't figured out yet and which thus might not even exist... * The standard suspend/resume functionality could probably make use of * some improvement, too... * - figure out what all unknown port bits are responsible for * - figure out some cleverly evil scheme to possibly make ALSA AC97 code * fully accept our quite incompatible ""AC97"" mixer and thus save some * code (but I'm not too optimistic that doing this is possible at all) */#include <sound/driver.h>#include <asm/io.h>#include <linux/init.h>#include <linux/pci.h>#include <linux/delay.h>#include <linux/slab.h>#include <linux/gameport.h>#include <linux/moduleparam.h>#include <linux/dma-mapping.h>#include <sound/core.h>#include <sound/control.h>#include <sound/pcm.h>#include <sound/rawmidi.h>#include <sound/mpu401.h>#include <sound/opl3.h>#include <sound/initval.h>#include "azt3328.h"MODULE_AUTHOR("Andreas Mohr <andi AT lisas.de>");MODULE_DESCRIPTION("Aztech AZF3328 (PCI168)");MODULE_LICENSE("GPL");MODULE_SUPPORTED_DEVICE("{{Aztech,AZF3328}}");#if defined(CONFIG_GAMEPORT) || (defined(MODULE) && defined(CONFIG_GAMEPORT_MODULE))#define SUPPORT_JOYSTICK 1#endif#define DEBUG_MISC 0#define DEBUG_CALLS 0#define DEBUG_MIXER 0#define DEBUG_PLAY_REC 0#define DEBUG_IO 0#define DEBUG_TIMER 0#define MIXER_TESTING 0#if DEBUG_MISC#define snd_azf3328_dbgmisc(format, args...) printk(KERN_ERR format, ##args)#else#define snd_azf3328_dbgmisc(format, args...)#endif #if DEBUG_CALLS#define snd_azf3328_dbgcalls(format, args...) printk(format, ##args)#define snd_azf3328_dbgcallenter() printk(KERN_ERR "--> %s\n", __FUNCTION__)#define snd_azf3328_dbgcallleave() printk(KERN_ERR "<-- %s\n", __FUNCTION__)#else#define snd_azf3328_dbgcalls(format, args...)#define snd_azf3328_dbgcallenter()#define snd_azf3328_dbgcallleave()#endif #if DEBUG_MIXER#define snd_azf3328_dbgmixer(format, args...) printk(format, ##args)#else#define snd_azf3328_dbgmixer(format, args...)#endif #if DEBUG_PLAY_REC#define snd_azf3328_dbgplay(format, args...) printk(KERN_ERR format, ##args)#else#define snd_azf3328_dbgplay(format, args...)#endif #if DEBUG_MISC#define snd_azf3328_dbgtimer(format, args...) printk(KERN_ERR format, ##args)#else#define snd_azf3328_dbgtimer(format, args...)#endif static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */module_param_array(index, int, NULL, 0444);MODULE_PARM_DESC(index, "Index value for AZF3328 soundcard.");static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */module_param_array(id, charp, NULL, 0444);MODULE_PARM_DESC(id, "ID string for AZF3328 soundcard.");static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; /* Enable this card */module_param_array(enable, bool, NULL, 0444);MODULE_PARM_DESC(enable, "Enable AZF3328 soundcard.");#ifdef SUPPORT_JOYSTICKstatic int joystick[SNDRV_CARDS];module_param_array(joystick, bool, NULL, 0444);MODULE_PARM_DESC(joystick, "Enable joystick for AZF3328 soundcard.");#endifstatic int seqtimer_scaling = 128;module_param(seqtimer_scaling, int, 0444);MODULE_PARM_DESC(seqtimer_scaling, "Set 1024000Hz sequencer timer scale factor (lockup danger!). Default 128.");struct snd_azf3328 { /* often-used fields towards beginning, then grouped */ unsigned long codec_port; unsigned long io2_port; unsigned long mpu_port; unsigned long synth_port; unsigned long mixer_port; spinlock_t reg_lock; struct snd_timer *timer; struct snd_pcm *pcm; struct snd_pcm_substream *playback_substream; struct snd_pcm_substream *capture_substream; unsigned int is_playing; unsigned int is_recording; struct snd_card *card; struct snd_rawmidi *rmidi;#ifdef SUPPORT_JOYSTICK struct gameport *gameport;#endif struct pci_dev *pci; int irq;#ifdef CONFIG_PM /* register value containers for power management * Note: not always full I/O range preserved (just like Win driver!) */ u16 saved_regs_codec [AZF_IO_SIZE_CODEC_PM / 2]; u16 saved_regs_io2 [AZF_IO_SIZE_IO2_PM / 2]; u16 saved_regs_mpu [AZF_IO_SIZE_MPU_PM / 2]; u16 saved_regs_synth[AZF_IO_SIZE_SYNTH_PM / 2]; u16 saved_regs_mixer[AZF_IO_SIZE_MIXER_PM / 2];#endif};static const struct pci_device_id snd_azf3328_ids[] = { { 0x122D, 0x50DC, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, /* PCI168/3328 */ { 0x122D, 0x80DA, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0 }, /* 3328 */ { 0, }};MODULE_DEVICE_TABLE(pci, snd_azf3328_ids);static inline voidsnd_azf3328_codec_outb(const struct snd_azf3328 *chip, int reg, u8 value){ outb(value, chip->codec_port + reg);}static inline u8snd_azf3328_codec_inb(const struct snd_azf3328 *chip, int reg){ return inb(chip->codec_port + reg);}static inline voidsnd_azf3328_codec_outw(const struct snd_azf3328 *chip, int reg, u16 value){ outw(value, chip->codec_port + reg);}static inline u16snd_azf3328_codec_inw(const struct snd_azf3328 *chip, int reg){ return inw(chip->codec_port + reg);}static inline voidsnd_azf3328_codec_outl(const struct snd_azf3328 *chip, int reg, u32 value){ outl(value, chip->codec_port + reg);}static inline voidsnd_azf3328_io2_outb(const struct snd_azf3328 *chip, int reg, u8 value){ outb(value, chip->io2_port + reg);}static inline u8snd_azf3328_io2_inb(const struct snd_azf3328 *chip, int reg){ return inb(chip->io2_port + reg);}static inline voidsnd_azf3328_mixer_outw(const struct snd_azf3328 *chip, int reg, u16 value){ outw(value, chip->mixer_port + reg);}static inline u16snd_azf3328_mixer_inw(const struct snd_azf3328 *chip, int reg){ return inw(chip->mixer_port + reg);}static voidsnd_azf3328_mixer_set_mute(const struct snd_azf3328 *chip, int reg, int do_mute){ unsigned long portbase = chip->mixer_port + reg + 1; unsigned char oldval; /* the mute bit is on the *second* (i.e. right) register of a * left/right channel setting */ oldval = inb(portbase); if (do_mute) oldval |= 0x80; else oldval &= ~0x80; outb(oldval, portbase);}static voidsnd_azf3328_mixer_write_volume_gradually(const struct snd_azf3328 *chip, int reg, unsigned char dst_vol_left, unsigned char dst_vol_right, int chan_sel, int delay){ unsigned long portbase = chip->mixer_port + reg; unsigned char curr_vol_left = 0, curr_vol_right = 0; int left_done = 0, right_done = 0; snd_azf3328_dbgcallenter(); if (chan_sel & SET_CHAN_LEFT) curr_vol_left = inb(portbase + 1); else left_done = 1; if (chan_sel & SET_CHAN_RIGHT) curr_vol_right = inb(portbase + 0); else right_done = 1; /* take care of muting flag (0x80) contained in left channel */ if (curr_vol_left & 0x80) dst_vol_left |= 0x80; else dst_vol_left &= ~0x80; do { if (!left_done) { if (curr_vol_left > dst_vol_left) curr_vol_left--; else if (curr_vol_left < dst_vol_left) curr_vol_left++; else left_done = 1; outb(curr_vol_left, portbase + 1); } if (!right_done) { if (curr_vol_right > dst_vol_right) curr_vol_right--; else if (curr_vol_right < dst_vol_right) curr_vol_right++; else right_done = 1; /* during volume change, the right channel is crackling * somewhat more than the left channel, unfortunately. * This seems to be a hardware issue. */ outb(curr_vol_right, portbase + 0); } if (delay) mdelay(delay); } while ((!left_done) || (!right_done)); snd_azf3328_dbgcallleave();}/* * general mixer element */struct azf3328_mixer_reg { unsigned int reg; unsigned int lchan_shift, rchan_shift; unsigned int mask; unsigned int invert: 1; unsigned int stereo: 1; unsigned int enum_c: 4;};#define COMPOSE_MIXER_REG(reg,lchan_shift,rchan_shift,mask,invert,stereo,enum_c) \ ((reg) | (lchan_shift << 8) | (rchan_shift << 12) | \ (mask << 16) | \ (invert << 24) | \ (stereo << 25) | \ (enum_c << 26))static void snd_azf3328_mixer_reg_decode(struct azf3328_mixer_reg *r, unsigned long val){
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -