diff options
Diffstat (limited to 'sys/dev/pci/auixp.c')
-rw-r--r-- | sys/dev/pci/auixp.c | 1850 |
1 files changed, 1850 insertions, 0 deletions
diff --git a/sys/dev/pci/auixp.c b/sys/dev/pci/auixp.c new file mode 100644 index 00000000000..d0e3bc25a1d --- /dev/null +++ b/sys/dev/pci/auixp.c @@ -0,0 +1,1850 @@ +/* $OpenBSD: auixp.c,v 1.1 2005/08/07 20:08:45 mickey Exp $ */ +/* $NetBSD: auixp.c,v 1.9 2005/06/27 21:13:09 thorpej 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. + */ +/* + * 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. + */ + +/*#define DEBUG_AUIXP*/ + +#include <sys/types.h> +#include <sys/errno.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/ac97.h> + +#include <dev/pci/auixpreg.h> +#include <dev/pci/auixpvar.h> + +/* 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) + +/* 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) + +const struct pci_matchid auixp_pci_devices[] = { + { PCI_VENDOR_ATI, PCI_PRODUCT_ATI_IXP_AUDIO_200 }, + { PCI_VENDOR_ATI, PCI_PRODUCT_ATI_IXP_AUDIO_300 }, + { PCI_VENDOR_ATI, PCI_PRODUCT_ATI_IXP_AUDIO_400 }, +}; + +struct cfdriver auixp_cd = { + NULL, "auixp", DV_DULL +}; + +int auixp_match( struct device *, void *, void *); +void auixp_attach(struct device *, struct device *, void *); +int auixp_detach(struct device *, int); + +struct cfattach auixp_ca = { + sizeof(struct auixp_softc), auixp_match, auixp_attach +}; + +int auixp_open(void *v, int flags); +void auixp_close(void *v); +int auixp_query_encoding(void *, struct audio_encoding *); +int auixp_set_params(void *, int, int, struct audio_params *, + struct audio_params *); +int auixp_commit_settings(void *); +int auixp_round_blocksize(void *, int); +int auixp_trigger_output(void *, void *, void *, int, + void (*)(void *), void *, struct audio_params *); +int auixp_trigger_input(void *, void *, void *, int, + void (*)(void *), void *, struct audio_params *); +int auixp_halt_output(void *); +int auixp_halt_input(void *); +int auixp_set_port(void *, mixer_ctrl_t *); +int auixp_get_port(void *, mixer_ctrl_t *); +int auixp_query_devinfo(void *, mixer_devinfo_t *); +void * auixp_malloc(void *, int, size_t, int, int); +void auixp_free(void *, void *, int); +int auixp_getdev(void *, struct audio_device *); +size_t auixp_round_buffersize(void *, int, size_t); +int auixp_get_props(void *); +int auixp_intr(void *); +int auixp_allocmem(struct auixp_softc *, size_t, size_t, + struct auixp_dma *); +int auixp_freemem(struct auixp_softc *, struct auixp_dma *); +paddr_t auixp_mappage(void *, void *, off_t, int); + + +/* power management (do we support that already?) */ +int auixp_power(struct auixp_softc *, int); +#if 0 +void auixp_powerhook(int, void *); +int auixp_suspend(struct auixp_softc *); +int auixp_resume(struct auixp_softc *); +#endif + + +/* Supporting subroutines */ +int auixp_init(struct auixp_softc *); +void auixp_autodetect_codecs(struct auixp_softc *); +void auixp_post_config(void *); + +void auixp_reset_aclink(struct auixp_softc *); +int auixp_attach_codec(void *, struct ac97_codec_if *); +int auixp_read_codec(void *, u_int8_t, u_int16_t *); +int auixp_write_codec(void *, u_int8_t, u_int16_t); +int auixp_wait_for_codecs(struct auixp_softc *, const char *); +void auixp_reset_codec(void *); +enum ac97_host_flags auixp_flags_codec(void *); + +void auixp_enable_dma(struct auixp_softc *, struct auixp_dma *); +void auixp_disable_dma(struct auixp_softc *, struct auixp_dma *); +void auixp_enable_interrupts(struct auixp_softc *); +void auixp_disable_interrupts(struct auixp_softc *); + +void auixp_link_daisychain(struct auixp_softc *, + struct auixp_dma *, struct auixp_dma *, int, int); +int auixp_allocate_dma_chain(struct auixp_softc *, struct auixp_dma **); +void auixp_program_dma_chain(struct auixp_softc *, struct auixp_dma *); +void auixp_dma_update(struct auixp_softc *, struct auixp_dma *); +void auixp_update_busbusy(struct auixp_softc *); + +#ifdef DEBUG_AUIXP +#define DPRINTF(x) printf x; +#else +#define DPRINTF(x) +#endif + +struct audio_hw_if auixp_hw_if = { + auixp_open, + auixp_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 +}; + +int +auixp_open(void *v, int flags) +{ + + return 0; +} + +void +auixp_close(void *v) +{ +} + +int +auixp_query_encoding(void *hdl, struct audio_encoding *aep) +{ + switch (aep->index) { + case 0: + strlcpy(aep->name, AudioEulinear, sizeof aep->name); + aep->encoding = AUDIO_ENCODING_ULINEAR; + aep->precision = 8; + aep->flags = AUDIO_ENCODINGFLAG_EMULATED; + return (0); + case 1: + strlcpy(aep->name, AudioEmulaw, sizeof aep->name); + aep->encoding = AUDIO_ENCODING_ULAW; + aep->precision = 8; + aep->flags = AUDIO_ENCODINGFLAG_EMULATED; + return (0); + case 2: + strlcpy(aep->name, AudioEalaw, sizeof aep->name); + aep->encoding = AUDIO_ENCODING_ALAW; + aep->precision = 8; + aep->flags = AUDIO_ENCODINGFLAG_EMULATED; + return (0); + case 3: + strlcpy(aep->name, AudioEslinear, sizeof aep->name); + aep->encoding = AUDIO_ENCODING_SLINEAR; + aep->precision = 8; + aep->flags = AUDIO_ENCODINGFLAG_EMULATED; + return (0); + case 4: + strlcpy(aep->name, AudioEslinear_le, sizeof aep->name); + aep->encoding = AUDIO_ENCODING_SLINEAR_LE; + aep->precision = 16; + aep->flags = 0; + return (0); + case 5: + strlcpy(aep->name, AudioEulinear_le, sizeof aep->name); + aep->encoding = AUDIO_ENCODING_ULINEAR_LE; + aep->precision = 16; + aep->flags = AUDIO_ENCODINGFLAG_EMULATED; + return (0); + case 6: + strlcpy(aep->name, AudioEslinear_be, sizeof aep->name); + aep->encoding = AUDIO_ENCODING_SLINEAR_BE; + aep->precision = 16; + aep->flags = AUDIO_ENCODINGFLAG_EMULATED; + return (0); + case 7: + strlcpy(aep->name, AudioEulinear_be, sizeof aep->name); + aep->encoding = AUDIO_ENCODING_ULINEAR_BE; + aep->precision = 16; + aep->flags = AUDIO_ENCODINGFLAG_EMULATED; + return (0); + default: + return (EINVAL); + } +} + + +/* commit setting and program ATI IXP chip */ +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; + u_int32_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 */ +int +auixp_set_params(void *hdl, int setmode, int usemode, + struct audio_params *play, struct audio_params *rec) +{ + struct auixp_codec *co; + struct auixp_softc *sc; + int error; + + co = (struct auixp_codec *) hdl; + sc = co->sc; + if (setmode & AUMODE_PLAY) { + play->factor = 1; + play->sw_code = NULL; + switch(play->encoding) { + case AUDIO_ENCODING_ULAW: + switch (play->channels) { + case 1: + play->factor = 4; + play->sw_code = mulaw_to_slinear16_mts; + break; + case 2: + play->factor = 2; + play->sw_code = mulaw_to_slinear16; + break; + default: + return (EINVAL); + } + break; + case AUDIO_ENCODING_SLINEAR_LE: + switch (play->precision) { + case 8: + switch (play->channels) { + case 1: + play->factor = 4; + play->sw_code = linear8_to_linear16_mts; + break; + case 2: + play->factor = 2; + play->sw_code = linear8_to_linear16; + break; + default: + return (EINVAL); + } + break; + case 16: + switch (play->channels) { + case 1: + play->factor = 2; + play->sw_code = noswap_bytes_mts; + break; + case 2: + break; + default: + return (EINVAL); + } + break; + default: + return (EINVAL); + } + break; + case AUDIO_ENCODING_ULINEAR_LE: + switch (play->precision) { + case 8: + switch (play->channels) { + case 1: + play->factor = 4; + play->sw_code = ulinear8_to_linear16_mts; + break; + case 2: + play->factor = 2; + play->sw_code = ulinear8_to_linear16; + break; + default: + return (EINVAL); + } + break; + case 16: + switch (play->channels) { + case 1: + play->factor = 2; + play->sw_code = change_sign16_mts; + break; + case 2: + play->sw_code = change_sign16; + break; + default: + return (EINVAL); + } + break; + default: + return (EINVAL); + } + break; + case AUDIO_ENCODING_ALAW: + switch (play->channels) { + case 1: + play->factor = 4; + play->sw_code = alaw_to_slinear16_mts; + case 2: + play->factor = 2; + play->sw_code = alaw_to_slinear16; + default: + return (EINVAL); + } + break; + case AUDIO_ENCODING_SLINEAR_BE: + switch (play->precision) { + case 8: + switch (play->channels) { + case 1: + play->factor = 4; + play->sw_code = linear8_to_linear16_mts; + break; + case 2: + play->factor = 2; + play->sw_code = linear8_to_linear16; + break; + default: + return (EINVAL); + } + break; + case 16: + switch (play->channels) { + case 1: + play->factor = 2; + play->sw_code = swap_bytes_mts; + break; + case 2: + play->sw_code = swap_bytes; + break; + default: + return (EINVAL); + } + break; + default: + return (EINVAL); + } + break; + case AUDIO_ENCODING_ULINEAR_BE: + switch (play->precision) { + case 8: + switch (play->channels) { + case 1: + play->factor = 4; + play->sw_code = ulinear8_to_linear16_mts; + break; + case 2: + play->factor = 2; + play->sw_code = ulinear8_to_linear16; + break; + default: + return (EINVAL); + } + break; + case 16: + switch (play->channels) { + case 1: + play->factor = 2; + play->sw_code = change_sign16_swap_bytes_mts; + break; + case 2: + play->sw_code = change_sign16_swap_bytes; + break; + default: + return (EINVAL); + } + break; + default: + return (EINVAL); + } + break; + default: + return (EINVAL); + } + + error = ac97_set_rate(co->codec_if, play, AUMODE_PLAY); + if (error) + return (error); + } + + if (setmode & AUMODE_RECORD) { + rec->factor = 1; + rec->sw_code = 0; + switch(rec->encoding) { + case AUDIO_ENCODING_ULAW: + rec->sw_code = ulinear8_to_mulaw; + break; + case AUDIO_ENCODING_SLINEAR_LE: + if (rec->precision == 8) + rec->sw_code = change_sign8; + break; + case AUDIO_ENCODING_ULINEAR_LE: + if (rec->precision == 16) + rec->sw_code = change_sign16; + break; + case AUDIO_ENCODING_ALAW: + rec->sw_code = ulinear8_to_alaw; + break; + case AUDIO_ENCODING_SLINEAR_BE: + if (rec->precision == 16) + rec->sw_code = swap_bytes; + else + rec->sw_code = change_sign8; + break; + case AUDIO_ENCODING_ULINEAR_BE: + if (rec->precision == 16) + rec->sw_code = swap_bytes_change_sign16; + break; + default: + return (EINVAL); + } + + error = ac97_set_rate(co->codec_if, rec, AUMODE_RECORD); + if (error) + return (error); + } + + return (0); +} + + +/* called to translate a requested blocksize to a hw-possible one */ +int +auixp_round_blocksize(void *v, int blk) +{ + + blk = (blk + 0x1f) & ~0x1f; + /* Be conservative; align to 32 bytes and maximise it to 64 kb */ + if (blk > 0x10000) + blk = 0x10000; + + return blk; +} + + +/* + * 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. + */ +void * +auixp_malloc(void *hdl, int direction, size_t size, int pool, 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), pool, flags); + if (!dma) + return NULL; + + /* get us a dma buffer itself */ + error = auixp_allocmem(sc, size, 16, dma); + if (error) { + free(dma, pool); + 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), (u_int32_t) DMAADDR(dma), dma->size, + dma->nsegs) + ); + + return KERNADDR(dma); +} + +/* + * free and release dma capable memory we allocated before and remove its + * recording + */ +void +auixp_free(void *hdl, void *addr, int pool) +{ + 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, pool); + return; + } + } +} + +int +auixp_getdev(void *v, struct audio_device *adp) +{ + struct auixp_softc *sc = v; + *adp = sc->sc_audev; + return 0; +} + +/* pass request to AC'97 codec code */ +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 */ +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 */ +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); +} + + +size_t +auixp_round_buffersize(void *hdl, int direction, size_t bufsize) +{ + + /* XXX force maximum? i.e. 256 kb? */ + return bufsize; +} + + +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. + */ +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; + u_int32_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; + } +} + + +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(*dma), M_DEVBUF, M_NOWAIT); + if (!dma) + return ENOMEM; + bzero(dma, sizeof(*dma)); + + /* 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); + 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 */ +void +auixp_program_dma_chain(struct auixp_softc *sc, struct auixp_dma *dma) +{ + bus_space_tag_t iot; + bus_space_handle_t ioh; + u_int32_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 */ +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? + */ +void +auixp_update_busbusy(struct auixp_softc *sc) +{ + bus_space_tag_t iot; + bus_space_handle_t ioh; + u_int32_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 */ +int +auixp_trigger_output(void *hdl, void *start, void *end, int blksize, + void (*intr)(void *), void *intrarg, struct audio_params *param) +{ + struct auixp_codec *co; + struct auixp_softc *sc; + struct auixp_dma *chain_dma; + struct auixp_dma *sound_dma; + u_int32_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 */ +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 */ +int +auixp_trigger_input(void *hdl, void *start, void *end, int blksize, + void (*intr)(void *), void *intrarg, struct audio_params *param) +{ + struct auixp_codec *co; + struct auixp_softc *sc; + struct auixp_dma *chain_dma; + struct auixp_dma *sound_dma; + u_int32_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 */ +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_output_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" + * + */ +int +auixp_intr(void *softc) +{ + struct auixp_softc *sc; + bus_space_tag_t iot; + bus_space_handle_t ioh; + u_int32_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 */ +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 */ +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 */ +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); +} + +int +auixp_match(struct device *dev, void *match, void *aux) +{ + return (pci_matchbyid((struct pci_attach_args *)aux, auixp_pci_devices, + sizeof(auixp_pci_devices)/sizeof(auixp_pci_devices[0]))); +} + +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 char *intrstr; + int len; + + sc = (struct auixp_softc *)self; + pa = (struct pci_attach_args *)aux; + tag = pa->pa_tag; + pc = pa->pa_pc; + + /* 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, 0)) { + printf(": can't map memory space\n"); + 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; + + /* 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; + +#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 + + if (pci_intr_map(pa, &ih)) { + printf(": can't map interrupt\n"); + return; + } + intrstr = pci_intr_string(pc, ih); + sc->sc_ih = pci_intr_establish(pc, ih, IPL_AUDIO, auixp_intr, sc, + sc->sc_dev.dv_xname); + if (sc->sc_ih == NULL) { + printf(": can't establish interrupt"); + if (intrstr != NULL) + printf(" at %s", intrstr); + printf("\n"); + return; + } + printf(": %s\n", intrstr); + + strlcpy(sc->sc_audev.name, "ATI IXP AC97", sizeof sc->sc_audev.name); + snprintf(sc->sc_audev.version, sizeof sc->sc_audev.version, "0x%02x", + PCI_REVISION(pa->pa_class)); + strlcpy(sc->sc_audev.config, sc->sc_dev.dv_xname, + sizeof sc->sc_audev.config); + + /* power up chip */ + auixp_power(sc, PCI_PMCSR_STATE_D0); + + /* init chip */ + if (auixp_init(sc) == -1) { + printf("%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(u_int16_t) * (ESA_REV_B_CODE_MEMORY_LENGTH + + ESA_REV_B_DATA_MEMORY_LENGTH + 1); + sc->savemem = (u_int16_t *)malloc(len, M_DEVBUF, M_NOWAIT | M_ZERO); + if (sc->savemem == NULL) { + printf("%s: unable to allocate suspend buffer\n", + sc->sc_dev.dv_xname); + return; + } + + sc->powerhook = powerhook_establish(auixp_powerhook, sc); + if (sc->powerhook == NULL) + printf("%s: WARNING: unable to establish powerhook\n", + sc->sc_dev.dv_xname); + +#endif + + /* + * delay further configuration of codecs and audio after interrupts + * are enabled. + */ + mountroothook_establish(auixp_post_config, self); +} + +/* called from autoconfigure system when interrupts are enabled */ +void +auixp_post_config(void *self) +{ + struct auixp_softc *sc; + struct auixp_codec *codec; + int codec_nr; + + sc = (struct auixp_softc *)self; + /* detect the AC97 codecs */ + auixp_autodetect_codecs(sc); + +#if notyet + /* copy formats and invalidate entries not suitable for codec0 */ + 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); +#endif + + /* attach audio devices for all detected codecs */ + 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); + } + + /* done! now enable all interrupts we can service */ + auixp_enable_interrupts(sc); +} + +void +auixp_enable_interrupts(struct auixp_softc *sc) +{ + bus_space_tag_t iot; + bus_space_handle_t ioh; + u_int32_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); +} + +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 */ +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 + */ + + +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; +} + +int +auixp_read_codec(void *aux, u_int8_t reg, u_int16_t *result) +{ + struct auixp_codec *co; + struct auixp_softc *sc; + bus_space_tag_t iot; + bus_space_handle_t ioh; + u_int32_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; +} + +int +auixp_write_codec(void *aux, u_int8_t reg, u_int16_t data) +{ + struct auixp_codec *co; + struct auixp_softc *sc; + bus_space_tag_t iot; + bus_space_handle_t ioh; + u_int32_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 = (((u_int32_t) data) << ATI_REG_PHYS_OUT_DATA_SHIFT) | + (((u_int32_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; +} + +void +auixp_reset_codec(void *aux) +{ + + /* nothing to be done? */ +} + +enum ac97_host_flags +auixp_flags_codec(void *aux) +{ + struct auixp_codec *ixp_codec; + + ixp_codec = aux; + return ixp_codec->codec_flags; +} + +int +auixp_wait_for_codecs(struct auixp_softc *sc, const char *func) +{ + bus_space_tag_t iot; + bus_space_handle_t ioh; + u_int32_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; +} + +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) == 0) + sc->sc_num_codecs++; + } + +#ifdef notyet + 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++; + } +#endif + + if (sc->sc_num_codecs == 0) { + printf("%s: no codecs detected or initialised\n", + sc->sc_dev.dv_xname); + return; + } +} + +void +auixp_disable_dma(struct auixp_softc *sc, struct auixp_dma *dma) +{ + bus_space_tag_t iot; + bus_space_handle_t ioh; + u_int32_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); + } +} + +void +auixp_enable_dma(struct auixp_softc *sc, struct auixp_dma *dma) +{ + bus_space_tag_t iot; + bus_space_handle_t ioh; + u_int32_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); + } +} + +void +auixp_reset_aclink(struct auixp_softc *sc) +{ + bus_space_tag_t iot; + bus_space_handle_t ioh; + u_int32_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 */ +int +auixp_init(struct auixp_softc *sc) +{ + bus_space_tag_t iot; + bus_space_handle_t ioh; + u_int32_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 + */ +int +auixp_power(struct auixp_softc *sc, int state) +{ + pcitag_t tag; + pci_chipset_tag_t pc; + pcireg_t data; + int pmcapreg; + + tag = sc->sc_tag; + pc = sc->sc_pct; + if (pci_get_capability(pc, tag, PCI_CAP_PWRMGMT, &pmcapreg, 0)) { + data = pci_conf_read(pc, tag, pmcapreg + PCI_PMCSR); + if ((data & PCI_PMCSR_STATE_MASK) != state) + pci_conf_write(pc, tag, pmcapreg + PCI_PMCSR, state); + } + + return 0; +} + +#if 0 +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; + } +} + +int +auixp_suspend(struct auixp_softc *sc) +{ + + /* XXX no power functions yet XXX */ + return 0; +} + +int +auixp_resume(struct auixp_softc *sc) +{ + + /* XXX no power functions yet XXX */ + return 0; +} +#endif /* 0 */ |