diff options
author | Mike Belopuhov <mikeb@cvs.openbsd.org> | 2008-08-28 10:21:24 +0000 |
---|---|---|
committer | Mike Belopuhov <mikeb@cvs.openbsd.org> | 2008-08-28 10:21:24 +0000 |
commit | 8c5fa0fe20f13caf23bc92c87baebface4d8ecf7 (patch) | |
tree | 9bc5972eaa80393aab04187148fe57bec04bad1d /sys/dev | |
parent | 6a0eafbaaf688b834fbaeda56bc4bcb1d0c64d16 (diff) |
Driver for Acer Labs M5455 integrated sound found on recent Ultra workstations.
Suggestions and corrections by kettenis, ratchov, jakemsr. Thanks a lot!
Recording doesn't work at the moment.
ok kettenis ratchov
Diffstat (limited to 'sys/dev')
-rw-r--r-- | sys/dev/pci/auacer.c | 1112 | ||||
-rw-r--r-- | sys/dev/pci/auacerreg.h | 155 | ||||
-rw-r--r-- | sys/dev/pci/files.pci | 7 |
3 files changed, 1273 insertions, 1 deletions
diff --git a/sys/dev/pci/auacer.c b/sys/dev/pci/auacer.c new file mode 100644 index 00000000000..b48cf9171b9 --- /dev/null +++ b/sys/dev/pci/auacer.c @@ -0,0 +1,1112 @@ +/* $OpenBSD: auacer.c,v 1.1 2008/08/28 10:21:23 mikeb Exp $ */ +/* $NetBSD: auacer.c,v 1.3 2004/11/10 04:20:26 kent Exp $ */ + +/*- + * Copyright (c) 2004 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson. + * + * 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. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``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 FOUNDATION OR CONTRIBUTORS + * 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. + */ + +/* + * Acer Labs M5455 audio driver + * + * Acer provides data sheets after signing an NDA. + * The chip behaves somewhat like the Intel i8x0, so this driver + * is loosely based on the auich driver. Additional information taken from + * the ALSA intel8x0.c driver (which handles M5455 as well). + */ + + +#include <sys/cdefs.h> + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/kernel.h> +#include <sys/malloc.h> +#include <sys/device.h> +#include <sys/fcntl.h> +#include <sys/proc.h> + +#include <dev/pci/pcidevs.h> +#include <dev/pci/pcivar.h> +#include <dev/pci/auacerreg.h> + +#include <sys/audioio.h> +#include <dev/audio_if.h> +#include <dev/mulaw.h> +#include <dev/auconv.h> + +#include <machine/bus.h> + +#include <dev/ic/ac97.h> + +struct auacer_dma { + bus_dmamap_t map; + caddr_t addr; + bus_dma_segment_t segs[1]; + int nsegs; + size_t size; + struct auacer_dma *next; +}; + +#define DMAADDR(p) ((p)->map->dm_segs[0].ds_addr) +#define KERNADDR(p) ((void *)((p)->addr)) + +struct auacer_cdata { + struct auacer_dmalist ic_dmalist_pcmo[ALI_DMALIST_MAX]; +}; + +struct auacer_chan { + uint32_t ptr; + uint32_t start, p, end; + uint32_t blksize, fifoe; + uint32_t ack; + uint32_t port; + struct auacer_dmalist *dmalist; + void (*intr)(void *); + void *arg; +}; + +struct auacer_softc { + struct device sc_dev; + void *sc_ih; + + audio_device_t sc_audev; + + bus_space_tag_t iot; + bus_space_handle_t mix_ioh; + bus_space_handle_t aud_ioh; + bus_dma_tag_t dmat; + + struct ac97_codec_if *codec_if; + struct ac97_host_if host_if; + + /* DMA scatter-gather lists. */ + bus_dmamap_t sc_cddmamap; +#define sc_cddma sc_cddmamap->dm_segs[0].ds_addr + + struct auacer_cdata *sc_cdata; + + struct auacer_chan sc_pcmo; + + struct auacer_dma *sc_dmas; + + pci_chipset_tag_t sc_pc; + pcitag_t sc_pt; + + int sc_dmamap_flags; + + /* Power Management */ + void *sc_powerhook; + int sc_suspend; +}; + +#define READ1(sc, a) bus_space_read_1(sc->iot, sc->aud_ioh, a) +#define READ2(sc, a) bus_space_read_2(sc->iot, sc->aud_ioh, a) +#define READ4(sc, a) bus_space_read_4(sc->iot, sc->aud_ioh, a) +#define WRITE1(sc, a, v) bus_space_write_1(sc->iot, sc->aud_ioh, a, v) +#define WRITE2(sc, a, v) bus_space_write_2(sc->iot, sc->aud_ioh, a, v) +#define WRITE4(sc, a, v) bus_space_write_4(sc->iot, sc->aud_ioh, a, v) + +/* Debug */ +#ifdef AUACER_DEBUG +#define DPRINTF(l,x) do { if (auacer_debug & (l)) printf x; } while(0) +int auacer_debug = 0; +#define ALI_DEBUG_CODECIO 0x0001 +#define ALI_DEBUG_DMA 0x0002 +#define ALI_DEBUG_INTR 0x0004 +#define ALI_DEBUG_API 0x0008 +#define ALI_DEBUG_MIXERAPI 0x0010 +#else +#define DPRINTF(x,y) /* nothing */ +#endif + +struct cfdriver auacer_cd = { + NULL, "auacer", DV_DULL +}; + +int auacer_match(struct device *, void *, void *); +void auacer_attach(struct device *, struct device *, void *); +int auacer_intr(void *); + +struct cfattach auacer_ca = { + sizeof(struct auacer_softc), auacer_match, auacer_attach +}; + +int auacer_open(void *, int); +void auacer_close(void *); +int auacer_query_encoding(void *, struct audio_encoding *); +int auacer_set_params(void *, int, int, struct audio_params *, + struct audio_params *); +int auacer_round_blocksize(void *, int); +int auacer_halt_output(void *); +int auacer_halt_input(void *); +int auacer_getdev(void *, struct audio_device *); +int auacer_set_port(void *, mixer_ctrl_t *); +int auacer_get_port(void *, mixer_ctrl_t *); +int auacer_query_devinfo(void *, mixer_devinfo_t *); +void *auacer_allocm(void *, int, size_t, int, int); +void auacer_freem(void *, void *, int); +size_t auacer_round_buffersize(void *, int, size_t); +paddr_t auacer_mappage(void *, void *, off_t, int); +int auacer_get_props(void *); +int auacer_trigger_output(void *, void *, void *, int, void (*)(void *), + void *, struct audio_params *); +int auacer_trigger_input(void *, void *, void *, int, void (*)(void *), + void *, struct audio_params *); +void auacer_get_default_params(void *, int, struct audio_params *); + +int auacer_alloc_cdata(struct auacer_softc *); + +int auacer_allocmem(struct auacer_softc *, size_t, size_t, + struct auacer_dma *); +int auacer_freemem(struct auacer_softc *, struct auacer_dma *); + +void auacer_powerhook(int, void *); +int auacer_set_rate(struct auacer_softc *, int, u_long); +void auacer_finish_attach(struct device *); + +static void auacer_reset(struct auacer_softc *sc); + +struct audio_hw_if auacer_hw_if = { + auacer_open, + auacer_close, + NULL, /* drain */ + auacer_query_encoding, + auacer_set_params, + auacer_round_blocksize, + NULL, /* commit_setting */ + NULL, /* init_output */ + NULL, /* init_input */ + NULL, /* start_output */ + NULL, /* start_input */ + auacer_halt_output, + auacer_halt_input, + NULL, /* speaker_ctl */ + auacer_getdev, + NULL, /* getfd */ + auacer_set_port, + auacer_get_port, + auacer_query_devinfo, + auacer_allocm, + auacer_freem, + auacer_round_buffersize, + auacer_mappage, + auacer_get_props, + auacer_trigger_output, + auacer_trigger_input, + auacer_get_default_params +}; + +int auacer_attach_codec(void *, struct ac97_codec_if *); +int auacer_read_codec(void *, u_int8_t, u_int16_t *); +int auacer_write_codec(void *, u_int8_t, u_int16_t); +void auacer_reset_codec(void *); + +int +auacer_match(struct device *parent, void *match, void *aux) +{ + struct pci_attach_args *pa = (struct pci_attach_args *)aux; + + if (PCI_VENDOR(pa->pa_id) == PCI_VENDOR_ALI && + PCI_PRODUCT(pa->pa_id) == PCI_PRODUCT_ALI_M5455) + return 1; + return 0; +} + +void +auacer_attach(struct device *parent, struct device *self, void *aux) +{ + struct auacer_softc *sc = (struct auacer_softc *)self; + struct pci_attach_args *pa = aux; + pci_intr_handle_t ih; + bus_size_t aud_size; + pcireg_t v; + const char *intrstr; + + if (pci_mapreg_map(pa, 0x10, PCI_MAPREG_TYPE_IO, 0, &sc->iot, + &sc->aud_ioh, NULL, &aud_size, 0)) { + printf(": can't map i/o space\n"); + return; + } + + sc->sc_pc = pa->pa_pc; + sc->sc_pt = pa->pa_tag; + sc->dmat = pa->pa_dmat; + + sc->sc_dmamap_flags = BUS_DMA_COHERENT; /* XXX remove */ + + /* enable bus mastering */ + v = pci_conf_read(pa->pa_pc, pa->pa_tag, PCI_COMMAND_STATUS_REG); + pci_conf_write(pa->pa_pc, pa->pa_tag, PCI_COMMAND_STATUS_REG, + v | PCI_COMMAND_MASTER_ENABLE); + + /* Map and establish the interrupt. */ + if (pci_intr_map(pa, &ih)) { + printf("%s: can't map interrupt\n", sc->sc_dev.dv_xname); + return; + } + intrstr = pci_intr_string(pa->pa_pc, ih); + sc->sc_ih = pci_intr_establish(pa->pa_pc, ih, IPL_AUDIO, + auacer_intr, sc, sc->sc_dev.dv_xname); + if (sc->sc_ih == NULL) { + printf("%s: can't establish interrupt", + sc->sc_dev.dv_xname); + if (intrstr != NULL) + printf(" at %s", intrstr); + printf("\n"); + return; + } + + strlcpy(sc->sc_audev.name, "M5455 AC97", MAX_AUDIO_DEV_LEN); + snprintf(sc->sc_audev.version, MAX_AUDIO_DEV_LEN, + "0x%02x", PCI_REVISION(pa->pa_class)); + strlcpy(sc->sc_audev.config, sc->sc_dev.dv_xname, MAX_AUDIO_DEV_LEN); + + printf(": %s\n", intrstr); + + /* Set up DMA lists. */ + auacer_alloc_cdata(sc); + sc->sc_pcmo.dmalist = sc->sc_cdata->ic_dmalist_pcmo; + sc->sc_pcmo.ptr = 0; + sc->sc_pcmo.port = ALI_BASE_PO; + + DPRINTF(ALI_DEBUG_DMA, ("auacer_attach: lists %p\n", + sc->sc_pcmo.dmalist)); + + sc->host_if.arg = sc; + sc->host_if.attach = auacer_attach_codec; + sc->host_if.read = auacer_read_codec; + sc->host_if.write = auacer_write_codec; + sc->host_if.reset = auacer_reset_codec; + + if (ac97_attach(&sc->host_if) != 0) + return; + + /* Watch for power change */ + sc->sc_suspend = PWR_RESUME; + sc->sc_powerhook = powerhook_establish(auacer_powerhook, sc); + + audio_attach_mi(&auacer_hw_if, sc, &sc->sc_dev); + + auacer_reset(sc); +} + +static int +auacer_ready_codec(struct auacer_softc *sc, int mask) +{ + int count = 0; + + for (count = 0; count < 0x7f; count++) { + int val = READ1(sc, ALI_CSPSR); + if (val & mask) + return 0; + } + + printf("auacer_ready_codec: AC97 codec ready timeout.\n"); + return EBUSY; +} + +static int +auacer_sema_codec(struct auacer_softc *sc) +{ + int ttime = 100; + + while (ttime-- && (READ4(sc, ALI_CAS) & ALI_CAS_SEM_BUSY)) + delay(1); + if (!ttime) + printf("auacer_sema_codec: timeout\n"); + return auacer_ready_codec(sc, ALI_CSPSR_CODEC_READY); +} + +int +auacer_read_codec(void *v, u_int8_t reg, u_int16_t *val) +{ + struct auacer_softc *sc = v; + + if (auacer_sema_codec(sc)) + return EIO; + + reg |= ALI_CPR_ADDR_READ; +#if 0 + if (ac97->num) + reg |= ALI_CPR_ADDR_SECONDARY; +#endif + WRITE2(sc, ALI_CPR_ADDR, reg); + if (auacer_ready_codec(sc, ALI_CSPSR_READ_OK)) + return EIO; + *val = READ2(sc, ALI_SPR); + + DPRINTF(ALI_DEBUG_CODECIO, ("auacer_read_codec: reg=0x%x val=0x%x\n", + reg, *val)); + + return 0; +} + +int +auacer_write_codec(void *v, u_int8_t reg, u_int16_t val) +{ + struct auacer_softc *sc = v; + + DPRINTF(ALI_DEBUG_CODECIO, ("auacer_write_codec: reg=0x%x val=0x%x\n", + reg, val)); + + if (auacer_sema_codec(sc)) + return EIO; + WRITE2(sc, ALI_CPR, val); +#if 0 + if (ac97->num) + reg |= ALI_CPR_ADDR_SECONDARY; +#endif + WRITE2(sc, ALI_CPR_ADDR, reg); + auacer_ready_codec(sc, ALI_CSPSR_WRITE_OK); + return 0; +} + +int +auacer_attach_codec(void *v, struct ac97_codec_if *cif) +{ + struct auacer_softc *sc = v; + + sc->codec_if = cif; + return 0; +} + +void +auacer_reset_codec(void *v) +{ + struct auacer_softc *sc = v; + u_int32_t reg; + int i = 0; + + reg = READ4(sc, ALI_SCR); + if ((reg & 2) == 0) /* Cold required */ + reg |= 2; + else + reg |= 1; /* Warm */ + reg &= ~0x80000000; /* ACLink on */ + WRITE4(sc, ALI_SCR, reg); + + while (i < 10) { + if ((READ4(sc, ALI_INTERRUPTSR) & ALI_INT_GPIO) == 0) + break; + delay(50000); /* XXX */ + i++; + } + if (i == 10) { + return; + } + + for (i = 0; i < 10; i++) { + reg = READ4(sc, ALI_RTSR); + if (reg & 0x80) /* primary codec */ + break; + WRITE4(sc, ALI_RTSR, reg | 0x80); + delay(50000); /* XXX */ + } +} + +static void +auacer_reset(struct auacer_softc *sc) +{ + WRITE4(sc, ALI_SCR, ALI_SCR_RESET); + WRITE4(sc, ALI_FIFOCR1, 0x83838383); + WRITE4(sc, ALI_FIFOCR2, 0x83838383); + WRITE4(sc, ALI_FIFOCR3, 0x83838383); + WRITE4(sc, ALI_INTERFACECR, ALI_IF_PO); /* XXX pcm out only */ + WRITE4(sc, ALI_INTERRUPTCR, 0x00000000); + WRITE4(sc, ALI_INTERRUPTSR, 0x00000000); +} + +int +auacer_open(void *v, int flags) +{ + DPRINTF(ALI_DEBUG_API, ("auacer_open: flags=%d\n", flags)); + return 0; +} + +void +auacer_close(void *v) +{ + DPRINTF(ALI_DEBUG_API, ("auacer_close\n")); +} + +void +auacer_get_default_params(void *addr, int mode, struct audio_params *params) +{ + ac97_get_default_params(params); +} + +int +auacer_query_encoding(void *v, struct audio_encoding *aep) +{ + DPRINTF(ALI_DEBUG_API, ("auacer_query_encoding\n")); + + 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); + } +} + +int +auacer_set_rate(struct auacer_softc *sc, int mode, u_long srate) +{ + int ret; + u_long ratetmp; + + DPRINTF(ALI_DEBUG_API, ("auacer_set_rate: srate=%lu\n", srate)); + + ratetmp = srate; + if (mode == AUMODE_RECORD) + return sc->codec_if->vtbl->set_rate(sc->codec_if, + AC97_REG_PCM_LR_ADC_RATE, &ratetmp); + ret = sc->codec_if->vtbl->set_rate(sc->codec_if, + AC97_REG_PCM_FRONT_DAC_RATE, &ratetmp); + if (ret) + return ret; + ratetmp = srate; + ret = sc->codec_if->vtbl->set_rate(sc->codec_if, + AC97_REG_PCM_SURR_DAC_RATE, &ratetmp); + if (ret) + return ret; + ratetmp = srate; + ret = sc->codec_if->vtbl->set_rate(sc->codec_if, + AC97_REG_PCM_LFE_DAC_RATE, &ratetmp); + return ret; +} + +static int +auacer_fixup_rate(int rate) +{ + int i; + int rates[] = { + 8000, 11025, 12000, 16000, 22050, 32000, 44100, 48000 + }; + + for (i = 0; i < sizeof(rates)/sizeof(rates[0]) - 1; i++) + if (rate <= (rates[i] + rates[i+1]) / 2) + return (rates[i]); + return (rates[i]); +} + +int +auacer_set_params(void *v, int setmode, int usemode, struct audio_params *play, + struct audio_params *rec) +{ + struct auacer_softc *sc = v; + struct audio_params *p; + uint32_t control; + int mode; + + DPRINTF(ALI_DEBUG_API, ("auacer_set_params\n")); + + for (mode = AUMODE_RECORD; mode != -1; + mode = mode == AUMODE_RECORD ? AUMODE_PLAY : -1) { + if ((setmode & mode) == 0) + continue; + + p = mode == AUMODE_PLAY ? play : rec; + if (p == NULL) + continue; + + p->sample_rate = auacer_fixup_rate(p->sample_rate); + p->factor = 1; + if (p->precision == 8) + p->factor *= 2; + p->sw_code = NULL; + + if (mode == AUMODE_RECORD) { + if (p->channels < 1 || p->channels > 2) + return EINVAL; + } + + switch (p->encoding) { + case AUDIO_ENCODING_SLINEAR_BE: + if (p->precision == 16) { + p->sw_code = swap_bytes; + } else { + if (mode == AUMODE_PLAY) + p->sw_code = linear8_to_linear16_le; + else + p->sw_code = linear16_to_linear8_le; + } + break; + + case AUDIO_ENCODING_SLINEAR_LE: + if (p->precision != 16) { + if (mode == AUMODE_PLAY) + p->sw_code = linear8_to_linear16_le; + else + p->sw_code = linear16_to_linear8_le; + } + break; + + case AUDIO_ENCODING_ULINEAR_BE: + if (p->precision == 16) { + if (mode == AUMODE_PLAY) + p->sw_code = + swap_bytes_change_sign16_le; + else + p->sw_code = + change_sign16_swap_bytes_le; + } else { + if (mode == AUMODE_PLAY) + p->sw_code = ulinear8_to_linear16_le; + else + p->sw_code = linear16_to_ulinear8_le; + } + break; + + case AUDIO_ENCODING_ULINEAR_LE: + if (p->precision == 16) { + p->sw_code = change_sign16_le; + } else { + if (mode == AUMODE_PLAY) + p->sw_code = ulinear8_to_linear16_le; + else + p->sw_code = linear16_to_ulinear8_le; + } + break; + + case AUDIO_ENCODING_ULAW: + if (mode == AUMODE_PLAY) + p->sw_code = mulaw_to_slinear16_le; + else + p->sw_code = slinear16_to_mulaw_le; + break; + + case AUDIO_ENCODING_ALAW: + if (mode == AUMODE_PLAY) + p->sw_code = alaw_to_slinear16_le; + else + p->sw_code = slinear16_to_alaw_le; + break; + + default: + return (EINVAL); + } + + if (AC97_IS_FIXED_RATE(sc->codec_if)) + p->sample_rate = AC97_SINGLE_RATE; + else if (auacer_set_rate(sc, mode, p->sample_rate)) + return EINVAL; + + if (mode == AUMODE_PLAY) { + control = READ4(sc, ALI_SCR); + control &= ~ALI_SCR_PCM_246_MASK; + if (p->channels == 4) + control |= ALI_SCR_PCM_4; + else if (p->channels == 6) + control |= ALI_SCR_PCM_6; + WRITE4(sc, ALI_SCR, control); + } + } + + return (0); +} + +int +auacer_round_blocksize(void *v, int blk) +{ + return (blk & ~0x3f); /* keep good alignment */ +} + +static void +auacer_halt(struct auacer_softc *sc, struct auacer_chan *chan) +{ + uint32_t val; + uint8_t port = chan->port; + uint32_t slot; + + DPRINTF(ALI_DEBUG_API, ("auacer_halt: port=0x%x\n", port)); + + chan->intr = 0; + + slot = ALI_PORT2SLOT(port); + + val = READ4(sc, ALI_DMACR); + val |= 1 << (slot+16); /* pause */ + val &= ~(1 << slot); /* no start */ + WRITE4(sc, ALI_DMACR, val); + WRITE1(sc, port + ALI_OFF_CR, 0); + while (READ1(sc, port + ALI_OFF_CR)) + ; + /* reset whole DMA things */ + WRITE1(sc, port + ALI_OFF_CR, ALI_CR_RR); + /* clear interrupts */ + WRITE1(sc, port + ALI_OFF_SR, READ1(sc, port+ALI_OFF_SR) | ALI_SR_W1TC); + WRITE4(sc, ALI_INTERRUPTSR, ALI_PORT2INTR(port)); +} + +int +auacer_halt_output(void *v) +{ + struct auacer_softc *sc = v; + + DPRINTF(ALI_DEBUG_DMA, ("auacer_halt_output\n")); + auacer_halt(sc, &sc->sc_pcmo); + return (0); +} + +int +auacer_halt_input(void *v) +{ + DPRINTF(ALI_DEBUG_DMA, ("auacer_halt_input\n")); + return (0); +} + +int +auacer_getdev(void *v, struct audio_device *adp) +{ + struct auacer_softc *sc = v; + + DPRINTF(ALI_DEBUG_API, ("auacer_getdev\n")); + *adp = sc->sc_audev; + return (0); +} + +int +auacer_set_port(void *v, mixer_ctrl_t *cp) +{ + struct auacer_softc *sc = v; + + DPRINTF(ALI_DEBUG_MIXERAPI, ("auacer_set_port\n")); + return (sc->codec_if->vtbl->mixer_set_port(sc->codec_if, cp)); +} + +int +auacer_get_port(void *v, mixer_ctrl_t *cp) +{ + struct auacer_softc *sc = v; + + DPRINTF(ALI_DEBUG_MIXERAPI, ("auacer_get_port\n")); + return (sc->codec_if->vtbl->mixer_get_port(sc->codec_if, cp)); +} + +int +auacer_query_devinfo(void *v, mixer_devinfo_t *dp) +{ + struct auacer_softc *sc = v; + + DPRINTF(ALI_DEBUG_MIXERAPI, ("auacer_query_devinfo\n")); + return (sc->codec_if->vtbl->query_devinfo(sc->codec_if, dp)); +} + +void * +auacer_allocm(void *v, int direction, size_t size, int pool, int flags) +{ + struct auacer_softc *sc = v; + struct auacer_dma *p; + int error; + + if (size > (ALI_DMALIST_MAX * ALI_DMASEG_MAX)) + return (NULL); + + p = malloc(sizeof(*p), pool, flags | M_ZERO); + if (p == NULL) + return (NULL); + + error = auacer_allocmem(sc, size, PAGE_SIZE, p); + if (error) { + free(p, pool); + return (NULL); + } + + p->next = sc->sc_dmas; + sc->sc_dmas = p; + + return (KERNADDR(p)); +} + +void +auacer_freem(void *v, void *ptr, int pool) +{ + struct auacer_softc *sc = v; + struct auacer_dma *p, **pp; + + for (pp = &sc->sc_dmas; (p = *pp) != NULL; pp = &p->next) { + if (KERNADDR(p) == ptr) { + auacer_freemem(sc, p); + *pp = p->next; + free(p, pool); + return; + } + } +} + +size_t +auacer_round_buffersize(void *v, int direction, size_t size) +{ + + if (size > (ALI_DMALIST_MAX * ALI_DMASEG_MAX)) + size = ALI_DMALIST_MAX * ALI_DMASEG_MAX; + + return size; +} + +paddr_t +auacer_mappage(void *v, void *mem, off_t off, int prot) +{ + struct auacer_softc *sc = v; + struct auacer_dma *p; + + if (off < 0) + return (-1); + + for (p = sc->sc_dmas; p && KERNADDR(p) != mem; p = p->next) + ; + if (!p) + return (-1); + return (bus_dmamem_mmap(sc->dmat, p->segs, p->nsegs, + off, prot, BUS_DMA_WAITOK)); +} + +int +auacer_get_props(void *v) +{ + return (AUDIO_PROP_INDEPENDENT | AUDIO_PROP_FULLDUPLEX | + AUDIO_PROP_MMAP); +} + +static void +auacer_add_entry(struct auacer_chan *chan) +{ + struct auacer_dmalist *q; + + q = &chan->dmalist[chan->ptr]; + + DPRINTF(ALI_DEBUG_INTR, + ("auacer_add_entry: %p = %x @ 0x%x\n", + q, chan->blksize / 2, chan->p)); + + q->base = htole32(chan->p); + q->len = htole32((chan->blksize / ALI_SAMPLE_SIZE) | ALI_DMAF_IOC); + chan->p += chan->blksize; + if (chan->p >= chan->end) + chan->p = chan->start; + + if (++chan->ptr >= ALI_DMALIST_MAX) + chan->ptr = 0; +} + +static void +auacer_upd_chan(struct auacer_softc *sc, struct auacer_chan *chan) +{ + uint32_t sts; + uint32_t civ; + + sts = READ2(sc, chan->port + ALI_OFF_SR); + /* intr ack */ + WRITE2(sc, chan->port + ALI_OFF_SR, sts & ALI_SR_W1TC); + WRITE4(sc, ALI_INTERRUPTSR, ALI_PORT2INTR(chan->port)); + + DPRINTF(ALI_DEBUG_INTR, ("auacer_upd_chan: sts=0x%x\n", sts)); + + if (sts & ALI_SR_DMA_INT_FIFO) { + printf("%s: fifo underrun # %u\n", + sc->sc_dev.dv_xname, ++chan->fifoe); + } + + civ = READ1(sc, chan->port + ALI_OFF_CIV); + + DPRINTF(ALI_DEBUG_INTR,("auacer_intr: civ=%u ptr=%u\n",civ,chan->ptr)); + + /* XXX */ + while (chan->ptr != civ) { + auacer_add_entry(chan); + } + + WRITE1(sc, chan->port + ALI_OFF_LVI, (chan->ptr - 1) & ALI_LVI_MASK); + + while (chan->ack != civ) { + if (chan->intr) { + DPRINTF(ALI_DEBUG_INTR,("auacer_upd_chan: callback\n")); + chan->intr(chan->arg); + } + chan->ack++; + if (chan->ack >= ALI_DMALIST_MAX) + chan->ack = 0; + } +} + +int +auacer_intr(void *v) +{ + struct auacer_softc *sc = v; + int ret, intrs; + + intrs = READ4(sc, ALI_INTERRUPTSR); + DPRINTF(ALI_DEBUG_INTR, ("auacer_intr: intrs=0x%x\n", intrs)); + + ret = 0; + if (intrs & ALI_INT_PCMOUT) { + auacer_upd_chan(sc, &sc->sc_pcmo); + ret++; + } + + return ret != 0; +} + +static void +auacer_setup_chan(struct auacer_softc *sc, struct auacer_chan *chan, + uint32_t start, uint32_t size, uint32_t blksize, void (*intr)(void *), + void *arg) +{ + uint32_t port, slot; + uint32_t offs, val; + + chan->start = start; + chan->ptr = 0; + chan->p = chan->start; + chan->end = chan->start + size; + chan->blksize = blksize; + chan->ack = 0; + chan->intr = intr; + chan->arg = arg; + + auacer_add_entry(chan); + auacer_add_entry(chan); + + port = chan->port; + slot = ALI_PORT2SLOT(port); + + WRITE1(sc, port + ALI_OFF_CIV, 0); + WRITE1(sc, port + ALI_OFF_LVI, (chan->ptr - 1) & ALI_LVI_MASK); + offs = (char *)chan->dmalist - (char *)sc->sc_cdata; + WRITE4(sc, port + ALI_OFF_BDBAR, sc->sc_cddma + offs); + WRITE1(sc, port + ALI_OFF_CR, + ALI_CR_IOCE | ALI_CR_FEIE | ALI_CR_LVBIE | ALI_CR_RPBM); + val = READ4(sc, ALI_DMACR); + val &= ~(1 << (slot+16)); /* no pause */ + val |= 1 << slot; /* start */ + WRITE4(sc, ALI_DMACR, val); +} + +int +auacer_trigger_output(void *v, void *start, void *end, int blksize, + void (*intr)(void *), void *arg, struct audio_params *param) +{ + struct auacer_softc *sc = v; + struct auacer_dma *p; + uint32_t size; + + DPRINTF(ALI_DEBUG_DMA, + ("auacer_trigger_output(%p, %p, %d, %p, %p, %p)\n", + start, end, blksize, intr, arg, param)); + + for (p = sc->sc_dmas; p && KERNADDR(p) != start; p = p->next) + ; + if (!p) { + printf("auacer_trigger_output: bad addr %p\n", start); + return (EINVAL); + } + + size = (char *)end - (char *)start; + auacer_setup_chan(sc, &sc->sc_pcmo, DMAADDR(p), size, blksize, + intr, arg); + + return 0; +} + +int +auacer_trigger_input(void *v, void *start, void *end, int blksize, + void (*intr)(void *), void *arg, struct audio_params *param) +{ + return (EINVAL); +} + +int +auacer_allocmem(struct auacer_softc *sc, size_t size, size_t align, + struct auacer_dma *p) +{ + int error; + + p->size = size; + error = bus_dmamem_alloc(sc->dmat, p->size, align, 0, p->segs, + sizeof(p->segs)/sizeof(p->segs[0]), &p->nsegs, BUS_DMA_NOWAIT); + if (error) + return (error); + + error = bus_dmamem_map(sc->dmat, p->segs, p->nsegs, p->size, &p->addr, + BUS_DMA_NOWAIT | sc->sc_dmamap_flags); + if (error) + goto free; + + error = bus_dmamap_create(sc->dmat, p->size, 1, p->size, 0, + BUS_DMA_NOWAIT, &p->map); + if (error) + goto unmap; + + error = bus_dmamap_load(sc->dmat, p->map, p->addr, p->size, NULL, + BUS_DMA_NOWAIT); + if (error) + goto destroy; + return (0); + + destroy: + bus_dmamap_destroy(sc->dmat, p->map); + unmap: + bus_dmamem_unmap(sc->dmat, p->addr, p->size); + free: + bus_dmamem_free(sc->dmat, p->segs, p->nsegs); + return (error); +} + +int +auacer_freemem(struct auacer_softc *sc, struct auacer_dma *p) +{ + + bus_dmamap_unload(sc->dmat, p->map); + bus_dmamap_destroy(sc->dmat, p->map); + bus_dmamem_unmap(sc->dmat, p->addr, p->size); + bus_dmamem_free(sc->dmat, p->segs, p->nsegs); + return (0); +} + +int +auacer_alloc_cdata(struct auacer_softc *sc) +{ + bus_dma_segment_t seg; + int error, rseg; + + /* + * Allocate the control data structure, and create and load the + * DMA map for it. + */ + if ((error = bus_dmamem_alloc(sc->dmat, sizeof(struct auacer_cdata), + PAGE_SIZE, 0, &seg, 1, &rseg, 0)) != 0) { + printf("%s: unable to allocate control data, error = %d\n", + sc->sc_dev.dv_xname, error); + goto fail_0; + } + + if ((error = bus_dmamem_map(sc->dmat, &seg, rseg, + sizeof(struct auacer_cdata), (caddr_t *) &sc->sc_cdata, + sc->sc_dmamap_flags)) != 0) { + printf("%s: unable to map control data, error = %d\n", + sc->sc_dev.dv_xname, error); + goto fail_1; + } + + if ((error = bus_dmamap_create(sc->dmat, sizeof(struct auacer_cdata), 1, + sizeof(struct auacer_cdata), 0, 0, &sc->sc_cddmamap)) != 0) { + printf("%s: unable to create control data DMA map, " + "error = %d\n", sc->sc_dev.dv_xname, error); + goto fail_2; + } + + if ((error = bus_dmamap_load(sc->dmat, sc->sc_cddmamap, sc->sc_cdata, + sizeof(struct auacer_cdata), NULL, 0)) != 0) { + printf("%s: unable to load control data DMA map, error = %d\n", + sc->sc_dev.dv_xname, error); + goto fail_3; + } + + return (0); + + fail_3: + bus_dmamap_destroy(sc->dmat, sc->sc_cddmamap); + fail_2: + bus_dmamem_unmap(sc->dmat, (caddr_t) sc->sc_cdata, + sizeof(struct auacer_cdata)); + fail_1: + bus_dmamem_free(sc->dmat, &seg, rseg); + fail_0: + return (error); +} + +void +auacer_powerhook(int why, void *addr) +{ + struct auacer_softc *sc = (struct auacer_softc *)addr; + + if (why != PWR_RESUME) { + /* Power down */ + DPRINTF(1, ("%s: power down\n", sc->sc_dev.dv_xname)); + sc->sc_suspend = why; + } else { + /* Wake up */ + DPRINTF(1, ("%s: power resume\n", sc->sc_dev.dv_xname)); + if (sc->sc_suspend == PWR_RESUME) { + printf("%s: resume without suspend.\n", + sc->sc_dev.dv_xname); + sc->sc_suspend = why; + return; + } + sc->sc_suspend = why; + auacer_reset_codec(sc); + delay(1000); + (sc->codec_if->vtbl->restore_ports)(sc->codec_if); + } +} diff --git a/sys/dev/pci/auacerreg.h b/sys/dev/pci/auacerreg.h new file mode 100644 index 00000000000..ade61b18e46 --- /dev/null +++ b/sys/dev/pci/auacerreg.h @@ -0,0 +1,155 @@ +/* $OpenBSD: auacerreg.h,v 1.1 2008/08/28 10:21:23 mikeb Exp $ */ +/* $NetBSD: auacer.h,v 1.1 2004/10/10 16:37:07 augustss Exp $ */ + +/*- + * Copyright (c) 2004 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson. + * + * 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. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``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 FOUNDATION OR CONTRIBUTORS + * 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. + */ + +#ifndef _DEV_PCI_AUACERREG_H_ +#define _DEV_PCI_AUACERREG_H_ + +#define ALI_SCR 0x00 /* System Control Register */ +#define ALI_SCR_RESET (1<<31) /* master reset */ +#define ALI_SCR_AC97_DBL (1<<30) +#define ALI_SCR_CODEC_SPDF (3<<20) /* 1=7/8, 2=6/9, 3=10/11 */ +#define ALI_SCR_IN_BITS (3<<18) +#define ALI_SCR_OUT_BITS (3<<16) +#define ALI_SCR_6CH_CFG (3<<14) +#define ALI_SCR_PCM_4 (1<<8) +#define ALI_SCR_PCM_6 (2<<8) +#define ALI_SCR_PCM_246_MASK (ALI_SCR_PCM_4 | ALI_SCR_PCM_6) +#define ALI_SSR 0x04 /* System Status Register */ +#define ALI_SSR_SEC_ID (3<<5) +#define ALI_SSR_PRI_ID (3<<3) +#define ALI_DMACR 0x08 /* DMA Control Register */ +#define ALI_DMACR_PAUSE 16 /* offset for pause bits */ +#define ALI_FIFOCR1 0x0c /* FIFO Control Register 1 */ +#define ALI_INTERFACECR 0x10 /* Interface Control Register */ +#define ALI_INTERRUPTCR 0x14 /* Interrupt Control Register */ +#define ALI_INTERRUPTSR 0x18 /* Interrupt Status Register */ +#define ALI_INT_MICIN2 (1<<26) +#define ALI_INT_PCMIN2 (1<<25) +#define ALI_INT_I2SIN (1<<24) +#define ALI_INT_SPDIFOUT (1<<23) +#define ALI_INT_SPDIFIN (1<<22) +#define ALI_INT_LFEOUT (1<<21) +#define ALI_INT_CENTEROUT (1<<20) +#define ALI_INT_CODECSPDIFOUT (1<<19) +#define ALI_INT_MICIN (1<<18) +#define ALI_INT_PCMOUT (1<<17) +#define ALI_INT_PCMIN (1<<16) +#define ALI_INT_CPRAIS (1<<7) +#define ALI_INT_SPRAIS (1<<5) +#define ALI_INT_GPIO (1<<1) +#define ALI_FIFOCR2 0x1c /* FIFO Control Register 2 */ +#define ALI_CPR 0x20 /* Command Port Register */ +#define ALI_CPR_ADDR_SECONDARY 0x100 +#define ALI_CPR_ADDR_READ 0x80 +#define ALI_CPR_ADDR 0x22 /* AC97 write addr */ +#define ALI_SPR 0x24 /* Status Port Register */ +#define ALI_SPR_ADDR 0x26 /* AC97 read addr */ +#define ALI_FIFOCR3 0x2c /* FIFO Control Register 3 */ +#define ALI_TTSR 0x30 /* Transmit Tag Slot Register */ +#define ALI_RTSR 0x34 /* Receive Tag Slot Register */ +#define ALI_CSPSR 0x38 /* Command/Status Port Status Register */ +#define ALI_CSPSR_CODEC_READY 0x08 +#define ALI_CSPSR_READ_OK 0x02 +#define ALI_CSPSR_WRITE_OK 0x01 +#define ALI_CAS 0x3c /* Codec Write Semaphore Register */ +#define ALI_CAS_SEM_BUSY 0x80000000 +#define ALI_HWVOL 0xf0 /* hardware volume control/status */ +#define ALI_I2SCR 0xf4 /* I2S control/status */ +#define ALI_SPDIFCSR 0xf8 /* SPDIF Channel Status Register */ +#define ALI_SPDIFICS 0xfc /* SPDIF Interface Control/Status */ + + +#define ALI_OFF_BDBAR 0x00 /* Buffer Descriptor list Base Address */ +#define ALI_OFF_CIV 0x04 /* Current Index Value */ +#define ALI_OFF_LVI 0x05 /* Last Valid Index */ +#define ALI_LVI_MASK 0x1f +#define ALI_OFF_SR 0x06 /* Status Register */ +#define ALI_SR_DMA_INT_FIFO (1<<4) /* fifo under/over flow */ +#define ALI_SR_DMA_INT_COMPLETE (1<<3) /* buffer read/write complete and ioc set */ +#define ALI_SR_DMA_INT_LVI (1<<2) /* last valid done */ +#define ALI_SR_DMA_INT_CELV (1<<1) /* last valid is current */ +#define ALI_SR_DMA_INT_DCH (1<<0) /* DMA Controller Halted (happens on LVI interrupts) */ +#define ALI_SR_W1TC (ALI_SR_DMA_INT_LVI | ALI_SR_DMA_INT_COMPLETE | ALI_SR_DMA_INT_FIFO | ALI_SR_DMA_INT_CELV) +#define ALI_OFF_PICB 0x08 /* Position In Current Buffer */ +#define ALI_PIV 0x0a /* 5 bits prefetched index value */ +#define ALI_OFF_CR 0x0b /* Control Register */ +#define ALI_CR_IOCE 0x10 /* Int On Completion Enable */ +#define ALI_CR_FEIE 0x08 /* Fifo Error Int Enable */ +#define ALI_CR_LVBIE 0x04 /* Last Valid Buf Int Enable */ +#define ALI_CR_RR 0x02 /* 1 - Reset Regs */ +#define ALI_CR_RPBM 0x01 /* 1 - Run, 0 - Pause */ + +#define ALI_BASE_PI 0x40 /* PCM In */ +#define ALI_BASE_PO 0x50 /* PCM Out */ +#define ALI_BASE_MC 0x60 /* Mic In */ +#define ALI_BASE_CODEC_SPDIFO 0x70 /* Codec SPDIF Out */ +#define ALI_BASE_CENTER 0x80 /* Center out */ +#define ALI_BASE_LFE 0x90 /* ? */ +#define ALI_BASE_CTL_SPDIFI 0xa0 /* Controller SPDIF In */ +#define ALI_BASE_CTL_SPDIFO 0xb0 /* Controller SPDIF Out */ + +#define ALI_PORT2SLOT(port) (((port) - 0x40) / 0x10) +#define ALI_PORT2INTR(port) (ALI_PORT2SLOT(port) + 16) + +#define ALI_IF_AC97SP (1<<21) +#define ALI_IF_MC (1<<20) +#define ALI_IF_PI (1<<19) +#define ALI_IF_MC2 (1<<18) +#define ALI_IF_PI2 (1<<17) +#define ALI_IF_LINE_SRC (1<<15) /* 0/1 = slot 3/6 */ +#define ALI_IF_MIC_SRC (1<<14) /* 0/1 = slot 3/6 */ +#define ALI_IF_SPDF_SRC (3<<12) /* 00 = PCM, 01 = AC97-in, 10 = spdif-in, 11 = i2s */ +#define ALI_IF_AC97_OUT (3<<8) /* 00 = PCM, 10 = spdif-in, 11 = i2s */ +#define ALI_IF_PO_SPDF (1<<3) +#define ALI_IF_PO (1<<1) + +#define ALI_INT_MASK (ALI_INT_SPDIFOUT|ALI_INT_CODECSPDIFOUT|ALI_INT_MICIN|ALI_INT_PCMOUT|ALI_INT_PCMIN) + +#define ALI_SAMPLE_SIZE 2 + + +/* + * according to the dev/audiovar.h AU_RING_SIZE is 2^16, what fits + * in our limits perfectly, i.e. setting it to higher value + * in your kernel config would improve perfomance, still 2^21 is the max + */ +#define ALI_DMALIST_MAX 32 +#define ALI_DMASEG_MAX (65536*2) /* 64k samples, 2x16 bit samples */ +struct auacer_dmalist { + u_int32_t base; + u_int32_t len; +#define ALI_DMAF_IOC 0x80000000 /* 1-int on complete */ +#define ALI_DMAF_BUP 0x40000000 /* 0-retrans last, 1-transmit 0 */ +}; + +#endif /* _DEV_PCI_AUACERREG_H_ */ + diff --git a/sys/dev/pci/files.pci b/sys/dev/pci/files.pci index 1df012e89ad..5192efff21b 100644 --- a/sys/dev/pci/files.pci +++ b/sys/dev/pci/files.pci @@ -1,4 +1,4 @@ -# $OpenBSD: files.pci,v 1.254 2008/06/08 20:01:02 reyk Exp $ +# $OpenBSD: files.pci,v 1.255 2008/08/28 10:21:23 mikeb Exp $ # $NetBSD: files.pci,v 1.20 1996/09/24 17:47:15 christos Exp $ # # Config file and device description for machine-independent PCI code. @@ -119,6 +119,11 @@ file dev/pci/eso.c eso attach opl at eso with opl_eso file dev/pci/opl_eso.c opl_eso +# Acer Labs M5455 +device auacer: audio, auconv, mulaw, ac97 +attach auacer at pci +file dev/pci/auacer.c auacer + # Intel 810/820/440MX ICH audio with AC'97 device auich: audio, auconv, mulaw, ac97 attach auich at pci |