summaryrefslogtreecommitdiffstats
path: root/sound/pci/azt3328.c
diff options
context:
space:
mode:
authorAndreas Mohr <andi@lisas.de>2008-05-16 12:18:29 +0200
committerJaroslav Kysela <perex@perex.cz>2008-05-19 13:19:19 +0200
commit02330fbaaded5b603cba112e4bbf62cdadec159a (patch)
tree90895cb270106d00bbc30a3f15afe91d07795ee3 /sound/pci/azt3328.c
parentf99a633a151686a599413bef758253dfd04887d1 (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.c1192
1 files changed, 796 insertions, 396 deletions
diff --git a/sound/pci/azt3328.c b/sound/pci/azt3328.c
index 5f63af6b88a2..b832333c3023 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(&reg, 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(&reg, 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(&reg, 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);
#else
bufptr = substream->runtime-&g