linux/sound/pci/azt3328.c

// SPDX-License-Identifier: GPL-2.0-or-later
/*  azt3328.c - driver for Aztech AZF3328 based soundcards (e.g. PCI168).
 *  Copyright (C) 2002, 2005 - 2011 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!!
 *
 *  Keywords: Windows XP Vista 168nt4-125.zip 168win95-125.zip PCI 168 download
 *  (XP/Vista do not support this card at all but every Linux distribution
 *   has very good support out of the box;
 *   just to make sure that the right people hit this and get to know that,
 *   despite the high level of Internet ignorance - as usual :-P -
 *   about very good support for this card - on Linux!)
 *
 * 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)
 *
 *  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) 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:
 *    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.
 *    Well, not quite: now ac97 layer is much improved (bus-specific ops!),
 *    thus I was able to implement support - it's actually working quite well.
 *    An interesting item might be Aztech AMR 2800-W, since it's an AC97
 *    modem card which might reveal the Aztech-specific codec ID which
 *    we might want to pretend, too. Dito PCI168's brother, PCI368,
 *    where the advertising datasheet says it's AC97-based and has a
 *    Digital Enhanced Game Port.
 *  - builtin genuine OPL3 - verified to work fine, 20080506
 *  - full duplex 16bit playback/record at independent sampling rate
 *  - MPU401 (+ legacy address support, claimed by one official spec sheet)
 *    FIXME: how to enable legacy addr??
 *  - game port (legacy address support)
 *  - builtin DirectInput support, helps reduce CPU overhead (interrupt-driven
 *    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 output port for external DAC
 *    [FIXME: 3.3V or 5V level? maximum rate is 66.2kHz right?]
 *  - 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 [24C02 SEEPROM chip]
 *    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
 *    [TDA1517P chip]
 *
 *  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"
 *
 *  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.
 *  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:
 *  - use speaker (amplifier) output instead of headphone output
 *    (in case crackling is due to overloaded output clipping)
 *  - plug card into a different PCI slot, preferably 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, 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
 *  - use PCI_VDEVICE
 *  - verify driver status on x86_64
 *  - test multi-card driver operation
 *  - (ab)use 1MHz DirectX timer as kernel clocksource
 *  - test MPU401 MIDI playback etc.
 *  - add more power micro-management (disable various units of the card
 *    as long as they're unused, to improve audio quality and save power).
 *    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...
 *  - 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)
 *  - use MMIO (memory-mapped I/O)? Slightly faster access, e.g. for gameport.
 */

#include <linux/io.h>
#include <linux/init.h>
#include <linux/bug.h> /* WARN_ONCE */
#include <linux/pci.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/gameport.h>
#include <linux/module.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>
/*
 * Config switch, to use ALSA's AC97 layer instead of old custom mixer crap.
 * If the AC97 compatibility parts we needed to implement locally turn out
 * to work nicely, then remove the old implementation eventually.
 */
#define AZF_USE_AC97_LAYER

#ifdef AZF_USE_AC97_LAYER
#include <sound/ac97_codec.h>
#endif
#include "azt3328.h"

MODULE_AUTHOR();
MODULE_DESCRIPTION();
MODULE_LICENSE();

#if IS_REACHABLE(CONFIG_GAMEPORT)
#define SUPPORT_GAMEPORT
#endif

/* === Debug settings ===
  Further diagnostic functionality than the settings below
  does not need to be provided, since one can easily write a POSIX shell script
  to dump the card's I/O ports (those listed in lspci -v -v):
  dump()
  {
    local descr=$1; local addr=$2; local count=$3

    echo "${descr}: ${count} @ ${addr}:"
    dd if=/dev/port skip=`printf %d ${addr}` count=${count} bs=1 \
      2>/dev/null| hexdump -C
  }
  and then use something like
  "dump joy200 0x200 8", "dump mpu388 0x388 4", "dump joy 0xb400 8",
  "dump codec00 0xa800 32", "dump mixer 0xb800 64", "dump synth 0xbc00 8",
  possibly within a "while true; do ... sleep 1; done" loop.
  Tweaking ports could be done using
  VALSTRING="`printf "%02x" $value`"
  printf "\x""$VALSTRING"|dd of=/dev/port seek=`printf %d ${addr}` bs=1 \
    2>/dev/null
*/

static int index[SNDRV_CARDS] =;	/* Index 0-MAX */
module_param_array();
MODULE_PARM_DESC();

static char *id[SNDRV_CARDS] =;	/* ID for this card */
module_param_array();
MODULE_PARM_DESC();

static bool enable[SNDRV_CARDS] =;	/* Enable this card */
module_param_array();
MODULE_PARM_DESC();

static int seqtimer_scaling =;
module_param(seqtimer_scaling, int, 0444);
MODULE_PARM_DESC();

enum snd_azf3328_codec_type {};

struct snd_azf3328_codec_data {};

struct snd_azf3328 {};

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, bool do_set)
{}

static inline void
snd_azf3328_codec_outb(const struct snd_azf3328_codec_data *codec,
		       unsigned reg,
		       u8 value
)
{}

static inline u8
snd_azf3328_codec_inb(const struct snd_azf3328_codec_data *codec, unsigned reg)
{}

static inline void
snd_azf3328_codec_outw(const struct snd_azf3328_codec_data *codec,
		       unsigned reg,
		       u16 value
)
{}

static inline u16
snd_azf3328_codec_inw(const struct snd_azf3328_codec_data *codec, unsigned reg)
{}

static inline void
snd_azf3328_codec_outl_multi(const struct snd_azf3328_codec_data *codec,
			     unsigned reg, const void *buffer, int count
)
{}

static inline u32
snd_azf3328_codec_inl(const struct snd_azf3328_codec_data *codec, unsigned reg)
{}

static inline void
snd_azf3328_ctrl_outb(const struct snd_azf3328 *chip, unsigned reg, u8 value)
{}

static inline u8
snd_azf3328_ctrl_inb(const struct snd_azf3328 *chip, unsigned reg)
{}

static inline u16
snd_azf3328_ctrl_inw(const struct snd_azf3328 *chip, unsigned reg)
{}

static inline void
snd_azf3328_ctrl_outw(const struct snd_azf3328 *chip, unsigned reg, u16 value)
{}

static inline void
snd_azf3328_ctrl_outl(const struct snd_azf3328 *chip, unsigned reg, u32 value)
{}

static inline void
snd_azf3328_game_outb(const struct snd_azf3328 *chip, unsigned reg, u8 value)
{}

static inline void
snd_azf3328_game_outw(const struct snd_azf3328 *chip, unsigned reg, u16 value)
{}

static inline u8
snd_azf3328_game_inb(const struct snd_azf3328 *chip, unsigned reg)
{}

static inline u16
snd_azf3328_game_inw(const struct snd_azf3328 *chip, unsigned reg)
{}

static inline void
snd_azf3328_mixer_outw(const struct snd_azf3328 *chip, unsigned reg, u16 value)
{}

static inline u16
snd_azf3328_mixer_inw(const struct snd_azf3328 *chip, unsigned reg)
{}

#define AZF_MUTE_BIT

static bool
snd_azf3328_mixer_mute_control(const struct snd_azf3328 *chip,
			   unsigned reg, bool do_mute
)
{}

static inline bool
snd_azf3328_mixer_mute_control_master(const struct snd_azf3328 *chip,
			   bool do_mute
)
{}

static inline bool
snd_azf3328_mixer_mute_control_pcm(const struct snd_azf3328 *chip,
			   bool do_mute
)
{}

static inline void
snd_azf3328_mixer_reset(const struct snd_azf3328 *chip)
{}

#ifdef AZF_USE_AC97_LAYER

static inline void
snd_azf3328_mixer_ac97_map_unsupported(const struct snd_azf3328 *chip,
				       unsigned short reg, const char *mode)
{}

/*
 * Need to have _special_ AC97 mixer hardware register index mapper,
 * to compensate for the issue of a rather AC97-incompatible hardware layout.
 */
#define AZF_REG_MASK
#define AZF_AC97_REG_UNSUPPORTED
#define AZF_AC97_REG_REAL_IO_READ
#define AZF_AC97_REG_REAL_IO_WRITE
#define AZF_AC97_REG_REAL_IO_RW
#define AZF_AC97_REG_EMU_IO_READ
#define AZF_AC97_REG_EMU_IO_WRITE
#define AZF_AC97_REG_EMU_IO_RW
static unsigned short
snd_azf3328_mixer_ac97_map_reg_idx(unsigned short reg)
{}

static const unsigned short
azf_emulated_ac97_caps =;

static const unsigned short
azf_emulated_ac97_powerdown =/* pretend everything to be active */
		AC97_PD_ADC_STATUS |
		AC97_PD_DAC_STATUS |
		AC97_PD_MIXER_STATUS |
		AC97_PD_VREF_STATUS;

/*
 * Emulated, _inofficial_ vendor ID
 * (there might be some devices such as the MR 2800-W
 * which could reveal the real Aztech AC97 ID).
 * We choose to use "AZT" prefix, and then use 1 to indicate PCI168
 * (better don't use 0x68 since there's a PCI368 as well).
 */
static const unsigned int
azf_emulated_ac97_vendor_id =;

static unsigned short
snd_azf3328_mixer_ac97_read(struct snd_ac97 *ac97, unsigned short reg_ac97)
{}

static void
snd_azf3328_mixer_ac97_write(struct snd_ac97 *ac97,
		     unsigned short reg_ac97, unsigned short val)
{}

static int
snd_azf3328_mixer_new(struct snd_azf3328 *chip)
{}
#else /* AZF_USE_AC97_LAYER */
static void
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_io + reg;
	unsigned char curr_vol_left = 0, curr_vol_right = 0;
	int left_change = 0, right_change = 0;

	if (chan_sel & SET_CHAN_LEFT) {
		curr_vol_left  = inb(portbase + 1);

		/* 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);

		right_change = (curr_vol_right > dst_vol_right) ? -1 : 1;
	}

	do {
		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_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);
			} else
			    right_change = 0;
		}
		if (delay)
			mdelay(delay);
	} while ((left_change) || (right_change));
}

/*
 * general mixer element
 */
struct azf3328_mixer_reg {
	unsigned 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

static void snd_azf3328_mixer_reg_decode(struct azf3328_mixer_reg *r, unsigned long val)
{
	r->reg = val & 0xff;
	r->lchan_shift = (val >> 8) & 0x0f;
	r->rchan_shift = (val >> 12) & 0x0f;
	r->mask = (val >> 16) & 0xff;
	r->invert = (val >> 24) & 1;
	r->stereo = (val >> 25) & 1;
	r->enum_c = (val >> 26) & 0x0f;
}

/*
 * mixer switches/volumes
 */

#define AZF3328_MIXER_SWITCH

#define AZF3328_MIXER_VOL_STEREO

#define AZF3328_MIXER_VOL_MONO

#define AZF3328_MIXER_VOL_SPECIAL

#define AZF3328_MIXER_ENUM

static int
snd_azf3328_info_mixer(struct snd_kcontrol *kcontrol,
		       struct snd_ctl_elem_info *uinfo)
{
	struct azf3328_mixer_reg reg;

	snd_azf3328_mixer_reg_decode(&reg, kcontrol->private_value);
	uinfo->type = reg.mask == 1 ?
		SNDRV_CTL_ELEM_TYPE_BOOLEAN : SNDRV_CTL_ELEM_TYPE_INTEGER;
	uinfo->count = reg.stereo + 1;
	uinfo->value.integer.min = 0;
	uinfo->value.integer.max = reg.mask;
	return 0;
}

static int
snd_azf3328_get_mixer(struct snd_kcontrol *kcontrol,
		      struct snd_ctl_elem_value *ucontrol)
{
	struct snd_azf3328 *chip = snd_kcontrol_chip(kcontrol);
	struct azf3328_mixer_reg reg;
	u16 oreg, val;

	snd_azf3328_mixer_reg_decode(&reg, kcontrol->private_value);

	oreg = snd_azf3328_mixer_inw(chip, reg.reg);
	val = (oreg >> reg.lchan_shift) & reg.mask;
	if (reg.invert)
		val = reg.mask - val;
	ucontrol->value.integer.value[0] = val;
	if (reg.stereo) {
		val = (oreg >> reg.rchan_shift) & reg.mask;
		if (reg.invert)
			val = reg.mask - val;
		ucontrol->value.integer.value[1] = val;
	}
	dev_dbg(chip->card->dev,
		"get: %02x is %04x -> vol %02lx|%02lx (shift %02d|%02d, mask %02x, inv. %d, stereo %d)\n",
		reg.reg, oreg,
		ucontrol->value.integer.value[0], ucontrol->value.integer.value[1],
		reg.lchan_shift, reg.rchan_shift, reg.mask, reg.invert, reg.stereo);
	return 0;
}

static int
snd_azf3328_put_mixer(struct snd_kcontrol *kcontrol,
		      struct snd_ctl_elem_value *ucontrol)
{
	struct snd_azf3328 *chip = snd_kcontrol_chip(kcontrol);
	struct azf3328_mixer_reg reg;
	u16 oreg, nreg, val;

	snd_azf3328_mixer_reg_decode(&reg, kcontrol->private_value);
	oreg = snd_azf3328_mixer_inw(chip, reg.reg);
	val = ucontrol->value.integer.value[0] & reg.mask;
	if (reg.invert)
		val = reg.mask - val;
	nreg = oreg & ~(reg.mask << reg.lchan_shift);
	nreg |= (val << reg.lchan_shift);
	if (reg.stereo) {
		val = ucontrol->value.integer.value[1] & reg.mask;
		if (reg.invert)
			val = reg.mask - val;
		nreg &= ~(reg.mask << reg.rchan_shift);
		nreg |= (val << reg.rchan_shift);
	}
	if (reg.mask >= 0x07) /* it's a volume control, so better take care */
		snd_azf3328_mixer_write_volume_gradually(
			chip, reg.reg, nreg >> 8, nreg & 0xff,
			/* just set both channels, doesn't matter */
			SET_CHAN_LEFT|SET_CHAN_RIGHT,
			0);
	else
        	snd_azf3328_mixer_outw(chip, reg.reg, nreg);

	dev_dbg(chip->card->dev,
		"put: %02x to %02lx|%02lx, oreg %04x; shift %02d|%02d -> nreg %04x; after: %04x\n",
		reg.reg, ucontrol->value.integer.value[0], ucontrol->value.integer.value[1],
		oreg, reg.lchan_shift, reg.rchan_shift,
		nreg, snd_azf3328_mixer_inw(chip, reg.reg));
	return (nreg != oreg);
}

static int
snd_azf3328_info_mixer_enum(struct snd_kcontrol *kcontrol,
			    struct snd_ctl_elem_info *uinfo)
{
	static const char * const texts1[] = {
		"Mic1", "Mic2"
	};
	static const char * const texts2[] = {
		"Mix", "Mic"
	};
	static const char * const texts3[] = {
		"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 * const *p = NULL;

	snd_azf3328_mixer_reg_decode(&reg, kcontrol->private_value);
	if (reg.reg == IDX_MIXER_ADVCTL2) {
		switch(reg.lchan_shift) {
		case 8: /* modem out sel */
			p = texts1;
			break;
		case 9: /* mono sel source */
			p = texts2;
			break;
		case 15: /* PCM Out Path */
			p = texts4;
			break;
		}
	} else if (reg.reg == IDX_MIXER_REC_SELECT)
		p = texts3;

	return snd_ctl_enum_info(uinfo,
				 (reg.reg == IDX_MIXER_REC_SELECT) ? 2 : 1,
				 reg.enum_c, p);
}

static int
snd_azf3328_get_mixer_enum(struct snd_kcontrol *kcontrol,
			   struct snd_ctl_elem_value *ucontrol)
{
        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) {
        	ucontrol->value.enumerated.item[0] = (val >> 8) & (reg.enum_c - 1);
        	ucontrol->value.enumerated.item[1] = (val >> 0) & (reg.enum_c - 1);
	} else
        	ucontrol->value.enumerated.item[0] = (val >> reg.lchan_shift) & (reg.enum_c - 1);

	dev_dbg(chip->card->dev,
		"get_enum: %02x is %04x -> %d|%d (shift %02d, enum_c %d)\n",
		reg.reg, val, ucontrol->value.enumerated.item[0], ucontrol->value.enumerated.item[1],
		reg.lchan_shift, reg.enum_c);
        return 0;
}

static int
snd_azf3328_put_mixer_enum(struct snd_kcontrol *kcontrol,
			   struct snd_ctl_elem_value *ucontrol)
{
        struct snd_azf3328 *chip = snd_kcontrol_chip(kcontrol);
	struct azf3328_mixer_reg reg;
	u16 oreg, nreg, val;

	snd_azf3328_mixer_reg_decode(&reg, kcontrol->private_value);
	oreg = snd_azf3328_mixer_inw(chip, reg.reg);
	val = oreg;
	if (reg.reg == IDX_MIXER_REC_SELECT) {
        	if (ucontrol->value.enumerated.item[0] > reg.enum_c - 1U ||
            	ucontrol->value.enumerated.item[1] > reg.enum_c - 1U)
                	return -EINVAL;
        	val = (ucontrol->value.enumerated.item[0] << 8) |
        	      (ucontrol->value.enumerated.item[1] << 0);
	} else {
        	if (ucontrol->value.enumerated.item[0] > reg.enum_c - 1U)
                	return -EINVAL;
		val &= ~((reg.enum_c - 1) << reg.lchan_shift);
        	val |= (ucontrol->value.enumerated.item[0] << reg.lchan_shift);
	}
	snd_azf3328_mixer_outw(chip, reg.reg, val);
	nreg = val;

	dev_dbg(chip->card->dev,
		"put_enum: %02x to %04x, oreg %04x\n", reg.reg, val, oreg);
	return (nreg != oreg);
}

static const struct snd_kcontrol_new snd_azf3328_mixer_controls[] = {
	AZF3328_MIXER_SWITCH("Master Playback Switch", IDX_MIXER_PLAY_MASTER, 15, 1),
	AZF3328_MIXER_VOL_STEREO("Master Playback Volume", IDX_MIXER_PLAY_MASTER, 0x1f, 1),
	AZF3328_MIXER_SWITCH("PCM Playback Switch", IDX_MIXER_WAVEOUT, 15, 1),
	AZF3328_MIXER_VOL_STEREO("PCM Playback Volume",
					IDX_MIXER_WAVEOUT, 0x1f, 1),
	AZF3328_MIXER_SWITCH("PCM 3D Bypass Playback Switch",
					IDX_MIXER_ADVCTL2, 7, 1),
	AZF3328_MIXER_SWITCH("FM Playback Switch", IDX_MIXER_FMSYNTH, 15, 1),
	AZF3328_MIXER_VOL_STEREO("FM Playback Volume", IDX_MIXER_FMSYNTH, 0x1f, 1),
	AZF3328_MIXER_SWITCH("CD Playback Switch", IDX_MIXER_CDAUDIO, 15, 1),
	AZF3328_MIXER_VOL_STEREO("CD Playback Volume", IDX_MIXER_CDAUDIO, 0x1f, 1),
	AZF3328_MIXER_SWITCH("Capture Switch", IDX_MIXER_REC_VOLUME, 15, 1),
	AZF3328_MIXER_VOL_STEREO("Capture Volume", IDX_MIXER_REC_VOLUME, 0x0f, 0),
	AZF3328_MIXER_ENUM("Capture Source", IDX_MIXER_REC_SELECT, 8, 0),
	AZF3328_MIXER_SWITCH("Mic Playback Switch", IDX_MIXER_MIC, 15, 1),
	AZF3328_MIXER_VOL_MONO("Mic Playback Volume", IDX_MIXER_MIC, 0x1f, 1),
	AZF3328_MIXER_SWITCH("Mic Boost (+20dB)", IDX_MIXER_MIC, 6, 0),
	AZF3328_MIXER_SWITCH("Line Playback Switch", IDX_MIXER_LINEIN, 15, 1),
	AZF3328_MIXER_VOL_STEREO("Line Playback Volume", IDX_MIXER_LINEIN, 0x1f, 1),
	AZF3328_MIXER_SWITCH("Beep Playback Switch", IDX_MIXER_PCBEEP, 15, 1),
	AZF3328_MIXER_VOL_SPECIAL("Beep Playback Volume", IDX_MIXER_PCBEEP, 0x0f, 1, 1),
	AZF3328_MIXER_SWITCH("Video Playback Switch", IDX_MIXER_VIDEO, 15, 1),
	AZF3328_MIXER_VOL_STEREO("Video Playback Volume", IDX_MIXER_VIDEO, 0x1f, 1),
	AZF3328_MIXER_SWITCH("Aux Playback Switch", IDX_MIXER_AUX, 15, 1),
	AZF3328_MIXER_VOL_STEREO("Aux Playback Volume", IDX_MIXER_AUX, 0x1f, 1),
	AZF3328_MIXER_SWITCH("Modem Playback Switch", IDX_MIXER_MODEMOUT, 15, 1),
	AZF3328_MIXER_VOL_MONO("Modem Playback Volume", IDX_MIXER_MODEMOUT, 0x1f, 1),
	AZF3328_MIXER_SWITCH("Modem Capture Switch", IDX_MIXER_MODEMIN, 15, 1),
	AZF3328_MIXER_VOL_MONO("Modem Capture Volume", IDX_MIXER_MODEMIN, 0x1f, 1),
	AZF3328_MIXER_ENUM("Mic Select", IDX_MIXER_ADVCTL2, 2, 8),
	AZF3328_MIXER_ENUM("Mono Output Select", IDX_MIXER_ADVCTL2, 2, 9),
	AZF3328_MIXER_ENUM("PCM Output Route", IDX_MIXER_ADVCTL2, 2, 15), /* PCM Out Path, place in front since it controls *both* 3D and Bass/Treble! */
	AZF3328_MIXER_VOL_SPECIAL("Tone Control - Treble", IDX_MIXER_BASSTREBLE, 0x07, 1, 0),
	AZF3328_MIXER_VOL_SPECIAL("Tone Control - Bass", IDX_MIXER_BASSTREBLE, 0x07, 9, 0),
	AZF3328_MIXER_SWITCH("3D Control - Switch", IDX_MIXER_ADVCTL2, 13, 0),
	AZF3328_MIXER_VOL_SPECIAL("3D Control - Width", IDX_MIXER_ADVCTL1, 0x07, 1, 0), /* "3D Width" */
	AZF3328_MIXER_VOL_SPECIAL("3D Control - Depth", IDX_MIXER_ADVCTL1, 0x03, 8, 0), /* "Hifi 3D" */
#if MIXER_TESTING
	AZF3328_MIXER_SWITCH("0", IDX_MIXER_ADVCTL2, 0, 0),
	AZF3328_MIXER_SWITCH("1", IDX_MIXER_ADVCTL2, 1, 0),
	AZF3328_MIXER_SWITCH("2", IDX_MIXER_ADVCTL2, 2, 0),
	AZF3328_MIXER_SWITCH("3", IDX_MIXER_ADVCTL2, 3, 0),
	AZF3328_MIXER_SWITCH("4", IDX_MIXER_ADVCTL2, 4, 0),
	AZF3328_MIXER_SWITCH("5", IDX_MIXER_ADVCTL2, 5, 0),
	AZF3328_MIXER_SWITCH("6", IDX_MIXER_ADVCTL2, 6, 0),
	AZF3328_MIXER_SWITCH("7", IDX_MIXER_ADVCTL2, 7, 0),
	AZF3328_MIXER_SWITCH("8", IDX_MIXER_ADVCTL2, 8, 0),
	AZF3328_MIXER_SWITCH("9", IDX_MIXER_ADVCTL2, 9, 0),
	AZF3328_MIXER_SWITCH("10", IDX_MIXER_ADVCTL2, 10, 0),
	AZF3328_MIXER_SWITCH("11", IDX_MIXER_ADVCTL2, 11, 0),
	AZF3328_MIXER_SWITCH("12", IDX_MIXER_ADVCTL2, 12, 0),
	AZF3328_MIXER_SWITCH("13", IDX_MIXER_ADVCTL2, 13, 0),
	AZF3328_MIXER_SWITCH("14", IDX_MIXER_ADVCTL2, 14, 0),
	AZF3328_MIXER_SWITCH("15", IDX_MIXER_ADVCTL2, 15, 0),
#endif
};

static const u16 snd_azf3328_init_values[][2] = {
        { IDX_MIXER_PLAY_MASTER,	MIXER_MUTE_MASK|0x1f1f },
        { IDX_MIXER_MODEMOUT,		MIXER_MUTE_MASK|0x1f1f },
	{ IDX_MIXER_BASSTREBLE,		0x0000 },
	{ IDX_MIXER_PCBEEP,		MIXER_MUTE_MASK|0x1f1f },
	{ IDX_MIXER_MODEMIN,		MIXER_MUTE_MASK|0x1f1f },
	{ IDX_MIXER_MIC,		MIXER_MUTE_MASK|0x001f },
	{ IDX_MIXER_LINEIN,		MIXER_MUTE_MASK|0x1f1f },
	{ IDX_MIXER_CDAUDIO,		MIXER_MUTE_MASK|0x1f1f },
	{ IDX_MIXER_VIDEO,		MIXER_MUTE_MASK|0x1f1f },
	{ IDX_MIXER_AUX,		MIXER_MUTE_MASK|0x1f1f },
        { IDX_MIXER_WAVEOUT,		MIXER_MUTE_MASK|0x1f1f },
        { IDX_MIXER_FMSYNTH,		MIXER_MUTE_MASK|0x1f1f },
        { IDX_MIXER_REC_VOLUME,		MIXER_MUTE_MASK|0x0707 },
};

static int
snd_azf3328_mixer_new(struct snd_azf3328 *chip)
{
	struct snd_card *card;
	const struct snd_kcontrol_new *sw;
	unsigned int idx;
	int err;

	if (snd_BUG_ON(!chip || !chip->card))
		return -EINVAL;

	card = chip->card;

	/* mixer reset */
	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) {
		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) {
		err = snd_ctl_add(chip->card, snd_ctl_new1(sw, chip));
		if (err < 0)
			return err;
	}
	snd_component_add(card, "AZF3328 mixer");
	strcpy(card->mixername, "AZF3328 mixer");

	return 0;
}
#endif /* AZF_USE_AC97_LAYER */

static void
snd_azf3328_codec_setfmt(struct snd_azf3328_codec_data *codec,
			       enum azf_freq_t bitrate,
			       unsigned int format_width,
			       unsigned int channels
)
{}

static inline void
snd_azf3328_codec_setfmt_lowpower(struct snd_azf3328_codec_data *codec
)
{}

static void
snd_azf3328_ctrl_reg_6AH_update(struct snd_azf3328 *chip,
					unsigned bitmask,
					bool enable
)
{}

static inline void
snd_azf3328_ctrl_enable_codecs(struct snd_azf3328 *chip, bool enable)
{}

static void
snd_azf3328_ctrl_codec_activity(struct snd_azf3328 *chip,
				enum snd_azf3328_codec_type codec_type,
				bool enable
)
{}

static void
snd_azf3328_codec_setdmaa(struct snd_azf3328 *chip,
			  struct snd_azf3328_codec_data *codec,
			  unsigned long addr,
			  unsigned int period_bytes,
			  unsigned int buffer_bytes
)
{}

static int
snd_azf3328_pcm_prepare(struct snd_pcm_substream *substream)
{}

static int
snd_azf3328_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
{}

static snd_pcm_uframes_t
snd_azf3328_pcm_pointer(struct snd_pcm_substream *substream
)
{}

/******************************************************************/

#ifdef SUPPORT_GAMEPORT
static inline void
snd_azf3328_gameport_irq_enable(struct snd_azf3328 *chip,
				bool enable
)
{}

static inline void
snd_azf3328_gameport_legacy_address_enable(struct snd_azf3328 *chip,
					   bool enable
)
{}

static void
snd_azf3328_gameport_set_counter_frequency(struct snd_azf3328 *chip,
					   unsigned int freq_cfg
)
{}

static inline void
snd_azf3328_gameport_axis_circuit_enable(struct snd_azf3328 *chip, bool enable)
{}

static inline void
snd_azf3328_gameport_interrupt(struct snd_azf3328 *chip)
{}

static int
snd_azf3328_gameport_open(struct gameport *gameport, int mode)
{}

static void
snd_azf3328_gameport_close(struct gameport *gameport)
{}

static int
snd_azf3328_gameport_cooked_read(struct gameport *gameport,
				 int *axes,
				 int *buttons
)
{}

static int
snd_azf3328_gameport(struct snd_azf3328 *chip, int dev)
{}

static void
snd_azf3328_gameport_free(struct snd_azf3328 *chip)
{}
#else
static inline int
snd_azf3328_gameport(struct snd_azf3328 *chip, int dev) { return -ENOSYS; }
static inline void
snd_azf3328_gameport_free(struct snd_azf3328 *chip) { }
static inline void
snd_azf3328_gameport_interrupt(struct snd_azf3328 *chip)
{
	dev_warn(chip->card->dev, "huh, game port IRQ occurred!?\n");
}
#endif /* SUPPORT_GAMEPORT */

/******************************************************************/

static inline void
snd_azf3328_irq_log_unknown_type(struct snd_azf3328 *chip, u8 which)
{}

static inline void
snd_azf3328_pcm_interrupt(struct snd_azf3328 *chip,
			  const struct snd_azf3328_codec_data *first_codec,
			  u8 status
)
{}

static irqreturn_t
snd_azf3328_interrupt(int irq, void *dev_id)
{}

/*****************************************************************/

/* as long as we think we have identical snd_pcm_hardware parameters
   for playback, capture and i2s out, we can use the same physical struct
   since the struct is simply being copied into a member.
*/
static const struct snd_pcm_hardware snd_azf3328_hardware =;


static const unsigned int snd_azf3328_fixed_rates[] =;

static const struct snd_pcm_hw_constraint_list snd_azf3328_hw_constraints_rates =;

/*****************************************************************/

static int
snd_azf3328_pcm_open(struct snd_pcm_substream *substream,
		     enum snd_azf3328_codec_type codec_type
)
{}

static int
snd_azf3328_pcm_playback_open(struct snd_pcm_substream *substream)
{}

static int
snd_azf3328_pcm_capture_open(struct snd_pcm_substream *substream)
{}

static int
snd_azf3328_pcm_i2s_out_open(struct snd_pcm_substream *substream)
{}

static int
snd_azf3328_pcm_close(struct snd_pcm_substream *substream
)
{}

/******************************************************************/

static const struct snd_pcm_ops snd_azf3328_playback_ops =;

static const struct snd_pcm_ops snd_azf3328_capture_ops =;

static const struct snd_pcm_ops snd_azf3328_i2s_out_ops =;

static int
snd_azf3328_pcm(struct snd_azf3328 *chip)
{}

/******************************************************************/

/*** NOTE: the physical timer resolution actually is 1024000 ticks per second
 *** (probably derived from main crystal via a divider of 24),
 *** but announcing those attributes to user-space would make programs
 *** configure the timer to a 1 tick value, resulting in an absolutely fatal
 *** timer IRQ storm.
 *** Thus I chose to announce a down-scaled virtual timer to the outside and
 *** calculate real timer countdown values internally.
 *** (the scale factor can be set via module parameter "seqtimer_scaling").
 ***/

static int
snd_azf3328_timer_start(struct snd_timer *timer)
{}

static int
snd_azf3328_timer_stop(struct snd_timer *timer)
{}


static int
snd_azf3328_timer_precise_resolution(struct snd_timer *timer,
					       unsigned long *num, unsigned long *den)
{}

static struct snd_timer_hardware snd_azf3328_timer_hw =;

static int
snd_azf3328_timer(struct snd_azf3328 *chip, int device)
{}

/******************************************************************/

static void
snd_azf3328_free(struct snd_card *card)
{}

#if 0
/* check whether a bit can be modified */
static void
snd_azf3328_test_bit(unsigned unsigned reg, int bit)
{
	unsigned char val, valoff, valon;

	val = inb(reg);

	outb(val & ~(1 << bit), reg);
	valoff = inb(reg);

	outb(val|(1 << bit), reg);
	valon = inb(reg);

	outb(val, reg);

	printk(KERN_DEBUG "reg %04x bit %d: %02x %02x %02x\n",
				reg, bit, val, valoff, valon
	);
}
#endif

static inline void
snd_azf3328_debug_show_ports(const struct snd_azf3328 *chip)
{}

static int
snd_azf3328_create(struct snd_card *card,
		   struct pci_dev *pci,
		   unsigned long device_type)
{}

static int
__snd_azf3328_probe(struct pci_dev *pci, const struct pci_device_id *pci_id)
{}

static int
snd_azf3328_probe(struct pci_dev *pci, const struct pci_device_id *pci_id)
{}

static inline void
snd_azf3328_suspend_regs(const struct snd_azf3328 *chip,
			 unsigned long io_addr, unsigned count, u32 *saved_regs)
{}

static inline void
snd_azf3328_resume_regs(const struct snd_azf3328 *chip,
			const u32 *saved_regs,
			unsigned long io_addr,
			unsigned count
)
{}

static inline void
snd_azf3328_suspend_ac97(struct snd_azf3328 *chip)
{}

static inline void
snd_azf3328_resume_ac97(const struct snd_azf3328 *chip)
{}

static int
snd_azf3328_suspend(struct device *dev)
{}

static int
snd_azf3328_resume(struct device *dev)
{}

static DEFINE_SIMPLE_DEV_PM_OPS(snd_azf3328_pm, snd_azf3328_suspend, snd_azf3328_resume);

static struct pci_driver azf3328_driver =;

module_pci_driver();