// 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 1
/* === 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
*/
staticint index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */
module_param_array(index, int, NULL, 0444);
MODULE_PARM_DESC(index, "Index value for AZF3328 soundcard.");
staticchar *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */
module_param_array(id, charp, NULL, 0444);
MODULE_PARM_DESC(id, "ID string for AZF3328 soundcard.");
/* register 0x6a is write-only, thus need to remember setting. * If we need to add more registers here, then we might try to fold this * into some transparent combined shadow register handling with
* CONFIG_PM register storage below, but that's slightly difficult. */
u16 shadow_reg_ctrl_6AH;
/* register value containers for power management
* Note: not always full I/O range preserved (similar to Win driver!) */
u32 saved_regs_ctrl[AZF_ALIGN(AZF_IO_SIZE_CTRL_PM) / 4];
u32 saved_regs_game[AZF_ALIGN(AZF_IO_SIZE_GAME_PM) / 4];
u32 saved_regs_mpu[AZF_ALIGN(AZF_IO_SIZE_MPU_PM) / 4];
u32 saved_regs_opl3[AZF_ALIGN(AZF_IO_SIZE_OPL3_PM) / 4];
u32 saved_regs_mixer[AZF_ALIGN(AZF_IO_SIZE_MIXER_PM) / 4];
};
staticint
snd_azf3328_io_reg_setb(unsigned reg, u8 mask, bool do_set)
{ /* Well, strictly spoken, the inb/outb sequence isn't atomic and would need locking. However we currently don't care
since it potentially complicates matters. */
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;
/* the mute bit is on the *second* (i.e. right) register of a
* left/right channel setting */
updated = snd_azf3328_io_reg_setb(portbase, AZF_MUTE_BIT, do_mute);
/* indicate whether it was muted before */ return (do_mute) ? !updated : updated;
}
staticinlinevoid
snd_azf3328_mixer_ac97_map_unsupported(conststruct snd_azf3328 *chip, unsignedshort reg, constchar *mode)
{ /* need to add some more or less clever emulation? */
dev_warn(chip->card->dev, "missing %s emulation for AC97 register 0x%02x!\n",
mode, reg);
}
/* * 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 0x3f #define AZF_AC97_REG_UNSUPPORTED 0x8000 #define AZF_AC97_REG_REAL_IO_READ 0x4000 #define AZF_AC97_REG_REAL_IO_WRITE 0x2000 #define AZF_AC97_REG_REAL_IO_RW \
(AZF_AC97_REG_REAL_IO_READ | AZF_AC97_REG_REAL_IO_WRITE) #define AZF_AC97_REG_EMU_IO_READ 0x0400 #define AZF_AC97_REG_EMU_IO_WRITE 0x0200 #define AZF_AC97_REG_EMU_IO_RW \
(AZF_AC97_REG_EMU_IO_READ | AZF_AC97_REG_EMU_IO_WRITE) staticunsignedshort
snd_azf3328_mixer_ac97_map_reg_idx(unsignedshort reg)
{ staticconststruct { unsignedshort azf_reg;
} azf_reg_mapper[] = { /* Especially when taking into consideration * mono/stereo-based sequence of azf vs. AC97 control series, * it's quite obvious that azf simply got rid * of the AC97_HEADPHONE control at its intended offset, * thus shifted _all_ controls by one, * and _then_ simply added it as an FMSYNTH control at the end, * to make up for the offset. * This means we'll have to translate indices here as * needed and then do some tiny AC97 patch action * (snd_ac97_rename_vol_ctl() etc.) - that's it.
*/
{ /* AC97_RESET */ IDX_MIXER_RESET
| AZF_AC97_REG_REAL_IO_WRITE
| AZF_AC97_REG_EMU_IO_READ },
{ /* AC97_MASTER */ IDX_MIXER_PLAY_MASTER }, /* note large shift: AC97_HEADPHONE to IDX_MIXER_FMSYNTH! */
{ /* AC97_HEADPHONE */ IDX_MIXER_FMSYNTH },
{ /* AC97_MASTER_MONO */ IDX_MIXER_MODEMOUT },
{ /* AC97_MASTER_TONE */ IDX_MIXER_BASSTREBLE },
{ /* AC97_PC_BEEP */ IDX_MIXER_PCBEEP },
{ /* AC97_PHONE */ IDX_MIXER_MODEMIN },
{ /* AC97_MIC */ IDX_MIXER_MIC },
{ /* AC97_LINE */ IDX_MIXER_LINEIN },
{ /* AC97_CD */ IDX_MIXER_CDAUDIO },
{ /* AC97_VIDEO */ IDX_MIXER_VIDEO },
{ /* AC97_AUX */ IDX_MIXER_AUX },
{ /* AC97_PCM */ IDX_MIXER_WAVEOUT },
{ /* AC97_REC_SEL */ IDX_MIXER_REC_SELECT },
{ /* AC97_REC_GAIN */ IDX_MIXER_REC_VOLUME },
{ /* AC97_REC_GAIN_MIC */ AZF_AC97_REG_EMU_IO_RW },
{ /* AC97_GENERAL_PURPOSE */ IDX_MIXER_ADVCTL2 },
{ /* AC97_3D_CONTROL */ IDX_MIXER_ADVCTL1 },
};
unsignedshort reg_azf = AZF_AC97_REG_UNSUPPORTED;
/* azf3328 supports the low-numbered and low-spec:ed range
of AC97 regs only */ if (reg <= AC97_3D_CONTROL) { unsignedshort reg_idx = reg / 2;
reg_azf = azf_reg_mapper[reg_idx].azf_reg; /* a translation-only entry means it's real read/write: */ if (!(reg_azf & ~AZF_REG_MASK))
reg_azf |= AZF_AC97_REG_REAL_IO_RW;
} else { switch (reg) { case AC97_POWERDOWN:
reg_azf = AZF_AC97_REG_EMU_IO_RW; break; case AC97_EXTENDED_ID:
reg_azf = AZF_AC97_REG_EMU_IO_READ; break; case AC97_EXTENDED_STATUS: /* I don't know what the h*ll AC97 layer * would consult this _extended_ register for * given a base-AC97-advertised card, * but let's just emulate it anyway :-P
*/
reg_azf = AZF_AC97_REG_EMU_IO_RW; break; case AC97_VENDOR_ID1: case AC97_VENDOR_ID2:
reg_azf = AZF_AC97_REG_EMU_IO_READ; break;
}
} return reg_azf;
}
staticconstunsignedshort
azf_emulated_ac97_caps =
AC97_BC_DEDICATED_MIC |
AC97_BC_BASS_TREBLE | /* Headphone is an FM Synth control here */
AC97_BC_HEADPHONE | /* no AC97_BC_LOUDNESS! */ /* mask 0x7c00 is vendor-specific 3D enhancement vendor indicator. Since there actually _is_ an entry for Aztech Labs (13), make damn sure
to indicate it. */
(13 << 10);
staticconstunsignedshort
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).
*/ staticconstunsignedint
azf_emulated_ac97_vendor_id = 0x415a5401;
dev_dbg(chip->card->dev, "snd_azf3328_mixer_ac97_write reg_ac97 %u val %u\n",
reg_ac97, val); if (reg_azf & AZF_AC97_REG_UNSUPPORTED)
unsupported = true; else { if (reg_azf & AZF_AC97_REG_REAL_IO_WRITE)
snd_azf3328_mixer_outw(
chip,
reg_azf & AZF_REG_MASK,
val
); else if (reg_azf & AZF_AC97_REG_EMU_IO_WRITE) { switch (reg_ac97) { case AC97_REC_GAIN_MIC: case AC97_POWERDOWN: case AC97_EXTENDED_STATUS: /* * Silently swallow these writes. * Since for most registers our card doesn't * actually support a comparable feature, * this is exactly what we should do here. * The AC97 layer's I/O caching probably * automatically takes care of all the rest... * (remembers written values etc.)
*/ break; default:
unsupported = true; break;
}
}
} if (unsupported)
snd_azf3328_mixer_ac97_map_unsupported(chip, reg_ac97, "write");
}
/* * ALSA's AC97 layer has terrible init crackling issues, * unfortunately, and since it makes use of AC97_RESET, * there's no use trying to mute Master Playback proactively.
*/
rc = snd_ac97_bus(chip->card, 0, &ops, NULL, &bus); if (!rc)
rc = snd_ac97_mixer(bus, &ac97, &chip->ac97); /* * Make sure to complain loudly in case of AC97 init failure, * since failure may happen quite often, * due to this card being a very quirky AC97 "lookalike".
*/ if (rc)
dev_err(chip->card->dev, "AC97 init failed, err %d!\n", rc);
/* If we return an error here, then snd_card_free() should * free up any ac97 codecs that got created, as well as the bus.
*/ return rc;
} #else/* AZF_USE_AC97_LAYER */ staticvoid
snd_azf3328_mixer_write_volume_gradually(conststruct snd_azf3328 *chip, unsigned reg, unsignedchar dst_vol_left, unsignedchar dst_vol_right, int chan_sel, int delay
)
{ unsignedlong portbase = chip->mixer_io + reg; unsignedchar 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;
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));
}
snd_azf3328_mixer_reg_decode(®, 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;
}
} elseif (reg.reg == IDX_MIXER_REC_SELECT)
p = texts3;
switch (bitrate) { case AZF_FREQ_4000: freq = SOUNDFORMAT_FREQ_SUSPECTED_4000; break; case AZF_FREQ_4800: freq = SOUNDFORMAT_FREQ_SUSPECTED_4800; break; case AZF_FREQ_5512: /* the AZF3328 names it "5510" for some strange reason */
freq = SOUNDFORMAT_FREQ_5510; break; case AZF_FREQ_6620: freq = SOUNDFORMAT_FREQ_6620; break; case AZF_FREQ_8000: freq = SOUNDFORMAT_FREQ_8000; break; case AZF_FREQ_9600: freq = SOUNDFORMAT_FREQ_9600; break; case AZF_FREQ_11025: freq = SOUNDFORMAT_FREQ_11025; break; case AZF_FREQ_13240: freq = SOUNDFORMAT_FREQ_SUSPECTED_13240; break; case AZF_FREQ_16000: freq = SOUNDFORMAT_FREQ_16000; break; case AZF_FREQ_22050: freq = SOUNDFORMAT_FREQ_22050; break; case AZF_FREQ_32000: freq = SOUNDFORMAT_FREQ_32000; break; default:
pr_warn("azf3328: unknown bitrate %d, assuming 44.1kHz!\n", bitrate);
fallthrough; case AZF_FREQ_44100: freq = SOUNDFORMAT_FREQ_44100; break; case AZF_FREQ_48000: freq = SOUNDFORMAT_FREQ_48000; break; case AZF_FREQ_66200: freq = 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? */ /* val = 0xff0a; 47m30.599s (4764,891Hz; -> 4800Hz???) yup, 4803Hz */ /* val = 0xff0c; 57m0.510s (4010,263Hz; -> 4000Hz???) yup, 4003Hz */ /* val = 0xff05; 5m11.556s (... -> 44100Hz) */ /* val = 0xff03; 10m21.529s (21872,463Hz; -> 22050Hz???) */ /* val = 0xff0f; 20m41.883s (10937,993Hz; -> 11025Hz???) */ /* val = 0xff0d; 41m23.135s (5523,600Hz; -> 5512Hz???) */ /* val = 0xff0e; 28m30.777s (8017Hz; -> 8000Hz???) */
val |= freq;
if (channels == 2)
val |= SOUNDFORMAT_FLAG_2CHANNELS;
if (format_width == 16)
val |= SOUNDFORMAT_FLAG_16BIT;
spin_lock_irqsave(codec->lock, flags);
/* set bitrate/format */
snd_azf3328_codec_outw(codec, IDX_IO_CODEC_SOUNDFORMAT, 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 * do some tweaking to reenable it and get rid of the clicking * (FIXME: yes, it works, but what exactly am I doing here?? :) * FIXME: does this have some side effects for full-duplex
* or other dramatic side effects? */ /* do it for non-capture codecs only */ if (codec->type != AZF_CODEC_CAPTURE)
snd_azf3328_codec_outw(codec, IDX_IO_CODEC_DMA_FLAGS,
snd_azf3328_codec_inw(codec, IDX_IO_CODEC_DMA_FLAGS) |
DMA_RUN_SOMETHING1 |
DMA_RUN_SOMETHING2 |
SOMETHING_ALMOST_ALWAYS_SET |
DMA_EPILOGUE_SOMETHING |
DMA_SOMETHING_ELSE
);
spin_unlock_irqrestore(codec->lock, flags);
}
staticinlinevoid
snd_azf3328_codec_setfmt_lowpower(struct snd_azf3328_codec_data *codec
)
{ /* 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(codec, AZF_FREQ_4000, 8, 1);
}
staticinlinevoid
snd_azf3328_ctrl_enable_codecs(struct snd_azf3328 *chip, bool enable)
{
dev_dbg(chip->card->dev, "codec_enable %d\n", enable); /* no idea what exactly is being done here, but I strongly assume it's
* PM related */
snd_azf3328_ctrl_reg_6AH_update(
chip, IO_6A_PAUSE_PLAYBACK_BIT8, enable
);
}
if (enable) /* if enable codec, call enable_codecs func
to enable codec supply... */
call_function = 1; else { /* ...otherwise call enable_codecs func (which globally shuts down operation of codecs) only in case the other codecs are currently
not active either! */
call_function =
((!chip->codecs[peer_codecs[codec_type].other1]
.running)
&& (!chip->codecs[peer_codecs[codec_type].other2]
.running));
} if (call_function)
snd_azf3328_ctrl_enable_codecs(chip, enable);
/* ...and adjust clock, too
* (reduce noise and power consumption) */ if (!enable)
snd_azf3328_codec_setfmt_lowpower(codec);
codec->running = enable;
}
}
staticvoid
snd_azf3328_codec_setdmaa(struct snd_azf3328 *chip, struct snd_azf3328_codec_data *codec, unsignedlong addr, unsignedint period_bytes, unsignedint buffer_bytes
)
{
WARN_ONCE(period_bytes & 1, "odd period length!?\n");
WARN_ONCE(buffer_bytes != 2 * period_bytes, "missed our input expectations! %u vs. %u\n",
buffer_bytes, period_bytes); if (!codec->running) { /* AZF3328 uses a two buffer pointer DMA transfer approach */
/* Hmm, are we really supposed to decrement this by 1?? Most definitely certainly not: configuring full length does work properly (i.e. likely better), and BTW we violated possibly differing frame sizes with this...
/* stop transfer */
flags1 &= ~DMA_RESUME;
snd_azf3328_codec_outw(codec, IDX_IO_CODEC_DMA_FLAGS, flags1);
/* hmm, is this really required? we're resetting the same bit
* immediately thereafter... */
flags1 |= DMA_RUN_SOMETHING1;
snd_azf3328_codec_outw(codec, IDX_IO_CODEC_DMA_FLAGS, flags1);
staticinlinevoid
snd_azf3328_gameport_interrupt(struct snd_azf3328 *chip)
{ /* * skeleton handler only * (we do not want axis reading in interrupt handler - too much load!)
*/
dev_dbg(chip->card->dev, "gameport irq\n");
/* this should ACK the gameport IRQ properly, hopefully. */
snd_azf3328_game_inw(chip, IDX_GAME_AXIS_VALUE);
}
staticint
snd_azf3328_gameport_open(struct gameport *gameport, int mode)
{ struct snd_azf3328 *chip = gameport_get_port_data(gameport); int res;
dev_dbg(chip->card->dev, "gameport_open, mode %d\n", mode); switch (mode) { case GAMEPORT_MODE_COOKED: case GAMEPORT_MODE_RAW:
res = 0; break; default:
res = -1; break;
}
/* ok, this one is a bit dirty: cooked_read is being polled by a timer, * thus we're atomic and cannot actively wait in here * (which would be useful for us since it probably would be better * to trigger a measurement in here, then wait a short amount of * time until it's finished, then read values of _this_ measurement). * * Thus we simply resort to reading values if they're available already * and trigger the next measurement.
*/
val = snd_azf3328_game_inb(chip, IDX_GAME_AXES_CONFIG); if (val & GAME_AXES_SAMPLING_READY) { for (i = 0; i < ARRAY_SIZE(chip->axes); ++i) { /* configure the axis to read */
val = (i << 4) | 0x0f;
snd_azf3328_game_outb(chip, IDX_GAME_AXES_CONFIG, val);
/* trigger next sampling of axes, to be evaluated the next time we
* enter this function */
/* for some very, very strange reason we cannot enable * Measurement Ready monitoring for all axes here,
* at least not when only one joystick connected */
val = 0x03; /* we're able to monitor axes 1 and 2 only */
snd_azf3328_game_outb(chip, IDX_GAME_AXES_CONFIG, val);
status = snd_azf3328_ctrl_inb(chip, IDX_IO_IRQSTATUS);
/* fast path out, to ease interrupt sharing */ if (!(status &
(IRQ_PLAYBACK|IRQ_RECORDING|IRQ_I2S_OUT
|IRQ_GAMEPORT|IRQ_MPU401|IRQ_TIMER)
)) return IRQ_NONE; /* must be interrupt for another device */
if (status & (IRQ_PLAYBACK|IRQ_RECORDING|IRQ_I2S_OUT))
snd_azf3328_pcm_interrupt(chip, chip->codecs, status);
if (status & IRQ_GAMEPORT)
snd_azf3328_gameport_interrupt(chip);
/* MPU401 has less critical IRQ requirements
* than timer and playback/recording, right? */ if (status & IRQ_MPU401) {
snd_mpu401_uart_interrupt(irq, chip->rmidi->private_data);
/* hmm, do we have to ack the IRQ here somehow?
* If so, then I don't know how yet... */
dev_dbg(chip->card->dev, "MPU401 IRQ\n");
} return IRQ_HANDLED;
}
/* 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.
*/ staticconststruct snd_pcm_hardware snd_azf3328_hardware =
{ /* FIXME!! Correct? */
.info = SNDRV_PCM_INFO_MMAP |
SNDRV_PCM_INFO_INTERLEAVED |
SNDRV_PCM_INFO_MMAP_VALID,
.formats = SNDRV_PCM_FMTBIT_S8 |
SNDRV_PCM_FMTBIT_U8 |
SNDRV_PCM_FMTBIT_S16_LE |
SNDRV_PCM_FMTBIT_U16_LE,
.rates = SNDRV_PCM_RATE_5512 |
SNDRV_PCM_RATE_8000_48000 |
SNDRV_PCM_RATE_KNOT,
.rate_min = AZF_FREQ_4000,
.rate_max = AZF_FREQ_66200,
.channels_min = 1,
.channels_max = 2,
.buffer_bytes_max = (64*1024),
.period_bytes_min = 1024,
.period_bytes_max = (32*1024), /* We simply have two DMA areas (instead of a list of descriptors such as other cards); I believe that this is a fixed hardware attribute and there isn't much driver magic to be done to expand it.
Thus indicate that we have at least and at most 2 periods. */
.periods_min = 2,
.periods_max = 2, /* FIXME: maybe that card actually has a FIFO? * Hmm, it seems newer revisions do have one, but we still don't know
* its size... */
.fifo_size = 0,
};
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.0.11Bemerkung:
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.