diff options
author | julian <julian@FreeBSD.org> | 2004-11-10 04:29:09 +0000 |
---|---|---|
committer | julian <julian@FreeBSD.org> | 2004-11-10 04:29:09 +0000 |
commit | 0c7042c36cb8089aa7fc3d1a95ebf7c57319aee9 (patch) | |
tree | 1bd2c720008dfa82daa2ca482d4887dc8ce6e57a /sys/dev | |
parent | 8d0c676b01ecbb1941e3882c115987589adfb7e6 (diff) | |
download | FreeBSD-src-0c7042c36cb8089aa7fc3d1a95ebf7c57319aee9.zip FreeBSD-src-0c7042c36cb8089aa7fc3d1a95ebf7c57319aee9.tar.gz |
Add record capability.
Submitted by: Taku Yamamoto (original author)
Diffstat (limited to 'sys/dev')
-rw-r--r-- | sys/dev/sound/pci/maestro.c | 1834 | ||||
-rw-r--r-- | sys/dev/sound/pci/maestro_reg.h | 59 |
2 files changed, 1405 insertions, 488 deletions
diff --git a/sys/dev/sound/pci/maestro.c b/sys/dev/sound/pci/maestro.c index 79c1277..c80b718 100644 --- a/sys/dev/sound/pci/maestro.c +++ b/sys/dev/sound/pci/maestro.c @@ -23,7 +23,7 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * $Id: maestro.c,v 1.18 2003/07/01 15:52:01 scottl Exp $ + * maestro.c,v 1.23.2.1 2003/10/03 18:21:38 taku Exp */ /* @@ -42,6 +42,9 @@ * were looked at by * Munehiro Matsuda <haro@tk.kubota.co.jp>, * who brought patches based on the Linux driver with some simplification. + * + * Hardware volume controller was implemented by + * John Baldwin <jhb@freebsd.org>. */ #include <dev/sound/pcm/sound.h> @@ -53,6 +56,7 @@ SND_DECLARE_FILE("$FreeBSD$"); + #define inline __inline /* @@ -70,60 +74,150 @@ SND_DECLARE_FILE("$FreeBSD$"); #define NEC_SUBID1 0x80581033 /* Taken from Linux driver */ #define NEC_SUBID2 0x803c1033 /* NEC VersaProNX VA26D */ -#ifndef AGG_MAXPLAYCH +#ifdef AGG_MAXPLAYCH +# if AGG_MAXPLAYCH > 4 +# undef AGG_MAXPLAYCH +# define AGG_MAXPLAYCH 4 +# endif +#else # define AGG_MAXPLAYCH 4 #endif #define AGG_DEFAULT_BUFSZ 0x4000 /* 0x1000, but gets underflows */ +/* compatibility */ +#if __FreeBSD_version < 500000 +# define critical_enter() disable_intr() +# define critical_exit() enable_intr() +#endif + +#ifndef PCIR_BAR +#define PCIR_BAR(x) (PCIR_MAPS + (x) * 4) +#endif + + /* ----------------------------- * Data structures. */ struct agg_chinfo { + /* parent softc */ struct agg_info *parent; + + /* FreeBSD newpcm related */ struct pcm_channel *channel; struct snd_dbuf *buffer; - bus_addr_t offset; - u_int32_t blocksize; + + /* OS independent */ + bus_addr_t phys; /* channel buffer physical address */ + bus_addr_t base; /* channel buffer segment base */ + u_int32_t blklen; /* DMA block length in WORDs */ + u_int32_t buflen; /* channel buffer length in WORDs */ u_int32_t speed; - int dir; - u_int num; - u_int16_t aputype; - u_int16_t wcreg_tpl; + unsigned num : 3; + unsigned stereo : 1; + unsigned qs16 : 1; /* quantum size is 16bit */ + unsigned us : 1; /* in unsigned format */ +}; + +struct agg_rchinfo { + /* parent softc */ + struct agg_info *parent; + + /* FreeBSD newpcm related */ + struct pcm_channel *channel; + struct snd_dbuf *buffer; + + /* OS independent */ + bus_addr_t phys; /* channel buffer physical address */ + bus_addr_t base; /* channel buffer segment base */ + u_int32_t blklen; /* DMA block length in WORDs */ + u_int32_t buflen; /* channel buffer length in WORDs */ + u_int32_t speed; + unsigned : 3; + unsigned stereo : 1; + bus_addr_t srcphys; + int16_t *src; /* stereo peer buffer */ + int16_t *sink; /* channel buffer pointer */ + volatile u_int32_t hwptr; /* ready point in 16bit sample */ }; struct agg_info { + /* FreeBSD newbus related */ device_t dev; + + /* I wonder whether bus_space_* are in common in *BSD... */ struct resource *reg; int regid; - bus_space_tag_t st; bus_space_handle_t sh; - bus_dma_tag_t parent_dmat; struct resource *irq; int irqid; void *ih; - u_int8_t *stat; - bus_addr_t baseaddr; + bus_dma_tag_t buf_dmat; + bus_dma_tag_t stat_dmat; + /* FreeBSD SMPng related */ +#ifdef USING_MUTEX + struct mtx lock; /* mutual exclusion */ +#endif + /* FreeBSD newpcm related */ struct ac97_info *codec; - struct mtx *lock; - unsigned int bufsz; - u_int playchns, active; + /* OS independent */ + u_int8_t *stat; /* status buffer pointer */ + bus_addr_t phys; /* status buffer physical address */ + unsigned int bufsz; /* channel buffer size in bytes */ + u_int playchns; + volatile u_int active; struct agg_chinfo pch[AGG_MAXPLAYCH]; - struct agg_chinfo rch; + struct agg_rchinfo rch; + volatile u_int8_t curpwr; /* current power status: D[0-3] */ }; + +/* ----------------------------- + * Sysctls for debug. + */ +static unsigned int powerstate_active = PCI_POWERSTATE_D1; +#ifdef MAESTRO_AGGRESSIVE_POWERSAVE +static unsigned int powerstate_idle = PCI_POWERSTATE_D2; +#else +static unsigned int powerstate_idle = PCI_POWERSTATE_D1; +#endif +static unsigned int powerstate_init = PCI_POWERSTATE_D2; + +SYSCTL_NODE(_debug, OID_AUTO, maestro, CTLFLAG_RD, 0, ""); +SYSCTL_UINT(_debug_maestro, OID_AUTO, powerstate_active, CTLFLAG_RW, + &powerstate_active, 0, "The Dx power state when active (0-1)"); +SYSCTL_UINT(_debug_maestro, OID_AUTO, powerstate_idle, CTLFLAG_RW, + &powerstate_idle, 0, "The Dx power state when idle (0-2)"); +SYSCTL_UINT(_debug_maestro, OID_AUTO, powerstate_init, CTLFLAG_RW, + &powerstate_init, 0, "The Dx power state prior to the first use (0-2)"); + + +/* ----------------------------- + * Prototypes + */ + +static inline void agg_lock(struct agg_info*); +static inline void agg_unlock(struct agg_info*); +static inline void agg_sleep(struct agg_info*, const char *wmesg, int msec); + +static inline u_int32_t agg_rd(struct agg_info*, int, int size); +static inline void agg_wr(struct agg_info*, int, u_int32_t data, int size); + +static inline int agg_rdcodec(struct agg_info*, int); +static inline int agg_wrcodec(struct agg_info*, int, u_int32_t); + static inline void ringbus_setdest(struct agg_info*, int, int); static inline u_int16_t wp_rdreg(struct agg_info*, u_int16_t); static inline void wp_wrreg(struct agg_info*, u_int16_t, u_int16_t); -static inline u_int16_t wp_rdapu(struct agg_info*, int, u_int16_t); -static inline void wp_wrapu(struct agg_info*, int, u_int16_t, u_int16_t); +static inline u_int16_t wp_rdapu(struct agg_info*, unsigned, u_int16_t); +static inline void wp_wrapu(struct agg_info*, unsigned, u_int16_t, u_int16_t); static inline void wp_settimer(struct agg_info*, u_int); static inline void wp_starttimer(struct agg_info*); static inline void wp_stoptimer(struct agg_info*); @@ -133,16 +227,22 @@ static inline void wc_wrreg(struct agg_info*, u_int16_t, u_int16_t); static inline u_int16_t wc_rdchctl(struct agg_info*, int); static inline void wc_wrchctl(struct agg_info*, int, u_int16_t); -static inline void agg_power(struct agg_info*, int); +static inline void agg_stopclock(struct agg_info*, int part, int st); +static inline void agg_initcodec(struct agg_info*); static void agg_init(struct agg_info*); +static void agg_power(struct agg_info*, int); static void aggch_start_dac(struct agg_chinfo*); static void aggch_stop_dac(struct agg_chinfo*); +static void aggch_start_adc(struct agg_rchinfo*); +static void aggch_stop_adc(struct agg_rchinfo*); +static void aggch_feed_adc_stereo(struct agg_rchinfo*); +static void aggch_feed_adc_mono(struct agg_rchinfo*); static inline void suppress_jitter(struct agg_chinfo*); +static inline void suppress_rec_jitter(struct agg_rchinfo*); -static inline u_int calc_timer_freq(struct agg_chinfo*); static void set_timer(struct agg_info*); static void agg_intr(void *); @@ -153,181 +253,260 @@ static int agg_suspend(device_t); static int agg_resume(device_t); static int agg_shutdown(device_t); -static void *dma_malloc(struct agg_info*, u_int32_t, bus_addr_t*); -static void dma_free(struct agg_info*, void *); +static void *dma_malloc(bus_dma_tag_t, u_int32_t, bus_addr_t*); +static void dma_free(bus_dma_tag_t, void *); + /* ----------------------------- * Subsystems. */ -/* Codec/Ringbus */ +/* locking */ -/* -------------------------------------------------------------------- */ +static inline void +agg_lock(struct agg_info *sc) +{ +#ifdef USING_MUTEX + mtx_lock(&sc->lock); +#endif +} -static u_int32_t -agg_ac97_init(kobj_t obj, void *sc) +static inline void +agg_unlock(struct agg_info *sc) { - struct agg_info *ess = sc; +#ifdef USING_MUTEX + mtx_unlock(&sc->lock); +#endif +} - return (bus_space_read_1(ess->st, ess->sh, PORT_CODEC_STAT) & CODEC_STAT_MASK)? 0 : 1; +static inline void +agg_sleep(struct agg_info *sc, const char *wmesg, int msec) +{ + int timo; + + timo = msec * hz / 1000; + if (timo == 0) + timo = 1; +#ifdef USING_MUTEX + msleep(sc, &sc->lock, PWAIT, wmesg, timo); +#else + tsleep(sc, PWAIT, wmesg, timo); +#endif } -static int -agg_rdcodec(kobj_t obj, void *sc, int regno) + +/* I/O port */ + +static inline u_int32_t +agg_rd(struct agg_info *sc, int regno, int size) { - struct agg_info *ess = sc; - unsigned t; + switch (size) { + case 1: + return bus_space_read_1(sc->st, sc->sh, regno); + case 2: + return bus_space_read_2(sc->st, sc->sh, regno); + case 4: + return bus_space_read_4(sc->st, sc->sh, regno); + default: + return ~(u_int32_t)0; + } +} - /* We have to wait for a SAFE time to write addr/data */ - for (t = 0; t < 20; t++) { - if ((bus_space_read_1(ess->st, ess->sh, PORT_CODEC_STAT) - & CODEC_STAT_MASK) != CODEC_STAT_PROGLESS) - break; +#define AGG_RD(sc, regno, size) \ + bus_space_read_##size( \ + ((struct agg_info*)(sc))->st, \ + ((struct agg_info*)(sc))->sh, (regno)) + +static inline void +agg_wr(struct agg_info *sc, int regno, u_int32_t data, int size) +{ + switch (size) { + case 1: + bus_space_write_1(sc->st, sc->sh, regno, data); + break; + case 2: + bus_space_write_2(sc->st, sc->sh, regno, data); + break; + case 4: + bus_space_write_4(sc->st, sc->sh, regno, data); + break; + } +} + +#define AGG_WR(sc, regno, data, size) \ + bus_space_write_##size( \ + ((struct agg_info*)(sc))->st, \ + ((struct agg_info*)(sc))->sh, (regno), (data)) + +/* -------------------------------------------------------------------- */ + +/* Codec/Ringbus */ + +static inline int +agg_codec_wait4idle(struct agg_info *ess) +{ + unsigned t = 26; + + while (AGG_RD(ess, PORT_CODEC_STAT, 1) & CODEC_STAT_MASK) { + if (--t == 0) + return EBUSY; DELAY(2); /* 20.8us / 13 */ } - if (t == 20) + return 0; +} + + +static inline int +agg_rdcodec(struct agg_info *ess, int regno) +{ + int ret; + + /* We have to wait for a SAFE time to write addr/data */ + if (agg_codec_wait4idle(ess)) { + /* Timed out. No read performed. */ device_printf(ess->dev, "agg_rdcodec() PROGLESS timed out.\n"); + return -1; + } - bus_space_write_1(ess->st, ess->sh, PORT_CODEC_CMD, - CODEC_CMD_READ | regno); - DELAY(21); /* AC97 cycle = 20.8usec */ + AGG_WR(ess, PORT_CODEC_CMD, CODEC_CMD_READ | regno, 1); + /*DELAY(21); * AC97 cycle = 20.8usec */ /* Wait for data retrieve */ - for (t = 0; t < 20; t++) { - if ((bus_space_read_1(ess->st, ess->sh, PORT_CODEC_STAT) - & CODEC_STAT_MASK) == CODEC_STAT_RW_DONE) - break; - DELAY(2); /* 20.8us / 13 */ - } - if (t == 20) - /* Timed out, but perform dummy read. */ + if (!agg_codec_wait4idle(ess)) { + ret = AGG_RD(ess, PORT_CODEC_REG, 2); + } else { + /* Timed out. No read performed. */ device_printf(ess->dev, "agg_rdcodec() RW_DONE timed out.\n"); + ret = -1; + } - return bus_space_read_2(ess->st, ess->sh, PORT_CODEC_REG); + return ret; } -static int -agg_wrcodec(kobj_t obj, void *sc, int regno, u_int32_t data) +static inline int +agg_wrcodec(struct agg_info *ess, int regno, u_int32_t data) { - unsigned t; - struct agg_info *ess = sc; - /* We have to wait for a SAFE time to write addr/data */ - for (t = 0; t < 20; t++) { - if ((bus_space_read_1(ess->st, ess->sh, PORT_CODEC_STAT) - & CODEC_STAT_MASK) != CODEC_STAT_PROGLESS) - break; - DELAY(2); /* 20.8us / 13 */ - } - if (t == 20) { + if (agg_codec_wait4idle(ess)) { /* Timed out. Abort writing. */ device_printf(ess->dev, "agg_wrcodec() PROGLESS timed out.\n"); return -1; } - bus_space_write_2(ess->st, ess->sh, PORT_CODEC_REG, data); - bus_space_write_1(ess->st, ess->sh, PORT_CODEC_CMD, - CODEC_CMD_WRITE | regno); + AGG_WR(ess, PORT_CODEC_REG, data, 2); + AGG_WR(ess, PORT_CODEC_CMD, CODEC_CMD_WRITE | regno, 1); + + /* Wait for write completion */ + if (agg_codec_wait4idle(ess)) { + /* Timed out. */ + device_printf(ess->dev, "agg_wrcodec() RW_DONE timed out.\n"); + return -1; + } return 0; } -static kobj_method_t agg_ac97_methods[] = { - KOBJMETHOD(ac97_init, agg_ac97_init), - KOBJMETHOD(ac97_read, agg_rdcodec), - KOBJMETHOD(ac97_write, agg_wrcodec), - { 0, 0 } -}; -AC97_DECLARE(agg_ac97); - -/* -------------------------------------------------------------------- */ - static inline void ringbus_setdest(struct agg_info *ess, int src, int dest) { u_int32_t data; - data = bus_space_read_4(ess->st, ess->sh, PORT_RINGBUS_CTRL); + data = AGG_RD(ess, PORT_RINGBUS_CTRL, 4); data &= ~(0xfU << src); data |= (0xfU & dest) << src; - bus_space_write_4(ess->st, ess->sh, PORT_RINGBUS_CTRL, data); + AGG_WR(ess, PORT_RINGBUS_CTRL, data, 4); } +/* -------------------------------------------------------------------- */ + /* Wave Processor */ static inline u_int16_t wp_rdreg(struct agg_info *ess, u_int16_t reg) { - bus_space_write_2(ess->st, ess->sh, PORT_DSP_INDEX, reg); - return bus_space_read_2(ess->st, ess->sh, PORT_DSP_DATA); + AGG_WR(ess, PORT_DSP_INDEX, reg, 2); + return AGG_RD(ess, PORT_DSP_DATA, 2); } static inline void wp_wrreg(struct agg_info *ess, u_int16_t reg, u_int16_t data) { - bus_space_write_2(ess->st, ess->sh, PORT_DSP_INDEX, reg); - bus_space_write_2(ess->st, ess->sh, PORT_DSP_DATA, data); + AGG_WR(ess, PORT_DSP_INDEX, reg, 2); + AGG_WR(ess, PORT_DSP_DATA, data, 2); } -static inline void -apu_setindex(struct agg_info *ess, u_int16_t reg) +static inline int +wp_wait_data(struct agg_info *ess, u_int16_t data) { - int t; + unsigned t = 0; - wp_wrreg(ess, WPREG_CRAM_PTR, reg); - /* Sometimes WP fails to set apu register index. */ - for (t = 0; t < 1000; t++) { - if (bus_space_read_2(ess->st, ess->sh, PORT_DSP_DATA) == reg) - break; - bus_space_write_2(ess->st, ess->sh, PORT_DSP_DATA, reg); + while (AGG_RD(ess, PORT_DSP_DATA, 2) != data) { + if (++t == 1000) { + return EAGAIN; + } + AGG_WR(ess, PORT_DSP_DATA, data, 2); } - if (t == 1000) - device_printf(ess->dev, "apu_setindex() timed out.\n"); + + return 0; } static inline u_int16_t -wp_rdapu(struct agg_info *ess, int ch, u_int16_t reg) +wp_rdapu(struct agg_info *ess, unsigned ch, u_int16_t reg) { - u_int16_t ret; - - apu_setindex(ess, ((unsigned)ch << 4) + reg); - ret = wp_rdreg(ess, WPREG_DATA_PORT); - return ret; + wp_wrreg(ess, WPREG_CRAM_PTR, reg | (ch << 4)); + if (wp_wait_data(ess, reg | (ch << 4)) != 0) + device_printf(ess->dev, "wp_rdapu() indexing timed out.\n"); + return wp_rdreg(ess, WPREG_DATA_PORT); } static inline void -wp_wrapu(struct agg_info *ess, int ch, u_int16_t reg, u_int16_t data) +wp_wrapu(struct agg_info *ess, unsigned ch, u_int16_t reg, u_int16_t data) { - int t; - - apu_setindex(ess, ((unsigned)ch << 4) + reg); - wp_wrreg(ess, WPREG_DATA_PORT, data); - for (t = 0; t < 1000; t++) { - if (bus_space_read_2(ess->st, ess->sh, PORT_DSP_DATA) == data) - break; - bus_space_write_2(ess->st, ess->sh, PORT_DSP_DATA, data); + wp_wrreg(ess, WPREG_CRAM_PTR, reg | (ch << 4)); + if (wp_wait_data(ess, reg | (ch << 4)) == 0) { + wp_wrreg(ess, WPREG_DATA_PORT, data); + if (wp_wait_data(ess, data) != 0) + device_printf(ess->dev, "wp_wrapu() write timed out.\n"); + } else { + device_printf(ess->dev, "wp_wrapu() indexing timed out.\n"); } - if (t == 1000) - device_printf(ess->dev, "wp_wrapu() timed out.\n"); } static inline void -wp_settimer(struct agg_info *ess, u_int freq) +apu_setparam(struct agg_info *ess, int apuch, + u_int32_t wpwa, u_int16_t size, int16_t pan, u_int dv) { - u_int clock = 48000 << 2; - u_int prescale = 0, divide = (freq != 0) ? (clock / freq) : ~0; + wp_wrapu(ess, apuch, APUREG_WAVESPACE, (wpwa >> 8) & APU_64KPAGE_MASK); + wp_wrapu(ess, apuch, APUREG_CURPTR, wpwa); + wp_wrapu(ess, apuch, APUREG_ENDPTR, wpwa + size); + wp_wrapu(ess, apuch, APUREG_LOOPLEN, size); + wp_wrapu(ess, apuch, APUREG_ROUTING, 0); + wp_wrapu(ess, apuch, APUREG_AMPLITUDE, 0xf000); + wp_wrapu(ess, apuch, APUREG_POSITION, 0x8f00 + | (APU_RADIUS_MASK & (RADIUS_CENTERCIRCLE << APU_RADIUS_SHIFT)) + | (APU_PAN_MASK & ((pan + PAN_FRONT) << APU_PAN_SHIFT))); + wp_wrapu(ess, apuch, APUREG_FREQ_LOBYTE, + APU_plus6dB | ((dv & 0xff) << APU_FREQ_LOBYTE_SHIFT)); + wp_wrapu(ess, apuch, APUREG_FREQ_HIWORD, dv >> 8); +} + +static inline void +wp_settimer(struct agg_info *ess, u_int divide) +{ + u_int prescale = 0; - RANGE(divide, 4, 32 << 8); + RANGE(divide, 2, 32 << 7); - for (; divide > 32 << 1; divide >>= 1) + for (; divide > 32; divide >>= 1) { prescale++; - divide = (divide + 1) >> 1; + divide++; + } for (; prescale < 7 && divide > 2 && !(divide & 1); divide >>= 1) prescale++; wp_wrreg(ess, WPREG_TIMER_ENABLE, 0); - wp_wrreg(ess, WPREG_TIMER_FREQ, + wp_wrreg(ess, WPREG_TIMER_FREQ, 0x9000 | (prescale << WP_TIMER_FREQ_PRESCALE_SHIFT) | (divide - 1)); wp_wrreg(ess, WPREG_TIMER_ENABLE, 1); } @@ -335,30 +514,37 @@ wp_settimer(struct agg_info *ess, u_int freq) static inline void wp_starttimer(struct agg_info *ess) { + AGG_WR(ess, PORT_INT_STAT, 1, 2); + AGG_WR(ess, PORT_HOSTINT_CTRL, HOSTINT_CTRL_DSOUND_INT_ENABLED + | AGG_RD(ess, PORT_HOSTINT_CTRL, 2), 2); wp_wrreg(ess, WPREG_TIMER_START, 1); } static inline void wp_stoptimer(struct agg_info *ess) { + AGG_WR(ess, PORT_HOSTINT_CTRL, ~HOSTINT_CTRL_DSOUND_INT_ENABLED + & AGG_RD(ess, PORT_HOSTINT_CTRL, 2), 2); + AGG_WR(ess, PORT_INT_STAT, 1, 2); wp_wrreg(ess, WPREG_TIMER_START, 0); - bus_space_write_2(ess->st, ess->sh, PORT_INT_STAT, 1); } +/* -------------------------------------------------------------------- */ + /* WaveCache */ static inline u_int16_t wc_rdreg(struct agg_info *ess, u_int16_t reg) { - bus_space_write_2(ess->st, ess->sh, PORT_WAVCACHE_INDEX, reg); - return bus_space_read_2(ess->st, ess->sh, PORT_WAVCACHE_DATA); + AGG_WR(ess, PORT_WAVCACHE_INDEX, reg, 2); + return AGG_RD(ess, PORT_WAVCACHE_DATA, 2); } static inline void wc_wrreg(struct agg_info *ess, u_int16_t reg, u_int16_t data) { - bus_space_write_2(ess->st, ess->sh, PORT_WAVCACHE_INDEX, reg); - bus_space_write_2(ess->st, ess->sh, PORT_WAVCACHE_DATA, data); + AGG_WR(ess, PORT_WAVCACHE_INDEX, reg, 2); + AGG_WR(ess, PORT_WAVCACHE_DATA, data, 2); } static inline u_int16_t @@ -373,16 +559,26 @@ wc_wrchctl(struct agg_info *ess, int ch, u_int16_t data) wc_wrreg(ess, ch << 3, data); } -/* Power management */ +/* -------------------------------------------------------------------- */ +/* Power management */ static inline void -agg_power(struct agg_info *ess, int status) +agg_stopclock(struct agg_info *ess, int part, int st) { - u_int8_t data; + u_int32_t data; - data = pci_read_config(ess->dev, CONF_PM_PTR, 1); - if (pci_read_config(ess->dev, data, 1) == PPMI_CID) - pci_write_config(ess->dev, data + PM_CTRL, status, 1); + data = pci_read_config(ess->dev, CONF_ACPI_STOPCLOCK, 4); + if (part < 16) { + if (st == PCI_POWERSTATE_D1) + data &= ~(1 << part); + else + data |= (1 << part); + if (st == PCI_POWERSTATE_D1 || st == PCI_POWERSTATE_D2) + data |= (0x10000 << part); + else + data &= ~(0x10000 << part); + pci_write_config(ess->dev, CONF_ACPI_STOPCLOCK, data, 4); + } } @@ -395,46 +591,38 @@ agg_initcodec(struct agg_info* ess) { u_int16_t data; - if (bus_space_read_4(ess->st, ess->sh, PORT_RINGBUS_CTRL) - & RINGBUS_CTRL_ACLINK_ENABLED) { - bus_space_write_4(ess->st, ess->sh, PORT_RINGBUS_CTRL, 0); + if (AGG_RD(ess, PORT_RINGBUS_CTRL, 4) & RINGBUS_CTRL_ACLINK_ENABLED) { + AGG_WR(ess, PORT_RINGBUS_CTRL, 0, 4); DELAY(104); /* 20.8us * (4 + 1) */ } /* XXX - 2nd codec should be looked at. */ - bus_space_write_4(ess->st, ess->sh, PORT_RINGBUS_CTRL, - RINGBUS_CTRL_AC97_SWRESET); + AGG_WR(ess, PORT_RINGBUS_CTRL, RINGBUS_CTRL_AC97_SWRESET, 4); DELAY(2); - bus_space_write_4(ess->st, ess->sh, PORT_RINGBUS_CTRL, - RINGBUS_CTRL_ACLINK_ENABLED); - DELAY(21); - - agg_rdcodec(NULL, ess, 0); - if (bus_space_read_1(ess->st, ess->sh, PORT_CODEC_STAT) - & CODEC_STAT_MASK) { - bus_space_write_4(ess->st, ess->sh, PORT_RINGBUS_CTRL, 0); + AGG_WR(ess, PORT_RINGBUS_CTRL, RINGBUS_CTRL_ACLINK_ENABLED, 4); + DELAY(50); + + if (agg_rdcodec(ess, 0) < 0) { + AGG_WR(ess, PORT_RINGBUS_CTRL, 0, 4); DELAY(21); /* Try cold reset. */ device_printf(ess->dev, "will perform cold reset.\n"); - data = bus_space_read_2(ess->st, ess->sh, PORT_GPIO_DIR); + data = AGG_RD(ess, PORT_GPIO_DIR, 2); if (pci_read_config(ess->dev, 0x58, 2) & 1) data |= 0x10; - data |= 0x009 & - ~bus_space_read_2(ess->st, ess->sh, PORT_GPIO_DATA); - bus_space_write_2(ess->st, ess->sh, PORT_GPIO_MASK, 0xff6); - bus_space_write_2(ess->st, ess->sh, PORT_GPIO_DIR, - data | 0x009); - bus_space_write_2(ess->st, ess->sh, PORT_GPIO_DATA, 0x000); + data |= 0x009 & ~AGG_RD(ess, PORT_GPIO_DATA, 2); + AGG_WR(ess, PORT_GPIO_MASK, 0xff6, 2); + AGG_WR(ess, PORT_GPIO_DIR, data | 0x009, 2); + AGG_WR(ess, PORT_GPIO_DATA, 0x000, 2); DELAY(2); - bus_space_write_2(ess->st, ess->sh, PORT_GPIO_DATA, 0x001); + AGG_WR(ess, PORT_GPIO_DATA, 0x001, 2); DELAY(1); - bus_space_write_2(ess->st, ess->sh, PORT_GPIO_DATA, 0x009); - DELAY(500000); - bus_space_write_2(ess->st, ess->sh, PORT_GPIO_DIR, data); + AGG_WR(ess, PORT_GPIO_DATA, 0x009, 2); + agg_sleep(ess, "agginicd", 500); + AGG_WR(ess, PORT_GPIO_DIR, data, 2); DELAY(84); /* 20.8us * 4 */ - bus_space_write_4(ess->st, ess->sh, PORT_RINGBUS_CTRL, - RINGBUS_CTRL_ACLINK_ENABLED); - DELAY(21); + AGG_WR(ess, PORT_RINGBUS_CTRL, RINGBUS_CTRL_ACLINK_ENABLED, 4); + DELAY(50); } } @@ -455,47 +643,93 @@ agg_init(struct agg_info* ess) * Prefer PCI timing rather than that of ISA. * Don't swap L/R. */ data = pci_read_config(ess->dev, CONF_MAESTRO, 4); + data |= MAESTRO_PMC; data |= MAESTRO_CHIBUS | MAESTRO_POSTEDWRITE | MAESTRO_DMA_PCITIMING; data &= ~MAESTRO_SWAP_LR; pci_write_config(ess->dev, CONF_MAESTRO, data, 4); - /* Reset direct sound. */ - bus_space_write_2(ess->st, ess->sh, PORT_HOSTINT_CTRL, - HOSTINT_CTRL_DSOUND_RESET); - DELAY(10000); /* XXX - too long? */ - bus_space_write_2(ess->st, ess->sh, PORT_HOSTINT_CTRL, 0); - DELAY(10000); + /* Turn off unused parts if necessary. */ + /* consult CONF_MAESTRO. */ + if (data & MAESTRO_SPDIF) + agg_stopclock(ess, ACPI_PART_SPDIF, PCI_POWERSTATE_D2); + else + agg_stopclock(ess, ACPI_PART_SPDIF, PCI_POWERSTATE_D1); + if (data & MAESTRO_HWVOL) + agg_stopclock(ess, ACPI_PART_HW_VOL, PCI_POWERSTATE_D3); + else + agg_stopclock(ess, ACPI_PART_HW_VOL, PCI_POWERSTATE_D1); + + /* parts that never be used */ + agg_stopclock(ess, ACPI_PART_978, PCI_POWERSTATE_D1); + agg_stopclock(ess, ACPI_PART_DAA, PCI_POWERSTATE_D1); + agg_stopclock(ess, ACPI_PART_GPIO, PCI_POWERSTATE_D1); + agg_stopclock(ess, ACPI_PART_SB, PCI_POWERSTATE_D1); + agg_stopclock(ess, ACPI_PART_FM, PCI_POWERSTATE_D1); + agg_stopclock(ess, ACPI_PART_MIDI, PCI_POWERSTATE_D1); + agg_stopclock(ess, ACPI_PART_GAME_PORT, PCI_POWERSTATE_D1); + + /* parts that will be used only when play/recording */ + agg_stopclock(ess, ACPI_PART_WP, PCI_POWERSTATE_D2); + + /* parts that should always be turned on */ + agg_stopclock(ess, ACPI_PART_CODEC_CLOCK, PCI_POWERSTATE_D3); + agg_stopclock(ess, ACPI_PART_GLUE, PCI_POWERSTATE_D3); + agg_stopclock(ess, ACPI_PART_PCI_IF, PCI_POWERSTATE_D3); + agg_stopclock(ess, ACPI_PART_RINGBUS, PCI_POWERSTATE_D3); - /* Enable direct sound interruption and hardware volume control. */ - bus_space_write_2(ess->st, ess->sh, PORT_HOSTINT_CTRL, - HOSTINT_CTRL_DSOUND_INT_ENABLED | HOSTINT_CTRL_HWVOL_ENABLED); + /* Reset direct sound. */ + AGG_WR(ess, PORT_HOSTINT_CTRL, HOSTINT_CTRL_SOFT_RESET, 2); + DELAY(100); + AGG_WR(ess, PORT_HOSTINT_CTRL, 0, 2); + DELAY(100); + AGG_WR(ess, PORT_HOSTINT_CTRL, HOSTINT_CTRL_DSOUND_RESET, 2); + DELAY(100); + AGG_WR(ess, PORT_HOSTINT_CTRL, 0, 2); + DELAY(100); + + /* Enable hardware volume control interruption. */ + if (data & MAESTRO_HWVOL) /* XXX - why not use device flags? */ + AGG_WR(ess, PORT_HOSTINT_CTRL,HOSTINT_CTRL_HWVOL_ENABLED, 2); /* Setup Wave Processor. */ /* Enable WaveCache, set DMA base address. */ wp_wrreg(ess, WPREG_WAVE_ROMRAM, WP_WAVE_VIRTUAL_ENABLED | WP_WAVE_DRAM_ENABLED); - bus_space_write_2(ess->st, ess->sh, PORT_WAVCACHE_CTRL, - WAVCACHE_ENABLED | WAVCACHE_WTSIZE_4MB); + wp_wrreg(ess, WPREG_CRAM_DATA, 0); + + AGG_WR(ess, PORT_WAVCACHE_CTRL, + WAVCACHE_ENABLED | WAVCACHE_WTSIZE_2MB | WAVCACHE_SGC_32_47, 2); for (data = WAVCACHE_PCMBAR; data < WAVCACHE_PCMBAR + 4; data++) - wc_wrreg(ess, data, ess->baseaddr >> WAVCACHE_BASEADDR_SHIFT); + wc_wrreg(ess, data, ess->phys >> WAVCACHE_BASEADDR_SHIFT); /* Setup Codec/Ringbus. */ agg_initcodec(ess); - bus_space_write_4(ess->st, ess->sh, PORT_RINGBUS_CTRL, - RINGBUS_CTRL_RINGBUS_ENABLED | RINGBUS_CTRL_ACLINK_ENABLED); - - wp_wrreg(ess, WPREG_BASE, 0x8500); /* Parallel I/O */ + AGG_WR(ess, PORT_RINGBUS_CTRL, + RINGBUS_CTRL_RINGBUS_ENABLED | RINGBUS_CTRL_ACLINK_ENABLED, 4); + + wp_wrreg(ess, 0x08, 0xB004); + wp_wrreg(ess, 0x09, 0x001B); + wp_wrreg(ess, 0x0A, 0x8000); + wp_wrreg(ess, 0x0B, 0x3F37); + wp_wrreg(ess, WPREG_BASE, 0x8598); /* Parallel I/O */ + wp_wrreg(ess, WPREG_BASE + 1, 0x7632); ringbus_setdest(ess, RINGBUS_SRC_ADC, RINGBUS_DEST_STEREO | RINGBUS_DEST_DSOUND_IN); ringbus_setdest(ess, RINGBUS_SRC_DSOUND, RINGBUS_DEST_STEREO | RINGBUS_DEST_DAC); + /* Enable S/PDIF if necessary. */ + if (pci_read_config(ess->dev, CONF_MAESTRO, 4) & MAESTRO_SPDIF) + /* XXX - why not use device flags? */ + AGG_WR(ess, PORT_RINGBUS_CTRL_B, RINGBUS_CTRL_SPDIF | + AGG_RD(ess, PORT_RINGBUS_CTRL_B, 1), 1); + /* Setup ASSP. Needed for Dell Inspiron 7500? */ - bus_space_write_1(ess->st, ess->sh, PORT_ASSP_CTRL_B, 0x00); - bus_space_write_1(ess->st, ess->sh, PORT_ASSP_CTRL_A, 0x03); - bus_space_write_1(ess->st, ess->sh, PORT_ASSP_CTRL_C, 0x00); + AGG_WR(ess, PORT_ASSP_CTRL_B, 0x00, 1); + AGG_WR(ess, PORT_ASSP_CTRL_A, 0x03, 1); + AGG_WR(ess, PORT_ASSP_CTRL_C, 0x00, 1); /* * Setup GPIO. @@ -507,82 +741,398 @@ agg_init(struct agg_info* ess) case NEC_SUBID2: /* Matthew Braithwaite <matt@braithwaite.net> reported that * NEC Versa LX doesn't need GPIO operation. */ - bus_space_write_2(ess->st, ess->sh, PORT_GPIO_MASK, 0x9ff); - bus_space_write_2(ess->st, ess->sh, PORT_GPIO_DIR, - bus_space_read_2(ess->st, ess->sh, PORT_GPIO_DIR) | 0x600); - bus_space_write_2(ess->st, ess->sh, PORT_GPIO_DATA, 0x200); + AGG_WR(ess, PORT_GPIO_MASK, 0x9ff, 2); + AGG_WR(ess, PORT_GPIO_DIR, + AGG_RD(ess, PORT_GPIO_DIR, 2) | 0x600, 2); + AGG_WR(ess, PORT_GPIO_DATA, 0x200, 2); break; } } +/* Deals power state transition. Must be called with softc->lock held. */ +static void +agg_power(struct agg_info *ess, int status) +{ + u_int8_t lastpwr; + + lastpwr = ess->curpwr; + if (lastpwr == status) + return; + + switch (status) { + case PCI_POWERSTATE_D0: + case PCI_POWERSTATE_D1: + switch (lastpwr) { + case PCI_POWERSTATE_D2: + pci_set_powerstate(ess->dev, status); + /* Turn on PCM-related parts. */ + agg_wrcodec(ess, AC97_REG_POWER, 0); + DELAY(100); +#if 0 + if ((agg_rdcodec(ess, AC97_REG_POWER) & 3) != 3) + device_printf(ess->dev, "warning: codec not ready.\n"); +#endif + AGG_WR(ess, PORT_RINGBUS_CTRL, + (AGG_RD(ess, PORT_RINGBUS_CTRL, 4) + & ~RINGBUS_CTRL_ACLINK_ENABLED) + | RINGBUS_CTRL_RINGBUS_ENABLED, 4); + DELAY(50); + AGG_WR(ess, PORT_RINGBUS_CTRL, + AGG_RD(ess, PORT_RINGBUS_CTRL, 4) + | RINGBUS_CTRL_ACLINK_ENABLED, 4); + break; + case PCI_POWERSTATE_D3: + /* Initialize. */ + pci_set_powerstate(ess->dev, PCI_POWERSTATE_D0); + DELAY(100); + agg_init(ess); + /* FALLTHROUGH */ + case PCI_POWERSTATE_D0: + case PCI_POWERSTATE_D1: + pci_set_powerstate(ess->dev, status); + break; + } + break; + case PCI_POWERSTATE_D2: + switch (lastpwr) { + case PCI_POWERSTATE_D3: + /* Initialize. */ + pci_set_powerstate(ess->dev, PCI_POWERSTATE_D0); + DELAY(100); + agg_init(ess); + /* FALLTHROUGH */ + case PCI_POWERSTATE_D0: + case PCI_POWERSTATE_D1: + /* Turn off PCM-related parts. */ + AGG_WR(ess, PORT_RINGBUS_CTRL, + AGG_RD(ess, PORT_RINGBUS_CTRL, 4) + & ~RINGBUS_CTRL_RINGBUS_ENABLED, 4); + DELAY(100); + agg_wrcodec(ess, AC97_REG_POWER, 0x300); + DELAY(100); + break; + } + pci_set_powerstate(ess->dev, status); + break; + case PCI_POWERSTATE_D3: + /* Entirely power down. */ + agg_wrcodec(ess, AC97_REG_POWER, 0xdf00); + DELAY(100); + AGG_WR(ess, PORT_RINGBUS_CTRL, 0, 4); + /*DELAY(1);*/ + if (lastpwr != PCI_POWERSTATE_D2) + wp_stoptimer(ess); + AGG_WR(ess, PORT_HOSTINT_CTRL, 0, 2); + AGG_WR(ess, PORT_HOSTINT_STAT, 0xff, 1); + pci_set_powerstate(ess->dev, status); + break; + default: + /* Invalid power state; let it ignored. */ + status = lastpwr; + break; + } + + ess->curpwr = status; +} + +/* -------------------------------------------------------------------- */ + /* Channel controller. */ static void aggch_start_dac(struct agg_chinfo *ch) { - bus_addr_t wpwa = APU_USE_SYSMEM | (ch->offset >> 9); - u_int size = ch->parent->bufsz >> 1; - u_int speed = ch->speed; - bus_addr_t offset = ch->offset >> 1; - u_int cp = 0; - u_int16_t apuch = ch->num << 1; - u_int dv; - int pan = 0; - - switch (ch->aputype) { - case APUTYPE_16BITSTEREO: - wpwa >>= 1; - size >>= 1; - offset >>= 1; - cp >>= 1; - /* FALLTHROUGH */ - case APUTYPE_8BITSTEREO: - pan = 8; - apuch++; - break; - case APUTYPE_8BITLINEAR: - speed >>= 1; - break; + bus_addr_t wpwa; + u_int32_t speed; + u_int16_t size, apuch, wtbar, wcreg, aputype; + u_int dv; + int pan; + + speed = ch->speed; + wpwa = (ch->phys - ch->base) >> 1; + wtbar = 0xc & (wpwa >> WPWA_WTBAR_SHIFT(2)); + wcreg = (ch->phys - 16) & WAVCACHE_CHCTL_ADDRTAG_MASK; + size = ch->buflen; + apuch = (ch->num << 1) | 32; + pan = PAN_RIGHT - PAN_FRONT; + + if (ch->stereo) { + wcreg |= WAVCACHE_CHCTL_STEREO; + if (ch->qs16) { + aputype = APUTYPE_16BITSTEREO; + wpwa >>= 1; + size >>= 1; + pan = -pan; + } else + aputype = APUTYPE_8BITSTEREO; + } else { + pan = 0; + if (ch->qs16) + aputype = APUTYPE_16BITLINEAR; + else { + aputype = APUTYPE_8BITLINEAR; + speed >>= 1; + } } + if (ch->us) + wcreg |= WAVCACHE_CHCTL_U8; + + if (wtbar > 8) + wtbar = (wtbar >> 1) + 4; dv = (((speed % 48000) << 16) + 24000) / 48000 + ((speed / 48000) << 16); - do { - wp_wrapu(ch->parent, apuch, APUREG_WAVESPACE, wpwa & 0xff00); - wp_wrapu(ch->parent, apuch, APUREG_CURPTR, offset + cp); - wp_wrapu(ch->parent, apuch, APUREG_ENDPTR, offset + size); - wp_wrapu(ch->parent, apuch, APUREG_LOOPLEN, size); - wp_wrapu(ch->parent, apuch, APUREG_AMPLITUDE, 0xe800); - wp_wrapu(ch->parent, apuch, APUREG_POSITION, 0x8f00 - | (RADIUS_CENTERCIRCLE << APU_RADIUS_SHIFT) - | ((PAN_FRONT + pan) << APU_PAN_SHIFT)); - wp_wrapu(ch->parent, apuch, APUREG_FREQ_LOBYTE, APU_plus6dB - | ((dv & 0xff) << APU_FREQ_LOBYTE_SHIFT)); - wp_wrapu(ch->parent, apuch, APUREG_FREQ_HIWORD, dv >> 8); - - if (ch->aputype == APUTYPE_16BITSTEREO) - wpwa |= APU_STEREO >> 1; - pan = -pan; - } while (pan < 0 && apuch--); - - wc_wrchctl(ch->parent, apuch, ch->wcreg_tpl); - wc_wrchctl(ch->parent, apuch + 1, ch->wcreg_tpl); - - wp_wrapu(ch->parent, apuch, APUREG_APUTYPE, - (ch->aputype << APU_APUTYPE_SHIFT) | APU_DMA_ENABLED | 0xf); - if (ch->wcreg_tpl & WAVCACHE_CHCTL_STEREO) + agg_lock(ch->parent); + agg_power(ch->parent, powerstate_active); + + wc_wrreg(ch->parent, WAVCACHE_WTBAR + wtbar, + ch->base >> WAVCACHE_BASEADDR_SHIFT); + wc_wrreg(ch->parent, WAVCACHE_WTBAR + wtbar + 1, + ch->base >> WAVCACHE_BASEADDR_SHIFT); + if (wtbar < 8) { + wc_wrreg(ch->parent, WAVCACHE_WTBAR + wtbar + 2, + ch->base >> WAVCACHE_BASEADDR_SHIFT); + wc_wrreg(ch->parent, WAVCACHE_WTBAR + wtbar + 3, + ch->base >> WAVCACHE_BASEADDR_SHIFT); + } + wc_wrchctl(ch->parent, apuch, wcreg); + wc_wrchctl(ch->parent, apuch + 1, wcreg); + + apu_setparam(ch->parent, apuch, wpwa, size, pan, dv); + if (ch->stereo) { + if (ch->qs16) + wpwa |= (WPWA_STEREO >> 1); + apu_setparam(ch->parent, apuch + 1, wpwa, size, -pan, dv); + + critical_enter(); + wp_wrapu(ch->parent, apuch, APUREG_APUTYPE, + (aputype << APU_APUTYPE_SHIFT) | APU_DMA_ENABLED | 0xf); wp_wrapu(ch->parent, apuch + 1, APUREG_APUTYPE, - (ch->aputype << APU_APUTYPE_SHIFT) | APU_DMA_ENABLED | 0xf); + (aputype << APU_APUTYPE_SHIFT) | APU_DMA_ENABLED | 0xf); + critical_exit(); + } else { + wp_wrapu(ch->parent, apuch, APUREG_APUTYPE, + (aputype << APU_APUTYPE_SHIFT) | APU_DMA_ENABLED | 0xf); + } + + /* to mark that this channel is ready for intr. */ + ch->parent->active |= (1 << ch->num); + + set_timer(ch->parent); + wp_starttimer(ch->parent); + agg_unlock(ch->parent); } static void aggch_stop_dac(struct agg_chinfo *ch) { - wp_wrapu(ch->parent, (ch->num << 1), APUREG_APUTYPE, + agg_lock(ch->parent); + + /* to mark that this channel no longer needs further intrs. */ + ch->parent->active &= ~(1 << ch->num); + + wp_wrapu(ch->parent, (ch->num << 1) | 32, APUREG_APUTYPE, APUTYPE_INACTIVE << APU_APUTYPE_SHIFT); - wp_wrapu(ch->parent, (ch->num << 1) + 1, APUREG_APUTYPE, + wp_wrapu(ch->parent, (ch->num << 1) | 33, APUREG_APUTYPE, APUTYPE_INACTIVE << APU_APUTYPE_SHIFT); + + if (ch->parent->active) { + set_timer(ch->parent); + wp_starttimer(ch->parent); + } else { + wp_stoptimer(ch->parent); + agg_power(ch->parent, powerstate_idle); + } + agg_unlock(ch->parent); +} + +static void +aggch_start_adc(struct agg_rchinfo *ch) +{ + bus_addr_t wpwa, wpwa2; + u_int16_t wcreg, wcreg2; + u_int dv; + int pan; + + /* speed > 48000 not cared */ + dv = ((ch->speed << 16) + 24000) / 48000; + + /* RATECONV doesn't seem to like dv == 0x10000. */ + if (dv == 0x10000) + dv--; + + if (ch->stereo) { + wpwa = (ch->srcphys - ch->base) >> 1; + wpwa2 = (ch->srcphys + ch->parent->bufsz/2 - ch->base) >> 1; + wcreg = (ch->srcphys - 16) & WAVCACHE_CHCTL_ADDRTAG_MASK; + wcreg2 = (ch->base - 16) & WAVCACHE_CHCTL_ADDRTAG_MASK; + pan = PAN_LEFT - PAN_FRONT; + } else { + wpwa = (ch->phys - ch->base) >> 1; + wpwa2 = (ch->srcphys - ch->base) >> 1; + wcreg = (ch->phys - 16) & WAVCACHE_CHCTL_ADDRTAG_MASK; + wcreg2 = (ch->base - 16) & WAVCACHE_CHCTL_ADDRTAG_MASK; + pan = 0; + } + + agg_lock(ch->parent); + + ch->hwptr = 0; + agg_power(ch->parent, powerstate_active); + + /* Invalidate WaveCache. */ + wc_wrchctl(ch->parent, 0, wcreg | WAVCACHE_CHCTL_STEREO); + wc_wrchctl(ch->parent, 1, wcreg | WAVCACHE_CHCTL_STEREO); + wc_wrchctl(ch->parent, 2, wcreg2 | WAVCACHE_CHCTL_STEREO); + wc_wrchctl(ch->parent, 3, wcreg2 | WAVCACHE_CHCTL_STEREO); + + /* Load APU registers. */ + /* APU #0 : Sample rate converter for left/center. */ + apu_setparam(ch->parent, 0, WPWA_USE_SYSMEM | wpwa, + ch->buflen >> ch->stereo, 0, dv); + wp_wrapu(ch->parent, 0, APUREG_AMPLITUDE, 0); + wp_wrapu(ch->parent, 0, APUREG_ROUTING, 2 << APU_DATASRC_A_SHIFT); + + /* APU #1 : Sample rate converter for right. */ + apu_setparam(ch->parent, 1, WPWA_USE_SYSMEM | wpwa2, + ch->buflen >> ch->stereo, 0, dv); + wp_wrapu(ch->parent, 1, APUREG_AMPLITUDE, 0); + wp_wrapu(ch->parent, 1, APUREG_ROUTING, 3 << APU_DATASRC_A_SHIFT); + + /* APU #2 : Input mixer for left. */ + apu_setparam(ch->parent, 2, WPWA_USE_SYSMEM | 0, + ch->parent->bufsz >> 2, pan, 0x10000); + wp_wrapu(ch->parent, 2, APUREG_AMPLITUDE, 0); + wp_wrapu(ch->parent, 2, APUREG_EFFECT_GAIN, 0xf0); + wp_wrapu(ch->parent, 2, APUREG_ROUTING, 0x15 << APU_DATASRC_A_SHIFT); + + /* APU #3 : Input mixer for right. */ + apu_setparam(ch->parent, 3, WPWA_USE_SYSMEM | (ch->parent->bufsz >> 2), + ch->parent->bufsz >> 2, -pan, 0x10000); + wp_wrapu(ch->parent, 3, APUREG_AMPLITUDE, 0); + wp_wrapu(ch->parent, 3, APUREG_EFFECT_GAIN, 0xf0); + wp_wrapu(ch->parent, 3, APUREG_ROUTING, 0x14 << APU_DATASRC_A_SHIFT); + + /* to mark this channel ready for intr. */ + ch->parent->active |= (1 << ch->parent->playchns); + + /* start adc */ + critical_enter(); + wp_wrapu(ch->parent, 0, APUREG_APUTYPE, + (APUTYPE_RATECONV << APU_APUTYPE_SHIFT) | APU_DMA_ENABLED | 0xf); + wp_wrapu(ch->parent, 1, APUREG_APUTYPE, + (APUTYPE_RATECONV << APU_APUTYPE_SHIFT) | APU_DMA_ENABLED | 0xf); + wp_wrapu(ch->parent, 2, APUREG_APUTYPE, + (APUTYPE_INPUTMIXER << APU_APUTYPE_SHIFT) | 0xf); + wp_wrapu(ch->parent, 3, APUREG_APUTYPE, + (APUTYPE_INPUTMIXER << APU_APUTYPE_SHIFT) | 0xf); + critical_exit(); + + set_timer(ch->parent); + wp_starttimer(ch->parent); + agg_unlock(ch->parent); +} + +static void +aggch_stop_adc(struct agg_rchinfo *ch) +{ + int apuch; + + agg_lock(ch->parent); + + /* to mark that this channel no longer needs further intrs. */ + ch->parent->active &= ~(1 << ch->parent->playchns); + + for (apuch = 0; apuch < 4; apuch++) + wp_wrapu(ch->parent, apuch, APUREG_APUTYPE, + APUTYPE_INACTIVE << APU_APUTYPE_SHIFT); + + if (ch->parent->active) { + set_timer(ch->parent); + wp_starttimer(ch->parent); + } else { + wp_stoptimer(ch->parent); + agg_power(ch->parent, powerstate_idle); + } + agg_unlock(ch->parent); +} + +/* + * Feed from L/R channel of ADC to destination with stereo interleaving. + * This function expects n not overwrapping the buffer boundary. + * Note that n is measured in sample unit. + * + * XXX - this function works in 16bit stereo format only. + */ +static inline void +interleave(int16_t *l, int16_t *r, int16_t *p, unsigned n) +{ + int16_t *end; + + for (end = l + n; l < end; ) { + *p++ = *l++; + *p++ = *r++; + } +} + +static void +aggch_feed_adc_stereo(struct agg_rchinfo *ch) +{ + unsigned cur, last; + int16_t *src2; + + agg_lock(ch->parent); + cur = wp_rdapu(ch->parent, 0, APUREG_CURPTR); + agg_unlock(ch->parent); + cur -= 0xffff & ((ch->srcphys - ch->base) >> 1); + last = ch->hwptr; + src2 = ch->src + ch->parent->bufsz/4; + + if (cur < last) { + interleave(ch->src + last, src2 + last, + ch->sink + 2*last, ch->buflen/2 - last); + interleave(ch->src, src2, + ch->sink, cur); + } else if (cur > last) + interleave(ch->src + last, src2 + last, + ch->sink + 2*last, cur - last); + ch->hwptr = cur; +} + +/* + * Feed from R channel of ADC and mixdown to destination L/center. + * This function expects n not overwrapping the buffer boundary. + * Note that n is measured in sample unit. + * + * XXX - this function works in 16bit monoral format only. + */ +static inline void +mixdown(int16_t *src, int16_t *dest, unsigned n) +{ + int16_t *end; + + for (end = dest + n; dest < end; dest++) + *dest = (int16_t)(((int)*dest - (int)*src++) / 2); +} + +static void +aggch_feed_adc_mono(struct agg_rchinfo *ch) +{ + unsigned cur, last; + + agg_lock(ch->parent); + cur = wp_rdapu(ch->parent, 0, APUREG_CURPTR); + agg_unlock(ch->parent); + cur -= 0xffff & ((ch->phys - ch->base) >> 1); + last = ch->hwptr; + + if (cur < last) { + mixdown(ch->src + last, ch->sink + last, ch->buflen - last); + mixdown(ch->src, ch->sink, cur); + } else if (cur > last) + mixdown(ch->src + last, ch->sink + last, cur - last); + ch->hwptr = cur; } /* @@ -593,45 +1143,84 @@ aggch_stop_dac(struct agg_chinfo *ch) static inline void suppress_jitter(struct agg_chinfo *ch) { - if (ch->wcreg_tpl & WAVCACHE_CHCTL_STEREO) { - int cp, diff, halfsize = ch->parent->bufsz >> 2; - - if (ch->aputype == APUTYPE_16BITSTEREO) - halfsize >>= 1; - cp = wp_rdapu(ch->parent, (ch->num << 1), APUREG_CURPTR); - diff = wp_rdapu(ch->parent, (ch->num << 1) + 1, APUREG_CURPTR); - diff -= cp; - if (diff >> 1 && diff > -halfsize && diff < halfsize) - bus_space_write_2(ch->parent->st, ch->parent->sh, - PORT_DSP_DATA, cp); + if (ch->stereo) { + int cp1, cp2, diff /*, halfsize*/ ; + + /*halfsize = (ch->qs16? ch->buflen >> 2 : ch->buflen >> 1);*/ + cp1 = wp_rdapu(ch->parent, (ch->num << 1) | 32, APUREG_CURPTR); + cp2 = wp_rdapu(ch->parent, (ch->num << 1) | 33, APUREG_CURPTR); + if (cp1 != cp2) { + diff = (cp1 > cp2 ? cp1 - cp2 : cp2 - cp1); + if (diff > 1 /* && diff < halfsize*/ ) + AGG_WR(ch->parent, PORT_DSP_DATA, cp1, 2); + } + } +} + +static inline void +suppress_rec_jitter(struct agg_rchinfo *ch) +{ + int cp1, cp2, diff /*, halfsize*/ ; + + /*halfsize = (ch->stereo? ch->buflen >> 2 : ch->buflen >> 1);*/ + cp1 = (ch->stereo? ch->parent->bufsz >> 2 : ch->parent->bufsz >> 1) + + wp_rdapu(ch->parent, 0, APUREG_CURPTR); + cp2 = wp_rdapu(ch->parent, 1, APUREG_CURPTR); + if (cp1 != cp2) { + diff = (cp1 > cp2 ? cp1 - cp2 : cp2 - cp1); + if (diff > 1 /* && diff < halfsize*/ ) + AGG_WR(ch->parent, PORT_DSP_DATA, cp1, 2); } } static inline u_int -calc_timer_freq(struct agg_chinfo *ch) +calc_timer_div(struct agg_chinfo *ch) { - u_int ss = 2; + u_int speed; + + speed = ch->speed; +#ifdef INVARIANTS + if (speed == 0) { + printf("snd_maestro: pch[%d].speed == 0, which shouldn't\n", + ch->num); + speed = 1; + } +#endif + return (48000 * (ch->blklen << (!ch->qs16 + !ch->stereo)) + + speed - 1) / speed; +} - if (ch->aputype == APUTYPE_16BITSTEREO) - ss <<= 1; - if (ch->aputype == APUTYPE_8BITLINEAR) - ss >>= 1; +static inline u_int +calc_timer_div_rch(struct agg_rchinfo *ch) +{ + u_int speed; - return (ch->speed * ss) / ch->blocksize; + speed = ch->speed; +#ifdef INVARIANTS + if (speed == 0) { + printf("snd_maestro: rch.speed == 0, which shouldn't\n"); + speed = 1; + } +#endif + return (48000 * (ch->blklen << (!ch->stereo)) + + speed - 1) / speed; } static void set_timer(struct agg_info *ess) { int i; - u_int freq = 0; + u_int dv = 32 << 7, newdv; for (i = 0; i < ess->playchns; i++) if ((ess->active & (1 << i)) && - (freq < calc_timer_freq(ess->pch + i))) - freq = calc_timer_freq(ess->pch + i); + (dv > (newdv = calc_timer_div(ess->pch + i)))) + dv = newdv; + if ((ess->active & (1 << i)) && + (dv > (newdv = calc_timer_div_rch(&ess->rch)))) + dv = newdv; - wp_settimer(ess, freq); + wp_settimer(ess, dv); } @@ -639,142 +1228,225 @@ set_timer(struct agg_info *ess) * Newpcm glue. */ +/* AC97 mixer interface. */ + +static u_int32_t +agg_ac97_init(kobj_t obj, void *sc) +{ + struct agg_info *ess = sc; + + return (AGG_RD(ess, PORT_CODEC_STAT, 1) & CODEC_STAT_MASK)? 0 : 1; +} + +static int +agg_ac97_read(kobj_t obj, void *sc, int regno) +{ + struct agg_info *ess = sc; + int ret; + + agg_lock(ess); + ret = agg_rdcodec(ess, regno); + agg_unlock(ess); + return ret; +} + +static int +agg_ac97_write(kobj_t obj, void *sc, int regno, u_int32_t data) +{ + struct agg_info *ess = sc; + int ret; + + agg_lock(ess); + ret = agg_wrcodec(ess, regno, data); + agg_unlock(ess); + return ret; +} + + +static kobj_method_t agg_ac97_methods[] = { + KOBJMETHOD(ac97_init, agg_ac97_init), + KOBJMETHOD(ac97_read, agg_ac97_read), + KOBJMETHOD(ac97_write, agg_ac97_write), + { 0, 0 } +}; +AC97_DECLARE(agg_ac97); + + +/* -------------------------------------------------------------------- */ + +/* Playback channel. */ + static void * -aggch_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) +aggpch_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) { struct agg_info *ess = devinfo; struct agg_chinfo *ch; bus_addr_t physaddr; void *p; - ch = (dir == PCMDIR_PLAY)? ess->pch + ess->playchns : &ess->rch; + KASSERT((dir == PCMDIR_PLAY), + ("aggpch_init() called for RECORDING channel!")); + ch = ess->pch + ess->playchns; ch->parent = ess; ch->channel = c; ch->buffer = b; ch->num = ess->playchns; - ch->dir = dir; - physaddr = ess->baseaddr + ch->offset; - p = ess->stat + ch->offset; + p = dma_malloc(ess->buf_dmat, ess->bufsz, &physaddr); + if (p == NULL) + return NULL; + ch->phys = physaddr; + ch->base = physaddr & ((~(bus_addr_t)0) << WAVCACHE_BASEADDR_SHIFT); + sndbuf_setup(b, p, ess->bufsz); + ch->blklen = sndbuf_getblksz(b) / 2; + ch->buflen = sndbuf_getsize(b) / 2; + ess->playchns++; - ch->wcreg_tpl = (physaddr - 16) & WAVCACHE_CHCTL_ADDRTAG_MASK; + return ch; +} - if (dir == PCMDIR_PLAY) { - ess->playchns++; - if (bootverbose) - device_printf(ess->dev, "pch[%d].offset = %#llx\n", ch->num, (long long)ch->offset); - } else if (bootverbose) - device_printf(ess->dev, "rch.offset = %#llx\n", (long long)ch->offset); +static void +adjust_pchbase(struct agg_chinfo *chans, u_int n, u_int size) +{ + struct agg_chinfo *pchs[AGG_MAXPLAYCH]; + u_int i, j, k; + bus_addr_t base; + + /* sort pchs by phys address */ + for (i = 0; i < n; i++) { + for (j = 0; j < i; j++) + if (chans[i].phys < pchs[j]->phys) { + for (k = i; k > j; k--) + pchs[k] = pchs[k - 1]; + break; + } + pchs[j] = chans + i; + } - return ch; + /* use new base register if next buffer can not be addressed + via current base. */ +#define BASE_SHIFT (WPWA_WTBAR_SHIFT(2) + 2 + 1) + base = pchs[0]->base; + for (k = 1, i = 1; i < n; i++) { + if (pchs[i]->phys + size - base >= 1 << BASE_SHIFT) + /* not addressable: assign new base */ + base = (pchs[i]->base -= k++ << BASE_SHIFT); + else + pchs[i]->base = base; + } +#undef BASE_SHIFT + + if (bootverbose) { + printf("Total of %d bases are assigned.\n", k); + for (i = 0; i < n; i++) { + printf("ch.%d: phys 0x%llx, wpwa 0x%llx\n", + i, (long long)chans[i].phys, + (long long)(chans[i].phys - + chans[i].base) >> 1); + } + } } static int -aggch_free(kobj_t obj, void *data) +aggpch_free(kobj_t obj, void *data) { + struct agg_chinfo *ch = data; + struct agg_info *ess = ch->parent; + + /* free up buffer - called after channel stopped */ + dma_free(ess->buf_dmat, sndbuf_getbuf(ch->buffer)); + /* return 0 if ok */ return 0; } static int -aggch_setplayformat(kobj_t obj, void *data, u_int32_t format) +aggpch_setformat(kobj_t obj, void *data, u_int32_t format) { struct agg_chinfo *ch = data; - u_int16_t wcreg_tpl; - u_int16_t aputype = APUTYPE_16BITLINEAR; - wcreg_tpl = ch->wcreg_tpl & WAVCACHE_CHCTL_ADDRTAG_MASK; + if (format & AFMT_BIGENDIAN || format & AFMT_U16_LE) + return EINVAL; + ch->stereo = ch->qs16 = ch->us = 0; + if (format & AFMT_STEREO) + ch->stereo = 1; - if (format & AFMT_STEREO) { - wcreg_tpl |= WAVCACHE_CHCTL_STEREO; - aputype += 1; - } if (format & AFMT_U8 || format & AFMT_S8) { - aputype += 2; if (format & AFMT_U8) - wcreg_tpl |= WAVCACHE_CHCTL_U8; - } - if (format & AFMT_BIGENDIAN || format & AFMT_U16_LE) { - format &= ~AFMT_BIGENDIAN & ~AFMT_U16_LE; - format |= AFMT_S16_LE; - } - ch->wcreg_tpl = wcreg_tpl; - ch->aputype = aputype; + ch->us = 1; + } else + ch->qs16 = 1; return 0; } static int -aggch_setspeed(kobj_t obj, void *data, u_int32_t speed) +aggpch_setspeed(kobj_t obj, void *data, u_int32_t speed) { - struct agg_chinfo *ch = data; - - ch->speed = speed; - return ch->speed; + return ((struct agg_chinfo*)data)->speed = speed; } static int -aggch_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) +aggpch_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) { - return ((struct agg_chinfo*)data)->blocksize = blocksize; + struct agg_chinfo *ch = data; + int blkcnt; + + /* try to keep at least 20msec DMA space */ + blkcnt = (ch->speed << (ch->stereo + ch->qs16)) / (50 * blocksize); + RANGE(blkcnt, 2, ch->parent->bufsz / blocksize); + + if (sndbuf_getsize(ch->buffer) != blkcnt * blocksize) { + sndbuf_resize(ch->buffer, blkcnt, blocksize); + blkcnt = sndbuf_getblkcnt(ch->buffer); + blocksize = sndbuf_getblksz(ch->buffer); + } else { + sndbuf_setblkcnt(ch->buffer, blkcnt); + sndbuf_setblksz(ch->buffer, blocksize); + } + + ch->blklen = blocksize / 2; + ch->buflen = blkcnt * blocksize / 2; + return blocksize; } static int -aggch_trigger(kobj_t obj, void *data, int go) +aggpch_trigger(kobj_t obj, void *data, int go) { struct agg_chinfo *ch = data; switch (go) { case PCMTRIG_EMLDMAWR: - return 0; + break; case PCMTRIG_START: - ch->parent->active |= (1 << ch->num); - if (ch->dir == PCMDIR_PLAY) - aggch_start_dac(ch); -#if 0 /* XXX - RECORDING */ - else - aggch_start_adc(ch); -#endif + aggch_start_dac(ch); break; case PCMTRIG_ABORT: case PCMTRIG_STOP: - ch->parent->active &= ~(1 << ch->num); - if (ch->dir == PCMDIR_PLAY) - aggch_stop_dac(ch); -#if 0 /* XXX - RECORDING */ - else - aggch_stop_adc(ch); -#endif + aggch_stop_dac(ch); break; } - - if (ch->parent->active) { - set_timer(ch->parent); - wp_starttimer(ch->parent); - } else - wp_stoptimer(ch->parent); - return 0; } static int -aggch_getplayptr(kobj_t obj, void *data) +aggpch_getptr(kobj_t obj, void *data) { struct agg_chinfo *ch = data; u_int cp; - cp = wp_rdapu(ch->parent, (ch->num << 1), APUREG_CURPTR); - if (ch->aputype == APUTYPE_16BITSTEREO) - cp = (0xffff << 2) & ((cp << 2) - ch->offset); - else - cp = (0xffff << 1) & ((cp << 1) - ch->offset); + agg_lock(ch->parent); + cp = wp_rdapu(ch->parent, (ch->num << 1) | 32, APUREG_CURPTR); + agg_unlock(ch->parent); - return cp; + return ch->qs16 && ch->stereo + ? (cp << 2) - ((0xffff << 2) & (ch->phys - ch->base)) + : (cp << 1) - ((0xffff << 1) & (ch->phys - ch->base)); } static struct pcmchan_caps * -aggch_getcaps(kobj_t obj, void *data) +aggpch_getcaps(kobj_t obj, void *data) { static u_int32_t playfmt[] = { AFMT_U8, @@ -785,33 +1457,161 @@ aggch_getcaps(kobj_t obj, void *data) AFMT_STEREO | AFMT_S16_LE, 0 }; - static struct pcmchan_caps playcaps = {2000, 96000, playfmt, 0}; + static struct pcmchan_caps playcaps = {2000, 767999, playfmt, 0}; + + return &playcaps; +} + + +static kobj_method_t aggpch_methods[] = { + KOBJMETHOD(channel_init, aggpch_init), + KOBJMETHOD(channel_free, aggpch_free), + KOBJMETHOD(channel_setformat, aggpch_setformat), + KOBJMETHOD(channel_setspeed, aggpch_setspeed), + KOBJMETHOD(channel_setblocksize, aggpch_setblocksize), + KOBJMETHOD(channel_trigger, aggpch_trigger), + KOBJMETHOD(channel_getptr, aggpch_getptr), + KOBJMETHOD(channel_getcaps, aggpch_getcaps), + { 0, 0 } +}; +CHANNEL_DECLARE(aggpch); + + +/* -------------------------------------------------------------------- */ +/* Recording channel. */ + +static void * +aggrch_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) +{ + struct agg_info *ess = devinfo; + struct agg_rchinfo *ch; + u_int8_t *p; + + KASSERT((dir == PCMDIR_REC), + ("aggrch_init() called for PLAYBACK channel!")); + ch = &ess->rch; + + ch->parent = ess; + ch->channel = c; + ch->buffer = b; + + /* Uses the bottom-half of the status buffer. */ + p = ess->stat + ess->bufsz; + ch->phys = ess->phys + ess->bufsz; + ch->base = ess->phys; + ch->src = (int16_t *)(p + ess->bufsz); + ch->srcphys = ch->phys + ess->bufsz; + ch->sink = (int16_t *)p; + + sndbuf_setup(b, p, ess->bufsz); + ch->blklen = sndbuf_getblksz(b) / 2; + ch->buflen = sndbuf_getsize(b) / 2; + + return ch; +} + +static int +aggrch_setformat(kobj_t obj, void *data, u_int32_t format) +{ + struct agg_rchinfo *ch = data; + + if (!(format & AFMT_S16_LE)) + return EINVAL; + if (format & AFMT_STEREO) + ch->stereo = 1; + else + ch->stereo = 0; + return 0; +} + +static int +aggrch_setspeed(kobj_t obj, void *data, u_int32_t speed) +{ + return ((struct agg_rchinfo*)data)->speed = speed; +} + +static int +aggrch_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) +{ + struct agg_rchinfo *ch = data; + int blkcnt; + + /* try to keep at least 20msec DMA space */ + blkcnt = (ch->speed << ch->stereo) / (25 * blocksize); + RANGE(blkcnt, 2, ch->parent->bufsz / blocksize); + + if (sndbuf_getsize(ch->buffer) != blkcnt * blocksize) { + sndbuf_resize(ch->buffer, blkcnt, blocksize); + blkcnt = sndbuf_getblkcnt(ch->buffer); + blocksize = sndbuf_getblksz(ch->buffer); + } else { + sndbuf_setblkcnt(ch->buffer, blkcnt); + sndbuf_setblksz(ch->buffer, blocksize); + } + + ch->blklen = blocksize / 2; + ch->buflen = blkcnt * blocksize / 2; + return blocksize; +} + +static int +aggrch_trigger(kobj_t obj, void *sc, int go) +{ + struct agg_rchinfo *ch = sc; + + switch (go) { + case PCMTRIG_EMLDMARD: + if (ch->stereo) + aggch_feed_adc_stereo(ch); + else + aggch_feed_adc_mono(ch); + break; + case PCMTRIG_START: + aggch_start_adc(ch); + break; + case PCMTRIG_ABORT: + case PCMTRIG_STOP: + aggch_stop_adc(ch); + break; + } + return 0; +} + +static int +aggrch_getptr(kobj_t obj, void *sc) +{ + struct agg_rchinfo *ch = sc; + + return ch->stereo? ch->hwptr << 2 : ch->hwptr << 1; +} + +static struct pcmchan_caps * +aggrch_getcaps(kobj_t obj, void *sc) +{ static u_int32_t recfmt[] = { - AFMT_S8, - AFMT_STEREO | AFMT_S8, AFMT_S16_LE, AFMT_STEREO | AFMT_S16_LE, 0 }; - static struct pcmchan_caps reccaps = {4000, 48000, recfmt, 0}; + static struct pcmchan_caps reccaps = {8000, 48000, recfmt, 0}; - return (((struct agg_chinfo*)data)->dir == PCMDIR_PLAY)? - &playcaps : &reccaps; + return &reccaps; } -static kobj_method_t aggch_methods[] = { - KOBJMETHOD(channel_init, aggch_init), - KOBJMETHOD(channel_free, aggch_free), - KOBJMETHOD(channel_setformat, aggch_setplayformat), - KOBJMETHOD(channel_setspeed, aggch_setspeed), - KOBJMETHOD(channel_setblocksize, aggch_setblocksize), - KOBJMETHOD(channel_trigger, aggch_trigger), - KOBJMETHOD(channel_getptr, aggch_getplayptr), - KOBJMETHOD(channel_getcaps, aggch_getcaps), +static kobj_method_t aggrch_methods[] = { + KOBJMETHOD(channel_init, aggrch_init), + /* channel_free: no-op */ + KOBJMETHOD(channel_setformat, aggrch_setformat), + KOBJMETHOD(channel_setspeed, aggrch_setspeed), + KOBJMETHOD(channel_setblocksize, aggrch_setblocksize), + KOBJMETHOD(channel_trigger, aggrch_trigger), + KOBJMETHOD(channel_getptr, aggrch_getptr), + KOBJMETHOD(channel_getcaps, aggrch_getcaps), { 0, 0 } }; -CHANNEL_DECLARE(aggch); +CHANNEL_DECLARE(aggrch); + /* ----------------------------- * Bus space. @@ -821,25 +1621,61 @@ static void agg_intr(void *sc) { struct agg_info* ess = sc; - u_int16_t status; + register u_int8_t status; int i; + u_int m; - status = bus_space_read_1(ess->st, ess->sh, PORT_HOSTINT_STAT); + status = AGG_RD(ess, PORT_HOSTINT_STAT, 1); if (!status) return; - /* Acknowledge all. */ - bus_space_write_2(ess->st, ess->sh, PORT_INT_STAT, 1); - bus_space_write_1(ess->st, ess->sh, PORT_HOSTINT_STAT, 0xff); + /* Acknowledge intr. */ + AGG_WR(ess, PORT_HOSTINT_STAT, status, 1); + + if (status & HOSTINT_STAT_DSOUND) { +#ifdef AGG_JITTER_CORRECTION + agg_lock(ess); +#endif + if (ess->curpwr <= PCI_POWERSTATE_D1) { + AGG_WR(ess, PORT_INT_STAT, 1, 2); +#ifdef AGG_JITTER_CORRECTION + for (i = 0, m = 1; i < ess->playchns; i++, m <<= 1) { + if (ess->active & m) + suppress_jitter(ess->pch + i); + } + if (ess->active & m) + suppress_rec_jitter(&ess->rch); + agg_unlock(ess); +#endif + for (i = 0, m = 1; i < ess->playchns; i++, m <<= 1) { + if (ess->active & m) { + if (ess->curpwr <= PCI_POWERSTATE_D1) + chn_intr(ess->pch[i].channel); + else { + m = 0; + break; + } + } + } + if ((ess->active & m) + && ess->curpwr <= PCI_POWERSTATE_D1) + chn_intr(ess->rch.channel); + } +#ifdef AGG_JITTER_CORRECTION + else + agg_unlock(ess); +#endif + } if (status & HOSTINT_STAT_HWVOL) { - u_int event; + register u_int8_t event; + + agg_lock(ess); + event = AGG_RD(ess, PORT_HWVOL_MASTER, 1); + AGG_WR(ess, PORT_HWVOL_MASTER, HWVOL_NOP, 1); + agg_unlock(ess); - event = bus_space_read_1(ess->st, ess->sh, PORT_HWVOL_MASTER); switch (event) { - case HWVOL_MUTE: - mixer_hwvol_mute(ess->dev); - break; case HWVOL_UP: mixer_hwvol_step(ess->dev, 1, 1); break; @@ -849,22 +1685,15 @@ agg_intr(void *sc) case HWVOL_NOP: break; default: - device_printf(ess->dev, "%s: unknown HWVOL event 0x%x\n", - device_get_nameunit(ess->dev), event); + if (event & HWVOL_MUTE) { + mixer_hwvol_mute(ess->dev); + break; + } + device_printf(ess->dev, + "%s: unknown HWVOL event 0x%x\n", + device_get_nameunit(ess->dev), event); } - bus_space_write_1(ess->st, ess->sh, PORT_HWVOL_MASTER, - HWVOL_NOP); } - - for (i = 0; i < ess->playchns; i++) - if (ess->active & (1 << i)) { - suppress_jitter(ess->pch + i); - chn_intr(ess->pch[i].channel); - } -#if 0 /* XXX - RECORDING */ - if (ess->active & (1 << i)) - chn_intr(ess->rch.channel); -#endif } static void @@ -882,25 +1711,25 @@ setmap(void *arg, bus_dma_segment_t *segs, int nseg, int error) } static void * -dma_malloc(struct agg_info *sc, u_int32_t sz, bus_addr_t *phys) +dma_malloc(bus_dma_tag_t dmat, u_int32_t sz, bus_addr_t *phys) { void *buf; bus_dmamap_t map; - if (bus_dmamem_alloc(sc->parent_dmat, &buf, BUS_DMA_NOWAIT, &map)) + if (bus_dmamem_alloc(dmat, &buf, BUS_DMA_NOWAIT, &map)) return NULL; - if (bus_dmamap_load(sc->parent_dmat, map, buf, sz, setmap, phys, 0) - || !*phys) { - bus_dmamem_free(sc->parent_dmat, buf, map); + if (bus_dmamap_load(dmat, map, buf, sz, setmap, phys, 0) + || !*phys || map) { + bus_dmamem_free(dmat, buf, map); return NULL; } return buf; } static void -dma_free(struct agg_info *sc, void *buf) +dma_free(bus_dma_tag_t dmat, void *buf) { - bus_dmamem_free(sc->parent_dmat, buf, NULL); + bus_dmamem_free(dmat, buf, NULL); } static int @@ -934,7 +1763,6 @@ agg_attach(device_t dev) { struct agg_info *ess = NULL; u_int32_t data; - int mapped = 0; int regid = PCIR_BAR(0); struct resource *reg = NULL; struct ac97_info *codec = NULL; @@ -942,103 +1770,149 @@ agg_attach(device_t dev) struct resource *irq = NULL; void *ih = NULL; char status[SND_STATUSLEN]; - bus_addr_t offset; - int i; + int ret = 0; if ((ess = malloc(sizeof *ess, M_DEVBUF, M_NOWAIT | M_ZERO)) == NULL) { device_printf(dev, "cannot allocate softc\n"); - return ENXIO; + ret = ENOMEM; + goto bad; } ess->dev = dev; +#ifdef USING_MUTEX + mtx_init(&ess->lock, device_get_desc(dev), "hardware status lock", + MTX_DEF | MTX_RECURSE); + if (!mtx_initialized(&ess->lock)) { + device_printf(dev, "failed to create a mutex.\n"); + ret = ENOMEM; + goto bad; + } +#endif + ess->bufsz = pcm_getbuffersize(dev, 4096, AGG_DEFAULT_BUFSZ, 65536); + if (bus_dma_tag_create(/*parent*/ NULL, + /*align */ 4, 1 << (16+1), + /*limit */ MAESTRO_MAXADDR, BUS_SPACE_MAXADDR, + /*filter*/ NULL, NULL, + /*size */ ess->bufsz, 1, 0x3ffff, + /*flags */ 0, +#if __FreeBSD_version >= 501102 + /*lock */ busdma_lock_mutex, &Giant, +#endif + &ess->buf_dmat) != 0) { + device_printf(dev, "unable to create dma tag\n"); + ret = ENOMEM; + goto bad; + } if (bus_dma_tag_create(/*parent*/NULL, - /*alignment*/1 << WAVCACHE_BASEADDR_SHIFT, - /*boundary*/WPWA_MAXADDR + 1, - /*lowaddr*/MAESTRO_MAXADDR, /*highaddr*/BUS_SPACE_MAXADDR, - /*filter*/NULL, /*filterarg*/NULL, - /*maxsize*/ess->bufsz * (1 + AGG_MAXPLAYCH + 1), /*nsegments*/1, - /*maxsegz*/0x3ffff, /*flags*/0, /*lockfunc*/busdma_lock_mutex, - /*lockarg*/&Giant, &ess->parent_dmat) != 0) { + /*align */ 1 << WAVCACHE_BASEADDR_SHIFT, + 1 << (16+1), + /*limit */ MAESTRO_MAXADDR, BUS_SPACE_MAXADDR, + /*filter*/ NULL, NULL, + /*size */ 3*ess->bufsz, 1, 0x3ffff, + /*flags */ 0, +#if __FreeBSD_version >= 501102 + /*lock */ busdma_lock_mutex, &Giant, +#endif + &ess->stat_dmat) != 0) { device_printf(dev, "unable to create dma tag\n"); + ret = ENOMEM; goto bad; } - ess->stat = dma_malloc(ess, ess->bufsz * (1 + AGG_MAXPLAYCH + 1), - &ess->baseaddr); + /* Allocate the room for brain-damaging status buffer. */ + ess->stat = dma_malloc(ess->stat_dmat, 3*ess->bufsz, &ess->phys); if (ess->stat == NULL) { - device_printf(dev, "cannot allocate DMA memory\n"); + device_printf(dev, "cannot allocate status buffer\n"); + ret = ENOMEM; goto bad; } if (bootverbose) - device_printf(dev, "Maestro DMA base: %#llx\n", - (long long)ess->baseaddr); - offset = ess->bufsz; - for (i = 0; i < AGG_MAXPLAYCH; i++) { - ess->pch[i].offset = offset; - offset += ess->bufsz; - } - ess->rch.offset = offset; + device_printf(dev, "Maestro status/record buffer: %#llx\n", + (long long)ess->phys); - agg_power(ess, PPMI_D0); - DELAY(100000); + /* State D0-uninitialized. */ + ess->curpwr = PCI_POWERSTATE_D3; + pci_set_powerstate(dev, PCI_POWERSTATE_D0); data = pci_read_config(dev, PCIR_COMMAND, 2); data |= (PCIM_CMD_PORTEN|PCIM_CMD_BUSMASTEREN); pci_write_config(dev, PCIR_COMMAND, data, 2); data = pci_read_config(dev, PCIR_COMMAND, 2); - if (data & PCIM_CMD_PORTEN) { + /* Allocate resources. */ + if (data & PCIM_CMD_PORTEN) reg = bus_alloc_resource(dev, SYS_RES_IOPORT, ®id, 0, BUS_SPACE_UNRESTRICTED, 256, RF_ACTIVE); - if (reg != NULL) { - ess->reg = reg; - ess->regid = regid; - ess->st = rman_get_bustag(reg); - ess->sh = rman_get_bushandle(reg); - mapped++; - } - } - if (mapped == 0) { + if (reg != NULL) { + ess->reg = reg; + ess->regid = regid; + ess->st = rman_get_bustag(reg); + ess->sh = rman_get_bushandle(reg); + } else { device_printf(dev, "unable to map register space\n"); + ret = ENXIO; + goto bad; + } + irq = bus_alloc_resource(dev, SYS_RES_IRQ, &irqid, + 0, BUS_SPACE_UNRESTRICTED, 1, RF_ACTIVE | RF_SHAREABLE); + if (irq != NULL) { + ess->irq = irq; + ess->irqid = irqid; + } else { + device_printf(dev, "unable to map interrupt\n"); + ret = ENXIO; goto bad; } - agg_init(ess); - if (agg_rdcodec(NULL, ess, 0) == 0x80) { + /* Setup resources. */ + if (snd_setup_intr(dev, irq, INTR_MPSAFE, agg_intr, ess, &ih)) { + device_printf(dev, "unable to setup interrupt\n"); + ret = ENXIO; + goto bad; + } else + ess->ih = ih; + + /* Transition from D0-uninitialized to D0. */ + agg_lock(ess); + agg_power(ess, PCI_POWERSTATE_D0); + if (agg_rdcodec(ess, 0) == 0x80) { + /* XXX - TODO: PT101 */ device_printf(dev, "PT101 codec detected!\n"); + ret = ENXIO; goto bad; } codec = AC97_CREATE(dev, ess, agg_ac97); - if (codec == NULL) - goto bad; - if (mixer_init(dev, ac97_getmixerclass(), codec) == -1) + if (codec == NULL) { + device_printf(dev, "failed to create AC97 codec softc!\n"); + ret = ENOMEM; goto bad; - ess->codec = codec; - - irq = bus_alloc_resource(dev, SYS_RES_IRQ, &irqid, - 0, BUS_SPACE_UNRESTRICTED, 1, RF_ACTIVE | RF_SHAREABLE); - if (irq == NULL || snd_setup_intr(dev, irq, 0, agg_intr, ess, &ih)) { - device_printf(dev, "unable to map interrupt\n"); + } + if (mixer_init(dev, ac97_getmixerclass(), codec) == -1) { + device_printf(dev, "mixer initialization failed!\n"); + ret = ENXIO; goto bad; } - ess->irq = irq; - ess->irqid = irqid; - ess->ih = ih; - - snprintf(status, SND_STATUSLEN, "at I/O port 0x%lx irq %ld %s", - rman_get_start(reg), rman_get_start(irq),PCM_KLDSTRING(snd_maestro)); + ess->codec = codec; - if (pcm_register(dev, ess, AGG_MAXPLAYCH, 1)) + ret = pcm_register(dev, ess, AGG_MAXPLAYCH, 1); + if (ret) goto bad; mixer_hwvol_init(dev); + agg_power(ess, powerstate_init); for (data = 0; data < AGG_MAXPLAYCH; data++) - pcm_addchan(dev, PCMDIR_PLAY, &aggch_class, ess); -#if 0 /* XXX - RECORDING */ + pcm_addchan(dev, PCMDIR_PLAY, &aggpch_class, ess); pcm_addchan(dev, PCMDIR_REC, &aggrch_class, ess); -#endif + adjust_pchbase(ess->pch, ess->playchns, ess->bufsz); + + agg_unlock(ess); + + snprintf(status, SND_STATUSLEN, + "port 0x%lx-0x%lx irq %ld at device %d.%d on pci%d", + rman_get_start(reg), rman_get_end(reg), rman_get_start(irq), + pci_get_slot(dev), pci_get_function(dev), pci_get_bus(dev)); pcm_setstatus(dev, status); return 0; @@ -1052,16 +1926,20 @@ agg_attach(device_t dev) bus_release_resource(dev, SYS_RES_IRQ, irqid, irq); if (reg != NULL) bus_release_resource(dev, SYS_RES_IOPORT, regid, reg); - if (ess != NULL) { - agg_power(ess, PPMI_D3); - if (ess->stat != NULL) - dma_free(ess, ess->stat); - if (ess->parent_dmat != NULL) - bus_dma_tag_destroy(ess->parent_dmat); + if (ess->stat != NULL) + dma_free(ess->stat_dmat, ess->stat); + if (ess->stat_dmat != NULL) + bus_dma_tag_destroy(ess->stat_dmat); + if (ess->buf_dmat != NULL) + bus_dma_tag_destroy(ess->buf_dmat); +#ifdef USING_MUTEX + if (mtx_initialized(&ess->lock)) + mtx_destroy(&ess->lock); +#endif + if (ess != NULL) free(ess, M_DEVBUF); - } - return ENXIO; + return ret; } static int @@ -1069,24 +1947,37 @@ agg_detach(device_t dev) { struct agg_info *ess = pcm_getdevinfo(dev); int r; + u_int16_t icr; + + icr = AGG_RD(ess, PORT_HOSTINT_CTRL, 2); + AGG_WR(ess, PORT_HOSTINT_CTRL, 0, 2); + + agg_lock(ess); + if (ess->active) { + AGG_WR(ess, PORT_HOSTINT_CTRL, icr, 2); + agg_unlock(ess); + return EBUSY; + } + agg_unlock(ess); r = pcm_unregister(dev); - if (r) + if (r) { + AGG_WR(ess, PORT_HOSTINT_CTRL, icr, 2); return r; + } - ess = pcm_getdevinfo(dev); - dma_free(ess, ess->stat); - - /* Power down everything except clock and vref. */ - agg_wrcodec(NULL, ess, AC97_REG_POWER, 0xd700); - DELAY(20); - bus_space_write_4(ess->st, ess->sh, PORT_RINGBUS_CTRL, 0); - agg_power(ess, PPMI_D3); + agg_lock(ess); + agg_power(ess, PCI_POWERSTATE_D3); bus_teardown_intr(dev, ess->irq, ess->ih); bus_release_resource(dev, SYS_RES_IRQ, ess->irqid, ess->irq); bus_release_resource(dev, SYS_RES_IOPORT, ess->regid, ess->reg); - bus_dma_tag_destroy(ess->parent_dmat); + dma_free(ess->stat_dmat, ess->stat); + bus_dma_tag_destroy(ess->stat_dmat); + bus_dma_tag_destroy(ess->buf_dmat); +#ifdef USING_MUTEX + mtx_destroy(&ess->lock); +#endif free(ess, M_DEVBUF); return 0; } @@ -1095,25 +1986,18 @@ static int agg_suspend(device_t dev) { struct agg_info *ess = pcm_getdevinfo(dev); - int i, x; +#ifndef USING_MUTEX + int x; x = spltty(); - wp_stoptimer(ess); - bus_space_write_2(ess->st, ess->sh, PORT_HOSTINT_CTRL, 0); - - for (i = 0; i < ess->playchns; i++) - aggch_stop_dac(ess->pch + i); - -#if 0 /* XXX - RECORDING */ - aggch_stop_adc(&ess->rch); #endif + AGG_WR(ess, PORT_HOSTINT_CTRL, 0, 2); + agg_lock(ess); + agg_power(ess, PCI_POWERSTATE_D3); + agg_unlock(ess); +#ifndef USING_MUTEX splx(x); - /* Power down everything except clock. */ - agg_wrcodec(NULL, ess, AC97_REG_POWER, 0xdf00); - DELAY(20); - bus_space_write_4(ess->st, ess->sh, PORT_RINGBUS_CTRL, 0); - DELAY(1); - agg_power(ess, PPMI_D3); +#endif return 0; } @@ -1121,30 +2005,32 @@ agg_suspend(device_t dev) static int agg_resume(device_t dev) { - int i, x; + int i; struct agg_info *ess = pcm_getdevinfo(dev); - - agg_power(ess, PPMI_D0); - DELAY(100000); - agg_init(ess); - if (mixer_reinit(dev)) { - device_printf(dev, "unable to reinitialize the mixer\n"); - return ENXIO; - } +#ifndef USING_MUTEX + int x; x = spltty(); +#endif for (i = 0; i < ess->playchns; i++) if (ess->active & (1 << i)) aggch_start_dac(ess->pch + i); -#if 0 /* XXX - RECORDING */ if (ess->active & (1 << i)) aggch_start_adc(&ess->rch); + + agg_lock(ess); + if (!ess->active) + agg_power(ess, powerstate_init); + agg_unlock(ess); +#ifndef USING_MUTEX + splx(x); #endif - if (ess->active) { - set_timer(ess); - wp_starttimer(ess); + + if (mixer_reinit(dev)) { + device_printf(dev, "unable to reinitialize the mixer\n"); + return ENXIO; } - splx(x); + return 0; } @@ -1152,17 +2038,11 @@ static int agg_shutdown(device_t dev) { struct agg_info *ess = pcm_getdevinfo(dev); - int i; - wp_stoptimer(ess); - bus_space_write_2(ess->st, ess->sh, PORT_HOSTINT_CTRL, 0); + agg_lock(ess); + agg_power(ess, PCI_POWERSTATE_D3); + agg_unlock(ess); - for (i = 0; i < ess->playchns; i++) - aggch_stop_dac(ess->pch + i); - -#if 0 /* XXX - RECORDING */ - aggch_stop_adc(&ess->rch); -#endif return 0; } @@ -1184,6 +2064,8 @@ static driver_t agg_driver = { PCM_SOFTC_SIZE, }; +/*static devclass_t pcm_devclass;*/ + DRIVER_MODULE(snd_maestro, pci, agg_driver, pcm_devclass, 0, 0); MODULE_DEPEND(snd_maestro, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(snd_maestro, 1); diff --git a/sys/dev/sound/pci/maestro_reg.h b/sys/dev/sound/pci/maestro_reg.h index 41ce351..ab35423 100644 --- a/sys/dev/sound/pci/maestro_reg.h +++ b/sys/dev/sound/pci/maestro_reg.h @@ -23,7 +23,7 @@ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * - * $Id: maestro_reg.h,v 1.10 2000/08/29 17:27:29 taku Exp $ + * maestro_reg.h,v 1.13 2001/11/11 18:29:46 taku Exp * $FreeBSD$ */ @@ -41,10 +41,13 @@ /* Chip configurations */ #define CONF_MAESTRO 0x50 +#define MAESTRO_PMC 0x08000000 +#define MAESTRO_SPDIF 0x01000000 +#define MAESTRO_HWVOL 0x00800000 #define MAESTRO_CHIBUS 0x00100000 #define MAESTRO_POSTEDWRITE 0x00000080 #define MAESTRO_DMA_PCITIMING 0x00000040 -#define MAESTRO_SWAP_LR 0x00000010 +#define MAESTRO_SWAP_LR 0x00000020 /* ACPI configurations */ #define CONF_ACPI_STOPCLOCK 0x54 @@ -135,12 +138,15 @@ #define HOSTINT_STAT_SB 0x01 /* Hardware volume */ +#define PORT_HWVOL_CTRL 0x1b /* BYTE RW */ +#define HWVOL_CTRL_SPLIT_SHADOW 0x01 + #define PORT_HWVOL_VOICE_SHADOW 0x1c /* BYTE RW */ #define PORT_HWVOL_VOICE 0x1d /* BYTE RW */ #define PORT_HWVOL_MASTER_SHADOW 0x1e /* BYTE RW */ #define PORT_HWVOL_MASTER 0x1f /* BYTE RW */ #define HWVOL_NOP 0x88 -#define HWVOL_MUTE 0x99 +#define HWVOL_MUTE 0x11 #define HWVOL_UP 0xaa #define HWVOL_DOWN 0x66 @@ -163,8 +169,6 @@ #define RINGBUS_CTRL_RINGBUS_ENABLED 0x20000000 #define RINGBUS_CTRL_ACLINK_ENABLED 0x10000000 #define RINGBUS_CTRL_AC97_SWRESET 0x08000000 -#define RINGBUS_CTRL_IODMA_PLAYBACK_ENABLED 0x04000000 -#define RINGBUS_CTRL_IODMA_RECORD_ENABLED 0x02000000 #define RINGBUS_SRC_MIC 20 #define RINGBUS_SRC_I2S 16 @@ -182,6 +186,15 @@ #define RINGBUS_DEST_DSOUND_IN 4 #define RINGBUS_DEST_ASSP_IN 5 +/* Ring bus control B */ +#define PORT_RINGBUS_CTRL_B 0x38 /* BYTE RW */ +#define RINGBUS_CTRL_SSPE 0x40 +#define RINGBUS_CTRL_2ndCODEC 0x20 +#define RINGBUS_CTRL_SPDIF 0x10 +#define RINGBUS_CTRL_ITB_DISABLE 0x08 +#define RINGBUS_CTRL_CODEC_ID_MASK 0x03 +#define RINGBUS_CTRL_CODEC_ID_AC98 2 + /* General Purpose I/O */ #define PORT_GPIO_DATA 0x60 /* WORD RW */ #define PORT_GPIO_MASK 0x64 /* WORD RW */ @@ -297,22 +310,35 @@ /* APU register 4 */ #define APUREG_WAVESPACE 4 -#define APU_STEREO 0x8000 -#define APU_USE_SYSMEM 0x4000 -#define APU_PCMBAR_MASK 0x6000 #define APU_64KPAGE_MASK 0xff00 -/* PCM Base Address Register selection */ -#define APU_PCMBAR_SHIFT 13 - /* 64KW (==128KB) Page */ #define APU_64KPAGE_SHIFT 8 +/* Wave Processor Wavespace Address */ +#define WPWA_MAX ((1 << 22) - 1) +#define WPWA_STEREO (1 << 23) +#define WPWA_USE_SYSMEM (1 << 22) + +#define WPWA_WTBAR_SHIFT(wtsz) WPWA_WTBAR_SHIFT_##wtsz +#define WPWA_WTBAR_SHIFT_1 15 +#define WPWA_WTBAR_SHIFT_2 16 +#define WPWA_WTBAR_SHIFT_4 17 +#define WPWA_WTBAR_SHIFT_8 18 + +#define WPWA_PCMBAR_SHIFT 20 + /* APU register 5 - 7 */ #define APUREG_CURPTR 5 #define APUREG_ENDPTR 6 #define APUREG_LOOPLEN 7 +/* APU register 8 */ +#define APUREG_EFFECT_GAIN 8 + +/* Effect gain? */ +#define APUREG_EFFECT_GAIN_MASK 0x00ff + /* APU register 9 */ #define APUREG_AMPLITUDE 9 #define APU_AMPLITUDE_NOW_MASK 0xff00 @@ -338,11 +364,20 @@ #define PAN_FRONT 0x08 #define PAN_LEFT 0x10 +/* Source routing. */ +#define APUREG_ROUTING 11 +#define APU_INVERT_POLARITY_B 0x8000 +#define APU_DATASRC_B_MASK 0x7f00 +#define APU_INVERT_POLARITY_A 0x0080 +#define APU_DATASRC_A_MASK 0x007f + +#define APU_DATASRC_A_SHIFT 0 +#define APU_DATASRC_B_SHIFT 8 + /* ----------------------------- * Limits. */ -#define WPWA_MAX ((1 << 22) - 1) #define WPWA_MAXADDR ((1 << 23) - 1) #define MAESTRO_MAXADDR ((1 << 28) - 1) |