summaryrefslogtreecommitdiffstats
path: root/sound/oss/ali5455.c
diff options
context:
space:
mode:
Diffstat (limited to 'sound/oss/ali5455.c')
-rw-r--r--sound/oss/ali5455.c3733
1 files changed, 3733 insertions, 0 deletions
diff --git a/sound/oss/ali5455.c b/sound/oss/ali5455.c
new file mode 100644
index 000000000000..9c9e6c0410f2
--- /dev/null
+++ b/sound/oss/ali5455.c
@@ -0,0 +1,3733 @@
+/*
+ * ALI ali5455 and friends ICH driver for Linux
+ * LEI HU <Lei_Hu@ali.com.tw>
+ *
+ * Built from:
+ * drivers/sound/i810_audio
+ *
+ * The ALi 5455 is similar but not quite identical to the Intel ICH
+ * series of controllers. Its easier to keep the driver separated from
+ * the i810 driver.
+ *
+ * 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
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ *
+ * ALi 5455 theory of operation
+ *
+ * The chipset provides three DMA channels that talk to an AC97
+ * CODEC (AC97 is a digital/analog mixer standard). At its simplest
+ * you get 48Khz audio with basic volume and mixer controls. At the
+ * best you get rate adaption in the codec. We set the card up so
+ * that we never take completion interrupts but instead keep the card
+ * chasing its tail around a ring buffer. This is needed for mmap
+ * mode audio and happens to work rather well for non-mmap modes too.
+ *
+ * The board has one output channel for PCM audio (supported) and
+ * a stereo line in and mono microphone input. Again these are normally
+ * locked to 48Khz only. Right now recording is not finished.
+ *
+ * There is no midi support, no synth support. Use timidity. To get
+ * esd working you need to use esd -r 48000 as it won't probe 48KHz
+ * by default. mpg123 can't handle 48Khz only audio so use xmms.
+ *
+ * If you need to force a specific rate set the clocking= option
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/string.h>
+#include <linux/ctype.h>
+#include <linux/ioport.h>
+#include <linux/sched.h>
+#include <linux/delay.h>
+#include <linux/sound.h>
+#include <linux/slab.h>
+#include <linux/soundcard.h>
+#include <linux/pci.h>
+#include <asm/io.h>
+#include <asm/dma.h>
+#include <linux/init.h>
+#include <linux/poll.h>
+#include <linux/spinlock.h>
+#include <linux/smp_lock.h>
+#include <linux/ac97_codec.h>
+#include <linux/interrupt.h>
+#include <asm/uaccess.h>
+
+#ifndef PCI_DEVICE_ID_ALI_5455
+#define PCI_DEVICE_ID_ALI_5455 0x5455
+#endif
+
+#ifndef PCI_VENDOR_ID_ALI
+#define PCI_VENDOR_ID_ALI 0x10b9
+#endif
+
+static int strict_clocking = 0;
+static unsigned int clocking = 0;
+static unsigned int codec_pcmout_share_spdif_locked = 0;
+static unsigned int codec_independent_spdif_locked = 0;
+static unsigned int controller_pcmout_share_spdif_locked = 0;
+static unsigned int controller_independent_spdif_locked = 0;
+static unsigned int globel = 0;
+
+#define ADC_RUNNING 1
+#define DAC_RUNNING 2
+#define CODEC_SPDIFOUT_RUNNING 8
+#define CONTROLLER_SPDIFOUT_RUNNING 4
+
+#define SPDIF_ENABLE_OUTPUT 4 /* bits 0,1 are PCM */
+
+#define ALI5455_FMT_16BIT 1
+#define ALI5455_FMT_STEREO 2
+#define ALI5455_FMT_MASK 3
+
+#define SPDIF_ON 0x0004
+#define SURR_ON 0x0010
+#define CENTER_LFE_ON 0x0020
+#define VOL_MUTED 0x8000
+
+
+#define ALI_SPDIF_OUT_CH_STATUS 0xbf
+/* the 810's array of pointers to data buffers */
+
+struct sg_item {
+#define BUSADDR_MASK 0xFFFFFFFE
+ u32 busaddr;
+#define CON_IOC 0x80000000 /* interrupt on completion */
+#define CON_BUFPAD 0x40000000 /* pad underrun with last sample, else 0 */
+#define CON_BUFLEN_MASK 0x0000ffff /* buffer length in samples */
+ u32 control;
+};
+
+/* an instance of the ali channel */
+#define SG_LEN 32
+struct ali_channel {
+ /* these sg guys should probably be allocated
+ separately as nocache. Must be 8 byte aligned */
+ struct sg_item sg[SG_LEN]; /* 32*8 */
+ u32 offset; /* 4 */
+ u32 port; /* 4 */
+ u32 used;
+ u32 num;
+};
+
+/*
+ * we have 3 separate dma engines. pcm in, pcm out, and mic.
+ * each dma engine has controlling registers. These goofy
+ * names are from the datasheet, but make it easy to write
+ * code while leafing through it.
+ */
+
+#define ENUM_ENGINE(PRE,DIG) \
+enum { \
+ PRE##_BDBAR = 0x##DIG##0, /* Buffer Descriptor list Base Address */ \
+ PRE##_CIV = 0x##DIG##4, /* Current Index Value */ \
+ PRE##_LVI = 0x##DIG##5, /* Last Valid Index */ \
+ PRE##_SR = 0x##DIG##6, /* Status Register */ \
+ PRE##_PICB = 0x##DIG##8, /* Position In Current Buffer */ \
+ PRE##_CR = 0x##DIG##b /* Control Register */ \
+}
+
+ENUM_ENGINE(OFF, 0); /* Offsets */
+ENUM_ENGINE(PI, 4); /* PCM In */
+ENUM_ENGINE(PO, 5); /* PCM Out */
+ENUM_ENGINE(MC, 6); /* Mic In */
+ENUM_ENGINE(CODECSPDIFOUT, 7); /* CODEC SPDIF OUT */
+ENUM_ENGINE(CONTROLLERSPDIFIN, A); /* CONTROLLER SPDIF In */
+ENUM_ENGINE(CONTROLLERSPDIFOUT, B); /* CONTROLLER SPDIF OUT */
+
+
+enum {
+ ALI_SCR = 0x00, /* System Control Register */
+ ALI_SSR = 0x04, /* System Status Register */
+ ALI_DMACR = 0x08, /* DMA Control Register */
+ ALI_FIFOCR1 = 0x0c, /* FIFO Control Register 1 */
+ ALI_INTERFACECR = 0x10, /* Interface Control Register */
+ ALI_INTERRUPTCR = 0x14, /* Interrupt control Register */
+ ALI_INTERRUPTSR = 0x18, /* Interrupt Status Register */
+ ALI_FIFOCR2 = 0x1c, /* FIFO Control Register 2 */
+ ALI_CPR = 0x20, /* Command Port Register */
+ ALI_SPR = 0x24, /* Status Port Register */
+ ALI_FIFOCR3 = 0x2c, /* FIFO Control Register 3 */
+ ALI_TTSR = 0x30, /* Transmit Tag Slot Register */
+ ALI_RTSR = 0x34, /* Receive Tag Slot Register */
+ ALI_CSPSR = 0x38, /* Command/Status Port Status Register */
+ ALI_CAS = 0x3c, /* Codec Write Semaphore Register */
+ ALI_SPDIFCSR = 0xf8, /* spdif channel status register */
+ ALI_SPDIFICS = 0xfc /* spdif interface control/status */
+};
+
+// x-status register(x:pcm in ,pcm out, mic in,)
+/* interrupts for a dma engine */
+#define DMA_INT_FIFO (1<<4) /* fifo under/over flow */
+#define DMA_INT_COMPLETE (1<<3) /* buffer read/write complete and ioc set */
+#define DMA_INT_LVI (1<<2) /* last valid done */
+#define DMA_INT_CELV (1<<1) /* last valid is current */
+#define DMA_INT_DCH (1) /* DMA Controller Halted (happens on LVI interrupts) */ //not eqult intel
+#define DMA_INT_MASK (DMA_INT_FIFO|DMA_INT_COMPLETE|DMA_INT_LVI)
+
+/* interrupts for the whole chip */// by interrupt status register finish
+
+#define INT_SPDIFOUT (1<<23) /* controller spdif out INTERRUPT */
+#define INT_SPDIFIN (1<<22)
+#define INT_CODECSPDIFOUT (1<<19)
+#define INT_MICIN (1<<18)
+#define INT_PCMOUT (1<<17)
+#define INT_PCMIN (1<<16)
+#define INT_CPRAIS (1<<7)
+#define INT_SPRAIS (1<<5)
+#define INT_GPIO (1<<1)
+#define INT_MASK (INT_SPDIFOUT|INT_CODECSPDIFOUT|INT_MICIN|INT_PCMOUT|INT_PCMIN)
+
+#define DRIVER_VERSION "0.02ac"
+
+/* magic numbers to protect our data structures */
+#define ALI5455_CARD_MAGIC 0x5072696E /* "Prin" */
+#define ALI5455_STATE_MAGIC 0x63657373 /* "cess" */
+#define ALI5455_DMA_MASK 0xffffffff /* DMA buffer mask for pci_alloc_consist */
+#define NR_HW_CH 5 //I think 5 channel
+
+/* maxinum number of AC97 codecs connected, AC97 2.0 defined 4 */
+#define NR_AC97 2
+
+/* Please note that an 8bit mono stream is not valid on this card, you must have a 16bit */
+/* stream at a minimum for this card to be happy */
+static const unsigned sample_size[] = { 1, 2, 2, 4 };
+/* Samples are 16bit values, so we are shifting to a word, not to a byte, hence shift */
+/* values are one less than might be expected */
+static const unsigned sample_shift[] = { -1, 0, 0, 1 };
+
+#define ALI5455
+static char *card_names[] = {
+ "ALI 5455"
+};
+
+static struct pci_device_id ali_pci_tbl[] = {
+ {PCI_VENDOR_ID_ALI, PCI_DEVICE_ID_ALI_5455,
+ PCI_ANY_ID, PCI_ANY_ID, 0, 0, ALI5455},
+ {0,}
+};
+
+MODULE_DEVICE_TABLE(pci, ali_pci_tbl);
+
+#ifdef CONFIG_PM
+#define PM_SUSPENDED(card) (card->pm_suspended)
+#else
+#define PM_SUSPENDED(card) (0)
+#endif
+
+/* "software" or virtual channel, an instance of opened /dev/dsp */
+struct ali_state {
+ unsigned int magic;
+ struct ali_card *card; /* Card info */
+
+ /* single open lock mechanism, only used for recording */
+ struct semaphore open_sem;
+ wait_queue_head_t open_wait;
+
+ /* file mode */
+ mode_t open_mode;
+
+ /* virtual channel number */
+ int virt;
+
+#ifdef CONFIG_PM
+ unsigned int pm_saved_dac_rate, pm_saved_adc_rate;
+#endif
+ struct dmabuf {
+ /* wave sample stuff */
+ unsigned int rate;
+ unsigned char fmt, enable, trigger;
+
+ /* hardware channel */
+ struct ali_channel *read_channel;
+ struct ali_channel *write_channel;
+ struct ali_channel *codec_spdifout_channel;
+ struct ali_channel *controller_spdifout_channel;
+
+ /* OSS buffer management stuff */
+ void *rawbuf;
+ dma_addr_t dma_handle;
+ unsigned buforder;
+ unsigned numfrag;
+ unsigned fragshift;
+
+ /* our buffer acts like a circular ring */
+ unsigned hwptr; /* where dma last started, updated by update_ptr */
+ unsigned swptr; /* where driver last clear/filled, updated by read/write */
+ int count; /* bytes to be consumed or been generated by dma machine */
+ unsigned total_bytes; /* total bytes dmaed by hardware */
+
+ unsigned error; /* number of over/underruns */
+ wait_queue_head_t wait; /* put process on wait queue when no more space in buffer */
+
+ /* redundant, but makes calculations easier */
+ /* what the hardware uses */
+ unsigned dmasize;
+ unsigned fragsize;
+ unsigned fragsamples;
+
+ /* what we tell the user to expect */
+ unsigned userfrags;
+ unsigned userfragsize;
+
+ /* OSS stuff */
+ unsigned mapped:1;
+ unsigned ready:1;
+ unsigned update_flag;
+ unsigned ossfragsize;
+ unsigned ossmaxfrags;
+ unsigned subdivision;
+ } dmabuf;
+};
+
+
+struct ali_card {
+ struct ali_channel channel[5];
+ unsigned int magic;
+
+ /* We keep ali5455 cards in a linked list */
+ struct ali_card *next;
+
+ /* The ali has a certain amount of cross channel interaction
+ so we use a single per card lock */
+ spinlock_t lock;
+ spinlock_t ac97_lock;
+
+ /* PCI device stuff */
+ struct pci_dev *pci_dev;
+ u16 pci_id;
+#ifdef CONFIG_PM
+ u16 pm_suspended;
+ int pm_saved_mixer_settings[SOUND_MIXER_NRDEVICES][NR_AC97];
+#endif
+ /* soundcore stuff */
+ int dev_audio;
+
+ /* structures for abstraction of hardware facilities, codecs, banks and channels */
+ struct ac97_codec *ac97_codec[NR_AC97];
+ struct ali_state *states[NR_HW_CH];
+
+ u16 ac97_features;
+ u16 ac97_status;
+ u16 channels;
+
+ /* hardware resources */
+ unsigned long iobase;
+
+ u32 irq;
+
+ /* Function support */
+ struct ali_channel *(*alloc_pcm_channel) (struct ali_card *);
+ struct ali_channel *(*alloc_rec_pcm_channel) (struct ali_card *);
+ struct ali_channel *(*alloc_rec_mic_channel) (struct ali_card *);
+ struct ali_channel *(*alloc_codec_spdifout_channel) (struct ali_card *);
+ struct ali_channel *(*alloc_controller_spdifout_channel) (struct ali_card *);
+ void (*free_pcm_channel) (struct ali_card *, int chan);
+
+ /* We have a *very* long init time possibly, so use this to block */
+ /* attempts to open our devices before we are ready (stops oops'es) */
+ int initializing;
+};
+
+
+static struct ali_card *devs = NULL;
+
+static int ali_open_mixdev(struct inode *inode, struct file *file);
+static int ali_ioctl_mixdev(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg);
+static u16 ali_ac97_get(struct ac97_codec *dev, u8 reg);
+static void ali_ac97_set(struct ac97_codec *dev, u8 reg, u16 data);
+
+static struct ali_channel *ali_alloc_pcm_channel(struct ali_card *card)
+{
+ if (card->channel[1].used == 1)
+ return NULL;
+ card->channel[1].used = 1;
+ return &card->channel[1];
+}
+
+static struct ali_channel *ali_alloc_rec_pcm_channel(struct ali_card *card)
+{
+ if (card->channel[0].used == 1)
+ return NULL;
+ card->channel[0].used = 1;
+ return &card->channel[0];
+}
+
+static struct ali_channel *ali_alloc_rec_mic_channel(struct ali_card *card)
+{
+ if (card->channel[2].used == 1)
+ return NULL;
+ card->channel[2].used = 1;
+ return &card->channel[2];
+}
+
+static struct ali_channel *ali_alloc_codec_spdifout_channel(struct ali_card *card)
+{
+ if (card->channel[3].used == 1)
+ return NULL;
+ card->channel[3].used = 1;
+ return &card->channel[3];
+}
+
+static struct ali_channel *ali_alloc_controller_spdifout_channel(struct ali_card *card)
+{
+ if (card->channel[4].used == 1)
+ return NULL;
+ card->channel[4].used = 1;
+ return &card->channel[4];
+}
+static void ali_free_pcm_channel(struct ali_card *card, int channel)
+{
+ card->channel[channel].used = 0;
+}
+
+
+//add support codec spdif out
+static int ali_valid_spdif_rate(struct ac97_codec *codec, int rate)
+{
+ unsigned long id = 0L;
+
+ id = (ali_ac97_get(codec, AC97_VENDOR_ID1) << 16);
+ id |= ali_ac97_get(codec, AC97_VENDOR_ID2) & 0xffff;
+ switch (id) {
+ case 0x41445361: /* AD1886 */
+ if (rate == 48000) {
+ return 1;
+ }
+ break;
+ case 0x414c4720: /* ALC650 */
+ if (rate == 48000) {
+ return 1;
+ }
+ break;
+ default: /* all other codecs, until we know otherwiae */
+ if (rate == 48000 || rate == 44100 || rate == 32000) {
+ return 1;
+ }
+ break;
+ }
+ return (0);
+}
+
+/* ali_set_spdif_output
+ *
+ * Configure the S/PDIF output transmitter. When we turn on
+ * S/PDIF, we turn off the analog output. This may not be
+ * the right thing to do.
+ *
+ * Assumptions:
+ * The DSP sample rate must already be set to a supported
+ * S/PDIF rate (32kHz, 44.1kHz, or 48kHz) or we abort.
+ */
+static void ali_set_spdif_output(struct ali_state *state, int slots,
+ int rate)
+{
+ int vol;
+ int aud_reg;
+ struct ac97_codec *codec = state->card->ac97_codec[0];
+
+ if (!(state->card->ac97_features & 4)) {
+ state->card->ac97_status &= ~SPDIF_ON;
+ } else {
+ if (slots == -1) { /* Turn off S/PDIF */
+ aud_reg = ali_ac97_get(codec, AC97_EXTENDED_STATUS);
+ ali_ac97_set(codec, AC97_EXTENDED_STATUS, (aud_reg & ~AC97_EA_SPDIF));
+
+ /* If the volume wasn't muted before we turned on S/PDIF, unmute it */
+ if (!(state->card->ac97_status & VOL_MUTED)) {
+ aud_reg = ali_ac97_get(codec, AC97_MASTER_VOL_STEREO);
+ ali_ac97_set(codec, AC97_MASTER_VOL_STEREO,
+ (aud_reg & ~VOL_MUTED));
+ }
+ state->card->ac97_status &= ~(VOL_MUTED | SPDIF_ON);
+ return;
+ }
+
+ vol = ali_ac97_get(codec, AC97_MASTER_VOL_STEREO);
+ state->card->ac97_status = vol & VOL_MUTED;
+
+ /* Set S/PDIF transmitter sample rate */
+ aud_reg = ali_ac97_get(codec, AC97_SPDIF_CONTROL);
+ switch (rate) {
+ case 32000:
+ aud_reg = (aud_reg & AC97_SC_SPSR_MASK) | AC97_SC_SPSR_32K;
+ break;
+ case 44100:
+ aud_reg = (aud_reg & AC97_SC_SPSR_MASK) | AC97_SC_SPSR_44K;
+ break;
+ case 48000:
+ aud_reg = (aud_reg & AC97_SC_SPSR_MASK) | AC97_SC_SPSR_48K;
+ break;
+ default:
+ /* turn off S/PDIF */
+ aud_reg = ali_ac97_get(codec, AC97_EXTENDED_STATUS);
+ ali_ac97_set(codec, AC97_EXTENDED_STATUS, (aud_reg & ~AC97_EA_SPDIF));
+ state->card->ac97_status &= ~SPDIF_ON;
+ return;
+ }
+
+ ali_ac97_set(codec, AC97_SPDIF_CONTROL, aud_reg);
+
+ aud_reg = ali_ac97_get(codec, AC97_EXTENDED_STATUS);
+ aud_reg = (aud_reg & AC97_EA_SLOT_MASK) | slots | AC97_EA_SPDIF;
+ ali_ac97_set(codec, AC97_EXTENDED_STATUS, aud_reg);
+
+ aud_reg = ali_ac97_get(codec, AC97_POWER_CONTROL);
+ aud_reg |= 0x0002;
+ ali_ac97_set(codec, AC97_POWER_CONTROL, aud_reg);
+ udelay(1);
+
+ state->card->ac97_status |= SPDIF_ON;
+
+ /* Check to make sure the configuration is valid */
+ aud_reg = ali_ac97_get(codec, AC97_EXTENDED_STATUS);
+ if (!(aud_reg & 0x0400)) {
+ /* turn off S/PDIF */
+ ali_ac97_set(codec, AC97_EXTENDED_STATUS, (aud_reg & ~AC97_EA_SPDIF));
+ state->card->ac97_status &= ~SPDIF_ON;
+ return;
+ }
+ if (codec_independent_spdif_locked > 0) {
+ aud_reg = ali_ac97_get(codec, 0x6a);
+ ali_ac97_set(codec, 0x6a, (aud_reg & 0xefff));
+ }
+ /* Mute the analog output */
+ /* Should this only mute the PCM volume??? */
+ }
+}
+
+/* ali_set_dac_channels
+ *
+ * Configure the codec's multi-channel DACs
+ *
+ * The logic is backwards. Setting the bit to 1 turns off the DAC.
+ *
+ * What about the ICH? We currently configure it using the
+ * SNDCTL_DSP_CHANNELS ioctl. If we're turnning on the DAC,
+ * does that imply that we want the ICH set to support
+ * these channels?
+ *
+ * TODO:
+ * vailidate that the codec really supports these DACs
+ * before turning them on.
+ */
+static void ali_set_dac_channels(struct ali_state *state, int channel)
+{
+ int aud_reg;
+ struct ac97_codec *codec = state->card->ac97_codec[0];
+
+ aud_reg = ali_ac97_get(codec, AC97_EXTENDED_STATUS);
+ aud_reg |= AC97_EA_PRI | AC97_EA_PRJ | AC97_EA_PRK;
+ state->card->ac97_status &= ~(SURR_ON | CENTER_LFE_ON);
+
+ switch (channel) {
+ case 2: /* always enabled */
+ break;
+ case 4:
+ aud_reg &= ~AC97_EA_PRJ;
+ state->card->ac97_status |= SURR_ON;
+ break;
+ case 6:
+ aud_reg &= ~(AC97_EA_PRJ | AC97_EA_PRI | AC97_EA_PRK);
+ state->card->ac97_status |= SURR_ON | CENTER_LFE_ON;
+ break;
+ default:
+ break;
+ }
+ ali_ac97_set(codec, AC97_EXTENDED_STATUS, aud_reg);
+
+}
+
+/* set playback sample rate */
+static unsigned int ali_set_dac_rate(struct ali_state *state,
+ unsigned int rate)
+{
+ struct dmabuf *dmabuf = &state->dmabuf;
+ u32 new_rate;
+ struct ac97_codec *codec = state->card->ac97_codec[0];
+
+ if (!(state->card->ac97_features & 0x0001)) {
+ dmabuf->rate = clocking;
+ return clocking;
+ }
+
+ if (rate > 48000)
+ rate = 48000;
+ if (rate < 8000)
+ rate = 8000;
+ dmabuf->rate = rate;
+
+ /*
+ * Adjust for misclocked crap
+ */
+
+ rate = (rate * clocking) / 48000;
+
+ if (strict_clocking && rate < 8000) {
+ rate = 8000;
+ dmabuf->rate = (rate * 48000) / clocking;
+ }
+
+ new_rate = ac97_set_dac_rate(codec, rate);
+ if (new_rate != rate) {
+ dmabuf->rate = (new_rate * 48000) / clocking;
+ }
+ rate = new_rate;
+ return dmabuf->rate;
+}
+
+/* set recording sample rate */
+static unsigned int ali_set_adc_rate(struct ali_state *state,
+ unsigned int rate)
+{
+ struct dmabuf *dmabuf = &state->dmabuf;
+ u32 new_rate;
+ struct ac97_codec *codec = state->card->ac97_codec[0];
+
+ if (!(state->card->ac97_features & 0x0001)) {
+ dmabuf->rate = clocking;
+ return clocking;
+ }
+
+ if (rate > 48000)
+ rate = 48000;
+ if (rate < 8000)
+ rate = 8000;
+ dmabuf->rate = rate;
+
+ /*
+ * Adjust for misclocked crap
+ */
+
+ rate = (rate * clocking) / 48000;
+ if (strict_clocking && rate < 8000) {
+ rate = 8000;
+ dmabuf->rate = (rate * 48000) / clocking;
+ }
+
+ new_rate = ac97_set_adc_rate(codec, rate);
+
+ if (new_rate != rate) {
+ dmabuf->rate = (new_rate * 48000) / clocking;
+ rate = new_rate;
+ }
+ return dmabuf->rate;
+}
+
+/* set codec independent spdifout sample rate */
+static unsigned int ali_set_codecspdifout_rate(struct ali_state *state,
+ unsigned int rate)
+{
+ struct dmabuf *dmabuf = &state->dmabuf;
+
+ if (!(state->card->ac97_features & 0x0001)) {
+ dmabuf->rate = clocking;
+ return clocking;
+ }
+
+ if (rate > 48000)
+ rate = 48000;
+ if (rate < 8000)
+ rate = 8000;
+ dmabuf->rate = rate;
+
+ return dmabuf->rate;
+}
+
+/* set controller independent spdif out function sample rate */
+static void ali_set_spdifout_rate(struct ali_state *state,
+ unsigned int rate)
+{
+ unsigned char ch_st_sel;
+ unsigned short status_rate;
+
+ switch (rate) {
+ case 44100:
+ status_rate = 0;
+ break;
+ case 32000:
+ status_rate = 0x300;
+ break;
+ case 48000:
+ default:
+ status_rate = 0x200;
+ break;
+ }
+
+ ch_st_sel = inb(state->card->iobase + ALI_SPDIFICS) & ALI_SPDIF_OUT_CH_STATUS; //select spdif_out
+
+ ch_st_sel |= 0x80; //select right
+ outb(ch_st_sel, (state->card->iobase + ALI_SPDIFICS));
+ outb(status_rate | 0x20, (state->card->iobase + ALI_SPDIFCSR + 2));
+
+ ch_st_sel &= (~0x80); //select left
+ outb(ch_st_sel, (state->card->iobase + ALI_SPDIFICS));
+ outw(status_rate | 0x10, (state->card->iobase + ALI_SPDIFCSR + 2));
+}
+
+/* get current playback/recording dma buffer pointer (byte offset from LBA),
+ called with spinlock held! */
+
+static inline unsigned ali_get_dma_addr(struct ali_state *state, int rec)
+{
+ struct dmabuf *dmabuf = &state->dmabuf;
+ unsigned int civ, offset, port, port_picb;
+ unsigned int data;
+
+ if (!dmabuf->enable)
+ return 0;
+
+ if (rec == 1)
+ port = state->card->iobase + dmabuf->read_channel->port;
+ else if (rec == 2)
+ port = state->card->iobase + dmabuf->codec_spdifout_channel->port;
+ else if (rec == 3)
+ port = state->card->iobase + dmabuf->controller_spdifout_channel->port;
+ else
+ port = state->card->iobase + dmabuf->write_channel->port;
+
+ port_picb = port + OFF_PICB;
+
+ do {
+ civ = inb(port + OFF_CIV) & 31;
+ offset = inw(port_picb);
+ /* Must have a delay here! */
+ if (offset == 0)
+ udelay(1);
+
+ /* Reread both registers and make sure that that total
+ * offset from the first reading to the second is 0.
+ * There is an issue with SiS hardware where it will count
+ * picb down to 0, then update civ to the next value,
+ * then set the new picb to fragsize bytes. We can catch
+ * it between the civ update and the picb update, making
+ * it look as though we are 1 fragsize ahead of where we
+ * are. The next to we get the address though, it will
+ * be back in thdelay is more than long enough
+ * that we won't have to worry about the chip still being
+ * out of sync with reality ;-)
+ */
+ } while (civ != (inb(port + OFF_CIV) & 31) || offset != inw(port_picb));
+
+ data = ((civ + 1) * dmabuf->fragsize - (2 * offset)) % dmabuf->dmasize;
+ if (inw(port_picb) == 0)
+ data -= 2048;
+
+ return data;
+}
+
+/* Stop recording (lock held) */
+static inline void __stop_adc(struct ali_state *state)
+{
+ struct dmabuf *dmabuf = &state->dmabuf;
+ struct ali_card *card = state->card;
+
+ dmabuf->enable &= ~ADC_RUNNING;
+
+ outl((1 << 18) | (1 << 16), card->iobase + ALI_DMACR);
+ udelay(1);
+
+ outb(0, card->iobase + PI_CR);
+ while (inb(card->iobase + PI_CR) != 0);
+
+ // now clear any latent interrupt bits (like the halt bit)
+ outb(inb(card->iobase + PI_SR) | 0x001e, card->iobase + PI_SR);
+ outl(inl(card->iobase + ALI_INTERRUPTSR) & INT_PCMIN, card->iobase + ALI_INTERRUPTSR);
+}
+
+static void stop_adc(struct ali_state *state)
+{
+ struct ali_card *card = state->card;
+ unsigned long flags;
+ spin_lock_irqsave(&card->lock, flags);
+ __stop_adc(state);
+ spin_unlock_irqrestore(&card->lock, flags);
+}
+
+static inline void __start_adc(struct ali_state *state)
+{
+ struct dmabuf *dmabuf = &state->dmabuf;
+
+ if (dmabuf->count < dmabuf->dmasize && dmabuf->ready
+ && !dmabuf->enable && (dmabuf->trigger & PCM_ENABLE_INPUT)) {
+ dmabuf->enable |= ADC_RUNNING;
+ outb((1 << 4) | (1 << 2), state->card->iobase + PI_CR);
+ if (state->card->channel[0].used == 1)
+ outl(1, state->card->iobase + ALI_DMACR); // DMA CONTROL REGISTRER
+ udelay(100);
+ if (state->card->channel[2].used == 1)
+ outl((1 << 2), state->card->iobase + ALI_DMACR); //DMA CONTROL REGISTER
+ udelay(100);
+ }
+}
+
+static void start_adc(struct ali_state *state)
+{
+ struct ali_card *card = state->card;
+ unsigned long flags;
+
+ spin_lock_irqsave(&card->lock, flags);
+ __start_adc(state);
+ spin_unlock_irqrestore(&card->lock, flags);
+}
+
+/* stop playback (lock held) */
+static inline void __stop_dac(struct ali_state *state)
+{
+ struct dmabuf *dmabuf = &state->dmabuf;
+ struct ali_card *card = state->card;
+
+ dmabuf->enable &= ~DAC_RUNNING;
+ outl(0x00020000, card->iobase + 0x08);
+ outb(0, card->iobase + PO_CR);
+ while (inb(card->iobase + PO_CR) != 0)
+ cpu_relax();
+
+ outb(inb(card->iobase + PO_SR) | 0x001e, card->iobase + PO_SR);
+
+ outl(inl(card->iobase + ALI_INTERRUPTSR) & INT_PCMOUT, card->iobase + ALI_INTERRUPTSR);
+}
+
+static void stop_dac(struct ali_state *state)
+{
+ struct ali_card *card = state->card;
+ unsigned long flags;
+ spin_lock_irqsave(&card->lock, flags);
+ __stop_dac(state);
+ spin_unlock_irqrestore(&card->lock, flags);
+}
+
+static inline void __start_dac(struct ali_state *state)
+{
+ struct dmabuf *dmabuf = &state->dmabuf;
+ if (dmabuf->count > 0 && dmabuf->ready && !dmabuf->enable &&
+ (dmabuf->trigger & PCM_ENABLE_OUTPUT)) {
+ dmabuf->enable |= DAC_RUNNING;
+ outb((1 << 4) | (1 << 2), state->card->iobase + PO_CR);
+ outl((1 << 1), state->card->iobase + 0x08); //dma control register
+ }
+}
+
+static void start_dac(struct ali_state *state)
+{
+ struct ali_card *card = state->card;
+ unsigned long flags;
+ spin_lock_irqsave(&card->lock, flags);
+ __start_dac(state);
+ spin_unlock_irqrestore(&card->lock, flags);
+}
+
+/* stop codec and controller spdif out (lock held) */
+static inline void __stop_spdifout(struct ali_state *state)
+{
+ struct dmabuf *dmabuf = &state->dmabuf;
+ struct ali_card *card = state->card;
+
+ if (codec_independent_spdif_locked > 0) {
+ dmabuf->enable &= ~CODEC_SPDIFOUT_RUNNING;
+ outl((1 << 19), card->iobase + 0x08);
+ outb(0, card->iobase + CODECSPDIFOUT_CR);
+
+ while (inb(card->iobase + CODECSPDIFOUT_CR) != 0)
+ cpu_relax();
+
+ outb(inb(card->iobase + CODECSPDIFOUT_SR) | 0x001e, card->iobase + CODECSPDIFOUT_SR);
+ outl(inl(card->iobase + ALI_INTERRUPTSR) & INT_CODECSPDIFOUT, card->iobase + ALI_INTERRUPTSR);
+ } else {
+ if (controller_independent_spdif_locked > 0) {
+ dmabuf->enable &= ~CONTROLLER_SPDIFOUT_RUNNING;
+ outl((1 << 23), card->iobase + 0x08);
+ outb(0, card->iobase + CONTROLLERSPDIFOUT_CR);
+ while (inb(card->iobase + CONTROLLERSPDIFOUT_CR) != 0)
+ cpu_relax();
+ outb(inb(card->iobase + CONTROLLERSPDIFOUT_SR) | 0x001e, card->iobase + CONTROLLERSPDIFOUT_SR);
+ outl(inl(card->iobase + ALI_INTERRUPTSR) & INT_SPDIFOUT, card->iobase + ALI_INTERRUPTSR);
+ }
+ }
+}
+
+static void stop_spdifout(struct ali_state *state)
+{
+ struct ali_card *card = state->card;
+ unsigned long flags;
+ spin_lock_irqsave(&card->lock, flags);
+ __stop_spdifout(state);
+ spin_unlock_irqrestore(&card->lock, flags);
+}
+
+static inline void __start_spdifout(struct ali_state *state)
+{
+ struct dmabuf *dmabuf = &state->dmabuf;
+ if (dmabuf->count > 0 && dmabuf->ready && !dmabuf->enable &&
+ (dmabuf->trigger & SPDIF_ENABLE_OUTPUT)) {
+ if (codec_independent_spdif_locked > 0) {
+ dmabuf->enable |= CODEC_SPDIFOUT_RUNNING;
+ outb((1 << 4) | (1 << 2), state->card->iobase + CODECSPDIFOUT_CR);
+ outl((1 << 3), state->card->iobase + 0x08); //dma control register
+ } else {
+ if (controller_independent_spdif_locked > 0) {
+ dmabuf->enable |= CONTROLLER_SPDIFOUT_RUNNING;
+ outb((1 << 4) | (1 << 2), state->card->iobase + CONTROLLERSPDIFOUT_CR);
+ outl((1 << 7), state->card->iobase + 0x08); //dma control register
+ }
+ }
+ }
+}
+
+static void start_spdifout(struct ali_state *state)
+{
+ struct ali_card *card = state->card;
+ unsigned long flags;
+ spin_lock_irqsave(&card->lock, flags);
+ __start_spdifout(state);
+ spin_unlock_irqrestore(&card->lock, flags);
+}
+
+#define DMABUF_DEFAULTORDER (16-PAGE_SHIFT)
+#define DMABUF_MINORDER 1
+
+/* allocate DMA buffer, playback , recording,spdif out buffer should be allocated separately */
+static int alloc_dmabuf(struct ali_state *state)
+{
+ struct dmabuf *dmabuf = &state->dmabuf;
+ void *rawbuf = NULL;
+ int order, size;
+ struct page *page, *pend;
+
+ /* If we don't have any oss frag params, then use our default ones */
+ if (dmabuf->ossmaxfrags == 0)
+ dmabuf->ossmaxfrags = 4;
+ if (dmabuf->ossfragsize == 0)
+ dmabuf->ossfragsize = (PAGE_SIZE << DMABUF_DEFAULTORDER) / dmabuf->ossmaxfrags;
+ size = dmabuf->ossfragsize * dmabuf->ossmaxfrags;
+
+ if (dmabuf->rawbuf && (PAGE_SIZE << dmabuf->buforder) == size)
+ return 0;
+ /* alloc enough to satisfy the oss params */
+ for (order = DMABUF_DEFAULTORDER; order >= DMABUF_MINORDER; order--) {
+ if ((PAGE_SIZE << order) > size)
+ continue;
+ if ((rawbuf = pci_alloc_consistent(state->card->pci_dev,
+ PAGE_SIZE << order,
+ &dmabuf->dma_handle)))
+ break;
+ }
+ if (!rawbuf)
+ return -ENOMEM;
+
+ dmabuf->ready = dmabuf->mapped = 0;
+ dmabuf->rawbuf = rawbuf;
+ dmabuf->buforder = order;
+
+ /* now mark the pages as reserved; otherwise remap_pfn_range doesn't do what we want */
+ pend = virt_to_page(rawbuf + (PAGE_SIZE << order) - 1);
+ for (page = virt_to_page(rawbuf); page <= pend; page++)
+ SetPageReserved(page);
+ return 0;
+}
+
+/* free DMA buffer */
+static void dealloc_dmabuf(struct ali_state *state)
+{
+ struct dmabuf *dmabuf = &state->dmabuf;
+ struct page *page, *pend;
+
+ if (dmabuf->rawbuf) {
+ /* undo marking the pages as reserved */
+ pend = virt_to_page(dmabuf->rawbuf + (PAGE_SIZE << dmabuf->buforder) - 1);
+ for (page = virt_to_page(dmabuf->rawbuf); page <= pend; page++)
+ ClearPageReserved(page);
+ pci_free_consistent(state->card->pci_dev,
+ PAGE_SIZE << dmabuf->buforder,
+ dmabuf->rawbuf, dmabuf->dma_handle);
+ }
+ dmabuf->rawbuf = NULL;
+ dmabuf->mapped = dmabuf->ready = 0;
+}
+
+static int prog_dmabuf(struct ali_state *state, unsigned rec)
+{
+ struct dmabuf *dmabuf = &state->dmabuf;
+ struct ali_channel *c = NULL;
+ struct sg_item *sg;
+ unsigned long flags;
+ int ret;
+ unsigned fragint;
+ int i;
+
+ spin_lock_irqsave(&state->card->lock, flags);
+ if (dmabuf->enable & DAC_RUNNING)
+ __stop_dac(state);
+ if (dmabuf->enable & ADC_RUNNING)
+ __stop_adc(state);
+ if (dmabuf->enable & CODEC_SPDIFOUT_RUNNING)
+ __stop_spdifout(state);
+ if (dmabuf->enable & CONTROLLER_SPDIFOUT_RUNNING)
+ __stop_spdifout(state);
+
+ dmabuf->total_bytes = 0;
+ dmabuf->count = dmabuf->error = 0;
+ dmabuf->swptr = dmabuf->hwptr = 0;
+ spin_unlock_irqrestore(&state->card->lock, flags);
+
+ /* allocate DMA buffer, let alloc_dmabuf determine if we are already
+ * allocated well enough or if we should replace the current buffer
+ * (assuming one is already allocated, if it isn't, then allocate it).
+ */
+ if ((ret = alloc_dmabuf(state)))
+ return ret;
+
+ /* FIXME: figure out all this OSS fragment stuff */
+ /* I did, it now does what it should according to the OSS API. DL */
+ /* We may not have realloced our dmabuf, but the fragment size to
+ * fragment number ratio may have changed, so go ahead and reprogram
+ * things
+ */
+
+ dmabuf->dmasize = PAGE_SIZE << dmabuf->buforder;
+ dmabuf->numfrag = SG_LEN;
+ dmabuf->fragsize = dmabuf->dmasize / dmabuf->numfrag;
+ dmabuf->fragsamples = dmabuf->fragsize >> 1;
+ dmabuf->userfragsize = dmabuf->ossfragsize;
+ dmabuf->userfrags = dmabuf->dmasize / dmabuf->ossfragsize;
+
+ memset(dmabuf->rawbuf, 0, dmabuf->dmasize);
+
+ if (dmabuf->ossmaxfrags == 4) {
+ fragint = 8;
+ dmabuf->fragshift = 2;
+ } else if (dmabuf->ossmaxfrags == 8) {
+ fragint = 4;
+ dmabuf->fragshift = 3;
+ } else if (dmabuf->ossmaxfrags == 16) {
+ fragint = 2;
+ dmabuf->fragshift = 4;
+ } else {
+ fragint = 1;
+ dmabuf->fragshift = 5;
+ }
+ /*
+ * Now set up the ring
+ */
+
+ if (rec == 1)
+ c = dmabuf->read_channel;
+ else if (rec == 2)
+ c = dmabuf->codec_spdifout_channel;
+ else if (rec == 3)
+ c = dmabuf->controller_spdifout_channel;
+ else if (rec == 0)
+ c = dmabuf->write_channel;
+ if (c != NULL) {
+ sg = &c->sg[0];
+ /*
+ * Load up 32 sg entries and take an interrupt at half
+ * way (we might want more interrupts later..)
+ */
+ for (i = 0; i < dmabuf->numfrag; i++) {
+ sg->busaddr =
+ virt_to_bus(dmabuf->rawbuf +
+ dmabuf->fragsize * i);
+ // the card will always be doing 16bit stereo
+ sg->control = dmabuf->fragsamples;
+ sg->control |= CON_BUFPAD; //I modify
+ // set us up to get IOC interrupts as often as needed to
+ // satisfy numfrag requirements, no more
+ if (((i + 1) % fragint) == 0) {
+ sg->control |= CON_IOC;
+ }
+ sg++;
+ }
+ spin_lock_irqsave(&state->card->lock, flags);
+ outb(2, state->card->iobase + c->port + OFF_CR); /* reset DMA machine */
+ outl(virt_to_bus(&c->sg[0]), state->card->iobase + c->port + OFF_BDBAR);
+ outb(0, state->card->iobase + c->port + OFF_CIV);
+ outb(0, state->card->iobase + c->port + OFF_LVI);
+ spin_unlock_irqrestore(&state->card->lock, flags);
+ }
+ /* set the ready flag for the dma buffer */
+ dmabuf->ready = 1;
+ return 0;
+}
+
+static void __ali_update_lvi(struct ali_state *state, int rec)
+{
+ struct dmabuf *dmabuf = &state->dmabuf;
+ int x, port;
+ port = state->card->iobase;
+ if (rec == 1)
+ port += dmabuf->read_channel->port;
+ else if (rec == 2)
+ port += dmabuf->codec_spdifout_channel->port;
+ else if (rec == 3)
+ port += dmabuf->controller_spdifout_channel->port;
+ else if (rec == 0)
+ port += dmabuf->write_channel->port;
+ /* if we are currently stopped, then our CIV is actually set to our
+ * *last* sg segment and we are ready to wrap to the next. However,
+ * if we set our LVI to the last sg segment, then it won't wrap to
+ * the next sg segment, it won't even get a start. So, instead, when
+ * we are stopped, we set both the LVI value and also we increment
+ * the CIV value to the next sg segment to be played so that when
+ * we call start_{dac,adc}, things will operate properly
+ */
+ if (!dmabuf->enable && dmabuf->ready) {
+ if (rec && dmabuf->count < dmabuf->dmasize && (dmabuf->trigger & PCM_ENABLE_INPUT)) {
+ outb((inb(port + OFF_CIV) + 1) & 31, port + OFF_LVI);
+ __start_adc(state);
+ while (! (inb(port + OFF_CR) & ((1 << 4) | (1 << 2))))
+ cpu_relax();
+ } else if (!rec && dmabuf->count && (dmabuf->trigger & PCM_ENABLE_OUTPUT)) {
+ outb((inb(port + OFF_CIV) + 1) & 31, port + OFF_LVI);
+ __start_dac(state);
+ while (!(inb(port + OFF_CR) & ((1 << 4) | (1 << 2))))
+ cpu_relax();
+ } else if (rec && dmabuf->count && (dmabuf->trigger & SPDIF_ENABLE_OUTPUT)) {