diff options
Diffstat (limited to 'kernel/drv/oss_atiaudio/oss_atiaudio.c')
-rw-r--r-- | kernel/drv/oss_atiaudio/oss_atiaudio.c | 1147 |
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; + +} |