NetBSD/sys/dev/pci/auixp.c
reinoud 3b1c762eae Since SPDIF support fo the auixp driver is still half done disable it for
now since some chipset revisions will freak out on the aparent
half-initialisation. Even on my machine i can't seem to get the SPDIF led
to light up so something is wrong.

Also delay the setting of the DMA bits until after the codec detection but
before the enabling of interrupts. Note that the dma has to be explicitly
started when the device is opened.
2006-09-18 15:18:52 +00:00

1849 lines
46 KiB
C

/* $NetBSD: auixp.c,v 1.17 2006/09/18 15:18:52 reinoud Exp $ */
/*
* Copyright (c) 2004, 2005 Reinoud Zandijk <reinoud@netbsd.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the NetBSD
* Foundation, Inc. and its contributors.
* 4. Neither the name of The NetBSD Foundation nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
/*
* NetBSD audio driver for ATI IXP-{150,200,...} audio driver hardware.
*
* Recording and playback has been tested OK on various sample rates and
* encodings.
*
* Known problems and issues :
* - SPDIF is untested and needs some work still (LED stays off)
* - 32 bit audio playback failed last time i tried but that might an AC'97
* codec support problem.
* - 32 bit recording works but can't try out playing: see above.
* - no suspend/resume support yet.
* - multiple codecs are `supported' but not tested; the implemetation needs
* some cleaning up.
*/
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: auixp.c,v 1.17 2006/09/18 15:18:52 reinoud Exp $");
#include <sys/types.h>
#include <sys/errno.h>
#include <sys/null.h>
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/device.h>
#include <sys/conf.h>
#include <sys/exec.h>
#include <sys/select.h>
#include <sys/audioio.h>
#include <sys/queue.h>
#include <machine/bus.h>
#include <machine/intr.h>
#include <dev/pci/pcidevs.h>
#include <dev/pci/pcivar.h>
#include <dev/audio_if.h>
#include <dev/mulaw.h>
#include <dev/auconv.h>
#include <dev/ic/ac97var.h>
#include <dev/ic/ac97reg.h>
#include <dev/pci/auixpreg.h>
#include <dev/pci/auixpvar.h>
//#define DEBUG_AUIXP
/* why isn't this base address register not in the headerfile? */
#define PCI_CBIO 0x10
/* macro's used */
#define KERNADDR(p) ((void *)((p)->addr))
#define DMAADDR(p) ((p)->map->dm_segs[0].ds_addr)
/* the differences might be irrelevant */
enum {
IXP_200,
IXP_300,
IXP_400
};
/* our `cards' */
static const struct auixp_card_type {
uint16_t pci_vendor_id;
uint16_t pci_product_id;
int type;
} auixp_card_types[] = {
{ PCI_VENDOR_ATI, PCI_PRODUCT_ATI_IXP_AUDIO_200, IXP_200 },
{ PCI_VENDOR_ATI, PCI_PRODUCT_ATI_IXP_AUDIO_300, IXP_300 },
{ PCI_VENDOR_ATI, PCI_PRODUCT_ATI_IXP_AUDIO_400, IXP_400 },
{ 0, 0, 0 }
};
struct audio_device auixp_device = {
"ATI IXP audio",
"",
"auixp"
};
/* codec detection constant indicating the interrupt flags */
#define ALL_CODECS_NOT_READY \
(ATI_REG_ISR_CODEC0_NOT_READY |\
ATI_REG_ISR_CODEC1_NOT_READY |\
ATI_REG_ISR_CODEC2_NOT_READY)
#define CODEC_CHECK_BITS (ALL_CODECS_NOT_READY|ATI_REG_ISR_NEW_FRAME)
/* autoconfig */
static int auixp_match( struct device *, struct cfdata *, void *);
static void auixp_attach(struct device *, struct device *, void *);
static int auixp_detach(struct device *, int);
/* audio(9) function prototypes */
static int auixp_query_encoding(void *, struct audio_encoding *);
static int auixp_set_params(void *, int, int, audio_params_t *,
audio_params_t *,
stream_filter_list_t *, stream_filter_list_t *);
static int auixp_commit_settings(void *);
static int auixp_round_blocksize(void *, int, int, const audio_params_t *);
static int auixp_trigger_output(void *, void *, void *, int,
void (*)(void *),
void *, const audio_params_t *);
static int auixp_trigger_input(void *, void *, void *, int,
void (*)(void *),
void *, const audio_params_t *);
static int auixp_halt_output(void *);
static int auixp_halt_input(void *);
static int auixp_set_port(void *, mixer_ctrl_t *);
static int auixp_get_port(void *, mixer_ctrl_t *);
static int auixp_query_devinfo(void *, mixer_devinfo_t *);
static void * auixp_malloc(void *, int, size_t, struct malloc_type *, int);
static void auixp_free(void *, void *, struct malloc_type *);
static int auixp_getdev(void *, struct audio_device *);
static size_t auixp_round_buffersize(void *, int, size_t);
static int auixp_get_props(void *);
static int auixp_intr(void *);
static int auixp_allocmem(struct auixp_softc *, size_t, size_t,
struct auixp_dma *);
static int auixp_freemem(struct auixp_softc *, struct auixp_dma *);
static paddr_t auixp_mappage(void *, void *, off_t, int);
/* power management (do we support that already?) */
#if 0
static void auixp_powerhook(int, void *);
static int auixp_suspend(struct auixp_softc *);
static int auixp_resume(struct auixp_softc *);
#endif
/* Supporting subroutines */
static int auixp_init(struct auixp_softc *);
static void auixp_autodetect_codecs(struct auixp_softc *);
static void auixp_post_config(struct device *);
static void auixp_reset_aclink(struct auixp_softc *);
static int auixp_attach_codec(void *, struct ac97_codec_if *);
static int auixp_read_codec(void *, uint8_t, uint16_t *);
static int auixp_write_codec(void *, uint8_t, uint16_t);
static int auixp_wait_for_codecs(struct auixp_softc *, const char *);
static int auixp_reset_codec(void *);
static enum ac97_host_flags auixp_flags_codec(void *);
static void auixp_enable_dma(struct auixp_softc *, struct auixp_dma *);
static void auixp_disable_dma(struct auixp_softc *, struct auixp_dma *);
static void auixp_enable_interrupts(struct auixp_softc *);
static void auixp_disable_interrupts(struct auixp_softc *);
/* statics */
static void auixp_link_daisychain(struct auixp_softc *,
struct auixp_dma *, struct auixp_dma *,
int, int);
static int auixp_allocate_dma_chain(struct auixp_softc *,
struct auixp_dma **);
static void auixp_program_dma_chain(struct auixp_softc *,
struct auixp_dma *);
static void auixp_dma_update(struct auixp_softc *, struct auixp_dma *);
static void auixp_update_busbusy(struct auixp_softc *);
#ifdef DEBUG_AUIXP
static struct auixp_softc *static_sc;
static void auixp_dumpreg(void);
# define DPRINTF(x) printf x;
#else
# define DPRINTF(x)
#endif
static const struct audio_hw_if auixp_hw_if = {
NULL, /* open */
NULL, /* close */
NULL, /* drain */
auixp_query_encoding,
auixp_set_params,
auixp_round_blocksize,
auixp_commit_settings,
NULL, /* init_output */
NULL, /* init_input */
NULL, /* start_output */
NULL, /* start_input */
auixp_halt_output,
auixp_halt_input,
NULL, /* speaker_ctl */
auixp_getdev,
NULL, /* getfd */
auixp_set_port,
auixp_get_port,
auixp_query_devinfo,
auixp_malloc,
auixp_free,
auixp_round_buffersize,
auixp_mappage,
auixp_get_props,
auixp_trigger_output,
auixp_trigger_input,
NULL, /* dev_ioctl */
NULL, /* powerstate */
};
CFATTACH_DECL(auixp, sizeof(struct auixp_softc), auixp_match, auixp_attach,
auixp_detach, NULL);
/*
* audio(9) functions
*/
static int
auixp_query_encoding(void *hdl, struct audio_encoding *ae)
{
struct auixp_codec *co;
struct auixp_softc *sc;
co = (struct auixp_codec *) hdl;
sc = co->sc;
return auconv_query_encoding(sc->sc_encodings, ae);
}
static int
auixp_set_rate(struct auixp_codec *co, int mode, u_int srate)
{
int ret;
u_int ratetmp;
ratetmp = srate;
if (mode == AUMODE_RECORD) {
ret = co->codec_if->vtbl->set_rate(co->codec_if,
AC97_REG_PCM_LR_ADC_RATE, &ratetmp);
return ret;
}
/* play mode */
ret = co->codec_if->vtbl->set_rate(co->codec_if,
AC97_REG_PCM_FRONT_DAC_RATE, &ratetmp);
if (ret)
return ret;
ratetmp = srate;
ret = co->codec_if->vtbl->set_rate(co->codec_if,
AC97_REG_PCM_SURR_DAC_RATE, &ratetmp);
if (ret)
return ret;
ratetmp = srate;
ret = co->codec_if->vtbl->set_rate(co->codec_if,
AC97_REG_PCM_LFE_DAC_RATE, &ratetmp);
return ret;
}
/* commit setting and program ATI IXP chip */
static int
auixp_commit_settings(void *hdl)
{
struct auixp_codec *co;
struct auixp_softc *sc;
bus_space_tag_t iot;
bus_space_handle_t ioh;
struct audio_params *params;
uint32_t value;
/* XXX would it be better to stop interrupts first? XXX */
co = (struct auixp_codec *) hdl;
sc = co->sc;
iot = sc->sc_iot;
ioh = sc->sc_ioh;
/* process input settings */
params = &sc->sc_play_params;
/* set input interleaving (precision) */
value = bus_space_read_4(iot, ioh, ATI_REG_CMD);
value &= ~ATI_REG_CMD_INTERLEAVE_IN;
if (params->precision <= 16)
value |= ATI_REG_CMD_INTERLEAVE_IN;
bus_space_write_4(iot, ioh, ATI_REG_CMD, value);
/* process output settings */
params = &sc->sc_play_params;
value = bus_space_read_4(iot, ioh, ATI_REG_OUT_DMA_SLOT);
value &= ~ATI_REG_OUT_DMA_SLOT_MASK;
/* TODO SPDIF case for 8 channels */
switch (params->channels) {
case 6:
value |= ATI_REG_OUT_DMA_SLOT_BIT(7) |
ATI_REG_OUT_DMA_SLOT_BIT(8);
/* fallthru */
case 4:
value |= ATI_REG_OUT_DMA_SLOT_BIT(6) |
ATI_REG_OUT_DMA_SLOT_BIT(9);
/* fallthru */
default:
value |= ATI_REG_OUT_DMA_SLOT_BIT(3) |
ATI_REG_OUT_DMA_SLOT_BIT(4);
break;
}
/* set output threshold */
value |= 0x04 << ATI_REG_OUT_DMA_THRESHOLD_SHIFT;
bus_space_write_4(iot, ioh, ATI_REG_OUT_DMA_SLOT, value);
/* set output interleaving (precision) */
value = bus_space_read_4(iot, ioh, ATI_REG_CMD);
value &= ~ATI_REG_CMD_INTERLEAVE_OUT;
if (params->precision <= 16)
value |= ATI_REG_CMD_INTERLEAVE_OUT;
bus_space_write_4(iot, ioh, ATI_REG_CMD, value);
/* enable 6 channel reordering */
value = bus_space_read_4(iot, ioh, ATI_REG_6CH_REORDER);
value &= ~ATI_REG_6CH_REORDER_EN;
if (params->channels == 6)
value |= ATI_REG_6CH_REORDER_EN;
bus_space_write_4(iot, ioh, ATI_REG_6CH_REORDER, value);
if (sc->has_spdif) {
/* set SPDIF (if present) */
value = bus_space_read_4(iot, ioh, ATI_REG_CMD);
value &= ~ATI_REG_CMD_SPDF_CONFIG_MASK;
value |= ATI_REG_CMD_SPDF_CONFIG_34; /* NetBSD AC'97 default */
/* XXX this prolly is not nessisary unless splitted XXX */
value &= ~ATI_REG_CMD_INTERLEAVE_SPDF;
if (params->precision <= 16)
value |= ATI_REG_CMD_INTERLEAVE_SPDF;
bus_space_write_4(iot, ioh, ATI_REG_CMD, value);
}
return 0;
}
/* set audio properties in desired setting */
static int
auixp_set_params(void *hdl, int setmode, int usemode, audio_params_t *play,
audio_params_t *rec, stream_filter_list_t *pfil,
stream_filter_list_t *rfil)
{
struct auixp_codec *co;
struct auixp_softc *sc;
audio_params_t *params;
stream_filter_list_t *fil;
int mode, index;
/*
* In current NetBSD AC'97 implementation, SPDF is linked to channel 3
* and 4 i.e. stereo output.
*/
co = (struct auixp_codec *) hdl;
sc = co->sc;
for (mode = AUMODE_RECORD; mode != -1;
mode = (mode == AUMODE_RECORD) ? AUMODE_PLAY : -1) {
if ((setmode & mode) == 0)
continue;
params = (mode == AUMODE_PLAY) ? play : rec;
fil = (mode == AUMODE_PLAY) ? pfil : rfil;
if (params == NULL)
continue;
/* AD1888 settings ... don't know the IXP limits */
if (params->sample_rate < AUIXP_MINRATE)
return EINVAL;
if (params->sample_rate > AUIXP_MAXRATE)
return EINVAL;
index = auconv_set_converter(sc->sc_formats, AUIXP_NFORMATS,
mode, params, TRUE, fil);
/* nothing found? */
if (index < 0)
return EINVAL;
/* not sure yet as to why i have to change params here */
if (fil->req_size > 0)
params = &fil->filters[0].param;
/* if variable speed and we can't set the desired rate, fail */
if ((sc->sc_formats[index].frequency_type != 1) &&
auixp_set_rate(co, mode, params->sample_rate))
return EINVAL;
/* preserve the settings */
if (mode == AUMODE_PLAY)
sc->sc_play_params = *params;
if (mode == AUMODE_RECORD)
sc->sc_rec_params = *params;
}
return 0;
}
/* called to translate a requested blocksize to a hw-possible one */
static int
auixp_round_blocksize(void *hdl, int bs, int mode, const audio_params_t *param)
{
uint32_t new_bs;
new_bs = bs;
/* Be conservative; align to 32 bytes and maximise it to 64 kb */
/* 256 kb possible */
if (new_bs > 0x10000)
bs = 0x10000; /* 64 kb max */
new_bs = (bs & ~0x20); /* 32 bytes align */
return new_bs;
}
/*
* allocate dma capable memory and record its information for later retrieval
* when we program the dma chain itself. The trigger routines passes on the
* kernel virtual address we return here as a reference to the mapping.
*/
static void *
auixp_malloc(void *hdl, int direction, size_t size,
struct malloc_type *type, int flags)
{
struct auixp_codec *co;
struct auixp_softc *sc;
struct auixp_dma *dma;
int error;
co = (struct auixp_codec *) hdl;
sc = co->sc;
/* get us a auixp_dma structure */
dma = malloc(sizeof(*dma), type, flags);
if (!dma)
return NULL;
/* get us a dma buffer itself */
error = auixp_allocmem(sc, size, 16, dma);
if (error) {
free(dma, type);
printf("%s: auixp_malloc: not enough memory\n",
sc->sc_dev.dv_xname);
return NULL;
}
SLIST_INSERT_HEAD(&sc->sc_dma_list, dma, dma_chain);
DPRINTF(("auixp_malloc: returning kern %p, hw 0x%08x for %d bytes "
"in %d segs\n", KERNADDR(dma), (uint32_t) DMAADDR(dma), dma->size,
dma->nsegs)
);
return KERNADDR(dma);
}
/*
* free and release dma capable memory we allocated before and remove its
* recording
*/
static void
auixp_free(void *hdl, void *addr, struct malloc_type *type)
{
struct auixp_codec *co;
struct auixp_softc *sc;
struct auixp_dma *dma;
co = (struct auixp_codec *) hdl;
sc = co->sc;
SLIST_FOREACH(dma, &sc->sc_dma_list, dma_chain) {
if (KERNADDR(dma) == addr) {
SLIST_REMOVE(&sc->sc_dma_list, dma, auixp_dma,
dma_chain);
auixp_freemem(sc, dma);
free(dma, type);
return;
}
}
}
static int
auixp_getdev(void *hdl, struct audio_device *ret)
{
*ret = auixp_device;
return 0;
}
/* pass request to AC'97 codec code */
static int
auixp_set_port(void *hdl, mixer_ctrl_t *mc)
{
struct auixp_codec *co;
co = (struct auixp_codec *) hdl;
return co->codec_if->vtbl->mixer_set_port(co->codec_if, mc);
}
/* pass request to AC'97 codec code */
static int
auixp_get_port(void *hdl, mixer_ctrl_t *mc)
{
struct auixp_codec *co;
co = (struct auixp_codec *) hdl;
return co->codec_if->vtbl->mixer_get_port(co->codec_if, mc);
}
/* pass request to AC'97 codec code */
static int
auixp_query_devinfo(void *hdl, mixer_devinfo_t *di)
{
struct auixp_codec *co;
co = (struct auixp_codec *) hdl;
return co->codec_if->vtbl->query_devinfo(co->codec_if, di);
}
static size_t
auixp_round_buffersize(void *hdl, int direction, size_t bufsize)
{
/* XXX force maximum? i.e. 256 kb? */
return bufsize;
}
static int
auixp_get_props(void *hdl)
{
return AUDIO_PROP_MMAP | AUDIO_PROP_INDEPENDENT | AUDIO_PROP_FULLDUPLEX;
}
/*
* A dma descriptor has dma->nsegs segments defined in dma->segs set up when
* we claimed the memory.
*
* Due to our demand for one contiguous DMA area, we only have one segment. A
* c_dma structure is about 3 kb for the 256 entries we maximally program
* -arbitrary limit AFAIK- so all is most likely to be in one segment/page
* anyway.
*
* XXX ought to implement fragmented dma area XXX
*
* Note that _v variables depict kernel virtual addresses, _p variables depict
* physical addresses.
*/
static void
auixp_link_daisychain(struct auixp_softc *sc,
struct auixp_dma *c_dma, struct auixp_dma *s_dma,
int blksize, int blocks)
{
atiixp_dma_desc_t *caddr_v, *next_caddr_v;
uint32_t caddr_p, next_caddr_p, saddr_p;
int i;
/* just make sure we are not changing when its running */
auixp_disable_dma(sc, c_dma);
/* setup dma chain start addresses */
caddr_v = KERNADDR(c_dma);
caddr_p = DMAADDR(c_dma);
saddr_p = DMAADDR(s_dma);
/* program the requested number of blocks */
for (i = 0; i < blocks; i++) {
/* clear the block just in case */
bzero(caddr_v, sizeof(atiixp_dma_desc_t));
/* round robin the chain dma addresses for its successor */
next_caddr_v = caddr_v + 1;
next_caddr_p = caddr_p + sizeof(atiixp_dma_desc_t);
if (i == blocks-1) {
next_caddr_v = KERNADDR(c_dma);
next_caddr_p = DMAADDR(c_dma);
}
/* fill in the hardware dma chain descriptor in little-endian */
caddr_v->addr = htole32(saddr_p);
caddr_v->status = htole16(0);
caddr_v->size = htole16((blksize >> 2)); /* in dwords (!!!) */
caddr_v->next = htole32(next_caddr_p);
/* advance slot */
saddr_p += blksize; /* XXX assuming contiguous XXX */
caddr_v = next_caddr_v;
caddr_p = next_caddr_p;
}
}
static int
auixp_allocate_dma_chain(struct auixp_softc *sc, struct auixp_dma **dmap)
{
struct auixp_dma *dma;
int error;
/* allocate keeper of dma area */
*dmap = NULL;
dma = malloc(sizeof(struct auixp_dma), M_DEVBUF, M_NOWAIT | M_ZERO);
if (!dma)
return ENOMEM;
/* allocate for daisychain of IXP hardware-dma descriptors */
error = auixp_allocmem(sc, DMA_DESC_CHAIN * sizeof(atiixp_dma_desc_t),
16, dma);
if (error) {
printf("%s: can't malloc dma descriptor chain\n",
sc->sc_dev.dv_xname);
free(dma, M_DEVBUF);
return ENOMEM;
}
/* return info and initialise structure */
dma->intr = NULL;
dma->intrarg = NULL;
*dmap = dma;
return 0;
}
/* program dma chain in it's link address descriptor */
static void
auixp_program_dma_chain(struct auixp_softc *sc, struct auixp_dma *dma)
{
bus_space_tag_t iot;
bus_space_handle_t ioh;
uint32_t value;
iot = sc->sc_iot;
ioh = sc->sc_ioh;
/* get hardware start address of DMA chain and set valid-flag in it */
/* XXX allways at start? XXX */
value = DMAADDR(dma);
value = value | ATI_REG_LINKPTR_EN;
/* reset linkpointer */
bus_space_write_4(iot, ioh, dma->linkptr, 0);
/* reset this DMA engine */
auixp_disable_dma(sc, dma);
auixp_enable_dma(sc, dma);
/* program new DMA linkpointer */
bus_space_write_4(iot, ioh, dma->linkptr, value);
}
/* called from interrupt code to signal end of one dma-slot */
static void
auixp_dma_update(struct auixp_softc *sc, struct auixp_dma *dma)
{
/* be very paranoid */
if (!dma)
panic("auixp: update: dma = NULL");
if (!dma->intr)
panic("auixp: update: dma->intr = NULL");
/* request more input from upper layer */
(*dma->intr)(dma->intrarg);
}
/*
* The magic `busbusy' bit that needs to be set when dma is active; allowing
* busmastering?
*/
static void
auixp_update_busbusy(struct auixp_softc *sc)
{
bus_space_tag_t iot;
bus_space_handle_t ioh;
uint32_t value;
int running;
iot = sc->sc_iot;
ioh = sc->sc_ioh;
/* set bus-busy flag when either recording or playing is performed */
value = bus_space_read_4(iot, ioh, ATI_REG_IER);
value &= ~ATI_REG_IER_SET_BUS_BUSY;
running = ((sc->sc_output_dma->running) || (sc->sc_input_dma->running));
if (running)
value |= ATI_REG_IER_SET_BUS_BUSY;
bus_space_write_4(iot, ioh, ATI_REG_IER, value);
}
/*
* Called from upper audio layer to request playing audio, only called once;
* audio is refilled by calling the intr() function when space is available
* again.
*/
/* XXX allmost literaly a copy of trigger-input; could be factorised XXX */
static int
auixp_trigger_output(void *hdl, void *start, void *end, int blksize,
void (*intr)(void *), void *intrarg, const audio_params_t *param)
{
struct auixp_codec *co;
struct auixp_softc *sc;
struct auixp_dma *chain_dma;
struct auixp_dma *sound_dma;
uint32_t blocks;
co = (struct auixp_codec *) hdl;
sc = co->sc;
chain_dma = sc->sc_output_dma;
/* add functions to call back */
chain_dma->intr = intr;
chain_dma->intrarg = intrarg;
/*
* Program output DMA chain with blocks from [start...end] with
* blksize fragments.
*
* NOTE, we can assume its in one block since we asked for it to be in
* one contiguous blob; XXX change this? XXX
*/
blocks = (size_t) (((caddr_t) end) - ((caddr_t) start)) / blksize;
/* lookup `start' address in our list of DMA area's */
SLIST_FOREACH(sound_dma, &sc->sc_dma_list, dma_chain) {
if (KERNADDR(sound_dma) == start)
break;
}
/* not ours ? then bail out */
if (!sound_dma) {
printf("%s: auixp_trigger_output: bad sound addr %p\n",
sc->sc_dev.dv_xname, start);
return EINVAL;
}
/* link round-robin daisychain and program hardware */
auixp_link_daisychain(sc, chain_dma, sound_dma, blksize, blocks);
auixp_program_dma_chain(sc, chain_dma);
/* mark we are now able to run now */
chain_dma->running = 1;
/* update bus-flags; XXX programs more flags XXX */
auixp_update_busbusy(sc);
/* callbacks happen in interrupt routine */
return 0;
}
/* halt output of audio, just disable it's dma and update bus state */
static int
auixp_halt_output(void *hdl)
{
struct auixp_codec *co;
struct auixp_softc *sc;
struct auixp_dma *dma;
co = (struct auixp_codec *) hdl;
sc = co->sc;
dma = sc->sc_output_dma;
auixp_disable_dma(sc, dma);
dma->running = 0;
auixp_update_busbusy(sc);
return 0;
}
/* XXX allmost literaly a copy of trigger-output; could be factorised XXX */
static int
auixp_trigger_input(void *hdl, void *start, void *end, int blksize,
void (*intr)(void *), void *intrarg, const audio_params_t *param)
{
struct auixp_codec *co;
struct auixp_softc *sc;
struct auixp_dma *chain_dma;
struct auixp_dma *sound_dma;
uint32_t blocks;
co = (struct auixp_codec *) hdl;
sc = co->sc;
chain_dma = sc->sc_input_dma;
/* add functions to call back */
chain_dma->intr = intr;
chain_dma->intrarg = intrarg;
/*
* Program output DMA chain with blocks from [start...end] with
* blksize fragments.
*
* NOTE, we can assume its in one block since we asked for it to be in
* one contiguous blob; XXX change this? XXX
*/
blocks = (size_t) (((caddr_t) end) - ((caddr_t) start)) / blksize;
/* lookup `start' address in our list of DMA area's */
SLIST_FOREACH(sound_dma, &sc->sc_dma_list, dma_chain) {
if (KERNADDR(sound_dma) == start)
break;
}
/* not ours ? then bail out */
if (!sound_dma) {
printf("%s: auixp_trigger_input: bad sound addr %p\n",
sc->sc_dev.dv_xname, start);
return EINVAL;
}
/* link round-robin daisychain and program hardware */
auixp_link_daisychain(sc, chain_dma, sound_dma, blksize, blocks);
auixp_program_dma_chain(sc, chain_dma);
/* mark we are now able to run now */
chain_dma->running = 1;
/* update bus-flags; XXX programs more flags XXX */
auixp_update_busbusy(sc);
/* callbacks happen in interrupt routine */
return 0;
}
/* halt sampling audio, just disable it's dma and update bus state */
static int
auixp_halt_input(void *hdl)
{
struct auixp_codec *co;
struct auixp_softc *sc;
struct auixp_dma *dma;
co = (struct auixp_codec *) hdl;
sc = co->sc;
dma = sc->sc_input_dma;
auixp_disable_dma(sc, dma);
dma->running = 0;
auixp_update_busbusy(sc);
return 0;
}
/*
* IXP audio interrupt handler
*
* note that we return the number of bits handled; the return value is not
* documentated but i saw it implemented in other drivers. Prolly returning a
* value > 0 means "i've dealt with it"
*
*/
static int
auixp_intr(void *softc)
{
struct auixp_softc *sc;
bus_space_tag_t iot;
bus_space_handle_t ioh;
uint32_t status, enable, detected_codecs;
int ret;
sc = softc;
iot = sc->sc_iot;
ioh = sc->sc_ioh;
ret = 0;
/* get status from the interrupt status register */
status = bus_space_read_4(iot, ioh, ATI_REG_ISR);
if (status == 0)
return 0;
DPRINTF(("%s: (status = %x)\n", sc->sc_dev.dv_xname, status));
/* check DMA UPDATE flags for input & output */
if (status & ATI_REG_ISR_IN_STATUS) {
ret++; DPRINTF(("IN_STATUS\n"));
auixp_dma_update(sc, sc->sc_input_dma);
}
if (status & ATI_REG_ISR_OUT_STATUS) {
ret++; DPRINTF(("OUT_STATUS\n"));
auixp_dma_update(sc, sc->sc_output_dma);
}
/* XXX XRUN flags not used/needed yet; should i implement it? XXX */
/* acknowledge the interrupts nevertheless */
if (status & ATI_REG_ISR_IN_XRUN) {
ret++; DPRINTF(("IN_XRUN\n"));
/* auixp_dma_xrun(sc, sc->sc_input_dma); */
}
if (status & ATI_REG_ISR_OUT_XRUN) {
ret++; DPRINTF(("OUT_XRUN\n"));
/* auixp_dma_xrun(sc, sc->sc_output_dma); */
}
/* check if we are looking for codec detection */
if (status & CODEC_CHECK_BITS) {
ret++;
/* mark missing codecs as not ready */
detected_codecs = status & CODEC_CHECK_BITS;
sc->sc_codec_not_ready_bits |= detected_codecs;
/* disable detected interupt sources */
enable = bus_space_read_4(iot, ioh, ATI_REG_IER);
enable &= ~detected_codecs;
bus_space_write_4(iot, ioh, ATI_REG_IER, enable);
}
/* acknowledge interrupt sources */
bus_space_write_4(iot, ioh, ATI_REG_ISR, status);
return ret;
}
/* allocate memory for dma purposes; on failure of any of the steps, roll back */
static int
auixp_allocmem(struct auixp_softc *sc, size_t size,
size_t align, struct auixp_dma *dma)
{
int error;
/* remember size */
dma->size = size;
/* allocate DMA safe memory but in just one segment for now :( */
error = bus_dmamem_alloc(sc->sc_dmat, dma->size, align, 0,
dma->segs, sizeof(dma->segs) / sizeof(dma->segs[0]), &dma->nsegs,
BUS_DMA_NOWAIT);
if (error)
return error;
/*
* map allocated memory into kernel virtual address space and keep it
* coherent with the CPU.
*/
error = bus_dmamem_map(sc->sc_dmat, dma->segs, dma->nsegs, dma->size,
&dma->addr, BUS_DMA_NOWAIT | BUS_DMA_COHERENT);
if (error)
goto free;
/* allocate associated dma handle and initialize it. */
error = bus_dmamap_create(sc->sc_dmat, dma->size, 1, dma->size, 0,
BUS_DMA_NOWAIT, &dma->map);
if (error)
goto unmap;
/*
* load the dma handle with mappings for a dma transfer; all pages
* need to be wired.
*/
error = bus_dmamap_load(sc->sc_dmat, dma->map, dma->addr, dma->size, NULL,
BUS_DMA_NOWAIT);
if (error)
goto destroy;
return 0;
destroy:
bus_dmamap_destroy(sc->sc_dmat, dma->map);
unmap:
bus_dmamem_unmap(sc->sc_dmat, dma->addr, dma->size);
free:
bus_dmamem_free(sc->sc_dmat, dma->segs, dma->nsegs);
return error;
}
/* undo dma mapping and release memory allocated */
static int
auixp_freemem(struct auixp_softc *sc, struct auixp_dma *p)
{
bus_dmamap_unload(sc->sc_dmat, p->map);
bus_dmamap_destroy(sc->sc_dmat, p->map);
bus_dmamem_unmap(sc->sc_dmat, p->addr, p->size);
bus_dmamem_free(sc->sc_dmat, p->segs, p->nsegs);
return 0;
}
/* memory map dma memory */
static paddr_t
auixp_mappage(void *hdl, void *mem, off_t off, int prot)
{
struct auixp_codec *co;
struct auixp_softc *sc;
struct auixp_dma *p;
co = (struct auixp_codec *) hdl;
sc = co->sc;
/* for sanity */
if (off < 0)
return -1;
/* look up allocated DMA area */
SLIST_FOREACH(p, &sc->sc_dma_list, dma_chain) {
if (KERNADDR(p) == mem)
break;
}
/* have we found it ? */
if (!p)
return -1;
/* return mmap'd region */
return bus_dmamem_mmap(sc->sc_dmat, p->segs, p->nsegs,
off, prot, BUS_DMA_WAITOK);
}
/*
* Attachment section
*/
/* Is it my hardware? */
static int
auixp_match(struct device *dev, struct cfdata *match, void *aux)
{
struct pci_attach_args *pa;
pa = (struct pci_attach_args *)aux;
switch(PCI_VENDOR(pa->pa_id)) {
case PCI_VENDOR_ATI:
switch(PCI_PRODUCT(pa->pa_id)) {
case PCI_PRODUCT_ATI_IXP_AUDIO_200:
case PCI_PRODUCT_ATI_IXP_AUDIO_300:
case PCI_PRODUCT_ATI_IXP_AUDIO_400:
return 1;
}
}
return 0;
}
/* it is... now hook up and set up the resources we need */
static void
auixp_attach(struct device *parent, struct device *self, void *aux)
{
struct auixp_softc *sc;
struct pci_attach_args *pa;
pcitag_t tag;
pci_chipset_tag_t pc;
pci_intr_handle_t ih;
const struct auixp_card_type *card;
const char *intrstr;
uint32_t data;
char devinfo[256];
int revision, len, error;
sc = (struct auixp_softc *)self;
pa = (struct pci_attach_args *)aux;
tag = pa->pa_tag;
pc = pa->pa_pc;
#ifdef DEBUG_AUIXP
static_sc = sc;
#endif
/* print information confirming attachment */
aprint_naive(": Audio controller\n");
pci_devinfo(pa->pa_id, pa->pa_class, 0, devinfo, sizeof(devinfo));
revision = PCI_REVISION(pa->pa_class);
aprint_normal(": %s (rev. 0x%02x)\n", devinfo, revision);
/* set up details from our set of known `cards'/chips */
for (card = auixp_card_types; card->pci_vendor_id; card++)
if (PCI_VENDOR(pa->pa_id) == card->pci_vendor_id &&
PCI_PRODUCT(pa->pa_id) == card->pci_product_id) {
sc->type = card->type;
break;
}
/* device only has 32 bit non prefetchable memory */
/* set MEM space access and enable the card's busmastering */
data = pci_conf_read(pc, tag, PCI_COMMAND_STATUS_REG);
data |= (PCI_COMMAND_MEM_ENABLE | PCI_COMMAND_MASTER_ENABLE);
pci_conf_write(pc, tag, PCI_COMMAND_STATUS_REG, data);
/* map memory; its not sized -> what is the size? max PCI slot size? */
if (pci_mapreg_map(pa, PCI_CBIO, PCI_MAPREG_TYPE_MEM, 0,
&sc->sc_iot, &sc->sc_ioh, &sc->sc_iob, &sc->sc_ios)) {
aprint_error("%s: can't map memory space\n",
sc->sc_dev.dv_xname);
return;
}
/* Initialize softc */
sc->sc_tag = tag;
sc->sc_pct = pc;
sc->sc_dmat = pa->pa_dmat;
SLIST_INIT(&sc->sc_dma_list);
/* get us the auixp_dma structures */
auixp_allocate_dma_chain(sc, &sc->sc_output_dma);
auixp_allocate_dma_chain(sc, &sc->sc_input_dma);
/* when that fails we are dead in the water */
if (!sc->sc_output_dma || !sc->sc_input_dma)
return;
#if 0
/* could preliminary program DMA chain */
auixp_program_dma_chain(sc, sc->sc_output_dma);
auixp_program_dma_chain(sc, sc->sc_input_dma);
#endif
/* map interrupt on the pci bus */
if (pci_intr_map(pa, &ih)) {
aprint_error("%s: can't map interrupt\n", sc->sc_dev.dv_xname);
return;
}
/* where are we connected at ? */
intrstr = pci_intr_string(pc, ih);
/* establish interrupt routine hookup at IPL_AUDIO level */
sc->sc_ih = pci_intr_establish(pc, ih, IPL_AUDIO, auixp_intr, self);
if (sc->sc_ih == NULL) {
aprint_error("%s: can't establish interrupt",
sc->sc_dev.dv_xname);
if (intrstr != NULL)
aprint_normal(" at %s", intrstr);
aprint_normal("\n");
return;
}
aprint_normal("%s: interrupting at %s\n", sc->sc_dev.dv_xname, intrstr);
/* power up chip */
if ((error = pci_activate(pa->pa_pc, pa->pa_tag, sc,
pci_activate_null)) && error != EOPNOTSUPP) {
aprint_error("%s: cannot activate %d\n", sc->sc_dev.dv_xname,
error);
return;
}
/* init chip */
if (auixp_init(sc) == -1) {
aprint_error("%s: auixp_attach: unable to initialize the card\n",
sc->sc_dev.dv_xname);
return;
}
/* XXX set up power hooks; not implemented yet XXX */
len = 1; /* shut up gcc */
#ifdef notyet
/* create suspend save area */
len = sizeof(uint16_t) * (ESA_REV_B_CODE_MEMORY_LENGTH
+ ESA_REV_B_DATA_MEMORY_LENGTH + 1);
sc->savemem = (uint16_t *)malloc(len, M_DEVBUF, M_NOWAIT | M_ZERO);
if (sc->savemem == NULL) {
aprint_error("%s: unable to allocate suspend buffer\n",
sc->sc_dev.dv_xname);
return;
}
sc->powerhook = powerhook_establish(auixp_powerhook, sc);
if (sc->powerhook == NULL)
aprint_error("%s: WARNING: unable to establish powerhook\n",
sc->sc_dev.dv_xname);
#endif
/*
* delay further configuration of codecs and audio after interrupts
* are enabled.
*/
config_interrupts(self, auixp_post_config);
}
/* called from autoconfigure system when interrupts are enabled */
static void
auixp_post_config(struct device *self)
{
struct auixp_softc *sc;
struct auixp_codec *codec;
int codec_nr;
int res, i;
sc = (struct auixp_softc *)self;
/* detect the AC97 codecs */
auixp_autodetect_codecs(sc);
/* setup audio translation formats : following codec0 (!) */
codec = &sc->sc_codec[0];
if (!codec->present) {
/* nothing??? then invalidate all formats */
for (i = 0; i < AUIXP_NFORMATS; i++) {
AUFMT_INVALIDATE(&sc->sc_formats[i]);
}
return;
}
/* copy formats and invalidate entries not suitable for codec0 */
memcpy(sc->sc_formats, auixp_formats, sizeof(auixp_formats));
sc->has_4ch = AC97_IS_4CH(codec->codec_if);
sc->has_6ch = AC97_IS_6CH(codec->codec_if);
sc->is_fixed = AC97_IS_FIXED_RATE(codec->codec_if);
sc->has_spdif = AC97_HAS_SPDIF(codec->codec_if);
for (i = 0; i < AUIXP_NFORMATS; i++) {
if (sc->is_fixed) {
sc->sc_formats[i].frequency_type = 1;
sc->sc_formats[i].frequency[0] = 48000;
}
switch (sc->sc_formats[i].channels) {
case 4 :
if (sc->has_4ch)
break;
AUFMT_INVALIDATE(&sc->sc_formats[i]);
break;
case 6 :
if (sc->has_6ch)
break;
AUFMT_INVALIDATE(&sc->sc_formats[i]);
break;
default :
break;
}
}
/*
* Create all encodings (and/or -translations) based on the formats
* supported. */
res = auconv_create_encodings(sc->sc_formats, AUIXP_NFORMATS,
&sc->sc_encodings);
if (res) {
printf("%s: auconv_create_encodings failed; "
"no attachments\n", sc->sc_dev.dv_xname);
return;
}
/* attach audio devices for all detected codecs */
/* XXX wise? look at other multiple-codec able chipsets XXX */
for (codec_nr = 0; codec_nr < ATI_IXP_CODECS; codec_nr++) {
codec = &sc->sc_codec[codec_nr];
if (codec->present)
audio_attach_mi(&auixp_hw_if, codec, &sc->sc_dev);
}
if (sc->has_spdif) {
aprint_normal("%s: codec spdif support detected but disabled "
"for now\n", sc->sc_dev.dv_xname);
sc->has_spdif = 0;
}
/* fill in the missing details about the dma channels. */
/* for output */
sc->sc_output_dma->linkptr = ATI_REG_OUT_DMA_LINKPTR;
sc->sc_output_dma->dma_enable_bit = ATI_REG_CMD_OUT_DMA_EN |
ATI_REG_CMD_SEND_EN;
/* have spdif? then this too! XXX not seeing LED yet! XXX */
if (sc->has_spdif)
sc->sc_output_dma->dma_enable_bit |= ATI_REG_CMD_SPDF_OUT_EN;
/* and for input */
sc->sc_input_dma->linkptr = ATI_REG_IN_DMA_LINKPTR;
sc->sc_input_dma->dma_enable_bit = ATI_REG_CMD_IN_DMA_EN |
ATI_REG_CMD_RECEIVE_EN;
/* done! now enable all interrupts we can service */
auixp_enable_interrupts(sc);
}
static void
auixp_enable_interrupts(struct auixp_softc *sc)
{
bus_space_tag_t iot;
bus_space_handle_t ioh;
uint32_t value;
iot = sc->sc_iot;
ioh = sc->sc_ioh;
/* clear all pending */
bus_space_write_4(iot, ioh, ATI_REG_ISR, 0xffffffff);
/* enable all relevant interrupt sources we can handle */
value = bus_space_read_4(iot, ioh, ATI_REG_IER);
value |= ATI_REG_IER_IO_STATUS_EN;
#ifdef notyet
value |= ATI_REG_IER_IN_XRUN_EN;
value |= ATI_REG_IER_OUT_XRUN_EN;
value |= ATI_REG_IER_SPDIF_XRUN_EN;
value |= ATI_REG_IER_SPDF_STATUS_EN;
#endif
bus_space_write_4(iot, ioh, ATI_REG_IER, value);
}
static void
auixp_disable_interrupts(struct auixp_softc *sc)
{
bus_space_tag_t iot;
bus_space_handle_t ioh;
iot = sc->sc_iot;
ioh = sc->sc_ioh;
/* disable all interrupt sources */
bus_space_write_4(iot, ioh, ATI_REG_IER, 0);
/* clear all pending */
bus_space_write_4(iot, ioh, ATI_REG_ISR, 0xffffffff);
}
/* dismantle what we've set up by undoing setup */
static int
auixp_detach(struct device *self, int flags)
{
struct auixp_softc *sc;
sc = (struct auixp_softc *)self;
/* XXX shouldn't we just reset the chip? XXX */
/*
* should we explicitly disable interrupt generation and acknowledge
* what's left on? better be safe than sorry.
*/
auixp_disable_interrupts(sc);
/* tear down .... */
config_detach(&sc->sc_dev, flags); /* XXX OK? XXX */
if (sc->sc_ih != NULL)
pci_intr_disestablish(sc->sc_pct, sc->sc_ih);
if (sc->sc_ios)
bus_space_unmap(sc->sc_iot, sc->sc_ioh, sc->sc_ios);
if (sc->savemem)
free(sc->savemem, M_DEVBUF);
return 0;
}
/*
* codec handling
*
* IXP audio support can have upto 3 codecs! are they chained ? or
* alternative outlets with the same audio feed i.e. with different mixer
* settings? XXX does NetBSD support more than one audio codec? XXX
*/
static int
auixp_attach_codec(void *aux, struct ac97_codec_if *codec_if)
{
struct auixp_codec *ixp_codec;
ixp_codec = aux;
ixp_codec->codec_if = codec_if;
ixp_codec->present = 1;
return 0;
}
static int
auixp_read_codec(void *aux, uint8_t reg, uint16_t *result)
{
struct auixp_codec *co;
struct auixp_softc *sc;
bus_space_tag_t iot;
bus_space_handle_t ioh;
uint32_t data;
int timeout;
co = aux;
sc = co->sc;
iot = sc->sc_iot;
ioh = sc->sc_ioh;
if (auixp_wait_for_codecs(sc, "read_codec"))
return 0xffff;
/* build up command for reading codec register */
data = (reg << ATI_REG_PHYS_OUT_ADDR_SHIFT) |
ATI_REG_PHYS_OUT_ADDR_EN |
ATI_REG_PHYS_OUT_RW |
co->codec_nr;
bus_space_write_4(iot, ioh, ATI_REG_PHYS_OUT_ADDR, data);
if (auixp_wait_for_codecs(sc, "read_codec"))
return 0xffff;
/* wait until codec info is clocked in */
timeout = 500; /* 500*2 usec -> 0.001 sec */
do {
data = bus_space_read_4(iot, ioh, ATI_REG_PHYS_IN_ADDR);
if (data & ATI_REG_PHYS_IN_READ_FLAG) {
DPRINTF(("read ac'97 codec reg 0x%x = 0x%08x\n",
reg, data >> ATI_REG_PHYS_IN_DATA_SHIFT)
);
*result = data >> ATI_REG_PHYS_IN_DATA_SHIFT;
return 0;
}
DELAY(2);
timeout--;
} while (timeout > 0);
if (reg < 0x7c)
printf("%s: codec read timeout! (reg %x)\n",
sc->sc_dev.dv_xname, reg);
return 0xffff;
}
static int
auixp_write_codec(void *aux, uint8_t reg, uint16_t data)
{
struct auixp_codec *co;
struct auixp_softc *sc;
bus_space_tag_t iot;
bus_space_handle_t ioh;
uint32_t value;
DPRINTF(("write ac'97 codec reg 0x%x = 0x%08x\n", reg, data));
co = aux;
sc = co->sc;
iot = sc->sc_iot;
ioh = sc->sc_ioh;
if (auixp_wait_for_codecs(sc, "write_codec"))
return -1;
/* build up command for writing codec register */
value = (((uint32_t) data) << ATI_REG_PHYS_OUT_DATA_SHIFT) |
(((uint32_t) reg) << ATI_REG_PHYS_OUT_ADDR_SHIFT) |
ATI_REG_PHYS_OUT_ADDR_EN |
co->codec_nr;
bus_space_write_4(iot, ioh, ATI_REG_PHYS_OUT_ADDR, value);
return 0;
}
static int
auixp_reset_codec(void *aux)
{
/* nothing to be done? */
return 0;
}
static enum ac97_host_flags
auixp_flags_codec(void *aux)
{
struct auixp_codec *ixp_codec;
ixp_codec = aux;
return ixp_codec->codec_flags;
}
static int
auixp_wait_for_codecs(struct auixp_softc *sc, const char *func)
{
bus_space_tag_t iot;
bus_space_handle_t ioh;
uint32_t value;
int timeout;
iot = sc->sc_iot;
ioh = sc->sc_ioh;
/* wait until all codec transfers are done */
timeout = 500; /* 500*2 usec -> 0.001 sec */
do {
value = bus_space_read_4(iot, ioh, ATI_REG_PHYS_OUT_ADDR);
if ((value & ATI_REG_PHYS_OUT_ADDR_EN) == 0)
return 0;
DELAY(2);
timeout--;
} while (timeout > 0);
printf("%s: %s: timed out\n", func, sc->sc_dev.dv_xname);
return -1;
}
static void
auixp_autodetect_codecs(struct auixp_softc *sc)
{
bus_space_tag_t iot;
bus_space_handle_t ioh;
struct auixp_codec *codec;
int timeout, codec_nr;
iot = sc->sc_iot;
ioh = sc->sc_ioh;
/* ATI IXP can have upto 3 codecs; mark all codecs as not existing */
sc->sc_codec_not_ready_bits = 0;
sc->sc_num_codecs = 0;
/* enable all codecs to interrupt as well as the new frame interrupt */
bus_space_write_4(iot, ioh, ATI_REG_IER, CODEC_CHECK_BITS);
/* wait for the interrupts to happen */
timeout = 100; /* 100.000 usec -> 0.1 sec */
while (timeout > 0) {
DELAY(1000);
if (sc->sc_codec_not_ready_bits)
break;
timeout--;
}
if (timeout == 0)
printf("%s: WARNING: timeout during codec detection; "
"codecs might be present but haven't interrupted\n",
sc->sc_dev.dv_xname);
/* disable all interrupts for now */
auixp_disable_interrupts(sc);
/* Attach AC97 host interfaces */
for (codec_nr = 0; codec_nr < ATI_IXP_CODECS; codec_nr++) {
codec = &sc->sc_codec[codec_nr];
bzero(codec, sizeof(struct auixp_codec));
codec->sc = sc;
codec->codec_nr = codec_nr;
codec->present = 0;
codec->host_if.arg = codec;
codec->host_if.attach = auixp_attach_codec;
codec->host_if.read = auixp_read_codec;
codec->host_if.write = auixp_write_codec;
codec->host_if.reset = auixp_reset_codec;
codec->host_if.flags = auixp_flags_codec;
}
if (!(sc->sc_codec_not_ready_bits & ATI_REG_ISR_CODEC0_NOT_READY)) {
/* codec 0 present */
DPRINTF(("auixp : YAY! codec 0 present!\n"));
if (ac97_attach(&sc->sc_codec[0].host_if, &sc->sc_dev) == 0)
sc->sc_num_codecs++;
}
if (!(sc->sc_codec_not_ready_bits & ATI_REG_ISR_CODEC1_NOT_READY)) {
/* codec 1 present */
DPRINTF(("auixp : YAY! codec 1 present!\n"));
if (ac97_attach(&sc->sc_codec[1].host_if, &sc->sc_dev) == 0)
sc->sc_num_codecs++;
}
if (!(sc->sc_codec_not_ready_bits & ATI_REG_ISR_CODEC2_NOT_READY)) {
/* codec 2 present */
DPRINTF(("auixp : YAY! codec 2 present!\n"));
if (ac97_attach(&sc->sc_codec[2].host_if, &sc->sc_dev) == 0)
sc->sc_num_codecs++;
}
if (sc->sc_num_codecs == 0) {
printf("%s: no codecs detected or "
"no codecs managed to initialise\n",
sc->sc_dev.dv_xname);
return;
}
}
/* initialisation routines */
static void
auixp_disable_dma(struct auixp_softc *sc, struct auixp_dma *dma)
{
bus_space_tag_t iot;
bus_space_handle_t ioh;
uint32_t value;
iot = sc->sc_iot;
ioh = sc->sc_ioh;
/* lets not stress the DMA engine more than nessisary */
value = bus_space_read_4(iot, ioh, ATI_REG_CMD);
if (value & dma->dma_enable_bit) {
value &= ~dma->dma_enable_bit;
bus_space_write_4(iot, ioh, ATI_REG_CMD, value);
}
}
static void
auixp_enable_dma(struct auixp_softc *sc, struct auixp_dma *dma)
{
bus_space_tag_t iot;
bus_space_handle_t ioh;
uint32_t value;
iot = sc->sc_iot;
ioh = sc->sc_ioh;
/* lets not stress the DMA engine more than nessisary */
value = bus_space_read_4(iot, ioh, ATI_REG_CMD);
if (!(value & dma->dma_enable_bit)) {
value |= dma->dma_enable_bit;
bus_space_write_4(iot, ioh, ATI_REG_CMD, value);
}
}
static void
auixp_reset_aclink(struct auixp_softc *sc)
{
bus_space_tag_t iot;
bus_space_handle_t ioh;
uint32_t value, timeout;
iot = sc->sc_iot;
ioh = sc->sc_ioh;
/* if power is down, power it up */
value = bus_space_read_4(iot, ioh, ATI_REG_CMD);
if (value & ATI_REG_CMD_POWERDOWN) {
printf("%s: powering up\n", sc->sc_dev.dv_xname);
/* explicitly enable power */
value &= ~ATI_REG_CMD_POWERDOWN;
bus_space_write_4(iot, ioh, ATI_REG_CMD, value);
/* have to wait at least 10 usec for it to initialise */
DELAY(20);
};
printf("%s: soft resetting aclink\n", sc->sc_dev.dv_xname);
/* perform a soft reset */
value = bus_space_read_4(iot, ioh, ATI_REG_CMD);
value |= ATI_REG_CMD_AC_SOFT_RESET;
bus_space_write_4(iot, ioh, ATI_REG_CMD, value);
/* need to read the CMD reg and wait aprox. 10 usec to init */
value = bus_space_read_4(iot, ioh, ATI_REG_CMD);
DELAY(20);
/* clear soft reset flag again */
value = bus_space_read_4(iot, ioh, ATI_REG_CMD);
value &= ~ATI_REG_CMD_AC_SOFT_RESET;
bus_space_write_4(iot, ioh, ATI_REG_CMD, value);
/* check if the ac-link is working; reset device otherwise */
timeout = 10;
value = bus_space_read_4(iot, ioh, ATI_REG_CMD);
while (!(value & ATI_REG_CMD_ACLINK_ACTIVE)) {
printf("%s: not up; resetting aclink hardware\n",
sc->sc_dev.dv_xname);
/* dip aclink reset but keep the acsync */
value &= ~ATI_REG_CMD_AC_RESET;
value |= ATI_REG_CMD_AC_SYNC;
bus_space_write_4(iot, ioh, ATI_REG_CMD, value);
/* need to read CMD again and wait again (clocking in issue?) */
value = bus_space_read_4(iot, ioh, ATI_REG_CMD);
DELAY(20);
/* assert aclink reset again */
value = bus_space_read_4(iot, ioh, ATI_REG_CMD);
value |= ATI_REG_CMD_AC_RESET;
bus_space_write_4(iot, ioh, ATI_REG_CMD, value);
/* check if its active now */
value = bus_space_read_4(iot, ioh, ATI_REG_CMD);
timeout--;
if (timeout == 0) break;
};
if (timeout == 0) {
printf("%s: giving up aclink reset\n", sc->sc_dev.dv_xname);
};
if (timeout != 10) {
printf("%s: aclink hardware reset successful\n",
sc->sc_dev.dv_xname);
};
/* assert reset and sync for safety */
value = bus_space_read_4(iot, ioh, ATI_REG_CMD);
value |= ATI_REG_CMD_AC_SYNC | ATI_REG_CMD_AC_RESET;
bus_space_write_4(iot, ioh, ATI_REG_CMD, value);
}
/* chip hard init */
static int
auixp_init(struct auixp_softc *sc)
{
bus_space_tag_t iot;
bus_space_handle_t ioh;
uint32_t value;
iot = sc->sc_iot;
ioh = sc->sc_ioh;
/* disable all interrupts and clear all sources */
auixp_disable_interrupts(sc);
/* clear all DMA enables (preserving rest of settings) */
value = bus_space_read_4(iot, ioh, ATI_REG_CMD);
value &= ~( ATI_REG_CMD_IN_DMA_EN |
ATI_REG_CMD_OUT_DMA_EN |
ATI_REG_CMD_SPDF_OUT_EN );
bus_space_write_4(iot, ioh, ATI_REG_CMD, value);
/* Reset AC-link */
auixp_reset_aclink(sc);
/*
* codecs get auto-detected later
*
* note: we are NOT enabling interrupts yet, no codecs have been
* detected yet nor is anything else set up
*/
return 0;
}
/*
* TODO power saving and suspend / resume support
*
*/
#if 0
static void
auixp_powerhook(int why, void *hdl)
{
struct auixp_softc *sc;
sc = (struct auixp_softc *)hdl;
switch (why) {
case PWR_SUSPEND:
case PWR_STANDBY:
auixp_suspend(sc);
break;
case PWR_RESUME:
auixp_resume(sc);
/* XXX fix me XXX */
// (sc->codec_if->vtbl->restore_ports)(sc->codec_if);
break;
}
}
static int
auixp_suspend(struct auixp_softc *sc)
{
/* XXX no power functions yet XXX */
return 0;
}
static int
auixp_resume(struct auixp_softc *sc)
{
/* XXX no power functions yet XXX */
return 0;
}
#endif /* 0 */
#ifdef DEBUG_AUIXP
static void
auixp_dumpreg(void)
{
struct auixp_softc *sc;
bus_space_tag_t iot;
bus_space_handle_t ioh;
int i;
sc = static_sc;
iot = sc->sc_iot;
ioh = sc->sc_ioh;
printf("%s register dump:\n", sc->sc_dev.dv_xname);
for (i = 0; i < 256; i+=4) {
printf("\t0x%02x: 0x%08x\n", i, bus_space_read_4(iot, ioh, i));
}
printf("\n");
}
#endif