diff options
author | Andreas Mohr <andi@lisas.de> | 2009-07-05 13:55:46 +0200 |
---|---|---|
committer | Takashi Iwai <tiwai@suse.de> | 2009-07-06 08:24:47 +0200 |
commit | dfbf9511155d3584b8747c935216077f46eb9a4f (patch) | |
tree | 0591d571c38dd032eff9c2ca41ab1c34e19aa4f8 /sound/pci/azt3328.c | |
parent | 3eff8958308ed875a4e845d59a498288f8bbad77 (diff) |
ALSA: azt3328: large codec cleanup, add I2S port etc.
- fully separate codec I/O port handling, enabling the use of a single
function each for all codecs (playback, capture, I2S out)
- add a new separate pcm for I2S out port (UNTESTED, no I2S DAC
available yet)
- switch gameport to low frequency while idle, to try to reduce noise/power
- improve snd_azf3328_codec_setdmaa() calculation
- minor variable type cleanup (u16, bool etc.)
- add some doc updates (help those lost Windows users, debug help, ...)
Note that due to the large cleanup aspect of the codec I/O change,
I was able to fit everything including all improvements into the
same binary size!! (a measly 10 bytes more or so)
This should now be the almost last patch to this driver
(minus some possible kernel clocksource patch and x86_64 fixes or so).
I just felt like taking a break from the usual stuff and wanted to
get this driver's structure finished, and it's rather clean now...
Tested, working and checkpatch.pl:ed on 2.6.30-rc5,
applies cleanly to 2.6.30 proper.
Signed-off-by: Andreas Mohr <andi@lisas.de>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
Diffstat (limited to 'sound/pci/azt3328.c')
-rw-r--r-- | sound/pci/azt3328.c | 978 |
1 files changed, 524 insertions, 454 deletions
diff --git a/sound/pci/azt3328.c b/sound/pci/azt3328.c index f290bc56178..39dfdaa6a56 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 - 2008 by Andreas Mohr <andi AT lisas.de> + * Copyright (C) 2002, 2005 - 2009 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), @@ -10,6 +10,13 @@ * 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 Linux support for this card) + * * GPL LICENSE * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -71,10 +78,11 @@ * - 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 + * - 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. @@ -82,6 +90,7 @@ * 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 @@ -146,10 +155,15 @@ * 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). However this requires more I/O ports which I - * haven't figured out yet and which thus might not even exist... + * 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 @@ -185,6 +199,26 @@ MODULE_SUPPORTED_DEVICE("{{Aztech,AZF3328}}"); #define SUPPORT_GAMEPORT 1 #endif +/* === Debug settings === + Further diagnostic functionality than the settings below + does not need to be provided, since one can easily write a bash script + to dump the card's I/O ports (those listed in lspci -v -v): + function dump() + { + local descr=$1; local addr=$2; local count=$3 + + echo "${descr}: ${count} @ ${addr}:" + dd if=/dev/port skip=$[${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=$[${addr}] bs=1 2>/dev/null +*/ + #define DEBUG_MISC 0 #define DEBUG_CALLS 0 #define DEBUG_MIXER 0 @@ -250,22 +284,23 @@ 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_azf3328_codec_data { + unsigned long io_base; struct snd_pcm_substream *substream; - int enabled; - int running; - unsigned long portbase; + bool running; + const char *name; }; -enum snd_azf3328_stream_index { - AZF_PLAYBACK = 0, - AZF_CAPTURE = 1, +enum snd_azf3328_codec_type { + AZF_CODEC_PLAYBACK = 0, + AZF_CODEC_CAPTURE = 1, + AZF_CODEC_I2S_OUT = 2, }; struct snd_azf3328 { /* often-used fields towards beginning, then grouped */ - unsigned long codec_io; /* usually 0xb000, size 128 */ + unsigned long ctrl_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 */ @@ -275,15 +310,17 @@ struct snd_azf3328 { struct snd_timer *timer; - struct snd_pcm *pcm; - struct snd_azf3328_audio_stream audio_stream[2]; + struct snd_pcm *pcm[3]; + + /* playback, recording and I2S out codecs */ + struct snd_azf3328_codec_data codecs[3]; struct snd_card *card; struct snd_rawmidi *rmidi; #ifdef SUPPORT_GAMEPORT struct gameport *gameport; - int axes[4]; + u16 axes[4]; #endif struct pci_dev *pci; @@ -293,12 +330,12 @@ struct snd_azf3328 { * 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_codec_6AH; + u16 shadow_reg_ctrl_6AH; #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_ctrl[AZF_IO_SIZE_CTRL_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]; @@ -316,7 +353,7 @@ MODULE_DEVICE_TABLE(pci, snd_azf3328_ids); static int -snd_azf3328_io_reg_setb(unsigned reg, u8 mask, int do_set) +snd_azf3328_io_reg_setb(unsigned reg, u8 mask, bool do_set) { u8 prev = inb(reg), new; @@ -331,39 +368,72 @@ snd_azf3328_io_reg_setb(unsigned reg, u8 mask, int do_set) } static inline void -snd_azf3328_codec_outb(const struct snd_azf3328 *chip, unsigned reg, u8 value) +snd_azf3328_codec_outb(const struct snd_azf3328_codec_data *codec, + unsigned reg, + u8 value +) { - outb(value, chip->codec_io + reg); + outb(value, codec->io_base + reg); } static inline u8 -snd_azf3328_codec_inb(const struct snd_azf3328 *chip, unsigned reg) +snd_azf3328_codec_inb(const struct snd_azf3328_codec_data *codec, unsigned reg) { - return inb(chip->codec_io + reg); + return inb(codec->io_base + reg); } static inline void -snd_azf3328_codec_outw(const struct snd_azf3328 *chip, unsigned reg, u16 value) +snd_azf3328_codec_outw(const struct snd_azf3328_codec_data *codec, + unsigned reg, + u16 value +) { - outw(value, chip->codec_io + reg); + outw(value, codec->io_base + reg); } static inline u16 -snd_azf3328_codec_inw(const struct snd_azf3328 *chip, unsigned reg) +snd_azf3328_codec_inw(const struct snd_azf3328_codec_data *codec, unsigned reg) { - return inw(chip->codec_io + reg); + return inw(codec->io_base + reg); } static inline void -snd_azf3328_codec_outl(const struct snd_azf3328 *chip, unsigned reg, u32 value) +snd_azf3328_codec_outl(const struct snd_azf3328_codec_data *codec, + unsigned reg, + u32 value +) { - outl(value, chip->codec_io + reg); + outl(value, codec->io_base + reg); } static inline u32 -snd_azf3328_codec_inl(const struct snd_azf3328 *chip, unsigned reg) +snd_azf3328_codec_inl(const struct snd_azf3328_codec_data *codec, unsigned reg) +{ + return inl(codec->io_base + reg); +} + +static inline void +snd_azf3328_ctrl_outb(const struct snd_azf3328 *chip, unsigned reg, u8 value) { - return inl(chip->codec_io + reg); + outb(value, chip->ctrl_io + reg); +} + +static inline u8 +snd_azf3328_ctrl_inb(const struct snd_azf3328 *chip, unsigned reg) +{ + return inb(chip->ctrl_io + reg); +} + +static inline void +snd_azf3328_ctrl_outw(const struct snd_azf3328 *chip, unsigned reg, u16 value) +{ + outw(value, chip->ctrl_io + reg); +} + +static inline void +snd_azf3328_ctrl_outl(const struct snd_azf3328 *chip, unsigned reg, u32 value) +{ + outl(value, chip->ctrl_io + reg); } static inline void @@ -404,13 +474,13 @@ snd_azf3328_mixer_inw(const struct snd_azf3328 *chip, unsigned reg) #define AZF_MUTE_BIT 0x80 -static int +static bool snd_azf3328_mixer_set_mute(const struct snd_azf3328 *chip, - unsigned reg, int do_mute + unsigned reg, bool do_mute ) { unsigned long portbase = chip->mixer_io + reg + 1; - int updated; + bool updated; /* the mute bit is on the *second* (i.e. right) register of a * left/right channel setting */ @@ -569,7 +639,7 @@ snd_azf3328_get_mixer(struct snd_kcontrol *kcontrol, { struct snd_azf3328 *chip = snd_kcontrol_chip(kcontrol); struct azf3328_mixer_reg reg; - unsigned int oreg, val; + u16 oreg, val; snd_azf3328_dbgcallenter(); snd_azf3328_mixer_reg_decode(®, kcontrol->private_value); @@ -600,7 +670,7 @@ snd_azf3328_put_mixer(struct snd_kcontrol *kcontrol, { struct snd_azf3328 *chip = snd_kcontrol_chip(kcontrol); struct azf3328_mixer_reg reg; - unsigned int oreg, nreg, val; + u16 oreg, nreg, val; snd_azf3328_dbgcallenter(); snd_azf3328_mixer_reg_decode(®, kcontrol->private_value); @@ -709,7 +779,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; + u16 oreg, nreg, val; snd_azf3328_mixer_reg_decode(®, kcontrol->private_value); oreg = snd_azf3328_mixer_inw(chip, reg.reg); @@ -867,14 +937,15 @@ snd_azf3328_hw_free(struct snd_pcm_substream *substream) static void snd_azf3328_codec_setfmt(struct snd_azf3328 *chip, - unsigned reg, + enum snd_azf3328_codec_type codec_type, enum azf_freq_t bitrate, unsigned int format_width, unsigned int channels ) { - u16 val = 0xff00; unsigned long flags; + const struct snd_azf3328_codec_data *codec = &chip->codecs[codec_type]; + u16 val = 0xff00; snd_azf3328_dbgcallenter(); switch (bitrate) { @@ -917,7 +988,7 @@ snd_azf3328_codec_setfmt(struct snd_azf3328 *chip, spin_lock_irqsave(&chip->reg_lock, flags); /* set bitrate/format */ - snd_azf3328_codec_outw(chip, reg, val); + 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 @@ -926,11 +997,11 @@ snd_azf3328_codec_setfmt(struct snd_azf3328 *chip, * (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? */ - if (reg == IDX_IO_PLAY_SOUNDFORMAT) /* only do it for playback */ - snd_azf3328_codec_outw(chip, IDX_IO_PLAY_FLAGS, - snd_azf3328_codec_inw(chip, IDX_IO_PLAY_FLAGS) | - DMA_PLAY_SOMETHING1 | - DMA_PLAY_SOMETHING2 | + if (codec_type == AZF_CODEC_PLAYBACK) /* only do it for playback */ + 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 @@ -942,134 +1013,132 @@ snd_azf3328_codec_setfmt(struct snd_azf3328 *chip, static inline void snd_azf3328_codec_setfmt_lowpower(struct snd_azf3328 *chip, - unsigned reg + enum snd_azf3328_codec_type codec_type ) { /* 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); + snd_azf3328_codec_setfmt(chip, codec_type, AZF_FREQ_4000, 8, 1); } static void -snd_azf3328_codec_reg_6AH_update(struct snd_azf3328 *chip, +snd_azf3328_ctrl_reg_6AH_update(struct snd_azf3328 *chip, unsigned bitmask, - int enable + bool enable ) { if (enable) - chip->shadow_reg_codec_6AH &= ~bitmask; + chip->shadow_reg_ctrl_6AH &= ~bitmask; else - chip->shadow_reg_codec_6AH |= bitmask; + chip->shadow_reg_ctrl_6AH |= bitmask; snd_azf3328_dbgplay("6AH_update mask 0x%04x enable %d: val 0x%04x\n", - bitmask, enable, chip->shadow_reg_codec_6AH); - snd_azf3328_codec_outw(chip, IDX_IO_6AH, chip->shadow_reg_codec_6AH); + bitmask, enable, chip->shadow_reg_ctrl_6AH); + snd_azf3328_ctrl_outw(chip, IDX_IO_6AH, chip->shadow_reg_ctrl_6AH); } static inline void -snd_azf3328_codec_enable(struct snd_azf3328 *chip, int enable) +snd_azf3328_ctrl_enable_codecs(struct snd_azf3328 *chip, bool enable) { snd_azf3328_dbgplay("codec_enable %d\n", enable); /* no idea what exactly is being done here, but I strongly assume it's * PM related */ - snd_azf3328_codec_reg_6AH_update( + snd_azf3328_ctrl_reg_6AH_update( chip, 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 +snd_azf3328_ctrl_codec_activity(struct snd_azf3328 *chip, + enum snd_azf3328_codec_type codec_type, + bool enable ) { - int need_change = (chip->audio_stream[stream_type].running != enable); + struct snd_azf3328_codec_data *codec = &chip->codecs[codec_type]; + bool need_change = (codec->running != enable); snd_azf3328_dbgplay( - "codec_activity: type %d, enable %d, need_change %d\n", - stream_type, enable, need_change + "codec_activity: %s codec, enable %d, need_change %d\n", + codec->name, 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); + static const struct { + enum snd_azf3328_codec_type other1; + enum snd_azf3328_codec_type other2; + } peer_codecs[3] = + { { AZF_CODEC_CAPTURE, AZF_CODEC_I2S_OUT }, + { AZF_CODEC_PLAYBACK, AZF_CODEC_I2S_OUT }, + { AZF_CODEC_PLAYBACK, AZF_CODEC_CAPTURE } }; + bool call_function; + + 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! */ + if ((!chip->codecs[peer_codecs[codec_type].other1] + .running) + && (!chip->codecs[peer_codecs[codec_type].other2] + .running)) + call_function = 1; + } + 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( chip, - chip->audio_stream[stream_type].portbase - + IDX_IO_PLAY_SOUNDFORMAT + codec_type ); } - chip->audio_stream[stream_type].running = enable; + codec->running = enable; } static void -snd_azf3328_setdmaa(struct snd_azf3328 *chip, - long unsigned int addr, - unsigned int count, - unsigned int size, - enum snd_azf3328_stream_index stream_type +snd_azf3328_codec_setdmaa(struct snd_azf3328 *chip, + enum snd_azf3328_codec_type codec_type, + unsigned long addr, + unsigned int count, + unsigned int size ) { + const struct snd_azf3328_codec_data *codec = &chip->codecs[codec_type]; snd_azf3328_dbgcallenter(); - if (!chip->audio_stream[stream_type].running) { - /* AZF3328 uses a two buffer pointer DMA playback approach */ + if (!codec->running) { + /* AZF3328 uses a two buffer pointer DMA transfer approach */ - unsigned long flags, portbase, addr_area2; + unsigned long flags; /* width 32bit (prevent overflow): */ - unsigned long count_areas, count_tmp; + u32 addr_area2, count_areas, lengths; - portbase = chip->audio_stream[stream_type].portbase; count_areas = size/2; addr_area2 = addr+count_areas; count_areas--; /* max. index */ snd_azf3328_dbgplay("set DMA: buf1 %08lx[%lu], buf2 %08lx[%lu]\n", addr, count_areas, addr_area2, count_areas); /* build combined I/O buffer length word */ - count_tmp = count_areas; - count_areas |= (count_tmp << 16); + lengths = (count_areas << 16) | (count_areas); spin_lock_irqsave(&chip->reg_lock, flags); - outl(addr, portbase + IDX_IO_PLAY_DMA_START_1); - outl(addr_area2, portbase + IDX_IO_PLAY_DMA_START_2); - outl(count_areas, portbase + IDX_IO_PLAY_DMA_LEN_1); + snd_azf3328_codec_outl(codec, IDX_IO_CODEC_DMA_START_1, addr); + snd_azf3328_codec_outl(codec, IDX_IO_CODEC_DMA_START_2, + addr_area2); + snd_azf3328_codec_outl(codec, IDX_IO_CODEC_DMA_LENGTHS, + lengths); spin_unlock_irqrestore(&chip->reg_lock, flags); } snd_azf3328_dbgcallleave(); } static int -snd_azf3328_playback_prepare(struct snd_pcm_substream *substream) -{ -#if 0 - struct snd_azf3328 *chip = snd_pcm_substream_chip(substream); - struct snd_pcm_runtime *runtime = substream->runtime; - unsigned int size = snd_pcm_lib_buffer_bytes(substream); - unsigned int count = snd_pcm_lib_period_bytes(substream); -#endif - - snd_azf3328_dbgcallenter(); -#if 0 - 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, AZF_PLAYBACK); -#endif - snd_azf3328_dbgcallleave(); - return 0; -} - -static int -snd_azf3328_capture_prepare(struct snd_pcm_substream *substream) +snd_azf3328_codec_prepare(struct snd_pcm_substream *substream) { #if 0 struct snd_azf3328 *chip = snd_pcm_substream_chip(substream); @@ -1080,135 +1149,161 @@ snd_azf3328_capture_prepare(struct snd_pcm_substream *substream) snd_azf3328_dbgcallenter(); #if 0 - snd_azf3328_codec_setfmt(chip, IDX_IO_REC_SOUNDFORMAT, + snd_azf3328_codec_setfmt(chip, AZF_CODEC_..., runtime->rate, snd_pcm_format_width(runtime->format), runtime->channels); - snd_azf3328_setdmaa(chip, runtime->dma_addr, count, size, AZF_CAPTURE); + snd_azf3328_codec_setdmaa(chip, AZF_CODEC_..., + runtime->dma_addr, count, size); #endif snd_azf3328_dbgcallleave(); return 0; } static int -snd_azf3328_playback_trigger(struct snd_pcm_substream *substream, int cmd) +snd_azf3328_codec_trigger(enum snd_azf3328_codec_type codec_type, + struct snd_pcm_substream *substream, int cmd) { struct snd_azf3328 *chip = snd_pcm_substream_chip(substream); + const struct snd_azf3328_codec_data *codec = &chip->codecs[codec_type]; struct snd_pcm_runtime *runtime = substream->runtime; int result = 0; - unsigned int status1; - int previously_muted; + u16 flags1; + bool previously_muted = 0; + bool is_playback_codec = (AZF_CODEC_PLAYBACK == codec_type); - snd_azf3328_dbgcalls("snd_azf3328_playback_trigger cmd %d\n", cmd); + snd_azf3328_dbgcalls("snd_azf3328_codec_trigger cmd %d\n", cmd); switch (cmd) { case SNDRV_PCM_TRIGGER_START: - snd_azf3328_dbgplay("START PLAYBACK\n"); - - /* mute WaveOut (avoid clicking during setup) */ - previously_muted = - snd_azf3328_mixer_set_mute(chip, IDX_MIXER_WAVEOUT, 1); + snd_azf3328_dbgplay("START %s\n", codec->name); + + if (is_playback_codec) { + /* mute WaveOut (avoid clicking during setup) */ + previously_muted = + snd_azf3328_mixer_set_mute( + chip, IDX_MIXER_WAVEOUT, 1 + ); + } - snd_azf3328_codec_setfmt(chip, IDX_IO_PLAY_SOUNDFORMAT, + snd_azf3328_codec_setfmt(chip, codec_type, runtime->rate, snd_pcm_format_width(runtime->format), runtime->channels); spin_lock(&chip->reg_lock); /* first, remember current value: */ - status1 = snd_azf3328_codec_inw(chip, IDX_IO_PLAY_FLAGS); + flags1 = snd_azf3328_codec_inw(codec, IDX_IO_CODEC_DMA_FLAGS); - /* stop playback */ - status1 &= ~DMA_RESUME; - snd_azf3328_codec_outw(chip, IDX_IO_PLAY_FLAGS, status1); + /* stop transfer */ + flags1 &= ~DMA_RESUME; + snd_azf3328_codec_outw(codec, IDX_IO_CODEC_DMA_FLAGS, flags1); /* FIXME: clear interrupts or what??? */ - snd_azf3328_codec_outw(chip, IDX_IO_PLAY_IRQTYPE, 0xffff); + snd_azf3328_codec_outw(codec, IDX_IO_CODEC_IRQTYPE, 0xffff); spin_unlock(&chip->reg_lock); - snd_azf3328_setdmaa(chip, runtime->dma_addr, + snd_azf3328_codec_setdmaa(chip, codec_type, runtime->dma_addr, snd_pcm_lib_period_bytes(substream), - snd_pcm_lib_buffer_bytes(substream), - AZF_PLAYBACK); + snd_pcm_lib_buffer_bytes(substream) + ); spin_lock(&chip->reg_lock); #ifdef WIN9X /* FIXME: enable playback/recording??? */ - status1 |= DMA_PLAY_SOMETHING1 | DMA_PLAY_SOMETHING2; - snd_azf3328_codec_outw(chip, IDX_IO_PLAY_FLAGS, status1); + flags1 |= DMA_RUN_SOMETHING1 | DMA_RUN_SOMETHING2; + snd_azf3328_codec_outw(codec, IDX_IO_CODEC_DMA_FLAGS, flags1); - /* start playback again */ + /* start transfer again */ /* FIXME: what is this value (0x0010)??? */ - status1 |= DMA_RESUME | DMA_EPILOGUE_SOMETHING; - snd_azf3328_codec_outw(chip, IDX_IO_PLAY_FLAGS, status1); + flags1 |= DMA_RESUME | DMA_EPILOGUE_SOMETHING; + snd_azf3328_codec_outw(codec, IDX_IO_CODEC_DMA_FLAGS, flags1); #else /* NT4 */ - snd_azf3328_codec_outw(chip, IDX_IO_PLAY_FLAGS, + snd_azf3328_codec_outw(codec, IDX_IO_CODEC_DMA_FLAGS, 0x0000); - snd_azf3328_codec_outw(chip, IDX_IO_PLAY_FLAGS, - DMA_PLAY_SOMETHING1); - snd_azf3328_codec_outw(chip, IDX_IO_PLAY_FLAGS, - DMA_PLAY_SOMETHING1 | - DMA_PLAY_SOMETHING2); - snd_azf3328_codec_outw(chip, IDX_IO_PLAY_FLAGS, + snd_azf3328_codec_outw(codec, IDX_IO_CODEC_DMA_FLAGS, + DMA_RUN_SOMETHING1); + snd_azf3328_codec_outw(codec, IDX_IO_CODEC_DMA_FLAGS, + DMA_RUN_SOMETHING1 | + DMA_RUN_SOMETHING2); + snd_azf3328_codec_outw(codec, IDX_IO_CODEC_DMA_FLAGS, DMA_RESUME | SOMETHING_ALMOST_ALWAYS_SET | DMA_EPILOGUE_SOMETHING | DMA_SOMETHING_ELSE); #endif spin_unlock(&chip->reg_lock); - snd_azf3328_codec_activity(chip, AZF_PLAYBACK, 1); - - /* now unmute WaveOut */ - if (!previously_muted) - snd_azf3328_mixer_set_mute(chip, IDX_MIXER_WAVEOUT, 0); + snd_azf3328_ctrl_codec_activity(chip, codec_type, 1); + + if (is_playback_codec) { + /* now unmute WaveOut */ + if (!previously_muted) + snd_azf3328_mixer_set_mute( + chip, IDX_MIXER_WAVEOUT, 0 + ); + } - snd_azf3328_dbgplay("STARTED PLAYBACK\n"); + snd_azf3328_dbgplay("STARTED %s\n", codec->name); break; case SNDRV_PCM_TRIGGER_RESUME: - snd_azf3328_dbgplay("RESUME PLAYBACK\n"); - /* resume playback if we were active */ + snd_azf3328_dbgplay("RESUME %s\n", codec->name); + /* resume codec if we were active */ 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); + if (codec->running) + snd_azf3328_codec_outw(codec, IDX_IO_CODEC_DMA_FLAGS, + snd_azf3328_codec_inw( + codec, IDX_IO_CODEC_DMA_FLAGS + ) | DMA_RESUME + ); spin_unlock(&chip->reg_lock); break; case SNDRV_PCM_TRIGGER_STOP: - snd_azf3328_dbgplay("STOP PLAYBACK\n"); - - /* mute WaveOut (avoid clicking during setup) */ - previously_muted = - snd_azf3328_mixer_set_mute(chip, IDX_MIXER_WAVEOUT, 1); + snd_azf3328_dbgplay("STOP %s\n", codec->name); + + if (is_playback_codec) { + /* mute WaveOut (avoid clicking during setup) */ + previously_muted = + snd_azf3328_mixer_set_mute( + chip, IDX_MIXER_WAVEOUT, 1 + ); + } spin_lock(&chip->reg_lock); /* first, remember current value: */ - status1 = snd_azf3328_codec_inw(chip, IDX_IO_PLAY_FLAGS); + flags1 = snd_azf3328_codec_inw(codec, IDX_IO_CODEC_DMA_FLAGS); - /* stop playback */ - status1 &= ~DMA_RESUME; - snd_azf3328_codec_outw(chip, IDX_IO_PLAY_FLAGS, status1); + /* 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... */ - status1 |= DMA_PLAY_SOMETHING1; - snd_azf3328_codec_outw(chip, IDX_IO_PLAY_FLAGS, status1); + flags1 |= DMA_RUN_SOMETHING1; + snd_azf3328_codec_outw(codec, IDX_IO_CODEC_DMA_FLAGS, flags1); - status1 &= ~DMA_PLAY_SOMETHING1; - snd_azf3328_codec_outw(chip, IDX_IO_PLAY_FLAGS, status1); + flags1 &= ~DMA_RUN_SOMETHING1; + snd_azf3328_codec_outw(codec, IDX_IO_CODEC_DMA_FLAGS, flags1); spin_unlock(&chip->reg_lock); - snd_azf3328_codec_activity(chip, AZF_PLAYBACK, 0); - - /* now unmute WaveOut */ - if (!previously_muted) - snd_azf3328_mixer_set_mute(chip, IDX_MIXER_WAVEOUT, 0); + snd_azf3328_ctrl_codec_activity(chip, codec_type, 0); + + if (is_playback_codec) { + /* now unmute WaveOut */ + if (!previously_muted) + snd_azf3328_mixer_set_mute( + chip, IDX_MIXER_WAVEOUT, 0 + ); + } - snd_azf3328_dbgplay("STOPPED PLAYBACK\n"); + snd_azf3328_dbgplay("STOPPED %s\n", codec->name); break; case SNDRV_PCM_TRIGGER_SUSPEND: - snd_azf3328_dbgplay("SUSPEND PLAYBACK\n"); - /* make sure playback is stopped */ - snd_azf3328_codec_outw(chip, IDX_IO_PLAY_FLAGS, - snd_azf3328_codec_inw(chip, IDX_IO_PLAY_FLAGS) & ~DMA_RESUME); + snd_azf3328_dbgplay("SUSPEND %s\n", codec->name); + /* make sure codec is stopped */ + snd_azf3328_codec_outw(codec, IDX_IO_CODEC_DMA_FLAGS, + snd_azf3328_codec_inw( + codec, IDX_IO_CODEC_DMA_FLAGS + ) & ~DMA_RESUME + ); break; case SNDRV_PCM_TRIGGER_PAUSE_PUSH: snd_printk(KERN_ERR "FIXME: SNDRV_PCM_TRIGGER_PAUSE_PUSH NIY!\n"); @@ -1225,172 +1320,74 @@ snd_azf3328_playback_trigger(struct snd_pcm_substream *substream, int cmd) return result; } -/* this is just analogous to playback; I'm not quite sure whether recording - * should actually be triggered like that */ static int -snd_azf3328_capture_trigger(struct snd_pcm_substream *substream, int cmd) +snd_azf3328_codec_playback_trigger(struct snd_pcm_substream *substream, int cmd) { - struct snd_azf3328 *chip = snd_pcm_substream_chip(substream); - struct snd_pcm_runtime *runtime = substream->runtime; - int result = 0; - unsigned int status1; - - snd_azf3328_dbgcalls("snd_azf3328_capture_trigger cmd %d\n", cmd); - - switch (cmd) { - case SNDRV_PCM_TRIGGER_START: - - snd_azf3328_dbgplay("START CAPTURE\n"); - - snd_azf3328_codec_setfmt(chip, IDX_IO_REC_SOUNDFORMAT, - runtime->rate, - snd_pcm_format_width(runtime->format), - runtime->channels); - - spin_lock(&chip->reg_lock); - /* 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); - - snd_azf3328_setdmaa(chip, runtime->dma_addr, - snd_pcm_lib_period_bytes(substream), - snd_pcm_lib_buffer_bytes(substream), - AZF_CAPTURE); - - spin_lock(&chip->reg_lock); -#ifdef WIN9X - /* FIXME: enable playback/recording??? */ - status1 |= DMA_PLAY_SOMETHING1 | DMA_PLAY_SOMETHING2; - snd_azf3328_codec_outw(chip, IDX_IO_REC_FLAGS, status1); - - /* start capture again */ - /* FIXME: what is this value (0x0010)??? */ - status1 |= DMA_RESUME | DMA_EPILOGUE_SOMETHING; - snd_azf3328_codec_outw(chip, IDX_IO_REC_FLAGS, status1); -#else - snd_azf3328_codec_outw(chip, IDX_IO_REC_FLAGS, - 0x0000); - snd_azf3328_codec_outw(chip, IDX_IO_REC_FLAGS, - DMA_PLAY_SOMETHING1); - snd_azf3328_codec_outw(chip, IDX_IO_REC_FLAGS, - DMA_PLAY_SOMETHING1 | - DMA_PLAY_SOMETHING2); - snd_azf3328_codec_outw(chip, IDX_IO_REC_FLAGS, - DMA_RESUME | - SOMETHING_ALMOST_ALWAYS_SET | - DMA_EPILOGUE_SOMETHING | - DMA_SOMETHING_ELSE); -#endif - spin_unlock(&chip->reg_lock); - snd_azf3328_codec_activity(chip, AZF_CAPTURE, 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 */ - 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); - /* 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); - - status1 |= DMA_PLAY_SOMETHING1; - snd_azf3328_codec_outw(chip, IDX_IO_REC_FLAGS, status1); - - status1 &= ~DMA_PLAY_SOMETHING1; - snd_azf3328_codec_outw(chip, IDX_IO_REC_FLAGS, status1); - spin_unlock(&chip->reg_lock); - snd_azf3328_codec_activity(chip, AZF_CAPTURE, 0); + return snd_azf3328_codec_trigger(AZF_CODEC_PLAYBACK, substream, cmd); +} - snd_azf3328_dbgplay("STOPPED CAPTURE\n"); - break; - case SNDRV_PCM_TRIGGER_SUSPEND: - snd_azf3328_dbgplay("SUSPEND CAPTURE\n"); - /* make sure recording is stopped */ - snd_azf3328_codec_outw(chip, IDX_IO_REC_FLAGS, - snd_azf3328_codec_inw(chip, IDX_IO_REC_FLAGS) & ~DMA_RESUME); - break; - case SNDRV_PCM_TRIGGER_PAUSE_PUSH: - snd_printk(KERN_ERR "FIXME: SNDRV_PCM_TRIGGER_PAUSE_PUSH NIY!\n"); - break; - case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: - snd_printk(KERN_ERR "FIXME: SNDRV_PCM_TRIGGER_PAUSE_RELEASE NIY!\n"); - break; - default: - printk(KERN_ERR "FIXME: unknown trigger mode!\n"); - return -EINVAL; - } +static int +snd_azf3328_codec_capture_trigger(struct snd_pcm_substream *substream, int cmd) +{ + return snd_azf3328_codec_trigger(AZF_CODEC_CAPTURE, substream, cmd); +} - snd_azf3328_dbgcallleave(); - return result; +static int +snd_azf3328_codec_i2s_out_trigger(struct snd_pcm_substream *substream, int cmd) +{ + return snd_azf3328_codec_trigger(AZF_CODEC_I2S_OUT, substream, cmd); } static snd_pcm_uframes_t -snd_azf3328_playback_pointer(struct snd_pcm_substream *substream) +snd_azf3328_codec_pointer(struct snd_pcm_substream *substream, + enum snd_azf3328_codec_type codec_type +) { - struct snd_azf3328 *chip = snd_pcm_substream_chip(substream); + const struct snd_azf3328 *chip = snd_pcm_substream_chip(substream); + const struct snd_azf3328_codec_data *codec = &chip->codecs[codec_type]; unsigned long bufptr, result; snd_pcm_uframes_t frmres; #ifdef QUERY_HARDWARE - bufptr = snd_azf3328_codec_inl(chip, IDX_IO_PLAY_DMA_START_1); + bufptr = snd_azf3328_codec_inl(codec, IDX_IO_CODEC_DMA_START_1); #else bufptr = substream->runtime->dma_addr; #endif - result = snd_azf3328_codec_inl(chip, IDX_IO_PLAY_DMA_CURRPOS); + result = snd_azf3328_codec_inl(codec, IDX_IO_CODEC_DMA_CURRPOS); /* calculate offset */ result -= bufptr; frmres = bytes_to_frames( substream->runtime, result); - snd_azf3328_dbgplay("PLAY @ 0x%8lx, frames %8ld\n", result, frmres); + snd_azf3328_dbgplay("%s @ 0x%8lx, frames %8ld\n", + codec->name, result, frmres); return frmres; } static snd_pcm_uframes_t -snd_azf3328_capture_pointer(struct snd_pcm_substream *substream) +snd_azf3328_codec_playback_pointer(struct snd_pcm_substream *substream) { - struct snd_azf3328 *chip = snd_pcm_substream_chip(substream); - unsigned long bufptr, result; - snd_pcm_uframes_t frmres; + return snd_azf3328_codec_pointer(substream, AZF_CODEC_PLAYBACK); +} -#ifdef QUERY_HARDWARE - bufptr = snd_azf3328_codec_inl(chip, IDX_IO_REC_DMA_START_1); -#else - bufptr = substream->runtime->dma_addr; -#endif - result = snd_azf3328_codec_inl(chip, IDX_IO_REC_DMA_CURRPOS); +static snd_pcm_uframes_t +snd_azf3328_codec_capture_pointer(struct snd_pcm_substream *substream) +{ + return snd_azf3328_codec_pointer(substream, AZF_CODEC_CAPTURE); +} - /* calculate offset */ - result -= bufptr; - frmres = bytes_to_frames( substream->runtime, result); - snd_azf3328_dbgplay("REC @ 0x%8lx, frames %8ld\n", result, frmres); - return frmres; +static snd_pcm_uframes_t +snd_azf3328_codec_i2s_out_pointer(struct snd_pcm_substream *substream) +{ + return snd_azf3328_codec_pointer(substream, AZF_CODEC_I2S_OUT); } /******************************************************************/ #ifdef SUPPORT_GAMEPORT static inline void -snd_azf3328_gameport_irq_enable(struct snd_azf3328 *chip, int enable) +snd_azf3328_gameport_irq_enable(struct snd_azf3328 *chip, + bool enable +) { snd_azf3328_io_reg_setb( chip->game_io+IDX_GAME_HWCONFIG, @@ -1400,7 +1397,9 @@ snd_azf3328_gameport_irq_enable(struct snd_azf3328 *chip, int enable) } static inline void -snd_azf3328_gameport_legacy_address_enable(struct snd_azf3328 *chip, int enable) +snd_azf3328_gameport_legacy_address_enable(struct snd_azf3328 *chip, + bool enable +) { snd_azf3328_io_reg_setb( chip->game_io+IDX_GAME_HWCONFIG, @@ -1409,10 +1408,27 @@ snd_azf3328_gameport_legacy_address_enable(struct snd_azf3328 *chip, int enable) ); } +static void +snd_azf3328_gameport_set_counter_frequency(struct snd_azf3328 *chip, + unsigned int freq_cfg +) +{ + snd_azf3328_io_reg_setb( + chip->game_io+IDX_GAME_HWCONFIG, + 0x02, + (freq_cfg & 1) != 0 + ); + snd_azf3328_io_reg_setb( + chip->game_io+IDX_GAME_HWCONFIG, + 0x04, + (freq_cfg & 2) != 0 + ); +} + static inline void -snd_azf3328_gameport_axis_circuit_enable(struct snd_azf3328 *chip, int enable) +snd_azf3328_gameport_axis_circuit_enable(struct snd_azf3328 *chip, bool enable) { - snd_azf3328_codec_reg_6AH_update( + snd_azf3328_ctrl_reg_6AH_update( chip, IO_6A_SOMETHING2_GAMEPORT, enable ); } @@ -1447,6 +1463,8 @@ snd_azf3328_gameport_open(struct gameport *gameport, int mode) break; } + snd_azf3328_gameport_set_counter_frequency(chip, + GAME_HWCFG_ADC_COUNTER_FREQ_STD); snd_azf3328_gameport_axis_circuit_enable(chip, (res == 0)); return res; @@ -1458,6 +1476,8 @@ snd_azf3328_gameport_close(struct gameport *gameport) struct snd_azf3328 *chip = gameport_get_port_data(gameport); snd_azf3328_dbggame("gameport_close\n"); + snd_azf3328_gameport_set_counter_frequency(chip, + GAME_HWCFG_ADC_COUNTER_FREQ_1_200); snd_azf3328_gameport_axis_circuit_enable(chip, 0); } @@ -1491,7 +1511,7 @@ snd_azf3328_gameport_cooked_read(struct gameport *gameport, val = snd_azf3328_game_inb(chip, IDX_GAME_AXES_CONFIG); if (val & GAME_AXES_SAMPLING_READY) { - for (i = 0; i < 4; ++i) { + 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); @@ -1514,7 +1534,7 @@ snd_azf3328_gameport_cooked_read(struct gameport *gameport, snd_azf3328_game_outw(chip, IDX_GAME_AXIS_VALUE, 0xffff); spin_unlock_irqrestore(&chip->reg_lock, flags); - for (i = 0; i < 4; i++) { + for (i = 0; i < ARRAY_SIZE(chip->axes); i++) { axes[i] = chip->axes[i]; if (axes[i] == 0xffff) axes[i] = -1; @@ -1552,6 +1572,8 @@ snd_azf3328_gameport(struct snd_azf3328 *chip, int dev) /* DISABLE legacy address: we don't need it! */ snd_azf3328_gameport_legacy_address_enable(chip, 0); + snd_azf3328_gameport_set_counter_frequency(chip, + GAME_HWCFG_ADC_COUNTER_FREQ_1_200); snd_azf3328_gameport_axis_circuit_enable(chip, 0); gameport_register_port(chip->gameport); @@ -1591,29 +1613,69 @@ snd_azf3328_irq_log_unknown_type(u8 which) ); } +static inline void +snd_azf3328_codec_interrupt(struct snd_azf3328 *chip, u8 status) +{ + u8 which; + enum snd_azf3328_codec_type codec_type; + const struct snd_azf3328_codec_data *codec; + + for (codec_type = AZF_CODEC_PLAYBACK; + codec_type <= AZF_CODEC_I2S_OUT; + ++codec_type) { + + /* skip codec if there's no interrupt for it */ + if (!(status & (1 << codec_type))) + continue; + + codec = &chip->codecs[codec_type]; + + spin_lock(&chip->reg_lock); + which = snd_azf3328_codec_inb(codec, IDX_IO_CODEC_IRQTYPE); + /* ack all IRQ types immediately */ + snd_azf3328_codec_outb(codec, IDX_IO_CODEC_IRQTYPE, which); + spin_unlock(&chip->reg_lock); + + if ((chip->pcm[codec_type]) + && (chip->codecs[codec_type].substream)) { + snd_pcm_period_elapsed( + chip->codecs[codec_type].substream + ); + snd_azf3328_dbgplay("%s period done (#%x), @ %x\n", + codec->name, + which, + snd_azf3328_codec_inl( + codec, IDX_IO_CODEC_DMA_CURRPOS + ) + ); + } else + printk(KERN_WARNING "azt3328: irq handler problem!\n"); + if (which & IRQ_SOMETHING) + snd_azf3328_irq_log_unknown_type(which); + } +} + static irqreturn_t snd_azf3328_interrupt(int irq, void *dev_id) { struct snd_azf3328 *chip = dev_id; - u8 status, which; + u8 status; #if DEBUG_PLAY_REC static unsigned long irq_count; #endif - status = snd_azf3328_codec_inb(chip, IDX_IO_IRQSTATUS); + status = snd_azf3328_ctrl_inb(chip, IDX_IO_IRQSTATUS); /* fast path out, to ease interrupt sharing */ if (!(status & - (IRQ_PLAYBACK|IRQ_RECORDING|IRQ_GAMEPORT|IRQ_MPU401|IRQ_TIMER) + (IRQ_PLAYBACK|IRQ_RECORDING|IRQ_I2S_OUT + |IRQ_GAMEPORT|IRQ_MPU401|IRQ_TIMER) )) return IRQ_NONE; /* must be interrupt for another device */ snd_azf3328_dbgplay( - "irq_count %ld! IDX_IO_PLAY_FLAGS %04x, " - "IDX_IO_PLAY_IRQTYPE %04x, IDX_IO_IRQSTATUS %04x\n", + "irq_count %ld! IDX_IO_IRQSTATUS %04x\n", irq_count++ /* debug-only */, - snd_azf3328_codec_inw(chip, IDX_IO_PLAY_FLAGS), - snd_azf3328_codec_inw(chip, IDX_IO_PLAY_IRQTYPE), status ); @@ -1626,63 +1688,24 @@ snd_azf3328_interrupt(int irq, void *dev_id) snd_timer_interrupt(chip->timer, chip->timer->sticks); /* ACK timer */ spin_lock(&chip->reg_lock); - snd_azf3328_codec_outb(chip, IDX_IO_TIMER_VALUE + 3, 0x07); + snd_azf3328_ctrl_outb(chip, IDX_IO_TIMER_VALUE + 3, 0x07); spin_unlock(&chip->reg_lock); snd_azf3328_dbgplay("azt3328: timer IRQ\n"); } - if (status & IRQ_PLAYBACK) { - spin_lock(&chip->reg_lock); - which = snd_azf3328_codec_inb(chip, IDX_IO_PLAY_IRQTYPE); - /* ack all IRQ types immediately */ - snd_azf3328_codec_outb(chip, IDX_IO_PLAY_IRQTYPE, which); - spin_unlock(&chip->reg_lock); - if (chip->pcm && chip->audio_stream[AZF_PLAYBACK].substream) { - snd_pcm_period_elapsed( - chip->audio_stream[AZF_PLAYBACK].substream - ); - snd_azf3328_dbgplay("PLAY period done (#%x), @ %x\n", - which, - snd_azf3328_codec_inl( - chip, IDX_IO_PLAY_DMA_CURRPOS - ) - ); - } else - printk(KERN_WARNING "azt3328: irq handler problem!\n"); - if (which & IRQ_PLAY_SOMETHING) - snd_azf3328_irq_log_unknown_type(which); - } - if (status & IRQ_RECORDING) { - spin_lock(&chip->reg_lock); - which = snd_azf3328_codec_inb(chip, IDX_IO_REC_IRQTYPE); - /* ack all IRQ types immediately */ - snd_azf3328_codec_outb(chip, IDX_IO_REC_IRQTYPE, which); - spin_unlock(&chip->reg_lock); + if (status & (IRQ_PLAYBACK|IRQ_RECORDING|IRQ_I2S_OUT)) + snd_azf3328_codec_interrupt(chip, status); - if (chip->pcm && chip->audio_stream[AZF_CAPTURE].substream) { - snd_pcm_period_elapsed( - chip->audio_stream[AZF_CAPTURE].substream - ); - snd_azf3328_dbgplay("REC period done (#%x), @ %x\n", - which, - snd_azf3328_codec_inl( - chip, IDX_IO_REC_DMA_CURRPOS - ) - ); - } else - printk(KERN_WARNING "azt3328: irq handler problem!\n"); - if (which & IRQ_REC_SOMETHING) - snd_azf3328_irq_log_unknown_type(which); - } 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... */ + * If so, then I don't know how yet... */ snd_azf3328_dbgplay("azt3328: MPU401 IRQ\n"); } return IRQ_HANDLED; @@ -1690,7 +1713,11 @@ snd_azf3328_interrupt(int irq, void *dev_id) /*****************************************************************/ -static const struct snd_pcm_hardware snd_azf3328_playback = +/* 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 = { /* FIXME!! Correct? */ .info = SNDRV_PCM_INFO_MMAP | @@ -1718,31 +1745,6 @@ static const struct snd_pcm_hardware snd_azf3328_playback = .fifo_size = 0, }; -static const struct snd_pcm_hardware snd_azf3328_capture = -{ - /* FIXME */ - .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 = 65536, - .period_bytes_min = 64, - .period_bytes_max = 65536, - .periods_min = 1, - .periods_max = 1024, - .fifo_size = 0, -}; - static unsigned int snd_azf3328_fixed_rates[] = { AZF_FREQ_4000, @@ -1770,14 +1772,19 @@ static struct snd_pcm_hw_constraint_list snd_azf3328_hw_constraints_rates = { /*****************************************************************/ static int -snd_azf3328_playback_open(struct snd_pcm_substream *substream) +snd_azf3328_pcm_open(struct snd_pcm_substream *substream, + enum snd_azf3328_codec_type codec_type +) { struct snd_azf3328 *chip = snd_pcm_substream_chip(substream); struct snd_pcm_runtime *runtime = substream->runtime; snd_azf3328_dbgcallenter(); - chip->audio_stream[AZF_PLAYBACK].substream = substream; - runtime->hw = snd_azf3328_playback; + chip->codecs[codec_type].substream = substream; + + /* same parameters for all our codecs - at least we think so... */ + runtime->hw = snd_azf3328_hardware; + snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, &snd_azf3328_hw_constraints_rates); snd_azf3328_dbgcallleave(); @@ -1785,40 +1792,52 @@ snd_azf3328_playback_open(struct snd_pcm_substream *substream) } static int +snd_azf3328_playback_open(struct snd_pcm_substream *substream) +{ + return snd_azf3328_pcm_open(substream, AZF_CODEC_PLAYBACK); +} + +static int snd_azf3328_capture_open(struct snd_pcm_substream *substream) { - struct snd_azf3328 *chip = snd_pcm_substream_chip(substream); - struct snd_pcm_runtime *runtime = substream->runtime; + return snd_azf3328_pcm_open(substream, AZF_CODEC_CAPTURE); +} - snd_azf3328_dbgcallenter(); - chip->audio_stream[AZF_CAPTURE].substream = substream; - runtime->hw = snd_azf3328_capture; - snd_pcm_hw_constraint_list(runtime, 0, SNDRV_PCM_HW_PARAM_RATE, - &snd_azf3328_hw_constraints_rates); - snd_azf3328_dbgcallleave(); - return 0; +static int +snd_azf3328_i2s_out_open(struct snd_pcm_substream *substream) +{ + return snd_azf3328_pcm_open(substream, AZF_CODEC_I2S_OUT); } static int -snd_azf3328_playback_close(struct snd_pcm_substream *substream) +snd_azf3328_pcm_close(struct snd_pcm_substream *substream, + enum snd_azf3328_codec_type codec_type +) { struct snd_azf3328 *chip = snd_pcm_substream_chip(substream); snd_azf3328_dbgcallenter(); - chip->audio_stream[AZF_PLAYBACK].substream = NULL; + chip->codecs[codec_type].substream = NULL; snd_azf3328_dbgcallleave(); return 0; } static int +snd_azf3328_playback_close(struct snd_pcm_substream *substream) +{ + return snd_azf3328_pcm_close(substream, AZF_CODEC_PLAYBACK); +} + +static int snd_azf3328_capture_close(struct snd_pcm_substream *substream) { - struct snd_azf3328 *chip = snd_pcm_substream_chip(substream); + return snd_azf3328_pcm_close(substream, AZF_CODEC_CAPTURE); +} - snd_azf3328_dbgcallenter(); - chip->audio_stream[AZF_CAPTURE].substream = NULL; - snd_azf3328_dbgcallleave(); - return 0; +static int +snd_azf3328_i2s_out_close(struct snd_pcm_substream *substream) +{ + return snd_azf3328_pcm_close(substream, AZF_CODEC_I2S_OUT); } /******************************************************************/ @@ -1829,9 +1848,9 @@ static struct snd_pcm_ops snd_azf3328_playback_ops = { .ioctl = snd_pcm_lib_ioctl, .hw_params = snd_azf3328_hw_params, .hw_free = snd_azf3328_hw_free, - .prepare = snd_azf3328_playback_prepare, - .trigger = snd_azf3328_playback_trigger, - .pointer = snd_azf3328_playback_pointer + .prepare = snd_azf3328_codec_prepare, + .trigger = snd_azf3328_codec_playback_trigger, + .pointer = snd_azf3328_codec_playback_pointer }; static struct snd_pcm_ops snd_azf3328_capture_ops = { @@ -1840,30 +1859,67 @@ static struct snd_pcm_ops snd_azf3328_capture_ops = { .ioctl = snd_pcm_lib_ioctl, .hw_params = snd_azf3328_hw_params, .hw_free = snd_azf3328_hw_free, - .prepare = snd_azf3328_capture_prepare, - .trigger = snd_azf3328_capture_trigger, - .pointer = snd_azf3328_capture_pointer + .prepare = snd_azf3328_codec_prepare, + .trigger = snd_azf3328_codec_capture_trigger, + .pointer = snd_azf3328_codec_capture_pointer +}; + +static struct snd_pcm_ops snd_azf3328_i2s_out_ops = { + .open = snd_azf3328_i2s_out_open, + .close = snd_azf3328_i2s_out_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_azf3328_hw_params, + .hw_free = snd_azf3328_hw_free, + .prepare = snd_azf3328_codec_prepare, + .trigger = snd_azf3328_codec_i2s_out_trigger, + .pointer = snd_azf3328_codec_i2s_out_pointer }; static int __devinit -snd_azf3328_pcm(struct snd_azf3328 *chip, int device) +snd_azf3328_pcm(struct snd_azf3328 *chip) { +enum { AZF_PCMDEV_STD, AZF_PCMDEV_I2S_OUT, NUM_AZF_PCMDEVS }; /* pcm devices */ + struct snd_pcm *pcm; int err; snd_azf3328_dbgcallenter(); - if ((err = snd_pcm_new(chip->card, "AZF3328 DSP", device, 1, 1, &pcm)) < 0) + + err = snd_pcm_new(chip->card, "AZF3328 DSP", AZF_PCMDEV_STD, + 1, 1, &pcm); + if (err < 0) return err; - snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &snd_azf3328_playback_ops); - snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_azf3328_capture_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, + &snd_azf3328_playback_ops); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, + &snd_azf3328_capture_ops); pcm->private_data = chip; pcm->info_flags = 0; strcpy(pcm->name, chip->card->shortname); - chip->pcm = pcm; + /* same pcm object for playback/capture (see snd_pcm_new() above) */ + chip->pcm[AZF_CODEC_PLAYBACK] = pcm; + chip->pcm[AZF_CODEC_CAPTURE] = pcm; snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, - snd_dma_pci_data(chip->pci), 64*1024, 64*1024); + snd_dma_pci_data(chip->pci), + 64*1024, 64*1024); + + err = snd_pcm_new(chip->card, "AZF3328 I2S OUT", AZF_PCMDEV_I2S_OUT, + 1, 0, &pcm); + if (err < 0) + return err; + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, + &snd_azf3328_i2s_out_ops); + + pcm->private_data = chip; + pcm->info_flags = 0; + strcpy(pcm->name, chip->card->shortname); + chip->pcm[AZF_CODEC_I2S_OUT] = pcm; + + snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV, + snd_dma_pci_data(chip->pci), + 64*1024, 64*1024); snd_azf3328_dbgcallleave(); return 0; @@ -1902,7 +1958,7 @@ snd_azf3328_timer_start(struct snd_timer *timer) snd_azf3328_dbgtimer("setting timer countdown value %d, add COUNTDOWN|IRQ\n", delay); delay |= TIMER_COUNTDOWN_ENABLE | TIMER_IRQ_ENABLE; spin_lock_irqsave(&chip->reg_lock, flags); - snd_azf3328_codec_outl(chip, IDX_IO_TIMER_VALUE, delay); + snd_azf3328_ctrl_outl(chip, IDX_IO_TIMER_VALUE, delay); spin_unlock_irqrestore(&chip->reg_lock, flags); snd_azf3328_dbgcallleave(); return 0; @@ -1919,7 +1975,7 @@ snd_azf3328_timer_stop(struct snd_timer *timer) spin_lock_irqsave(&chip->reg_lock, flags); /* disable timer countdown and interrupt */ /* FIXME: should we write TIMER_IRQ_ACK here? */ - snd_azf3328_codec_outb(chip, IDX_IO_TIMER_VALUE + 3, 0); + snd_azf3328_ctrl_outb(chip, IDX_IO_TIMER_VALUE + 3, 0); spin_unlock_irqrestore(&chip->reg_lock, flags); snd_azf3328_dbgcallleave(); return 0; @@ -2048,9 +2104,9 @@ snd_azf3328_debug_show_ports(const struct snd_azf3328 *chip) u16 tmp; snd_azf3328_dbgmisc( - "codec_io 0x%lx, game_io 0x%lx, mpu_io 0x%lx, " + "ctrl_io 0x%lx, game_io 0x%lx, mpu_io 0x%lx, " "opl3_io 0x%lx, mixer_io 0x%lx, irq %d\n", - chip->codec_io, chip->game_io, chip->mpu_io, + chip->ctrl_io, chip->game_io, chip->mpu_io, chip->opl3_io, chip->mixer_io, chip->irq ); @@ -2083,9 +2139,9 @@ snd_azf3328_debug_show_ports(const struct snd_azf3328 *chip) inb(0x38c + tmp) ); - for (tmp = 0; tmp < AZF_IO_SIZE_CODEC; tmp += 2) - snd_azf3328_dbgmisc("codec 0x%02x: 0x%04x\n", - tmp, snd_azf3328_codec_inw(chip, tmp) + for (tmp = 0; tmp < AZF_IO_SIZE_CTRL; tmp += 2) + snd_azf3328_dbgmisc("ctrl 0x%02x: 0x%04x\n", + tmp, snd_azf3328_ctrl_inw(chip, tmp) ); for (tmp = 0; tmp < AZF_IO_SIZE_MIXER; tmp += 2) @@ -2106,7 +2162,8 @@ snd_azf3328_create(struct snd_card *card, static struct snd_device_ops ops = { .dev_free = snd_azf3328_dev_free, }; - u16 tmp; + u8 dma_init; + enum snd_azf3328_codec_type codec_type; *rchip = NULL; @@ -2138,14 +2195,21 @@ snd_azf3328_create(struct snd_card *card, if (err < 0) goto out_err; - chip->codec_io = pci_resource_start(pci, 0); + chip->ctrl_io = pci_resource_start(pci, 0); chip->game_io = pci_resource_start(pci, 1); chip->mpu_io = pci_resource_start(pci, 2); - chip->opl3_io = pci_resource_start(pci, 3); + chip->opl3_io = pci_resource_start(pci, 3); chip->mixer_io = pci_resource_start(pci, 4); - chip->audio_stream[AZF_PLAYBACK].portbase = chip->codec_io + 0x00; - chip->audio_stream[AZF_CAPTURE].portbase = chip->codec_io + 0x20; + chip->codecs[AZF_CODEC_PLAYBACK].io_base = + chip->ctrl_io + AZF_IO_OFFS_CODEC_PLAYBACK; + chip->codecs[AZF_CODEC_PLAYBACK].name = "PLAYBACK"; + chip->codecs[AZF_CODEC_CAPTURE].io_base = + chip->ctrl_io + AZF_IO_OFFS_CODEC_CAPTURE; + chip->codecs[AZF_CODEC_CAPTURE].name = "CAPTURE"; + chip->codecs[AZF_CODEC_I2S_OUT].io_base = + chip->ctrl_io + AZF_IO_OFFS_CODEC_I2S_OUT; + chip->codecs[AZF_CODEC_I2S_OUT].name = "I2S_OUT"; if (request_irq(pci->irq, snd_azf3328_interrupt, IRQF_SHARED, card->shortname, chip)) { @@ -2168,20 +2232,25 @@ snd_azf3328_create(struct snd_card *card, if (err < 0) goto out_err; - /* shutdown codecs to save power */ - /* have snd_azf3328_codec_activity() act properly */ - chip->audio_stream[AZF_PLAYBACK].running = 1; - snd_azf3328_codec_activity(chip, AZF_PLAYBACK, 0); + /* standard codec init stuff */ + /* default DMA init value */ + dma_init = DMA_RUN_SOMETHING2|DMA_EPILOGUE_SOMETHING|DMA_SOMETHING_ELSE; + + for (codec_type = AZF_CODEC_PLAYBACK; + codec_type <= AZF_CODEC_I2S_OUT; ++codec_type) { + struct snd_azf3328_codec_data *codec = + &chip->codecs[codec_type]; - /* standard chip init stuff */ - /* default IRQ init value */ - tmp = DMA_PLAY_SOMETHING2|DMA_EPILOGUE_SOMETHING|DMA_SOMETHING_ELSE; + /* shutdown codecs to save power */ + /* have ...ctrl_codec_activity() act properly */ + codec->running = 1; + snd_azf3328_ctrl_codec_activity(chip, codec_type, 0); - spin_lock_irq(&chip->reg_lock); - snd_azf3328_codec_outb(chip, IDX_IO_PLAY_FLAGS, tmp); - snd_azf3328_codec_outb(chip, IDX_IO_REC_FLAGS, tmp); - snd_azf3328_codec_outb(chip, IDX_IO_SOMETHING_FLAGS, tmp); - spin_unlock_irq(&chip->reg_lock); + spin_lock_irq(&chip->reg_lock); + snd_azf3328_codec_outb(codec, IDX_IO_CODEC_DMA_FLAGS, + dma_init); + spin_unlock_irq(&chip->reg_lock); + } snd_card_set_dev(card, &pci->dev); @@ -2244,7 +2313,7 @@ snd_azf3328_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) if (err < 0) goto out_err; - err = snd_azf3328_pcm(chip, 0); + err = snd_azf3328_pcm(chip); if (err < 0) goto out_err; @@ -2266,7 +2335,7 @@ snd_azf3328_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) opl3->private_data = chip; sprintf(card->longname, "%s at 0x%lx, irq %i", - card->shortname, chip->codec_io, chip->irq); + card->shortname, chip->ctrl_io, chip->irq); err = snd_card_register(card); if (err < 0) @@ -2317,7 +2386,8 @@ snd_azf3328_suspend(struct pci_dev *pci, pm_message_t state) snd_power_change_state(card, SNDRV_CTL_POWER_D3hot); - snd_pcm_suspend_all(chip->pcm); + snd_pcm_suspend_all(chip->pcm[AZF_CODEC_PLAYBACK]); + snd_pcm_suspend_all(chip->pcm[AZF_CODEC_I2S_OUT]); for (reg = 0; reg < AZF_IO_SIZE_MIXER_PM / 2; ++reg) chip->saved_regs_mixer[reg] = inw(chip->mixer_io + reg * 2); @@ -2326,11 +2396,11 @@ snd_azf3328_suspend(struct pci_dev *pci, pm_message_t state) snd_azf3328_mixer_set_mute(chip, IDX_MIXER_PLAY_MASTER, 1); snd_azf3328_mixer_set_mute(chip, IDX_MIXER_WAVEOUT, 1); - for (reg = 0; reg < AZF_IO_SIZE_CODEC_PM / 2; ++reg) - chip->saved_regs_codec[reg] = inw(chip->codec_io + reg * 2); + for (reg = 0; reg < AZF_IO_SIZE_CTRL_PM / 2; ++reg) + chip->saved_regs_ctrl[reg] = inw(chip->ctrl_io + reg * 2); /* manually store the one currently relevant write-only reg, too */ - chip->saved_regs_codec[IDX_IO_6AH / 2] = chip->shadow_reg_codec_6AH; + chip->saved_regs_ctrl[IDX_IO_6AH / 2] = chip->shadow_reg_ctrl_6AH; for (reg = 0; reg < AZF_IO_SIZE_GAME_PM / 2; ++reg) chip->saved_regs_game[reg] = inw(chip->game_io + reg * 2); @@ -2349,7 +2419,7 @@ static int snd_azf3328_resume(struct pci_dev *pci) { struct snd_card *card = pci_get_drvdata(pci); - struct snd_azf3328 *chip = card->private_data; + const struct snd_azf3328 *chip = card->private_data; unsigned reg; pci_set_power_state(pci, PCI_D0); @@ -2370,8 +2440,8 @@ snd_azf3328_resume(struct pci_dev *pci) outw(chip->saved_regs_opl3[reg], chip->opl3_io + reg * 2); for (reg = 0; reg < AZF_IO_SIZE_MIXER_PM / 2; ++reg) outw(chip->saved_regs_mixer[reg], chip->mixer_io + reg * 2); - for (reg = 0; reg < AZF_IO_SIZE_CODEC_PM / 2; ++reg) - outw(chip->saved_regs_codec[reg], chip->codec_io + reg * 2); + for (reg = 0; reg < AZF_IO_SIZE_CTRL_PM / 2; ++reg) + outw(chip->saved_regs_ctrl[reg], chip->ctrl_io + reg * 2); snd_power_change_state(card, SNDRV_CTL_POWER_D0); return 0; |