diff options
author | Andreas Mohr <andi@lisas.de> | 2008-05-16 12:18:29 +0200 |
---|---|---|
committer | Jaroslav Kysela <perex@perex.cz> | 2008-05-19 13:19:19 +0200 |
commit | 02330fbaaded5b603cba112e4bbf62cdadec159a (patch) | |
tree | 90895cb270106d00bbc30a3f15afe91d07795ee3 /sound/pci/azt3328.c | |
parent | f99a633a151686a599413bef758253dfd04887d1 (diff) |
[ALSA] PCI168 snd-azt3328 Linux driver: another huge update
- figured out 'Digital(ly) Enhanced Game Port' functionality,
implemented support for it (eliminating gameport polling overhead)
- removed optional joystick activation, gameport now enabled unconditionally,
since we now support it via the PCI I/O space, not via conflict-prone
legacy I/O (which I was thus able to DISABLE now)!
- fix playback bug (a muted wave output would get unmuted upon start of
playback, of course this is not what we want, thus remember mute state)
- implement partial power management: when idle, lower clock rate and disable
codec (reduced noise!), and disable gameport circuit when unused
- instantiate OPL3 timer, too
- much better implementation of snd_azf3328_mixer_write_volume_gradually()
- slightly optimized interrupt handling
- lots of cleanup
This time, I also found a way to verify proper OPL3 operation
via MIDI file playback (emulation via synth hardware).
Signed-off-by: Andreas Mohr <andi@lisas.de>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
Signed-off-by: Jaroslav Kysela <perex@perex.cz>
Diffstat (limited to 'sound/pci/azt3328.c')
-rw-r--r-- | sound/pci/azt3328.c | 1192 |
1 files changed, 796 insertions, 396 deletions
diff --git a/sound/pci/azt3328.c b/sound/pci/azt3328.c index 5f63af6b88a..b832333c302 100644 --- a/sound/pci/azt3328.c +++ b/sound/pci/azt3328.c @@ -1,6 +1,6 @@ /* * azt3328.c - driver for Aztech AZF3328 based soundcards (e.g. PCI168). - * Copyright (C) 2002, 2005, 2006, 2007 by Andreas Mohr <andi AT lisas.de> + * Copyright (C) 2002, 2005 - 2008 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), @@ -35,9 +35,20 @@ * (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) * + * It is quite likely that the AZF3328 chip is the PCI cousin of the + * AZF3318 ("azt1020 pnp", "MM Pro 16") ISA chip, given very similar specs. + * * The AZF3328 chip (note: AZF3328, *not* AZT3328, that's just the driver name - * for compatibility reasons) has the following features: + * for compatibility reasons) from Azfin (joint-venture of Aztech and Fincitec, + * Fincitec acquired by National Semiconductor in 2002, together with the + * Fincitec-related company ARSmikro) has the following features: * + * - compatibility & compliance: + * - Microsoft PC 97 ("PC 97 Hardware Design Guide", + * http://www.microsoft.com/whdc/archive/pcguides.mspx) + * - Microsoft PC 98 Baseline Audio + * - MPU401 UART + * - Sound Blaster Emulation (DOS Box) * - builtin AC97 conformant codec (SNR over 80dB) * Note that "conformant" != "compliant"!! this chip's mixer register layout * *differs* from the standard AC97 layout: @@ -48,21 +59,28 @@ * 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 + * - builtin genuine OPL3 - verified to work fine, 20080506 * - full duplex 16bit playback/record at independent sampling rate - * - MPU401 (+ legacy address support) FIXME: how to enable legacy addr?? + * - MPU401 (+ legacy address support, claimed by one official spec sheet) + * 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) + * features supported). - See common term "Digital Enhanced Game Port"... + * (probably DirectInput 3.0 spec - confirm) + * - builtin 3D enhancement (said to be YAMAHA Ymersion) * - built-in General DirectX timer having a 20 bits counter * with 1us resolution (see below!) - * - I2S serial port for external DAC + * - I2S serial output 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?) + * At least the Trident 4D Wave DX has one bit somewhere + * to enable writes to PCI subsystem VID registers, that should be it. + * This might easily be in extended PCI reg space, since PCI168 also has + * some custom data starting at 0x80. What kind of config settings + * are located in our extended PCI space anyway?? * - 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, @@ -74,6 +92,24 @@ * - "timidity -iAv -B2,8 -Os -EFreverb=0" * - "pmidi -p 128:0 jazz.mid" * + * OPL3 hardware playback testing, try something like: + * cat /proc/asound/hwdep + * and + * aconnect -o + * Then use + * sbiload -Dhw:x,y --opl3 /usr/share/sounds/opl3/std.o3 ......./drums.o3 + * where x,y is the xx-yy number as given in hwdep. + * Then try + * pmidi -p a:b jazz.mid + * where a:b is the client number plus 0 usually, as given by aconnect above. + * Oh, and make sure to unmute the FM mixer control (doh!) + * NOTE: power use during OPL3 playback is _VERY_ high (70W --> 90W!) + * despite no CPU activity, possibly due to hindering ACPI idling somehow. + * Shouldn't be a problem of the AZF3328 chip itself, I'd hope. + * Higher PCM / FM mixer levels seem to conflict (causes crackling), + * at least sometimes. Maybe even use with hardware sequencer timer above :) + * adplay/adplug-utils might soon offer hardware-based OPL3 playback, too. + * * 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. @@ -87,6 +123,8 @@ * better than a VIA, yet ironically I still get crackling, like many other * people with the same chipset. * Possible remedies: + * - use speaker (amplifier) output instead of headphone output + * (in case crackling is due to overloaded output clipping) * - 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 @@ -94,18 +132,23 @@ * - 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 + * - full-duplex might *still* be problematic, however a recent test was fine * - (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!! - * + * - locking is not entirely clean, especially the audio stream activity + * ints --> may be racy + * - an _unconnected_ secondary joystick at the gameport will be reported + * to be "active" (floating values, not precisely -1) due to the way we need + * to read the Digital Enhanced Game Port. Not sure whether it is fixable. + * * 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 + * - add more power micro-management (disable various units of the card + * as long as they're unused). However this requires more 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... @@ -113,6 +156,7 @@ * - 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) + * - use MMIO (memory-mapped I/O)? Slightly faster access, e.g. for gameport. */ #include <asm/io.h> @@ -138,7 +182,7 @@ MODULE_LICENSE("GPL"); MODULE_SUPPORTED_DEVICE("{{Aztech,AZF3328}}"); #if defined(CONFIG_GAMEPORT) || (defined(MODULE) && defined(CONFIG_GAMEPORT_MODULE)) -#define SUPPORT_JOYSTICK 1 +#define SUPPORT_GAMEPORT 1 #endif #define DEBUG_MISC 0 @@ -147,13 +191,14 @@ MODULE_SUPPORTED_DEVICE("{{Aztech,AZF3328}}"); #define DEBUG_PLAY_REC 0 #define DEBUG_IO 0 #define DEBUG_TIMER 0 +#define DEBUG_GAME 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 +#endif #if DEBUG_CALLS #define snd_azf3328_dbgcalls(format, args...) printk(format, ##args) @@ -163,25 +208,31 @@ MODULE_SUPPORTED_DEVICE("{{Aztech,AZF3328}}"); #define snd_azf3328_dbgcalls(format, args...) #define snd_azf3328_dbgcallenter() #define snd_azf3328_dbgcallleave() -#endif +#endif #if DEBUG_MIXER #define snd_azf3328_dbgmixer(format, args...) printk(format, ##args) #else #define snd_azf3328_dbgmixer(format, args...) -#endif +#endif #if DEBUG_PLAY_REC #define snd_azf3328_dbgplay(format, args...) printk(KERN_ERR format, ##args) #else #define snd_azf3328_dbgplay(format, args...) -#endif +#endif #if DEBUG_MISC #define snd_azf3328_dbgtimer(format, args...) printk(KERN_ERR format, ##args) #else #define snd_azf3328_dbgtimer(format, args...) -#endif +#endif + +#if DEBUG_GAME +#define snd_azf3328_dbggame(format, args...) printk(KERN_ERR format, ##args) +#else +#define snd_azf3328_dbggame(format, args...) +#endif static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */ module_param_array(index, int, NULL, 0444); @@ -195,39 +246,44 @@ 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_JOYSTICK -static int joystick[SNDRV_CARDS]; -module_param_array(joystick, bool, NULL, 0444); -MODULE_PARM_DESC(joystick, "Enable joystick for AZF3328 soundcard."); -#endif - static 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_audio_stream { + struct snd_pcm_substream *substream; + int enabled; + int running; + unsigned long portbase; +}; + +enum snd_azf3328_stream_index { + AZF_PLAYBACK = 0, + AZF_CAPTURE = 1, +}; + 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; + + unsigned long codec_io; /* usually 0xb000, size 128 */ + unsigned long game_io; /* usually 0xb400, size 8 */ + unsigned long mpu_io; /* usually 0xb800, size 4 */ + unsigned long opl3_io; /* usually 0xbc00, size 8 */ + unsigned long mixer_io; /* usually 0xc000, size 64 */ 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_azf3328_audio_stream audio_stream[2]; struct snd_card *card; struct snd_rawmidi *rmidi; -#ifdef SUPPORT_JOYSTICK +#ifdef SUPPORT_GAMEPORT struct gameport *gameport; + int axes[4]; #endif struct pci_dev *pci; @@ -236,10 +292,10 @@ struct snd_azf3328 { #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_codec[AZF_IO_SIZE_CODEC_PM / 2]; + u16 saved_regs_game [AZF_IO_SIZE_GAME_PM / 2]; + u16 saved_regs_mpu [AZF_IO_SIZE_MPU_PM / 2]; + u16 saved_regs_opl3 [AZF_IO_SIZE_OPL3_PM / 2]; u16 saved_regs_mixer[AZF_IO_SIZE_MIXER_PM / 2]; #endif }; @@ -252,126 +308,181 @@ static const struct pci_device_id snd_azf3328_ids[] = { MODULE_DEVICE_TABLE(pci, snd_azf3328_ids); + +static int +snd_azf3328_io_reg_setb(unsigned reg, u8 mask, int do_set) +{ + u8 prev = inb(reg), new; + + new = (do_set) ? (prev|mask) : (prev & ~mask); + /* we need to always write the new value no matter whether it differs + * or not, since some register bits don't indicate their setting */ + outb(new, reg); + if (new != prev) + return 1; + + return 0; +} + +static int +snd_azf3328_io_reg_setw(unsigned reg, u16 mask, int do_set) +{ + u16 prev = inw(reg), new; + + new = (do_set) ? (prev|mask) : (prev & ~mask); + /* we need to always write the new value no matter whether it differs + * or not, since some register bits don't indicate their setting */ + outw(new, reg); + if (new != prev) + return 1; + + return 0; +} + static inline void -snd_azf3328_codec_outb(const struct snd_azf3328 *chip, int reg, u8 value) +snd_azf3328_codec_outb(const struct snd_azf3328 *chip, unsigned reg, u8 value) { - outb(value, chip->codec_port + reg); + outb(value, chip->codec_io + reg); } static inline u8 -snd_azf3328_codec_inb(const struct snd_azf3328 *chip, int reg) +snd_azf3328_codec_inb(const struct snd_azf3328 *chip, unsigned reg) { - return inb(chip->codec_port + reg); + return inb(chip->codec_io + reg); } static inline void -snd_azf3328_codec_outw(const struct snd_azf3328 *chip, int reg, u16 value) +snd_azf3328_codec_outw(const struct snd_azf3328 *chip, unsigned reg, u16 value) { - outw(value, chip->codec_port + reg); + outw(value, chip->codec_io + reg); } static inline u16 -snd_azf3328_codec_inw(const struct snd_azf3328 *chip, int reg) +snd_azf3328_codec_inw(const struct snd_azf3328 *chip, unsigned reg) +{ + return inw(chip->codec_io + reg); +} + +static inline void +snd_azf3328_codec_outl(const struct snd_azf3328 *chip, unsigned reg, u32 value) +{ + outl(value, chip->codec_io + reg); +} + +static inline u32 +snd_azf3328_codec_inl(const struct snd_azf3328 *chip, unsigned reg) { - return inw(chip->codec_port + reg); + return inl(chip->codec_io + reg); } static inline void -snd_azf3328_codec_outl(const struct snd_azf3328 *chip, int reg, u32 value) +snd_azf3328_game_outb(const struct snd_azf3328 *chip, unsigned reg, u8 value) { - outl(value, chip->codec_port + reg); + outb(value, chip->game_io + reg); } static inline void -snd_azf3328_io2_outb(const struct snd_azf3328 *chip, int reg, u8 value) +snd_azf3328_game_outw(const struct snd_azf3328 *chip, unsigned reg, u16 value) { - outb(value, chip->io2_port + reg); + outw(value, chip->game_io + reg); } static inline u8 -snd_azf3328_io2_inb(const struct snd_azf3328 *chip, int reg) +snd_azf3328_game_inb(const struct snd_azf3328 *chip, unsigned reg) +{ + return inb(chip->game_io + reg); +} + +static inline u16 +snd_azf3328_game_inw(const struct snd_azf3328 *chip, unsigned reg) { - return inb(chip->io2_port + reg); + return inw(chip->game_io + reg); } static inline void -snd_azf3328_mixer_outw(const struct snd_azf3328 *chip, int reg, u16 value) +snd_azf3328_mixer_outw(const struct snd_azf3328 *chip, unsigned reg, u16 value) { - outw(value, chip->mixer_port + reg); + outw(value, chip->mixer_io + reg); } static inline u16 -snd_azf3328_mixer_inw(const struct snd_azf3328 *chip, int reg) +snd_azf3328_mixer_inw(const struct snd_azf3328 *chip, unsigned reg) { - return inw(chip->mixer_port + reg); + return inw(chip->mixer_io + reg); } -static void -snd_azf3328_mixer_set_mute(const struct snd_azf3328 *chip, int reg, int do_mute) +#define AZF_MUTE_BIT 0x80 + +static int +snd_azf3328_mixer_set_mute(const struct snd_azf3328 *chip, + unsigned reg, int do_mute +) { - unsigned long portbase = chip->mixer_port + reg + 1; - unsigned char oldval; + unsigned long portbase = chip->mixer_io + reg + 1; + int updated; /* 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); + updated = snd_azf3328_io_reg_setb(portbase, AZF_MUTE_BIT, do_mute); + + /* indicate whether it was muted before */ + return (do_mute) ? !updated : updated; } static void -snd_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) +snd_azf3328_mixer_write_volume_gradually(const struct snd_azf3328 *chip, + unsigned reg, + unsigned char dst_vol_left, + unsigned char dst_vol_right, + int chan_sel, int delay +) { - unsigned long portbase = chip->mixer_port + reg; + unsigned long portbase = chip->mixer_io + reg; unsigned char curr_vol_left = 0, curr_vol_right = 0; - int left_done = 0, right_done = 0; - + int left_change = 0, right_change = 0; + snd_azf3328_dbgcallenter(); - if (chan_sel & SET_CHAN_LEFT) + + if (chan_sel & SET_CHAN_LEFT) { curr_vol_left = inb(portbase + 1); - else - left_done = 1; - if (chan_sel & SET_CHAN_RIGHT) + + /* take care of muting flag contained in left channel */ + if (curr_vol_left & AZF_MUTE_BIT) + dst_vol_left |= AZF_MUTE_BIT; + else + dst_vol_left &= ~AZF_MUTE_BIT; + + left_change = (curr_vol_left > dst_vol_left) ? -1 : 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; + + right_change = (curr_vol_right > dst_vol_right) ? -1 : 1; + } 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 (left_change) { + if (curr_vol_left != dst_vol_left) { + curr_vol_left += left_change; + outb(curr_vol_left, portbase + 1); + } else + left_change = 0; } - 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; + if (right_change) { + if (curr_vol_right != dst_vol_right) { + curr_vol_right += right_change; + /* 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); + outb(curr_vol_right, portbase + 0); + } else + right_change = 0; } if (delay) mdelay(delay); - } while ((!left_done) || (!right_done)); + } while ((left_change) || (right_change)); snd_azf3328_dbgcallleave(); } @@ -379,7 +490,7 @@ snd_azf3328_mixer_write_volume_gradually(const struct snd_azf3328 *chip, int reg * general mixer element */ struct azf3328_mixer_reg { - unsigned int reg; + unsigned reg; unsigned int lchan_shift, rchan_shift; unsigned int mask; unsigned int invert: 1; @@ -544,13 +655,14 @@ snd_azf3328_info_mixer_enum(struct snd_kcontrol *kcontrol, "Mix", "Mic" }; static const char * const texts3[] = { - "Mic", "CD", "Video", "Aux", + "Mic", "CD", "Video", "Aux", "Line", "Mix", "Mix Mono", "Phone" }; static const char * const texts4[] = { "pre 3D", "post 3D" }; struct azf3328_mixer_reg reg; + const char *p = NULL; snd_azf3328_mixer_reg_decode(®, kcontrol->private_value); uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; @@ -561,18 +673,20 @@ snd_azf3328_info_mixer_enum(struct snd_kcontrol *kcontrol, if (reg.reg == IDX_MIXER_ADVCTL2) { switch(reg.lchan_shift) { case 8: /* modem out sel */ - strcpy(uinfo->value.enumerated.name, texts1[uinfo->value.enumerated.item]); + p = texts1[uinfo->value.enumerated.item]; break; case 9: /* mono sel source */ - strcpy(uinfo->value.enumerated.name, texts2[uinfo->value.enumerated.item]); + p = texts2[uinfo->value.enumerated.item]; break; case 15: /* PCM Out Path */ - strcpy(uinfo->value.enumerated.name, texts4[uinfo->value.enumerated.item]); + p = texts4[uinfo->value.enumerated.item]; break; } } else - strcpy(uinfo->value.enumerated.name, texts3[uinfo->value.enumerated.item] -); + if (reg.reg == IDX_MIXER_REC_SELECT) + p = texts3[uinfo->value.enumerated.item]; + + strcpy(uinfo->value.enumerated.name, p); return 0; } @@ -583,7 +697,7 @@ snd_azf3328_get_mixer_enum(struct snd_kcontrol *kcontrol, struct snd_azf3328 *chip = snd_kcontrol_chip(kcontrol); struct azf3328_mixer_reg reg; unsigned short val; - + snd_azf3328_mixer_reg_decode(®, kcontrol->private_value); val = snd_azf3328_mixer_inw(chip, reg.reg); if (reg.reg == IDX_MIXER_REC_SELECT) { @@ -605,7 +719,7 @@ snd_azf3328_put_mixer_enum(struct snd_kcontrol *kcontrol, struct snd_azf3328 *chip = snd_kcontrol_chip(kcontrol); struct azf3328_mixer_reg reg; unsigned int oreg, nreg, val; - + snd_azf3328_mixer_reg_decode(®, kcontrol->private_value); oreg = snd_azf3328_mixer_inw(chip, reg.reg); val = oreg; @@ -717,15 +831,16 @@ snd_azf3328_mixer_new(struct snd_azf3328 *chip) snd_azf3328_mixer_outw(chip, IDX_MIXER_RESET, 0x0000); /* mute and zero volume channels */ - for (idx = 0; idx < ARRAY_SIZE(snd_azf3328_init_values); idx++) { + for (idx = 0; idx < ARRAY_SIZE(snd_azf3328_init_values); ++idx) { snd_azf3328_mixer_outw(chip, snd_azf3328_init_values[idx][0], snd_azf3328_init_values[idx][1]); } - + /* add mixer controls */ sw = snd_azf3328_mixer_controls; - for (idx = 0; idx < ARRAY_SIZE(snd_azf3328_mixer_controls); idx++, sw++) { + for (idx = 0; idx < ARRAY_SIZE(snd_azf3328_mixer_controls); + ++idx, ++sw) { if ((err = snd_ctl_add(chip->card, snd_ctl_new1(sw, chip))) < 0) return err; } @@ -757,8 +872,8 @@ snd_azf3328_hw_free(struct snd_pcm_substream *substream) } static void -snd_azf3328_setfmt(struct snd_azf3328 *chip, - unsigned int reg, +snd_azf3328_codec_setfmt(struct snd_azf3328 *chip, + unsigned reg, unsigned int bitrate, unsigned int format_width, unsigned int channels @@ -769,24 +884,25 @@ snd_azf3328_setfmt(struct snd_azf3328 *chip, snd_azf3328_dbgcallenter(); switch (bitrate) { - case 4000: val |= SOUNDFORMAT_FREQ_SUSPECTED_4000; break; - case 4800: val |= SOUNDFORMAT_FREQ_SUSPECTED_4800; break; - case 5512: val |= SOUNDFORMAT_FREQ_5510; break; /* the AZF3328 names it "5510" for some strange reason */ - case 6620: val |= SOUNDFORMAT_FREQ_6620; break; - case 8000: val |= SOUNDFORMAT_FREQ_8000; break; - case 9600: val |= SOUNDFORMAT_FREQ_9600; break; - case 11025: val |= SOUNDFORMAT_FREQ_11025; break; - case 13240: val |= SOUNDFORMAT_FREQ_SUSPECTED_13240; break; - case 16000: val |= SOUNDFORMAT_FREQ_16000; break; - case 22050: val |= SOUNDFORMAT_FREQ_22050; break; - case 32000: val |= SOUNDFORMAT_FREQ_32000; break; - case 44100: val |= SOUNDFORMAT_FREQ_44100; break; - case 48000: val |= SOUNDFORMAT_FREQ_48000; break; - case 66200: val |= SOUNDFORMAT_FREQ_SUSPECTED_66200; break; + case AZF_FREQ_4000: val |= SOUNDFORMAT_FREQ_SUSPECTED_4000; break; + case AZF_FREQ_4800: val |= SOUNDFORMAT_FREQ_SUSPECTED_4800; break; + case AZF_FREQ_5512: + /* the AZF3328 names it "5510" for some strange reason */ + val |= SOUNDFORMAT_FREQ_5510; break; + case AZF_FREQ_6620: val |= SOUNDFORMAT_FREQ_6620; break; + case AZF_FREQ_8000: val |= SOUNDFORMAT_FREQ_8000; break; + case AZF_FREQ_9600: val |= SOUNDFORMAT_FREQ_9600; break; + case AZF_FREQ_11025: val |= SOUNDFORMAT_FREQ_11025; break; + case AZF_FREQ_13240: val |= SOUNDFORMAT_FREQ_SUSPECTED_13240; break; + case AZF_FREQ_16000: val |= SOUNDFORMAT_FREQ_16000; break; + case AZF_FREQ_22050: val |= SOUNDFORMAT_FREQ_22050; break; + case AZF_FREQ_32000: val |= SOUNDFORMAT_FREQ_32000; break; default: snd_printk(KERN_WARNING "unknown bitrate %d, assuming 44.1kHz!\n", bitrate); - val |= SOUNDFORMAT_FREQ_44100; - break; + /* fall-through */ + case AZF_FREQ_44100: val |= SOUNDFORMAT_FREQ_44100; break; + case AZF_FREQ_48000: val |= SOUNDFORMAT_FREQ_48000; break; + case AZF_FREQ_66200: val |= SOUNDFORMAT_FREQ_SUSPECTED_66200; break; } /* val = 0xff07; 3m27.993s (65301Hz; -> 64000Hz???) hmm, 66120, 65967, 66123 */ /* val = 0xff09; 17m15.098s (13123,478Hz; -> 12000Hz???) hmm, 13237.2Hz? */ @@ -805,10 +921,10 @@ snd_azf3328_setfmt(struct snd_azf3328 *chip, val |= SOUNDFORMAT_FLAG_16BIT; spin_lock_irqsave(&chip->reg_lock, flags); - + /* set bitrate/format */ snd_azf3328_codec_outw(chip, reg, val); - + /* changing the bitrate/format settings switches off the * audio output with an annoying click in case of 8/16bit format change * (maybe shutting down DAC/ADC?), thus immediately @@ -830,31 +946,81 @@ snd_azf3328_setfmt(struct snd_azf3328 *chip, snd_azf3328_dbgcallleave(); } +static inline void +snd_azf3328_codec_setfmt_lowpower(struct snd_azf3328 *chip, + unsigned reg +) +{ + /* choose lowest frequency for low power consumption. + * While this will cause louder noise due to rather coarse frequency, + * it should never matter since output should always + * get disabled properly when idle anyway. */ + snd_azf3328_codec_setfmt(chip, reg, AZF_FREQ_4000, 8, 1); +} + +static inline void +snd_azf3328_codec_enable(struct snd_azf3328 *chip, int enable) +{ + /* no idea what exactly is being done here, but I strongly assume it's + * PM related */ + snd_azf3328_io_reg_setw( + chip->codec_io+IDX_IO_6AH, + IO_6A_PAUSE_PLAYBACK_BIT8, + !enable + ); +} + +static void +snd_azf3328_codec_activity(struct snd_azf3328 *chip, + enum snd_azf3328_stream_index stream_type, + int enable +) +{ + int need_change = (chip->audio_stream[stream_type].running != enable); + + snd_azf3328_dbgplay( + "codec_activity: type %d, enable %d, need_change %d\n", + stream_type, enable, need_change + ); + if (need_change) { + enum snd_azf3328_stream_index other = + (stream_type == AZF_PLAYBACK) ? + AZF_CAPTURE : AZF_PLAYBACK; + /* small check to prevent shutting down the other party + * in case it's active */ + if ((enable) || !(chip->audio_stream[other].running)) + snd_azf3328_codec_enable(chip, enable); + + /* ...and adjust clock, too + * (reduce noise and power consumption) */ + if (!enable) + snd_azf3328_codec_setfmt_lowpower( + chip, + chip->audio_stream[stream_type].portbase + + IDX_IO_PLAY_SOUNDFORMAT + ); + } + chip->audio_stream[stream_type].running = enable; +} + static void snd_azf3328_setdmaa(struct snd_azf3328 *chip, long unsigned int addr, unsigned int count, unsigned int size, - int do_recording) + enum snd_azf3328_stream_index stream_type +) { - unsigned long flags, portbase; - unsigned int is_running; - snd_azf3328_dbgcallenter(); - if (do_recording) { - /* access capture registers, i.e. skip playback reg section */ - portbase = chip->codec_port + 0x20; - is_running = chip->is_recording; - } else { - /* access the playback register section */ - portbase = chip->codec_port + 0x00; - is_running = chip->is_playing; - } + if (!chip->audio_stream[stream_type].running) { + /* AZF3328 uses a two buffer pointer DMA playback approach */ + + unsigned long flags, portbase, addr_area2; + + /* width 32bit (prevent overflow): */ + unsigned long count_areas, count_tmp; - /* AZF3328 uses a two buffer pointer DMA playback approach */ - if (!is_running) { - unsigned long addr_area2; - unsigned long count_areas, count_tmp; /* width 32bit -- overflow!! */ + portbase = chip->audio_stream[stream_type].portbase; count_areas = size/2; addr_area2 = addr+count_areas; count_areas--; /* max. index */ @@ -884,11 +1050,11 @@ snd_azf3328_playback_prepare(struct snd_pcm_substream *substream) snd_azf3328_dbgcallenter(); #if 0 - snd_azf3328_setfmt(chip, IDX_IO_PLAY_SOUNDFORMAT, + snd_azf3328_codec_setfmt(chip, IDX_IO_PLAY_SOUNDFORMAT, runtime->rate, snd_pcm_format_width(runtime->format), runtime->channels); - snd_azf3328_setdmaa(chip, runtime->dma_addr, count, size, 0); + snd_azf3328_setdmaa(chip, runtime->dma_addr, count, size, AZF_PLAYBACK); #endif snd_azf3328_dbgcallleave(); return 0; @@ -906,11 +1072,11 @@ snd_azf3328_capture_prepare(struct snd_pcm_substream *substream) snd_azf3328_dbgcallenter(); #if 0 - snd_azf3328_setfmt(chip, IDX_IO_REC_SOUNDFORMAT, + snd_azf3328_codec_setfmt(chip, IDX_IO_REC_SOUNDFORMAT, runtime->rate, snd_pcm_format_width(runtime->format), runtime->channels); - snd_azf3328_setdmaa(chip, runtime->dma_addr, count, size, 1); + snd_azf3328_setdmaa(chip, runtime->dma_addr, count, size, AZF_CAPTURE); #endif snd_azf3328_dbgcallleave(); return 0; @@ -923,6 +1089,7 @@ snd_azf3328_playback_trigger(struct snd_pcm_substream *substream, int cmd) struct snd_pcm_runtime *runtime = substream->runtime; int result = 0; unsigned int status1; + int previously_muted; snd_azf3328_dbgcalls("snd_azf3328_playback_trigger cmd %d\n", cmd); @@ -930,20 +1097,23 @@ snd_azf3328_playback_trigger(struct snd_pcm_substream *substream, int cmd) case SNDRV_PCM_TRIGGER_START: snd_azf3328_dbgplay("START PLAYBACK\n"); - /* mute WaveOut */ - snd_azf3328_mixer_set_mute(chip, IDX_MIXER_WAVEOUT, 1); + /* mute WaveOut (avoid clicking during setup) */ + previously_muted = + snd_azf3328_mixer_set_mute(chip, IDX_MIXER_WAVEOUT, 1); - snd_azf3328_setfmt(chip, IDX_IO_PLAY_SOUNDFORMAT, + snd_azf3328_codec_setfmt(chip, IDX_IO_PLAY_SOUNDFORMAT, runtime->rate, snd_pcm_format_width(runtime->format), runtime->channels); spin_lock(&chip->reg_lock); - /* stop playback */ + /* first, remember current value: */ status1 = snd_azf3328_codec_inw(chip, IDX_IO_PLAY_FLAGS); + + /* stop playback */ status1 &= ~DMA_RESUME; snd_azf3328_codec_outw(chip, IDX_IO_PLAY_FLAGS, status1); - + /* FIXME: clear interrupts or what??? */ snd_azf3328_codec_outw(chip, IDX_IO_PLAY_IRQTYPE, 0xffff); spin_unlock(&chip->reg_lock); @@ -951,7 +1121,7 @@ snd_azf3328_playback_trigger(struct snd_pcm_substream *substream, int cmd) snd_azf3328_setdmaa(chip, runtime->dma_addr, snd_pcm_lib_period_bytes(substream), snd_pcm_lib_buffer_bytes(substream), - 0); + AZF_PLAYBACK); spin_lock(&chip->reg_lock); #ifdef WIN9X @@ -978,30 +1148,35 @@ snd_azf3328_playback_trigger(struct snd_pcm_substream *substream, int cmd) DMA_SOMETHING_ELSE); #endif spin_unlock(&chip->reg_lock); + snd_azf3328_codec_activity(chip, AZF_PLAYBACK, 1); /* now unmute WaveOut */ - snd_azf3328_mixer_set_mute(chip, IDX_MIXER_WAVEOUT, 0); + if (!previously_muted) + snd_azf3328_mixer_set_mute(chip, IDX_MIXER_WAVEOUT, 0); - chip->is_playing = 1; snd_azf3328_dbgplay("STARTED PLAYBACK\n"); break; case SNDRV_PCM_TRIGGER_RESUME: snd_azf3328_dbgplay("RESUME PLAYBACK\n"); /* resume playback if we were active */ - if (chip->is_playing) + spin_lock(&chip->reg_lock); + if (chip->audio_stream[AZF_PLAYBACK].running) snd_azf3328_codec_outw(chip, IDX_IO_PLAY_FLAGS, snd_azf3328_codec_inw(chip, IDX_IO_PLAY_FLAGS) | DMA_RESUME); + spin_unlock(&chip->reg_lock); break; case SNDRV_PCM_TRIGGER_STOP: snd_azf3328_dbgplay("STOP PLAYBACK\n"); - /* mute WaveOut */ - snd_azf3328_mixer_set_mute(chip, IDX_MIXER_WAVEOUT, 1); + /* mute WaveOut (avoid clicking during setup) */ + previously_muted = + snd_azf3328_mixer_set_mute(chip, IDX_MIXER_WAVEOUT, 1); spin_lock(&chip->reg_lock); - /* stop playback */ + /* first, remember current value: */ status1 = snd_azf3328_codec_inw(chip, IDX_IO_PLAY_FLAGS); + /* stop playback */ status1 &= ~DMA_RESUME; snd_azf3328_codec_outw(chip, IDX_IO_PLAY_FLAGS, status1); @@ -1013,10 +1188,12 @@ snd_azf3328_playback_trigger(struct snd_pcm_substream *substream, int cmd) status1 &= ~DMA_PLAY_SOMETHING1; snd_azf3328_codec_outw(chip, IDX_IO_PLAY_FLAGS, status1); spin_unlock(&chip->reg_lock); - + snd_azf3328_codec_activity(chip, AZF_PLAYBACK, 0); + /* now unmute WaveOut */ - snd_azf3328_mixer_set_mute(chip, IDX_MIXER_WAVEOUT, 0); - chip->is_playing = 0; + if (!previously_muted) + snd_azf3328_mixer_set_mute(chip, IDX_MIXER_WAVEOUT, 0); + snd_azf3328_dbgplay("STOPPED PLAYBACK\n"); break; case SNDRV_PCM_TRIGGER_SUSPEND: @@ -1035,7 +1212,7 @@ snd_azf3328_playback_trigger(struct snd_pcm_substream *substream, int cmd) printk(KERN_ERR "FIXME: unknown trigger mode!\n"); return -EINVAL; } - + snd_azf3328_dbgcallleave(); return result; } @@ -1057,17 +1234,19 @@ snd_azf3328_capture_trigger(struct snd_pcm_substream *substream, int cmd) snd_azf3328_dbgplay("START CAPTURE\n"); - snd_azf3328_setfmt(chip, IDX_IO_REC_SOUNDFORMAT, + snd_azf3328_codec_setfmt(chip, IDX_IO_REC_SOUNDFORMAT, runtime->rate, snd_pcm_format_width(runtime->format), runtime->channels); spin_lock(&chip->reg_lock); - /* stop recording */ + /* first, remember current value: */ status1 = snd_azf3328_codec_inw(chip, IDX_IO_REC_FLAGS); + + /* stop recording */ status1 &= ~DMA_RESUME; snd_azf3328_codec_outw(chip, IDX_IO_REC_FLAGS, status1); - + /* FIXME: clear interrupts or what??? */ snd_azf3328_codec_outw(chip, IDX_IO_REC_IRQTYPE, 0xffff); spin_unlock(&chip->reg_lock); @@ -1075,7 +1254,7 @@ snd_azf3328_capture_trigger(struct snd_pcm_substream *substream, int cmd) snd_azf3328_setdmaa(chip, runtime->dma_addr, snd_pcm_lib_period_bytes(substream), snd_pcm_lib_buffer_bytes(substream), - 1); + AZF_CAPTURE); spin_lock(&chip->reg_lock); #ifdef WIN9X @@ -1102,24 +1281,27 @@ snd_azf3328_capture_trigger(struct snd_pcm_substream *substream, int cmd) DMA_SOMETHING_ELSE); #endif spin_unlock(&chip->reg_lock); + snd_azf3328_codec_activity(chip, AZF_CAPTURE, 1); - chip->is_recording = 1; snd_azf3328_dbgplay("STARTED CAPTURE\n"); break; case SNDRV_PCM_TRIGGER_RESUME: snd_azf3328_dbgplay("RESUME CAPTURE\n"); /* resume recording if we were active */ - if (chip->is_recording) + spin_lock(&chip->reg_lock); + if (chip->audio_stream[AZF_CAPTURE].running) snd_azf3328_codec_outw(chip, IDX_IO_REC_FLAGS, snd_azf3328_codec_inw(chip, IDX_IO_REC_FLAGS) | DMA_RESUME); + spin_unlock(&chip->reg_lock); break; case SNDRV_PCM_TRIGGER_STOP: snd_azf3328_dbgplay("STOP CAPTURE\n"); spin_lock(&chip->reg_lock); - /* stop recording */ + /* first, remember current value: */ status1 = snd_azf3328_codec_inw(chip, IDX_IO_REC_FLAGS); + /* stop recording */ status1 &= ~DMA_RESUME; snd_azf3328_codec_outw(chip, IDX_IO_REC_FLAGS, status1); @@ -1129,8 +1311,8 @@ snd_azf3328_capture_trigger(struct snd_pcm_substream *substream, int cmd) status1 &= ~DMA_PLAY_SOMETHING1; snd_azf3328_codec_outw(chip, IDX_IO_REC_FLAGS, status1); spin_unlock(&chip->reg_lock); - - chip->is_recording = 0; + snd_azf3328_codec_activity(chip, AZF_CAPTURE, 0); + snd_azf3328_dbgplay("STOPPED CAPTURE\n"); break; case SNDRV_PCM_TRIGGER_SUSPEND: @@ -1149,7 +1331,7 @@ snd_azf3328_capture_trigger(struct snd_pcm_substream *substream, int cmd) printk(KERN_ERR "FIXME: unknown trigger mode!\n"); return -EINVAL; } - + snd_azf3328_dbgcallleave(); return result; } @@ -1162,11 +1344,11 @@ snd_azf3328_playback_pointer(struct snd_pcm_substream *substream) snd_pcm_uframes_t frmres; #ifdef QUERY_HARDWARE - bufptr = inl(chip->codec_port+IDX_IO_PLAY_DMA_START_1); + bufptr = snd_azf3328_codec_inl(chip, IDX_IO_PLAY_DMA_START_1); |