NetBSD-Bugs archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

port-i386/40284: add AMD Geode CS5536 audio driver



>Number:         40284
>Category:       port-i386
>Synopsis:       add AMD Geode CS5536 audio driver
>Confidential:   no
>Severity:       non-critical
>Priority:       medium
>Responsible:    port-i386-maintainer
>State:          open
>Class:          sw-bug
>Submitter-Id:   net
>Arrival-Date:   Sun Dec 28 13:45:01 +0000 2008
>Originator:     Ryo Shimizu
>Release:        NetBSD 5.99.5
>Organization:
>Environment:
System: NetBSD msr 5.99.5 NetBSD 5.99.5 (KOHJINSHA) #51: Sun Dec 28 07:47:59 
JST 2008  ryo%moveq.nerv.org@localhost:/usr/src/sys/arch/i386/compile/KOHJINSHA 
i386
Architecture: i386
Machine: i386
>Description:
        gcscaudio - AMD Geode CS5536 integrated AC'97 audio device driver

        The gcscaudio driver provides suport for AMD Geode CS5536 chip.
        CS5536 and this driver support 1,2,4,6(5.1) channels output,
        1,2 channels input and full-duplex.

        This driver tested only on KOHJINSHA SA5SX04, and
        5.1ch output is not tested. (not wired on SA5SX04)

>How-To-Repeat:
        Boot a kernel on AMD Geode LX chipset.

        NetBSD 5.99.5 (KOHJINSHA) #50: Sun Dec 28 07:39:13 JST 2008
                
ryo%moveq.nerv.org@localhost:/usr/src/sys/arch/i386/compile/KOHJINSHA
        total memory = 1007 MB
        avail memory = 982 MB
        timecounter: Timecounters tick every 10.000 msec
        timecounter: Timecounter "i8254" frequency 1193182 Hz quality 100
        KOHJINSHA Co.,Ltd SA Series (Rev 1.00)
        mainbus0 (root)
        pci_addr_fixup: 000:15:0 0x1022 0x2090 new address 0x00005800
        cpu0 at mainbus0: AMD 586-class, 498MHz, id 0x5a2
           :
        Advanced Micro Devices CS5536 Audio (audio multimedia, revision 0x01) 
at pci0 dev 15 function 3 not configured
           :

>Fix:
        apply this patch.

diff -auNr NetBSD.orig/sys/arch/i386/conf/files.i386 
NetBSD/sys/arch/i386/conf/files.i386
--- NetBSD.orig/sys/arch/i386/conf/files.i386   2008-12-20 21:41:19.000000000 
+0900
+++ NetBSD/sys/arch/i386/conf/files.i386        2008-12-21 07:08:50.000000000 
+0900
@@ -189,6 +189,11 @@
 attach gcscide at pci
 file   arch/i386/pci/gcscide.c         gcscide
 
+# AMD Geode CS5536 Companion Audio
+device gcscaudio: audiobus, auconv, mulaw, ac97, aurateconv
+attach gcscaudio at pci
+file   arch/i386/pci/gcscaudio.c       gcscaudio
+
 # AMD Geode CS5536 Companion EHCI
 device gcscehci: usbus
 attach gcscehci at pci
diff -auNr NetBSD.orig/sys/arch/i386/pci/gcscaudio.c 
NetBSD/sys/arch/i386/pci/gcscaudio.c
--- NetBSD.orig/sys/arch/i386/pci/gcscaudio.c   1970-01-01 09:00:00.000000000 
+0900
+++ NetBSD/sys/arch/i386/pci/gcscaudio.c        2008-12-28 07:05:32.000000000 
+0900
@@ -0,0 +1,1339 @@
+/*     $NetBSD$        */
+
+/*-
+ * Copyright (c) 2008 SHIMIZU Ryo <ryo%nerv.org@localhost>
+ * 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$");
+
+#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 <arch/i386/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;
+}
diff -auNr NetBSD.orig/sys/arch/i386/pci/gcscaudioreg.h 
NetBSD/sys/arch/i386/pci/gcscaudioreg.h
--- NetBSD.orig/sys/arch/i386/pci/gcscaudioreg.h        1970-01-01 
09:00:00.000000000 +0900
+++ NetBSD/sys/arch/i386/pci/gcscaudioreg.h     2008-12-28 07:06:51.000000000 
+0900
@@ -0,0 +1,151 @@
+/*     $NetBSD$        */
+
+/*-
+ * Copyright (c) 2008 SHIMIZU Ryo <ryo%nerv.org@localhost>
+ * 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_ */



Home | Main Index | Thread Index | Old Index