/* $NetBSD$ */ /* * Copyright (c) 2008 SHIMIZU Ryo * 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 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. */ #include __KERNEL_RCSID(0, "$NetBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define GCSCAUDIO_NPRDTABLE 256 /* including a JMP-PRD for loop */ #define GCSCAUDIO_PRD_SIZE_MAX 65532 /* limited by CS5536 Controller */ #define GCSCAUDIO_BUFSIZE_MAX (GCSCAUDIO_PRD_SIZE_MAX * (GCSCAUDIO_NPRDTABLE - 1)) struct gcscaudio_prdtables { struct acc_prd prd[GCSCAUDIO_NPRDTABLE]; }; struct gcscaudio_softc_ch { /* PRD table for play */ struct gcscaudio_prdtables *ch_prdtables; bus_dmamap_t ch_prdmap; bus_dma_segment_t ch_prdsegs[1]; int ch_prdnseg; void (*ch_intr)(void *); void *ch_intr_arg; struct audio_params ch_params; }; struct gcscaudio_dma { LIST_ENTRY(gcscaudio_dma) list; bus_dmamap_t map; void *addr; size_t size; bus_dma_segment_t segs[1]; int nseg; }; struct gcscaudio_softc { struct device sc_dev; pci_chipset_tag_t sc_pc; pcitag_t sc_pt; void *sc_ih; bus_space_tag_t sc_iot; bus_space_handle_t sc_ioh; bus_size_t sc_ios; bus_dma_tag_t sc_dmat; /* allocated DMA buffer list */ LIST_HEAD(, gcscaudio_dma) sc_dmalist; #define GCSCAUDIO_MAXFORMATS 4 struct audio_format sc_formats[GCSCAUDIO_MAXFORMATS]; int sc_nformats; struct audio_encoding_set *sc_encodings; /* AC97 codec */ struct ac97_host_if host_if; struct ac97_codec_if *codec_if; /* input, output channels */ struct gcscaudio_softc_ch sc_play; struct gcscaudio_softc_ch sc_rec; bool sc_surround_independent; bool sc_lfe_independent; bool sc_spdif; }; /* for cfattach */ static int gcscaudio_match(device_t, struct cfdata *, void *); static void gcscaudio_attach(device_t, device_t, void *); /* for audio_hw_if */ static int gcscaudio_open(void *, int); static void gcscaudio_close(void *); static int gcscaudio_query_encoding(void *, struct audio_encoding *); static int gcscaudio_set_params(void *, int, int, audio_params_t *, audio_params_t *, stream_filter_list_t *, stream_filter_list_t *); static int gcscaudio_round_blocksize(void *, int, int, const audio_params_t *); static int gcscaudio_halt_output(void *); static int gcscaudio_halt_input(void *); static int gcscaudio_getdev(void *, struct audio_device *); static int gcscaudio_set_port(void *, mixer_ctrl_t *); static int gcscaudio_get_port(void *, mixer_ctrl_t *); static int gcscaudio_query_devinfo(void *, mixer_devinfo_t *); static void *gcscaudio_malloc(void *, int, size_t, struct malloc_type *, int); static void gcscaudio_free(void *, void *, struct malloc_type *); static size_t gcscaudio_round_buffersize(void *, int, size_t); static paddr_t gcscaudio_mappage(void *, void *, off_t, int); static int gcscaudio_get_props(void *); static int gcscaudio_trigger_output(void *, void *, void *, int, void (*)(void *), void *, const audio_params_t *); static int gcscaudio_trigger_input(void *, void *, void *, int, void (*)(void *), void *, const audio_params_t *); static bool gcscaudio_resume(device_t PMF_FN_PROTO); static int gcscaudio_intr(void *); /* for codec_if */ static int gcscaudio_attach_codec(void *, struct ac97_codec_if *); static int gcscaudio_write_codec(void *, uint8_t, uint16_t); static int gcscaudio_read_codec(void *, uint8_t, uint16_t *); static int gcscaudio_reset_codec(void *); static void gcscaudio_spdif_event_codec(void *, bool); /* misc */ static int gcscaudio_append_formats(struct gcscaudio_softc *, const struct audio_format *); static int gcscaudio_wait_ready_codec(struct gcscaudio_softc *sc); static int gcscaudio_set_params_ch(struct gcscaudio_softc *, struct gcscaudio_softc_ch *, int, audio_params_t *, stream_filter_list_t *); static int gcscaudio_allocate_dma(struct gcscaudio_softc *, size_t, void **, bus_dma_segment_t *, int, int *, int, bus_dmamap_t *); CFATTACH_DECL(gcscaudio, sizeof (struct gcscaudio_softc), gcscaudio_match, gcscaudio_attach, NULL, NULL); static struct audio_device gcscaudio_device = { "AMD Geode CS5536", "", "gcscaudio" }; static const struct audio_hw_if gcscaudio_hw_if = { .open = gcscaudio_open, .close = gcscaudio_close, .drain = NULL, .query_encoding = gcscaudio_query_encoding, .set_params = gcscaudio_set_params, .round_blocksize = gcscaudio_round_blocksize, .commit_settings = NULL, .init_output = NULL, .init_input = NULL, .start_output = NULL, .start_input = NULL, .halt_output = gcscaudio_halt_output, .halt_input = gcscaudio_halt_input, .speaker_ctl = NULL, .getdev = gcscaudio_getdev, .setfd = NULL, .set_port = gcscaudio_set_port, .get_port = gcscaudio_get_port, .query_devinfo = gcscaudio_query_devinfo, .allocm = gcscaudio_malloc, .freem = gcscaudio_free, .round_buffersize = gcscaudio_round_buffersize, .mappage = gcscaudio_mappage, .get_props = gcscaudio_get_props, .trigger_output = gcscaudio_trigger_output, .trigger_input = gcscaudio_trigger_input, .dev_ioctl = NULL, .powerstate = NULL }; static const struct audio_format gcscaudio_formats_2ch = { NULL, AUMODE_PLAY | AUMODE_RECORD, AUDIO_ENCODING_SLINEAR_LE, 16, 16, 2, AUFMT_STEREO, 0, {8000, 48000} }; static const struct audio_format gcscaudio_formats_4ch = { NULL, AUMODE_PLAY, AUDIO_ENCODING_SLINEAR_LE, 16, 16, 4, AUFMT_SURROUND4, 0, {8000, 48000} }; static const struct audio_format gcscaudio_formats_6ch = { NULL, AUMODE_PLAY, AUDIO_ENCODING_SLINEAR_LE, 16, 16, 6, AUFMT_DOLBY_5_1, 0, {8000, 48000} }; static int gcscaudio_match(device_t parent, struct cfdata *match, void *aux) { struct pci_attach_args *pa; pa = (struct pci_attach_args *)aux; if ((PCI_VENDOR(pa->pa_id) == PCI_VENDOR_AMD) && (PCI_PRODUCT(pa->pa_id) == PCI_PRODUCT_AMD_CS5536_AUDIO)) return 1; return 0; } static int gcscaudio_append_formats(struct gcscaudio_softc *sc, const struct audio_format *format) { if (sc->sc_nformats >= GCSCAUDIO_MAXFORMATS) { aprint_error_dev(&sc->sc_dev, "too many formats\n"); return EINVAL; } sc->sc_formats[sc->sc_nformats++] = *format; return 0; } static void gcscaudio_attach(device_t parent, device_t self, void *aux) { struct gcscaudio_softc *sc; struct pci_attach_args *pa; const char *intrstr; pci_intr_handle_t ih; int rc, i; sc = device_private(self); aprint_naive(": Audio controller\n"); pa = aux; sc->sc_pc = pa->pa_pc; sc->sc_pt = pa->pa_tag; sc->sc_dmat = pa->pa_dmat; LIST_INIT(&sc->sc_dmalist); aprint_normal(": AMD Geode CS5536 Audio\n"); if (pci_mapreg_map(pa, PCI_MAPREG_START, PCI_MAPREG_TYPE_IO, 0, &sc->sc_iot, &sc->sc_ioh, NULL, &sc->sc_ios)) { aprint_error_dev(&sc->sc_dev, "can't map i/o space\n"); return; } if (pci_intr_map(pa, &ih)) { aprint_error_dev(&sc->sc_dev, "couldn't map interrupt\n"); goto attach_failure_unmap; } intrstr = pci_intr_string(sc->sc_pc, ih); sc->sc_ih = pci_intr_establish(sc->sc_pc, ih, IPL_AUDIO, gcscaudio_intr, sc); if (sc->sc_ih == NULL) { aprint_error_dev(&sc->sc_dev, "couldn't establish interrupt"); if (intrstr != NULL) aprint_normal(" at %s", intrstr); aprint_normal("\n"); goto attach_failure_unmap; } aprint_normal_dev(&sc->sc_dev, "interrupting at %s\n", intrstr); if ((rc = gcscaudio_allocate_dma(sc, sizeof(struct gcscaudio_prdtables), (void **)&sc->sc_play.ch_prdtables, sc->sc_play.ch_prdsegs, 1, &sc->sc_play.ch_prdnseg, M_WAITOK, &sc->sc_play.ch_prdmap)) != 0) { goto attach_failure_intr; } if ((rc = gcscaudio_allocate_dma(sc, sizeof(struct gcscaudio_prdtables), (void **)&sc->sc_rec.ch_prdtables, sc->sc_rec.ch_prdsegs, 1, &sc->sc_rec.ch_prdnseg, M_WAITOK, &sc->sc_rec.ch_prdmap)) != 0) { goto attach_failure_intr; } sc->host_if.arg = sc; sc->host_if.attach = gcscaudio_attach_codec; sc->host_if.read = gcscaudio_read_codec; sc->host_if.write = gcscaudio_write_codec; sc->host_if.reset = gcscaudio_reset_codec; sc->host_if.spdif_event = gcscaudio_spdif_event_codec; if ((rc = ac97_attach(&sc->host_if, self)) != 0) { aprint_error_dev(&sc->sc_dev, "can't attach codec (error=%d)\n", rc); goto attach_failure_intr; } if (!pmf_device_register(self, NULL, gcscaudio_resume)) aprint_error_dev(self, "couldn't establish power handler\n"); sc->sc_nformats = 0; gcscaudio_append_formats(sc, &gcscaudio_formats_2ch); if (AC97_IS_4CH(sc->codec_if)) gcscaudio_append_formats(sc, &gcscaudio_formats_4ch); if (AC97_IS_6CH(sc->codec_if)) gcscaudio_append_formats(sc, &gcscaudio_formats_6ch); if (AC97_IS_FIXED_RATE(sc->codec_if)) { for (i = 0; i < sc->sc_nformats; i++) { sc->sc_formats[i].frequency_type = 1; sc->sc_formats[i].frequency[0] = 48000; } } if ((rc = auconv_create_encodings(sc->sc_formats, sc->sc_nformats, &sc->sc_encodings)) != 0) { aprint_error_dev(self, "auconv_create_encoding: error=%d\n", rc); goto attach_failure_codec; } audio_attach_mi(&gcscaudio_hw_if, sc, &sc->sc_dev); sc->codec_if->vtbl->unlock(sc->codec_if); return; attach_failure_codec: sc->codec_if->vtbl->detach(sc->codec_if); attach_failure_intr: pci_intr_disestablish(sc->sc_pc, sc->sc_ih); attach_failure_unmap: bus_space_unmap(sc->sc_iot, sc->sc_ioh, sc->sc_ios); return; } static int gcscaudio_attach_codec(void *arg, struct ac97_codec_if *codec_if) { struct gcscaudio_softc *sc; sc = (struct gcscaudio_softc *)arg; sc->codec_if = codec_if; return 0; } static int gcscaudio_reset_codec(void *arg) { struct gcscaudio_softc *sc; sc = (struct gcscaudio_softc *)arg; /* XXX: not yet */ return 0; } static void gcscaudio_spdif_event_codec(void *arg, bool flag) { struct gcscaudio_softc *sc; sc = (struct gcscaudio_softc *)arg; sc->sc_spdif = flag; } static int gcscaudio_wait_ready_codec(struct gcscaudio_softc *sc) { int i; #define GCSCAUDIO_WAIT_READY_CODEC_TIMEOUT 500 for (i = GCSCAUDIO_WAIT_READY_CODEC_TIMEOUT; (i >= 0) && (bus_space_read_4(sc->sc_iot, sc->sc_ioh, ACC_CODEC_CNTL) & ACC_CODEC_CNTL_CMD_NEW); i--) delay(1); if (i < 0) { aprint_error_dev(&sc->sc_dev, "codec busy timeout\n"); return 1; } return 0; } static int gcscaudio_write_codec(void *arg, uint8_t reg, uint16_t val) { struct gcscaudio_softc *sc; sc = (struct gcscaudio_softc *)arg; bus_space_write_4(sc->sc_iot, sc->sc_ioh, ACC_CODEC_CNTL, ACC_CODEC_CNTL_WRITE_CMD | ACC_CODEC_CNTL_CMD_NEW | ACC_CODEC_REG2ADDR(reg) | (val & ACC_CODEC_CNTL_CMD_DATA_MASK)); if (gcscaudio_wait_ready_codec(sc)) return 1; #if 0 /* DEBUG */ aprint_error_dev(&sc->sc_dev, "codec write: reg=0x%02x, val=0x%04x\n", reg, val); #endif return 0; } static int gcscaudio_read_codec(void *arg, uint8_t reg, uint16_t *val) { struct gcscaudio_softc *sc; uint32_t v; int i; sc = (struct gcscaudio_softc *)arg; bus_space_write_4(sc->sc_iot, sc->sc_ioh, ACC_CODEC_CNTL, ACC_CODEC_CNTL_READ_CMD | ACC_CODEC_CNTL_CMD_NEW | ACC_CODEC_REG2ADDR(reg)); if (gcscaudio_wait_ready_codec(sc)) return 1; #define GCSCAUDIO_READ_CODEC_TIMEOUT 50 for (i = GCSCAUDIO_READ_CODEC_TIMEOUT; i >= 0; i--) { v = bus_space_read_4(sc->sc_iot, sc->sc_ioh, ACC_CODEC_STATUS); if ((v & ACC_CODEC_STATUS_STS_NEW) && (ACC_CODEC_ADDR2REG(v) == reg)) break; delay(10); } if (i < 0) { aprint_error_dev(&sc->sc_dev, "codec timeout\n"); return 1; } #if 0 /* DEBUG */ aprint_error_dev(&sc->sc_dev, "codec read: reg=0x%02x, val=0x%04x\n", reg, v & ACC_CODEC_STATUS_STS_DATA_MASK); #endif *val = v; return 0; } static int gcscaudio_open(void *arg, int flags) { struct gcscaudio_softc *sc; sc = (struct gcscaudio_softc *)arg; sc->codec_if->vtbl->lock(sc->codec_if); return 0; } static void gcscaudio_close(void *arg) { struct gcscaudio_softc *sc; sc = (struct gcscaudio_softc *)arg; sc->codec_if->vtbl->unlock(sc->codec_if); } static int gcscaudio_query_encoding(void *arg, struct audio_encoding *fp) { struct gcscaudio_softc *sc; sc = (struct gcscaudio_softc *)arg; return auconv_query_encoding(sc->sc_encodings, fp); } static int gcscaudio_set_params_ch(struct gcscaudio_softc *sc, struct gcscaudio_softc_ch *ch, int mode, audio_params_t *p, stream_filter_list_t *fil) { int error, idx; if ((p->sample_rate < 8000) || (p->sample_rate > 48000)) return EINVAL; if (p->precision != 8 && p->precision != 16) return EINVAL; if ((idx = auconv_set_converter(sc->sc_formats, sc->sc_nformats, mode, p, TRUE, fil)) < 0) return EINVAL; if (fil->req_size > 0) p = &fil->filters[0].param; if (mode == AUMODE_PLAY) { if (!AC97_IS_FIXED_RATE(sc->codec_if)) { /* setup rate of DAC/ADC */ if ((error = sc->codec_if->vtbl->set_rate(sc->codec_if, AC97_REG_PCM_LR_ADC_RATE, &p->sample_rate)) != 0) return error; /* additional rate of DAC for Surround */ if ((p->channels >= 4) && (error = sc->codec_if->vtbl->set_rate(sc->codec_if, AC97_REG_PCM_SURR_DAC_RATE, &p->sample_rate)) != 0) return error; /* additional rate of DAC for LowFrequencyEffect */ if ((p->channels == 6) && (error = sc->codec_if->vtbl->set_rate(sc->codec_if, AC97_REG_PCM_LFE_DAC_RATE, &p->sample_rate)) != 0) return error; } /* * rearrange ordered channel to continuous per channel * * 2ch: L,R,L,R,L,R,L,R,L,R,... => BM0: L,R,L,R,L,R,L,R,L,R,... * BM6: (same of BM0) * BM4: none * BM7: none * * 4ch: L,R,SL,SR,L,R,SL,SR,... => BM0: L,R,L,R,... * BM6: SL,SR,SL,SR,... * BM4: none * BM7: none * * 5.1ch: L,C,R,SL,SR,LFE,... => BM0: L,R,... * BM6: SL,SR,... * BM4: C,... * BM7: LFE,... */ } if (mode == AUMODE_RECORD) { if (!AC97_IS_FIXED_RATE(sc->codec_if)) { /* setup rate of DAC/ADC */ if ((error = sc->codec_if->vtbl->set_rate(sc->codec_if, AC97_REG_PCM_FRONT_DAC_RATE, &p->sample_rate)) != 0) return error; } } ch->ch_params = *p; return 0; } static int gcscaudio_set_params(void *arg, int setmode, int usemode, audio_params_t *play, audio_params_t *rec, stream_filter_list_t *pfil, stream_filter_list_t *rfil) { struct gcscaudio_softc *sc; int error; sc = (struct gcscaudio_softc *)arg; if (setmode & AUMODE_PLAY) { if ((error = gcscaudio_set_params_ch(sc, &sc->sc_play, AUMODE_PLAY, play, pfil)) != 0) return error; } if (setmode & AUMODE_RECORD) { if ((error = gcscaudio_set_params_ch(sc, &sc->sc_rec, AUMODE_RECORD, rec, rfil)) != 0) return error; } return 0; } static int gcscaudio_round_blocksize(void *arg, int blk, int mode, const audio_params_t *param) { blk &= -4; if (blk > GCSCAUDIO_PRD_SIZE_MAX) blk = GCSCAUDIO_PRD_SIZE_MAX; return blk; } static int gcscaudio_halt_output(void *arg) { struct gcscaudio_softc *sc; sc = (struct gcscaudio_softc *)arg; bus_space_write_4(sc->sc_iot, sc->sc_ioh, ACC_BM0_CMD, ACC_BMx_CMD_BM_CTL_DISABLE); bus_space_write_4(sc->sc_iot, sc->sc_ioh, ACC_BM6_CMD, ACC_BMx_CMD_BM_CTL_DISABLE); sc->sc_play.ch_intr = NULL; return 0; } static int gcscaudio_halt_input(void *arg) { struct gcscaudio_softc *sc; sc = (struct gcscaudio_softc *)arg; bus_space_write_4(sc->sc_iot, sc->sc_ioh, ACC_BM1_CMD, ACC_BMx_CMD_BM_CTL_DISABLE); sc->sc_rec.ch_intr = NULL; return 0; } static int gcscaudio_getdev(void *addr, struct audio_device *retp) { *retp = gcscaudio_device; return 0; } static int gcscaudio_set_port(void *addr, mixer_ctrl_t *cp) { struct gcscaudio_softc *sc; sc = addr; return sc->codec_if->vtbl->mixer_set_port(sc->codec_if, cp); } static int gcscaudio_get_port(void *addr, mixer_ctrl_t *cp) { struct gcscaudio_softc *sc; sc = addr; return sc->codec_if->vtbl->mixer_get_port(sc->codec_if, cp); } static int gcscaudio_query_devinfo(void *addr, mixer_devinfo_t *dip) { struct gcscaudio_softc *sc; sc = addr; return sc->codec_if->vtbl->query_devinfo(sc->codec_if, dip); } static void * gcscaudio_malloc(void *arg, int direction, size_t size, struct malloc_type *pool, int flags) { struct gcscaudio_softc *sc; struct gcscaudio_dma *p; int error; sc = (struct gcscaudio_softc *)arg; p = malloc(sizeof(*p), pool, flags); if (p == NULL) return NULL; p->size = size; error = gcscaudio_allocate_dma(sc, size, &p->addr, p->segs, sizeof(p->segs)/sizeof(p->segs[0]), &p->nseg, BUS_DMA_NOWAIT, &p->map); if (error) { free(p, pool); return NULL; } LIST_INSERT_HEAD(&sc->sc_dmalist, p, list); return p->addr; } static void gcscaudio_free(void *arg, void *ptr, struct malloc_type *pool) { struct gcscaudio_softc *sc; struct gcscaudio_dma *p; sc = (struct gcscaudio_softc *)arg; LIST_FOREACH(p, &sc->sc_dmalist, list) { if (p->addr == ptr) { 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->nseg); LIST_REMOVE(p, list); free(p, pool); break; } } } static paddr_t gcscaudio_mappage(void *arg, void *mem, off_t off, int prot) { struct gcscaudio_softc *sc; struct gcscaudio_dma *p; if (off < 0) return -1; sc = (struct gcscaudio_softc *)arg; LIST_FOREACH(p, &sc->sc_dmalist, list) { if (p->addr == mem) { return bus_dmamem_mmap(sc->sc_dmat, p->segs, p->nseg, off, prot, BUS_DMA_WAITOK); } } return -1; } static size_t gcscaudio_round_buffersize(void *addr, int direction, size_t size) { if (size > GCSCAUDIO_BUFSIZE_MAX) size = GCSCAUDIO_BUFSIZE_MAX; return size; } static int gcscaudio_get_props(void *addr) { struct gcscaudio_softc *sc; int props; props = AUDIO_PROP_INDEPENDENT | AUDIO_PROP_FULLDUPLEX; sc = addr; /* * Even if the codec is fixed-rate, set_param() succeeds for any sample * rate because of aurateconv. Applications can't know what rate the * device can process in the case of mmap(). */ if (!AC97_IS_FIXED_RATE(sc->codec_if)) props |= AUDIO_PROP_MMAP; return props; } static int build_prdtables(struct gcscaudio_softc *sc, struct gcscaudio_softc_ch *ch, void *addr, void *start, void *end, int blksize) { struct gcscaudio_dma *p; bus_addr_t paddr; int size; int i; /* get physical address of start */ paddr = (bus_addr_t)0; LIST_FOREACH(p, &sc->sc_dmalist, list) { if (p->addr == start) { paddr = p->map->dm_segs[0].ds_addr; break; } } if (!paddr) { aprint_error_dev(&sc->sc_dev, "gcscaudio_trigger_output: bad addr %p\n", start); return EINVAL; } #define PRDADDR(ch,idx) \ ((ch->ch_prdmap->dm_segs[0].ds_addr) + sizeof(struct acc_prd) * (idx)) /* * build PRD table * [PRD0]-[PRD1]-[PRD2]-...-[PRDn]-[jmp to PRD0] */ size = (char *)end - (char *)start; for (i = 0; size > 0; size -= blksize, i++) { ch->ch_prdtables->prd[i].address = paddr + blksize * i; ch->ch_prdtables->prd[i].ctrlsize = (size < blksize ? size : blksize) | ACC_BMx_PRD_CTRL_EOP; } ch->ch_prdtables->prd[i].address = PRDADDR(ch, 0); ch->ch_prdtables->prd[i].ctrlsize = ACC_BMx_PRD_CTRL_JMP; bus_dmamap_sync(sc->sc_dmat, ch->ch_prdmap, 0, sizeof(struct acc_prd) * i, BUS_DMASYNC_PREWRITE); return 0; } static int gcscaudio_trigger_output(void *addr, void *start, void *end, int blksize, void (*intr)(void *), void *arg, const audio_params_t *param) { struct gcscaudio_softc *sc; struct gcscaudio_softc_ch *ch; sc = (struct gcscaudio_softc *)addr; ch = &sc->sc_play; ch->ch_intr = intr; ch->ch_intr_arg = arg; if (build_prdtables(sc, ch, addr, start, end, blksize)) return EINVAL; switch (sc->sc_play.ch_params.channels) { case 2: if (!AC97_IS_4CH(sc->codec_if)) { /* * output 2ch PCM to FRONT.LR */ bus_space_write_4(sc->sc_iot, sc->sc_ioh, ACC_BM0_PRD, PRDADDR(ch, 0)); /* start DMA transfer */ bus_space_write_1(sc->sc_iot, sc->sc_ioh, ACC_BM0_CMD, ACC_BMx_CMD_WRITE | ACC_BMx_CMD_BYTE_ORD_EL | ACC_BMx_CMD_BM_CTL_ENABLE); } else { /* * output 2ch PCM to FRONT.LR & SURROUND.LR (same data) * CENTER and LFE doesn't sound */ bus_space_write_4(sc->sc_iot, sc->sc_ioh, ACC_BM0_PRD, PRDADDR(ch, 0)); bus_space_write_4(sc->sc_iot, sc->sc_ioh, ACC_BM6_PRD, PRDADDR(ch, 0)); /* start DMA transfer */ bus_space_write_1(sc->sc_iot, sc->sc_ioh, ACC_BM0_CMD, ACC_BMx_CMD_WRITE | ACC_BMx_CMD_BYTE_ORD_EL | ACC_BMx_CMD_BM_CTL_ENABLE); bus_space_write_1(sc->sc_iot, sc->sc_ioh, ACC_BM6_CMD, ACC_BMx_CMD_WRITE | ACC_BMx_CMD_BYTE_ORD_EL | ACC_BMx_CMD_BM_CTL_ENABLE); } break; case 4: /* XXX: notyet 4ch */ break; case 6: /* XXX: notyet 6ch */ break; } return 0; } static int gcscaudio_trigger_input(void *addr, void *start, void *end, int blksize, void (*intr)(void *), void *arg, const audio_params_t *param) { struct gcscaudio_softc *sc; struct gcscaudio_softc_ch *ch; sc = (struct gcscaudio_softc *)addr; ch = &sc->sc_rec; ch->ch_intr = intr; ch->ch_intr_arg = arg; if (build_prdtables(sc, ch, addr, start, end, blksize)) return EINVAL; bus_space_write_4(sc->sc_iot, sc->sc_ioh, ACC_BM1_PRD, PRDADDR(ch, 0)); /* start transfer */ bus_space_write_1(sc->sc_iot, sc->sc_ioh, ACC_BM1_CMD, ACC_BMx_CMD_READ | ACC_BMx_CMD_BYTE_ORD_EL | ACC_BMx_CMD_BM_CTL_ENABLE); return 0; } static int gcscaudio_intr(void *arg) { struct gcscaudio_softc *sc; uint16_t intr; uint8_t bmstat; int nintr; nintr = 0; sc = (struct gcscaudio_softc *)arg; intr = bus_space_read_2(sc->sc_iot, sc->sc_ioh, ACC_IRQ_STATUS); if (intr == 0) return 0; /* Front output */ if (intr & ACC_IRQ_STATUS_BM0_IRQ_STS) { bmstat = bus_space_read_1(sc->sc_iot, sc->sc_ioh, ACC_BM0_STATUS); if (bmstat & ACC_BMx_STATUS_BM_EOP_ERR) aprint_normal_dev(&sc->sc_dev, "BM0: Bus Master Error\n"); if (!(bmstat & ACC_BMx_STATUS_EOP)) aprint_normal_dev(&sc->sc_dev, "BM0: NO End of Page?\n"); if (sc->sc_play.ch_intr) { sc->sc_play.ch_intr(sc->sc_play.ch_intr_arg); } nintr++; } /* surround output */ if (intr & ACC_IRQ_STATUS_BM6_IRQ_STS) { bmstat = bus_space_read_1(sc->sc_iot, sc->sc_ioh, ACC_BM6_STATUS); if (bmstat & ACC_BMx_STATUS_BM_EOP_ERR) aprint_normal_dev(&sc->sc_dev, "BM6: Bus Master Error\n"); if (!(bmstat & ACC_BMx_STATUS_EOP)) aprint_normal_dev(&sc->sc_dev, "BM6: NO End of Page?\n"); nintr++; } /* record */ if (intr & ACC_IRQ_STATUS_BM1_IRQ_STS) { bmstat = bus_space_read_1(sc->sc_iot, sc->sc_ioh, ACC_BM1_STATUS); if (bmstat & ACC_BMx_STATUS_BM_EOP_ERR) aprint_normal_dev(&sc->sc_dev, "BM1: Bus Master Error\n"); if (!(bmstat & ACC_BMx_STATUS_EOP)) aprint_normal_dev(&sc->sc_dev, "BM1: NO End of Page?\n"); if (sc->sc_rec.ch_intr) { sc->sc_rec.ch_intr(sc->sc_rec.ch_intr_arg); } nintr++; } #if 1 /* XXX: DEBUG */ if (intr & ACC_IRQ_STATUS_IRQ_STS) aprint_normal_dev(&sc->sc_dev, "Codec GPIO IRQ Status\n"); if (intr & ACC_IRQ_STATUS_WU_IRQ_STS) aprint_normal_dev(&sc->sc_dev, "Codec GPIO Wakeup IRQ Status\n"); if (intr & ACC_IRQ_STATUS_BM2_IRQ_STS) aprint_normal_dev(&sc->sc_dev, "Audio Bus Master 2 IRQ Status\n"); if (intr & ACC_IRQ_STATUS_BM3_IRQ_STS) aprint_normal_dev(&sc->sc_dev, "Audio Bus Master 3 IRQ Status\n"); if (intr & ACC_IRQ_STATUS_BM4_IRQ_STS) aprint_normal_dev(&sc->sc_dev, "Audio Bus Master 4 IRQ Status\n"); if (intr & ACC_IRQ_STATUS_BM5_IRQ_STS) aprint_normal_dev(&sc->sc_dev, "Audio Bus Master 5 IRQ Status\n"); if (intr & ACC_IRQ_STATUS_BM7_IRQ_STS) aprint_normal_dev(&sc->sc_dev, "Audio Bus Master 7 IRQ Status\n"); #endif return nintr; } static bool gcscaudio_resume(device_t dv PMF_FN_ARGS) { struct gcscaudio_softc *sc = device_private(dv); gcscaudio_reset_codec(sc); DELAY(1000); (sc->codec_if->vtbl->restore_ports)(sc->codec_if); return true; } static int gcscaudio_allocate_dma(struct gcscaudio_softc *sc, size_t size, void **addrp, bus_dma_segment_t *seglist, int nseg, int *rsegp, int flags, bus_dmamap_t *mapp) { int error; if ((error = bus_dmamem_alloc(sc->sc_dmat, size, PAGE_SIZE, 0, seglist, nseg, rsegp, flags)) != 0) { aprint_error_dev(&sc->sc_dev, "unable to allocate DMA buffer, error=%d\n", error); goto fail_alloc; } if ((error = bus_dmamem_map(sc->sc_dmat, seglist, nseg, size, addrp, BUS_DMA_NOWAIT | BUS_DMA_COHERENT)) != 0) { aprint_error_dev(&sc->sc_dev, "unable to map DMA buffer, error=%d\n", error); goto fail_map; } if ((error = bus_dmamap_create(sc->sc_dmat, size, nseg, size, 0, BUS_DMA_NOWAIT, mapp)) != 0) { aprint_error_dev(&sc->sc_dev, "unable to create DMA map, error=%d\n", error); goto fail_create; } if ((error = bus_dmamap_load(sc->sc_dmat, *mapp, *addrp, size, NULL, BUS_DMA_NOWAIT)) != 0) { aprint_error_dev(&sc->sc_dev, "unable to load DMA map, error=%d\n", error); goto fail_load; } return 0; fail_load: bus_dmamap_destroy(sc->sc_dmat, *mapp); fail_create: bus_dmamem_unmap(sc->sc_dmat, *addrp, size); fail_map: bus_dmamem_free(sc->sc_dmat, seglist, nseg); fail_alloc: return error; }