summaryrefslogtreecommitdiff
path: root/kernel/drv/oss_atiaudio/oss_atiaudio.c
diff options
context:
space:
mode:
Diffstat (limited to 'kernel/drv/oss_atiaudio/oss_atiaudio.c')
-rw-r--r--kernel/drv/oss_atiaudio/oss_atiaudio.c1147
1 files changed, 1147 insertions, 0 deletions
diff --git a/kernel/drv/oss_atiaudio/oss_atiaudio.c b/kernel/drv/oss_atiaudio/oss_atiaudio.c
new file mode 100644
index 0000000..6727894
--- /dev/null
+++ b/kernel/drv/oss_atiaudio/oss_atiaudio.c
@@ -0,0 +1,1147 @@
+/*
+ * Purpose: Driver for the ATI IXP (AC97) audio controller
+ */
+/*
+ *
+ * This file is part of Open Sound System.
+ *
+ * Copyright (C) 4Front Technologies 1996-2008.
+ *
+ * This this source file is released under GPL v2 license (no other versions).
+ * See the COPYING file included in the main directory of this source
+ * distribution for the license terms and conditions.
+ *
+ */
+
+#include "oss_atiaudio_cfg.h"
+#include "oss_pci.h"
+#include "ac97.h"
+
+#define ATI_VENDOR_ID 0x1002
+#define ATI_DEVICE_IXP 0x4341
+#define ATI_DEVICE_IXP300 0x4361
+#define ATI_DEVICE_IXP400 0x4370
+
+#define MAX_PORTC 3
+#define BDL_SIZE 64
+
+typedef struct
+{
+ int open_mode;
+ int speed, bits, channels;
+ int audio_enabled;
+ int trigger_bits;
+ int audiodev;
+ int port_type;
+#define DF_ANALOG 0
+#define DF_SPDIF 1
+}
+ATI_portc;
+
+typedef struct
+{
+ unsigned int addr;
+ unsigned short status;
+ unsigned short size;
+ unsigned int next;
+}
+bdl_t;
+
+typedef struct
+{
+ oss_device_t *osdev;
+ oss_native_word membar_addr;
+ char *membar_virt;
+ int irq;
+ oss_mutex_t mutex;
+ oss_mutex_t low_mutex;
+
+ /* Mixer */
+ ac97_devc ac97devc;
+ int mixer_dev;
+ int inverted;
+
+ /* Audio parameters */
+ int open_mode;
+
+ /* Buffer Descriptor List */
+ char *bdlBuffer;
+ bdl_t *playBDL, *recBDL, *spdifBDL;
+ oss_native_word playBDL_phys, recBDL_phys, spdifBDL_phys;
+ oss_dma_handle_t bldbuf_dma_handle;
+
+ int play_currbuf, play_currfrag;
+ int rec_currbuf, rec_currfrag;
+ char *chip_name;
+ ATI_portc portc[MAX_PORTC];
+ int play_frag_index[BDL_SIZE];
+ int rec_frag_index[BDL_SIZE];
+}
+ATI_devc;
+
+
+/* Mem mapped I/O registers */
+#define READL(o,a) *(volatile unsigned int*)(devc->membar_virt+(a))
+#define READW(o,a) *(volatile unsigned short*)(devc->membar_virt+(a))
+#define READB(o,a) *(volatile unsigned char*)(devc->membar_virt+(a))
+#define WRITEL(o,d,a) *(volatile unsigned int*)(devc->membar_virt+(a))=d
+#define WRITEW(o,d,a) *(volatile unsigned short*)(devc->membar_virt+(a))=d
+#define WRITEB(o,d,a) *(volatile unsigned char*)(devc->membar_virt+(a))=d
+
+
+static int
+ac97_ready (void *devc_)
+{
+ ATI_devc *devc = devc_;
+ int timeout = 10000;
+
+ while (READL (devc->osdev, 0x0c) & 0x100)
+ {
+ if (!timeout--)
+ {
+ cmn_err (CE_WARN, "codec ready timed out\n");
+ return OSS_EIO;
+ }
+ oss_udelay (10);
+ }
+ return 0;
+}
+
+static int
+ac97_read (void *devc_, int reg)
+{
+ ATI_devc *devc = devc_;
+ unsigned int data = 0;
+ unsigned int addr = 0;
+ int timeout;
+ oss_native_word flags;
+
+ MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags);
+
+again:
+ if (ac97_ready (devc) < 0)
+ {
+ cmn_err (CE_WARN, "ac97 not ready\n");
+ MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
+ return OSS_EIO;
+ }
+
+ addr = (unsigned int) (reg << 9) | (1 << 8) | (1 << 2);
+ WRITEL (devc->osdev, addr, 0x0c); /* Set read commmand */
+
+ if (ac97_ready (devc) < 0)
+ {
+ cmn_err (CE_WARN, "ac97 not ready\n");
+ MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
+ return OSS_EIO;
+ }
+
+ timeout = 1000;
+
+ for (;;)
+ {
+ data = READL (devc->osdev, 0x10); /* ac97 data port */
+ if (data & 0x100)
+ {
+ /* if the register returned isn't the reg sent then resend command */
+ if ((data & 0xFE00) != (reg << 9))
+ goto again;
+ else
+ break;
+ }
+
+ if (!--timeout)
+ {
+ cmn_err (CE_WARN, "AC97 read timed out\n");
+ break;
+ }
+ }
+ MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
+ return (data >> 16);
+}
+
+
+static int
+ac97_write (void *devc_, int reg, int data)
+{
+ ATI_devc *devc = devc_;
+ oss_native_word flags;
+ unsigned int val;
+
+ MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags);
+ if (ac97_ready (devc) < 0)
+ {
+ cmn_err (CE_WARN, "AC97 not ready\n");
+ MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
+ return 0;
+ }
+
+ val = (data << 16) | (reg << 9) | 0x100;
+ WRITEL (devc->osdev, val, 0x0c);
+ MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
+ return 1;
+}
+
+static int
+ATIIXPintr (oss_device_t * osdev)
+{
+ int status, i;
+ int serviced = 0;
+ ATI_devc *devc = (ATI_devc *) osdev->devc;
+ ATI_portc *portc;
+ /* oss_native_word flags; */
+
+/*
+ * TODO: Enable the mutexes and move inputintr/outputintr calls outside the
+ * mutex region.
+ */
+
+ /* MUTEX_ENTER (devc->mutex, flags); */
+ /* Handle Global Interrupts */
+ status = READL (devc->osdev, 0x00);
+
+ /* handle PCM output */
+ if (status & 0x08)
+ for (i = 0; i < MAX_PORTC; i++)
+ {
+ portc = &devc->portc[i];
+ if ((portc->trigger_bits & PCM_ENABLE_OUTPUT)
+ && (portc->port_type == DF_ANALOG))
+ {
+ dmap_t *dmap = audio_engines[portc->audiodev]->dmap_out;
+ int ptr=0, n = 0;
+ int timeout = 1000;
+ serviced = 1;
+ while (timeout--)
+ {
+ ptr = READL (devc->osdev, 0x44);
+
+ if (ptr < dmap->dmabuf_phys)
+ continue;
+
+ ptr -= dmap->dmabuf_phys;
+
+ if (ptr >= dmap->bytes_in_use)
+ continue;
+
+ break;
+ }
+
+ if (dmap->fragment_size == 0)
+ {
+ cmn_err (CE_WARN, "dmap->fragment_size == 0\n");
+ continue;
+ }
+
+ ptr /= dmap->fragment_size;
+
+ if ((ptr < 0) || (ptr > dmap->nfrags))
+ ptr = 0;
+
+ n = 0;
+ while ((dmap_get_qhead (dmap) != ptr) && (n++ < dmap->nfrags))
+ oss_audio_outputintr (portc->audiodev, 1);
+ }
+ }
+
+ /* handle PCM input */
+ if (status & 0x02)
+ for (i = 0; i < MAX_PORTC; i++)
+ {
+ portc = &devc->portc[i];
+ if ((portc->trigger_bits & PCM_ENABLE_INPUT)
+ && (portc->port_type == DF_ANALOG))
+ {
+ dmap_t *dmap = audio_engines[portc->audiodev]->dmap_in;
+ int ptr=0, n = 0;
+ int timeout = 1000;
+ serviced = 1;
+
+ while (timeout--)
+ {
+ ptr = READL (devc->osdev, 0x2c);
+
+ if (ptr < dmap->dmabuf_phys)
+ continue;
+
+ ptr -= dmap->dmabuf_phys;
+
+ if (ptr >= dmap->bytes_in_use)
+ continue;
+
+ break;
+ }
+
+ if (dmap->fragment_size == 0)
+ {
+ cmn_err (CE_WARN, "dmap->fragment_size == 0\n");
+ continue;
+ }
+
+ ptr /= dmap->fragment_size;
+
+ if ((ptr < 0) || (ptr > dmap->nfrags))
+ ptr = 0;
+
+ n = 0;
+ while ((dmap_get_qtail (dmap) != ptr) && (n++ < dmap->nfrags))
+ oss_audio_inputintr (portc->audiodev, 0);
+ }
+ }
+
+ /* handle SPDIF output */
+ if (status & 0x20)
+ {
+ portc = &devc->portc[2];
+ if ((portc->trigger_bits & PCM_ENABLE_OUTPUT)
+ && (portc->port_type == DF_SPDIF))
+ {
+ dmap_t *dmap = audio_engines[portc->audiodev]->dmap_out;
+ int ptr=0, n = 0;
+ int timeout = 1000;
+ serviced = 1;
+
+ while (timeout--)
+ {
+ ptr = READL (devc->osdev, 0x5c);
+ if (ptr < dmap->dmabuf_phys)
+ continue;
+ ptr -= dmap->dmabuf_phys;
+ if (ptr >= dmap->bytes_in_use)
+ continue;
+ break;
+ }
+
+ if (dmap->fragment_size == 0)
+ cmn_err (CE_WARN, "dmap->fragment_size == 0\n");
+ else
+ {
+ ptr /= dmap->fragment_size;
+
+ if ((ptr < 0) || (ptr > dmap->nfrags))
+ ptr = 0;
+
+ n = 0;
+ while ((dmap_get_qhead (dmap) != ptr) && (n++ < dmap->nfrags))
+ oss_audio_outputintr (portc->audiodev, 1);
+ }
+ }
+ }
+
+ /* Acknowledge Interrupts */
+ WRITEL (devc->osdev, status, 0x00);
+ /* MUTEX_EXIT (devc->mutex, flags); */
+ return serviced;
+}
+
+/*
+ * Audio routines
+ */
+
+static int
+ATI_audio_set_rate (int dev, int arg)
+{
+ ATI_portc *portc = audio_engines[dev]->portc;
+
+ if (arg == 0)
+ return portc->speed;
+
+ if (audio_engines[dev]->flags & ADEV_FIXEDRATE)
+ arg = 48000;
+
+ if (arg > 48000)
+ arg = 48000;
+ if (arg < 5000)
+ arg = 5000;
+ portc->speed = arg;
+ return portc->speed;
+}
+
+static short
+ATI_audio_set_channels (int dev, short arg)
+{
+ ATI_portc *portc = audio_engines[dev]->portc;
+
+ if ((arg == 1) || (arg == 2))
+ {
+ audio_engines[dev]->flags |= ADEV_STEREOONLY;
+ arg = 2;
+ }
+ else
+ audio_engines[dev]->flags &= ~ADEV_STEREOONLY;
+
+ if (arg>6)
+ arg=6;
+
+ if ((arg != 1) && (arg != 2) && (arg != 4) && (arg != 6))
+ return portc->channels;
+ portc->channels = arg;
+
+ return portc->channels;
+}
+
+static unsigned int
+ATI_audio_set_format (int dev, unsigned int arg)
+{
+ ATI_portc *portc = audio_engines[dev]->portc;
+
+ if (arg == 0)
+ return portc->bits;
+
+ if (!(arg & (AFMT_U8 | AFMT_S16_LE | AFMT_AC3)))
+ return portc->bits;
+ portc->bits = arg;
+
+ return portc->bits;
+}
+
+/*ARGSUSED*/
+static int
+ATI_audio_ioctl (int dev, unsigned int cmd, ioctl_arg arg)
+{
+ return OSS_EINVAL;
+}
+
+static void ATI_audio_trigger (int dev, int state);
+
+static void
+ATI_audio_reset (int dev)
+{
+ ATI_audio_trigger (dev, 0);
+}
+
+static void
+ATI_audio_reset_input (int dev)
+{
+ ATI_portc *portc = audio_engines[dev]->portc;
+ ATI_audio_trigger (dev, portc->trigger_bits & ~PCM_ENABLE_INPUT);
+}
+
+static void
+ATI_audio_reset_output (int dev)
+{
+ ATI_portc *portc = audio_engines[dev]->portc;
+ ATI_audio_trigger (dev, portc->trigger_bits & ~PCM_ENABLE_OUTPUT);
+}
+
+/*ARGSUSED*/
+static int
+ATI_audio_open (int dev, int mode, int openflags)
+{
+ ATI_portc *portc = audio_engines[dev]->portc;
+ ATI_devc *devc = audio_engines[dev]->devc;
+ oss_native_word flags;
+
+ MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
+ if (portc->open_mode || (devc->open_mode & mode))
+ {
+ MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
+ return OSS_EBUSY;
+ }
+
+ portc->open_mode = mode;
+ portc->audio_enabled &= ~mode;
+ devc->open_mode |= mode;
+ MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
+
+ return 0;
+}
+
+static void
+ATI_audio_close (int dev, int mode)
+{
+ ATI_portc *portc = audio_engines[dev]->portc;
+ ATI_devc *devc = audio_engines[dev]->devc;
+
+ ATI_audio_reset (dev);
+ portc->open_mode = 0;
+ devc->open_mode &= ~mode;
+ portc->audio_enabled &= ~mode;
+}
+
+/*ARGSUSED*/
+static void
+ATI_audio_output_block (int dev, oss_native_word buf, int count,
+ int fragsize, int intrflag)
+{
+ ATI_portc *portc = audio_engines[dev]->portc;
+
+ portc->audio_enabled |= PCM_ENABLE_OUTPUT;
+ portc->trigger_bits &= ~PCM_ENABLE_OUTPUT;
+}
+
+/*ARGSUSED*/
+static void
+ATI_audio_start_input (int dev, oss_native_word buf, int count,
+ int fragsize, int intrflag)
+{
+ ATI_portc *portc = audio_engines[dev]->portc;
+
+ portc->audio_enabled |= PCM_ENABLE_INPUT;
+ portc->trigger_bits &= ~PCM_ENABLE_INPUT;
+}
+
+static void
+ATI_enable_dma (int dev, int direction, int enable)
+{
+ ATI_devc *devc = audio_engines[dev]->devc;
+ ATI_portc *portc = audio_engines[dev]->portc;
+
+ if (enable)
+ {
+ if (direction)
+ {
+ /* flush fifo and enable analog or spdif DMA */
+ WRITEL (devc->osdev, READL (devc->osdev, 0x88) | 1, 0x88);
+
+ if (portc->port_type == DF_ANALOG)
+ WRITEL (devc->osp, READL (devc->osp, 0x08) | 1 << 9, 0x08);
+ else
+ WRITEL (devc->osp, READL (devc->osp, 0x08) | 1 << 10, 0x08);
+
+ }
+ else
+ {
+ /* flush fifo */
+ WRITEL (devc->osdev, READL (devc->osdev, 0x88) | 2, 0x88);
+ WRITEL (devc->osdev, READL (devc->osdev, 0x08) | 1 << 8, 0x08);
+ }
+ }
+ else
+ {
+ if (direction)
+ {
+ if (portc->port_type == DF_ANALOG)
+ WRITEL (devc->osp, READL (devc->osp, 0x08) & ~(1 << 9), 0x08);
+ else
+ WRITEL (devc->osp, READL (devc->osp, 0x08) & ~(1 << 10), 0x08);
+ }
+ else
+ WRITEL (devc->osdev, READL (devc->osdev, 0x08) & ~(1 << 8), 0x08);
+ }
+}
+
+static void
+ATI_audio_trigger (int dev, int state)
+{
+ ATI_devc *devc = audio_engines[dev]->devc;
+ ATI_portc *portc = audio_engines[dev]->portc;
+ oss_native_word flags;
+
+ MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
+
+ if (portc->open_mode & OPEN_WRITE)
+ {
+ if (state & PCM_ENABLE_OUTPUT)
+ {
+ if ((portc->audio_enabled & PCM_ENABLE_OUTPUT) &&
+ !(portc->trigger_bits & PCM_ENABLE_OUTPUT))
+ {
+ ATI_enable_dma (dev, 1, 1);
+
+ /* set BDL & enable analog or SPDIF DMA */
+ if (portc->port_type == DF_ANALOG)
+ {
+ WRITEL (devc->osp, devc->playBDL_phys | 1, 0x38);
+ WRITEL (devc->osp, READL (devc->osp, 0x08) | 0x04, 0x08);
+ }
+ else /* SPDIF */
+ {
+ WRITEL (devc->osp, devc->spdifBDL_phys | 1, 0x50);
+ WRITEL (devc->osp, READL (devc->osp, 0x08) | 0x10, 0x08);
+ }
+
+ /* set bus busy */
+ WRITEL (devc->osdev, READL (devc->osdev, 0x04) | 1 << 14, 0x04);
+
+ portc->trigger_bits |= PCM_ENABLE_OUTPUT;
+ }
+ }
+ else
+ {
+ if ((portc->audio_enabled & PCM_ENABLE_OUTPUT) &&
+ (portc->trigger_bits & PCM_ENABLE_OUTPUT))
+ {
+ portc->audio_enabled &= ~PCM_ENABLE_OUTPUT;
+ portc->trigger_bits &= ~PCM_ENABLE_OUTPUT;
+
+ ATI_enable_dma (dev, 1, 0);
+
+ /* Stop Anlog or SPDIF DMA */
+ if (portc->port_type == DF_ANALOG)
+ WRITEL (devc->osp, READL (devc->osp, 0x08) & ~0x04, 0x08);
+ else
+ WRITEL (devc->osp, READL (devc->osp, 0x08) & ~0x10, 0x08);
+
+ /* set bus clear */
+ WRITEL (devc->osdev, READL (devc->osdev, 0x04) & ~(1 << 14),
+ 0x04);
+
+ }
+ }
+ }
+
+ if (portc->open_mode & OPEN_READ)
+ {
+ if (state & PCM_ENABLE_INPUT)
+ {
+ if ((portc->audio_enabled & PCM_ENABLE_INPUT) &&
+ !(portc->trigger_bits & PCM_ENABLE_INPUT))
+ {
+ portc->trigger_bits |= PCM_ENABLE_INPUT;
+ ATI_enable_dma (dev, 0, 1);
+ WRITEL (devc->osdev, devc->recBDL_phys | 1, 0x20);
+
+ /* enable audio intput and enable input DMA */
+ WRITEL (devc->osdev, READL (devc->osdev, 0x08) | 0x02, 0x08);
+ /* set bus busy */
+ WRITEL (devc->osdev, READL (devc->osdev, 0x04) | (1 << 14),
+ 0x04);
+ }
+ }
+ else
+ {
+ if ((portc->audio_enabled & PCM_ENABLE_INPUT) &&
+ (portc->trigger_bits & PCM_ENABLE_INPUT))
+ {
+ portc->audio_enabled &= ~PCM_ENABLE_INPUT;
+ portc->trigger_bits &= ~PCM_ENABLE_INPUT;
+ ATI_enable_dma (dev, 0, 0);
+ /* stop audio input */
+ WRITEL (devc->osdev, READL (devc->osdev, 0x08) & ~0x02, 0x08);
+ /* clear bus busy */
+ WRITEL (devc->osdev, READL (devc->osdev, 0x04) & ~(1 << 14),
+ 0x04);
+ }
+ }
+ }
+ MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
+}
+
+/*ARGSUSED*/
+static int
+ATI_audio_prepare_for_input (int dev, int bsize, int bcount)
+{
+ ATI_devc *devc = audio_engines[dev]->devc;
+ ATI_portc *portc = audio_engines[dev]->portc;
+ dmap_t *dmap = audio_engines[dev]->dmap_in;
+ int i, n;
+ unsigned int data;
+ oss_native_word flags;
+
+ MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
+
+ /* set the bits */
+ data = READL (devc->osdev, 0x08) & ~(1 << 21);
+ if (portc->bits == 16)
+ data |= (1 << 21);
+ WRITEL (devc->osdev, data, 0x08); /*set 8/16 bits */
+
+ n = dmap->nfrags;
+ if (n > BDL_SIZE)
+ {
+ cmn_err (CE_WARN, "Internal error - BDL too small\n");
+ return OSS_EIO;
+ }
+
+ for (i = 0; i < n; i++)
+ {
+ int next = i + 1;
+ if (i == n - 1)
+ next = 0;
+
+ devc->recBDL[i].addr = dmap->dmabuf_phys + (i * dmap->fragment_size);
+ devc->recBDL[i].status = 0;
+ devc->recBDL[i].size = dmap->fragment_size >> 2;
+ devc->recBDL[i].next = devc->recBDL_phys + next * sizeof (bdl_t);
+ }
+
+ portc->audio_enabled &= ~PCM_ENABLE_INPUT;
+ portc->trigger_bits &= ~PCM_ENABLE_INPUT;
+
+ MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
+ return 0;
+}
+
+/*ARGSUSED*/
+static int
+ATI_audio_prepare_for_output (int dev, int bsize, int bcount)
+{
+ ATI_devc *devc = audio_engines[dev]->devc;
+ ATI_portc *portc = audio_engines[dev]->portc;
+ dmap_t *dmap = audio_engines[dev]->dmap_out;
+ int i, n;
+ unsigned int data;
+ oss_native_word flags;
+
+ MUTEX_ENTER_IRQDISABLE (devc->mutex, flags);
+
+
+ if (portc->port_type == DF_SPDIF)
+ {
+ ac97_spdif_setup (devc->mixer_dev, portc->speed, portc->bits);
+
+ if (portc->bits == AFMT_AC3)
+ {
+ portc->channels = 2;
+ portc->bits = 16;
+ }
+
+ n = dmap->nfrags;
+ if (n > BDL_SIZE)
+ n = BDL_SIZE;
+
+ for (i = 0; i < n; i++)
+ {
+ int next = i + 1;
+ if (i == n - 1)
+ next = 0;
+
+ devc->spdifBDL[i].addr =
+ dmap->dmabuf_phys + (i * dmap->fragment_size);
+ devc->spdifBDL[i].status = 0;
+ devc->spdifBDL[i].size = dmap->fragment_size >> 2;
+ devc->spdifBDL[i].next =
+ devc->spdifBDL_phys + next * sizeof (bdl_t);
+ }
+
+ }
+ else
+ {
+ data = READL (devc->osdev, 0x34) & ~0x3ff;
+ switch (portc->channels)
+ {
+ case 6:
+ data |= 0x30; /* slots 7, 8 */
+ break;
+ case 4:
+ data |= 0x48; /* slts 6, 9 */
+ break;
+ default:
+ data |= 0x03; /* slots 3, 4 */
+ break;
+ }
+ data |= 4 << 11;
+ WRITEL (devc->osdev, data, 0x34);
+
+ /* set the bits */
+ data = READL (devc->osdev, 0x08) & ~(1 << 22);
+ if (portc->bits == 16)
+ data |= (1 << 22);
+ WRITEL (devc->osdev, data, 0x08); /*set 8/16 bits */
+#if 0
+ /* set 6 channel reorder */
+ if (portc->channels >= 6)
+ WRITEL (devc->osdev, 0x1, 0x84);
+ else
+ WRITEL (devc->osdev, 0x00, 0x84);
+#endif
+ n = dmap->nfrags;
+ if (n > BDL_SIZE)
+ {
+ cmn_err (CE_WARN, "Internal error - BDL too small\n");
+ return OSS_EIO;
+ }
+
+ for (i = 0; i < n; i++)
+ {
+ int next = i + 1;
+ if (i == n - 1)
+ next = 0;
+
+ devc->playBDL[i].addr =
+ dmap->dmabuf_phys + (i * dmap->fragment_size);
+ devc->playBDL[i].status = 0;
+ devc->playBDL[i].size = dmap->fragment_size >> 2;
+ devc->playBDL[i].next = devc->playBDL_phys + next * sizeof (bdl_t);
+ }
+ }
+ portc->audio_enabled &= ~PCM_ENABLE_OUTPUT;
+ portc->trigger_bits &= ~PCM_ENABLE_OUTPUT;
+ MUTEX_EXIT_IRQRESTORE (devc->mutex, flags);
+ return 0;
+}
+
+static int
+ATI_get_buffer_pointer (int dev, dmap_t * dmap, int direction)
+{
+ ATI_devc *devc = audio_engines[dev]->devc;
+ ATI_portc *portc = audio_engines[dev]->portc;
+ unsigned int f = 0;
+ oss_native_word flags;
+ int loop = 100;
+
+ MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags);
+ /* we need to read the regs a couple of times because of possible h/w bugs */
+ while (loop--)
+ {
+ if (direction == PCM_ENABLE_OUTPUT)
+ {
+ if (portc->port_type == DF_ANALOG)
+ f = READL (devc->osdev, 0x44);
+ else
+ f = READL (devc->osdev, 0x5c);
+ }
+
+ if (direction == PCM_ENABLE_INPUT)
+ {
+ f = READL (devc->osdev, 0x2c);
+ }
+
+ if (f < dmap->dmabuf_phys)
+ continue;
+
+ f -= dmap->dmabuf_phys;
+
+ if (f >= dmap->bytes_in_use)
+ continue;
+
+ MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
+ return f;
+ }
+
+ MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags);
+ return 0;
+}
+
+static const audiodrv_t ATI_audio_driver = {
+ ATI_audio_open,
+ ATI_audio_close,
+ ATI_audio_output_block,
+ ATI_audio_start_input,
+ ATI_audio_ioctl,
+ ATI_audio_prepare_for_input,
+ ATI_audio_prepare_for_output,
+ ATI_audio_reset,
+ NULL,
+ NULL,
+ ATI_audio_reset_input,
+ ATI_audio_reset_output,
+ ATI_audio_trigger,
+ ATI_audio_set_rate,
+ ATI_audio_set_format,
+ ATI_audio_set_channels,
+ NULL,
+ NULL,
+ NULL, /* ATI_check_input, */
+ NULL, /* ATI_check_output, */
+ NULL, /* ATI_alloc_buffer, */
+ NULL, /* ATI_free_buffer, */
+ NULL,
+ NULL,
+ ATI_get_buffer_pointer
+};
+
+static int
+init_ATI (ATI_devc * devc)
+{
+ int my_mixer, my_dev, opts;
+ int i, timeout;
+ oss_native_word phaddr;
+
+ /* Power up */
+ WRITEL (devc->osdev, READL (devc->osdev, 0x08) & ~1, 0x08);
+ oss_udelay (20);
+ /* check if the ACLink is Active */
+ timeout = 10;
+ while (!(READL (devc->osdev, 0x08) & (1 << 28)))
+ {
+ WRITEL (devc->osdev, READL (devc->osdev, 0x08) | (1 << 30), 0x08); /* assert sync */
+ oss_udelay (10);
+ if (READL (devc->osdev, 0x08) & (1 << 28))
+ break;
+
+ /* set AC97 reset field to 0 */
+ WRITEL (devc->osdev, READL (devc->osdev, 0x08) & ~(0x80000000), 0x08);
+ oss_udelay (10);
+
+ /* set AC97 reset field to 1 */
+ WRITEL (devc->osdev, READL (devc->osdev, 0x08) | (0x80000000), 0x08);
+ oss_udelay (10);
+
+ if (READL (devc->osdev, 0x08) & (1 << 28))
+ break;
+ if (!--timeout)
+ {
+ cmn_err (CE_WARN, "Timed out waiting for AC97 Link ready\n");
+ break;
+ }
+ }
+
+ /* set ac97 reset field to 1: deassert */
+ WRITEL (devc->osdev, READL (devc->osdev, 0x08) | (0x80000000), 0x08);
+
+ /* do a soft reset */
+ WRITEL (devc->osdev, READL (devc->osdev, 0x08) | (1 << 29), 0x08);
+ oss_udelay (10);
+
+ /* deassert softreset */
+ WRITEL (devc->osdev, READL (devc->osdev, 0x08) & ~(1 << 29), 0x08);
+ oss_udelay (10);
+
+ /* do a sync */
+ WRITEL (devc->osdev, READL (devc->osdev, 0x08) | (1 << 30), 0x08);
+ oss_udelay (10);
+
+ /* now continue with initializing the rest of the chip */
+ WRITEL (devc->osdev, READL (devc->osdev, 0x08) | (1 << 25), 0x08); /*enable burst */
+
+ WRITEL (devc->osdev, 0x22, 0x04); /* enable audio+spdif interrupts */
+ devc->bdlBuffer =
+ CONTIG_MALLOC (devc->osdev, MAX_PORTC * BDL_SIZE * sizeof (bdl_t),
+ MEMLIMIT_32BITS, &phaddr, devc->bldbuf_dma_handle);
+ if (devc->bdlBuffer == NULL)
+ {
+ cmn_err (CE_WARN, "OSS Failed to allocate BDL\n");
+ return 0;
+ }
+
+ devc->playBDL = (bdl_t *) devc->bdlBuffer;
+ devc->playBDL_phys = phaddr;
+ devc->recBDL =
+ (bdl_t *) (devc->bdlBuffer + (1 * BDL_SIZE * sizeof (bdl_t)));
+ devc->recBDL_phys = phaddr + (1 * BDL_SIZE * sizeof (bdl_t));
+ devc->spdifBDL =
+ (bdl_t *) (devc->bdlBuffer + (2 * BDL_SIZE * sizeof (bdl_t)));
+ devc->spdifBDL_phys = phaddr + (2 * BDL_SIZE * sizeof (bdl_t));
+
+/*
+ * Init mixer
+ */
+ my_mixer =
+ ac97_install_full (&devc->ac97devc, "AC97 Mixer", ac97_read, ac97_write,
+ devc, devc->osdev, devc->inverted);
+
+ if (my_mixer == -1)
+ return 0; /* No mixer */
+
+ devc->mixer_dev = my_mixer;
+
+
+ /* enable S/PDIF */
+ devc->ac97devc.spdif_slot = SPDIF_SLOT1011;
+ ac97_spdifout_ctl (devc->mixer_dev, SPDIFOUT_ENABLE, SNDCTL_MIX_WRITE, 1);
+
+ for (i = 0; i < MAX_PORTC; i++)
+ {
+ ATI_portc *portc = &devc->portc[i];
+ char tmp_name[100];
+ int formats = AFMT_S16_LE | AFMT_AC3;
+ char *devfile_name = "";
+ strcpy (tmp_name, devc->chip_name);
+ opts = ADEV_AUTOMODE | ADEV_STEREOONLY | ADEV_FIXEDRATE;
+ portc->port_type = DF_ANALOG;
+
+ if (i == 0)
+ {
+ opts |= ADEV_DUPLEX;
+ strcpy (tmp_name, devc->chip_name);
+ }
+ if (i == 1)
+ {
+ strcpy (tmp_name, devc->chip_name);
+ opts |= ADEV_DUPLEX | ADEV_SHADOW;
+ }
+ if (i == 2)
+ {
+ sprintf (tmp_name, "%s (SPDIF out)", devc->chip_name);
+ opts |= ADEV_NOINPUT | ADEV_SPECIAL;
+ portc->port_type = DF_SPDIF;
+ devfile_name = "spdout";
+ }
+
+ if ((my_dev = oss_install_audiodev_with_devname (OSS_AUDIO_DRIVER_VERSION,
+ devc->osdev,
+ devc->osdev,
+ tmp_name,
+ &ATI_audio_driver,
+ sizeof (audiodrv_t),
+ opts, formats, devc, -1,
+ devfile_name)) < 0)
+ {
+ my_dev = -1;
+ return 0;
+ }
+ else
+ {
+ audio_engines[my_dev]->portc = portc;
+ audio_engines[my_dev]->mixer_dev = my_mixer;
+ audio_engines[my_dev]->min_rate =
+ (opts & ADEV_FIXEDRATE) ? 48000 : 5000;
+ audio_engines[my_dev]->max_rate = 48000;
+ audio_engines[my_dev]->caps |= PCM_CAP_FREERATE;
+ audio_engines[my_dev]->min_channels = 2;
+ audio_engines[my_dev]->max_channels = 6;
+
+ /*
+ * We can have at most BDL_SIZE fragments and they can
+ * be at most 128k each.
+ */
+ audio_engines[my_dev]->max_block = 128 * 1024;
+ audio_engines[my_dev]->max_fragments = BDL_SIZE;
+ portc->open_mode = 0;
+ portc->audio_enabled = 0;
+ portc->audiodev = my_dev;
+ if (audio_engines[my_dev]->flags & ADEV_FIXEDRATE)
+ audio_engines[my_dev]->fixed_rate = 48000;
+#ifdef CONFIG_OSS_VMIX
+ if (i == 0)
+ vmix_attach_audiodev(devc->osdev, my_dev, -1, 0);
+#endif
+ }
+ }
+ return 1;
+}
+
+
+int
+oss_atiaudio_attach (oss_device_t * osdev)
+{
+ unsigned char pci_irq_line, pci_revision /*, pci_latency */ ;
+ unsigned short pci_command, vendor, device, sub_vendor, sub_id;
+ unsigned int pci_ioaddr;
+ ATI_devc *devc;
+
+ DDB (cmn_err (CE_WARN, "Entered ATI AC97 probe routine\n"));
+
+ pci_read_config_word (osdev, PCI_VENDOR_ID, &vendor);
+ pci_read_config_word (osdev, PCI_DEVICE_ID, &device);
+
+ if ((vendor != ATI_VENDOR_ID) || (device != ATI_DEVICE_IXP &&
+ device != ATI_DEVICE_IXP300
+ && device != ATI_DEVICE_IXP400))
+ return 0;
+
+ oss_pci_byteswap (osdev, 1);
+
+ pci_read_config_byte (osdev, PCI_REVISION_ID, &pci_revision);
+ pci_read_config_word (osdev, PCI_COMMAND, &pci_command);
+ pci_read_config_irq (osdev, PCI_INTERRUPT_LINE, &pci_irq_line);
+ pci_read_config_dword (osdev, PCI_MEM_BASE_ADDRESS_0, &pci_ioaddr);
+
+ if (pci_ioaddr == 0)
+ {
+ cmn_err (CE_WARN, "I/O address not assigned by BIOS.\n");
+ return 0;
+ }
+
+ if (pci_irq_line == 0)
+ {
+ cmn_err (CE_WARN, "IRQ not assigned by BIOS (%d).\n", pci_irq_line);
+ return 0;
+ }
+
+ if ((devc = PMALLOC (osdev, sizeof (*devc))) == NULL)
+ {
+ cmn_err (CE_WARN, "Out of memory\n");
+ return 0;
+ }
+
+ devc->osdev = osdev;
+ osdev->devc = devc;
+
+ pci_read_config_word (osdev, PCI_SUBSYSTEM_VENDOR_ID, &sub_vendor);
+ pci_read_config_word (osdev, PCI_SUBSYSTEM_ID, &sub_id);
+ switch ((sub_id << 16) | sub_vendor)
+ {
+ case 0x11831043: /* ASUS A6R */
+ case 0x2043161f: /* Maxselect x710s */
+ case 0x0506107b: /* Gateway 7510GX */
+ devc->inverted = AC97_INVERTED;
+ cmn_err (CE_CONT, "An inverted amplifier has been autodetected\n");
+ break;
+ default:
+ devc->inverted = 0;
+ break;
+ }
+
+ devc->membar_addr = pci_ioaddr;
+ devc->membar_virt =
+ (char *) MAP_PCI_MEM (devc->osdev, 0, devc->membar_addr, 256);
+
+ if (devc->membar_virt == NULL)
+ {
+ cmn_err (CE_WARN, "ATIIXP: Cannot map pci mem\n");
+ return 0;
+ }
+
+ pci_command |= PCI_COMMAND_MASTER | PCI_COMMAND_MEMORY;
+ pci_write_config_word (osdev, PCI_COMMAND, pci_command);
+
+#if 0
+ latency = pci_read_config_dword (osdev, 0x50, &latency);
+ pci_write_config_dword (osdev, 0x50, latency | 1);
+#endif
+
+ switch (device)
+ {
+ case ATI_DEVICE_IXP:
+ devc->chip_name = "ATI IXP200";
+ break;
+
+ case ATI_DEVICE_IXP300:
+ devc->chip_name = "ATI IXP300";
+ break;
+
+ case ATI_DEVICE_IXP400:
+ devc->chip_name = "ATI IXP400";
+ break;
+
+ default:
+ devc->chip_name = "ATI IXP";
+ break;
+ }
+
+ devc->irq = pci_irq_line;
+ devc->open_mode = 0;
+
+ MUTEX_INIT (devc->osdev, devc->mutex, MH_DRV);
+ MUTEX_INIT (devc->osdev, devc->low_mutex, MH_DRV + 1);
+
+ oss_register_device (osdev, devc->chip_name);
+
+ if (oss_register_interrupts (devc->osdev, 0, ATIIXPintr, NULL) < 0)
+ {
+ cmn_err (CE_WARN, "Unable to register interrupts\n");
+ return 0;
+ }
+
+ return init_ATI (devc); /* Detected */
+}
+
+
+int
+oss_atiaudio_detach (oss_device_t * osdev)
+{
+ ATI_devc *devc = (ATI_devc *) osdev->devc;
+
+ if (oss_disable_device (osdev) < 0)
+ return 0;
+
+ WRITEL (devc->osdev, 0x3f, 0x00); /* ack all interrupts */
+ WRITEL (devc->osdev, 0, 0x04); /* disable interrupts */
+
+ /* disable S/PDIF */
+ if (devc->mixer_dev)
+ ac97_spdifout_ctl (devc->mixer_dev, SPDIFOUT_ENABLE, SNDCTL_MIX_WRITE, 0);
+
+ oss_unregister_interrupts (devc->osdev);
+
+ if (devc->bdlBuffer)
+ CONTIG_FREE (devc->osdev, devc->bdlBuffer,
+ MAX_PORTC * BDL_SIZE * sizeof (bdl_t), devc->bldbuf_dma_handle);
+
+ MUTEX_CLEANUP (devc->mutex);
+ MUTEX_CLEANUP (devc->low_mutex);
+
+ if (devc->membar_addr != 0)
+ {
+ UNMAP_PCI_MEM (devc->osdev, 0, devc->membar_addr, devc->membar_virt,
+ 256);
+ devc->membar_addr = 0;
+ }
+
+ oss_unregister_device (devc->osdev);
+ return 1;
+
+}