Module Name: src Committed By: snj Date: Fri May 1 01:19:09 UTC 2009
Modified Files: src/sys/dev/pci [netbsd-5]: files.pci Added Files: src/sys/dev/pci [netbsd-5]: gcscaudio.c gcscaudioreg.h Log Message: Pull up following revision(s) (requested by jmcneill in ticket #260): sys/dev/pci/files.pci: revision 1.309 sys/dev/pci/gcscaudio.c: revision 1.1 sys/dev/pci/gcscaudioreg.h: revision 1.1 PR# port-i386/40284: add AMD Geode CS5536 audio driver; written by SHIMIZU Ryo <r...@nerv.org> To generate a diff of this commit: cvs rdiff -u -r1.308 -r1.308.2.1 src/sys/dev/pci/files.pci cvs rdiff -u -r0 -r1.1.8.2 src/sys/dev/pci/gcscaudio.c \ src/sys/dev/pci/gcscaudioreg.h Please note that diffs are not public domain; they are subject to the copyright notices on the relevant files.
Modified files: Index: src/sys/dev/pci/files.pci diff -u src/sys/dev/pci/files.pci:1.308 src/sys/dev/pci/files.pci:1.308.2.1 --- src/sys/dev/pci/files.pci:1.308 Thu Oct 30 12:02:14 2008 +++ src/sys/dev/pci/files.pci Fri May 1 01:19:09 2009 @@ -1,4 +1,4 @@ -# $NetBSD: files.pci,v 1.308 2008/10/30 12:02:14 darran Exp $ +# $NetBSD: files.pci,v 1.308.2.1 2009/05/01 01:19:09 snj Exp $ # # Config file and device description for machine-independent PCI code. # Included by ports that need it. Requires that the SCSI files be @@ -481,6 +481,11 @@ file dev/pci/azalia.c azalia file dev/pci/azalia_codec.c azalia +# AMD Geode CS5536 Companion Audio +device gcscaudio: audiobus, auconv, mulaw, ac97, aurateconv +attach gcscaudio at pci +file dev/pci/gcscaudio.c gcscaudio + # NeoMagic 256 AC'97 Audio device neo: audiobus, auconv, mulaw, ac97 attach neo at pci Added files: Index: src/sys/dev/pci/gcscaudio.c diff -u /dev/null src/sys/dev/pci/gcscaudio.c:1.1.8.2 --- /dev/null Fri May 1 01:19:09 2009 +++ src/sys/dev/pci/gcscaudio.c Fri May 1 01:19:09 2009 @@ -0,0 +1,1339 @@ +/* $NetBSD: gcscaudio.c,v 1.1.8.2 2009/05/01 01:19:09 snj Exp $ */ + +/*- + * Copyright (c) 2008 SHIMIZU Ryo <r...@nerv.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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 <sys/cdefs.h> +__KERNEL_RCSID(0, "$NetBSD: gcscaudio.c,v 1.1.8.2 2009/05/01 01:19:09 snj Exp $"); + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/malloc.h> +#include <sys/device.h> +#include <sys/queue.h> + +#include <uvm/uvm_extern.h> + +#include <dev/pci/pcidevs.h> +#include <dev/pci/pcivar.h> + +#include <sys/audioio.h> +#include <dev/audio_if.h> +#include <dev/mulaw.h> +#include <dev/auconv.h> +#include <dev/ic/ac97reg.h> +#include <dev/ic/ac97var.h> + +#include <dev/pci/gcscaudioreg.h> + + +#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_prd { + /* PRD table for play/rec */ + struct gcscaudio_prdtables { +#define PRD_TABLE_FRONT 0 +#define PRD_TABLE_SURR 1 +#define PRD_TABLE_CENTER 2 +#define PRD_TABLE_LFE 3 +#define PRD_TABLE_REC 4 +#define PRD_TABLE_MAX 5 + struct acc_prd prdtbl[PRD_TABLE_MAX][GCSCAUDIO_NPRDTABLE]; + } *p_prdtables; + bus_dmamap_t p_prdmap; + bus_dma_segment_t p_prdsegs[1]; + int p_prdnseg; +}; + +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_ch { + void (*ch_intr)(void *); + void *ch_intr_arg; + struct audio_params ch_params; +}; + +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; + struct gcscaudio_prd sc_prd; + + /* multi channel splitter work; {4,6}ch stream to {2,4} DMA buffers */ + void *sc_mch_split_buf; + void *sc_mch_split_start; + int sc_mch_split_off; + int sc_mch_split_size; + int sc_mch_split_blksize; + void (*sc_mch_splitter)(void *, void *, int, int); + 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, const char *); +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); + sc->sc_mch_split_buf = NULL; + + 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 (gcscaudio_allocate_dma(sc, sizeof(*sc->sc_prd.p_prdtables), + (void **)&(sc->sc_prd.p_prdtables), sc->sc_prd.p_prdsegs, 1, + &(sc->sc_prd.p_prdnseg), M_WAITOK, &(sc->sc_prd.p_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; + + bus_space_write_4(sc->sc_iot, sc->sc_ioh, ACC_CODEC_CNTL, + ACC_CODEC_CNTL_LNK_WRM_RST | + ACC_CODEC_CNTL_CMD_NEW); + + if (gcscaudio_wait_ready_codec(sc, "reset timeout\n")) + return 1; + + 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, const char *timeout_msg) +{ + 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, timeout_msg); + 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, "codec write timeout\n")) + return 1; + +#ifdef GCSCAUDIO_CODEC_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, "codec write timeout for reading")) + 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 read timeout\n"); + return 1; + } + +#ifdef GCSCAUDIO_CODEC_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; + } + } + + 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_BM4_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); + bus_space_write_4(sc->sc_iot, sc->sc_ioh, ACC_BM7_CMD, + ACC_BMx_CMD_BM_CTL_DISABLE); + sc->sc_play.ch_intr = NULL; + + /* channel splitter */ + sc->sc_mch_splitter = NULL; + if (sc->sc_mch_split_buf) + gcscaudio_free(sc, sc->sc_mch_split_buf, M_DEVBUF); + sc->sc_mch_split_buf = 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; + + sc = (struct gcscaudio_softc *)addr; + props = AUDIO_PROP_INDEPENDENT | AUDIO_PROP_FULLDUPLEX; + /* + * 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, int prdidx, + void *addr, size_t size, int blksize, int blklen, int blkoff) +{ + struct gcscaudio_dma *p; + struct acc_prd *prdp; + bus_addr_t paddr; + int i; + + /* get physical address of start */ + paddr = (bus_addr_t)0; + LIST_FOREACH(p, &sc->sc_dmalist, list) { + if (p->addr == addr) { + paddr = p->map->dm_segs[0].ds_addr; + break; + } + } + if (!paddr) { + aprint_error_dev(&sc->sc_dev, + "bad addr %p\n", addr); + return EINVAL; + } + +#define PRDADDR(prdidx,idx) \ + (sc->sc_prd.p_prdmap->dm_segs[0].ds_addr) + sizeof(struct acc_prd) * \ + (((prdidx) * GCSCAUDIO_NPRDTABLE) + (idx)) + + /* + * build PRD table + * prdtbl[] = <PRD0>, <PRD1>, <PRD2>, ..., <PRDn>, <jmp to PRD0> + */ + prdp = sc->sc_prd.p_prdtables->prdtbl[prdidx]; + for (i = 0; size > 0; size -= blksize, i++) { + prdp[i].address = paddr + blksize * i + blkoff; + prdp[i].ctrlsize = + (size < blklen ? size : blklen) | ACC_BMx_PRD_CTRL_EOP; + } + prdp[i].address = PRDADDR(prdidx, 0); + prdp[i].ctrlsize = ACC_BMx_PRD_CTRL_JMP; + + bus_dmamap_sync(sc->sc_dmat, sc->sc_prd.p_prdmap, 0, + sizeof(struct acc_prd) * i, BUS_DMASYNC_PREWRITE); + + return 0; +} + +static void +split_buffer_4ch(void *dst, void *src, int size, int blksize) +{ + int left, i; + uint16_t *s, *d; + + /* + * src[blk0]: L,R,SL,SR,L,R,SL,SR,L,R,SL,SR,.... + * src[blk1]: L,R,SL,SR,L,R,SL,SR,L,R,SL,SR,.... + * src[blk2]: L,R,SL,SR,L,R,SL,SR,L,R,SL,SR,.... + * : + * + * rearrange to + * + * src[blk0]: L,R,L,R,L,R,L,R,.. + * src[blk1]: L,R,L,R,L,R,L,R,.. + * src[blk2]: L,R,L,R,L,R,L,R,.. + * : + * dst[blk0]: SL,SR,SL,SR,SL,SR,SL,SR,.. + * dst[blk1]: SL,SR,SL,SR,SL,SR,SL,SR,.. + * dst[blk2]: SL,SR,SL,SR,SL,SR,SL,SR,.. + * : + */ + for (left = size; left > 0; left -= blksize) { + s = (uint16_t *)src; + d = (uint16_t *)dst; + for (i = 0; i < blksize / sizeof(uint16_t) / 4; i++) { + /* L,R,SL,SR -> SL,SR */ + s++; + s++; + *d++ = *s++; + *d++ = *s++; + } + + s = (uint16_t *)src; + d = (uint16_t *)src; + for (i = 0; i < blksize / sizeof(uint16_t) / 2 / 2; i++) { + /* L,R,SL,SR -> L,R */ + *d++ = *s++; + *d++ = *s++; + s++; + s++; + } + + src = (char *)src + blksize; + dst = (char *)dst + blksize; + } +} + +static void +split_buffer_6ch(void *dst, void *src, int size, int blksize) +{ + int left, i; + uint16_t *s, *d, *dc, *dl; + + /* + * by default, treat as WAV style 5.1ch order + * 5.1ch(WAV): L R C LFE SL SR + * 5.1ch(AAC): C L R SL SR LFE + * : + */ + + /* + * src[blk0]: L,R,C,LFE,SL,SR,L,R,C,LFE,SL,SR,... + * src[blk1]: L,R,C,LFE,SL,SR,L,R,C,LFE,SL,SR,... + * src[blk2]: L,R,C,LFE,SL,SR,L,R,C,LFE,SL,SR,... + * : + * src[N-1] : L,R,C,LFE,SL,SR,L,R,C,LFE,SL,SR,... + * + * rearrange to + * + * src[blk0]: L,R,L,R,.. + * src[blk1]: L,R,L,R,.. + * src[blk2]: L,R,L,R,.. + * : + * + * dst[blk0]: SL,SR,SL,SR,.. + * dst[blk1]: SL,SR,SL,SR,.. + * dst[blk2]: SL,SR,SL,SR,.. + * : + * + * dst[N/2+0]: C,C,C,.. + * dst[N/2+1]: C,C,C,.. + * : + * + * dst[N/2+N/4+0]: LFE,LFE,LFE,.. + * dst[N/2+N/4+1]: LFE,LFE,LFE,.. + * : + */ + + for (left = size; left > 0; left -= blksize) { + s = (uint16_t *)src; + d = (uint16_t *)dst; + dc = (uint16_t *)((char *)dst + blksize / 2); + dl = (uint16_t *)((char *)dst + blksize / 2 + blksize / 4); + for (i = 0; i < blksize / sizeof(uint16_t) / 6; i++) { +#ifdef GCSCAUDIO_5_1CH_AAC_ORDER + /* + * AAC: [C,L,R,SL,SR,LFE] + * => [SL,SR] + * => [C] + * => [LFE] + */ + *dc++ = s[0]; /* C */ + *dl++ = s[5]; /* LFE */ + *d++ = s[3]; /* SL */ + *d++ = s[4]; /* SR */ +#else + /* + * WAV: [L,R,C,LFE,SL,SR] + * => [SL,SR] + * => [C] + * => [LFE] + */ + *dc++ = s[2]; /* C */ + *dl++ = s[3]; /* LFE */ + *d++ = s[4]; /* SL */ + *d++ = s[5]; /* SR */ +#endif + s += 6; + } + + s = (uint16_t *)src; + d = (uint16_t *)src; + for (i = 0; i < blksize / sizeof(uint16_t) / 2 / 2; i++) { +#ifdef GCSCAUDIO_5_1CH_AAC_ORDER + /* AAC: [C,L,R,SL,SR,LFE] => [L,R] */ + *d++ = s[1]; + *d++ = s[2]; +#else + /* WAV: [L,R,C,LFE,SL,SR] => [L,R] */ + *d++ = s[0]; + *d++ = s[1]; +#endif + s += 6; + } + + src = (char *)src + blksize; + dst = (char *)dst + blksize; + } +} + +static void +channel_splitter(struct gcscaudio_softc *sc) +{ + int splitsize, left; + void *src, *dst; + + if (sc->sc_mch_splitter == NULL) + return; + + left = sc->sc_mch_split_size - sc->sc_mch_split_off; + splitsize = sc->sc_mch_split_blksize; + if (left < splitsize) + splitsize = left; + + src = (char *)sc->sc_mch_split_start + sc->sc_mch_split_off; + dst = (char *)sc->sc_mch_split_buf + sc->sc_mch_split_off; + + sc->sc_mch_splitter(dst, src, splitsize, sc->sc_mch_split_blksize); + + sc->sc_mch_split_off += sc->sc_mch_split_blksize; + if (sc->sc_mch_split_off >= sc->sc_mch_split_size) + sc->sc_mch_split_off = 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; + size_t size; + + sc = (struct gcscaudio_softc *)addr; + sc->sc_play.ch_intr = intr; + sc->sc_play.ch_intr_arg = arg; + size = (char *)end - (char *)start; + + switch (sc->sc_play.ch_params.channels) { + case 2: + if (build_prdtables(sc, PRD_TABLE_FRONT, start, size, blksize, + blksize, 0)) + return EINVAL; + + if (!AC97_IS_4CH(sc->codec_if)) { + /* + * output 2ch PCM to FRONT.LR(BM0) + * + * 2ch: L,R,L,R,L,R,L,R,... => BM0: L,R,L,R,L,R,L,R,... + * + */ + bus_space_write_4(sc->sc_iot, sc->sc_ioh, ACC_BM0_PRD, + PRDADDR(PRD_TABLE_FRONT, 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 same PCM to FRONT.LR(BM0) and SURROUND.LR(BM6). + * CENTER(BM4) and LFE(BM7) doesn't sound. + * + * 2ch: L,R,L,R,L,R,L,R,... => BM0: L,R,L,R,L,R,L,R,... + * BM6: (same of BM0) + * BM4: none + * BM7: none + */ + bus_space_write_4(sc->sc_iot, sc->sc_ioh, ACC_BM0_PRD, + PRDADDR(PRD_TABLE_FRONT, 0)); + bus_space_write_4(sc->sc_iot, sc->sc_ioh, ACC_BM6_PRD, + PRDADDR(PRD_TABLE_FRONT, 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: + /* + * output 4ch PCM split to FRONT.LR(BM0) and SURROUND.LR(BM6). + * CENTER(BM4) and LFE(BM7) doesn't sound. + * + * rearrange ordered channel to continuous per channel + * + * 4ch: L,R,SL,SR,L,R,SL,SR,... => BM0: L,R,L,R,... + * BM6: SL,SR,SL,SR,... + * BM4: none + * BM7: none + */ + if (sc->sc_mch_split_buf) + gcscaudio_free(sc, sc->sc_mch_split_buf, M_DEVBUF); + + if ((sc->sc_mch_split_buf = gcscaudio_malloc(sc, AUMODE_PLAY, + size, M_DEVBUF, M_WAITOK)) == NULL) + return ENOMEM; + + /* + * 1st and 2nd blocks are split immediately. + * Other blocks will be split synchronous with intr. + */ + split_buffer_4ch(sc->sc_mch_split_buf, start, blksize * 2, + blksize); + + sc->sc_mch_split_start = start; + sc->sc_mch_split_size = size; + sc->sc_mch_split_blksize = blksize; + sc->sc_mch_split_off = (blksize * 2) % size; + sc->sc_mch_splitter = split_buffer_4ch; /* split function */ + + if (build_prdtables(sc, PRD_TABLE_FRONT, start, size, blksize, + blksize / 2, 0)) + return EINVAL; + if (build_prdtables(sc, PRD_TABLE_SURR, sc->sc_mch_split_buf, + size, blksize, blksize / 2, 0)) + return EINVAL; + + bus_space_write_4(sc->sc_iot, sc->sc_ioh, ACC_BM0_PRD, + PRDADDR(PRD_TABLE_FRONT, 0)); + bus_space_write_4(sc->sc_iot, sc->sc_ioh, ACC_BM6_PRD, + PRDADDR(PRD_TABLE_SURR, 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 6: + /* + * output 6ch PCM split to + * FRONT.LR(BM0), SURROUND.LR(BM6), CENTER(BM4) and LFE(BM7) + * + * rearrange ordered channel to continuous per channel + * + * 5.1ch: L,R,C,LFE,SL,SR,... => BM0: L,R,... + * BM4: C,... + * BM6: SL,SR,... + * BM7: LFE,... + * + */ + if (sc->sc_mch_split_buf) + gcscaudio_free(sc, sc->sc_mch_split_buf, M_DEVBUF); + + if ((sc->sc_mch_split_buf = gcscaudio_malloc(sc, AUMODE_PLAY, + size, M_DEVBUF, M_WAITOK)) == NULL) + return ENOMEM; + + /* + * 1st and 2nd blocks are split immediately. + * Other block will be split synchronous with intr. + */ + split_buffer_6ch(sc->sc_mch_split_buf, start, blksize * 2, + blksize); + + sc->sc_mch_split_start = start; + sc->sc_mch_split_size = size; + sc->sc_mch_split_blksize = blksize; + sc->sc_mch_split_off = (blksize * 2) % size; + sc->sc_mch_splitter = split_buffer_6ch; /* split function */ + + if (build_prdtables(sc, PRD_TABLE_FRONT, start, size, blksize, + blksize / 3, 0)) + return EINVAL; + if (build_prdtables(sc, PRD_TABLE_CENTER, sc->sc_mch_split_buf, + size, blksize, blksize / 3, blksize / 2)) + return EINVAL; + if (build_prdtables(sc, PRD_TABLE_SURR, sc->sc_mch_split_buf, + size, blksize, blksize / 3, 0)) + return EINVAL; + if (build_prdtables(sc, PRD_TABLE_LFE, sc->sc_mch_split_buf, + size, blksize, blksize / 3, blksize / 2 + blksize / 4)) + return EINVAL; + + bus_space_write_4(sc->sc_iot, sc->sc_ioh, ACC_BM0_PRD, + PRDADDR(PRD_TABLE_FRONT, 0)); + bus_space_write_4(sc->sc_iot, sc->sc_ioh, ACC_BM4_PRD, + PRDADDR(PRD_TABLE_CENTER, 0)); + bus_space_write_4(sc->sc_iot, sc->sc_ioh, ACC_BM6_PRD, + PRDADDR(PRD_TABLE_SURR, 0)); + bus_space_write_4(sc->sc_iot, sc->sc_ioh, ACC_BM7_PRD, + PRDADDR(PRD_TABLE_LFE, 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_BM4_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); + bus_space_write_1(sc->sc_iot, sc->sc_ioh, ACC_BM7_CMD, + ACC_BMx_CMD_WRITE | ACC_BMx_CMD_BYTE_ORD_EL | + ACC_BMx_CMD_BM_CTL_ENABLE); + 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; + size_t size; + + sc = (struct gcscaudio_softc *)addr; + sc->sc_rec.ch_intr = intr; + sc->sc_rec.ch_intr_arg = arg; + size = (char *)end - (char *)start; + + if (build_prdtables(sc, PRD_TABLE_REC, start, size, blksize, blksize, 0)) + return EINVAL; + + bus_space_write_4(sc->sc_iot, sc->sc_ioh, ACC_BM1_PRD, + PRDADDR(PRD_TABLE_REC, 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); + channel_splitter(sc); + } + nintr++; + } + + /* Center output */ + if (intr & ACC_IRQ_STATUS_BM4_IRQ_STS) { + bmstat = bus_space_read_1(sc->sc_iot, sc->sc_ioh, ACC_BM4_STATUS); + if (bmstat & ACC_BMx_STATUS_BM_EOP_ERR) + aprint_normal_dev(&sc->sc_dev, "BM4: Bus Master Error\n"); + if (!(bmstat & ACC_BMx_STATUS_EOP)) + aprint_normal_dev(&sc->sc_dev, "BM4: NO End of Page?\n"); + + 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++; + } + + /* LowFrequencyEffect output */ + if (intr & ACC_IRQ_STATUS_BM7_IRQ_STS) { + bmstat = bus_space_read_1(sc->sc_iot, sc->sc_ioh, ACC_BM7_STATUS); + if (bmstat & ACC_BMx_STATUS_BM_EOP_ERR) + aprint_normal_dev(&sc->sc_dev, "BM7: Bus Master Error\n"); + if (!(bmstat & ACC_BMx_STATUS_EOP)) + aprint_normal_dev(&sc->sc_dev, "BM7: 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++; + } + +#ifdef GCSCAUDIO_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_BM5_IRQ_STS) + aprint_normal_dev(&sc->sc_dev, "Audio Bus Master 5 IRQ Status\n"); +#endif + + return nintr ? 1 : 0; +} + +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; +} Index: src/sys/dev/pci/gcscaudioreg.h diff -u /dev/null src/sys/dev/pci/gcscaudioreg.h:1.1.8.2 --- /dev/null Fri May 1 01:19:10 2009 +++ src/sys/dev/pci/gcscaudioreg.h Fri May 1 01:19:09 2009 @@ -0,0 +1,151 @@ +/* $NetBSD: gcscaudioreg.h,v 1.1.8.2 2009/05/01 01:19:09 snj Exp $ */ + +/*- + * Copyright (c) 2008 SHIMIZU Ryo <r...@nerv.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. 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 _I386_PCI_GCSCAUDIOREG_H_ +#define _I386_PCI_GCSCAUDIOREG_H_ + +/* + * Reference: + * - AMD Geode CS5536 Companion Device Data Book + * http://www.amd.com/files/connectivitysolutions/geode/geode_lx/33238G_cs5536_db.pdf + */ + +#define ACC_GLD_MSR_CAP 0x51500000 /* GeodeLinkDevice Capabilities */ + +/* + * AC97 Audio Codec Controller (ACC) Registers + */ +#define ACC_GPIO_STATUS 0x00 /* Codec GPIO Status Register */ +# define ACC_GPIO_STATUS_GPIO_EN 0x80000000 /* GPIO Enable */ +# define ACC_GPIO_STATUS_INT_EN 0x40000000 /* Codec GPIO Interrupt Enable */ +# define ACC_GPIO_STATUS_WU_INT_EN 0x20000000 /* Codec GPIO Wakeup Interrupt Enable */ +# define ACC_GPIO_STATUS_INT_FLAG 0x00200000 /* Codec GPIO Interrupt Flag (Read to Clear) */ +# define ACC_GPIO_STATUS_WU_INT_FLAG 0x00100000 /* Codec GPIO Wakeup Interrupt Flag (Read to Clear) */ +# define ACC_GPIO_STATUS_PIN_STS_MASK 0x000fffff /* Codec GPIO Pin Status (Read Only) */ + +#define ACC_GPIO_CNTL 0x04 /* Codec GPIO Control Register */ +# define ACC_GPIO_CNTL_PIN_DATA_MASK 0x000fffff /* Codec GPIO Pin Data */ + +#define ACC_CODEC_REG2ADDR(reg) (((reg) & 0x7f) << 24) +#define ACC_CODEC_ADDR2REG(adr) (((adr) >> 24) & 0x7f) + +#define ACC_CODEC_STATUS 0x08 /* Codec Status Register */ +# define ACC_CODEC_STATUS_STS_ADD_MASK 0xff000000 /* Codec Status Address (Read Only) */ +# define ACC_CODEC_STATUS_PRM_RDY_STS 0x00800000 /* Primary Codec Ready (Read Only) */ +# define ACC_CODEC_STATUS_SEC_RDY_STS 0x00400000 /* Secondary Codec Ready (Read Only) */ +# define ACC_CODEC_STATUS_SDATAIN2_EN 0x00200000 /* Enable Second Serial Data Input (AC_S_IN2) */ +# define ACC_CODEC_STATUS_BM5_SEL 0x00100000 /* Audio Bus Master 5 AC97 Slot Select */ +# define ACC_CODEC_STATUS_BM4_SEL 0x00080000 /* Audio Bus Master 4 AC97 Slot Select */ +# define ACC_CODEC_STATUS_STS_NEW 0x00020000 /* Codec Status New (Read to Clear) */ +# define ACC_CODEC_STATUS_STS_DATA_MASK 0x0000ffff /* Codec Status Data (Read Only) */ + +#define ACC_CODEC_CNTL 0x0c /* Codec Control Register */ +# define ACC_CODEC_CNTL_RW_CMD 0x80000000 /* Codec Read/Write Command */ +# define ACC_CODEC_CNTL_READ_CMD 0x80000000 /* Codec Read Command */ +# define ACC_CODEC_CNTL_WRITE_CMD 0x00000000 /* Codec Write Command */ +# define ACC_CODEC_CNTL_ADD_MASK 0x7f000000 /* CMD_ADD Codec Command Address */ +# define ACC_CODEC_CNTL_COMM_SEL_MASK 0x00c00000 /* COMM_SEL Audio Codec Communication */ +# define ACC_CODEC_CNTL_PD_PRIM 0x00200000 /* Power-down Semaphore for Primary Codec */ +# define ACC_CODEC_CNTL_PD_SEC 0x00100000 /* Power-down Semaphore for Secondary Codec */ +# define ACC_CODEC_CNTL_LNK_SHTDWN 0x00040000 /* AC Link Shutdown */ +# define ACC_CODEC_CNTL_LNK_WRM_RST 0x00020000 /* AC Link Warm Reset */ +# define ACC_CODEC_CNTL_CMD_NEW 0x00010000 /* Codec Command New */ +# define ACC_CODEC_CNTL_CMD_DATA_MASK 0x0000ffff /* Codec Command Data */ + +#define ACC_IRQ_STATUS 0x12 /* Second Level Audio IRQ Status Register */ +# define ACC_IRQ_STATUS_BM7_IRQ_STS 0x0200 /* Audio Bus Master 7 IRQ Status */ +# define ACC_IRQ_STATUS_BM6_IRQ_STS 0x0100 /* Audio Bus Master 6 IRQ Status */ +# define ACC_IRQ_STATUS_BM5_IRQ_STS 0x0080 /* Audio Bus Master 5 IRQ Status */ +# define ACC_IRQ_STATUS_BM4_IRQ_STS 0x0040 /* Audio Bus Master 4 IRQ Status */ +# define ACC_IRQ_STATUS_BM3_IRQ_STS 0x0020 /* Audio Bus Master 3 IRQ Status */ +# define ACC_IRQ_STATUS_BM2_IRQ_STS 0x0010 /* Audio Bus Master 2 IRQ Status */ +# define ACC_IRQ_STATUS_BM1_IRQ_STS 0x0008 /* Audio Bus Master 1 IRQ Status */ +# define ACC_IRQ_STATUS_BM0_IRQ_STS 0x0004 /* Audio Bus Master 0 IRQ Status */ +# define ACC_IRQ_STATUS_WU_IRQ_STS 0x0002 /* Codec GPIO Wakeup IRQ Status */ +# define ACC_IRQ_STATUS_IRQ_STS 0x0001 /* Codec GPIO IRQ Status */ + +#define ACC_ENGINE_CNTL 0x14 /* Bus Master Engine Control Register */ +# define ACC_ENGINE_CNTL_SSND_MODE 0x00000001 /* Surround Sound (5.1) Synchronization Mode */ + +#define ACC_BM0_CMD 0x20 /* Bus Master 0 Command */ +#define ACC_BM0_STATUS 0x21 /* Bus Master 0 IRQ Status */ +#define ACC_BM0_PRD 0x24 /* Bus Master 0 PRD Table Address */ +#define ACC_BM1_CMD 0x28 /* Bus Master 1 Command */ +#define ACC_BM1_STATUS 0x29 /* Bus Master 1 IRQ Status */ +#define ACC_BM1_PRD 0x2c /* Bus Master 1 PRD Table Address */ +#define ACC_BM2_CMD 0x30 /* Bus Master 2 Command */ +#define ACC_BM2_STATUS 0x31 /* Bus Master 2 IRQ Status */ +#define ACC_BM2_PRD 0x34 /* Bus Master 2 PRD Table Address */ +#define ACC_BM3_CMD 0x38 /* Bus Master 3 Command */ +#define ACC_BM3_STATUS 0x39 /* Bus Master 3 IRQ Status */ +#define ACC_BM3_PRD 0x3c /* Bus Master 3 PRD Table Address */ +#define ACC_BM4_CMD 0x40 /* Bus Master 4 Command */ +#define ACC_BM4_STATUS 0x41 /* Bus Master 4 IRQ Status */ +#define ACC_BM4_PRD 0x44 /* Bus Master 4 PRD Table Address */ +#define ACC_BM5_CMD 0x48 /* Bus Master 5 Command */ +#define ACC_BM5_STATUS 0x49 /* Bus Master 5 IRQ Status */ +#define ACC_BM5_PRD 0x4c /* Bus Master 5 PRD Table Address */ +#define ACC_BM6_CMD 0x50 /* Bus Master 6 Command */ +#define ACC_BM6_STATUS 0x51 /* Bus Master 6 IRQ Status */ +#define ACC_BM6_PRD 0x54 /* Bus Master 6 PRD Table Address */ +#define ACC_BM7_CMD 0x58 /* Bus Master 7 Command */ +#define ACC_BM7_STATUS 0x59 /* Bus Master 7 IRQ Status */ +#define ACC_BM7_PRD 0x5c /* Bus Master 7 PRD Table Address */ +# define ACC_BMx_CMD_RW_MASK 0x08 +# define ACC_BMx_CMD_READ 0x08 /* Codec to Memory */ +# define ACC_BMx_CMD_WRITE 0x00 /* Memory to Codec */ +# define ACC_BMx_CMD_BYTE_ORD_MASK 0x04 +# define ACC_BMx_CMD_BYTE_ORD_EL 0x00 /* Little Endian */ +# define ACC_BMx_CMD_BYTE_ORD_EB 0x04 /* Big Endian */ +# define ACC_BMx_CMD_BM_CTL_MASK 0x03 +# define ACC_BMx_CMD_BM_CTL_DISABLE 0x00 /* Disable bus master */ +# define ACC_BMx_CMD_BM_CTL_ENABLE 0x01 /* Enable bus master */ +# define ACC_BMx_CMD_BM_CTL_PAUSE 0x03 /* Pause bus master */ +# define ACC_BMx_STATUS_BM_EOP_ERR 0x02 /* Bus Master Error */ +# define ACC_BMx_STATUS_EOP 0x01 /* End of Page */ + +/* PRD - Physical Region Descriptor Table (addressed by ACC_BMx_PRD) */ +struct acc_prd { + uint32_t address; + uint32_t ctrlsize; +#define ACC_BMx_PRD_CTRL_EOT 0x80000000 +#define ACC_BMx_PRD_CTRL_EOP 0x40000000 +#define ACC_BMx_PRD_CTRL_JMP 0x20000000 +#define ACC_BMx_PRD_SIZE_MASK 0x0000ffff +}; + +#define ACC_BM0_PNTR 0x60 /* Bus Master 0 DMA Pointer */ +#define ACC_BM1_PNTR 0x64 /* Bus Master 1 DMA Pointer */ +#define ACC_BM2_PNTR 0x68 /* Bus Master 2 DMA Pointer */ +#define ACC_BM3_PNTR 0x6C /* Bus Master 3 DMA Pointer */ +#define ACC_BM4_PNTR 0x70 /* Bus Master 4 DMA Pointer */ +#define ACC_BM5_PNTR 0x74 /* Bus Master 5 DMA Pointer */ +#define ACC_BM6_PNTR 0x78 /* Bus Master 6 DMA Pointer */ +#define ACC_BM7_PNTR 0x7C /* Bus Master 7 DMA Pointer */ + +#endif /* _I386_PCI_GCSCAUDIOREG_H_ */