/* $OpenBSD: auich.c,v 1.82 2010/06/07 23:35:24 ratchov Exp $ */ /* * Copyright (c) 2000,2001 Michael Shalayeff * 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. 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 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 OR HIS RELATIVES 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 MIND, 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. */ /* * AC'97 audio found on Intel 810/815/820/440MX chipsets. * http://developer.intel.com/design/chipsets/datashts/290655.htm * http://developer.intel.com/design/chipsets/manuals/298028.htm * http://www.intel.com/design/chipsets/datashts/290714.htm * http://www.intel.com/design/chipsets/datashts/290744.htm */ #include #include #include #include #include #include #include #include #include #include #include #include #include /* 12.1.10 NAMBAR - native audio mixer base address register */ #define AUICH_NAMBAR 0x10 /* 12.1.11 NABMBAR - native audio bus mastering base address register */ #define AUICH_NABMBAR 0x14 #define AUICH_CFG 0x41 #define AUICH_CFG_IOSE 0x01 /* ICH4/ICH5/ICH6/ICH7 native audio mixer BAR */ #define AUICH_MMBAR 0x18 /* ICH4/ICH5/ICH6/ICH7 native bus mastering BAR */ #define AUICH_MBBAR 0x1c #define AUICH_S2CR 0x10000000 /* tertiary codec ready */ /* table 12-3. native audio bus master control registers */ #define AUICH_BDBAR 0x00 /* 8-byte aligned address */ #define AUICH_CIV 0x04 /* 5 bits current index value */ #define AUICH_LVI 0x05 /* 5 bits last valid index value */ #define AUICH_LVI_MASK 0x1f #define AUICH_STS 0x06 /* 16 bits status */ #define AUICH_FIFOE 0x10 /* fifo error */ #define AUICH_BCIS 0x08 /* r- buf cmplt int sts; wr ack */ #define AUICH_LVBCI 0x04 /* r- last valid bci, wr ack */ #define AUICH_CELV 0x02 /* current equals last valid */ #define AUICH_DCH 0x01 /* dma halted */ #define AUICH_ISTS_BITS "\020\01dch\02celv\03lvbci\04bcis\05fifoe" #define AUICH_PICB 0x08 /* 16 bits */ #define AUICH_PIV 0x0a /* 5 bits prefetched index value */ #define AUICH_CTRL 0x0b /* control */ #define AUICH_IOCE 0x10 /* int on completion enable */ #define AUICH_FEIE 0x08 /* fifo error int enable */ #define AUICH_LVBIE 0x04 /* last valid buf int enable */ #define AUICH_RR 0x02 /* 1 - reset regs */ #define AUICH_RPBM 0x01 /* 1 - run, 0 - pause */ #define AUICH_PCMI 0x00 #define AUICH_PCMO 0x10 #define AUICH_MICI 0x20 #define AUICH_GCTRL 0x2c #define AUICH_SSM_78 0x40000000 /* S/PDIF slots 7 and 8 */ #define AUICH_SSM_69 0x80000000 /* S/PDIF slots 6 and 9 */ #define AUICH_SSM_1011 0xc0000000 /* S/PDIF slots 10 and 11 */ #define AUICH_POM16 0x000000 /* PCM out precision 16bit */ #define AUICH_POM20 0x400000 /* PCM out precision 20bit */ #define AUICH_PCM246_MASK 0x300000 #define AUICH_PCM2 0x000000 /* 2ch output */ #define AUICH_PCM4 0x100000 /* 4ch output */ #define AUICH_PCM6 0x200000 /* 6ch output */ #define AUICH_SIS_PCM246_MASK 0x0000c0 /* SiS 7012 */ #define AUICH_SIS_PCM2 0x000000 /* SiS 7012 2ch output */ #define AUICH_SIS_PCM4 0x000040 /* SiS 7012 4ch output */ #define AUICH_SIS_PCM6 0x000080 /* SiS 7012 6ch output */ #define AUICH_S2RIE 0x40 /* int when tertiary codec resume */ #define AUICH_SRIE 0x20 /* int when 2ndary codec resume */ #define AUICH_PRIE 0x10 /* int when primary codec resume */ #define AUICH_ACLSO 0x08 /* aclink shut off */ #define AUICH_WRESET 0x04 /* warm reset */ #define AUICH_CRESET 0x02 /* cold reset */ #define AUICH_GIE 0x01 /* gpi int enable */ #define AUICH_GSTS 0x30 #define AUICH_MD3 0x20000 /* pwr-dn semaphore for modem */ #define AUICH_AD3 0x10000 /* pwr-dn semaphore for audio */ #define AUICH_RCS 0x08000 /* read completion status */ #define AUICH_B3S12 0x04000 /* bit 3 of slot 12 */ #define AUICH_B2S12 0x02000 /* bit 2 of slot 12 */ #define AUICH_B1S12 0x01000 /* bit 1 of slot 12 */ #define AUICH_SRI 0x00800 /* secondary resume int */ #define AUICH_PRI 0x00400 /* primary resume int */ #define AUICH_SCR 0x00200 /* secondary codec ready */ #define AUICH_PCR 0x00100 /* primary codec ready */ #define AUICH_MINT 0x00080 /* mic in int */ #define AUICH_POINT 0x00040 /* pcm out int */ #define AUICH_PIINT 0x00020 /* pcm in int */ #define AUICH_MOINT 0x00004 /* modem out int */ #define AUICH_MIINT 0x00002 /* modem in int */ #define AUICH_GSCI 0x00001 /* gpi status change */ #define AUICH_GSTS_BITS "\020\01gsci\02miict\03moint\06piint\07point\010mint\011pcr\012scr\013pri\014sri\015b1s12\016b2s12\017b3s12\020rcs\021ad3\022md3" #define AUICH_CAS 0x34 /* 1/8 bit */ #define AUICH_SEMATIMO 1000 /* us */ #define AUICH_RESETIMO 500000 /* us */ #define ICH_SIS_NV_CTL 0x4c /* some SiS/NVIDIA register. From Linux */ #define ICH_SIS_CTL_UNMUTE 0x01 /* un-mute the output */ /* * There are 32 buffer descriptors. Each can reference up to 2^16 16-bit * samples. */ #define AUICH_DMALIST_MAX 32 #define AUICH_DMASEG_MAX (65536*2) struct auich_dmalist { u_int32_t base; u_int32_t len; #define AUICH_DMAF_IOC 0x80000000 /* 1-int on complete */ #define AUICH_DMAF_BUP 0x40000000 /* 0-retrans last, 1-transmit 0 */ }; #define AUICH_FIXED_RATE 48000 struct auich_dma { bus_dmamap_t map; caddr_t addr; bus_dma_segment_t segs[1]; int nsegs; size_t size; }; struct auich_cdata { struct auich_dmalist ic_dmalist_pcmo[AUICH_DMALIST_MAX]; struct auich_dmalist ic_dmalist_pcmi[AUICH_DMALIST_MAX]; struct auich_dmalist ic_dmalist_mici[AUICH_DMALIST_MAX]; }; #define AUICH_CDOFF(x) offsetof(struct auich_cdata, x) #define AUICH_PCMO_OFF(x) AUICH_CDOFF(ic_dmalist_pcmo[(x)]) #define AUICH_PCMI_OFF(x) AUICH_CDOFF(ic_dmalist_pcmi[(x)]) #define AUICH_MICI_OFF(x) AUICH_CDOFF(ic_dmalist_mici[(x)]) struct auich_softc { struct device sc_dev; void *sc_ih; audio_device_t sc_audev; bus_space_tag_t iot; bus_space_tag_t iot_mix; 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; int sc_spdif; /* dma scatter-gather buffer lists */ bus_dmamap_t sc_cddmamap; #define sc_cddma sc_cddmamap->dm_segs[0].ds_addr struct auich_cdata *sc_cdata; struct auich_ring { int qptr; struct auich_dmalist *dmalist; uint32_t start, p, end; int blksize; void (*intr)(void *); void *arg; } pcmo, pcmi, mici; struct auich_dma *sc_pdma; /* play */ struct auich_dma *sc_rdma; /* record */ struct auich_dma *sc_cdma; /* calibrate */ #ifdef AUICH_DEBUG int pcmi_fifoe; int pcmo_fifoe; #endif void *powerhook; int suspend; u_int16_t ext_ctrl; int sc_sample_size; int sc_sts_reg; int sc_dmamap_flags; int sc_ignore_codecready; int flags; int sc_ac97rate; /* multi-channel control bits */ int sc_pcm246_mask; int sc_pcm2; int sc_pcm4; int sc_pcm6; }; #ifdef AUICH_DEBUG #define DPRINTF(l,x) do { if (auich_debug & (l)) printf x; } while(0) int auich_debug = 0x0002; #define AUICH_DEBUG_CODECIO 0x0001 #define AUICH_DEBUG_DMA 0x0002 #define AUICH_DEBUG_INTR 0x0004 #else #define DPRINTF(x,y) /* nothing */ #endif struct cfdriver auich_cd = { NULL, "auich", DV_DULL }; int auich_match(struct device *, void *, void *); void auich_attach(struct device *, struct device *, void *); int auich_intr(void *); struct cfattach auich_ca = { sizeof(struct auich_softc), auich_match, auich_attach }; static const struct auich_devtype { int vendor; int product; int options; char name[8]; } auich_devices[] = { { PCI_VENDOR_INTEL, PCI_PRODUCT_INTEL_6300ESB_ACA, 0, "ESB" }, { PCI_VENDOR_INTEL, PCI_PRODUCT_INTEL_6321ESB_ACA, 0, "ESB2" }, { PCI_VENDOR_INTEL, PCI_PRODUCT_INTEL_82801AA_ACA, 0, "ICH" }, { PCI_VENDOR_INTEL, PCI_PRODUCT_INTEL_82801AB_ACA, 0, "ICH0" }, { PCI_VENDOR_INTEL, PCI_PRODUCT_INTEL_82801BA_ACA, 0, "ICH2" }, { PCI_VENDOR_INTEL, PCI_PRODUCT_INTEL_82801CA_ACA, 0, "ICH3" }, { PCI_VENDOR_INTEL, PCI_PRODUCT_INTEL_82801DB_ACA, 0, "ICH4" }, { PCI_VENDOR_INTEL, PCI_PRODUCT_INTEL_82801EB_ACA, 0, "ICH5" }, { PCI_VENDOR_INTEL, PCI_PRODUCT_INTEL_82801FB_ACA, 0, "ICH6" }, { PCI_VENDOR_INTEL, PCI_PRODUCT_INTEL_82801GB_ACA, 0, "ICH7" }, { PCI_VENDOR_INTEL, PCI_PRODUCT_INTEL_82440MX_ACA, 0, "440MX" }, { PCI_VENDOR_SIS, PCI_PRODUCT_SIS_7012_ACA, 0, "SiS7012" }, { PCI_VENDOR_NVIDIA, PCI_PRODUCT_NVIDIA_NFORCE_ACA, 0, "nForce" }, { PCI_VENDOR_NVIDIA, PCI_PRODUCT_NVIDIA_NFORCE2_ACA, 0, "nForce2" }, { PCI_VENDOR_NVIDIA, PCI_PRODUCT_NVIDIA_NFORCE2_400_ACA, 0, "nForce2" }, { PCI_VENDOR_NVIDIA, PCI_PRODUCT_NVIDIA_NFORCE3_ACA, 0, "nForce3" }, { PCI_VENDOR_NVIDIA, PCI_PRODUCT_NVIDIA_NFORCE3_250_ACA, 0, "nForce3" }, { PCI_VENDOR_NVIDIA, PCI_PRODUCT_NVIDIA_NFORCE4_AC, 0, "nForce4" }, { PCI_VENDOR_NVIDIA, PCI_PRODUCT_NVIDIA_MCP04_AC97, 0, "MCP04" }, { PCI_VENDOR_NVIDIA, PCI_PRODUCT_NVIDIA_MCP51_ACA, 0, "MCP51" }, { PCI_VENDOR_AMD, PCI_PRODUCT_AMD_PBC768_ACA, 0, "AMD768" }, { PCI_VENDOR_AMD, PCI_PRODUCT_AMD_8111_ACA, 0, "AMD8111" }, }; int auich_open(void *, int); void auich_close(void *); int auich_query_encoding(void *, struct audio_encoding *); int auich_set_params(void *, int, int, struct audio_params *, struct audio_params *); int auich_round_blocksize(void *, int); void auich_halt_pipe(struct auich_softc *, int); int auich_halt_output(void *); int auich_halt_input(void *); int auich_getdev(void *, struct audio_device *); int auich_set_port(void *, mixer_ctrl_t *); int auich_get_port(void *, mixer_ctrl_t *); int auich_query_devinfo(void *, mixer_devinfo_t *); void *auich_allocm(void *, int, size_t, int, int); void auich_freem(void *, void *, int); size_t auich_round_buffersize(void *, int, size_t); paddr_t auich_mappage(void *, void *, off_t, int); int auich_get_props(void *); void auich_trigger_pipe(struct auich_softc *, int, struct auich_ring *); void auich_intr_pipe(struct auich_softc *, int, struct auich_ring *); int auich_trigger_output(void *, void *, void *, int, void (*)(void *), void *, struct audio_params *); int auich_trigger_input(void *, void *, void *, int, void (*)(void *), void *, struct audio_params *); int auich_alloc_cdata(struct auich_softc *); int auich_allocmem(struct auich_softc *, size_t, size_t, struct auich_dma *); int auich_freemem(struct auich_softc *, struct auich_dma *); void auich_get_default_params(void *, int, struct audio_params *); void auich_powerhook(int, void *); struct audio_hw_if auich_hw_if = { auich_open, auich_close, NULL, /* drain */ auich_query_encoding, auich_set_params, auich_round_blocksize, NULL, /* commit_setting */ NULL, /* init_output */ NULL, /* init_input */ NULL, /* start_output */ NULL, /* start_input */ auich_halt_output, auich_halt_input, NULL, /* speaker_ctl */ auich_getdev, NULL, /* getfd */ auich_set_port, auich_get_port, auich_query_devinfo, auich_allocm, auich_freem, auich_round_buffersize, auich_mappage, auich_get_props, auich_trigger_output, auich_trigger_input, auich_get_default_params }; int auich_attach_codec(void *, struct ac97_codec_if *); int auich_read_codec(void *, u_int8_t, u_int16_t *); int auich_write_codec(void *, u_int8_t, u_int16_t); void auich_reset_codec(void *); enum ac97_host_flags auich_flags_codec(void *); unsigned int auich_calibrate(struct auich_softc *); void auich_spdif_event(void *, int); int auich_match(parent, match, aux) struct device *parent; void *match; void *aux; { struct pci_attach_args *pa = aux; int i; for (i = sizeof(auich_devices)/sizeof(auich_devices[0]); i--;) if (PCI_VENDOR(pa->pa_id) == auich_devices[i].vendor && PCI_PRODUCT(pa->pa_id) == auich_devices[i].product) return 1; return 0; } void auich_attach(parent, self, aux) struct device *parent, *self; void *aux; { struct auich_softc *sc = (struct auich_softc *)self; struct pci_attach_args *pa = aux; pci_intr_handle_t ih; bus_size_t mix_size, aud_size; pcireg_t csr; const char *intrstr; u_int32_t status; int i; if (PCI_VENDOR(pa->pa_id) == PCI_VENDOR_INTEL && (PCI_PRODUCT(pa->pa_id) == PCI_PRODUCT_INTEL_82801DB_ACA || PCI_PRODUCT(pa->pa_id) == PCI_PRODUCT_INTEL_82801EB_ACA || PCI_PRODUCT(pa->pa_id) == PCI_PRODUCT_INTEL_82801FB_ACA || PCI_PRODUCT(pa->pa_id) == PCI_PRODUCT_INTEL_82801GB_ACA)) { /* * Use native mode for ICH4/ICH5/ICH6/ICH7 */ if (pci_mapreg_map(pa, AUICH_MMBAR, PCI_MAPREG_TYPE_MEM, 0, &sc->iot_mix, &sc->mix_ioh, NULL, &mix_size, 0)) { csr = pci_conf_read(pa->pa_pc, pa->pa_tag, AUICH_CFG); pci_conf_write(pa->pa_pc, pa->pa_tag, AUICH_CFG, csr | AUICH_CFG_IOSE); if (pci_mapreg_map(pa, AUICH_NAMBAR, PCI_MAPREG_TYPE_IO, 0, &sc->iot_mix, &sc->mix_ioh, NULL, &mix_size, 0)) { printf(": can't map codec mem/io space\n"); return; } } if (pci_mapreg_map(pa, AUICH_MBBAR, PCI_MAPREG_TYPE_MEM, 0, &sc->iot, &sc->aud_ioh, NULL, &aud_size, 0)) { csr = pci_conf_read(pa->pa_pc, pa->pa_tag, AUICH_CFG); pci_conf_write(pa->pa_pc, pa->pa_tag, AUICH_CFG, csr | AUICH_CFG_IOSE); if (pci_mapreg_map(pa, AUICH_NABMBAR, PCI_MAPREG_TYPE_IO, 0, &sc->iot, &sc->aud_ioh, NULL, &aud_size, 0)) { printf(": can't map device mem/io space\n"); bus_space_unmap(sc->iot_mix, sc->mix_ioh, mix_size); return; } } } else { if (pci_mapreg_map(pa, AUICH_NAMBAR, PCI_MAPREG_TYPE_IO, 0, &sc->iot_mix, &sc->mix_ioh, NULL, &mix_size, 0)) { printf(": can't map codec i/o space\n"); return; } if (pci_mapreg_map(pa, AUICH_NABMBAR, PCI_MAPREG_TYPE_IO, 0, &sc->iot, &sc->aud_ioh, NULL, &aud_size, 0)) { printf(": can't map device i/o space\n"); bus_space_unmap(sc->iot_mix, sc->mix_ioh, mix_size); return; } } sc->dmat = pa->pa_dmat; if (pci_intr_map(pa, &ih)) { bus_space_unmap(sc->iot, sc->aud_ioh, aud_size); bus_space_unmap(sc->iot_mix, sc->mix_ioh, mix_size); return; } intrstr = pci_intr_string(pa->pa_pc, ih); sc->sc_ih = pci_intr_establish(pa->pa_pc, ih, IPL_AUDIO, auich_intr, sc, sc->sc_dev.dv_xname); if (!sc->sc_ih) { printf(": can't establish interrupt"); if (intrstr) printf(" at %s", intrstr); printf("\n"); bus_space_unmap(sc->iot, sc->aud_ioh, aud_size); bus_space_unmap(sc->iot_mix, sc->mix_ioh, mix_size); return; } for (i = sizeof(auich_devices)/sizeof(auich_devices[0]); i--;) if (PCI_PRODUCT(pa->pa_id) == auich_devices[i].product) break; snprintf(sc->sc_audev.name, sizeof sc->sc_audev.name, "%s AC97", auich_devices[i].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); printf(": %s, %s\n", intrstr, sc->sc_audev.name); /* SiS 7012 needs special handling */ if (PCI_VENDOR(pa->pa_id) == PCI_VENDOR_SIS && PCI_PRODUCT(pa->pa_id) == PCI_PRODUCT_SIS_7012_ACA) { sc->sc_sts_reg = AUICH_PICB; sc->sc_sample_size = 1; sc->sc_pcm246_mask = AUICH_SIS_PCM246_MASK; sc->sc_pcm2 = AUICH_SIS_PCM2; sc->sc_pcm4 = AUICH_SIS_PCM4; sc->sc_pcm6 = AUICH_SIS_PCM6; /* un-mute output */ bus_space_write_4(sc->iot, sc->aud_ioh, ICH_SIS_NV_CTL, bus_space_read_4(sc->iot, sc->aud_ioh, ICH_SIS_NV_CTL) | ICH_SIS_CTL_UNMUTE); } else { sc->sc_sts_reg = AUICH_STS; sc->sc_sample_size = 2; sc->sc_pcm246_mask = AUICH_PCM246_MASK; sc->sc_pcm2 = AUICH_PCM2; sc->sc_pcm4 = AUICH_PCM4; sc->sc_pcm6 = AUICH_PCM6; } /* Workaround for a 440MX B-stepping erratum */ sc->sc_dmamap_flags = BUS_DMA_COHERENT; if (PCI_VENDOR(pa->pa_id) == PCI_VENDOR_INTEL && PCI_PRODUCT(pa->pa_id) == PCI_PRODUCT_INTEL_82440MX_ACA) { sc->sc_dmamap_flags |= BUS_DMA_NOCACHE; printf("%s: DMA bug workaround enabled\n", sc->sc_dev.dv_xname); } /* Set up DMA lists. */ sc->pcmo.qptr = sc->pcmi.qptr = sc->mici.qptr = 0; auich_alloc_cdata(sc); DPRINTF(AUICH_DEBUG_DMA, ("auich_attach: lists %p %p %p\n", sc->pcmo.dmalist, sc->pcmi.dmalist, sc->mici.dmalist)); /* Reset codec and AC'97 */ auich_reset_codec(sc); status = bus_space_read_4(sc->iot, sc->aud_ioh, AUICH_GSTS); if (!(status & AUICH_PCR)) { /* reset failure */ if (PCI_VENDOR(pa->pa_id) == PCI_VENDOR_INTEL && (PCI_PRODUCT(pa->pa_id) == PCI_PRODUCT_INTEL_82801DB_ACA || PCI_PRODUCT(pa->pa_id) == PCI_PRODUCT_INTEL_82801EB_ACA || PCI_PRODUCT(pa->pa_id) == PCI_PRODUCT_INTEL_82801FB_ACA || PCI_PRODUCT(pa->pa_id) == PCI_PRODUCT_INTEL_82801GB_ACA)) { /* MSI 845G Max never return AUICH_PCR */ sc->sc_ignore_codecready = 1; } else { printf("%s: reset failed!\n", sc->sc_dev.dv_xname); return; } } sc->host_if.arg = sc; sc->host_if.attach = auich_attach_codec; sc->host_if.read = auich_read_codec; sc->host_if.write = auich_write_codec; sc->host_if.reset = auich_reset_codec; sc->host_if.flags = auich_flags_codec; sc->host_if.spdif_event = auich_spdif_event; if (sc->sc_dev.dv_cfdata->cf_flags & 0x0001) sc->flags = AC97_HOST_SWAPPED_CHANNELS; if (ac97_attach(&sc->host_if) != 0) { pci_intr_disestablish(pa->pa_pc, sc->sc_ih); bus_space_unmap(sc->iot, sc->aud_ioh, aud_size); bus_space_unmap(sc->iot_mix, sc->mix_ioh, mix_size); return; } sc->codec_if->vtbl->unlock(sc->codec_if); audio_attach_mi(&auich_hw_if, sc, &sc->sc_dev); /* Watch for power changes */ sc->suspend = PWR_RESUME; sc->powerhook = powerhook_establish(auich_powerhook, sc); sc->sc_ac97rate = -1; } int auich_read_codec(v, reg, val) void *v; u_int8_t reg; u_int16_t *val; { struct auich_softc *sc = v; int i; /* wait for an access semaphore */ for (i = AUICH_SEMATIMO; i-- && bus_space_read_1(sc->iot, sc->aud_ioh, AUICH_CAS) & 1; DELAY(1)); if (!sc->sc_ignore_codecready && i < 0) { DPRINTF(AUICH_DEBUG_CODECIO, ("%s: read_codec timeout\n", sc->sc_dev.dv_xname)); return (-1); } *val = bus_space_read_2(sc->iot_mix, sc->mix_ioh, reg); DPRINTF(AUICH_DEBUG_CODECIO, ("%s: read_codec(%x, %x)\n", sc->sc_dev.dv_xname, reg, *val)); return (0); } int auich_write_codec(v, reg, val) void *v; u_int8_t reg; u_int16_t val; { struct auich_softc *sc = v; int i; /* wait for an access semaphore */ for (i = AUICH_SEMATIMO; i-- && bus_space_read_1(sc->iot, sc->aud_ioh, AUICH_CAS) & 1; DELAY(1)); if (sc->sc_ignore_codecready || i >= 0) { DPRINTF(AUICH_DEBUG_CODECIO, ("%s: write_codec(%x, %x)\n", sc->sc_dev.dv_xname, reg, val)); bus_space_write_2(sc->iot_mix, sc->mix_ioh, reg, val); return (0); } else { DPRINTF(AUICH_DEBUG_CODECIO, ("%s: write_codec timeout\n", sc->sc_dev.dv_xname)); return (-1); } } int auich_attach_codec(v, cif) void *v; struct ac97_codec_if *cif; { struct auich_softc *sc = v; sc->codec_if = cif; return 0; } void auich_reset_codec(v) void *v; { struct auich_softc *sc = v; u_int32_t control; int i; control = bus_space_read_4(sc->iot, sc->aud_ioh, AUICH_GCTRL); control &= ~(AUICH_ACLSO | sc->sc_pcm246_mask); control |= (control & AUICH_CRESET) ? AUICH_WRESET : AUICH_CRESET; bus_space_write_4(sc->iot, sc->aud_ioh, AUICH_GCTRL, control); for (i = AUICH_RESETIMO; i-- && !(bus_space_read_4(sc->iot, sc->aud_ioh, AUICH_GSTS) & AUICH_PCR); DELAY(1)); if (i < 0) DPRINTF(AUICH_DEBUG_CODECIO, ("%s: reset_codec timeout\n", sc->sc_dev.dv_xname)); } enum ac97_host_flags auich_flags_codec(void *v) { struct auich_softc *sc = v; return (sc->flags); } void auich_spdif_event(void *v, int flag) { struct auich_softc *sc = v; sc->sc_spdif = flag; } int auich_open(v, flags) void *v; int flags; { struct auich_softc *sc = v; if (sc->sc_ac97rate == -1) sc->sc_ac97rate = auich_calibrate(sc); sc->codec_if->vtbl->lock(sc->codec_if); return 0; } void auich_close(v) void *v; { struct auich_softc *sc = v; sc->codec_if->vtbl->unlock(sc->codec_if); } void auich_get_default_params(void *addr, int mode, struct audio_params *params) { ac97_get_default_params(params); } int auich_query_encoding(v, aep) void *v; struct audio_encoding *aep; { struct auich_softc *sc = v; if (sc->sc_spdif) { switch (aep->index) { case 0: strlcpy(aep->name, AudioEslinear_le, sizeof aep->name); aep->encoding = AUDIO_ENCODING_SLINEAR_LE; aep->precision = 16; aep->flags = 0; return (0); default: return (EINVAL); } } else { switch (aep->index) { case 0: strlcpy(aep->name, AudioEulinear, sizeof aep->name); aep->encoding = AUDIO_ENCODING_ULINEAR; aep->precision = 8; aep->flags = 0; 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 auich_set_params(v, setmode, usemode, play, rec) void *v; int setmode, usemode; struct audio_params *play, *rec; { struct auich_softc *sc = v; struct ac97_codec_if *codec = sc->codec_if; int error; u_int orate; u_int adj_rate; u_int32_t control; u_int16_t ext_id; if (setmode & AUMODE_PLAY) { /* only 16-bit 48kHz slinear_le if s/pdif enabled */ if (sc->sc_spdif) { play->sample_rate = 48000; play->precision = 16; play->encoding = AUDIO_ENCODING_SLINEAR_LE; } } if (setmode & AUMODE_PLAY) { play->factor = 1; play->sw_code = NULL; if (play->precision > 16) play->precision = 16; switch(play->encoding) { case AUDIO_ENCODING_ULAW: if (play->channels > 2) play->channels = 2; switch (play->channels) { case 1: play->factor = 4; play->sw_code = mulaw_to_slinear16_le_mts; break; case 2: play->factor = 2; play->sw_code = mulaw_to_slinear16_le; break; default: return (EINVAL); } break; case AUDIO_ENCODING_SLINEAR_LE: switch (play->precision) { case 8: if (play->channels > 2) play->channels = 2; switch (play->channels) { case 1: play->factor = 4; play->sw_code = linear8_to_linear16_le_mts; break; case 2: play->factor = 2; play->sw_code = linear8_to_linear16_le; break; default: return (EINVAL); } break; case 16: if (play->channels > 6) play->channels = 6; if (play->channels > 1) play->channels &= ~1; switch (play->channels) { case 1: play->factor = 2; play->sw_code = noswap_bytes_mts; break; case 2: break; case 4: ext_id = codec->vtbl->get_caps(codec); if (!(ext_id & AC97_EXT_AUDIO_SDAC)) play->channels = 2; break; case 6: ext_id = codec->vtbl->get_caps(codec); if ((ext_id & AC97_BITS_6CH) != AC97_BITS_6CH) play->channels = 2; break; default: return (EINVAL); } break; } break; case AUDIO_ENCODING_ULINEAR_LE: if (play->channels > 2) play->channels = 2; switch (play->precision) { case 8: switch (play->channels) { case 1: play->factor = 4; play->sw_code = ulinear8_to_linear16_le_mts; break; case 2: play->factor = 2; play->sw_code = ulinear8_to_linear16_le; break; default: return (EINVAL); } break; case 16: switch (play->channels) { case 1: play->factor = 2; play->sw_code = change_sign16_le_mts; break; case 2: play->sw_code = change_sign16_le; break; default: return (EINVAL); } break; default: return (EINVAL); } break; case AUDIO_ENCODING_ALAW: if (play->channels > 2) play->channels = 2; switch (play->channels) { case 1: play->factor = 4; play->sw_code = alaw_to_slinear16_le_mts; break; case 2: play->factor = 2; play->sw_code = alaw_to_slinear16_le; break; default: return (EINVAL); } break; case AUDIO_ENCODING_SLINEAR_BE: if (play->channels > 2) play->channels = 2; switch (play->precision) { case 8: switch (play->channels) { case 1: play->factor = 4; play->sw_code = linear8_to_linear16_le_mts; break; case 2: play->factor = 2; play->sw_code = linear8_to_linear16_le; 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: if (play->channels > 2) play->channels = 2; switch (play->precision) { case 8: switch (play->channels) { case 1: play->factor = 4; play->sw_code = ulinear8_to_linear16_le_mts; break; case 2: play->factor = 2; play->sw_code = ulinear8_to_linear16_le; break; default: return (EINVAL); } break; case 16: switch (play->channels) { case 1: play->factor = 2; play->sw_code = swap_bytes_change_sign16_le_mts; break; case 2: play->sw_code = swap_bytes_change_sign16_le; break; default: return (EINVAL); } break; default: return (EINVAL); } break; default: return (EINVAL); } orate = adj_rate = play->sample_rate; if (sc->sc_ac97rate != 0) adj_rate = orate * AUICH_FIXED_RATE / sc->sc_ac97rate; play->sample_rate = adj_rate; error = ac97_set_rate(sc->codec_if, AC97_REG_PCM_LFE_DAC_RATE, &play->sample_rate); if (error) return (error); play->sample_rate = adj_rate; error = ac97_set_rate(sc->codec_if, AC97_REG_PCM_SURR_DAC_RATE, &play->sample_rate); if (error) return (error); play->sample_rate = adj_rate; error = ac97_set_rate(sc->codec_if, AC97_REG_PCM_FRONT_DAC_RATE, &play->sample_rate); if (error) return (error); if (play->sample_rate == adj_rate) play->sample_rate = orate; control = bus_space_read_4(sc->iot, sc->aud_ioh, AUICH_GCTRL); control &= ~(sc->sc_pcm246_mask); if (play->channels == 4) control |= sc->sc_pcm4; else if (play->channels == 6) control |= sc->sc_pcm6; bus_space_write_4(sc->iot, sc->aud_ioh, AUICH_GCTRL, control); } if (setmode & AUMODE_RECORD) { rec->factor = 1; rec->sw_code = 0; if (rec->channels > 2) rec->channels = 2; if (rec->precision > 16) rec->precision = 16; switch(rec->encoding) { case AUDIO_ENCODING_ULAW: switch (rec->channels) { case 1: rec->sw_code = slinear16_to_mulaw_le_stm; rec->factor = 4; break; case 2: rec->sw_code = slinear16_to_mulaw_le; rec->factor = 2; break; } break; case AUDIO_ENCODING_ALAW: switch (rec->channels) { case 1: rec->sw_code = slinear16_to_alaw_le_stm; rec->factor = 4; break; case 2: rec->sw_code = slinear16_to_alaw_le; rec->factor = 2; break; } break; case AUDIO_ENCODING_SLINEAR_LE: switch (rec->precision) { case 8: switch (rec->channels) { case 1: rec->sw_code = linear16_to_linear8_le_stm; rec->factor = 4; break; case 2: rec->sw_code = linear16_to_linear8_le; rec->factor = 2; break; } break; case 16: switch (rec->channels) { case 1: rec->sw_code = linear16_decimator; rec->factor = 2; break; case 2: break; } break; default: return (EINVAL); } break; case AUDIO_ENCODING_ULINEAR_LE: switch (rec->precision) { case 8: switch (rec->channels) { case 1: rec->sw_code = linear16_to_ulinear8_le_stm; rec->factor = 4; break; case 2: rec->sw_code = linear16_to_ulinear8_le; rec->factor = 2; break; } break; case 16: switch (rec->channels) { case 1: rec->sw_code = change_sign16_le_stm; rec->factor = 2; break; case 2: rec->sw_code = change_sign16_le; break; } break; default: return (EINVAL); } break; case AUDIO_ENCODING_SLINEAR_BE: switch (rec->precision) { case 8: switch (rec->channels) { case 1: rec->sw_code = linear16_to_linear8_le_stm; rec->factor = 4; break; case 2: rec->sw_code = linear16_to_linear8_le; rec->factor = 2; break; } break; case 16: switch (rec->channels) { case 1: rec->sw_code = swap_bytes_stm; rec->factor = 2; break; case 2: rec->sw_code = swap_bytes; break; } break; default: return (EINVAL); } break; case AUDIO_ENCODING_ULINEAR_BE: switch (rec->precision) { case 8: switch (rec->channels) { case 1: rec->sw_code = linear16_to_ulinear8_le_stm; rec->factor = 4; break; case 2: rec->sw_code = linear16_to_ulinear8_le; rec->factor = 2; break; } break; case 16: switch (rec->channels) { case 1: rec->sw_code = change_sign16_swap_bytes_le_stm; rec->factor = 2; break; case 2: rec->sw_code = change_sign16_swap_bytes_le; break; } break; default: return (EINVAL); } break; default: return (EINVAL); } orate = rec->sample_rate; if (sc->sc_ac97rate != 0) rec->sample_rate = orate * AUICH_FIXED_RATE / sc->sc_ac97rate; error = ac97_set_rate(sc->codec_if, AC97_REG_PCM_LR_ADC_RATE, &rec->sample_rate); if (error) return (error); rec->sample_rate = orate; } return (0); } int auich_round_blocksize(v, blk) void *v; int blk; { return (blk + 0x3f) & ~0x3f; } void auich_halt_pipe(struct auich_softc *sc, int pipe) { int i; uint32_t sts; bus_space_write_1(sc->iot, sc->aud_ioh, pipe + AUICH_CTRL, 0); /* wait for DMA halted and clear interrupt / event bits if needed */ for (i = 0; i < 1000; i++) { sts = bus_space_read_2(sc->iot, sc->aud_ioh, pipe + sc->sc_sts_reg); if (sts & (AUICH_CELV | AUICH_LVBCI | AUICH_BCIS | AUICH_FIFOE)) bus_space_write_2(sc->iot, sc->aud_ioh, pipe + sc->sc_sts_reg, AUICH_CELV | AUICH_LVBCI | AUICH_BCIS | AUICH_FIFOE); if (sts & AUICH_DCH) break; DELAY(100); } bus_space_write_1(sc->iot, sc->aud_ioh, pipe + AUICH_CTRL, AUICH_RR); if (i > 0) DPRINTF(AUICH_DEBUG_DMA, ("auich_halt_pipe: halt took %d cycles\n", i)); } int auich_halt_output(v) void *v; { struct auich_softc *sc = v; DPRINTF(AUICH_DEBUG_DMA, ("%s: halt_output\n", sc->sc_dev.dv_xname)); auich_halt_pipe(sc, AUICH_PCMO); sc->pcmo.intr = NULL; return 0; } int auich_halt_input(v) void *v; { struct auich_softc *sc = v; DPRINTF(AUICH_DEBUG_DMA, ("%s: halt_input\n", sc->sc_dev.dv_xname)); /* XXX halt both unless known otherwise */ auich_halt_pipe(sc, AUICH_PCMI); auich_halt_pipe(sc, AUICH_MICI); sc->pcmi.intr = NULL; return 0; } int auich_getdev(v, adp) void *v; struct audio_device *adp; { struct auich_softc *sc = v; *adp = sc->sc_audev; return 0; } int auich_set_port(v, cp) void *v; mixer_ctrl_t *cp; { struct auich_softc *sc = v; return sc->codec_if->vtbl->mixer_set_port(sc->codec_if, cp); } int auich_get_port(v, cp) void *v; mixer_ctrl_t *cp; { struct auich_softc *sc = v; return sc->codec_if->vtbl->mixer_get_port(sc->codec_if, cp); } int auich_query_devinfo(v, dp) void *v; mixer_devinfo_t *dp; { struct auich_softc *sc = v; return sc->codec_if->vtbl->query_devinfo(sc->codec_if, dp); } void * auich_allocm(v, direction, size, pool, flags) void *v; int direction; size_t size; int pool, flags; { struct auich_softc *sc = v; struct auich_dma *p; int error; /* can only use 1 segment */ if (size > AUICH_DMASEG_MAX) { DPRINTF(AUICH_DEBUG_DMA, ("%s: requested buffer size too large: %d", \ sc->sc_dev.dv_xname, size)); return NULL; } p = malloc(sizeof(*p), pool, flags | M_ZERO); if (!p) return NULL; error = auich_allocmem(sc, size, PAGE_SIZE, p); if (error) { free(p, pool); return NULL; } if (direction == AUMODE_PLAY) sc->sc_pdma = p; else if (direction == AUMODE_RECORD) sc->sc_rdma = p; else sc->sc_cdma = p; return p->addr; } void auich_freem(void *v, void *ptr, int pool) { struct auich_softc *sc; struct auich_dma *p; sc = v; if (sc->sc_pdma != NULL && sc->sc_pdma->addr == ptr) p = sc->sc_pdma; else if (sc->sc_rdma != NULL && sc->sc_rdma->addr == ptr) p = sc->sc_rdma; else if (sc->sc_cdma != NULL && sc->sc_cdma->addr == ptr) p = sc->sc_cdma; else return; auich_freemem(sc, p); free(p, pool); } size_t auich_round_buffersize(v, direction, size) void *v; int direction; size_t size; { if (size > AUICH_DMALIST_MAX * AUICH_DMASEG_MAX) size = AUICH_DMALIST_MAX * AUICH_DMASEG_MAX; return size; } paddr_t auich_mappage(v, mem, off, prot) void *v; void *mem; off_t off; int prot; { struct auich_softc *sc = v; struct auich_dma *p; if (off < 0) return -1; p = NULL; if (sc->sc_pdma != NULL && sc->sc_pdma->addr == mem) p = sc->sc_pdma; else if (sc->sc_rdma != NULL && sc->sc_rdma->addr == mem) p = sc->sc_rdma; else return -1; return bus_dmamem_mmap(sc->dmat, p->segs, p->nsegs, off, prot, BUS_DMA_WAITOK); } int auich_get_props(v) void *v; { return AUDIO_PROP_MMAP | AUDIO_PROP_INDEPENDENT | AUDIO_PROP_FULLDUPLEX; } int auich_intr(v) void *v; { struct auich_softc *sc = v; int ret = 0, sts, gsts; gsts = bus_space_read_4(sc->iot, sc->aud_ioh, AUICH_GSTS); DPRINTF(AUICH_DEBUG_INTR, ("auich_intr: gsts=%b\n", gsts, AUICH_GSTS_BITS)); if (gsts & AUICH_POINT) { sts = bus_space_read_2(sc->iot, sc->aud_ioh, AUICH_PCMO + sc->sc_sts_reg); DPRINTF(AUICH_DEBUG_INTR, ("auich_intr: osts=%b\n", sts, AUICH_ISTS_BITS)); #ifdef AUICH_DEBUG if (sts & AUICH_FIFOE) { printf("%s: in fifo underrun # %u civ=%u ctrl=0x%x sts=%b\n", sc->sc_dev.dv_xname, sc->pcmo_fifoe++, bus_space_read_1(sc->iot, sc->aud_ioh, AUICH_PCMO + AUICH_CIV), bus_space_read_1(sc->iot, sc->aud_ioh, AUICH_PCMO + AUICH_CTRL), bus_space_read_2(sc->iot, sc->aud_ioh, AUICH_PCMO + sc->sc_sts_reg), AUICH_ISTS_BITS); } #endif if (sts & AUICH_BCIS) auich_intr_pipe(sc, AUICH_PCMO, &sc->pcmo); /* int ack */ bus_space_write_2(sc->iot, sc->aud_ioh, AUICH_PCMO + sc->sc_sts_reg, sts & (AUICH_BCIS | AUICH_FIFOE)); bus_space_write_4(sc->iot, sc->aud_ioh, AUICH_GSTS, AUICH_POINT); ret++; } if (gsts & AUICH_PIINT) { sts = bus_space_read_2(sc->iot, sc->aud_ioh, AUICH_PCMI + sc->sc_sts_reg); DPRINTF(AUICH_DEBUG_INTR, ("auich_intr: ists=%b\n", sts, AUICH_ISTS_BITS)); #ifdef AUICH_DEBUG if (sts & AUICH_FIFOE) { printf("%s: in fifo overrun civ=%u ctrl=0x%x sts=%b\n", sc->sc_dev.dv_xname, sc->pcmi_fifoe++, bus_space_read_1(sc->iot, sc->aud_ioh, AUICH_PCMI + AUICH_CIV), bus_space_read_1(sc->iot, sc->aud_ioh, AUICH_PCMI + AUICH_CTRL), bus_space_read_2(sc->iot, sc->aud_ioh, AUICH_PCMI + sc->sc_sts_reg), AUICH_ISTS_BITS); } #endif if (sts & AUICH_BCIS) auich_intr_pipe(sc, AUICH_PCMI, &sc->pcmi); /* int ack */ bus_space_write_2(sc->iot, sc->aud_ioh, AUICH_PCMI + sc->sc_sts_reg, sts & (AUICH_BCIS | AUICH_FIFOE)); bus_space_write_4(sc->iot, sc->aud_ioh, AUICH_GSTS, AUICH_PIINT); ret++; } if (gsts & AUICH_MINT) { sts = bus_space_read_2(sc->iot, sc->aud_ioh, AUICH_MICI + sc->sc_sts_reg); DPRINTF(AUICH_DEBUG_INTR, ("auich_intr: ists=%b\n", sts, AUICH_ISTS_BITS)); #ifdef AUICH_DEBUG if (sts & AUICH_FIFOE) { printf("%s: in fifo overrun # %u civ=%u ctrl=0x%x sts=%b\n", sc->sc_dev.dv_xname, bus_space_read_1(sc->iot, sc->aud_ioh, AUICH_MICI + AUICH_CIV), bus_space_read_1(sc->iot, sc->aud_ioh, AUICH_MICI + AUICH_CTRL), bus_space_read_2(sc->iot, sc->aud_ioh, AUICH_MICI + sc->sc_sts_reg), AUICH_ISTS_BITS); } #endif if (sts & AUICH_BCIS) auich_intr_pipe(sc, AUICH_MICI, &sc->mici); /* int ack */ bus_space_write_2(sc->iot, sc->aud_ioh, AUICH_MICI + sc->sc_sts_reg, sts + (AUICH_BCIS | AUICH_FIFOE)); bus_space_write_4(sc->iot, sc->aud_ioh, AUICH_GSTS, AUICH_MINT); ret++; } return ret; } void auich_trigger_pipe(struct auich_softc *sc, int pipe, struct auich_ring *ring) { int blksize, qptr, oqptr; struct auich_dmalist *q; blksize = ring->blksize; qptr = oqptr = bus_space_read_1(sc->iot, sc->aud_ioh, pipe + AUICH_CIV); /* XXX remove this when no one reports problems */ if(oqptr >= AUICH_DMALIST_MAX) { printf("%s: Unexpected CIV: %d\n", sc->sc_dev.dv_xname, oqptr); qptr = oqptr = 0; } do { q = &ring->dmalist[qptr]; q->base = ring->p; q->len = (blksize / sc->sc_sample_size) | AUICH_DMAF_IOC; DPRINTF(AUICH_DEBUG_INTR, ("auich_trigger_pipe: %p, %p = %x @ 0x%x qptr=%d\n", &ring->dmalist[qptr], q, q->len, q->base, qptr)); ring->p += blksize; if (ring->p >= ring->end) ring->p = ring->start; qptr = (qptr + 1) & AUICH_LVI_MASK; } while (qptr != oqptr); ring->qptr = qptr; DPRINTF(AUICH_DEBUG_DMA, ("auich_trigger_pipe: qptr=%d\n", qptr)); bus_space_write_1(sc->iot, sc->aud_ioh, pipe + AUICH_LVI, (qptr - 1) & AUICH_LVI_MASK); bus_space_write_1(sc->iot, sc->aud_ioh, pipe + AUICH_CTRL, AUICH_IOCE | AUICH_FEIE | AUICH_RPBM); } void auich_intr_pipe(struct auich_softc *sc, int pipe, struct auich_ring *ring) { int blksize, qptr, nqptr; struct auich_dmalist *q; blksize = ring->blksize; qptr = ring->qptr; nqptr = bus_space_read_1(sc->iot, sc->aud_ioh, pipe + AUICH_CIV); while (qptr != nqptr) { q = &ring->dmalist[qptr]; q->base = ring->p; q->len = (blksize / sc->sc_sample_size) | AUICH_DMAF_IOC; DPRINTF(AUICH_DEBUG_INTR, ("auich_intr: %p, %p = %x @ 0x%x qptr=%d\n", &ring->dmalist[qptr], q, q->len, q->base, qptr)); ring->p += blksize; if (ring->p >= ring->end) ring->p = ring->start; qptr = (qptr + 1) & AUICH_LVI_MASK; if (ring->intr) ring->intr(ring->arg); else printf("auich_intr: got progress with intr==NULL\n"); } ring->qptr = qptr; bus_space_write_1(sc->iot, sc->aud_ioh, pipe + AUICH_LVI, (qptr - 1) & AUICH_LVI_MASK); } int auich_trigger_output(v, start, end, blksize, intr, arg, param) void *v; void *start, *end; int blksize; void (*intr)(void *); void *arg; struct audio_params *param; { struct auich_softc *sc = v; struct auich_dma *p; size_t size; #ifdef AUICH_DEBUG uint16_t sts; sts = bus_space_read_2(sc->iot, sc->aud_ioh, AUICH_PCMO + sc->sc_sts_reg); DPRINTF(AUICH_DEBUG_DMA, ("auich_trigger_output(%x, %x, %d, %p, %p, %p) sts=%b\n", start, end, blksize, intr, arg, param, sts, AUICH_ISTS_BITS)); #endif if (sc->sc_pdma->addr == start) p = sc->sc_pdma; else return -1; size = (size_t)((caddr_t)end - (caddr_t)start); sc->pcmo.intr = intr; sc->pcmo.arg = arg; /* * The logic behind this is: * setup one buffer to play, then LVI dump out the rest * to the scatter-gather chain. */ sc->pcmo.start = p->segs->ds_addr; sc->pcmo.p = sc->pcmo.start; sc->pcmo.end = sc->pcmo.start + size; sc->pcmo.blksize = blksize; bus_space_write_4(sc->iot, sc->aud_ioh, AUICH_PCMO + AUICH_BDBAR, sc->sc_cddma + AUICH_PCMO_OFF(0)); auich_trigger_pipe(sc, AUICH_PCMO, &sc->pcmo); return 0; } int auich_trigger_input(v, start, end, blksize, intr, arg, param) void *v; void *start, *end; int blksize; void (*intr)(void *); void *arg; struct audio_params *param; { struct auich_softc *sc = v; struct auich_dma *p; size_t size; #ifdef AUICH_DEBUG DPRINTF(AUICH_DEBUG_DMA, ("auich_trigger_input(%x, %x, %d, %p, %p, %p) sts=%b\n", start, end, blksize, intr, arg, param, bus_space_read_2(sc->iot, sc->aud_ioh, AUICH_PCMI + sc->sc_sts_reg), AUICH_ISTS_BITS)); #endif if (sc->sc_rdma->addr == start) p = sc->sc_rdma; else return -1; size = (size_t)((caddr_t)end - (caddr_t)start); sc->pcmi.intr = intr; sc->pcmi.arg = arg; /* * The logic behind this is: * setup one buffer to play, then LVI dump out the rest * to the scatter-gather chain. */ sc->pcmi.start = p->segs->ds_addr; sc->pcmi.p = sc->pcmi.start; sc->pcmi.end = sc->pcmi.start + size; sc->pcmi.blksize = blksize; bus_space_write_4(sc->iot, sc->aud_ioh, AUICH_PCMI + AUICH_BDBAR, sc->sc_cddma + AUICH_PCMI_OFF(0)); auich_trigger_pipe(sc, AUICH_PCMI, &sc->pcmi); return 0; } int auich_allocmem(struct auich_softc *sc, size_t size, size_t align, struct auich_dma *p) { int error; p->size = size; error = bus_dmamem_alloc(sc->dmat, p->size, align, 0, p->segs, 1, &p->nsegs, BUS_DMA_NOWAIT); if (error) { DPRINTF(AUICH_DEBUG_DMA, ("%s: bus_dmamem_alloc failed: error %d\n", sc->sc_dev.dv_xname, error)); return error; } error = bus_dmamem_map(sc->dmat, p->segs, 1, p->size, &p->addr, BUS_DMA_NOWAIT | sc->sc_dmamap_flags); if (error) { DPRINTF(AUICH_DEBUG_DMA, ("%s: bus_dmamem_map failed: error %d\n", sc->sc_dev.dv_xname, error)); goto free; } error = bus_dmamap_create(sc->dmat, p->size, 1, p->size, 0, BUS_DMA_NOWAIT, &p->map); if (error) { DPRINTF(AUICH_DEBUG_DMA, ("%s: bus_dmamap_create failed: error %d\n", sc->sc_dev.dv_xname, error)); goto unmap; } error = bus_dmamap_load(sc->dmat, p->map, p->addr, p->size, NULL, BUS_DMA_NOWAIT); if (error) { DPRINTF(AUICH_DEBUG_DMA, ("%s: bus_dmamap_load failed: error %d\n", sc->sc_dev.dv_xname, 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 auich_freemem(struct auich_softc *sc, struct auich_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 auich_alloc_cdata(struct auich_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 auich_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, 1, sizeof(struct auich_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 auich_cdata), 1, sizeof(struct auich_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 auich_cdata), NULL, 0)) != 0) { printf("%s: unable tp load control data DMA map, " "error = %d\n", sc->sc_dev.dv_xname, error); goto fail_3; } sc->pcmo.dmalist = sc->sc_cdata->ic_dmalist_pcmo; sc->pcmi.dmalist = sc->sc_cdata->ic_dmalist_pcmi; sc->mici.dmalist = sc->sc_cdata->ic_dmalist_mici; 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 auich_cdata)); fail_1: bus_dmamem_free(sc->dmat, &seg, rseg); fail_0: return error; } void auich_powerhook(why, self) int why; void *self; { struct auich_softc *sc = (struct auich_softc *)self; if (why != PWR_RESUME) { /* Power down */ DPRINTF(1, ("auich: power down\n")); sc->suspend = why; auich_read_codec(sc, AC97_REG_EXT_AUDIO_CTRL, &sc->ext_ctrl); } else { /* Wake up */ DPRINTF(1, ("auich: power resume\n")); if (sc->suspend == PWR_RESUME) { printf("%s: resume without suspend?\n", sc->sc_dev.dv_xname); sc->suspend = why; return; } sc->suspend = why; auich_reset_codec(sc); DELAY(1000); (sc->codec_if->vtbl->restore_ports)(sc->codec_if); auich_write_codec(sc, AC97_REG_EXT_AUDIO_CTRL, sc->ext_ctrl); } } /* -------------------------------------------------------------------- */ /* Calibrate card (some boards are overclocked and need scaling) */ unsigned int auich_calibrate(struct auich_softc *sc) { struct timeval t1, t2; u_int8_t civ, ociv; uint16_t sts, osts; u_int32_t wait_us, actual_48k_rate, bytes, ac97rate; void *temp_buffer; struct auich_dma *p; ac97rate = AUICH_FIXED_RATE; /* * Grab audio from input for fixed interval and compare how * much we actually get with what we expect. Interval needs * to be sufficiently short that no interrupts are * generated. * XXX: Is this true? We don't request any interrupts, * so why should the chip issue any? */ /* Setup a buffer */ bytes = 16000; temp_buffer = auich_allocm(sc, 0, bytes, M_DEVBUF, M_NOWAIT); if (temp_buffer == NULL) return (ac97rate); if (sc->sc_cdma->addr == temp_buffer) { p = sc->sc_cdma; } else { printf("auich_calibrate: bad address %p\n", temp_buffer); return (ac97rate); } /* get current CIV (usually 0 after reboot) */ ociv = civ = bus_space_read_1(sc->iot, sc->aud_ioh, AUICH_PCMI + AUICH_CIV); sc->pcmi.dmalist[civ].base = p->map->dm_segs[0].ds_addr; sc->pcmi.dmalist[civ].len = bytes / sc->sc_sample_size; /* * our data format is stereo, 16 bit so each sample is 4 bytes. * assuming we get 48000 samples per second, we get 192000 bytes/sec. * we're going to start recording with interrupts disabled and measure * the time taken for one block to complete. we know the block size, * we know the time in microseconds, we calculate the sample rate: * * actual_rate [bps] = bytes / (time [s] * 4) * actual_rate [bps] = (bytes * 1000000) / (time [us] * 4) * actual_rate [Hz] = (bytes * 250000) / time [us] */ /* prepare */ bus_space_write_4(sc->iot, sc->aud_ioh, AUICH_PCMI + AUICH_BDBAR, sc->sc_cddma + AUICH_PCMI_OFF(0)); /* we got only one valid sample, so set LVI to CIV * otherwise we provoke a AUICH_FIFOE FIFO error * which will confuse the chip later on. */ bus_space_write_1(sc->iot, sc->aud_ioh, AUICH_PCMI + AUICH_LVI, civ & AUICH_LVI_MASK); /* start, but don't request any interupts */ microuptime(&t1); bus_space_write_1(sc->iot, sc->aud_ioh, AUICH_PCMI + AUICH_CTRL, AUICH_RPBM); /* XXX remove this sometime */ osts = bus_space_read_2(sc->iot, sc->aud_ioh, AUICH_PCMI + sc->sc_sts_reg); /* wait */ while(1) { microuptime(&t2); sts = bus_space_read_2(sc->iot, sc->aud_ioh, AUICH_PCMI + sc->sc_sts_reg); civ = bus_space_read_1(sc->iot, sc->aud_ioh, AUICH_PCMI + AUICH_CIV); /* turn time delta into us */ wait_us = ((t2.tv_sec - t1.tv_sec) * 1000000) + t2.tv_usec - t1.tv_usec; /* this should actually never happen because civ==lvi */ if ((civ & AUICH_LVI_MASK) != (ociv & AUICH_LVI_MASK)) { printf("%s: ac97 CIV progressed after %d us sts=%b civ=%u\n", sc->sc_dev.dv_xname, wait_us, sts, AUICH_ISTS_BITS, civ); ociv = civ; } /* normal completion */ if (sts & (AUICH_DCH | AUICH_CELV | AUICH_LVBCI)) break; /* * check for strange changes in STS - * XXX remove it when everythings fine */ if (sts != osts) { printf("%s: ac97 sts changed after %d us sts=%b civ=%u\n", sc->sc_dev.dv_xname, wait_us, sts, AUICH_ISTS_BITS, civ); osts = sts; } /* * timeout: we expect 83333 us for 48k sampling rate, * 600000 us will be enough even for 8k sampling rate */ if (wait_us > 600000) { printf("%s: ac97 link rate timed out %d us sts=%b civ=%u\n", sc->sc_dev.dv_xname, wait_us, sts, AUICH_ISTS_BITS, civ); /* reset and clean up*/ auich_halt_pipe(sc, AUICH_PCMI); auich_halt_pipe(sc, AUICH_MICI); auich_freem(sc, temp_buffer, M_DEVBUF); /* return default sample rate */ return (ac97rate); } } DPRINTF(AUICH_DEBUG_CODECIO, ("%s: ac97 link rate calibration took %d us sts=%b civ=%u\n", sc->sc_dev.dv_xname, wait_us, sts, AUICH_ISTS_BITS, civ)); /* reset and clean up */ auich_halt_pipe(sc, AUICH_PCMI); auich_halt_pipe(sc, AUICH_MICI); auich_freem(sc, temp_buffer, M_DEVBUF); #ifdef AUICH_DEBUG sts = bus_space_read_2(sc->iot, sc->aud_ioh, AUICH_PCMI + sc->sc_sts_reg); civ = bus_space_read_4(sc->iot, sc->aud_ioh, AUICH_PCMI + AUICH_CIV); printf("%s: after calibration and reset sts=%b civ=%u\n", sc->sc_dev.dv_xname, sts, AUICH_ISTS_BITS, civ); #endif /* now finally calculate measured samplerate */ actual_48k_rate = (bytes * 250000) / wait_us; if (actual_48k_rate <= 48500) ac97rate = AUICH_FIXED_RATE; else ac97rate = actual_48k_rate; DPRINTF(AUICH_DEBUG_CODECIO, ("%s: measured ac97 link rate at %d Hz", sc->sc_dev.dv_xname, actual_48k_rate)); if (ac97rate != actual_48k_rate) DPRINTF(AUICH_DEBUG_CODECIO, (", will use %d Hz", ac97rate)); DPRINTF(AUICH_DEBUG_CODECIO, ("\n")); return (ac97rate); }