diff options
Diffstat (limited to 'kernel/drv/oss_ali5455/oss_ali5455.c')
-rw-r--r-- | kernel/drv/oss_ali5455/oss_ali5455.c | 979 |
1 files changed, 979 insertions, 0 deletions
diff --git a/kernel/drv/oss_ali5455/oss_ali5455.c b/kernel/drv/oss_ali5455/oss_ali5455.c new file mode 100644 index 0000000..ce96844 --- /dev/null +++ b/kernel/drv/oss_ali5455/oss_ali5455.c @@ -0,0 +1,979 @@ +/* + * Purpose: Driver for the ALI 5455 (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_ali5455_cfg.h" +#include <oss_pci.h> +#include <ac97.h> + +#define ALI_VENDOR_ID 0x10b9 +#define ALI_DEVICE_5455 0x5455 + +#define MAX_ALI5455 1 +#define MAX_PORTC 3 +#define BDL_SIZE 32 + +#ifdef OSS_BIG_ENDIAN +static __inline__ unsigned int +swap32 (unsigned int x) +{ + return ((x & 0x000000ff) << 24) | + ((x & 0x0000ff00) << 8) | + ((x & 0x00ff0000) >> 8) | ((x & 0xff000000) >> 24); +} + +static __inline__ unsigned short +swap16 (unsigned short x) +{ + return ((x >> 8) & 0xff) | ((x & 0xff) << 8); +} + +#define SWAP32(x) swap32(x) +#define SWAP16(x) swap16(x) +#else +#define SWAP32(x) x +#define SWAP16(x) x +#endif + +typedef struct +{ + int open_mode; + int speed, bits, channels; + int audio_enabled; + int trigger_bits; + int audiodev; + int port_type; +#define DF_PCM 0 +#define DF_SPDIF 1 +} +ALI_portc; + +typedef struct +{ + unsigned int addr; + unsigned short size; + unsigned short flags; +} +bdl_t; + +typedef struct ALI_devc +{ + oss_device_t *osdev; + oss_native_word base; + int irq; + oss_mutex_t mutex; + oss_mutex_t low_mutex; + + /* Mixer */ + ac97_devc ac97devc; + int mixer_dev; + + /* 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 bdl_dma_handle; + + int play_currbuf, play_currfrag; + int spdif_currbuf, spdif_currfrag; + int rec_currbuf, rec_currfrag; + char *chip_name; + ALI_portc portc[MAX_PORTC]; + int play_frag_index[BDL_SIZE]; + int rec_frag_index[BDL_SIZE]; + int spdif_frag_index[BDL_SIZE]; +} +ALI_devc; + +static int +ac97_read (void *devc_, int reg) +{ + ALI_devc *devc = devc_; + int i = 100; + unsigned int status; + unsigned int data = 0; + unsigned short read_reg = 0; + oss_native_word flags; + + MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags); + status = INB (devc->osdev, devc->base + 0x34); + + /* wait for the Codec Access Semaphore bit to be set */ + while (i-- && (INL (devc->osdev, devc->base + 0x3c) & 0x80000000)) + oss_udelay (1); + + for (i = 0; i < 100; i++) + { + status = INB (devc->osdev, devc->base + 0x38); + if (status & 0x08) + break; + } + if (i == 100) + { + cmn_err (CE_WARN, "AC97 not ready for read\n"); + MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags); + return 0; + } + + OUTW (devc->osdev, reg | 0x80, devc->base + 0x22); + + for (i = 0; i < 100; i++) + { + status = INB (devc->osdev, devc->base + 0x38); + + if (status & 0x02) + { + + data = INW (devc->osdev, devc->base + 0x24); + read_reg = INW (devc->osdev, devc->base + 0x26); + break; + } + } + + if (i == 100) + { + cmn_err (CE_WARN, "AC97 read timed out \n"); + MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags); + return 0; + } + + if (read_reg != reg) + { + cmn_err (CE_WARN, "AC97 invalid reg read %x (%x)\n", read_reg, reg); + MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags); + return 0; + } + + MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags); + return data; +} + +static int +ac97_write (void *devc_, int reg, int data) +{ + ALI_devc *devc = devc_; + int i = 100, status; + oss_native_word flags; + + MUTEX_ENTER_IRQDISABLE (devc->low_mutex, flags); + /* wait for the Codec Access Semaphore bit to be set */ + while (i-- && (INL (devc->osdev, devc->base + 0x3c) & 0x80000000)) + oss_udelay (1); + + /* wait until command port is ready for write */ + for (i = 0; i < 100; i++) + { + status = INB (devc->osdev, devc->base + 0x38); + if (status & 0x01) + break; + } + + if (i == 100) + { + cmn_err (CE_WARN, "AC97 timed out for write\n"); + MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags); + return 0; + } + + OUTL (devc->osdev, reg << 16 | data, devc->base + 0x20); + + for (i = 0; i < 100; i++) + { + status = INB (devc->osdev, devc->base + 0x38); + if (status & 0x01) + break; + } + + MUTEX_EXIT_IRQRESTORE (devc->low_mutex, flags); + return 1; +} + +static int +ALIintr (oss_device_t * osdev) +{ + int status, global_status, p, f, i; + int serviced = 0; + ALI_devc *devc = (ALI_devc *) osdev->devc; + ALI_portc *portc; + /* oss_native_word flags; */ + + /* Handle playback */ + /* + * TODO: Fix mutexes and move the inputintr/outputintr calls outside the + * mutex block. + */ + + /* MUTEX_ENTER (devc->mutex, flags); */ + /* Handle Global Interrupts */ + global_status = INL (devc->osdev, devc->base + 0x18); + OUTL (devc->osdev, global_status, devc->base + 0x18); + + if (!(global_status & (0x10000 | 0x20000 | 0x80000 | 0x100000 | 0x200000))) + { + /* MUTEX_EXIT (devc->mutex, flags); */ + return serviced; + } + + /* Handle Playback Interrupts */ + + status = INB (devc->osdev, devc->base + 0x56); + + if ((status & 0x08) && (global_status & 0x20000)) + for (i = 0; i < MAX_PORTC - 1; i++) + { + portc = &devc->portc[i]; + serviced = 1; + if ((portc->trigger_bits & PCM_ENABLE_OUTPUT)) /* IOC interrupt */ + { + dmap_t *dmap = audio_engines[portc->audiodev]->dmap_out; + p = INB (devc->osdev, devc->base + 0x54); + + if (p != devc->play_currbuf) + { + p = devc->play_currbuf; + f = devc->play_currfrag; + devc->playBDL[p].addr = + SWAP32 (dmap->dmabuf_phys + (f * dmap->fragment_size)); + + devc->playBDL[p].size = SWAP16 (dmap->fragment_size / 2); + devc->playBDL[p].flags = SWAP16 (0xc000); /* IOC interrupts */ + + OUTB (devc->osdev, p, devc->base + 0x55); /* Set LVD */ + devc->play_frag_index[p] = f; + devc->play_currbuf = (p + 1) % BDL_SIZE; + devc->play_currfrag = (f + 1) % dmap->nfrags; + } + oss_audio_outputintr (portc->audiodev, 1); + } + } + OUTB (devc->osdev, status, devc->base + 0x56); /* Clear interrupts */ + +/*--------------------------------------------------------------------------*/ + /* handle SPDIF interrupts */ + + status = INB (devc->osdev, devc->base + 0x76); + if ((status & 0x08) && (global_status & 0x80000)) + { + portc = &devc->portc[2]; + serviced = 1; + if ((portc->trigger_bits & PCM_ENABLE_OUTPUT)) /* IOC interrupt */ + { + dmap_t *dmap = audio_engines[portc->audiodev]->dmap_out; + p = INB (devc->osdev, devc->base + 0x74); + + if (p != devc->spdif_currbuf) + { + p = devc->spdif_currbuf; + f = devc->spdif_currfrag; + devc->spdifBDL[p].addr = + SWAP32 (dmap->dmabuf_phys + (f * dmap->fragment_size)); + + devc->spdifBDL[p].size = SWAP16 (dmap->fragment_size / 2); + devc->spdifBDL[p].flags = SWAP16 (0xc000); /* IOC interrupts */ + + OUTB (devc->osdev, p, devc->base + 0x75); /* Set LVD */ + devc->spdif_frag_index[p] = f; + devc->spdif_currbuf = (p + 1) % BDL_SIZE; + devc->spdif_currfrag = (f + 1) % dmap->nfrags; + } + oss_audio_outputintr (portc->audiodev, 1); + } + } + OUTB (devc->osdev, status, devc->base + 0x76); /* Clear interrupts */ +/*---------------------------------------------------------------------------*/ + + /* Handle Recording Interrupts */ + status = INB (devc->osdev, devc->base + 0x46); + + if ((status & 0x08) && (global_status & 0x10000)) + for (i = 0; i < MAX_PORTC - 1; i++) + { + portc = &devc->portc[i]; + serviced = 1; + if ((portc->trigger_bits & PCM_ENABLE_INPUT)) /* IOC interrupt */ + { + dmap_t *dmap = audio_engines[portc->audiodev]->dmap_in; + p = INB (devc->osdev, devc->base + 0x44); + + if (p != devc->rec_currbuf) + { + p = devc->rec_currbuf; + f = devc->rec_currfrag; + devc->recBDL[p].addr = + SWAP32 (dmap->dmabuf_phys + (f * dmap->fragment_size)); + + /* SIS uses bytes, ali5455 uses samples */ + devc->recBDL[p].size = SWAP16 (dmap->fragment_size / 2); + + devc->recBDL[p].flags = SWAP16 (0xc000); /* IOC interrupts */ + + OUTB (devc->osdev, p, devc->base + 0x45); /* Set LVD */ + devc->rec_frag_index[p] = f; + devc->rec_currbuf = (p + 1) % BDL_SIZE; + devc->rec_currfrag = (f + 1) % dmap->nfrags; + } + oss_audio_inputintr (portc->audiodev, 0); + } + } + OUTB (devc->osdev, status, devc->base + 0x46); /* Clear int */ + + /* MUTEX_EXIT (devc->mutex, flags); */ + return serviced; +} + +/* + * Audio routines + */ + +static int +ALI_audio_set_rate (int dev, int arg) +{ + ALI_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 +ALI_audio_set_channels (int dev, short arg) +{ + ALI_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 +ALI_audio_set_format (int dev, unsigned int arg) +{ + ALI_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 +ALI_audio_ioctl (int dev, unsigned int cmd, ioctl_arg arg) +{ + return OSS_EINVAL; +} + +static void ALI_audio_trigger (int dev, int state); + +static void +ALI_audio_reset (int dev) +{ + ALI_audio_trigger (dev, 0); +} + +static void +ALI_audio_reset_input (int dev) +{ + ALI_portc *portc = audio_engines[dev]->portc; + ALI_audio_trigger (dev, portc->trigger_bits & ~PCM_ENABLE_INPUT); +} + +static void +ALI_audio_reset_output (int dev) +{ + ALI_portc *portc = audio_engines[dev]->portc; + ALI_audio_trigger (dev, portc->trigger_bits & ~PCM_ENABLE_OUTPUT); +} + +/*ARGSUSED*/ +static int +ALI_audio_open (int dev, int mode, int openflags) +{ + ALI_portc *portc = audio_engines[dev]->portc; + ALI_devc *devc = audio_engines[dev]->devc; + oss_native_word flags; + + MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); + if (portc->open_mode) + { + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); + return OSS_EBUSY; + } + + if (portc->port_type == DF_SPDIF) + { + if (mode & OPEN_READ) + { + cmn_err (CE_WARN, + "ICH: The S/PDIF device supports only playback\n"); + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); + return OSS_EIO; + } + } + else + { + if (devc->open_mode & mode) + { + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); + return OSS_EBUSY; + } + + devc->open_mode |= mode; + } + + + portc->open_mode = mode; + portc->audio_enabled &= ~mode; + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); + + return 0; +} + +static void +ALI_audio_close (int dev, int mode) +{ + ALI_portc *portc = audio_engines[dev]->portc; + ALI_devc *devc = audio_engines[dev]->devc; + + ALI_audio_reset (dev); + portc->open_mode = 0; + + if (portc->port_type != DF_SPDIF) + devc->open_mode &= ~mode; + + + portc->audio_enabled &= ~mode; +} + +/*ARGSUSED*/ +static void +ALI_audio_output_block (int dev, oss_native_word buf, int count, + int fragsize, int intrflag) +{ + ALI_portc *portc = audio_engines[dev]->portc; + + portc->audio_enabled |= PCM_ENABLE_OUTPUT; + portc->trigger_bits &= ~PCM_ENABLE_OUTPUT; +} + +/*ARGSUSED*/ +static void +ALI_audio_start_input (int dev, oss_native_word buf, int count, + int fragsize, int intrflag) +{ + ALI_portc *portc = audio_engines[dev]->portc; + + portc->audio_enabled |= PCM_ENABLE_INPUT; + portc->trigger_bits &= ~PCM_ENABLE_INPUT; +} + +static void +ALI_audio_trigger (int dev, int state) +{ + ALI_devc *devc = audio_engines[dev]->devc; + ALI_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)) + { + if (portc->port_type == DF_SPDIF) + { + OUTB (devc->osdev, 0x1d, devc->base + 0x7b); /* Setup intr */ + OUTW (devc->osdev, INW (devc->osdev, devc->base + 0x08) | 0x08, devc->base + 0x08); /* start DMA */ + } + + if (portc->port_type == DF_PCM) + { + OUTB (devc->osdev, 0x1d, devc->base + 0x5b); /* setup intr */ + OUTW (devc->osdev, INW (devc->osdev, devc->base + 0x08) | 0x02, devc->base + 0x08); /* start DMA */ + } + 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; + if (portc->port_type == DF_SPDIF) + { + OUTB (devc->osdev, 0x00, devc->base + 0x7b); /* reset */ + OUTW (devc->osdev, INW (devc->osdev, devc->base + 0x08) & ~0x08, devc->base + 0x08); /* stop DMA */ + } + + if (portc->port_type == DF_PCM) + { + OUTB (devc->osdev, 0x00, devc->base + 0x5b); /* reset */ + OUTW (devc->osdev, INW (devc->osdev, devc->base + 0x08) & ~0x02, devc->base + 0x08); /* stop DMA */ + } + } + } + } + + if (portc->open_mode & OPEN_READ) + { + if (state & PCM_ENABLE_INPUT) + { + if ((portc->audio_enabled & PCM_ENABLE_INPUT) && + !(portc->trigger_bits & PCM_ENABLE_INPUT)) + { + OUTB (devc->osdev, 0x1d, devc->base + 0x4b); /* Kickstart */ + OUTW (devc->osdev, INW (devc->osdev, devc->base + 0x08) | 0x01, devc->base + 0x08); /* stop DMA */ + portc->trigger_bits |= PCM_ENABLE_INPUT; + } + } + 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; + OUTB (devc->osdev, 0x00, devc->base + 0x4b); /* reset */ + OUTW (devc->osdev, INW (devc->osdev, devc->base + 0x08) & ~0x01, devc->base + 0x08); /* stop DMA */ + } + } + } + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); +} + +/*ARGSUSED*/ +static int +ALI_audio_prepare_for_input (int dev, int bsize, int bcount) +{ + ALI_devc *devc = audio_engines[dev]->devc; + ALI_portc *portc = audio_engines[dev]->portc; + dmap_t *dmap = audio_engines[dev]->dmap_in; + int i, n; + oss_native_word flags; + + MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); + + OUTB (devc->osdev, 0x02, devc->base + 0x4b); /* Reset */ + OUTL (devc->osdev, devc->recBDL_phys, devc->base + 0x40); /* BDL base */ + + ac97_recrate (&devc->ac97devc, portc->speed); + + n = bcount; + if (n > BDL_SIZE) + n = BDL_SIZE; + + for (i = 0; i < n; i++) + { + devc->recBDL[i].addr = + SWAP32 (dmap->dmabuf_phys + (i * dmap->fragment_size)); + devc->recBDL[i].size = SWAP16 (dmap->fragment_size / 2); + devc->recBDL[i].flags = SWAP16 (0xc000); /* IOC interrupts */ + devc->rec_frag_index[i] = i; + } + OUTB (devc->osdev, n - 1, devc->base + 0x45); /* Set last valid descriptor */ + + devc->rec_currbuf = n % BDL_SIZE; + devc->rec_currfrag = n; + if (devc->rec_currfrag >= dmap->nfrags) + devc->rec_currfrag = 0; + + portc->audio_enabled &= ~PCM_ENABLE_INPUT; + portc->trigger_bits &= ~PCM_ENABLE_INPUT; + + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); + return 0; +} + +/*ARGSUSED*/ +static int +ALI_audio_prepare_for_output (int dev, int bsize, int bcount) +{ + ALI_devc *devc = audio_engines[dev]->devc; + ALI_portc *portc = audio_engines[dev]->portc; + dmap_t *dmap = audio_engines[dev]->dmap_out; + int i, n; + oss_native_word flags; + + MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); + + ac97_spdifout_ctl (devc->mixer_dev, SPDIFOUT_AUDIO, SNDCTL_MIX_WRITE, 0); + ac97_spdif_setup (devc->mixer_dev, portc->speed, portc->bits); + + if (portc->bits == AFMT_AC3) + { + portc->channels = 2; + portc->bits = 16; + } + + /* do SPDIF out */ + if (portc->port_type == DF_SPDIF) + { + ac97_playrate (&devc->ac97devc, portc->speed); + OUTB (devc->osdev, 0x02, devc->base + 0x7b); /* Reset */ + OUTL (devc->osdev, devc->spdifBDL_phys, devc->base + 0x70); + + n = bcount; + if (n > BDL_SIZE) + n = BDL_SIZE; + + for (i = 0; i < n; i++) + { + devc->spdifBDL[i].addr = + SWAP32 (dmap->dmabuf_phys + (i * dmap->fragment_size)); + devc->spdifBDL[i].size = SWAP16 (dmap->fragment_size / 2); + devc->spdifBDL[i].flags = SWAP16 (0xc000); /* IOC interrupts */ + devc->spdif_frag_index[i] = i; + } + OUTB (devc->osdev, n - 1, devc->base + 0x75); /* Set LVI descriptor */ + devc->spdif_currbuf = n % BDL_SIZE; + devc->spdif_currfrag = n; + if (devc->spdif_currfrag >= dmap->nfrags) + devc->spdif_currfrag = 0; + + portc->audio_enabled &= ~PCM_ENABLE_OUTPUT; + portc->trigger_bits &= ~PCM_ENABLE_OUTPUT; + + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); + return 0; + } + + /* else do PCM */ + OUTB (devc->osdev, 0x02, devc->base + 0x5b); /* Reset */ + OUTL (devc->osdev, devc->playBDL_phys, devc->base + 0x50); + + ac97_playrate (&devc->ac97devc, portc->speed); + + /* set default to 2 channel mode */ + if (portc->channels == 2) + OUTW (devc->osdev, INW (devc->osdev, devc->base + 0x00) & ~0x300, + devc->base + 0x00); + if (portc->channels == 4) + OUTW (devc->osdev, + (INW (devc->osdev, devc->base + 0x00) & ~0x300) | 0x100, + devc->base + 0x00); + if (portc->channels == 6) + OUTW (devc->osdev, + (INW (devc->osdev, devc->base + 0x00) & ~0x300) | 0x200, + devc->base + 0x00); + + + n = bcount; + if (n > BDL_SIZE) + n = BDL_SIZE; + + for (i = 0; i < n; i++) + { + devc->playBDL[i].addr = + SWAP32 (dmap->dmabuf_phys + (i * dmap->fragment_size)); + devc->playBDL[i].size = SWAP16 (dmap->fragment_size / 2); + devc->playBDL[i].flags = SWAP16 (0xc000); /* IOC interrupts */ + devc->play_frag_index[i] = i; + } + OUTB (devc->osdev, n - 1, devc->base + 0x55); /* Set last valid descriptor */ + + devc->play_currbuf = n % BDL_SIZE; + devc->play_currfrag = n; + if (devc->play_currfrag >= dmap->nfrags) + devc->play_currfrag = 0; + + portc->audio_enabled &= ~PCM_ENABLE_OUTPUT; + portc->trigger_bits &= ~PCM_ENABLE_OUTPUT; + MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); + return 0; +} + +static const audiodrv_t ALI_audio_driver = { + ALI_audio_open, + ALI_audio_close, + ALI_audio_output_block, + ALI_audio_start_input, + ALI_audio_ioctl, + ALI_audio_prepare_for_input, + ALI_audio_prepare_for_output, + ALI_audio_reset, + NULL, + NULL, + ALI_audio_reset_input, + ALI_audio_reset_output, + ALI_audio_trigger, + ALI_audio_set_rate, + ALI_audio_set_format, + ALI_audio_set_channels +}; + +static int +init_ALI (ALI_devc * devc) +{ + int my_mixer, my_dev, opts; + oss_native_word phaddr; + int i; + + /* ACLink on, warm reset */ + OUTL (devc->osdev, 0x80008003, devc->base + 0x00); /*reset SCR */ + OUTL (devc->osdev, 0x83838383, devc->base + 0x0c); /*reset pcm in/out FIFO */ + OUTL (devc->osdev, 0x83838383, devc->base + 0x1c); /*reset SPDIF/LFEr FIFO */ + OUTL (devc->osdev, 0x0028000a, devc->base + 0x10); /*set spdif/pcm in/out */ + OUTL (devc->osdev, INL (devc->osdev, devc->base + 0xFC) | 0x3, + devc->base + 0xFC); + + /* set up Codec SPDIFOUT slot to 10/11 */ + OUTL (devc->osdev, INL (devc->osdev, devc->base + 0x00) | 0x300000, + devc->base + 0x00); + /* disable interrupts */ + OUTL (devc->osdev, 0x00, devc->base + 0x14); + OUTL (devc->osdev, 0x00, devc->base + 0x18); + + devc->bdlBuffer = + CONTIG_MALLOC (devc->osdev, 4 * 32 * 32, MEMLIMIT_32BITS, &phaddr, devc->bdl_dma_handle); + if (devc->bdlBuffer == NULL) + { + cmn_err (CE_WARN, "Failed to allocate BDL\n"); + return 0; + } + + devc->playBDL = (bdl_t *) devc->bdlBuffer; + devc->playBDL_phys = phaddr; + devc->recBDL = (bdl_t *) (devc->bdlBuffer + (1 * 32 * 32)); + devc->recBDL_phys = phaddr + (1 * 32 * 32); + devc->spdifBDL = (bdl_t *) (devc->bdlBuffer + (2 * 32 * 32)); + devc->spdifBDL_phys = phaddr + (2 * 32 * 32); + +/* + * Init mixer + */ + my_mixer = + ac97_install (&devc->ac97devc, "AC97 Mixer", ac97_read, ac97_write, devc, + devc->osdev); + + 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); + +#if 0 + /* enable variable rate mode */ + ac97_write (devc, 0x2a, ac97_read (devc, 0x2a) | 9); + if (!(ac97_read (devc, 0x2a) & 1)) + DDB (cmn_err (CE_WARN, "VRA not supported...using GRC\n")); +#endif + + for (i = 0; i < MAX_PORTC; i++) + { + ALI_portc *portc = &devc->portc[i]; + char tmp_name[100]; + int port_fmt = DF_PCM; + int formats = AFMT_S16_LE | AFMT_AC3; + strcpy (tmp_name, devc->chip_name); + opts = ADEV_AUTOMODE | ADEV_16BITONLY | ADEV_STEREOONLY; + portc->port_type = DF_PCM; + + if (!ac97_varrate (&devc->ac97devc)) + { + opts |= ADEV_FIXEDRATE; + } + + if (i == 0) + { + opts |= ADEV_DUPLEX; + strcpy (tmp_name, devc->chip_name); + } + if (i == 1) + { + opts |= ADEV_DUPLEX | ADEV_SHADOW; + strcpy (tmp_name, devc->chip_name); + } + + if (i == 2) + { + sprintf (tmp_name, "%s (S/PDIF)", devc->chip_name); + opts |= ADEV_NOINPUT | ADEV_SPECIAL | ADEV_FIXEDRATE; + port_fmt = DF_SPDIF; + } + + if ((my_dev = oss_install_audiodev (OSS_AUDIO_DRIVER_VERSION, + devc->osdev, + devc->osdev, + tmp_name, + &ALI_audio_driver, + sizeof (audiodrv_t), opts, + formats, devc, -1)) < 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_block = 4096; */ + /*audio_engines[my_dev]->max_block = 4096; */ + audio_engines[my_dev]->min_channels = 2; + audio_engines[my_dev]->max_channels = 6; + portc->open_mode = 0; + portc->audio_enabled = 0; + portc->audiodev = my_dev; + portc->port_type = port_fmt; + 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_ali5455_attach (oss_device_t * osdev) +{ + unsigned char pci_irq_line, pci_revision /*, pci_latency */ ; + unsigned short pci_command, vendor, device; + unsigned int pci_ioaddr0; + ALI_devc *devc; + + DDB (cmn_err (CE_WARN, "Entered ALI AC97 probe routine\n")); + + pci_read_config_word (osdev, PCI_VENDOR_ID, &vendor); + pci_read_config_word (osdev, PCI_DEVICE_ID, &device); + + if ((vendor != ALI_VENDOR_ID) || (device != ALI_DEVICE_5455)) + return 0; + + 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_BASE_ADDRESS_0, &pci_ioaddr0); + + if (pci_ioaddr0 == 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; + devc->open_mode = 0; + + /* Remove I/O space marker in bit 0. */ + devc->base = MAP_PCI_IOADDR (devc->osdev, 0, pci_ioaddr0); + devc->base &= ~0xF; + + pci_command |= PCI_COMMAND_MASTER | PCI_COMMAND_IO; + pci_write_config_word (osdev, PCI_COMMAND, pci_command); + + devc->chip_name = "ALI M5455"; + devc->irq = pci_irq_line; + + 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, ALIintr, NULL) < 0) + { + cmn_err (CE_WARN, "Unable to install interrupt handler\n"); + return 0; + } + + return init_ALI (devc); /* Detected */ +} + + +int +oss_ali5455_detach (oss_device_t * osdev) +{ + ALI_devc *devc = (ALI_devc *) osdev->devc; + + + if (oss_disable_device (osdev) < 0) + return 0; + + /* disable S/PDIF */ + if (devc->mixer_dev) + ac97_spdifout_ctl (devc->mixer_dev, SPDIFOUT_ENABLE, SNDCTL_MIX_WRITE, 0); + /* disable interrupts */ + OUTL (devc->osdev, 0x00, devc->base + 0x14); + OUTL (devc->osdev, 0x00, devc->base + 0x18); + + oss_unregister_interrupts (devc->osdev); + + if (devc->bdlBuffer) + { + CONTIG_FREE (devc->osdev, devc->bdlBuffer, 4 * 32 * 32, devc->bdl_dma_handle); + devc->bdlBuffer = NULL; + } + + MUTEX_CLEANUP (devc->mutex); + MUTEX_CLEANUP (devc->low_mutex); + UNMAP_PCI_IOADDR (devc->osdev, 0); + + oss_unregister_device (osdev); + return 1; +} |