From 12ab72d345dd5e468fb55507b26dde539bc710eb Mon Sep 17 00:00:00 2001 From: Alexander Leidinger Date: Sat, 17 Jun 2006 14:36:44 +0000 Subject: [PATCH] dd the envy24 driver as is to the tree. It's not connected to the build yet. More commits to follow. I got no response from the author, but since the driver is BSD licensed I don't think he will complain. :-) I got it from http://people.freebsd.org/~lofi/envy24.tar.gz Written by: Katsurajima Naoto --- sys/dev/sound/pci/ak452x.c | 247 +++ sys/dev/sound/pci/ak452x.h | 91 + sys/dev/sound/pci/envy24.c | 2445 ++++++++++++++++++++++ sys/dev/sound/pci/envy24.h | 482 +++++ sys/dev/sound/pci/envy24ht.c | 2445 ++++++++++++++++++++++ sys/dev/sound/pci/envy24ht.h | 482 +++++ sys/dev/sound/pci/spicds.c | 247 +++ sys/dev/sound/pci/spicds.h | 91 + sys/modules/sound/driver/ak452x/Makefile | 8 + sys/modules/sound/driver/envy24/Makefile | 8 + 10 files changed, 6546 insertions(+) create mode 100644 sys/dev/sound/pci/ak452x.c create mode 100644 sys/dev/sound/pci/ak452x.h create mode 100644 sys/dev/sound/pci/envy24.c create mode 100644 sys/dev/sound/pci/envy24.h create mode 100644 sys/dev/sound/pci/envy24ht.c create mode 100644 sys/dev/sound/pci/envy24ht.h create mode 100644 sys/dev/sound/pci/spicds.c create mode 100644 sys/dev/sound/pci/spicds.h create mode 100644 sys/modules/sound/driver/ak452x/Makefile create mode 100644 sys/modules/sound/driver/envy24/Makefile diff --git a/sys/dev/sound/pci/ak452x.c b/sys/dev/sound/pci/ak452x.c new file mode 100644 index 000000000000..2ca3501ddf04 --- /dev/null +++ b/sys/dev/sound/pci/ak452x.c @@ -0,0 +1,247 @@ +/* + * Copyright (c) 2001 Katsurajima Naoto + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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, WHETHERIN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THEPOSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include + +#include + +MALLOC_DEFINE(M_AK452X, "ak452x", "ak452x codec"); + +#define AK452X_NAMELEN 16 +struct ak452x_info { + device_t dev; + ak452x_ctrl ctrl; + void *devinfo; + int num; /* number of this device */ + unsigned int type; /* codec type */ + unsigned int cif; /* Controll data Interface Format (0/1) */ + unsigned int format; /* data format and master clock frequency */ + unsigned int dvc; /* De-emphasis and Volume Control */ + unsigned int left, right; + char name[AK452X_NAMELEN]; + void *lock; +}; + +static void +ak452x_wrbit(struct ak452x_info *codec, int bit) +{ + unsigned int cs, cdti; + if (codec->cif) + cs = 1; + else + cs = 0; + if (bit) + cdti = 1; + else + cdti = 0; + codec->ctrl(codec->devinfo, cs, 0, cdti); + DELAY(1); + codec->ctrl(codec->devinfo, cs, 1, cdti); + DELAY(1); + + return; +} + +static void +ak452x_wrcd(struct ak452x_info *codec, int reg, u_int8_t val) +{ + int mask; + +#if(0) + device_printf(codec->dev, "ak452x_wrcd(codec, 0x%02x, 0x%02x)\n", reg, val); +#endif + /* start */ + if (codec->cif) + codec->ctrl(codec->devinfo, 1, 1, 0); + else + codec->ctrl(codec->devinfo, 0, 1, 0); + DELAY(1); + /* chip address */ + ak452x_wrbit(codec, 1); + ak452x_wrbit(codec, 0); + /* write */ + ak452x_wrbit(codec, 1); + /* register address */ + for (mask = 0x10; mask != 0; mask >>= 1) + ak452x_wrbit(codec, reg & mask); + /* data */ + for (mask = 0x80; mask != 0; mask >>= 1) + ak452x_wrbit(codec, val & mask); + /* stop */ + DELAY(1); + if (codec->cif) { + codec->ctrl(codec->devinfo, 0, 1, 0); + DELAY(1); + codec->ctrl(codec->devinfo, 1, 1, 0); + } + else { + codec->ctrl(codec->devinfo, 1, 1, 0); + } + + return; +} + +struct ak452x_info * +ak452x_create(device_t dev, void *devinfo, int num, ak452x_ctrl ctrl) +{ + struct ak452x_info *codec; + +#if(0) + device_printf(dev, "ak452x_create(dev, devinfo, %d, ctrl)\n", num); +#endif + codec = (struct ak452x_info *)malloc(sizeof *codec, M_AK452X, M_NOWAIT); + if (codec == NULL) + return NULL; + + snprintf(codec->name, AK452X_NAMELEN, "%s:ak452x%d", device_get_nameunit(dev), num); + codec->lock = snd_mtxcreate(codec->name); + codec->dev = dev; + codec->ctrl = ctrl; + codec->devinfo = devinfo; + codec->num = num; + codec->type = AK452X_TYPE_4524; + codec->cif = 0; + codec->format = AK452X_FORMAT_I2S | AK452X_FORMAT_256FSN | AK452X_FORMAT_1X; + codec->dvc = AK452X_DVC_DEMOFF | AK452X_DVC_ZTM1024 | AK452X_DVC_ZCE; + + return codec; +} + +void +ak452x_destroy(struct ak452x_info *codec) +{ + snd_mtxfree(codec->lock); + free(codec, M_AK452X); +} + +void +ak452x_settype(struct ak452x_info *codec, unsigned int type) +{ + snd_mtxlock(codec->lock); + codec->type = type; + snd_mtxunlock(codec->lock); +} + +void +ak452x_setcif(struct ak452x_info *codec, unsigned int cif) +{ + snd_mtxlock(codec->lock); + codec->cif = cif; + snd_mtxunlock(codec->lock); +} + +void +ak452x_setformat(struct ak452x_info *codec, unsigned int format) +{ + snd_mtxlock(codec->lock); + codec->format = format; + snd_mtxunlock(codec->lock); +} + +void +ak452x_setdvc(struct ak452x_info *codec, unsigned int dvc) +{ + snd_mtxlock(codec->lock); + codec->type = dvc; + snd_mtxunlock(codec->lock); +} + +void +ak452x_init(struct ak452x_info *codec) +{ +#if(0) + device_printf(codec->dev, "ak452x_init(codec)\n"); +#endif + snd_mtxlock(codec->lock); + /* power off */ + ak452x_wrcd(codec, AK4524_POWER, 0); + /* set parameter */ + ak452x_wrcd(codec, AK4524_FORMAT, codec->format); + ak452x_wrcd(codec, AK4524_DVC, codec->dvc); + /* power on */ + ak452x_wrcd(codec, AK4524_POWER, AK452X_POWER_PWDA | AK452X_POWER_PWAD | AK452X_POWER_PWVR); + /* free reset register */ + ak452x_wrcd(codec, AK4524_RESET, AK452X_RESET_RSDA | AK452X_RESET_RSAD); + snd_mtxunlock(codec->lock); +} + +void +ak452x_reinit(struct ak452x_info *codec) +{ + snd_mtxlock(codec->lock); + /* reset */ + ak452x_wrcd(codec, AK4524_RESET, 0); + /* set parameter */ + ak452x_wrcd(codec, AK4524_FORMAT, codec->format); + ak452x_wrcd(codec, AK4524_DVC, codec->dvc); + /* free reset register */ + ak452x_wrcd(codec, AK4524_RESET, AK452X_RESET_RSDA | AK452X_RESET_RSAD); + snd_mtxunlock(codec->lock); +} + +void +ak452x_set(struct ak452x_info *codec, int dir, unsigned int left, unsigned int right) +{ +#if(0) + device_printf(codec->dev, "ak452x_set(codec, %d, %d, %d)\n", dir, left, right); +#endif + snd_mtxlock(codec->lock); + if (left >= 100) + left = 127; + else + left = left * 127 / 100; + if (right >= 100) + right = 127; + else + right = right * 127 / 100; + if (dir == PCMDIR_REC && codec->type == AK452X_TYPE_4524) { +#if(0) + device_printf(codec->dev, "ak452x_set(): AK4524(REC) %d/%d\n", left, right); +#endif + ak452x_wrcd(codec, AK4524_LIPGA, left); + ak452x_wrcd(codec, AK4524_RIPGA, right); + } + if (dir == PCMDIR_PLAY && codec->type == AK452X_TYPE_4524) { +#if(0) + device_printf(codec->dev, "ak452x_set(): AK4524(PLAY) %d/%d\n", left, right); +#endif + ak452x_wrcd(codec, AK4524_LOATT, left); + ak452x_wrcd(codec, AK4524_ROATT, right); + } + if (dir == PCMDIR_PLAY && codec->type == AK452X_TYPE_4528) { +#if(0) + device_printf(codec->dev, "ak452x_set(): AK4528(PLAY) %d/%d\n", left, right); +#endif + ak452x_wrcd(codec, AK4528_LOATT, left); + ak452x_wrcd(codec, AK4528_ROATT, right); + } + snd_mtxunlock(codec->lock); +} + +MODULE_DEPEND(snd_ak452x, snd_pcm, PCM_MINVER, PCM_PREFVER, PCM_MAXVER); +MODULE_VERSION(snd_ak452x, 1); diff --git a/sys/dev/sound/pci/ak452x.h b/sys/dev/sound/pci/ak452x.h new file mode 100644 index 000000000000..1df631fa70e8 --- /dev/null +++ b/sys/dev/sound/pci/ak452x.h @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2001 Katsurajima Naoto + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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, WHETHERIN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THEPOSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +/* supported CODECs */ +#define AK452X_TYPE_4524 0 +#define AK452X_TYPE_4528 1 + +/* AK4524/AK4528 control registers */ +#define AK4524_POWER 0x00 +#define AK4528_POWER 0x00 +#define AK452X_POWER_PWDA 0x01 +#define AK452X_POWER_PWAD 0x02 +#define AK452X_POWER_PWVR 0x04 +#define AK4524_RESET 0x01 +#define AK4528_RESET 0x01 +#define AK452X_RESET_RSDA 0x01 +#define AK452X_RESET_RSAD 0x02 +#define AK4524_FORMAT 0x02 +#define AK4528_FORMAT 0x02 +#define AK452X_FORMAT_1X 0x00 +#define AK452X_FORMAT_2X 0x01 +#define AK452X_FORMAT_4X1 0x02 +#define AK452X_FORMAT_4X2 0x03 +#define AK452X_FORMAT_256FSN 0x00 +#define AK452X_FORMAT_512FSN 0x04 +#define AK452X_FORMAT_1024FSN 0x08 +#define AK452X_FORMAT_384FSN 0x10 +#define AK452X_FORMAT_768FSN 0x14 +#define AK452X_FORMAT_OM24IL16 0x00 +#define AK452X_FORMAT_OM24IL20 0x20 +#define AK452X_FORMAT_OM24IM24 0x40 +#define AK452X_FORMAT_I2S 0x60 +#define AK452X_FORMAT_OM24IL24 0x80 +#define AK4524_DVC 0x03 +#define AK452X_DVC_DEM441 0x00 +#define AK452X_DVC_DEMOFF 0x01 +#define AK452X_DVC_DEM48 0x02 +#define AK452X_DVC_DEM32 0x03 +#define AK452X_DVC_ZTM256 0x00 +#define AK452X_DVC_ZTM512 0x04 +#define AK452X_DVC_ZTM1024 0x08 +#define AK452X_DVC_ZTM2048 0x0c +#define AK452X_DVC_ZCE 0x10 +#define AK452X_DVC_HPFL 0x04 +#define AK452X_DVC_HPFR 0x08 +#define AK452X_DVC_SMUTE 0x80 +#define AK4524_LIPGA 0x04 +#define AK4524_RIPGA 0x05 +#define AK4524_LOATT 0x06 +#define AK4524_ROATT 0x07 +#define AK4528_LOATT 0x04 +#define AK4528_ROATT 0x05 + +struct ak452x_info; + +typedef void (*ak452x_ctrl)(void *, unsigned int, unsigned int, unsigned int); + +struct ak452x_info *ak452x_create(device_t dev, void *devinfo, int num, ak452x_ctrl); +void ak452x_destroy(struct ak452x_info *codec); +void ak452x_settype(struct ak452x_info *codec, unsigned int type); +void ak452x_setcif(struct ak452x_info *codec, unsigned int cif); +void ak452x_setformat(struct ak452x_info *codec, unsigned int format); +void ak452x_setdvc(struct ak452x_info *codec, unsigned int dvc); +void ak452x_init(struct ak452x_info *codec); +void ak452x_reinit(struct ak452x_info *codec); +void ak452x_set(struct ak452x_info *codec, int dir, unsigned int left, unsigned int right); diff --git a/sys/dev/sound/pci/envy24.c b/sys/dev/sound/pci/envy24.c new file mode 100644 index 000000000000..c23efc331724 --- /dev/null +++ b/sys/dev/sound/pci/envy24.c @@ -0,0 +1,2445 @@ +/* + * Copyright (c) 2001 Katsurajima Naoto + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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, WHETHERIN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THEPOSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include +#include +#include +#include + +#include +#include + +#include "mixer_if.h" + +MALLOC_DEFINE(M_ENVY24, "envy24", "envy24 audio"); + +/* -------------------------------------------------------------------- */ + +struct sc_info; + +#define ENVY24_PLAY_CHNUM 10 +#define ENVY24_REC_CHNUM 12 +#define ENVY24_PLAY_BUFUNIT (4 /* byte/sample */ * 10 /* channel */) +#define ENVY24_REC_BUFUNIT (4 /* byte/sample */ * 12 /* channel */) +#define ENVY24_SAMPLE_NUM 4096 + +#define ENVY24_TIMEOUT 1000 + +#define ENVY24_DEFAULT_FORMAT (AFMT_STEREO | AFMT_S16_LE) + +#define ENVY24_NAMELEN 32 + +typedef volatile u_int32_t sample32_t; + +/* channel registers */ +struct sc_chinfo { + struct snd_dbuf *buffer; + struct pcm_channel *channel; + struct sc_info *parent; + int dir; + unsigned num; /* hw channel number */ + + /* channel information */ + u_int32_t format; + u_int32_t speed; + u_int32_t blk; /* hw block size(dword) */ + + /* format conversion structure */ + u_int8_t *data; + unsigned int size; /* data buffer size(byte) */ + int unit; /* sample size(byte) */ + unsigned int offset; /* samples number offset */ + void (*emldma)(struct sc_chinfo *); + + /* flags */ + int run; +}; + +/* codec interface entrys */ +struct codec_entry { + void *(*create)(device_t dev, void *devinfo, int dir, int num); + void (*destroy)(void *codec); + void (*init)(void *codec); + void (*reinit)(void *codec); + void (*setvolume)(void *codec, int dir, unsigned int left, unsigned int right); + void (*setrate)(void *codec, int which, int rate); +}; + +/* system configuration information */ +struct cfg_info { + char *name; + u_int16_t subvendor, subdevice; + u_int8_t scfg, acl, i2s, spdif; + u_int8_t gpiomask, gpiostate, gpiodir; + u_int8_t free; + struct codec_entry *codec; +}; + +/* device private data */ +struct sc_info { + device_t dev; + void *lock; + + /* Control/Status registor */ + struct resource *cs; + int csid; + bus_space_tag_t cst; + bus_space_handle_t csh; + /* DDMA registor */ + struct resource *ddma; + int ddmaid; + bus_space_tag_t ddmat; + bus_space_handle_t ddmah; + /* Consumer Section DMA Channel Registers */ + struct resource *ds; + int dsid; + bus_space_tag_t dst; + bus_space_handle_t dsh; + /* MultiTrack registor */ + struct resource *mt; + int mtid; + bus_space_tag_t mtt; + bus_space_handle_t mth; + /* DMA tag */ + bus_dma_tag_t dmat; + /* IRQ resource */ + struct resource *irq; + int irqid; + void *ih; + + /* system configuration data */ + struct cfg_info *cfg; + + /* ADC/DAC number and info */ + int adcn, dacn; + void *adc[4], *dac[4]; + + /* mixer control data */ + u_int32_t src; + u_int8_t left[ENVY24_CHAN_NUM]; + u_int8_t right[ENVY24_CHAN_NUM]; + + /* Play/Record DMA fifo */ + sample32_t *pbuf; + sample32_t *rbuf; + u_int32_t psize, rsize; /* DMA buffer size(byte) */ + u_int16_t blk[2]; /* transfer check blocksize(dword) */ + bus_dmamap_t pmap, rmap; + + /* current status */ + u_int32_t speed; + int run[2]; + u_int16_t intr[2]; + struct pcmchan_caps caps[2]; + + /* channel info table */ + unsigned chnum; + struct sc_chinfo chan[11]; +}; + +/* -------------------------------------------------------------------- */ + +/* + * prototypes + */ + +/* DMA emulator */ +static void envy24_p8u(struct sc_chinfo *); +static void envy24_p16sl(struct sc_chinfo *); +static void envy24_p32sl(struct sc_chinfo *); +static void envy24_r16sl(struct sc_chinfo *); +static void envy24_r32sl(struct sc_chinfo *); + +/* channel interface */ +static void *envy24chan_init(kobj_t, void *, struct snd_dbuf *, struct pcm_channel *, int); +static int envy24chan_setformat(kobj_t, void *, u_int32_t); +static int envy24chan_setspeed(kobj_t, void *, u_int32_t); +static int envy24chan_setblocksize(kobj_t, void *, u_int32_t); +static int envy24chan_trigger(kobj_t, void *, int); +static int envy24chan_getptr(kobj_t, void *); +static struct pcmchan_caps *envy24chan_getcaps(kobj_t, void *); + +/* mixer interface */ +static int envy24mixer_init(struct snd_mixer *); +static int envy24mixer_reinit(struct snd_mixer *); +static int envy24mixer_uninit(struct snd_mixer *); +static int envy24mixer_set(struct snd_mixer *, unsigned, unsigned, unsigned); +static u_int32_t envy24mixer_setrecsrc(struct snd_mixer *, u_int32_t); + +/* M-Audio Delta series AK4524 access interface */ +static void *envy24_delta_ak4524_create(device_t, void *, int, int); +static void envy24_delta_ak4524_destroy(void *); +static void envy24_delta_ak4524_init(void *); +static void envy24_delta_ak4524_reinit(void *); +static void envy24_delta_ak4524_setvolume(void *, int, unsigned int, unsigned int); + +/* -------------------------------------------------------------------- */ + +/* + system constant tables +*/ + +/* API -> hardware channel map */ +static unsigned envy24_chanmap[ENVY24_CHAN_NUM] = { + ENVY24_CHAN_PLAY_SPDIF, /* 0 */ + ENVY24_CHAN_PLAY_DAC1, /* 1 */ + ENVY24_CHAN_PLAY_DAC2, /* 2 */ + ENVY24_CHAN_PLAY_DAC3, /* 3 */ + ENVY24_CHAN_PLAY_DAC4, /* 4 */ + ENVY24_CHAN_REC_MIX, /* 5 */ + ENVY24_CHAN_REC_SPDIF, /* 6 */ + ENVY24_CHAN_REC_ADC1, /* 7 */ + ENVY24_CHAN_REC_ADC2, /* 8 */ + ENVY24_CHAN_REC_ADC3, /* 9 */ + ENVY24_CHAN_REC_ADC4, /* 10 */ +}; + +/* mixer -> API channel map. see above */ +static int envy24_mixmap[] = { + -1, /* Master output level. It is depend on codec support */ + -1, /* Treble level of all output channels */ + -1, /* Bass level of all output channels */ + -1, /* Volume of synthesier input */ + 0, /* Output level for the audio device */ + -1, /* Output level for the PC speaker */ + 7, /* line in jack */ + -1, /* microphone jack */ + -1, /* CD audio input */ + -1, /* Recording monitor */ + 1, /* alternative codec */ + -1, /* global recording level */ + -1, /* Input gain */ + -1, /* Output gain */ + 8, /* Input source 1 */ + 9, /* Input source 2 */ + 10, /* Input source 3 */ + 6, /* Digital (input) 1 */ + -1, /* Digital (input) 2 */ + -1, /* Digital (input) 3 */ + -1, /* Phone input */ + -1, /* Phone output */ + -1, /* Video/TV (audio) in */ + -1, /* Radio in */ + -1, /* Monitor volume */ +}; + +/* variable rate audio */ +static u_int32_t envy24_speed[] = { + 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, + 12000, 11025, 9600, 8000, 0 +}; + +/* known boards configuration */ +static struct codec_entry delta_codec = { + envy24_delta_ak4524_create, + envy24_delta_ak4524_destroy, + envy24_delta_ak4524_init, + envy24_delta_ak4524_reinit, + envy24_delta_ak4524_setvolume, + NULL, /* setrate */ +}; + +static struct cfg_info cfg_table[] = { + { + "Envy24 audio(M Audio Delta Dio 2496)", + 0x1412, 0xd631, + 0x10, 0x80, 0xf0, 0x03, + 0xff, 0x00, 0x00, + 0, + &delta_codec, + }, + { + "Envy24 audio(Generic)", + 0, 0, + 0x0f, 0x00, 0x01, 0x03, + 0xff, 0x00, 0x00, + 0, + &delta_codec, /* default codec routines */ + } +}; + +static u_int32_t envy24_recfmt[] = { + AFMT_STEREO | AFMT_S16_LE, + AFMT_STEREO | AFMT_S32_LE, + 0 +}; +static struct pcmchan_caps envy24_reccaps = {8000, 96000, envy24_recfmt, 0}; + +static u_int32_t envy24_playfmt[] = { + AFMT_STEREO | AFMT_U8, + AFMT_STEREO | AFMT_S16_LE, + AFMT_STEREO | AFMT_S32_LE, + 0 +}; + +static struct pcmchan_caps envy24_playcaps = {8000, 96000, envy24_playfmt, 0}; + +struct envy24_emldma { + u_int32_t format; + void (*emldma)(struct sc_chinfo *); + int unit; +}; + +static struct envy24_emldma envy24_pemltab[] = { + {AFMT_STEREO | AFMT_U8, envy24_p8u, 2}, + {AFMT_STEREO | AFMT_S16_LE, envy24_p16sl, 4}, + {AFMT_STEREO | AFMT_S32_LE, envy24_p32sl, 8}, + {0, NULL, 0} +}; + +static struct envy24_emldma envy24_remltab[] = { + {AFMT_STEREO | AFMT_S16_LE, envy24_r16sl, 4}, + {AFMT_STEREO | AFMT_S32_LE, envy24_r32sl, 8}, + {0, NULL, 0} +}; + +/* -------------------------------------------------------------------- */ + +/* common routines */ +static u_int32_t +envy24_rdcs(struct sc_info *sc, int regno, int size) +{ + switch (size) { + case 1: + return bus_space_read_1(sc->cst, sc->csh, regno); + case 2: + return bus_space_read_2(sc->cst, sc->csh, regno); + case 4: + return bus_space_read_4(sc->cst, sc->csh, regno); + default: + return 0xffffffff; + } +} + +static void +envy24_wrcs(struct sc_info *sc, int regno, u_int32_t data, int size) +{ + switch (size) { + case 1: + bus_space_write_1(sc->cst, sc->csh, regno, data); + break; + case 2: + bus_space_write_2(sc->cst, sc->csh, regno, data); + break; + case 4: + bus_space_write_4(sc->cst, sc->csh, regno, data); + break; + } +} + +static u_int32_t +envy24_rdmt(struct sc_info *sc, int regno, int size) +{ + switch (size) { + case 1: + return bus_space_read_1(sc->mtt, sc->mth, regno); + case 2: + return bus_space_read_2(sc->mtt, sc->mth, regno); + case 4: + return bus_space_read_4(sc->mtt, sc->mth, regno); + default: + return 0xffffffff; + } +} + +static void +envy24_wrmt(struct sc_info *sc, int regno, u_int32_t data, int size) +{ + switch (size) { + case 1: + bus_space_write_1(sc->mtt, sc->mth, regno, data); + break; + case 2: + bus_space_write_2(sc->mtt, sc->mth, regno, data); + break; + case 4: + bus_space_write_4(sc->mtt, sc->mth, regno, data); + break; + } +} + +static u_int32_t +envy24_rdci(struct sc_info *sc, int regno) +{ + envy24_wrcs(sc, ENVY24_CCS_INDEX, regno, 1); + return envy24_rdcs(sc, ENVY24_CCS_DATA, 1); +} + +static void +envy24_wrci(struct sc_info *sc, int regno, u_int32_t data) +{ + envy24_wrcs(sc, ENVY24_CCS_INDEX, regno, 1); + envy24_wrcs(sc, ENVY24_CCS_DATA, data, 1); +} + +/* -------------------------------------------------------------------- */ + +/* I2C port/E2PROM access routines */ + +static int +envy24_rdi2c(struct sc_info *sc, u_int32_t dev, u_int32_t addr) +{ + u_int32_t data; + int i; + +#if(0) + device_printf(sc->dev, "envy24_rdi2c(sc, 0x%02x, 0x%02x)\n", dev, addr); +#endif + for (i = 0; i < ENVY24_TIMEOUT; i++) { + data = envy24_rdcs(sc, ENVY24_CCS_I2CSTAT, 1); + if ((data & ENVY24_CCS_I2CSTAT_BSY) == 0) + break; + DELAY(32); /* 31.25kHz */ + } + if (i == ENVY24_TIMEOUT) { + return -1; + } + envy24_wrcs(sc, ENVY24_CCS_I2CADDR, addr, 1); + envy24_wrcs(sc, ENVY24_CCS_I2CDEV, + (dev & ENVY24_CCS_I2CDEV_ADDR) | ENVY24_CCS_I2CDEV_RD, 1); + for (i = 0; i < ENVY24_TIMEOUT; i++) { + data = envy24_rdcs(sc, ENVY24_CCS_I2CSTAT, 1); + if ((data & ENVY24_CCS_I2CSTAT_BSY) == 0) + break; + DELAY(32); /* 31.25kHz */ + } + if (i == ENVY24_TIMEOUT) { + return -1; + } + data = envy24_rdcs(sc, ENVY24_CCS_I2CDATA, 1); + +#if(0) + device_printf(sc->dev, "envy24_rdi2c(): return 0x%x\n", data); +#endif + return (int)data; +} + +static int +envy24_wri2c(struct sc_info *sc, u_int32_t dev, u_int32_t addr, u_int32_t data) +{ + u_int32_t tmp; + int i; + +#if(0) + device_printf(sc->dev, "envy24_rdi2c(sc, 0x%02x, 0x%02x)\n", dev, addr); +#endif + for (i = 0; i < ENVY24_TIMEOUT; i++) { + tmp = envy24_rdcs(sc, ENVY24_CCS_I2CSTAT, 1); + if ((tmp & ENVY24_CCS_I2CSTAT_BSY) == 0) + break; + DELAY(32); /* 31.25kHz */ + } + if (i == ENVY24_TIMEOUT) { + return -1; + } + envy24_wrcs(sc, ENVY24_CCS_I2CADDR, addr, 1); + envy24_wrcs(sc, ENVY24_CCS_I2CDATA, data, 1); + envy24_wrcs(sc, ENVY24_CCS_I2CDEV, + (dev & ENVY24_CCS_I2CDEV_ADDR) | ENVY24_CCS_I2CDEV_WR, 1); + for (i = 0; i < ENVY24_TIMEOUT; i++) { + data = envy24_rdcs(sc, ENVY24_CCS_I2CSTAT, 1); + if ((data & ENVY24_CCS_I2CSTAT_BSY) == 0) + break; + DELAY(32); /* 31.25kHz */ + } + if (i == ENVY24_TIMEOUT) { + return -1; + } + + return 0; +} + +static int +envy24_rdrom(struct sc_info *sc, u_int32_t addr) +{ + u_int32_t data; + +#if(0) + device_printf(sc->dev, "envy24_rdrom(sc, 0x%02x)\n", addr); +#endif + data = envy24_rdcs(sc, ENVY24_CCS_I2CSTAT, 1); + if ((data & ENVY24_CCS_I2CSTAT_ROM) == 0) { +#if(0) + device_printf(sc->dev, "envy24_rdrom(): E2PROM not presented\n"); +#endif + return -1; + } + + return envy24_rdi2c(sc, ENVY24_CCS_I2CDEV_ROM, addr); +} + +static struct cfg_info * +envy24_rom2cfg(struct sc_info *sc) +{ + struct cfg_info *buff; + int size; + int i; + +#if(0) + device_printf(sc->dev, "envy24_rom2cfg(sc)\n"); +#endif + size = envy24_rdrom(sc, ENVY24_E2PROM_SIZE); + if (size < ENVY24_E2PROM_GPIODIR + 1) { +#if(0) + device_printf(sc->dev, "envy24_rom2cfg(): ENVY24_E2PROM_SIZE-->%d\n", size); +#endif + return NULL; + } + buff = malloc(sizeof(*buff), M_ENVY24, M_NOWAIT); + if (buff == NULL) { +#if(0) + device_printf(sc->dev, "envy24_rom2cfg(): malloc()\n"); +#endif + return NULL; + } + buff->free = 1; + + buff->subvendor = envy24_rdrom(sc, ENVY24_E2PROM_SUBVENDOR) << 8; + buff->subvendor += envy24_rdrom(sc, ENVY24_E2PROM_SUBVENDOR + 1); + buff->subdevice = envy24_rdrom(sc, ENVY24_E2PROM_SUBDEVICE) << 8; + buff->subdevice += envy24_rdrom(sc, ENVY24_E2PROM_SUBDEVICE + 1); + buff->scfg = envy24_rdrom(sc, ENVY24_E2PROM_SCFG); + buff->acl = envy24_rdrom(sc, ENVY24_E2PROM_ACL); + buff->i2s = envy24_rdrom(sc, ENVY24_E2PROM_I2S); + buff->spdif = envy24_rdrom(sc, ENVY24_E2PROM_SPDIF); + buff->gpiomask = envy24_rdrom(sc, ENVY24_E2PROM_GPIOMASK); + buff->gpiostate = envy24_rdrom(sc, ENVY24_E2PROM_GPIOSTATE); + buff->gpiodir = envy24_rdrom(sc, ENVY24_E2PROM_GPIODIR); + + for (i = 0; cfg_table[i].subvendor != 0 || cfg_table[i].subdevice != 0; i++) + if (cfg_table[i].subvendor == buff->subvendor && + cfg_table[i].subdevice == buff->subdevice) + break; + buff->name = cfg_table[i].name; + buff->codec = cfg_table[i].codec; + + return buff; +} + +static void +envy24_cfgfree(struct cfg_info *cfg) { + if (cfg == NULL) + return; + if (cfg->free) + free(cfg, M_ENVY24); + return; +} + +/* -------------------------------------------------------------------- */ + +/* AC'97 codec access routines */ + +static int +envy24_coldcd(struct sc_info *sc) +{ + u_int32_t data; + int i; + +#if(0) + device_printf(sc->dev, "envy24_coldcd()\n"); +#endif + envy24_wrmt(sc, ENVY24_MT_AC97CMD, ENVY24_MT_AC97CMD_CLD, 1); + DELAY(10); + envy24_wrmt(sc, ENVY24_MT_AC97CMD, 0, 1); + DELAY(1000); + for (i = 0; i < ENVY24_TIMEOUT; i++) { + data = envy24_rdmt(sc, ENVY24_MT_AC97CMD, 1); + if (data & ENVY24_MT_AC97CMD_RDY) { + return 0; + } + } + + return -1; +} + +static int +envy24_slavecd(struct sc_info *sc) +{ + u_int32_t data; + int i; + +#if(0) + device_printf(sc->dev, "envy24_slavecd()\n"); +#endif + envy24_wrmt(sc, ENVY24_MT_AC97CMD, + ENVY24_MT_AC97CMD_CLD | ENVY24_MT_AC97CMD_WRM, 1); + DELAY(10); + envy24_wrmt(sc, ENVY24_MT_AC97CMD, 0, 1); + DELAY(1000); + for (i = 0; i < ENVY24_TIMEOUT; i++) { + data = envy24_rdmt(sc, ENVY24_MT_AC97CMD, 1); + if (data & ENVY24_MT_AC97CMD_RDY) { + return 0; + } + } + + return -1; +} + +static int +envy24_rdcd(kobj_t obj, void *devinfo, int regno) +{ + struct sc_info *sc = (struct sc_info *)devinfo; + u_int32_t data; + int i; + +#if(0) + device_printf(sc->dev, "envy24_rdcd(obj, sc, 0x%02x)\n", regno); +#endif + envy24_wrmt(sc, ENVY24_MT_AC97IDX, (u_int32_t)regno, 1); + envy24_wrmt(sc, ENVY24_MT_AC97CMD, ENVY24_MT_AC97CMD_RD, 1); + for (i = 0; i < ENVY24_TIMEOUT; i++) { + data = envy24_rdmt(sc, ENVY24_MT_AC97CMD, 1); + if ((data & ENVY24_MT_AC97CMD_RD) == 0) + break; + } + data = envy24_rdmt(sc, ENVY24_MT_AC97DLO, 2); + +#if(0) + device_printf(sc->dev, "envy24_rdcd(): return 0x%x\n", data); +#endif + return (int)data; +} + +static int +envy24_wrcd(kobj_t obj, void *devinfo, int regno, u_int16_t data) +{ + struct sc_info *sc = (struct sc_info *)devinfo; + u_int32_t cmd; + int i; + +#if(0) + device_printf(sc->dev, "envy24_wrcd(obj, sc, 0x%02x, 0x%04x)\n", regno, data); +#endif + envy24_wrmt(sc, ENVY24_MT_AC97IDX, (u_int32_t)regno, 1); + envy24_wrmt(sc, ENVY24_MT_AC97DLO, (u_int32_t)data, 2); + envy24_wrmt(sc, ENVY24_MT_AC97CMD, ENVY24_MT_AC97CMD_WR, 1); + for (i = 0; i < ENVY24_TIMEOUT; i++) { + cmd = envy24_rdmt(sc, ENVY24_MT_AC97CMD, 1); + if ((cmd & ENVY24_MT_AC97CMD_WR) == 0) + break; + } + + return 0; +} + +static kobj_method_t envy24_ac97_methods[] = { + KOBJMETHOD(ac97_read, envy24_rdcd), + KOBJMETHOD(ac97_write, envy24_wrcd), + {0, 0} +}; +AC97_DECLARE(envy24_ac97); + +/* -------------------------------------------------------------------- */ + +/* GPIO access routines */ + +static u_int32_t +envy24_gpiord(struct sc_info *sc) +{ + return envy24_rdci(sc, ENVY24_CCI_GPIODAT); +} + +static void +envy24_gpiowr(struct sc_info *sc, u_int32_t data) +{ +#if(0) + device_printf(sc->dev, "envy24_gpiowr(sc, 0x%02x)\n", data & 0xff); + return; +#endif + envy24_wrci(sc, ENVY24_CCI_GPIODAT, data); + return; +} + +static u_int32_t +envy24_gpiogetmask(struct sc_info *sc) +{ + return envy24_rdci(sc, ENVY24_CCI_GPIOMASK); +} + +static void +envy24_gpiosetmask(struct sc_info *sc, u_int32_t mask) +{ + envy24_wrci(sc, ENVY24_CCI_GPIOMASK, mask); + return; +} + +static u_int32_t +envy24_gpiogetdir(struct sc_info *sc) +{ + return envy24_rdci(sc, ENVY24_CCI_GPIOCTL); +} + +static void +envy24_gpiosetdir(struct sc_info *sc, u_int32_t dir) +{ + envy24_wrci(sc, ENVY24_CCI_GPIOCTL, dir); + return; +} + +/* -------------------------------------------------------------------- */ + +/* M-Audio Delta series AK4524 access interface routine */ + +struct envy24_delta_ak4524_codec { + struct ak452x_info *info; + struct sc_info *parent; + int dir; + int num; + int cs, cclk, cdti; +}; + +static void +envy24_delta_ak4524_ctl(void *codec, unsigned int cs, unsigned int cclk, unsigned int cdti) +{ + u_int32_t data = 0; + struct envy24_delta_ak4524_codec *ptr = codec; + +#if(0) + device_printf(ptr->parent->dev, "--> %d, %d, %d\n", cs, cclk, cdti); +#endif + data = envy24_gpiord(ptr->parent); + data &= ~(ptr->cs | ptr->cclk | ptr->cdti); + if (cs) data += ptr->cs; + if (cclk) data += ptr->cclk; + if (cdti) data += ptr->cdti; + envy24_gpiowr(ptr->parent, data); + return; +} + +static void * +envy24_delta_ak4524_create(device_t dev, void *info, int dir, int num) +{ + struct sc_info *sc = info; + struct envy24_delta_ak4524_codec *buff = NULL; + +#if(0) + device_printf(sc->dev, "envy24_delta_ak4524_create(dev, sc, %d, %d)\n", dir, num); +#endif + + buff = malloc(sizeof(*buff), M_ENVY24, M_NOWAIT); + if (buff == NULL) + return NULL; + + if (dir == PCMDIR_PLAY && sc->adc[num] != NULL) + buff->info = ((struct envy24_delta_ak4524_codec *)sc->adc[num])->info; + else if (dir == PCMDIR_REC && sc->dac[num] != NULL) + buff->info = ((struct envy24_delta_ak4524_codec *)sc->dac[num])->info; + else + buff->info = ak452x_create(dev, buff, num, envy24_delta_ak4524_ctl); + if (buff->info == NULL) { + free(buff, M_ENVY24); + return NULL; + } + + buff->parent = sc; + buff->dir = dir; + buff->num = num; + + return (void *)buff; +} + +static void +envy24_delta_ak4524_destroy(void *codec) +{ + struct envy24_delta_ak4524_codec *ptr = codec; + if (ptr == NULL) + return; +#if(0) + device_printf(ptr->parent->dev, "envy24_delta_ak4524_destroy()\n"); +#endif + + if (ptr->dir == PCMDIR_PLAY) { + if (ptr->parent->adc[ptr->num] == NULL) + ak452x_destroy(ptr->info); + } + else { + if (ptr->parent->dac[ptr->num] == NULL) + ak452x_destroy(ptr->info); + } + + free(codec, M_ENVY24); +} + +static void +envy24_delta_ak4524_init(void *codec) +{ + u_int32_t gpiomask, gpiodir; + struct envy24_delta_ak4524_codec *ptr = codec; + if (ptr == NULL) + return; +#if(0) + device_printf(ptr->parent->dev, "envy24_delta_ak4524_init()\n"); +#endif + + /* + gpiomask = envy24_gpiogetmask(ptr->parent); + gpiomask &= ~(ENVY24_GPIO_AK4524_CDTI | ENVY24_GPIO_AK4524_CCLK | ENVY24_GPIO_AK4524_CS0 | ENVY24_GPIO_AK4524_CS1); + envy24_gpiosetmask(ptr->parent, gpiomask); + gpiodir = envy24_gpiogetdir(ptr->parent); + gpiodir |= ENVY24_GPIO_AK4524_CDTI | ENVY24_GPIO_AK4524_CCLK | ENVY24_GPIO_AK4524_CS0 | ENVY24_GPIO_AK4524_CS1; + envy24_gpiosetdir(ptr->parent, gpiodir); + */ + envy24_gpiosetmask(ptr->parent, ENVY24_GPIO_CS8414_STATUS); + envy24_gpiosetdir(ptr->parent, ~ENVY24_GPIO_CS8414_STATUS); + if (ptr->num == 0) + ptr->cs = ENVY24_GPIO_AK4524_CS0; + else + ptr->cs = ENVY24_GPIO_AK4524_CS1; + ptr->cclk = ENVY24_GPIO_AK4524_CCLK; + ptr->cdti = ENVY24_GPIO_AK4524_CDTI; + ak452x_settype(ptr->info, AK452X_TYPE_4524); + ak452x_setcif(ptr->info, ENVY24_DELTA_AK4524_CIF); + ak452x_setformat(ptr->info, + AK452X_FORMAT_I2S | AK452X_FORMAT_256FSN | AK452X_FORMAT_1X); + ak452x_setdvc(ptr->info, 0); + ak452x_init(ptr->info); +} + +static void +envy24_delta_ak4524_reinit(void *codec) +{ + struct envy24_delta_ak4524_codec *ptr = codec; + if (ptr == NULL) + return; +#if(0) + device_printf(ptr->parent->dev, "envy24_delta_ak4524_reinit()\n"); +#endif + + ak452x_reinit(ptr->info); +} + +static void +envy24_delta_ak4524_setvolume(void *codec, int dir, unsigned int left, unsigned int right) +{ + struct envy24_delta_ak4524_codec *ptr = codec; + if (ptr == NULL) + return; +#if(0) + device_printf(ptr->parent->dev, "envy24_delta_ak4524_set()\n"); +#endif + + ak452x_set(ptr->info, dir, left, right); +} + +/* + There is no need for AK452[48] codec to set sample rate + static void + envy24_delta_ak4524_setrate(struct envy24_delta_ak4524_codec *codec, int which, int rate) + { + } +*/ + +/* -------------------------------------------------------------------- */ + +/* hardware access routeines */ + +static struct { + u_int32_t speed; + u_int32_t code; +} envy24_speedtab[] = { + {48000, ENVY24_MT_RATE_48000}, + {24000, ENVY24_MT_RATE_24000}, + {12000, ENVY24_MT_RATE_12000}, + {9600, ENVY24_MT_RATE_9600}, + {32000, ENVY24_MT_RATE_32000}, + {16000, ENVY24_MT_RATE_16000}, + {8000, ENVY24_MT_RATE_8000}, + {96000, ENVY24_MT_RATE_96000}, + {64000, ENVY24_MT_RATE_64000}, + {44100, ENVY24_MT_RATE_44100}, + {22050, ENVY24_MT_RATE_22050}, + {11025, ENVY24_MT_RATE_11025}, + {88200, ENVY24_MT_RATE_88200}, + {0, 0x10} +}; + +static int +envy24_setspeed(struct sc_info *sc, u_int32_t speed) { + u_int32_t code; + int i = 0; + +#if(0) + device_printf(sc->dev, "envy24_setspeed(sc, %d)\n", speed); +#endif + if (speed == 0) { + code = ENVY24_MT_RATE_SPDIF; /* external master clock */ + envy24_slavecd(sc); + } + else { + for (i = 0; envy24_speedtab[i].speed != 0; i++) { + if (envy24_speedtab[i].speed == speed) + break; + } + code = envy24_speedtab[i].code; + } +#if(0) + device_printf(sc->dev, "envy24_setspeed(): speed %d/code 0x%04x\n", envy24_speedtab[i].speed, code); +#endif + if (code < 0x10) { + envy24_wrmt(sc, ENVY24_MT_RATE, code, 1); + code = envy24_rdmt(sc, ENVY24_MT_RATE, 1); + code &= ENVY24_MT_RATE_MASK; + for (i = 0; envy24_speedtab[i].code < 0x10; i++) { + if (envy24_speedtab[i].code == code) + break; + } + speed = envy24_speedtab[i].speed; + } + else + speed = 0; + +#if(0) + device_printf(sc->dev, "envy24_setspeed(): return %d\n", speed); +#endif + return speed; +} + +static void +envy24_setvolume(struct sc_info *sc, unsigned ch) +{ +#if(0) + device_printf(sc->dev, "envy24_setvolume(sc, %d)\n", ch); +#endif + envy24_wrmt(sc, ENVY24_MT_VOLIDX, ch * 2, 1); + envy24_wrmt(sc, ENVY24_MT_VOLUME, 0x7f00 | sc->left[ch], 2); + envy24_wrmt(sc, ENVY24_MT_VOLIDX, ch * 2 + 1, 1); + envy24_wrmt(sc, ENVY24_MT_VOLUME, (sc->right[ch] << 8) | 0x7f, 2); +} + +static void +envy24_mutevolume(struct sc_info *sc, unsigned ch) +{ + u_int32_t vol; + +#if(0) + device_printf(sc->dev, "envy24_mutevolume(sc, %d)\n", ch); +#endif + vol = ENVY24_VOL_MUTE << 8 | ENVY24_VOL_MUTE; + envy24_wrmt(sc, ENVY24_MT_VOLIDX, ch * 2, 1); + envy24_wrmt(sc, ENVY24_MT_VOLUME, vol, 2); + envy24_wrmt(sc, ENVY24_MT_VOLIDX, ch * 2 + 1, 1); + envy24_wrmt(sc, ENVY24_MT_VOLUME, vol, 2); +} + +static u_int32_t +envy24_gethwptr(struct sc_info *sc, int dir) +{ + int unit, regno; + u_int32_t ptr, rtn; + +#if(0) + device_printf(sc->dev, "envy24_gethwptr(sc, %d)\n", dir); +#endif + if (dir == PCMDIR_PLAY) { + rtn = sc->psize / 4; + unit = ENVY24_PLAY_BUFUNIT / 4; + regno = ENVY24_MT_PCNT; + } + else { + rtn = sc->rsize / 4; + unit = ENVY24_REC_BUFUNIT / 4; + regno = ENVY24_MT_RCNT; + } + + ptr = envy24_rdmt(sc, regno, 2); + rtn -= (ptr + 1); + rtn /= unit; + +#if(0) + device_printf(sc->dev, "envy24_gethwptr(): return %d\n", rtn); +#endif + return rtn; +} + +static void +envy24_updintr(struct sc_info *sc, int dir) +{ + int regptr, regintr; + u_int32_t mask, intr; + u_int32_t ptr, size, cnt; + u_int16_t blk; + +#if(0) + device_printf(sc->dev, "envy24_updintr(sc, %d)\n", dir); +#endif + if (dir == PCMDIR_PLAY) { + blk = sc->blk[0]; + size = sc->psize / 4; + regptr = ENVY24_MT_PCNT; + regintr = ENVY24_MT_PTERM; + mask = ~ENVY24_MT_INT_PMASK; + } + else { + blk = sc->blk[1]; + size = sc->rsize / 4; + regptr = ENVY24_MT_RCNT; + regintr = ENVY24_MT_RTERM; + mask = ~ENVY24_MT_INT_RMASK; + } + + ptr = size - envy24_rdmt(sc, regptr, 2) - 1; + /* + cnt = blk - ptr % blk - 1; + if (cnt == 0) + cnt = blk - 1; + */ + cnt = blk - 1; +#if(0) + device_printf(sc->dev, "envy24_updintr():ptr = %d, blk = %d, cnt = %d\n", ptr, blk, cnt); +#endif + envy24_wrmt(sc, regintr, cnt, 2); + intr = envy24_rdmt(sc, ENVY24_MT_INT, 1); +#if(0) + device_printf(sc->dev, "envy24_updintr():intr = 0x%02x, mask = 0x%02x\n", intr, mask); +#endif + envy24_wrmt(sc, ENVY24_MT_INT, intr & mask, 1); +#if(0) + device_printf(sc->dev, "envy24_updintr():INT-->0x%02x\n", + envy24_rdmt(sc, ENVY24_MT_INT, 1)); +#endif + + return; +} + +static void +envy24_maskintr(struct sc_info *sc, int dir) +{ + u_int32_t mask, intr; + +#if(0) + device_printf(sc->dev, "envy24_maskintr(sc, %d)\n", dir); +#endif + if (dir == PCMDIR_PLAY) + mask = ENVY24_MT_INT_PMASK; + else + mask = ENVY24_MT_INT_RMASK; + intr = envy24_rdmt(sc, ENVY24_MT_INT, 1); + envy24_wrmt(sc, ENVY24_MT_INT, intr | mask, 1); + + return; +} + +static int +envy24_checkintr(struct sc_info *sc, int dir) +{ + u_int32_t mask, stat, intr, rtn; + +#if(0) + device_printf(sc->dev, "envy24_checkintr(sc, %d)\n", dir); +#endif + intr = envy24_rdmt(sc, ENVY24_MT_INT, 1); + if (dir == PCMDIR_PLAY) { + if ((rtn = intr & ENVY24_MT_INT_PSTAT) != 0) { + mask = ~ENVY24_MT_INT_RSTAT; + stat = ENVY24_MT_INT_PSTAT | ENVY24_MT_INT_PMASK; + envy24_wrmt(sc, ENVY24_MT_INT, (intr & mask) | stat, 1); + } + } + else { + if ((rtn = intr & ENVY24_MT_INT_RSTAT) != 0) { + mask = ~ENVY24_MT_INT_PSTAT; + stat = ENVY24_MT_INT_RSTAT | ENVY24_MT_INT_RMASK; + envy24_wrmt(sc, ENVY24_MT_INT, (intr & mask) | stat, 1); + } + } + + return rtn; +} + +static void +envy24_start(struct sc_info *sc, int dir) +{ + u_int32_t stat, sw; + +#if(0) + device_printf(sc->dev, "envy24_start(sc, %d)\n", dir); +#endif + if (dir == PCMDIR_PLAY) + sw = ENVY24_MT_PCTL_PSTART; + else + sw = ENVY24_MT_PCTL_RSTART; + + stat = envy24_rdmt(sc, ENVY24_MT_PCTL, 1); + envy24_wrmt(sc, ENVY24_MT_PCTL, stat | sw, 1); +#if(0) + DELAY(100); + device_printf(sc->dev, "PADDR:0x%08x\n", envy24_rdmt(sc, ENVY24_MT_PADDR, 4)); + device_printf(sc->dev, "PCNT:%ld\n", envy24_rdmt(sc, ENVY24_MT_PCNT, 2)); +#endif + + return; +} + +static void +envy24_stop(struct sc_info *sc, int dir) +{ + u_int32_t stat, sw; + +#if(0) + device_printf(sc->dev, "envy24_stop(sc, %d)\n", dir); +#endif + if (dir == PCMDIR_PLAY) + sw = ~ENVY24_MT_PCTL_PSTART; + else + sw = ~ENVY24_MT_PCTL_RSTART; + + stat = envy24_rdmt(sc, ENVY24_MT_PCTL, 1); + envy24_wrmt(sc, ENVY24_MT_PCTL, stat & sw, 1); + + return; +} + +static int +envy24_route(struct sc_info *sc, int dac, int class, int adc, int rev) +{ + u_int32_t reg, mask; + u_int32_t left, right; + +#if(0) + device_printf(sc->dev, "envy24_route(sc, %d, %d, %d, %d)\n", + dac, class, adc, rev); +#endif + /* parameter pattern check */ + if (dac < 0 || ENVY24_ROUTE_DAC_SPDIF < dac) + return -1; + if (class == ENVY24_ROUTE_CLASS_MIX && + (dac != ENVY24_ROUTE_DAC_1 && dac != ENVY24_ROUTE_DAC_SPDIF)) + return -1; + if (rev) { + left = ENVY24_ROUTE_RIGHT; + right = ENVY24_ROUTE_LEFT; + } + else { + left = ENVY24_ROUTE_LEFT; + right = ENVY24_ROUTE_RIGHT; + } + + if (dac == ENVY24_ROUTE_DAC_SPDIF) { + reg = class | class << 2 | + ((adc << 1 | left) | left << 3) << 8 | + ((adc << 1 | right) | right << 3) << 12; +#if(0) + device_printf(sc->dev, "envy24_route(): MT_SPDOUT-->0x%04x\n", reg); +#endif + envy24_wrmt(sc, ENVY24_MT_SPDOUT, reg, 2); + } + else { + mask = ~(0x0303 << dac * 2); + reg = envy24_rdmt(sc, ENVY24_MT_PSDOUT, 2); + reg = (reg & mask) | ((class | class << 8) << dac * 2); +#if(0) + device_printf(sc->dev, "envy24_route(): MT_PSDOUT-->0x%04x\n", reg); +#endif + envy24_wrmt(sc, ENVY24_MT_PSDOUT, reg, 2); + mask = ~(0xff << dac * 8); + reg = envy24_rdmt(sc, ENVY24_MT_RECORD, 4); + reg = (reg & mask) | + (((adc << 1 | left) | left << 3) | + ((adc << 1 | right) | right << 3) << 4) << dac * 8; +#if(0) + device_printf(sc->dev, "envy24_route(): MT_RECORD-->0x%08x\n", reg); +#endif + envy24_wrmt(sc, ENVY24_MT_RECORD, reg, 4); + } + + return 0; +} + +/* -------------------------------------------------------------------- */ + +/* buffer copy routines */ +static void +envy24_p32sl(struct sc_chinfo *ch) +{ + int length; + sample32_t *dmabuf; + u_int32_t *data; + int src, dst, ssize, dsize, slot; + int i; + + length = sndbuf_getready(ch->buffer) / 8; + dmabuf = ch->parent->pbuf; + data = (u_int32_t *)ch->data; + src = sndbuf_getreadyptr(ch->buffer) / 4; + dst = src / 2 + ch->offset; + ssize = ch->size / 4; + dsize = ch->size / 8; + slot = ch->num * 2; + + for (i = 0; i < length; i++) { + dmabuf[dst * ENVY24_PLAY_CHNUM + slot] = data[src]; + dmabuf[dst * ENVY24_PLAY_CHNUM + slot + 1] = data[src + 1]; + dst++; + dst %= dsize; + src += 2; + src %= ssize; + } + + return; +} + +static void +envy24_p16sl(struct sc_chinfo *ch) +{ + int length; + sample32_t *dmabuf; + u_int16_t *data; + int src, dst, ssize, dsize, slot; + int i; + +#if(0) + device_printf(ch->parent->dev, "envy24_p16sl()\n"); +#endif + length = sndbuf_getready(ch->buffer) / 4; + dmabuf = ch->parent->pbuf; + data = (u_int16_t *)ch->data; + src = sndbuf_getreadyptr(ch->buffer) / 2; + dst = src / 2 + ch->offset; + ssize = ch->size / 2; + dsize = ch->size / 4; + slot = ch->num * 2; +#if(0) + device_printf(ch->parent->dev, "envy24_p16sl():%lu-->%lu(%lu)\n", src, dst, length); +#endif + + for (i = 0; i < length; i++) { + dmabuf[dst * ENVY24_PLAY_CHNUM + slot] = (u_int32_t)data[src] << 16; + dmabuf[dst * ENVY24_PLAY_CHNUM + slot + 1] = (u_int32_t)data[src + 1] << 16; +#if(0) + if (i < 16) { + printf("%08x", dmabuf[dst * ENVY24_PLAY_CHNUM + slot]); + printf("%08x", dmabuf[dst * ENVY24_PLAY_CHNUM + slot + 1]); + } +#endif + dst++; + dst %= dsize; + src += 2; + src %= ssize; + } +#if(0) + printf("\n"); +#endif + + return; +} + +static void +envy24_p8u(struct sc_chinfo *ch) +{ + int length; + sample32_t *dmabuf; + u_int8_t *data; + int src, dst, ssize, dsize, slot; + int i; + + length = sndbuf_getready(ch->buffer) / 2; + dmabuf = ch->parent->pbuf; + data = (u_int8_t *)ch->data; + src = sndbuf_getreadyptr(ch->buffer); + dst = src / 2 + ch->offset; + ssize = ch->size; + dsize = ch->size / 4; + slot = ch->num * 2; + + for (i = 0; i < length; i++) { + dmabuf[dst * ENVY24_PLAY_CHNUM + slot] = ((u_int32_t)data[src] ^ 0x80) << 24; + dmabuf[dst * ENVY24_PLAY_CHNUM + slot + 1] = ((u_int32_t)data[src + 1] ^ 0x80) << 24; + dst++; + dst %= dsize; + src += 2; + src %= ssize; + } + + return; +} + +static void +envy24_r32sl(struct sc_chinfo *ch) +{ + int length; + sample32_t *dmabuf; + u_int32_t *data; + int src, dst, ssize, dsize, slot; + int i; + + length = sndbuf_getfree(ch->buffer) / 8; + dmabuf = ch->parent->rbuf; + data = (u_int32_t *)ch->data; + dst = sndbuf_getfreeptr(ch->buffer) / 4; + src = dst / 2 + ch->offset; + dsize = ch->size / 4; + ssize = ch->size / 8; + slot = (ch->num - ENVY24_CHAN_REC_ADC1) * 2; + + for (i = 0; i < length; i++) { + data[dst] = dmabuf[src * ENVY24_REC_CHNUM + slot]; + data[dst + 1] = dmabuf[src * ENVY24_REC_CHNUM + slot + 1]; + dst += 2; + dst %= dsize; + src++; + src %= ssize; + } + + return; +} + +static void +envy24_r16sl(struct sc_chinfo *ch) +{ + int length; + sample32_t *dmabuf; + u_int32_t *data; + int src, dst, ssize, dsize, slot; + int i; + + length = sndbuf_getfree(ch->buffer) / 4; + dmabuf = ch->parent->rbuf; + data = (u_int16_t *)ch->data; + dst = sndbuf_getfreeptr(ch->buffer) / 2; + src = dst / 2 + ch->offset; + dsize = ch->size / 2; + ssize = ch->size / 8; + slot = (ch->num - ENVY24_CHAN_REC_ADC1) * 2; + + for (i = 0; i < length; i++) { + data[dst] = dmabuf[src * ENVY24_REC_CHNUM + slot]; + data[dst + 1] = dmabuf[src * ENVY24_REC_CHNUM + slot + 1]; + dst += 2; + dst %= dsize; + src++; + src %= ssize; + } + + return; +} + +/* -------------------------------------------------------------------- */ + +/* channel interface */ +static void * +envy24chan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) +{ + struct sc_info *sc = (struct sc_info *)devinfo; + struct sc_chinfo *ch; + unsigned num; + +#if(0) + device_printf(sc->dev, "envy24chan_init(obj, devinfo, b, c, %d)\n", dir); +#endif + snd_mtxlock(sc->lock); + if ((sc->chnum > ENVY24_CHAN_PLAY_SPDIF && dir != PCMDIR_REC) || + (sc->chnum < ENVY24_CHAN_REC_ADC1 && dir != PCMDIR_PLAY)) { + snd_mtxunlock(sc->lock); + return NULL; + } + num = sc->chnum; + + ch = &sc->chan[num]; + ch->size = 8 * ENVY24_SAMPLE_NUM; + ch->data = malloc(ch->size, M_ENVY24, M_NOWAIT); + if (ch->data == NULL) { + ch->size = 0; + ch = NULL; + } + else { + ch->buffer = b; + ch->channel = c; + ch->parent = sc; + ch->dir = dir; + /* set channel map */ + ch->num = envy24_chanmap[num]; + sndbuf_setup(ch->buffer, ch->data, ch->size); + /* these 2 values are dummy */ + ch->unit = 4; + ch->blk = 10240; + } + snd_mtxunlock(sc->lock); + + return ch; +} + +static int +envy24chan_free(kobj_t obj, void *data) +{ + struct sc_chinfo *ch = data; + struct sc_info *sc = ch->parent; + +#if(0) + device_printf(sc->dev, "envy24chan_free()\n"); +#endif + snd_mtxlock(sc->lock); + if (ch->data != NULL) { + free(ch->data, M_ENVY24); + ch->data = NULL; + } + snd_mtxunlock(sc->lock); + + return 0; +} + +static int +envy24chan_setformat(kobj_t obj, void *data, u_int32_t format) +{ + struct sc_chinfo *ch = data; + struct sc_info *sc = ch->parent; + struct envy24_emldma *emltab; + unsigned int bcnt, bsize; + int i; + +#if(0) + device_printf(sc->dev, "envy24chan_setformat(obj, data, 0x%08x)\n", format); +#endif + snd_mtxlock(sc->lock); + /* check and get format related information */ + if (ch->dir == PCMDIR_PLAY) + emltab = envy24_pemltab; + else + emltab = envy24_remltab; + if (emltab == NULL) { + snd_mtxunlock(sc->lock); + return -1; + } + for (i = 0; emltab[i].format != 0; i++) + if (emltab[i].format == format) + break; + if (emltab[i].format == 0) { + snd_mtxunlock(sc->lock); + return -1; + } + + /* set format information */ + ch->format = format; + ch->emldma = emltab[i].emldma; + if (ch->unit > emltab[i].unit) + ch->blk *= ch->unit / emltab[i].unit; + else + ch->blk /= emltab[i].unit / ch->unit; + ch->unit = emltab[i].unit; + + /* set channel buffer information */ + ch->size = ch->unit * ENVY24_SAMPLE_NUM; + if (ch->dir == PCMDIR_PLAY) + bsize = ch->blk * 4 / ENVY24_PLAY_BUFUNIT; + else + bsize = ch->blk * 4 / ENVY24_REC_BUFUNIT; + bsize *= ch->unit; + bcnt = ch->size / bsize; + sndbuf_resize(ch->buffer, bcnt, bsize); + snd_mtxunlock(sc->lock); + +#if(0) + device_printf(sc->dev, "envy24chan_setformat(): return 0x%08x\n", 0); +#endif + return 0; +} + +/* + IMPLEMENT NOTICE: In this driver, setspeed function only do setting + of speed information value. And real hardware speed setting is done + at start triggered(see envy24chan_trigger()). So, at this function + is called, any value that ENVY24 can use is able to set. But, at + start triggerd, some other channel is running, and that channel's + speed isn't same with, then trigger function will fail. +*/ +static int +envy24chan_setspeed(kobj_t obj, void *data, u_int32_t speed) +{ + struct sc_chinfo *ch = data; + u_int32_t val, prev; + int i; + +#if(0) + device_printf(ch->parent->dev, "envy24chan_setspeed(obj, data, %d)\n", speed); +#endif + prev = 0x7fffffff; + for (i = 0; (val = envy24_speed[i]) != 0; i++) { + if (abs(val - speed) < abs(prev - speed)) + prev = val; + else + break; + } + ch->speed = prev; + +#if(0) + device_printf(ch->parent->dev, "envy24chan_setspeed(): return %d\n", ch->speed); +#endif + return ch->speed; +} + +static int +envy24chan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) +{ + struct sc_chinfo *ch = data; + struct sc_info *sc = ch->parent; + u_int32_t size, prev; + +#if(0) + device_printf(sc->dev, "envy24chan_setblocksize(obj, data, %d)\n", blocksize); +#endif + prev = 0x7fffffff; + snd_mtxlock(sc->lock); + for (size = ch->size / 2; size > 0; size /= 2) { + if (abs(size - blocksize) < abs(prev - blocksize)) + prev = size; + else + break; + } + + ch->blk = prev / ch->unit; + if (ch->dir == PCMDIR_PLAY) + ch->blk *= ENVY24_PLAY_BUFUNIT / 4; + else + ch->blk *= ENVY24_REC_BUFUNIT / 4; + snd_mtxunlock(sc->lock); + +#if(0) + device_printf(sc->dev, "envy24chan_setblocksize(): return %d\n", prev); +#endif + return prev; +} + +/* semantic note: must start at beginning of buffer */ +static int +envy24chan_trigger(kobj_t obj, void *data, int go) +{ + struct sc_chinfo *ch = data; + struct sc_info *sc = ch->parent; + u_int32_t ptr; + int slot; + int i; + +#if(0) + device_printf(sc->dev, "envy24chan_trigger(obj, data, %d)\n", go); +#endif + snd_mtxlock(sc->lock); + if (ch->dir == PCMDIR_PLAY) + slot = 0; + else + slot = 1; + switch (go) { + case PCMTRIG_START: +#if(0) + device_printf(sc->dev, "envy24chan_trigger(): start\n"); +#endif + /* check or set channel speed */ + if (sc->run[0] == 0 && sc->run[1] == 0) { + sc->speed = envy24_setspeed(sc, ch->speed); + sc->caps[0].minspeed = sc->caps[0].maxspeed = sc->speed; + sc->caps[1].minspeed = sc->caps[1].maxspeed = sc->speed; + } + else if (ch->speed != 0 && ch->speed != sc->speed) + return -1; + if (ch->speed == 0) + ch->channel->speed = sc->speed; + /* start or enable channel */ + sc->run[slot]++; + if (sc->run[slot] == 1) { + /* first channel */ + ch->offset = 0; + sc->blk[slot] = ch->blk; + } + else { + ptr = envy24_gethwptr(sc, ch->dir); + ch->offset = ((ptr / ch->blk + 1) * ch->blk % + (ch->size / 4)) * 4 / ch->unit; + if (ch->blk < sc->blk[slot]) + sc->blk[slot] = ch->blk; + } + if (ch->dir == PCMDIR_PLAY) { + ch->emldma(ch); + envy24_setvolume(sc, ch->num); + } + envy24_updintr(sc, ch->dir); + if (sc->run[slot] == 1) + envy24_start(sc, ch->dir); + ch->run = 1; + break; + case PCMTRIG_EMLDMAWR: +#if(0) + device_printf(sc->dev, "envy24chan_trigger(): emldmawr\n"); +#endif + if (ch->run != 1) + return -1; + ch->emldma(ch); + break; + case PCMTRIG_EMLDMARD: +#if(0) + device_printf(sc->dev, "envy24chan_trigger(): emldmard\n"); +#endif + if (ch->run != 1) + return -1; + ch->emldma(ch); + break; + case PCMTRIG_ABORT: +#if(0) + device_printf(sc->dev, "envy24chan_trigger(): abort\n"); +#endif + ch->run = 0; + sc->run[slot]--; + if (ch->dir == PCMDIR_PLAY) + envy24_mutevolume(sc, ch->num); + if (sc->run[slot] == 0) { + envy24_stop(sc, ch->dir); + sc->intr[slot] = 0; + } + else if (ch->blk == sc->blk[slot]) { + sc->blk[slot] = ENVY24_SAMPLE_NUM / 2; + for (i = 0; i < ENVY24_CHAN_NUM; i++) { + if (sc->chan[i].dir == ch->dir && + sc->chan[i].run == 1 && + sc->chan[i].blk < sc->blk[slot]) + sc->blk[slot] = sc->chan[i].blk; + } + if (ch->blk != sc->blk[slot]) + envy24_updintr(sc, ch->dir); + } + break; + } + snd_mtxunlock(sc->lock); + + return 0; +} + +static int +envy24chan_getptr(kobj_t obj, void *data) +{ + struct sc_chinfo *ch = data; + struct sc_info *sc = ch->parent; + u_int32_t ptr; + int rtn; + +#if(0) + device_printf(sc->dev, "envy24chan_getptr()\n"); +#endif + snd_mtxlock(sc->lock); + ptr = envy24_gethwptr(sc, ch->dir); + rtn = ptr * ch->unit; + snd_mtxunlock(sc->lock); + +#if(0) + device_printf(sc->dev, "envy24chan_getptr(): return %d\n", + rtn); +#endif + return rtn; +} + +static struct pcmchan_caps * +envy24chan_getcaps(kobj_t obj, void *data) +{ + struct sc_chinfo *ch = data; + struct sc_info *sc = ch->parent; + struct pcmchan_caps *rtn; + +#if(0) + device_printf(sc->dev, "envy24chan_getcaps()\n"); +#endif + snd_mtxlock(sc->lock); + if (ch->dir == PCMDIR_PLAY) { + if (sc->run[0] == 0) + rtn = &envy24_playcaps; + else + rtn = &sc->caps[0]; + } + else { + if (sc->run[1] == 0) + rtn = &envy24_reccaps; + else + rtn = &sc->caps[1]; + } + snd_mtxunlock(sc->lock); + + return rtn; +} + +static kobj_method_t envy24chan_methods[] = { + KOBJMETHOD(channel_init, envy24chan_init), + KOBJMETHOD(channel_free, envy24chan_free), + KOBJMETHOD(channel_setformat, envy24chan_setformat), + KOBJMETHOD(channel_setspeed, envy24chan_setspeed), + KOBJMETHOD(channel_setblocksize, envy24chan_setblocksize), + KOBJMETHOD(channel_trigger, envy24chan_trigger), + KOBJMETHOD(channel_getptr, envy24chan_getptr), + KOBJMETHOD(channel_getcaps, envy24chan_getcaps), + { 0, 0 } +}; +CHANNEL_DECLARE(envy24chan); + +/* -------------------------------------------------------------------- */ + +/* mixer interface */ + +static int +envy24mixer_init(struct snd_mixer *m) +{ + struct sc_info *sc = mix_getdevinfo(m); + +#if(0) + device_printf(sc->dev, "envy24mixer_init()\n"); +#endif + if (sc == NULL) + return -1; + + /* set volume control rate */ + snd_mtxlock(sc->lock); + envy24_wrmt(sc, ENVY24_MT_VOLRATE, 0x30, 1); /* 0x30 is default value */ + + mix_setdevs(m, ENVY24_MIX_MASK); + mix_setrecdevs(m, ENVY24_MIX_REC_MASK); + snd_mtxunlock(sc->lock); + + return 0; +} + +static int +envy24mixer_reinit(struct snd_mixer *m) +{ + struct sc_info *sc = mix_getdevinfo(m); + + if (sc == NULL) + return -1; +#if(0) + device_printf(sc->dev, "envy24mixer_reinit()\n"); +#endif + + return 0; +} + +static int +envy24mixer_uninit(struct snd_mixer *m) +{ + struct sc_info *sc = mix_getdevinfo(m); + + if (sc == NULL) + return -1; +#if(0) + device_printf(sc->dev, "envy24mixer_uninit()\n"); +#endif + + return 0; +} + +static int +envy24mixer_set(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right) +{ + struct sc_info *sc = mix_getdevinfo(m); + int ch = envy24_mixmap[dev]; + int hwch; + int i; + + if (sc == NULL) + return -1; + if (dev == 0 && sc->cfg->codec->setvolume == NULL) + return -1; + if (dev != 0 && ch == -1) + return -1; + hwch = envy24_chanmap[ch]; +#if(0) + device_printf(sc->dev, "envy24mixer_set(m, %d, %d, %d)\n", + dev, left, right); +#endif + + snd_mtxlock(sc->lock); + if (dev == 0) { + for (i = 0; i < sc->dacn; i++) { + sc->cfg->codec->setvolume(sc->dac[i], PCMDIR_PLAY, left, right); + } + } + else { + /* set volume value for hardware */ + if ((sc->left[hwch] = 100 - left) > ENVY24_VOL_MIN) + sc->left[hwch] = ENVY24_VOL_MUTE; + if ((sc->right[hwch] = 100 - right) > ENVY24_VOL_MIN) + sc->right[hwch] = ENVY24_VOL_MUTE; + + /* set volume for record channel and running play channel */ + if (hwch > ENVY24_CHAN_PLAY_SPDIF || sc->chan[ch].run) + envy24_setvolume(sc, hwch); + } + snd_mtxunlock(sc->lock); + + return right << 8 | left; +} + +static u_int32_t +envy24mixer_setrecsrc(struct snd_mixer *m, u_int32_t src) +{ + struct sc_info *sc = mix_getdevinfo(m); + int ch = envy24_mixmap[src]; +#if(0) + device_printf(sc->dev, "envy24mixer_setrecsrc(m, %d)\n", src); +#endif + + if (ch > ENVY24_CHAN_PLAY_SPDIF) + sc->src = ch; + return src; +} + +static kobj_method_t envy24mixer_methods[] = { + KOBJMETHOD(mixer_init, envy24mixer_init), + KOBJMETHOD(mixer_reinit, envy24mixer_reinit), + KOBJMETHOD(mixer_uninit, envy24mixer_uninit), + KOBJMETHOD(mixer_set, envy24mixer_set), + KOBJMETHOD(mixer_setrecsrc, envy24mixer_setrecsrc), + { 0, 0 } +}; +MIXER_DECLARE(envy24mixer); + +/* -------------------------------------------------------------------- */ + +/* The interrupt handler */ +static void +envy24_intr(void *p) +{ + struct sc_info *sc = (struct sc_info *)p; + struct sc_chinfo *ch; + u_int32_t ptr, dsize, feed; + int i; + +#if(0) + device_printf(sc->dev, "envy24_intr()\n"); +#endif + snd_mtxlock(sc->lock); + if (envy24_checkintr(sc, PCMDIR_PLAY)) { +#if(0) + device_printf(sc->dev, "envy24_intr(): play\n"); +#endif + dsize = sc->psize / 4; + ptr = dsize - envy24_rdmt(sc, ENVY24_MT_PCNT, 2) - 1; +#if(0) + device_printf(sc->dev, "envy24_intr(): ptr = %d-->", ptr); +#endif + ptr -= ptr % sc->blk[0]; + feed = (ptr + dsize - sc->intr[0]) % dsize; +#if(0) + printf("%d intr = %d feed = %d\n", ptr, sc->intr[0], feed); +#endif + for (i = ENVY24_CHAN_PLAY_DAC1; i <= ENVY24_CHAN_PLAY_SPDIF; i++) { + ch = &sc->chan[i]; +#if(0) + if (ch->run) + device_printf(sc->dev, "envy24_intr(): chan[%d].blk = %d\n", i, ch->blk); +#endif + if (ch->run && ch->blk <= feed) + chn_intr(ch->channel); + } + sc->intr[0] = ptr; + envy24_updintr(sc, PCMDIR_PLAY); + } + if (envy24_checkintr(sc, PCMDIR_REC)) { +#if(0) + device_printf(sc->dev, "envy24_intr(): rec\n"); +#endif + dsize = sc->rsize / 4; + ptr = dsize - envy24_rdmt(sc, ENVY24_MT_RCNT, 2) - 1; + ptr -= ptr % sc->blk[1]; + feed = (ptr + dsize - sc->intr[1]) % dsize; + for (i = ENVY24_CHAN_REC_ADC1; i <= ENVY24_CHAN_REC_SPDIF; i++) { + ch = &sc->chan[i]; + if (ch->run && ch->blk <= feed) + chn_intr(ch->channel); + } + sc->intr[1] = ptr; + envy24_updintr(sc, PCMDIR_REC); + } + snd_mtxunlock(sc->lock); + + return; +} + +/* + * Probe and attach the card + */ + +static int +envy24_pci_probe(device_t dev) +{ + u_int16_t sv, sd; + int i; + +#if(0) + printf("envy24_pci_probe()\n"); +#endif + if (pci_get_device(dev) == PCID_ENVY24 && + pci_get_vendor(dev) == PCIV_ENVY24) { + sv = pci_get_subvendor(dev); + sd = pci_get_subdevice(dev); + for (i = 0; cfg_table[i].subvendor != 0 || cfg_table[i].subdevice != 0; i++) { + if (cfg_table[i].subvendor == sv && + cfg_table[i].subdevice == sd) { + break; + } + } + device_set_desc(dev, cfg_table[i].name); +#if(0) + printf("envy24_pci_probe(): return 0\n"); +#endif + return 0; + } + else { +#if(0) + printf("envy24_pci_probe(): return ENXIO\n"); +#endif + return ENXIO; + } +} + +static void +envy24_dmapsetmap(void *arg, bus_dma_segment_t *segs, int nseg, int error) +{ + struct sc_info *sc = (struct sc_info *)arg; + +#if(0) + device_printf(sc->dev, "envy24_dmapsetmap()\n"); +#endif + if (bootverbose) { + printf("envy24(play): setmap %lx, %lx; ", + (unsigned long)segs->ds_addr, + (unsigned long)segs->ds_len); + printf("%p -> %lx\n", sc->pmap, (unsigned long)vtophys(sc->pmap)); + } +} + +static void +envy24_dmarsetmap(void *arg, bus_dma_segment_t *segs, int nseg, int error) +{ + struct sc_info *sc = (struct sc_info *)arg; + +#if(0) + device_printf(sc->dev, "envy24_dmarsetmap()\n"); +#endif + if (bootverbose) { + printf("envy24(record): setmap %lx, %lx; ", + (unsigned long)segs->ds_addr, + (unsigned long)segs->ds_len); + printf("%p -> %lx\n", sc->rmap, (unsigned long)vtophys(sc->pmap)); + } +} + +static void +envy24_dmafree(struct sc_info *sc) +{ +#if(0) + device_printf(sc->dev, "envy24_dmafree():"); + if (sc->rmap) printf(" sc->rmap(0x%08x)", (u_int32_t)sc->rmap); + else printf(" sc->rmap(null)"); + if (sc->pmap) printf(" sc->pmap(0x%08x)", (u_int32_t)sc->pmap); + else printf(" sc->pmap(null)"); + if (sc->rbuf) printf(" sc->rbuf(0x%08x)", (u_int32_t)sc->rbuf); + else printf(" sc->rbuf(null)"); + if (sc->pbuf) printf(" sc->pbuf(0x%08x)\n", (u_int32_t)sc->pbuf); + else printf(" sc->pbuf(null)\n"); +#endif +#if(0) + if (sc->rmap) + bus_dmamap_unload(sc->dmat, sc->rmap); + if (sc->pmap) + bus_dmamap_unload(sc->dmat, sc->pmap); + if (sc->rbuf) + bus_dmamem_free(sc->dmat, sc->rbuf, sc->rmap); + if (sc->pbuf) + bus_dmamem_free(sc->dmat, sc->pbuf, sc->pmap); +#else + bus_dmamap_unload(sc->dmat, sc->rmap); + bus_dmamap_unload(sc->dmat, sc->pmap); + bus_dmamem_free(sc->dmat, sc->rbuf, sc->rmap); + bus_dmamem_free(sc->dmat, sc->pbuf, sc->pmap); +#endif + + sc->rmap = sc->pmap = NULL; + sc->pbuf = NULL; + sc->rbuf = NULL; + + return; +} + +static int +envy24_dmainit(struct sc_info *sc) +{ + u_int32_t addr; + +#if(0) + device_printf(sc->dev, "envy24_dmainit()\n"); +#endif + /* init values */ + sc->psize = ENVY24_PLAY_BUFUNIT * ENVY24_SAMPLE_NUM; + sc->rsize = ENVY24_REC_BUFUNIT * ENVY24_SAMPLE_NUM; + sc->pbuf = NULL; + sc->rbuf = NULL; + sc->pmap = sc->rmap = NULL; + sc->blk[0] = sc->blk[1] = 0; + + /* allocate DMA buffer */ +#if(0) + device_printf(sc->dev, "envy24_dmainit(): bus_dmamem_alloc(): sc->pbuf\n"); +#endif + if (bus_dmamem_alloc(sc->dmat, (void **)&sc->pbuf, BUS_DMA_NOWAIT, &sc->pmap)) + goto bad; +#if(0) + device_printf(sc->dev, "envy24_dmainit(): bus_dmamem_alloc(): sc->rbuf\n"); +#endif + if (bus_dmamem_alloc(sc->dmat, (void **)&sc->rbuf, BUS_DMA_NOWAIT, &sc->rmap)) + goto bad; +#if(0) + device_printf(sc->dev, "envy24_dmainit(): bus_dmamem_load(): sc->pmap\n"); +#endif + if (bus_dmamap_load(sc->dmat, sc->pmap, sc->pbuf, sc->psize, envy24_dmapsetmap, sc, 0)) + goto bad; +#if(0) + device_printf(sc->dev, "envy24_dmainit(): bus_dmamem_load(): sc->rmap\n"); +#endif + if (bus_dmamap_load(sc->dmat, sc->rmap, sc->rbuf, sc->rsize, envy24_dmarsetmap, sc, 0)) + goto bad; + bzero(sc->pbuf, sc->psize); + bzero(sc->rbuf, sc->rsize); + + /* set values to register */ + addr = vtophys(sc->pbuf); +#if(0) + device_printf(sc->dev, "pbuf(0x%08x)\n", addr); +#endif + envy24_wrmt(sc, ENVY24_MT_PADDR, addr, 4); +#if(0) + device_printf(sc->dev, "PADDR-->(0x%08x)\n", envy24_rdmt(sc, ENVY24_MT_PADDR, 4)); + device_printf(sc->dev, "psize(%ld)\n", sc->psize / 4 - 1); +#endif + envy24_wrmt(sc, ENVY24_MT_PCNT, sc->psize / 4 - 1, 2); +#if(0) + device_printf(sc->dev, "PCNT-->(%ld)\n", envy24_rdmt(sc, ENVY24_MT_PCNT, 2)); +#endif + addr = vtophys(sc->rbuf); + envy24_wrmt(sc, ENVY24_MT_RADDR, addr, 4); + envy24_wrmt(sc, ENVY24_MT_RCNT, sc->rsize / 4 - 1, 2); + + return 0; + bad: + envy24_dmafree(sc); + return ENOSPC; +} + +static void +envy24_putcfg(struct sc_info *sc) +{ + device_printf(sc->dev, "system configuration\n", sc->adcn, sc->dacn); + printf(" SubVendorID: 0x%04x, SubDeviceID: 0x%04x\n", + sc->cfg->subvendor, sc->cfg->subdevice); + printf(" XIN2 Clock Source: "); + switch (sc->cfg->scfg & PCIM_SCFG_XIN2) { + case 0x00: + printf("22.5792MHz(44.1kHz*512)\n"); + break; + case 0x40: + printf("16.9344MHz(44.1kHz*384)\n"); + break; + case 0x80: + printf("from external clock synthesizer chip\n"); + break; + default: + printf("illeagal system setting\n"); + } + printf(" MPU-401 UART(s) #: "); + if (sc->cfg->scfg & PCIM_SCFG_MPU) + printf("2\n"); + else + printf("1\n"); + printf(" AC'97 codec: "); + if (sc->cfg->scfg & PCIM_SCFG_AC97) + printf("not exist\n"); + else + printf("exist\n"); + printf(" ADC #: "); + printf("%d\n", sc->adcn); + printf(" DAC #: "); + printf("%d\n", sc->dacn); + printf(" Multi-track converter type: "); + if ((sc->cfg->acl & PCIM_ACL_MTC) == 0) { + printf("AC'97(SDATA_OUT:"); + if (sc->cfg->acl & PCIM_ACL_OMODE) + printf("packed"); + else + printf("split"); + printf("|SDATA_IN:"); + if (sc->cfg->acl & PCIM_ACL_IMODE) + printf("packed"); + else + printf("split"); + printf(")\n"); + } + else { + printf("I2S("); + if (sc->cfg->i2s & PCIM_I2S_VOL) + printf("with volume, "); + if (sc->cfg->i2s & PCIM_I2S_96KHZ) + printf("96KHz support, "); + switch (sc->cfg->i2s & PCIM_I2S_RES) { + case PCIM_I2S_16BIT: + printf("16bit resolution, "); + break; + case PCIM_I2S_18BIT: + printf("18bit resolution, "); + break; + case PCIM_I2S_20BIT: + printf("20bit resolution, "); + break; + case PCIM_I2S_24BIT: + printf("24bit resolution, "); + break; + } + printf("ID#0x%x)\n", sc->cfg->i2s & PCIM_I2S_ID); + } + printf(" S/PDIF(IN/OUT): "); + if (sc->cfg->spdif & PCIM_SPDIF_IN) + printf("1/"); + else + printf("0/"); + if (sc->cfg->spdif & PCIM_SPDIF_OUT) + printf("1 "); + else + printf("0 "); + if (sc->cfg->spdif & (PCIM_SPDIF_IN | PCIM_SPDIF_OUT)) + printf("ID# 0x%02x\n", (sc->cfg->spdif & PCIM_SPDIF_ID) >> 2); + printf(" GPIO(mask/dir/state): 0x%02x/0x%02x/0x%02x\n", + sc->cfg->gpiomask, sc->cfg->gpiodir, sc->cfg->gpiostate); +} + +static int +envy24_init(struct sc_info *sc) +{ + u_int32_t data; +#if(0) + int rtn; +#endif + int i; + u_int32_t sv, sd; + + +#if(0) + device_printf(sc->dev, "envy24_init()\n"); +#endif + + /* reset chip */ + envy24_wrcs(sc, ENVY24_CCS_CTL, ENVY24_CCS_CTL_RESET | ENVY24_CCS_CTL_NATIVE, 1); + DELAY(200); + envy24_wrcs(sc, ENVY24_CCS_CTL, ENVY24_CCS_CTL_NATIVE, 1); + DELAY(200); + + /* legacy hardware disable */ + data = pci_read_config(sc->dev, PCIR_LAC, 2); + data |= PCIM_LAC_DISABLE; + pci_write_config(sc->dev, PCIR_LAC, data, 2); + + /* check system configuration */ + sc->cfg = NULL; + for (i = 0; cfg_table[i].subvendor != 0 || cfg_table[i].subdevice != 0; i++) { + /* 1st: search configuration from table */ + sv = pci_get_subvendor(sc->dev); + sd = pci_get_subdevice(sc->dev); + if (sv == cfg_table[i].subvendor && sd == cfg_table[i].subdevice) { +#if(0) + device_printf(sc->dev, "Set configuration from table\n"); +#endif + sc->cfg = &cfg_table[i]; + break; + } + } + if (sc->cfg == NULL) { + /* 2nd: read configuration from table */ + sc->cfg = envy24_rom2cfg(sc); + } + sc->adcn = ((sc->cfg->scfg & PCIM_SCFG_ADC) >> 2) + 1; + sc->dacn = (sc->cfg->scfg & PCIM_SCFG_DAC) + 1; + + if (1 /* bootverbose */) { + envy24_putcfg(sc); + } + + /* set system configuration */ + pci_write_config(sc->dev, PCIR_SCFG, sc->cfg->scfg, 1); + pci_write_config(sc->dev, PCIR_ACL, sc->cfg->acl, 1); + pci_write_config(sc->dev, PCIR_I2S, sc->cfg->i2s, 1); + pci_write_config(sc->dev, PCIR_SPDIF, sc->cfg->spdif, 1); + envy24_gpiosetmask(sc, sc->cfg->gpiomask); + envy24_gpiosetdir(sc, sc->cfg->gpiodir); + envy24_gpiowr(sc, sc->cfg->gpiostate); + for (i = 0; i < sc->adcn; i++) { + sc->adc[i] = sc->cfg->codec->create(sc->dev, sc, PCMDIR_REC, i); + sc->cfg->codec->init(sc->adc[i]); + } + for (i = 0; i < sc->dacn; i++) { + sc->dac[i] = sc->cfg->codec->create(sc->dev, sc, PCMDIR_PLAY, i); + sc->cfg->codec->init(sc->dac[i]); + } + + /* initialize DMA buffer */ +#if(0) + device_printf(sc->dev, "envy24_init(): initialize DMA buffer\n"); +#endif + if (envy24_dmainit(sc)) + return ENOSPC; + + /* initialize status */ + sc->run[0] = sc->run[1] = 0; + sc->intr[0] = sc->intr[1] = 0; + sc->speed = 0; + sc->caps[0].fmtlist = envy24_playfmt; + sc->caps[1].fmtlist = envy24_recfmt; + + /* set channel router */ + envy24_route(sc, ENVY24_ROUTE_DAC_1, ENVY24_ROUTE_CLASS_MIX, 0, 0); + envy24_route(sc, ENVY24_ROUTE_DAC_SPDIF, ENVY24_ROUTE_CLASS_DMA, 0, 0); + /* envy24_route(sc, ENVY24_ROUTE_DAC_SPDIF, ENVY24_ROUTE_CLASS_MIX, 0, 0); */ + + /* set macro interrupt mask */ + data = envy24_rdcs(sc, ENVY24_CCS_IMASK, 1); + envy24_wrcs(sc, ENVY24_CCS_IMASK, data & ~ENVY24_CCS_IMASK_PMT, 1); + data = envy24_rdcs(sc, ENVY24_CCS_IMASK, 1); +#if(0) + device_printf(sc->dev, "envy24_init(): CCS_IMASK-->0x%02x\n", data); +#endif + + return 0; +} + +static int +envy24_alloc_resource(struct sc_info *sc) +{ + /* allocate I/O port resource */ + sc->csid = PCIR_CCS; + sc->cs = bus_alloc_resource(sc->dev, SYS_RES_IOPORT, + &sc->csid, 0, ~0, 1, RF_ACTIVE); + sc->ddmaid = PCIR_DDMA; + sc->ddma = bus_alloc_resource(sc->dev, SYS_RES_IOPORT, + &sc->ddmaid, 0, ~0, 1, RF_ACTIVE); + sc->dsid = PCIR_DS; + sc->ds = bus_alloc_resource(sc->dev, SYS_RES_IOPORT, + &sc->dsid, 0, ~0, 1, RF_ACTIVE); + sc->mtid = PCIR_MT; + sc->mt = bus_alloc_resource(sc->dev, SYS_RES_IOPORT, + &sc->mtid, 0, ~0, 1, RF_ACTIVE); + if (!sc->cs || !sc->ddma || !sc->ds || !sc->mt) { + device_printf(sc->dev, "unable to map IO port space\n"); + return ENXIO; + } + sc->cst = rman_get_bustag(sc->cs); + sc->csh = rman_get_bushandle(sc->cs); + sc->ddmat = rman_get_bustag(sc->ddma); + sc->ddmah = rman_get_bushandle(sc->ddma); + sc->dst = rman_get_bustag(sc->ds); + sc->dsh = rman_get_bushandle(sc->ds); + sc->mtt = rman_get_bustag(sc->mt); + sc->mth = rman_get_bushandle(sc->mt); +#if(0) + device_printf(sc->dev, + "IO port register values\nCCS: 0x%lx\nDDMA: 0x%lx\nDS: 0x%lx\nMT: 0x%lx\n", + pci_read_config(sc->dev, PCIR_CCS, 4), + pci_read_config(sc->dev, PCIR_DDMA, 4), + pci_read_config(sc->dev, PCIR_DS, 4), + pci_read_config(sc->dev, PCIR_MT, 4)); +#endif + + /* allocate interupt resource */ + sc->irqid = 0; + sc->irq = bus_alloc_resource(sc->dev, SYS_RES_IRQ, &sc->irqid, + 0, ~0, 1, RF_ACTIVE | RF_SHAREABLE); + if (!sc->irq || + snd_setup_intr(sc->dev, sc->irq, INTR_MPSAFE, envy24_intr, sc, &sc->ih)) { + device_printf(sc->dev, "unable to map interrupt\n"); + return ENXIO; + } + + /* allocate DMA resource */ + if (bus_dma_tag_create(/*parent*/NULL, /*alignment*/4, /*boundary*/0, + /*lowaddr*/BUS_SPACE_MAXADDR_ENVY24, + /*highaddr*/BUS_SPACE_MAXADDR_ENVY24, + /*filter*/NULL, /*filterarg*/NULL, + /*maxsize*/BUS_SPACE_MAXSIZE_ENVY24, + /*nsegments*/1, /*maxsegsz*/0x3ffff, + /*flags*/0, &sc->dmat) != 0) { + device_printf(sc->dev, "unable to create dma tag\n"); + return ENXIO; + } + + return 0; +} + +static int +envy24_pci_attach(device_t dev) +{ + u_int32_t data; + struct sc_info *sc; + char status[SND_STATUSLEN]; + char name[ENVY24_NAMELEN]; + int err = 0; + int i; + +#if(0) + device_printf(dev, "envy24_pci_attach()\n"); +#endif + /* get sc_info data area */ + if ((sc = malloc(sizeof(*sc), M_ENVY24, M_NOWAIT)) == NULL) { + device_printf(dev, "cannot allocate softc\n"); + return ENXIO; + } + + bzero(sc, sizeof(*sc)); + snprintf(name, ENVY24_NAMELEN, "%s:envy24", device_get_nameunit(dev)); + sc->lock = snd_mtxcreate(name); + sc->dev = dev; + + /* initialize PCI interface */ + 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); + + /* allocate resources */ + if (err = envy24_alloc_resource(sc)) { + device_printf(dev, "unable to allocate system resources\n"); + goto bad; + } + + /* initialize card */ + if (err = envy24_init(sc)) { + device_printf(dev, "unable to initialize the card\n"); + goto bad; + } + + /* set multi track mixer */ + mixer_init(dev, &envy24mixer_class, sc); + + /* set channel information */ + if (err = pcm_register(dev, sc, 5, 2 + sc->adc)) + goto bad; + sc->chnum = 0; + for (i = 0; i < 5; i++) { + pcm_addchan(dev, PCMDIR_PLAY, &envy24chan_class, sc); + sc->chnum++; + } + for (i = 0; i < 2 + sc->adcn; i++) { + pcm_addchan(dev, PCMDIR_REC, &envy24chan_class, sc); + sc->chnum++; + } + + /* set status iformation */ + snprintf(status, SND_STATUSLEN, + "at io 0x%lx:%ld,0x%lx:%ld,0x%lx:%ld,0x%lx:%ld irq %ld", + rman_get_start(sc->cs), + rman_get_end(sc->cs) - rman_get_start(sc->cs) + 1, + rman_get_start(sc->ddma), + rman_get_end(sc->ddma) - rman_get_start(sc->ddma) + 1, + rman_get_start(sc->ds), + rman_get_end(sc->ds) - rman_get_start(sc->ds) + 1, + rman_get_start(sc->mt), + rman_get_end(sc->mt) - rman_get_start(sc->mt) + 1, + rman_get_start(sc->irq)); + pcm_setstatus(dev, status); + + return 0; + +bad: + if (sc->ih) + bus_teardown_intr(dev, sc->irq, sc->ih); + if (sc->irq) + bus_release_resource(dev, SYS_RES_IRQ, sc->irqid, sc->irq); + envy24_dmafree(sc); + if (sc->dmat) + bus_dma_tag_destroy(sc->dmat); + envy24_cfgfree(sc->cfg); + if (sc->cs) + bus_release_resource(dev, SYS_RES_IOPORT, sc->csid, sc->cs); + if (sc->ddma) + bus_release_resource(dev, SYS_RES_IOPORT, sc->ddmaid, sc->ddma); + if (sc->ds) + bus_release_resource(dev, SYS_RES_IOPORT, sc->dsid, sc->ds); + if (sc->mt) + bus_release_resource(dev, SYS_RES_IOPORT, sc->mtid, sc->mt); + if (sc->lock) + snd_mtxfree(sc->lock); + free(sc, M_ENVY24); + return err; +} + +static int +envy24_pci_detach(device_t dev) +{ + struct sc_info *sc; + int r; + int i; + +#if(0) + device_printf(dev, "envy24_pci_detach()\n"); +#endif + sc = pcm_getdevinfo(dev); + if (sc == NULL) + return 0; + r = pcm_unregister(dev); + if (r) + return r; + + envy24_dmafree(sc); + if (sc->cfg->codec->destroy != NULL) { + for (i = 0; i < sc->adcn; i++) + sc->cfg->codec->destroy(sc->adc[i]); + for (i = 0; i < sc->dacn; i++) + sc->cfg->codec->destroy(sc->dac[i]); + } + envy24_cfgfree(sc->cfg); + bus_dma_tag_destroy(sc->dmat); + bus_teardown_intr(dev, sc->irq, sc->ih); + bus_release_resource(dev, SYS_RES_IRQ, sc->irqid, sc->irq); + bus_release_resource(dev, SYS_RES_IOPORT, sc->csid, sc->cs); + bus_release_resource(dev, SYS_RES_IOPORT, sc->ddmaid, sc->ddma); + bus_release_resource(dev, SYS_RES_IOPORT, sc->dsid, sc->ds); + bus_release_resource(dev, SYS_RES_IOPORT, sc->mtid, sc->mt); + snd_mtxfree(sc->lock); + free(sc, M_ENVY24); + return 0; +} + +static device_method_t envy24_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, envy24_pci_probe), + DEVMETHOD(device_attach, envy24_pci_attach), + DEVMETHOD(device_detach, envy24_pci_detach), + { 0, 0 } +}; + +static driver_t envy24_driver = { + "pcm", + envy24_methods, +#if __FreeBSD_version > 500000 + PCM_SOFTC_SIZE, +#else + sizeof(struct snddev_info), +#endif +}; + +DRIVER_MODULE(snd_envy24, pci, envy24_driver, pcm_devclass, 0, 0); +MODULE_DEPEND(snd_envy24, snd_pcm, PCM_MINVER, PCM_PREFVER, PCM_MAXVER); +MODULE_DEPEND(snd_envy24, snd_ak452x, PCM_MINVER, PCM_PREFVER, PCM_MAXVER); +MODULE_VERSION(snd_envy24, 1); diff --git a/sys/dev/sound/pci/envy24.h b/sys/dev/sound/pci/envy24.h new file mode 100644 index 000000000000..8830145842b6 --- /dev/null +++ b/sys/dev/sound/pci/envy24.h @@ -0,0 +1,482 @@ +/* + * Copyright (c) 2001 Katsurajima Naoto + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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, WHETHERIN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THEPOSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + + +/* -------------------------------------------------------------------- */ + +/* PCI device ID */ +#define PCIV_ENVY24 0x1412 +#define PCID_ENVY24 0x1712 + +/* PCI Registers */ + +#define PCIR_CCS 0x10 /* Controller I/O Base Address */ +#define PCIR_DDMA 0x14 /* DDMA I/O Base Address */ +#define PCIR_DS 0x18 /* DMA Path Registers I/O Base Address */ +#define PCIR_MT 0x1c /* Professional Multi-Track I/O Base Address */ + +#define PCIR_LAC 0x40 /* Legacy Audio Control */ +#define PCIM_LAC_DISABLE 0x8000 /* Legacy Audio Hardware disabled */ +#define PCIM_LAC_SBDMA0 0x0000 /* SB DMA Channel Select: 0 */ +#define PCIM_LAC_SBDMA1 0x0040 /* SB DMA Channel Select: 1 */ +#define PCIM_LAC_SBDMA3 0x00c0 /* SB DMA Channel Select: 3 */ +#define PCIM_LAC_IOADDR10 0x0020 /* I/O Address Alias Control */ +#define PCIM_LAC_MPU401 0x0008 /* MPU-401 I/O enable */ +#define PCIM_LAC_GAME 0x0004 /* Game Port enable (200h) */ +#define PCIM_LAC_FM 0x0002 /* FM I/O enable (AdLib 388h base) */ +#define PCIM_LAC_SB 0x0001 /* SB I/O enable */ + +#define PCIR_LCC 0x42 /* Legacy Configuration Control */ +#define PCIM_LCC_VINT 0xff00 /* Interrupt vector to be snooped */ +#define PCIM_LCC_SVIDRW 0x0080 /* SVID read/write enable */ +#define PCIM_LCC_SNPSB 0x0040 /* snoop SB 22C/24Ch I/O write cycle */ +#define PCIM_LCC_SNPPIC 0x0020 /* snoop PIC I/O R/W cycle */ +#define PCIM_LCC_SNPPCI 0x0010 /* snoop PCI bus interrupt acknowledge cycle */ +#define PCIM_LCC_SBBASE 0x0008 /* SB base 240h(1)/220h(0) */ +#define PCIM_LCC_MPUBASE 0x0006 /* MPU-401 base 300h-330h */ +#define PCIM_LCC_LDMA 0x0001 /* Legacy DMA enable */ + +#define PCIR_SCFG 0x60 /* System Configuration Register */ +#define PCIM_SCFG_XIN2 0xc0 /* XIN2 Clock Source Configuration */ + /* 00: 22.5792MHz(44.1kHz*512) */ + /* 01: 16.9344MHz(44.1kHz*384) */ + /* 10: from external clock synthesizer chip */ +#define PCIM_SCFG_MPU 0x20 /* 1(0)/2(1) MPU-401 UART(s) */ +#define PCIM_SCFG_AC97 0x10 /* 0: AC'97 codec exist */ + /* 1: AC'97 codec not exist */ +#define PCIM_SCFG_ADC 0x0c /* 1-4 stereo ADC connected */ +#define PCIM_SCFG_DAC 0x03 /* 1-4 stereo DAC connected */ + +#define PCIR_ACL 0x61 /* AC-Link Configuration Register */ +#define PCIM_ACL_MTC 0x80 /* Multi-track converter type: 0:AC'97 1:I2S */ +#define PCIM_ACL_OMODE 0x02 /* AC 97 codec SDATA_OUT 0:split 1:packed */ +#define PCIM_ACL_IMODE 0x01 /* AC 97 codec SDATA_IN 0:split 1:packed */ + +#define PCIR_I2S 0x62 /* I2S Converters Features Register */ +#define PCIM_I2S_VOL 0x80 /* I2S codec Volume and mute */ +#define PCIM_I2S_96KHZ 0x40 /* I2S converter 96kHz sampling rate support */ +#define PCIM_I2S_RES 0x30 /* Converter resolution */ +#define PCIM_I2S_16BIT 0x00 /* 16bit */ +#define PCIM_I2S_18BIT 0x10 /* 18bit */ +#define PCIM_I2S_20BIT 0x20 /* 20bit */ +#define PCIM_I2S_24BIT 0x30 /* 24bit */ +#define PCIM_I2S_ID 0x0f /* Other I2S IDs */ + +#define PCIR_SPDIF 0x63 /* S/PDIF Configuration Register */ +#define PCIM_SPDIF_ID 0xfc /* S/PDIF chip ID */ +#define PCIM_SPDIF_IN 0x02 /* S/PDIF Stereo In is present */ +#define PCIM_SPDIF_OUT 0x01 /* S/PDIF Stereo Out is present */ + +#define PCIR_POWER_STAT 0x84 /* Power Management Control and Status */ + +/* Controller Registers */ + +#define ENVY24_CCS_CTL 0x00 /* Control/Status Register */ +#define ENVY24_CCS_CTL_RESET 0x80 /* Entire Chip soft reset */ +#define ENVY24_CCS_CTL_DMAINT 0x40 /* DS DMA Channel-C interrupt */ +#define ENVY24_CCS_CTL_DOSVOL 0x10 /* set the DOS WT volume control */ +#define ENVY24_CCS_CTL_EDGE 0x08 /* SERR# edge (only one PCI clock width) */ +#define ENVY24_CCS_CTL_SBINT 0x02 /* SERR# assertion for SB interrupt */ +#define ENVY24_CCS_CTL_NATIVE 0x01 /* Mode select: 0:SB mode 1:native mode */ + +#define ENVY24_CCS_IMASK 0x01 /* Interrupt Mask Register */ +#define ENVY24_CCS_IMASK_PMIDI 0x80 /* Primary MIDI */ +#define ENVY24_CCS_IMASK_TIMER 0x40 /* Timer */ +#define ENVY24_CCS_IMASK_SMIDI 0x20 /* Secondary MIDI */ +#define ENVY24_CCS_IMASK_PMT 0x10 /* Professional Multi-track */ +#define ENVY24_CCS_IMASK_FM 0x08 /* FM/MIDI trapping */ +#define ENVY24_CCS_IMASK_PDMA 0x04 /* Playback DS DMA */ +#define ENVY24_CCS_IMASK_RDMA 0x02 /* Consumer record DMA */ +#define ENVY24_CCS_IMASK_SB 0x01 /* Consumer/SB mode playback */ + +#define ENVY24_CCS_ISTAT 0x02 /* Interrupt Status Register */ +#define ENVY24_CCS_ISTAT_PMIDI 0x80 /* Primary MIDI */ +#define ENVY24_CCS_ISTAT_TIMER 0x40 /* Timer */ +#define ENVY24_CCS_ISTAT_SMIDI 0x20 /* Secondary MIDI */ +#define ENVY24_CCS_ISTAT_PMT 0x10 /* Professional Multi-track */ +#define ENVY24_CCS_ISTAT_FM 0x08 /* FM/MIDI trapping */ +#define ENVY24_CCS_ISTAT_PDMA 0x04 /* Playback DS DMA */ +#define ENVY24_CCS_ISTAT_RDMA 0x02 /* Consumer record DMA */ +#define ENVY24_CCS_ISTAT_SB 0x01 /* Consumer/SB mode playback */ + +#define ENVY24_CCS_INDEX 0x03 /* Envy24 Index Register */ +#define ENVY24_CCS_DATA 0x04 /* Envy24 Data Register */ + +#define ENVY24_CCS_NMI1 0x05 /* NMI Status Register 1 */ +#define ENVY24_CCS_NMI1_PCI 0x80 /* PCI I/O read/write cycle */ +#define ENVY24_CCS_NMI1_SB 0x40 /* SB 22C/24C write */ +#define ENVY24_CCS_NMI1_SBDMA 0x10 /* SB interrupt (SB DMA/SB F2 command) */ +#define ENVY24_CCS_NMI1_DSDMA 0x08 /* DS channel C DMA interrupt */ +#define ENVY24_CCS_NMI1_MIDI 0x04 /* MIDI 330h or [PCI_10]h+Ch write */ +#define ENVY24_CCS_NMI1_FM 0x01 /* FM data register write */ + +#define ENVY24_CCS_NMIDAT 0x06 /* NMI Data Register */ +#define ENVY24_CCS_NMIIDX 0x07 /* NMI Index Register */ +#define ENVY24_CCS_AC97IDX 0x08 /* Consumer AC'97 Index Register */ + +#define ENVY24_CCS_AC97CMD 0x09 /* Consumer AC'97 Command/Status Register */ +#define ENVY24_CCS_AC97CMD_COLD 0x80 /* Cold reset */ +#define ENVY24_CCS_AC97CMD_WARM 0x40 /* Warm reset */ +#define ENVY24_CCS_AC97CMD_WRCODEC 0x20 /* Write to AC'97 codec registers */ +#define ENVY24_CCS_AC97CMD_RDCODEC 0x10 /* Read from AC'97 codec registers */ +#define ENVY24_CCS_AC97CMD_READY 0x08 /* AC'97 codec ready status bit */ +#define ENVY24_CCS_AC97CMD_PVSR 0x02 /* VSR for Playback */ +#define ENVY24_CCS_AC97CMD_RVSR 0x01 /* VSR for Record */ + +#define ENVY24_CCS_AC97DAT 0x0a /* Consumer AC'97 Data Port Register */ +#define ENVY24_CCS_PMIDIDAT 0x0c /* Primary MIDI UART Data Register */ +#define ENVY24_CCS_PMIDICMD 0x0d /* Primary MIDI UART Command/Status Register */ + +#define ENVY24_CCS_NMI2 0x0e /* NMI Status Register 2 */ +#define ENVY24_CCS_NMI2_FMBANK 0x30 /* FM bank indicator */ +#define ENVY24_CCS_NMI2_FM0 0x10 /* FM bank 0 (388h/220h/228h) */ +#define ENVY24_CCS_NMI2_FM1 0x20 /* FM bank 1 (38ah/222h) */ +#define ENVY24_CCS_NMI2_PICIO 0x0f /* PIC I/O cycle */ +#define ENVY24_CCS_NMI2_PIC20W 0x01 /* 20h write */ +#define ENVY24_CCS_NMI2_PICA0W 0x02 /* a0h write */ +#define ENVY24_CCS_NMI2_PIC21W 0x05 /* 21h write */ +#define ENVY24_CCS_NMI2_PICA1W 0x06 /* a1h write */ +#define ENVY24_CCS_NMI2_PIC20R 0x09 /* 20h read */ +#define ENVY24_CCS_NMI2_PICA0R 0x0a /* a0h read */ +#define ENVY24_CCS_NMI2_PIC21R 0x0d /* 21h read */ +#define ENVY24_CCS_NMI2_PICA1R 0x0e /* a1h read */ + +#define ENVY24_CCS_JOY 0x0f /* Game port register */ + +#define ENVY24_CCS_I2CDEV 0x10 /* I2C Port Device Address Register */ +#define ENVY24_CCS_I2CDEV_ADDR 0xfe /* I2C device address */ +#define ENVY24_CCS_I2CDEV_ROM 0xa0 /* reserved for the external I2C E2PROM */ +#define ENVY24_CCS_I2CDEV_WR 0x01 /* write */ +#define ENVY24_CCS_I2CDEV_RD 0x00 /* read */ + +#define ENVY24_CCS_I2CADDR 0x11 /* I2C Port Byte Address Register */ +#define ENVY24_CCS_I2CDATA 0x12 /* I2C Port Read/Write Data Register */ + +#define ENVY24_CCS_I2CSTAT 0x13 /* I2C Port Control and Status Register */ +#define ENVY24_CCS_I2CSTAT_ROM 0x80 /* external E2PROM exists */ +#define ENVY24_CCS_I2CSTAT_BSY 0x01 /* I2C port read/write status busy */ + +#define ENVY24_CCS_CDMABASE 0x14 /* Consumer Record DMA Current/Base Address Register */ +#define ENVY24_CCS_CDMACNT 0x18 /* Consumer Record DMA Current/Base Count Register */ +#define ENVY24_CCS_SERR 0x1b /* PCI Configuration SERR# Shadow Register */ +#define ENVY24_CCS_SMIDIDAT 0x1c /* Secondary MIDI UART Data Register */ +#define ENVY24_CCS_SMIDICMD 0x1d /* Secondary MIDI UART Command/Status Register */ + +#define ENVY24_CCS_TIMER 0x1e /* Timer Register */ +#define ENVY24_CCS_TIMER_EN 0x8000 /* Timer count enable */ +#define ENVY24_CCS_TIMER_MASK 0x7fff /* Timer counter mask */ + +/* Controller Indexed Registers */ + +#define ENVY24_CCI_PTCHIGH 0x00 /* Playback Terminal Count Register (High Byte) */ +#define ENVY24_CCI_PTCLOW 0x01 /* Playback Terminal Count Register (Low Byte) */ + +#define ENVY24_CCI_PCTL 0x02 /* Playback Control Register */ +#define ENVY24_CCI_PCTL_TURBO 0x80 /* 4x up sampling in the host by software */ +#define ENVY24_CCI_PCTL_U8 0x10 /* 8 bits unsigned */ +#define ENVY24_CCI_PCTL_S16 0x00 /* 16 bits signed */ +#define ENVY24_CCI_PCTL_STEREO 0x08 /* stereo */ +#define ENVY24_CCI_PCTL_MONO 0x00 /* mono */ +#define ENVY24_CCI_PCTL_FLUSH 0x04 /* FIFO flush (sticky bit. Requires toggling) */ +#define ENVY24_CCI_PCTL_PAUSE 0x02 /* Pause */ +#define ENVY24_CCI_PCTL_ENABLE 0x01 /* Playback enable */ + +#define ENVY24_CCI_PLVOL 0x03 /* Playback Left Volume/Pan Register */ +#define ENVY24_CCI_PRVOL 0x04 /* Playback Right Volume/Pan Register */ +#define ENVY24_CCI_VOL_MASK 0x3f /* Volume value mask */ + +#define ENVY24_CCI_SOFTVOL 0x05 /* Soft Volume/Mute Control Register */ +#define ENVY24_CCI_PSRLOW 0x06 /* Playback Sampling Rate Register (Low Byte) */ +#define ENVY24_CCI_PSRMID 0x07 /* Playback Sampling Rate Register (Middle Byte) */ +#define ENVY24_CCI_PSRHIGH 0x08 /* Playback Sampling Rate Register (High Byte) */ +#define ENVY24_CCI_RTCHIGH 0x10 /* Record Terminal Count Register (High Byte) */ +#define ENVY24_CCI_RTCLOW 0x11 /* Record Terminal Count Register (Low Byte) */ + +#define ENVY24_CCI_RCTL 0x12 /* Record Control Register */ +#define ENVY24_CCI_RCTL_DRTN 0x80 /* Digital return enable */ +#define ENVY24_CCI_RCTL_U8 0x04 /* 8 bits unsigned */ +#define ENVY24_CCI_RCTL_S16 0x00 /* 16 bits signed */ +#define ENVY24_CCI_RCTL_STEREO 0x00 /* stereo */ +#define ENVY24_CCI_RCTL_MONO 0x02 /* mono */ +#define ENVY24_CCI_RCTL_ENABLE 0x01 /* Record enable */ + +#define ENVY24_CCI_GPIODAT 0x20 /* GPIO Data Register */ +#define ENVY24_CCI_GPIOMASK 0x21 /* GPIO Write Mask Register */ + +#define ENVY24_CCI_GPIOCTL 0x22 /* GPIO Direction Control Register */ +#define ENVY24_CCI_GPIO_OUT 1 /* output */ +#define ENVY24_CCI_GPIO_IN 0 /* input */ + +#define ENVY24_CCI_CPDWN 0x30 /* Consumer Section Power Down Register */ +#define ENVY24_CCI_CPDWN_XTAL 0x80 /* Crystal clock generation power down for XTAL_1 */ +#define ENVY24_CCI_CPDWN_GAME 0x40 /* Game port analog power down */ +#define ENVY24_CCI_CPDWN_I2C 0x10 /* I2C port clock */ +#define ENVY24_CCI_CPDWN_MIDI 0x08 /* MIDI clock */ +#define ENVY24_CCI_CPDWN_AC97 0x04 /* AC'97 clock */ +#define ENVY24_CCI_CPDWN_DS 0x02 /* DS Block clock */ +#define ENVY24_CCI_CPDWN_PCI 0x01 /* PCI clock for SB, DMA controller */ + +#define ENVY24_CCI_MTPDWN 0x31 /* Multi-Track Section Power Down Register */ +#define ENVY24_CCI_MTPDWN_XTAL 0x80 /* Crystal clock generation power down for XTAL_2 */ +#define ENVY24_CCI_MTPDWN_SPDIF 0x04 /* S/PDIF clock */ +#define ENVY24_CCI_MTPDWN_MIX 0x02 /* Professional digital mixer clock */ +#define ENVY24_CCI_MTPDWN_I2S 0x01 /* Multi-track I2S serial interface clock */ + +/* DDMA Registers */ + +#define ENVY24_DDMA_ADDR0 0x00 /* DMA Base and Current Address bit 0-7 */ +#define ENVY24_DDMA_ADDR8 0x01 /* DMA Base and Current Address bit 8-15 */ +#define ENVY24_DDMA_ADDR16 0x02 /* DMA Base and Current Address bit 16-23 */ +#define ENVY24_DDMA_ADDR24 0x03 /* DMA Base and Current Address bit 24-31 */ +#define ENVY24_DDMA_CNT0 0x04 /* DMA Base and Current Count 0-7 */ +#define ENVY24_DDMA_CNT8 0x05 /* DMA Base and Current Count 8-15 */ +#define ENVY24_DDMA_CNT16 0x06 /* (not supported) */ +#define ENVY24_DDMA_CMD 0x08 /* Status and Command */ +#define ENVY24_DDMA_MODE 0x0b /* Mode */ +#define ENVY24_DDMA_RESET 0x0c /* Master reset */ +#define ENVY24_DDMA_CHAN 0x0f /* Channel Mask */ + +/* Consumer Section DMA Channel Registers */ + +#define ENVY24_CS_INTMASK 0x00 /* DirectSound DMA Interrupt Mask Register */ +#define ENVY24_CS_INTSTAT 0x02 /* DirectSound DMA Interrupt Status Register */ +#define ENVY24_CS_CHDAT 0x04 /* Channel Data register */ + +#define ENVY24_CS_CHIDX 0x08 /* Channel Index Register */ +#define ENVY24_CS_CHIDX_NUM 0xf0 /* Channel number */ +#define ENVY24_CS_CHIDX_ADDR0 0x00 /* Buffer_0 DMA base address */ +#define ENVY24_CS_CHIDX_CNT0 0x01 /* Buffer_0 DMA base count */ +#define ENVY24_CS_CHIDX_ADDR1 0x02 /* Buffer_1 DMA base address */ +#define ENVY24_CS_CHIDX_CNT1 0x03 /* Buffer_1 DMA base count */ +#define ENVY24_CS_CHIDX_CTL 0x04 /* Channel Control and Status register */ +#define ENVY24_CS_CHIDX_RATE 0x05 /* Channel Sampling Rate */ +#define ENVY24_CS_CHIDX_VOL 0x06 /* Channel left and right volume/pan control */ +/* Channel Control and Status Register at Index 4h */ +#define ENVY24_CS_CTL_BUF 0x80 /* indicating that the current active buffer */ +#define ENVY24_CS_CTL_AUTO1 0x40 /* Buffer_1 auto init. enable */ +#define ENVY24_CS_CTL_AUTO0 0x20 /* Buffer_0 auto init. enable */ +#define ENVY24_CS_CTL_FLUSH 0x10 /* Flush FIFO */ +#define ENVY24_CS_CTL_STEREO 0x08 /* stereo(or mono) */ +#define ENVY24_CS_CTL_U8 0x04 /* 8-bit unsigned(or 16-bit signed) */ +#define ENVY24_CS_CTL_PAUSE 0x02 /* DMA request 1:pause */ +#define ENVY24_CS_CTL_START 0x01 /* DMA request 1: start, 0:stop */ +/* Consumer mode Left/Right Volume Register at Index 06h */ +#define ENVY24_CS_VOL_RIGHT 0x3f00 +#define ENVY24_CS_VOL_LEFT 0x003f + +/* Professional Multi-Track Control Registers */ + +#define ENVY24_MT_INT 0x00 /* DMA Interrupt Mask and Status Register */ +#define ENVY24_MT_INT_RMASK 0x80 /* Multi-track record interrupt mask */ +#define ENVY24_MT_INT_PMASK 0x40 /* Multi-track playback interrupt mask */ +#define ENVY24_MT_INT_RSTAT 0x02 /* Multi-track record interrupt status */ +#define ENVY24_MT_INT_PSTAT 0x01 /* Multi-track playback interrupt status */ + +#define ENVY24_MT_RATE 0x01 /* Sampling Rate Select Register */ +#define ENVY24_MT_RATE_SPDIF 0x10 /* S/PDIF input clock as the master */ +#define ENVY24_MT_RATE_48000 0x00 +#define ENVY24_MT_RATE_24000 0x01 +#define ENVY24_MT_RATE_12000 0x02 +#define ENVY24_MT_RATE_9600 0x03 +#define ENVY24_MT_RATE_32000 0x04 +#define ENVY24_MT_RATE_16000 0x05 +#define ENVY24_MT_RATE_8000 0x06 +#define ENVY24_MT_RATE_96000 0x07 +#define ENVY24_MT_RATE_64000 0x0f +#define ENVY24_MT_RATE_44100 0x08 +#define ENVY24_MT_RATE_22050 0x09 +#define ENVY24_MT_RATE_11025 0x0a +#define ENVY24_MT_RATE_88200 0x0b +#define ENVY24_MT_RATE_MASK 0x0f + +#define ENVY24_MT_I2S 0x02 /* I2S Data Format Register */ +#define ENVY24_MT_I2S_MLR128 0x08 /* MCLK/LRCLK ratio 128x(or 256x) */ +#define ENVY24_MT_I2S_SLR48 0x04 /* SCLK/LRCLK ratio 48bpf(or 64bpf) */ +#define ENVY24_MT_I2S_FORM 0x00 /* I2S data format */ + +#define ENVY24_MT_AC97IDX 0x04 /* Index Register for AC'97 Codecs */ + +#define ENVY24_MT_AC97CMD 0x05 /* Command and Status Register for AC'97 Codecs */ +#define ENVY24_MT_AC97CMD_CLD 0x80 /* Cold reset */ +#define ENVY24_MT_AC97CMD_WRM 0x40 /* Warm reset */ +#define ENVY24_MT_AC97CMD_WR 0x20 /* write to AC'97 codec register */ +#define ENVY24_MT_AC97CMD_RD 0x10 /* read AC'97 CODEC register */ +#define ENVY24_MT_AC97CMD_RDY 0x08 /* AC'97 codec ready status bit */ +#define ENVY24_MT_AC97CMD_ID 0x03 /* ID(0-3) for external AC 97 registers */ + +#define ENVY24_MT_AC97DLO 0x06 /* AC'97 codec register data low byte */ +#define ENVY24_MT_AC97DHI 0x07 /* AC'97 codec register data high byte */ +#define ENVY24_MT_PADDR 0x10 /* Playback DMA Current/Base Address Register */ +#define ENVY24_MT_PCNT 0x14 /* Playback DMA Current/Base Count Register */ +#define ENVY24_MT_PTERM 0x16 /* Playback Current/Base Terminal Count Register */ +#define ENVY24_MT_PCTL 0x18 /* Playback and Record Control Register */ +#define ENVY24_MT_PCTL_RSTART 0x04 /* 1: Record start; 0: Record stop */ +#define ENVY24_MT_PCTL_PAUSE 0x02 /* 1: Pause; 0: Resume */ +#define ENVY24_MT_PCTL_PSTART 0x01 /* 1: Playback start; 0: Playback stop */ + +#define ENVY24_MT_RADDR 0x20 /* Record DMA Current/Base Address Register */ +#define ENVY24_MT_RCNT 0x24 /* Record DMA Current/Base Count Register */ +#define ENVY24_MT_RTERM 0x26 /* Record Current/Base Terminal Count Register */ +#define ENVY24_MT_RCTL 0x28 /* Record Control Register */ +#define ENVY24_MT_RCTL_RSTART 0x01 /* 1: Record start; 0: Record stop */ + +#define ENVY24_MT_PSDOUT 0x30 /* Routing Control Register for Data to PSDOUT[0:3] */ +#define ENVY24_MT_SPDOUT 0x32 /* Routing Control Register for SPDOUT */ +#define ENVY24_MT_RECORD 0x34 /* Captured (Recorded) data Routing Selection Register */ + +#define BUS_SPACE_MAXADDR_ENVY24 0x0fffffff /* Address space beyond 256MB is not supported */ +#define BUS_SPACE_MAXSIZE_ENVY24 0x3fffc /* 64k x 4byte(1dword) */ + +#define ENVY24_MT_VOLUME 0x38 /* Left/Right Volume Control Data Register */ +#define ENVY24_MT_VOLUME_L 0x007f /* Left Volume Mask */ +#define ENVY24_MT_VOLUME_R 0x7f00 /* Right Volume Mask */ + +#define ENVY24_MT_VOLIDX 0x3a /* Volume Control Stream Index Register */ +#define ENVY24_MT_VOLRATE 0x3b /* Volume Control Rate Register */ +#define ENVY24_MT_MONAC97 0x3c /* Digital Mixer Monitor Routing Control Register */ +#define ENVY24_MT_PEAKIDX 0x3e /* Peak Meter Index Register */ +#define ENVY24_MT_PEAKDAT 0x3f /* Peak Meter Data Register */ + +/* -------------------------------------------------------------------- */ + +/* ENVY24 mixer channel defines */ +/* + ENVY24 mixer has original line matrix. So, general mixer command is not + able to use for this. If system has consumer AC'97 output, AC'97 line is + used as master mixer, and it is able to control. +*/ +#define ENVY24_CHAN_NUM 11 /* Play * 5 + Record * 5 + Mix * 1 */ + +#define ENVY24_CHAN_PLAY_DAC1 0 +#define ENVY24_CHAN_PLAY_DAC2 1 +#define ENVY24_CHAN_PLAY_DAC3 2 +#define ENVY24_CHAN_PLAY_DAC4 3 +#define ENVY24_CHAN_PLAY_SPDIF 4 +#define ENVY24_CHAN_REC_ADC1 5 +#define ENVY24_CHAN_REC_ADC2 6 +#define ENVY24_CHAN_REC_ADC3 7 +#define ENVY24_CHAN_REC_ADC4 8 +#define ENVY24_CHAN_REC_SPDIF 9 +#define ENVY24_CHAN_REC_MIX 10 + +#define ENVY24_MIX_MASK 0x3ff +#define ENVY24_MIX_REC_MASK 0x3e0 + +/* volume value constants */ +#define ENVY24_VOL_MAX 0 /* 0db(negate) */ +#define ENVY24_VOL_MIN 96 /* -144db(negate) */ +#define ENVY24_VOL_MUTE 127 /* mute */ + +/* -------------------------------------------------------------------- */ + +/* ENVY24 routing control defines */ +/* + ENVY24 has input->output data routing matrix switch. But original ENVY24 + matrix control is so complex. So, in this driver, matrix control is + defined 4 parameters. + + 1: output DAC channels (include S/PDIF output) + 2: output data classes + a. direct output from DMA + b. MIXER output which mixed the DMA outputs and input channels + (NOTICE: this class is able to set only DAC-1 and S/PDIF output) + c. direct input from ADC + d. direct input from S/PDIF + 3: input ADC channel selection(when 2:c. is selected) + 4: left/right reverse + + These parameters matrix is bit reduced from original ENVY24 matrix + pattern(ex. route different ADC input to one DAC). But almost case + this is enough to use. +*/ +#define ENVY24_ROUTE_DAC_1 0 +#define ENVY24_ROUTE_DAC_2 1 +#define ENVY24_ROUTE_DAC_3 2 +#define ENVY24_ROUTE_DAC_4 3 +#define ENVY24_ROUTE_DAC_SPDIF 4 + +#define ENVY24_ROUTE_CLASS_DMA 0 +#define ENVY24_ROUTE_CLASS_MIX 1 +#define ENVY24_ROUTE_CLASS_ADC 2 +#define ENVY24_ROUTE_CLASS_SPDIF 3 + +#define ENVY24_ROUTE_ADC_1 0 +#define ENVY24_ROUTE_ADC_2 1 +#define ENVY24_ROUTE_ADC_3 2 +#define ENVY24_ROUTE_ADC_4 3 + +#define ENVY24_ROUTE_NORMAL 0 +#define ENVY24_ROUTE_REVERSE 1 +#define ENVY24_ROUTE_LEFT 0 +#define ENVY24_ROUTE_RIGHT 1 + +/* -------------------------------------------------------------------- */ + +/* + These map values are refferd from ALSA sound driver. +*/ +/* ENVY24 configuration E2PROM map */ +#define ENVY24_E2PROM_SUBVENDOR 0x00 +#define ENVY24_E2PROM_SUBDEVICE 0x02 +#define ENVY24_E2PROM_SIZE 0x04 +#define ENVY24_E2PROM_VERSION 0x05 +#define ENVY24_E2PROM_SCFG 0x06 +#define ENVY24_E2PROM_ACL 0x07 +#define ENVY24_E2PROM_I2S 0x08 +#define ENVY24_E2PROM_SPDIF 0x09 +#define ENVY24_E2PROM_GPIOMASK 0x0a +#define ENVY24_E2PROM_GPIOSTATE 0x0b +#define ENVY24_E2PROM_GPIODIR 0x0c +#define ENVY24_E2PROM_AC97MAIN 0x0d +#define ENVY24_E2PROM_AC97PCM 0x0f +#define ENVY24_E2PROM_AC97REC 0x11 +#define ENVY24_E2PROM_AC97RECSRC 0x13 +#define ENVY24_E2PROM_DACID 0x14 +#define ENVY24_E2PROM_ADCID 0x18 +#define ENVY24_E2PROM_EXTRA 0x1c + +/* GPIO connect map of M-Audio Delta series */ +#define ENVY24_GPIO_CS84X4_PRO 0x01 +#define ENVY24_GPIO_CS8414_STATUS 0x02 +#define ENVY24_GPIO_CS84X4_CLK 0x04 +#define ENVY24_GPIO_CS84X4_DATA 0x08 +#define ENVY24_GPIO_AK4524_CDTI 0x10 /* this value is duplicated to input select */ +#define ENVY24_GPIO_AK4524_CCLK 0x20 +#define ENVY24_GPIO_AK4524_CS0 0x40 +#define ENVY24_GPIO_AK4524_CS1 0x80 + +/* M-Audio Delta series S/PDIF(CS84[01]4) control pin values */ +#define ENVY24_CS8404_PRO_RATE 0x18 +#define ENVY24_CS8404_PRO_RATE32 0x00 +#define ENVY24_CS8404_PRO_RATE441 0x10 +#define ENVY24_CS8404_PRO_RATE48 0x08 + +/* M-Audio Delta series parameter */ +#define ENVY24_DELTA_AK4524_CIF 0 + +/* end of file */ diff --git a/sys/dev/sound/pci/envy24ht.c b/sys/dev/sound/pci/envy24ht.c new file mode 100644 index 000000000000..c23efc331724 --- /dev/null +++ b/sys/dev/sound/pci/envy24ht.c @@ -0,0 +1,2445 @@ +/* + * Copyright (c) 2001 Katsurajima Naoto + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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, WHETHERIN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THEPOSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include +#include +#include +#include + +#include +#include + +#include "mixer_if.h" + +MALLOC_DEFINE(M_ENVY24, "envy24", "envy24 audio"); + +/* -------------------------------------------------------------------- */ + +struct sc_info; + +#define ENVY24_PLAY_CHNUM 10 +#define ENVY24_REC_CHNUM 12 +#define ENVY24_PLAY_BUFUNIT (4 /* byte/sample */ * 10 /* channel */) +#define ENVY24_REC_BUFUNIT (4 /* byte/sample */ * 12 /* channel */) +#define ENVY24_SAMPLE_NUM 4096 + +#define ENVY24_TIMEOUT 1000 + +#define ENVY24_DEFAULT_FORMAT (AFMT_STEREO | AFMT_S16_LE) + +#define ENVY24_NAMELEN 32 + +typedef volatile u_int32_t sample32_t; + +/* channel registers */ +struct sc_chinfo { + struct snd_dbuf *buffer; + struct pcm_channel *channel; + struct sc_info *parent; + int dir; + unsigned num; /* hw channel number */ + + /* channel information */ + u_int32_t format; + u_int32_t speed; + u_int32_t blk; /* hw block size(dword) */ + + /* format conversion structure */ + u_int8_t *data; + unsigned int size; /* data buffer size(byte) */ + int unit; /* sample size(byte) */ + unsigned int offset; /* samples number offset */ + void (*emldma)(struct sc_chinfo *); + + /* flags */ + int run; +}; + +/* codec interface entrys */ +struct codec_entry { + void *(*create)(device_t dev, void *devinfo, int dir, int num); + void (*destroy)(void *codec); + void (*init)(void *codec); + void (*reinit)(void *codec); + void (*setvolume)(void *codec, int dir, unsigned int left, unsigned int right); + void (*setrate)(void *codec, int which, int rate); +}; + +/* system configuration information */ +struct cfg_info { + char *name; + u_int16_t subvendor, subdevice; + u_int8_t scfg, acl, i2s, spdif; + u_int8_t gpiomask, gpiostate, gpiodir; + u_int8_t free; + struct codec_entry *codec; +}; + +/* device private data */ +struct sc_info { + device_t dev; + void *lock; + + /* Control/Status registor */ + struct resource *cs; + int csid; + bus_space_tag_t cst; + bus_space_handle_t csh; + /* DDMA registor */ + struct resource *ddma; + int ddmaid; + bus_space_tag_t ddmat; + bus_space_handle_t ddmah; + /* Consumer Section DMA Channel Registers */ + struct resource *ds; + int dsid; + bus_space_tag_t dst; + bus_space_handle_t dsh; + /* MultiTrack registor */ + struct resource *mt; + int mtid; + bus_space_tag_t mtt; + bus_space_handle_t mth; + /* DMA tag */ + bus_dma_tag_t dmat; + /* IRQ resource */ + struct resource *irq; + int irqid; + void *ih; + + /* system configuration data */ + struct cfg_info *cfg; + + /* ADC/DAC number and info */ + int adcn, dacn; + void *adc[4], *dac[4]; + + /* mixer control data */ + u_int32_t src; + u_int8_t left[ENVY24_CHAN_NUM]; + u_int8_t right[ENVY24_CHAN_NUM]; + + /* Play/Record DMA fifo */ + sample32_t *pbuf; + sample32_t *rbuf; + u_int32_t psize, rsize; /* DMA buffer size(byte) */ + u_int16_t blk[2]; /* transfer check blocksize(dword) */ + bus_dmamap_t pmap, rmap; + + /* current status */ + u_int32_t speed; + int run[2]; + u_int16_t intr[2]; + struct pcmchan_caps caps[2]; + + /* channel info table */ + unsigned chnum; + struct sc_chinfo chan[11]; +}; + +/* -------------------------------------------------------------------- */ + +/* + * prototypes + */ + +/* DMA emulator */ +static void envy24_p8u(struct sc_chinfo *); +static void envy24_p16sl(struct sc_chinfo *); +static void envy24_p32sl(struct sc_chinfo *); +static void envy24_r16sl(struct sc_chinfo *); +static void envy24_r32sl(struct sc_chinfo *); + +/* channel interface */ +static void *envy24chan_init(kobj_t, void *, struct snd_dbuf *, struct pcm_channel *, int); +static int envy24chan_setformat(kobj_t, void *, u_int32_t); +static int envy24chan_setspeed(kobj_t, void *, u_int32_t); +static int envy24chan_setblocksize(kobj_t, void *, u_int32_t); +static int envy24chan_trigger(kobj_t, void *, int); +static int envy24chan_getptr(kobj_t, void *); +static struct pcmchan_caps *envy24chan_getcaps(kobj_t, void *); + +/* mixer interface */ +static int envy24mixer_init(struct snd_mixer *); +static int envy24mixer_reinit(struct snd_mixer *); +static int envy24mixer_uninit(struct snd_mixer *); +static int envy24mixer_set(struct snd_mixer *, unsigned, unsigned, unsigned); +static u_int32_t envy24mixer_setrecsrc(struct snd_mixer *, u_int32_t); + +/* M-Audio Delta series AK4524 access interface */ +static void *envy24_delta_ak4524_create(device_t, void *, int, int); +static void envy24_delta_ak4524_destroy(void *); +static void envy24_delta_ak4524_init(void *); +static void envy24_delta_ak4524_reinit(void *); +static void envy24_delta_ak4524_setvolume(void *, int, unsigned int, unsigned int); + +/* -------------------------------------------------------------------- */ + +/* + system constant tables +*/ + +/* API -> hardware channel map */ +static unsigned envy24_chanmap[ENVY24_CHAN_NUM] = { + ENVY24_CHAN_PLAY_SPDIF, /* 0 */ + ENVY24_CHAN_PLAY_DAC1, /* 1 */ + ENVY24_CHAN_PLAY_DAC2, /* 2 */ + ENVY24_CHAN_PLAY_DAC3, /* 3 */ + ENVY24_CHAN_PLAY_DAC4, /* 4 */ + ENVY24_CHAN_REC_MIX, /* 5 */ + ENVY24_CHAN_REC_SPDIF, /* 6 */ + ENVY24_CHAN_REC_ADC1, /* 7 */ + ENVY24_CHAN_REC_ADC2, /* 8 */ + ENVY24_CHAN_REC_ADC3, /* 9 */ + ENVY24_CHAN_REC_ADC4, /* 10 */ +}; + +/* mixer -> API channel map. see above */ +static int envy24_mixmap[] = { + -1, /* Master output level. It is depend on codec support */ + -1, /* Treble level of all output channels */ + -1, /* Bass level of all output channels */ + -1, /* Volume of synthesier input */ + 0, /* Output level for the audio device */ + -1, /* Output level for the PC speaker */ + 7, /* line in jack */ + -1, /* microphone jack */ + -1, /* CD audio input */ + -1, /* Recording monitor */ + 1, /* alternative codec */ + -1, /* global recording level */ + -1, /* Input gain */ + -1, /* Output gain */ + 8, /* Input source 1 */ + 9, /* Input source 2 */ + 10, /* Input source 3 */ + 6, /* Digital (input) 1 */ + -1, /* Digital (input) 2 */ + -1, /* Digital (input) 3 */ + -1, /* Phone input */ + -1, /* Phone output */ + -1, /* Video/TV (audio) in */ + -1, /* Radio in */ + -1, /* Monitor volume */ +}; + +/* variable rate audio */ +static u_int32_t envy24_speed[] = { + 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, + 12000, 11025, 9600, 8000, 0 +}; + +/* known boards configuration */ +static struct codec_entry delta_codec = { + envy24_delta_ak4524_create, + envy24_delta_ak4524_destroy, + envy24_delta_ak4524_init, + envy24_delta_ak4524_reinit, + envy24_delta_ak4524_setvolume, + NULL, /* setrate */ +}; + +static struct cfg_info cfg_table[] = { + { + "Envy24 audio(M Audio Delta Dio 2496)", + 0x1412, 0xd631, + 0x10, 0x80, 0xf0, 0x03, + 0xff, 0x00, 0x00, + 0, + &delta_codec, + }, + { + "Envy24 audio(Generic)", + 0, 0, + 0x0f, 0x00, 0x01, 0x03, + 0xff, 0x00, 0x00, + 0, + &delta_codec, /* default codec routines */ + } +}; + +static u_int32_t envy24_recfmt[] = { + AFMT_STEREO | AFMT_S16_LE, + AFMT_STEREO | AFMT_S32_LE, + 0 +}; +static struct pcmchan_caps envy24_reccaps = {8000, 96000, envy24_recfmt, 0}; + +static u_int32_t envy24_playfmt[] = { + AFMT_STEREO | AFMT_U8, + AFMT_STEREO | AFMT_S16_LE, + AFMT_STEREO | AFMT_S32_LE, + 0 +}; + +static struct pcmchan_caps envy24_playcaps = {8000, 96000, envy24_playfmt, 0}; + +struct envy24_emldma { + u_int32_t format; + void (*emldma)(struct sc_chinfo *); + int unit; +}; + +static struct envy24_emldma envy24_pemltab[] = { + {AFMT_STEREO | AFMT_U8, envy24_p8u, 2}, + {AFMT_STEREO | AFMT_S16_LE, envy24_p16sl, 4}, + {AFMT_STEREO | AFMT_S32_LE, envy24_p32sl, 8}, + {0, NULL, 0} +}; + +static struct envy24_emldma envy24_remltab[] = { + {AFMT_STEREO | AFMT_S16_LE, envy24_r16sl, 4}, + {AFMT_STEREO | AFMT_S32_LE, envy24_r32sl, 8}, + {0, NULL, 0} +}; + +/* -------------------------------------------------------------------- */ + +/* common routines */ +static u_int32_t +envy24_rdcs(struct sc_info *sc, int regno, int size) +{ + switch (size) { + case 1: + return bus_space_read_1(sc->cst, sc->csh, regno); + case 2: + return bus_space_read_2(sc->cst, sc->csh, regno); + case 4: + return bus_space_read_4(sc->cst, sc->csh, regno); + default: + return 0xffffffff; + } +} + +static void +envy24_wrcs(struct sc_info *sc, int regno, u_int32_t data, int size) +{ + switch (size) { + case 1: + bus_space_write_1(sc->cst, sc->csh, regno, data); + break; + case 2: + bus_space_write_2(sc->cst, sc->csh, regno, data); + break; + case 4: + bus_space_write_4(sc->cst, sc->csh, regno, data); + break; + } +} + +static u_int32_t +envy24_rdmt(struct sc_info *sc, int regno, int size) +{ + switch (size) { + case 1: + return bus_space_read_1(sc->mtt, sc->mth, regno); + case 2: + return bus_space_read_2(sc->mtt, sc->mth, regno); + case 4: + return bus_space_read_4(sc->mtt, sc->mth, regno); + default: + return 0xffffffff; + } +} + +static void +envy24_wrmt(struct sc_info *sc, int regno, u_int32_t data, int size) +{ + switch (size) { + case 1: + bus_space_write_1(sc->mtt, sc->mth, regno, data); + break; + case 2: + bus_space_write_2(sc->mtt, sc->mth, regno, data); + break; + case 4: + bus_space_write_4(sc->mtt, sc->mth, regno, data); + break; + } +} + +static u_int32_t +envy24_rdci(struct sc_info *sc, int regno) +{ + envy24_wrcs(sc, ENVY24_CCS_INDEX, regno, 1); + return envy24_rdcs(sc, ENVY24_CCS_DATA, 1); +} + +static void +envy24_wrci(struct sc_info *sc, int regno, u_int32_t data) +{ + envy24_wrcs(sc, ENVY24_CCS_INDEX, regno, 1); + envy24_wrcs(sc, ENVY24_CCS_DATA, data, 1); +} + +/* -------------------------------------------------------------------- */ + +/* I2C port/E2PROM access routines */ + +static int +envy24_rdi2c(struct sc_info *sc, u_int32_t dev, u_int32_t addr) +{ + u_int32_t data; + int i; + +#if(0) + device_printf(sc->dev, "envy24_rdi2c(sc, 0x%02x, 0x%02x)\n", dev, addr); +#endif + for (i = 0; i < ENVY24_TIMEOUT; i++) { + data = envy24_rdcs(sc, ENVY24_CCS_I2CSTAT, 1); + if ((data & ENVY24_CCS_I2CSTAT_BSY) == 0) + break; + DELAY(32); /* 31.25kHz */ + } + if (i == ENVY24_TIMEOUT) { + return -1; + } + envy24_wrcs(sc, ENVY24_CCS_I2CADDR, addr, 1); + envy24_wrcs(sc, ENVY24_CCS_I2CDEV, + (dev & ENVY24_CCS_I2CDEV_ADDR) | ENVY24_CCS_I2CDEV_RD, 1); + for (i = 0; i < ENVY24_TIMEOUT; i++) { + data = envy24_rdcs(sc, ENVY24_CCS_I2CSTAT, 1); + if ((data & ENVY24_CCS_I2CSTAT_BSY) == 0) + break; + DELAY(32); /* 31.25kHz */ + } + if (i == ENVY24_TIMEOUT) { + return -1; + } + data = envy24_rdcs(sc, ENVY24_CCS_I2CDATA, 1); + +#if(0) + device_printf(sc->dev, "envy24_rdi2c(): return 0x%x\n", data); +#endif + return (int)data; +} + +static int +envy24_wri2c(struct sc_info *sc, u_int32_t dev, u_int32_t addr, u_int32_t data) +{ + u_int32_t tmp; + int i; + +#if(0) + device_printf(sc->dev, "envy24_rdi2c(sc, 0x%02x, 0x%02x)\n", dev, addr); +#endif + for (i = 0; i < ENVY24_TIMEOUT; i++) { + tmp = envy24_rdcs(sc, ENVY24_CCS_I2CSTAT, 1); + if ((tmp & ENVY24_CCS_I2CSTAT_BSY) == 0) + break; + DELAY(32); /* 31.25kHz */ + } + if (i == ENVY24_TIMEOUT) { + return -1; + } + envy24_wrcs(sc, ENVY24_CCS_I2CADDR, addr, 1); + envy24_wrcs(sc, ENVY24_CCS_I2CDATA, data, 1); + envy24_wrcs(sc, ENVY24_CCS_I2CDEV, + (dev & ENVY24_CCS_I2CDEV_ADDR) | ENVY24_CCS_I2CDEV_WR, 1); + for (i = 0; i < ENVY24_TIMEOUT; i++) { + data = envy24_rdcs(sc, ENVY24_CCS_I2CSTAT, 1); + if ((data & ENVY24_CCS_I2CSTAT_BSY) == 0) + break; + DELAY(32); /* 31.25kHz */ + } + if (i == ENVY24_TIMEOUT) { + return -1; + } + + return 0; +} + +static int +envy24_rdrom(struct sc_info *sc, u_int32_t addr) +{ + u_int32_t data; + +#if(0) + device_printf(sc->dev, "envy24_rdrom(sc, 0x%02x)\n", addr); +#endif + data = envy24_rdcs(sc, ENVY24_CCS_I2CSTAT, 1); + if ((data & ENVY24_CCS_I2CSTAT_ROM) == 0) { +#if(0) + device_printf(sc->dev, "envy24_rdrom(): E2PROM not presented\n"); +#endif + return -1; + } + + return envy24_rdi2c(sc, ENVY24_CCS_I2CDEV_ROM, addr); +} + +static struct cfg_info * +envy24_rom2cfg(struct sc_info *sc) +{ + struct cfg_info *buff; + int size; + int i; + +#if(0) + device_printf(sc->dev, "envy24_rom2cfg(sc)\n"); +#endif + size = envy24_rdrom(sc, ENVY24_E2PROM_SIZE); + if (size < ENVY24_E2PROM_GPIODIR + 1) { +#if(0) + device_printf(sc->dev, "envy24_rom2cfg(): ENVY24_E2PROM_SIZE-->%d\n", size); +#endif + return NULL; + } + buff = malloc(sizeof(*buff), M_ENVY24, M_NOWAIT); + if (buff == NULL) { +#if(0) + device_printf(sc->dev, "envy24_rom2cfg(): malloc()\n"); +#endif + return NULL; + } + buff->free = 1; + + buff->subvendor = envy24_rdrom(sc, ENVY24_E2PROM_SUBVENDOR) << 8; + buff->subvendor += envy24_rdrom(sc, ENVY24_E2PROM_SUBVENDOR + 1); + buff->subdevice = envy24_rdrom(sc, ENVY24_E2PROM_SUBDEVICE) << 8; + buff->subdevice += envy24_rdrom(sc, ENVY24_E2PROM_SUBDEVICE + 1); + buff->scfg = envy24_rdrom(sc, ENVY24_E2PROM_SCFG); + buff->acl = envy24_rdrom(sc, ENVY24_E2PROM_ACL); + buff->i2s = envy24_rdrom(sc, ENVY24_E2PROM_I2S); + buff->spdif = envy24_rdrom(sc, ENVY24_E2PROM_SPDIF); + buff->gpiomask = envy24_rdrom(sc, ENVY24_E2PROM_GPIOMASK); + buff->gpiostate = envy24_rdrom(sc, ENVY24_E2PROM_GPIOSTATE); + buff->gpiodir = envy24_rdrom(sc, ENVY24_E2PROM_GPIODIR); + + for (i = 0; cfg_table[i].subvendor != 0 || cfg_table[i].subdevice != 0; i++) + if (cfg_table[i].subvendor == buff->subvendor && + cfg_table[i].subdevice == buff->subdevice) + break; + buff->name = cfg_table[i].name; + buff->codec = cfg_table[i].codec; + + return buff; +} + +static void +envy24_cfgfree(struct cfg_info *cfg) { + if (cfg == NULL) + return; + if (cfg->free) + free(cfg, M_ENVY24); + return; +} + +/* -------------------------------------------------------------------- */ + +/* AC'97 codec access routines */ + +static int +envy24_coldcd(struct sc_info *sc) +{ + u_int32_t data; + int i; + +#if(0) + device_printf(sc->dev, "envy24_coldcd()\n"); +#endif + envy24_wrmt(sc, ENVY24_MT_AC97CMD, ENVY24_MT_AC97CMD_CLD, 1); + DELAY(10); + envy24_wrmt(sc, ENVY24_MT_AC97CMD, 0, 1); + DELAY(1000); + for (i = 0; i < ENVY24_TIMEOUT; i++) { + data = envy24_rdmt(sc, ENVY24_MT_AC97CMD, 1); + if (data & ENVY24_MT_AC97CMD_RDY) { + return 0; + } + } + + return -1; +} + +static int +envy24_slavecd(struct sc_info *sc) +{ + u_int32_t data; + int i; + +#if(0) + device_printf(sc->dev, "envy24_slavecd()\n"); +#endif + envy24_wrmt(sc, ENVY24_MT_AC97CMD, + ENVY24_MT_AC97CMD_CLD | ENVY24_MT_AC97CMD_WRM, 1); + DELAY(10); + envy24_wrmt(sc, ENVY24_MT_AC97CMD, 0, 1); + DELAY(1000); + for (i = 0; i < ENVY24_TIMEOUT; i++) { + data = envy24_rdmt(sc, ENVY24_MT_AC97CMD, 1); + if (data & ENVY24_MT_AC97CMD_RDY) { + return 0; + } + } + + return -1; +} + +static int +envy24_rdcd(kobj_t obj, void *devinfo, int regno) +{ + struct sc_info *sc = (struct sc_info *)devinfo; + u_int32_t data; + int i; + +#if(0) + device_printf(sc->dev, "envy24_rdcd(obj, sc, 0x%02x)\n", regno); +#endif + envy24_wrmt(sc, ENVY24_MT_AC97IDX, (u_int32_t)regno, 1); + envy24_wrmt(sc, ENVY24_MT_AC97CMD, ENVY24_MT_AC97CMD_RD, 1); + for (i = 0; i < ENVY24_TIMEOUT; i++) { + data = envy24_rdmt(sc, ENVY24_MT_AC97CMD, 1); + if ((data & ENVY24_MT_AC97CMD_RD) == 0) + break; + } + data = envy24_rdmt(sc, ENVY24_MT_AC97DLO, 2); + +#if(0) + device_printf(sc->dev, "envy24_rdcd(): return 0x%x\n", data); +#endif + return (int)data; +} + +static int +envy24_wrcd(kobj_t obj, void *devinfo, int regno, u_int16_t data) +{ + struct sc_info *sc = (struct sc_info *)devinfo; + u_int32_t cmd; + int i; + +#if(0) + device_printf(sc->dev, "envy24_wrcd(obj, sc, 0x%02x, 0x%04x)\n", regno, data); +#endif + envy24_wrmt(sc, ENVY24_MT_AC97IDX, (u_int32_t)regno, 1); + envy24_wrmt(sc, ENVY24_MT_AC97DLO, (u_int32_t)data, 2); + envy24_wrmt(sc, ENVY24_MT_AC97CMD, ENVY24_MT_AC97CMD_WR, 1); + for (i = 0; i < ENVY24_TIMEOUT; i++) { + cmd = envy24_rdmt(sc, ENVY24_MT_AC97CMD, 1); + if ((cmd & ENVY24_MT_AC97CMD_WR) == 0) + break; + } + + return 0; +} + +static kobj_method_t envy24_ac97_methods[] = { + KOBJMETHOD(ac97_read, envy24_rdcd), + KOBJMETHOD(ac97_write, envy24_wrcd), + {0, 0} +}; +AC97_DECLARE(envy24_ac97); + +/* -------------------------------------------------------------------- */ + +/* GPIO access routines */ + +static u_int32_t +envy24_gpiord(struct sc_info *sc) +{ + return envy24_rdci(sc, ENVY24_CCI_GPIODAT); +} + +static void +envy24_gpiowr(struct sc_info *sc, u_int32_t data) +{ +#if(0) + device_printf(sc->dev, "envy24_gpiowr(sc, 0x%02x)\n", data & 0xff); + return; +#endif + envy24_wrci(sc, ENVY24_CCI_GPIODAT, data); + return; +} + +static u_int32_t +envy24_gpiogetmask(struct sc_info *sc) +{ + return envy24_rdci(sc, ENVY24_CCI_GPIOMASK); +} + +static void +envy24_gpiosetmask(struct sc_info *sc, u_int32_t mask) +{ + envy24_wrci(sc, ENVY24_CCI_GPIOMASK, mask); + return; +} + +static u_int32_t +envy24_gpiogetdir(struct sc_info *sc) +{ + return envy24_rdci(sc, ENVY24_CCI_GPIOCTL); +} + +static void +envy24_gpiosetdir(struct sc_info *sc, u_int32_t dir) +{ + envy24_wrci(sc, ENVY24_CCI_GPIOCTL, dir); + return; +} + +/* -------------------------------------------------------------------- */ + +/* M-Audio Delta series AK4524 access interface routine */ + +struct envy24_delta_ak4524_codec { + struct ak452x_info *info; + struct sc_info *parent; + int dir; + int num; + int cs, cclk, cdti; +}; + +static void +envy24_delta_ak4524_ctl(void *codec, unsigned int cs, unsigned int cclk, unsigned int cdti) +{ + u_int32_t data = 0; + struct envy24_delta_ak4524_codec *ptr = codec; + +#if(0) + device_printf(ptr->parent->dev, "--> %d, %d, %d\n", cs, cclk, cdti); +#endif + data = envy24_gpiord(ptr->parent); + data &= ~(ptr->cs | ptr->cclk | ptr->cdti); + if (cs) data += ptr->cs; + if (cclk) data += ptr->cclk; + if (cdti) data += ptr->cdti; + envy24_gpiowr(ptr->parent, data); + return; +} + +static void * +envy24_delta_ak4524_create(device_t dev, void *info, int dir, int num) +{ + struct sc_info *sc = info; + struct envy24_delta_ak4524_codec *buff = NULL; + +#if(0) + device_printf(sc->dev, "envy24_delta_ak4524_create(dev, sc, %d, %d)\n", dir, num); +#endif + + buff = malloc(sizeof(*buff), M_ENVY24, M_NOWAIT); + if (buff == NULL) + return NULL; + + if (dir == PCMDIR_PLAY && sc->adc[num] != NULL) + buff->info = ((struct envy24_delta_ak4524_codec *)sc->adc[num])->info; + else if (dir == PCMDIR_REC && sc->dac[num] != NULL) + buff->info = ((struct envy24_delta_ak4524_codec *)sc->dac[num])->info; + else + buff->info = ak452x_create(dev, buff, num, envy24_delta_ak4524_ctl); + if (buff->info == NULL) { + free(buff, M_ENVY24); + return NULL; + } + + buff->parent = sc; + buff->dir = dir; + buff->num = num; + + return (void *)buff; +} + +static void +envy24_delta_ak4524_destroy(void *codec) +{ + struct envy24_delta_ak4524_codec *ptr = codec; + if (ptr == NULL) + return; +#if(0) + device_printf(ptr->parent->dev, "envy24_delta_ak4524_destroy()\n"); +#endif + + if (ptr->dir == PCMDIR_PLAY) { + if (ptr->parent->adc[ptr->num] == NULL) + ak452x_destroy(ptr->info); + } + else { + if (ptr->parent->dac[ptr->num] == NULL) + ak452x_destroy(ptr->info); + } + + free(codec, M_ENVY24); +} + +static void +envy24_delta_ak4524_init(void *codec) +{ + u_int32_t gpiomask, gpiodir; + struct envy24_delta_ak4524_codec *ptr = codec; + if (ptr == NULL) + return; +#if(0) + device_printf(ptr->parent->dev, "envy24_delta_ak4524_init()\n"); +#endif + + /* + gpiomask = envy24_gpiogetmask(ptr->parent); + gpiomask &= ~(ENVY24_GPIO_AK4524_CDTI | ENVY24_GPIO_AK4524_CCLK | ENVY24_GPIO_AK4524_CS0 | ENVY24_GPIO_AK4524_CS1); + envy24_gpiosetmask(ptr->parent, gpiomask); + gpiodir = envy24_gpiogetdir(ptr->parent); + gpiodir |= ENVY24_GPIO_AK4524_CDTI | ENVY24_GPIO_AK4524_CCLK | ENVY24_GPIO_AK4524_CS0 | ENVY24_GPIO_AK4524_CS1; + envy24_gpiosetdir(ptr->parent, gpiodir); + */ + envy24_gpiosetmask(ptr->parent, ENVY24_GPIO_CS8414_STATUS); + envy24_gpiosetdir(ptr->parent, ~ENVY24_GPIO_CS8414_STATUS); + if (ptr->num == 0) + ptr->cs = ENVY24_GPIO_AK4524_CS0; + else + ptr->cs = ENVY24_GPIO_AK4524_CS1; + ptr->cclk = ENVY24_GPIO_AK4524_CCLK; + ptr->cdti = ENVY24_GPIO_AK4524_CDTI; + ak452x_settype(ptr->info, AK452X_TYPE_4524); + ak452x_setcif(ptr->info, ENVY24_DELTA_AK4524_CIF); + ak452x_setformat(ptr->info, + AK452X_FORMAT_I2S | AK452X_FORMAT_256FSN | AK452X_FORMAT_1X); + ak452x_setdvc(ptr->info, 0); + ak452x_init(ptr->info); +} + +static void +envy24_delta_ak4524_reinit(void *codec) +{ + struct envy24_delta_ak4524_codec *ptr = codec; + if (ptr == NULL) + return; +#if(0) + device_printf(ptr->parent->dev, "envy24_delta_ak4524_reinit()\n"); +#endif + + ak452x_reinit(ptr->info); +} + +static void +envy24_delta_ak4524_setvolume(void *codec, int dir, unsigned int left, unsigned int right) +{ + struct envy24_delta_ak4524_codec *ptr = codec; + if (ptr == NULL) + return; +#if(0) + device_printf(ptr->parent->dev, "envy24_delta_ak4524_set()\n"); +#endif + + ak452x_set(ptr->info, dir, left, right); +} + +/* + There is no need for AK452[48] codec to set sample rate + static void + envy24_delta_ak4524_setrate(struct envy24_delta_ak4524_codec *codec, int which, int rate) + { + } +*/ + +/* -------------------------------------------------------------------- */ + +/* hardware access routeines */ + +static struct { + u_int32_t speed; + u_int32_t code; +} envy24_speedtab[] = { + {48000, ENVY24_MT_RATE_48000}, + {24000, ENVY24_MT_RATE_24000}, + {12000, ENVY24_MT_RATE_12000}, + {9600, ENVY24_MT_RATE_9600}, + {32000, ENVY24_MT_RATE_32000}, + {16000, ENVY24_MT_RATE_16000}, + {8000, ENVY24_MT_RATE_8000}, + {96000, ENVY24_MT_RATE_96000}, + {64000, ENVY24_MT_RATE_64000}, + {44100, ENVY24_MT_RATE_44100}, + {22050, ENVY24_MT_RATE_22050}, + {11025, ENVY24_MT_RATE_11025}, + {88200, ENVY24_MT_RATE_88200}, + {0, 0x10} +}; + +static int +envy24_setspeed(struct sc_info *sc, u_int32_t speed) { + u_int32_t code; + int i = 0; + +#if(0) + device_printf(sc->dev, "envy24_setspeed(sc, %d)\n", speed); +#endif + if (speed == 0) { + code = ENVY24_MT_RATE_SPDIF; /* external master clock */ + envy24_slavecd(sc); + } + else { + for (i = 0; envy24_speedtab[i].speed != 0; i++) { + if (envy24_speedtab[i].speed == speed) + break; + } + code = envy24_speedtab[i].code; + } +#if(0) + device_printf(sc->dev, "envy24_setspeed(): speed %d/code 0x%04x\n", envy24_speedtab[i].speed, code); +#endif + if (code < 0x10) { + envy24_wrmt(sc, ENVY24_MT_RATE, code, 1); + code = envy24_rdmt(sc, ENVY24_MT_RATE, 1); + code &= ENVY24_MT_RATE_MASK; + for (i = 0; envy24_speedtab[i].code < 0x10; i++) { + if (envy24_speedtab[i].code == code) + break; + } + speed = envy24_speedtab[i].speed; + } + else + speed = 0; + +#if(0) + device_printf(sc->dev, "envy24_setspeed(): return %d\n", speed); +#endif + return speed; +} + +static void +envy24_setvolume(struct sc_info *sc, unsigned ch) +{ +#if(0) + device_printf(sc->dev, "envy24_setvolume(sc, %d)\n", ch); +#endif + envy24_wrmt(sc, ENVY24_MT_VOLIDX, ch * 2, 1); + envy24_wrmt(sc, ENVY24_MT_VOLUME, 0x7f00 | sc->left[ch], 2); + envy24_wrmt(sc, ENVY24_MT_VOLIDX, ch * 2 + 1, 1); + envy24_wrmt(sc, ENVY24_MT_VOLUME, (sc->right[ch] << 8) | 0x7f, 2); +} + +static void +envy24_mutevolume(struct sc_info *sc, unsigned ch) +{ + u_int32_t vol; + +#if(0) + device_printf(sc->dev, "envy24_mutevolume(sc, %d)\n", ch); +#endif + vol = ENVY24_VOL_MUTE << 8 | ENVY24_VOL_MUTE; + envy24_wrmt(sc, ENVY24_MT_VOLIDX, ch * 2, 1); + envy24_wrmt(sc, ENVY24_MT_VOLUME, vol, 2); + envy24_wrmt(sc, ENVY24_MT_VOLIDX, ch * 2 + 1, 1); + envy24_wrmt(sc, ENVY24_MT_VOLUME, vol, 2); +} + +static u_int32_t +envy24_gethwptr(struct sc_info *sc, int dir) +{ + int unit, regno; + u_int32_t ptr, rtn; + +#if(0) + device_printf(sc->dev, "envy24_gethwptr(sc, %d)\n", dir); +#endif + if (dir == PCMDIR_PLAY) { + rtn = sc->psize / 4; + unit = ENVY24_PLAY_BUFUNIT / 4; + regno = ENVY24_MT_PCNT; + } + else { + rtn = sc->rsize / 4; + unit = ENVY24_REC_BUFUNIT / 4; + regno = ENVY24_MT_RCNT; + } + + ptr = envy24_rdmt(sc, regno, 2); + rtn -= (ptr + 1); + rtn /= unit; + +#if(0) + device_printf(sc->dev, "envy24_gethwptr(): return %d\n", rtn); +#endif + return rtn; +} + +static void +envy24_updintr(struct sc_info *sc, int dir) +{ + int regptr, regintr; + u_int32_t mask, intr; + u_int32_t ptr, size, cnt; + u_int16_t blk; + +#if(0) + device_printf(sc->dev, "envy24_updintr(sc, %d)\n", dir); +#endif + if (dir == PCMDIR_PLAY) { + blk = sc->blk[0]; + size = sc->psize / 4; + regptr = ENVY24_MT_PCNT; + regintr = ENVY24_MT_PTERM; + mask = ~ENVY24_MT_INT_PMASK; + } + else { + blk = sc->blk[1]; + size = sc->rsize / 4; + regptr = ENVY24_MT_RCNT; + regintr = ENVY24_MT_RTERM; + mask = ~ENVY24_MT_INT_RMASK; + } + + ptr = size - envy24_rdmt(sc, regptr, 2) - 1; + /* + cnt = blk - ptr % blk - 1; + if (cnt == 0) + cnt = blk - 1; + */ + cnt = blk - 1; +#if(0) + device_printf(sc->dev, "envy24_updintr():ptr = %d, blk = %d, cnt = %d\n", ptr, blk, cnt); +#endif + envy24_wrmt(sc, regintr, cnt, 2); + intr = envy24_rdmt(sc, ENVY24_MT_INT, 1); +#if(0) + device_printf(sc->dev, "envy24_updintr():intr = 0x%02x, mask = 0x%02x\n", intr, mask); +#endif + envy24_wrmt(sc, ENVY24_MT_INT, intr & mask, 1); +#if(0) + device_printf(sc->dev, "envy24_updintr():INT-->0x%02x\n", + envy24_rdmt(sc, ENVY24_MT_INT, 1)); +#endif + + return; +} + +static void +envy24_maskintr(struct sc_info *sc, int dir) +{ + u_int32_t mask, intr; + +#if(0) + device_printf(sc->dev, "envy24_maskintr(sc, %d)\n", dir); +#endif + if (dir == PCMDIR_PLAY) + mask = ENVY24_MT_INT_PMASK; + else + mask = ENVY24_MT_INT_RMASK; + intr = envy24_rdmt(sc, ENVY24_MT_INT, 1); + envy24_wrmt(sc, ENVY24_MT_INT, intr | mask, 1); + + return; +} + +static int +envy24_checkintr(struct sc_info *sc, int dir) +{ + u_int32_t mask, stat, intr, rtn; + +#if(0) + device_printf(sc->dev, "envy24_checkintr(sc, %d)\n", dir); +#endif + intr = envy24_rdmt(sc, ENVY24_MT_INT, 1); + if (dir == PCMDIR_PLAY) { + if ((rtn = intr & ENVY24_MT_INT_PSTAT) != 0) { + mask = ~ENVY24_MT_INT_RSTAT; + stat = ENVY24_MT_INT_PSTAT | ENVY24_MT_INT_PMASK; + envy24_wrmt(sc, ENVY24_MT_INT, (intr & mask) | stat, 1); + } + } + else { + if ((rtn = intr & ENVY24_MT_INT_RSTAT) != 0) { + mask = ~ENVY24_MT_INT_PSTAT; + stat = ENVY24_MT_INT_RSTAT | ENVY24_MT_INT_RMASK; + envy24_wrmt(sc, ENVY24_MT_INT, (intr & mask) | stat, 1); + } + } + + return rtn; +} + +static void +envy24_start(struct sc_info *sc, int dir) +{ + u_int32_t stat, sw; + +#if(0) + device_printf(sc->dev, "envy24_start(sc, %d)\n", dir); +#endif + if (dir == PCMDIR_PLAY) + sw = ENVY24_MT_PCTL_PSTART; + else + sw = ENVY24_MT_PCTL_RSTART; + + stat = envy24_rdmt(sc, ENVY24_MT_PCTL, 1); + envy24_wrmt(sc, ENVY24_MT_PCTL, stat | sw, 1); +#if(0) + DELAY(100); + device_printf(sc->dev, "PADDR:0x%08x\n", envy24_rdmt(sc, ENVY24_MT_PADDR, 4)); + device_printf(sc->dev, "PCNT:%ld\n", envy24_rdmt(sc, ENVY24_MT_PCNT, 2)); +#endif + + return; +} + +static void +envy24_stop(struct sc_info *sc, int dir) +{ + u_int32_t stat, sw; + +#if(0) + device_printf(sc->dev, "envy24_stop(sc, %d)\n", dir); +#endif + if (dir == PCMDIR_PLAY) + sw = ~ENVY24_MT_PCTL_PSTART; + else + sw = ~ENVY24_MT_PCTL_RSTART; + + stat = envy24_rdmt(sc, ENVY24_MT_PCTL, 1); + envy24_wrmt(sc, ENVY24_MT_PCTL, stat & sw, 1); + + return; +} + +static int +envy24_route(struct sc_info *sc, int dac, int class, int adc, int rev) +{ + u_int32_t reg, mask; + u_int32_t left, right; + +#if(0) + device_printf(sc->dev, "envy24_route(sc, %d, %d, %d, %d)\n", + dac, class, adc, rev); +#endif + /* parameter pattern check */ + if (dac < 0 || ENVY24_ROUTE_DAC_SPDIF < dac) + return -1; + if (class == ENVY24_ROUTE_CLASS_MIX && + (dac != ENVY24_ROUTE_DAC_1 && dac != ENVY24_ROUTE_DAC_SPDIF)) + return -1; + if (rev) { + left = ENVY24_ROUTE_RIGHT; + right = ENVY24_ROUTE_LEFT; + } + else { + left = ENVY24_ROUTE_LEFT; + right = ENVY24_ROUTE_RIGHT; + } + + if (dac == ENVY24_ROUTE_DAC_SPDIF) { + reg = class | class << 2 | + ((adc << 1 | left) | left << 3) << 8 | + ((adc << 1 | right) | right << 3) << 12; +#if(0) + device_printf(sc->dev, "envy24_route(): MT_SPDOUT-->0x%04x\n", reg); +#endif + envy24_wrmt(sc, ENVY24_MT_SPDOUT, reg, 2); + } + else { + mask = ~(0x0303 << dac * 2); + reg = envy24_rdmt(sc, ENVY24_MT_PSDOUT, 2); + reg = (reg & mask) | ((class | class << 8) << dac * 2); +#if(0) + device_printf(sc->dev, "envy24_route(): MT_PSDOUT-->0x%04x\n", reg); +#endif + envy24_wrmt(sc, ENVY24_MT_PSDOUT, reg, 2); + mask = ~(0xff << dac * 8); + reg = envy24_rdmt(sc, ENVY24_MT_RECORD, 4); + reg = (reg & mask) | + (((adc << 1 | left) | left << 3) | + ((adc << 1 | right) | right << 3) << 4) << dac * 8; +#if(0) + device_printf(sc->dev, "envy24_route(): MT_RECORD-->0x%08x\n", reg); +#endif + envy24_wrmt(sc, ENVY24_MT_RECORD, reg, 4); + } + + return 0; +} + +/* -------------------------------------------------------------------- */ + +/* buffer copy routines */ +static void +envy24_p32sl(struct sc_chinfo *ch) +{ + int length; + sample32_t *dmabuf; + u_int32_t *data; + int src, dst, ssize, dsize, slot; + int i; + + length = sndbuf_getready(ch->buffer) / 8; + dmabuf = ch->parent->pbuf; + data = (u_int32_t *)ch->data; + src = sndbuf_getreadyptr(ch->buffer) / 4; + dst = src / 2 + ch->offset; + ssize = ch->size / 4; + dsize = ch->size / 8; + slot = ch->num * 2; + + for (i = 0; i < length; i++) { + dmabuf[dst * ENVY24_PLAY_CHNUM + slot] = data[src]; + dmabuf[dst * ENVY24_PLAY_CHNUM + slot + 1] = data[src + 1]; + dst++; + dst %= dsize; + src += 2; + src %= ssize; + } + + return; +} + +static void +envy24_p16sl(struct sc_chinfo *ch) +{ + int length; + sample32_t *dmabuf; + u_int16_t *data; + int src, dst, ssize, dsize, slot; + int i; + +#if(0) + device_printf(ch->parent->dev, "envy24_p16sl()\n"); +#endif + length = sndbuf_getready(ch->buffer) / 4; + dmabuf = ch->parent->pbuf; + data = (u_int16_t *)ch->data; + src = sndbuf_getreadyptr(ch->buffer) / 2; + dst = src / 2 + ch->offset; + ssize = ch->size / 2; + dsize = ch->size / 4; + slot = ch->num * 2; +#if(0) + device_printf(ch->parent->dev, "envy24_p16sl():%lu-->%lu(%lu)\n", src, dst, length); +#endif + + for (i = 0; i < length; i++) { + dmabuf[dst * ENVY24_PLAY_CHNUM + slot] = (u_int32_t)data[src] << 16; + dmabuf[dst * ENVY24_PLAY_CHNUM + slot + 1] = (u_int32_t)data[src + 1] << 16; +#if(0) + if (i < 16) { + printf("%08x", dmabuf[dst * ENVY24_PLAY_CHNUM + slot]); + printf("%08x", dmabuf[dst * ENVY24_PLAY_CHNUM + slot + 1]); + } +#endif + dst++; + dst %= dsize; + src += 2; + src %= ssize; + } +#if(0) + printf("\n"); +#endif + + return; +} + +static void +envy24_p8u(struct sc_chinfo *ch) +{ + int length; + sample32_t *dmabuf; + u_int8_t *data; + int src, dst, ssize, dsize, slot; + int i; + + length = sndbuf_getready(ch->buffer) / 2; + dmabuf = ch->parent->pbuf; + data = (u_int8_t *)ch->data; + src = sndbuf_getreadyptr(ch->buffer); + dst = src / 2 + ch->offset; + ssize = ch->size; + dsize = ch->size / 4; + slot = ch->num * 2; + + for (i = 0; i < length; i++) { + dmabuf[dst * ENVY24_PLAY_CHNUM + slot] = ((u_int32_t)data[src] ^ 0x80) << 24; + dmabuf[dst * ENVY24_PLAY_CHNUM + slot + 1] = ((u_int32_t)data[src + 1] ^ 0x80) << 24; + dst++; + dst %= dsize; + src += 2; + src %= ssize; + } + + return; +} + +static void +envy24_r32sl(struct sc_chinfo *ch) +{ + int length; + sample32_t *dmabuf; + u_int32_t *data; + int src, dst, ssize, dsize, slot; + int i; + + length = sndbuf_getfree(ch->buffer) / 8; + dmabuf = ch->parent->rbuf; + data = (u_int32_t *)ch->data; + dst = sndbuf_getfreeptr(ch->buffer) / 4; + src = dst / 2 + ch->offset; + dsize = ch->size / 4; + ssize = ch->size / 8; + slot = (ch->num - ENVY24_CHAN_REC_ADC1) * 2; + + for (i = 0; i < length; i++) { + data[dst] = dmabuf[src * ENVY24_REC_CHNUM + slot]; + data[dst + 1] = dmabuf[src * ENVY24_REC_CHNUM + slot + 1]; + dst += 2; + dst %= dsize; + src++; + src %= ssize; + } + + return; +} + +static void +envy24_r16sl(struct sc_chinfo *ch) +{ + int length; + sample32_t *dmabuf; + u_int32_t *data; + int src, dst, ssize, dsize, slot; + int i; + + length = sndbuf_getfree(ch->buffer) / 4; + dmabuf = ch->parent->rbuf; + data = (u_int16_t *)ch->data; + dst = sndbuf_getfreeptr(ch->buffer) / 2; + src = dst / 2 + ch->offset; + dsize = ch->size / 2; + ssize = ch->size / 8; + slot = (ch->num - ENVY24_CHAN_REC_ADC1) * 2; + + for (i = 0; i < length; i++) { + data[dst] = dmabuf[src * ENVY24_REC_CHNUM + slot]; + data[dst + 1] = dmabuf[src * ENVY24_REC_CHNUM + slot + 1]; + dst += 2; + dst %= dsize; + src++; + src %= ssize; + } + + return; +} + +/* -------------------------------------------------------------------- */ + +/* channel interface */ +static void * +envy24chan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir) +{ + struct sc_info *sc = (struct sc_info *)devinfo; + struct sc_chinfo *ch; + unsigned num; + +#if(0) + device_printf(sc->dev, "envy24chan_init(obj, devinfo, b, c, %d)\n", dir); +#endif + snd_mtxlock(sc->lock); + if ((sc->chnum > ENVY24_CHAN_PLAY_SPDIF && dir != PCMDIR_REC) || + (sc->chnum < ENVY24_CHAN_REC_ADC1 && dir != PCMDIR_PLAY)) { + snd_mtxunlock(sc->lock); + return NULL; + } + num = sc->chnum; + + ch = &sc->chan[num]; + ch->size = 8 * ENVY24_SAMPLE_NUM; + ch->data = malloc(ch->size, M_ENVY24, M_NOWAIT); + if (ch->data == NULL) { + ch->size = 0; + ch = NULL; + } + else { + ch->buffer = b; + ch->channel = c; + ch->parent = sc; + ch->dir = dir; + /* set channel map */ + ch->num = envy24_chanmap[num]; + sndbuf_setup(ch->buffer, ch->data, ch->size); + /* these 2 values are dummy */ + ch->unit = 4; + ch->blk = 10240; + } + snd_mtxunlock(sc->lock); + + return ch; +} + +static int +envy24chan_free(kobj_t obj, void *data) +{ + struct sc_chinfo *ch = data; + struct sc_info *sc = ch->parent; + +#if(0) + device_printf(sc->dev, "envy24chan_free()\n"); +#endif + snd_mtxlock(sc->lock); + if (ch->data != NULL) { + free(ch->data, M_ENVY24); + ch->data = NULL; + } + snd_mtxunlock(sc->lock); + + return 0; +} + +static int +envy24chan_setformat(kobj_t obj, void *data, u_int32_t format) +{ + struct sc_chinfo *ch = data; + struct sc_info *sc = ch->parent; + struct envy24_emldma *emltab; + unsigned int bcnt, bsize; + int i; + +#if(0) + device_printf(sc->dev, "envy24chan_setformat(obj, data, 0x%08x)\n", format); +#endif + snd_mtxlock(sc->lock); + /* check and get format related information */ + if (ch->dir == PCMDIR_PLAY) + emltab = envy24_pemltab; + else + emltab = envy24_remltab; + if (emltab == NULL) { + snd_mtxunlock(sc->lock); + return -1; + } + for (i = 0; emltab[i].format != 0; i++) + if (emltab[i].format == format) + break; + if (emltab[i].format == 0) { + snd_mtxunlock(sc->lock); + return -1; + } + + /* set format information */ + ch->format = format; + ch->emldma = emltab[i].emldma; + if (ch->unit > emltab[i].unit) + ch->blk *= ch->unit / emltab[i].unit; + else + ch->blk /= emltab[i].unit / ch->unit; + ch->unit = emltab[i].unit; + + /* set channel buffer information */ + ch->size = ch->unit * ENVY24_SAMPLE_NUM; + if (ch->dir == PCMDIR_PLAY) + bsize = ch->blk * 4 / ENVY24_PLAY_BUFUNIT; + else + bsize = ch->blk * 4 / ENVY24_REC_BUFUNIT; + bsize *= ch->unit; + bcnt = ch->size / bsize; + sndbuf_resize(ch->buffer, bcnt, bsize); + snd_mtxunlock(sc->lock); + +#if(0) + device_printf(sc->dev, "envy24chan_setformat(): return 0x%08x\n", 0); +#endif + return 0; +} + +/* + IMPLEMENT NOTICE: In this driver, setspeed function only do setting + of speed information value. And real hardware speed setting is done + at start triggered(see envy24chan_trigger()). So, at this function + is called, any value that ENVY24 can use is able to set. But, at + start triggerd, some other channel is running, and that channel's + speed isn't same with, then trigger function will fail. +*/ +static int +envy24chan_setspeed(kobj_t obj, void *data, u_int32_t speed) +{ + struct sc_chinfo *ch = data; + u_int32_t val, prev; + int i; + +#if(0) + device_printf(ch->parent->dev, "envy24chan_setspeed(obj, data, %d)\n", speed); +#endif + prev = 0x7fffffff; + for (i = 0; (val = envy24_speed[i]) != 0; i++) { + if (abs(val - speed) < abs(prev - speed)) + prev = val; + else + break; + } + ch->speed = prev; + +#if(0) + device_printf(ch->parent->dev, "envy24chan_setspeed(): return %d\n", ch->speed); +#endif + return ch->speed; +} + +static int +envy24chan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize) +{ + struct sc_chinfo *ch = data; + struct sc_info *sc = ch->parent; + u_int32_t size, prev; + +#if(0) + device_printf(sc->dev, "envy24chan_setblocksize(obj, data, %d)\n", blocksize); +#endif + prev = 0x7fffffff; + snd_mtxlock(sc->lock); + for (size = ch->size / 2; size > 0; size /= 2) { + if (abs(size - blocksize) < abs(prev - blocksize)) + prev = size; + else + break; + } + + ch->blk = prev / ch->unit; + if (ch->dir == PCMDIR_PLAY) + ch->blk *= ENVY24_PLAY_BUFUNIT / 4; + else + ch->blk *= ENVY24_REC_BUFUNIT / 4; + snd_mtxunlock(sc->lock); + +#if(0) + device_printf(sc->dev, "envy24chan_setblocksize(): return %d\n", prev); +#endif + return prev; +} + +/* semantic note: must start at beginning of buffer */ +static int +envy24chan_trigger(kobj_t obj, void *data, int go) +{ + struct sc_chinfo *ch = data; + struct sc_info *sc = ch->parent; + u_int32_t ptr; + int slot; + int i; + +#if(0) + device_printf(sc->dev, "envy24chan_trigger(obj, data, %d)\n", go); +#endif + snd_mtxlock(sc->lock); + if (ch->dir == PCMDIR_PLAY) + slot = 0; + else + slot = 1; + switch (go) { + case PCMTRIG_START: +#if(0) + device_printf(sc->dev, "envy24chan_trigger(): start\n"); +#endif + /* check or set channel speed */ + if (sc->run[0] == 0 && sc->run[1] == 0) { + sc->speed = envy24_setspeed(sc, ch->speed); + sc->caps[0].minspeed = sc->caps[0].maxspeed = sc->speed; + sc->caps[1].minspeed = sc->caps[1].maxspeed = sc->speed; + } + else if (ch->speed != 0 && ch->speed != sc->speed) + return -1; + if (ch->speed == 0) + ch->channel->speed = sc->speed; + /* start or enable channel */ + sc->run[slot]++; + if (sc->run[slot] == 1) { + /* first channel */ + ch->offset = 0; + sc->blk[slot] = ch->blk; + } + else { + ptr = envy24_gethwptr(sc, ch->dir); + ch->offset = ((ptr / ch->blk + 1) * ch->blk % + (ch->size / 4)) * 4 / ch->unit; + if (ch->blk < sc->blk[slot]) + sc->blk[slot] = ch->blk; + } + if (ch->dir == PCMDIR_PLAY) { + ch->emldma(ch); + envy24_setvolume(sc, ch->num); + } + envy24_updintr(sc, ch->dir); + if (sc->run[slot] == 1) + envy24_start(sc, ch->dir); + ch->run = 1; + break; + case PCMTRIG_EMLDMAWR: +#if(0) + device_printf(sc->dev, "envy24chan_trigger(): emldmawr\n"); +#endif + if (ch->run != 1) + return -1; + ch->emldma(ch); + break; + case PCMTRIG_EMLDMARD: +#if(0) + device_printf(sc->dev, "envy24chan_trigger(): emldmard\n"); +#endif + if (ch->run != 1) + return -1; + ch->emldma(ch); + break; + case PCMTRIG_ABORT: +#if(0) + device_printf(sc->dev, "envy24chan_trigger(): abort\n"); +#endif + ch->run = 0; + sc->run[slot]--; + if (ch->dir == PCMDIR_PLAY) + envy24_mutevolume(sc, ch->num); + if (sc->run[slot] == 0) { + envy24_stop(sc, ch->dir); + sc->intr[slot] = 0; + } + else if (ch->blk == sc->blk[slot]) { + sc->blk[slot] = ENVY24_SAMPLE_NUM / 2; + for (i = 0; i < ENVY24_CHAN_NUM; i++) { + if (sc->chan[i].dir == ch->dir && + sc->chan[i].run == 1 && + sc->chan[i].blk < sc->blk[slot]) + sc->blk[slot] = sc->chan[i].blk; + } + if (ch->blk != sc->blk[slot]) + envy24_updintr(sc, ch->dir); + } + break; + } + snd_mtxunlock(sc->lock); + + return 0; +} + +static int +envy24chan_getptr(kobj_t obj, void *data) +{ + struct sc_chinfo *ch = data; + struct sc_info *sc = ch->parent; + u_int32_t ptr; + int rtn; + +#if(0) + device_printf(sc->dev, "envy24chan_getptr()\n"); +#endif + snd_mtxlock(sc->lock); + ptr = envy24_gethwptr(sc, ch->dir); + rtn = ptr * ch->unit; + snd_mtxunlock(sc->lock); + +#if(0) + device_printf(sc->dev, "envy24chan_getptr(): return %d\n", + rtn); +#endif + return rtn; +} + +static struct pcmchan_caps * +envy24chan_getcaps(kobj_t obj, void *data) +{ + struct sc_chinfo *ch = data; + struct sc_info *sc = ch->parent; + struct pcmchan_caps *rtn; + +#if(0) + device_printf(sc->dev, "envy24chan_getcaps()\n"); +#endif + snd_mtxlock(sc->lock); + if (ch->dir == PCMDIR_PLAY) { + if (sc->run[0] == 0) + rtn = &envy24_playcaps; + else + rtn = &sc->caps[0]; + } + else { + if (sc->run[1] == 0) + rtn = &envy24_reccaps; + else + rtn = &sc->caps[1]; + } + snd_mtxunlock(sc->lock); + + return rtn; +} + +static kobj_method_t envy24chan_methods[] = { + KOBJMETHOD(channel_init, envy24chan_init), + KOBJMETHOD(channel_free, envy24chan_free), + KOBJMETHOD(channel_setformat, envy24chan_setformat), + KOBJMETHOD(channel_setspeed, envy24chan_setspeed), + KOBJMETHOD(channel_setblocksize, envy24chan_setblocksize), + KOBJMETHOD(channel_trigger, envy24chan_trigger), + KOBJMETHOD(channel_getptr, envy24chan_getptr), + KOBJMETHOD(channel_getcaps, envy24chan_getcaps), + { 0, 0 } +}; +CHANNEL_DECLARE(envy24chan); + +/* -------------------------------------------------------------------- */ + +/* mixer interface */ + +static int +envy24mixer_init(struct snd_mixer *m) +{ + struct sc_info *sc = mix_getdevinfo(m); + +#if(0) + device_printf(sc->dev, "envy24mixer_init()\n"); +#endif + if (sc == NULL) + return -1; + + /* set volume control rate */ + snd_mtxlock(sc->lock); + envy24_wrmt(sc, ENVY24_MT_VOLRATE, 0x30, 1); /* 0x30 is default value */ + + mix_setdevs(m, ENVY24_MIX_MASK); + mix_setrecdevs(m, ENVY24_MIX_REC_MASK); + snd_mtxunlock(sc->lock); + + return 0; +} + +static int +envy24mixer_reinit(struct snd_mixer *m) +{ + struct sc_info *sc = mix_getdevinfo(m); + + if (sc == NULL) + return -1; +#if(0) + device_printf(sc->dev, "envy24mixer_reinit()\n"); +#endif + + return 0; +} + +static int +envy24mixer_uninit(struct snd_mixer *m) +{ + struct sc_info *sc = mix_getdevinfo(m); + + if (sc == NULL) + return -1; +#if(0) + device_printf(sc->dev, "envy24mixer_uninit()\n"); +#endif + + return 0; +} + +static int +envy24mixer_set(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right) +{ + struct sc_info *sc = mix_getdevinfo(m); + int ch = envy24_mixmap[dev]; + int hwch; + int i; + + if (sc == NULL) + return -1; + if (dev == 0 && sc->cfg->codec->setvolume == NULL) + return -1; + if (dev != 0 && ch == -1) + return -1; + hwch = envy24_chanmap[ch]; +#if(0) + device_printf(sc->dev, "envy24mixer_set(m, %d, %d, %d)\n", + dev, left, right); +#endif + + snd_mtxlock(sc->lock); + if (dev == 0) { + for (i = 0; i < sc->dacn; i++) { + sc->cfg->codec->setvolume(sc->dac[i], PCMDIR_PLAY, left, right); + } + } + else { + /* set volume value for hardware */ + if ((sc->left[hwch] = 100 - left) > ENVY24_VOL_MIN) + sc->left[hwch] = ENVY24_VOL_MUTE; + if ((sc->right[hwch] = 100 - right) > ENVY24_VOL_MIN) + sc->right[hwch] = ENVY24_VOL_MUTE; + + /* set volume for record channel and running play channel */ + if (hwch > ENVY24_CHAN_PLAY_SPDIF || sc->chan[ch].run) + envy24_setvolume(sc, hwch); + } + snd_mtxunlock(sc->lock); + + return right << 8 | left; +} + +static u_int32_t +envy24mixer_setrecsrc(struct snd_mixer *m, u_int32_t src) +{ + struct sc_info *sc = mix_getdevinfo(m); + int ch = envy24_mixmap[src]; +#if(0) + device_printf(sc->dev, "envy24mixer_setrecsrc(m, %d)\n", src); +#endif + + if (ch > ENVY24_CHAN_PLAY_SPDIF) + sc->src = ch; + return src; +} + +static kobj_method_t envy24mixer_methods[] = { + KOBJMETHOD(mixer_init, envy24mixer_init), + KOBJMETHOD(mixer_reinit, envy24mixer_reinit), + KOBJMETHOD(mixer_uninit, envy24mixer_uninit), + KOBJMETHOD(mixer_set, envy24mixer_set), + KOBJMETHOD(mixer_setrecsrc, envy24mixer_setrecsrc), + { 0, 0 } +}; +MIXER_DECLARE(envy24mixer); + +/* -------------------------------------------------------------------- */ + +/* The interrupt handler */ +static void +envy24_intr(void *p) +{ + struct sc_info *sc = (struct sc_info *)p; + struct sc_chinfo *ch; + u_int32_t ptr, dsize, feed; + int i; + +#if(0) + device_printf(sc->dev, "envy24_intr()\n"); +#endif + snd_mtxlock(sc->lock); + if (envy24_checkintr(sc, PCMDIR_PLAY)) { +#if(0) + device_printf(sc->dev, "envy24_intr(): play\n"); +#endif + dsize = sc->psize / 4; + ptr = dsize - envy24_rdmt(sc, ENVY24_MT_PCNT, 2) - 1; +#if(0) + device_printf(sc->dev, "envy24_intr(): ptr = %d-->", ptr); +#endif + ptr -= ptr % sc->blk[0]; + feed = (ptr + dsize - sc->intr[0]) % dsize; +#if(0) + printf("%d intr = %d feed = %d\n", ptr, sc->intr[0], feed); +#endif + for (i = ENVY24_CHAN_PLAY_DAC1; i <= ENVY24_CHAN_PLAY_SPDIF; i++) { + ch = &sc->chan[i]; +#if(0) + if (ch->run) + device_printf(sc->dev, "envy24_intr(): chan[%d].blk = %d\n", i, ch->blk); +#endif + if (ch->run && ch->blk <= feed) + chn_intr(ch->channel); + } + sc->intr[0] = ptr; + envy24_updintr(sc, PCMDIR_PLAY); + } + if (envy24_checkintr(sc, PCMDIR_REC)) { +#if(0) + device_printf(sc->dev, "envy24_intr(): rec\n"); +#endif + dsize = sc->rsize / 4; + ptr = dsize - envy24_rdmt(sc, ENVY24_MT_RCNT, 2) - 1; + ptr -= ptr % sc->blk[1]; + feed = (ptr + dsize - sc->intr[1]) % dsize; + for (i = ENVY24_CHAN_REC_ADC1; i <= ENVY24_CHAN_REC_SPDIF; i++) { + ch = &sc->chan[i]; + if (ch->run && ch->blk <= feed) + chn_intr(ch->channel); + } + sc->intr[1] = ptr; + envy24_updintr(sc, PCMDIR_REC); + } + snd_mtxunlock(sc->lock); + + return; +} + +/* + * Probe and attach the card + */ + +static int +envy24_pci_probe(device_t dev) +{ + u_int16_t sv, sd; + int i; + +#if(0) + printf("envy24_pci_probe()\n"); +#endif + if (pci_get_device(dev) == PCID_ENVY24 && + pci_get_vendor(dev) == PCIV_ENVY24) { + sv = pci_get_subvendor(dev); + sd = pci_get_subdevice(dev); + for (i = 0; cfg_table[i].subvendor != 0 || cfg_table[i].subdevice != 0; i++) { + if (cfg_table[i].subvendor == sv && + cfg_table[i].subdevice == sd) { + break; + } + } + device_set_desc(dev, cfg_table[i].name); +#if(0) + printf("envy24_pci_probe(): return 0\n"); +#endif + return 0; + } + else { +#if(0) + printf("envy24_pci_probe(): return ENXIO\n"); +#endif + return ENXIO; + } +} + +static void +envy24_dmapsetmap(void *arg, bus_dma_segment_t *segs, int nseg, int error) +{ + struct sc_info *sc = (struct sc_info *)arg; + +#if(0) + device_printf(sc->dev, "envy24_dmapsetmap()\n"); +#endif + if (bootverbose) { + printf("envy24(play): setmap %lx, %lx; ", + (unsigned long)segs->ds_addr, + (unsigned long)segs->ds_len); + printf("%p -> %lx\n", sc->pmap, (unsigned long)vtophys(sc->pmap)); + } +} + +static void +envy24_dmarsetmap(void *arg, bus_dma_segment_t *segs, int nseg, int error) +{ + struct sc_info *sc = (struct sc_info *)arg; + +#if(0) + device_printf(sc->dev, "envy24_dmarsetmap()\n"); +#endif + if (bootverbose) { + printf("envy24(record): setmap %lx, %lx; ", + (unsigned long)segs->ds_addr, + (unsigned long)segs->ds_len); + printf("%p -> %lx\n", sc->rmap, (unsigned long)vtophys(sc->pmap)); + } +} + +static void +envy24_dmafree(struct sc_info *sc) +{ +#if(0) + device_printf(sc->dev, "envy24_dmafree():"); + if (sc->rmap) printf(" sc->rmap(0x%08x)", (u_int32_t)sc->rmap); + else printf(" sc->rmap(null)"); + if (sc->pmap) printf(" sc->pmap(0x%08x)", (u_int32_t)sc->pmap); + else printf(" sc->pmap(null)"); + if (sc->rbuf) printf(" sc->rbuf(0x%08x)", (u_int32_t)sc->rbuf); + else printf(" sc->rbuf(null)"); + if (sc->pbuf) printf(" sc->pbuf(0x%08x)\n", (u_int32_t)sc->pbuf); + else printf(" sc->pbuf(null)\n"); +#endif +#if(0) + if (sc->rmap) + bus_dmamap_unload(sc->dmat, sc->rmap); + if (sc->pmap) + bus_dmamap_unload(sc->dmat, sc->pmap); + if (sc->rbuf) + bus_dmamem_free(sc->dmat, sc->rbuf, sc->rmap); + if (sc->pbuf) + bus_dmamem_free(sc->dmat, sc->pbuf, sc->pmap); +#else + bus_dmamap_unload(sc->dmat, sc->rmap); + bus_dmamap_unload(sc->dmat, sc->pmap); + bus_dmamem_free(sc->dmat, sc->rbuf, sc->rmap); + bus_dmamem_free(sc->dmat, sc->pbuf, sc->pmap); +#endif + + sc->rmap = sc->pmap = NULL; + sc->pbuf = NULL; + sc->rbuf = NULL; + + return; +} + +static int +envy24_dmainit(struct sc_info *sc) +{ + u_int32_t addr; + +#if(0) + device_printf(sc->dev, "envy24_dmainit()\n"); +#endif + /* init values */ + sc->psize = ENVY24_PLAY_BUFUNIT * ENVY24_SAMPLE_NUM; + sc->rsize = ENVY24_REC_BUFUNIT * ENVY24_SAMPLE_NUM; + sc->pbuf = NULL; + sc->rbuf = NULL; + sc->pmap = sc->rmap = NULL; + sc->blk[0] = sc->blk[1] = 0; + + /* allocate DMA buffer */ +#if(0) + device_printf(sc->dev, "envy24_dmainit(): bus_dmamem_alloc(): sc->pbuf\n"); +#endif + if (bus_dmamem_alloc(sc->dmat, (void **)&sc->pbuf, BUS_DMA_NOWAIT, &sc->pmap)) + goto bad; +#if(0) + device_printf(sc->dev, "envy24_dmainit(): bus_dmamem_alloc(): sc->rbuf\n"); +#endif + if (bus_dmamem_alloc(sc->dmat, (void **)&sc->rbuf, BUS_DMA_NOWAIT, &sc->rmap)) + goto bad; +#if(0) + device_printf(sc->dev, "envy24_dmainit(): bus_dmamem_load(): sc->pmap\n"); +#endif + if (bus_dmamap_load(sc->dmat, sc->pmap, sc->pbuf, sc->psize, envy24_dmapsetmap, sc, 0)) + goto bad; +#if(0) + device_printf(sc->dev, "envy24_dmainit(): bus_dmamem_load(): sc->rmap\n"); +#endif + if (bus_dmamap_load(sc->dmat, sc->rmap, sc->rbuf, sc->rsize, envy24_dmarsetmap, sc, 0)) + goto bad; + bzero(sc->pbuf, sc->psize); + bzero(sc->rbuf, sc->rsize); + + /* set values to register */ + addr = vtophys(sc->pbuf); +#if(0) + device_printf(sc->dev, "pbuf(0x%08x)\n", addr); +#endif + envy24_wrmt(sc, ENVY24_MT_PADDR, addr, 4); +#if(0) + device_printf(sc->dev, "PADDR-->(0x%08x)\n", envy24_rdmt(sc, ENVY24_MT_PADDR, 4)); + device_printf(sc->dev, "psize(%ld)\n", sc->psize / 4 - 1); +#endif + envy24_wrmt(sc, ENVY24_MT_PCNT, sc->psize / 4 - 1, 2); +#if(0) + device_printf(sc->dev, "PCNT-->(%ld)\n", envy24_rdmt(sc, ENVY24_MT_PCNT, 2)); +#endif + addr = vtophys(sc->rbuf); + envy24_wrmt(sc, ENVY24_MT_RADDR, addr, 4); + envy24_wrmt(sc, ENVY24_MT_RCNT, sc->rsize / 4 - 1, 2); + + return 0; + bad: + envy24_dmafree(sc); + return ENOSPC; +} + +static void +envy24_putcfg(struct sc_info *sc) +{ + device_printf(sc->dev, "system configuration\n", sc->adcn, sc->dacn); + printf(" SubVendorID: 0x%04x, SubDeviceID: 0x%04x\n", + sc->cfg->subvendor, sc->cfg->subdevice); + printf(" XIN2 Clock Source: "); + switch (sc->cfg->scfg & PCIM_SCFG_XIN2) { + case 0x00: + printf("22.5792MHz(44.1kHz*512)\n"); + break; + case 0x40: + printf("16.9344MHz(44.1kHz*384)\n"); + break; + case 0x80: + printf("from external clock synthesizer chip\n"); + break; + default: + printf("illeagal system setting\n"); + } + printf(" MPU-401 UART(s) #: "); + if (sc->cfg->scfg & PCIM_SCFG_MPU) + printf("2\n"); + else + printf("1\n"); + printf(" AC'97 codec: "); + if (sc->cfg->scfg & PCIM_SCFG_AC97) + printf("not exist\n"); + else + printf("exist\n"); + printf(" ADC #: "); + printf("%d\n", sc->adcn); + printf(" DAC #: "); + printf("%d\n", sc->dacn); + printf(" Multi-track converter type: "); + if ((sc->cfg->acl & PCIM_ACL_MTC) == 0) { + printf("AC'97(SDATA_OUT:"); + if (sc->cfg->acl & PCIM_ACL_OMODE) + printf("packed"); + else + printf("split"); + printf("|SDATA_IN:"); + if (sc->cfg->acl & PCIM_ACL_IMODE) + printf("packed"); + else + printf("split"); + printf(")\n"); + } + else { + printf("I2S("); + if (sc->cfg->i2s & PCIM_I2S_VOL) + printf("with volume, "); + if (sc->cfg->i2s & PCIM_I2S_96KHZ) + printf("96KHz support, "); + switch (sc->cfg->i2s & PCIM_I2S_RES) { + case PCIM_I2S_16BIT: + printf("16bit resolution, "); + break; + case PCIM_I2S_18BIT: + printf("18bit resolution, "); + break; + case PCIM_I2S_20BIT: + printf("20bit resolution, "); + break; + case PCIM_I2S_24BIT: + printf("24bit resolution, "); + break; + } + printf("ID#0x%x)\n", sc->cfg->i2s & PCIM_I2S_ID); + } + printf(" S/PDIF(IN/OUT): "); + if (sc->cfg->spdif & PCIM_SPDIF_IN) + printf("1/"); + else + printf("0/"); + if (sc->cfg->spdif & PCIM_SPDIF_OUT) + printf("1 "); + else + printf("0 "); + if (sc->cfg->spdif & (PCIM_SPDIF_IN | PCIM_SPDIF_OUT)) + printf("ID# 0x%02x\n", (sc->cfg->spdif & PCIM_SPDIF_ID) >> 2); + printf(" GPIO(mask/dir/state): 0x%02x/0x%02x/0x%02x\n", + sc->cfg->gpiomask, sc->cfg->gpiodir, sc->cfg->gpiostate); +} + +static int +envy24_init(struct sc_info *sc) +{ + u_int32_t data; +#if(0) + int rtn; +#endif + int i; + u_int32_t sv, sd; + + +#if(0) + device_printf(sc->dev, "envy24_init()\n"); +#endif + + /* reset chip */ + envy24_wrcs(sc, ENVY24_CCS_CTL, ENVY24_CCS_CTL_RESET | ENVY24_CCS_CTL_NATIVE, 1); + DELAY(200); + envy24_wrcs(sc, ENVY24_CCS_CTL, ENVY24_CCS_CTL_NATIVE, 1); + DELAY(200); + + /* legacy hardware disable */ + data = pci_read_config(sc->dev, PCIR_LAC, 2); + data |= PCIM_LAC_DISABLE; + pci_write_config(sc->dev, PCIR_LAC, data, 2); + + /* check system configuration */ + sc->cfg = NULL; + for (i = 0; cfg_table[i].subvendor != 0 || cfg_table[i].subdevice != 0; i++) { + /* 1st: search configuration from table */ + sv = pci_get_subvendor(sc->dev); + sd = pci_get_subdevice(sc->dev); + if (sv == cfg_table[i].subvendor && sd == cfg_table[i].subdevice) { +#if(0) + device_printf(sc->dev, "Set configuration from table\n"); +#endif + sc->cfg = &cfg_table[i]; + break; + } + } + if (sc->cfg == NULL) { + /* 2nd: read configuration from table */ + sc->cfg = envy24_rom2cfg(sc); + } + sc->adcn = ((sc->cfg->scfg & PCIM_SCFG_ADC) >> 2) + 1; + sc->dacn = (sc->cfg->scfg & PCIM_SCFG_DAC) + 1; + + if (1 /* bootverbose */) { + envy24_putcfg(sc); + } + + /* set system configuration */ + pci_write_config(sc->dev, PCIR_SCFG, sc->cfg->scfg, 1); + pci_write_config(sc->dev, PCIR_ACL, sc->cfg->acl, 1); + pci_write_config(sc->dev, PCIR_I2S, sc->cfg->i2s, 1); + pci_write_config(sc->dev, PCIR_SPDIF, sc->cfg->spdif, 1); + envy24_gpiosetmask(sc, sc->cfg->gpiomask); + envy24_gpiosetdir(sc, sc->cfg->gpiodir); + envy24_gpiowr(sc, sc->cfg->gpiostate); + for (i = 0; i < sc->adcn; i++) { + sc->adc[i] = sc->cfg->codec->create(sc->dev, sc, PCMDIR_REC, i); + sc->cfg->codec->init(sc->adc[i]); + } + for (i = 0; i < sc->dacn; i++) { + sc->dac[i] = sc->cfg->codec->create(sc->dev, sc, PCMDIR_PLAY, i); + sc->cfg->codec->init(sc->dac[i]); + } + + /* initialize DMA buffer */ +#if(0) + device_printf(sc->dev, "envy24_init(): initialize DMA buffer\n"); +#endif + if (envy24_dmainit(sc)) + return ENOSPC; + + /* initialize status */ + sc->run[0] = sc->run[1] = 0; + sc->intr[0] = sc->intr[1] = 0; + sc->speed = 0; + sc->caps[0].fmtlist = envy24_playfmt; + sc->caps[1].fmtlist = envy24_recfmt; + + /* set channel router */ + envy24_route(sc, ENVY24_ROUTE_DAC_1, ENVY24_ROUTE_CLASS_MIX, 0, 0); + envy24_route(sc, ENVY24_ROUTE_DAC_SPDIF, ENVY24_ROUTE_CLASS_DMA, 0, 0); + /* envy24_route(sc, ENVY24_ROUTE_DAC_SPDIF, ENVY24_ROUTE_CLASS_MIX, 0, 0); */ + + /* set macro interrupt mask */ + data = envy24_rdcs(sc, ENVY24_CCS_IMASK, 1); + envy24_wrcs(sc, ENVY24_CCS_IMASK, data & ~ENVY24_CCS_IMASK_PMT, 1); + data = envy24_rdcs(sc, ENVY24_CCS_IMASK, 1); +#if(0) + device_printf(sc->dev, "envy24_init(): CCS_IMASK-->0x%02x\n", data); +#endif + + return 0; +} + +static int +envy24_alloc_resource(struct sc_info *sc) +{ + /* allocate I/O port resource */ + sc->csid = PCIR_CCS; + sc->cs = bus_alloc_resource(sc->dev, SYS_RES_IOPORT, + &sc->csid, 0, ~0, 1, RF_ACTIVE); + sc->ddmaid = PCIR_DDMA; + sc->ddma = bus_alloc_resource(sc->dev, SYS_RES_IOPORT, + &sc->ddmaid, 0, ~0, 1, RF_ACTIVE); + sc->dsid = PCIR_DS; + sc->ds = bus_alloc_resource(sc->dev, SYS_RES_IOPORT, + &sc->dsid, 0, ~0, 1, RF_ACTIVE); + sc->mtid = PCIR_MT; + sc->mt = bus_alloc_resource(sc->dev, SYS_RES_IOPORT, + &sc->mtid, 0, ~0, 1, RF_ACTIVE); + if (!sc->cs || !sc->ddma || !sc->ds || !sc->mt) { + device_printf(sc->dev, "unable to map IO port space\n"); + return ENXIO; + } + sc->cst = rman_get_bustag(sc->cs); + sc->csh = rman_get_bushandle(sc->cs); + sc->ddmat = rman_get_bustag(sc->ddma); + sc->ddmah = rman_get_bushandle(sc->ddma); + sc->dst = rman_get_bustag(sc->ds); + sc->dsh = rman_get_bushandle(sc->ds); + sc->mtt = rman_get_bustag(sc->mt); + sc->mth = rman_get_bushandle(sc->mt); +#if(0) + device_printf(sc->dev, + "IO port register values\nCCS: 0x%lx\nDDMA: 0x%lx\nDS: 0x%lx\nMT: 0x%lx\n", + pci_read_config(sc->dev, PCIR_CCS, 4), + pci_read_config(sc->dev, PCIR_DDMA, 4), + pci_read_config(sc->dev, PCIR_DS, 4), + pci_read_config(sc->dev, PCIR_MT, 4)); +#endif + + /* allocate interupt resource */ + sc->irqid = 0; + sc->irq = bus_alloc_resource(sc->dev, SYS_RES_IRQ, &sc->irqid, + 0, ~0, 1, RF_ACTIVE | RF_SHAREABLE); + if (!sc->irq || + snd_setup_intr(sc->dev, sc->irq, INTR_MPSAFE, envy24_intr, sc, &sc->ih)) { + device_printf(sc->dev, "unable to map interrupt\n"); + return ENXIO; + } + + /* allocate DMA resource */ + if (bus_dma_tag_create(/*parent*/NULL, /*alignment*/4, /*boundary*/0, + /*lowaddr*/BUS_SPACE_MAXADDR_ENVY24, + /*highaddr*/BUS_SPACE_MAXADDR_ENVY24, + /*filter*/NULL, /*filterarg*/NULL, + /*maxsize*/BUS_SPACE_MAXSIZE_ENVY24, + /*nsegments*/1, /*maxsegsz*/0x3ffff, + /*flags*/0, &sc->dmat) != 0) { + device_printf(sc->dev, "unable to create dma tag\n"); + return ENXIO; + } + + return 0; +} + +static int +envy24_pci_attach(device_t dev) +{ + u_int32_t data; + struct sc_info *sc; + char status[SND_STATUSLEN]; + char name[ENVY24_NAMELEN]; + int err = 0; + int i; + +#if(0) + device_printf(dev, "envy24_pci_attach()\n"); +#endif + /* get sc_info data area */ + if ((sc = malloc(sizeof(*sc), M_ENVY24, M_NOWAIT)) == NULL) { + device_printf(dev, "cannot allocate softc\n"); + return ENXIO; + } + + bzero(sc, sizeof(*sc)); + snprintf(name, ENVY24_NAMELEN, "%s:envy24", device_get_nameunit(dev)); + sc->lock = snd_mtxcreate(name); + sc->dev = dev; + + /* initialize PCI interface */ + 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); + + /* allocate resources */ + if (err = envy24_alloc_resource(sc)) { + device_printf(dev, "unable to allocate system resources\n"); + goto bad; + } + + /* initialize card */ + if (err = envy24_init(sc)) { + device_printf(dev, "unable to initialize the card\n"); + goto bad; + } + + /* set multi track mixer */ + mixer_init(dev, &envy24mixer_class, sc); + + /* set channel information */ + if (err = pcm_register(dev, sc, 5, 2 + sc->adc)) + goto bad; + sc->chnum = 0; + for (i = 0; i < 5; i++) { + pcm_addchan(dev, PCMDIR_PLAY, &envy24chan_class, sc); + sc->chnum++; + } + for (i = 0; i < 2 + sc->adcn; i++) { + pcm_addchan(dev, PCMDIR_REC, &envy24chan_class, sc); + sc->chnum++; + } + + /* set status iformation */ + snprintf(status, SND_STATUSLEN, + "at io 0x%lx:%ld,0x%lx:%ld,0x%lx:%ld,0x%lx:%ld irq %ld", + rman_get_start(sc->cs), + rman_get_end(sc->cs) - rman_get_start(sc->cs) + 1, + rman_get_start(sc->ddma), + rman_get_end(sc->ddma) - rman_get_start(sc->ddma) + 1, + rman_get_start(sc->ds), + rman_get_end(sc->ds) - rman_get_start(sc->ds) + 1, + rman_get_start(sc->mt), + rman_get_end(sc->mt) - rman_get_start(sc->mt) + 1, + rman_get_start(sc->irq)); + pcm_setstatus(dev, status); + + return 0; + +bad: + if (sc->ih) + bus_teardown_intr(dev, sc->irq, sc->ih); + if (sc->irq) + bus_release_resource(dev, SYS_RES_IRQ, sc->irqid, sc->irq); + envy24_dmafree(sc); + if (sc->dmat) + bus_dma_tag_destroy(sc->dmat); + envy24_cfgfree(sc->cfg); + if (sc->cs) + bus_release_resource(dev, SYS_RES_IOPORT, sc->csid, sc->cs); + if (sc->ddma) + bus_release_resource(dev, SYS_RES_IOPORT, sc->ddmaid, sc->ddma); + if (sc->ds) + bus_release_resource(dev, SYS_RES_IOPORT, sc->dsid, sc->ds); + if (sc->mt) + bus_release_resource(dev, SYS_RES_IOPORT, sc->mtid, sc->mt); + if (sc->lock) + snd_mtxfree(sc->lock); + free(sc, M_ENVY24); + return err; +} + +static int +envy24_pci_detach(device_t dev) +{ + struct sc_info *sc; + int r; + int i; + +#if(0) + device_printf(dev, "envy24_pci_detach()\n"); +#endif + sc = pcm_getdevinfo(dev); + if (sc == NULL) + return 0; + r = pcm_unregister(dev); + if (r) + return r; + + envy24_dmafree(sc); + if (sc->cfg->codec->destroy != NULL) { + for (i = 0; i < sc->adcn; i++) + sc->cfg->codec->destroy(sc->adc[i]); + for (i = 0; i < sc->dacn; i++) + sc->cfg->codec->destroy(sc->dac[i]); + } + envy24_cfgfree(sc->cfg); + bus_dma_tag_destroy(sc->dmat); + bus_teardown_intr(dev, sc->irq, sc->ih); + bus_release_resource(dev, SYS_RES_IRQ, sc->irqid, sc->irq); + bus_release_resource(dev, SYS_RES_IOPORT, sc->csid, sc->cs); + bus_release_resource(dev, SYS_RES_IOPORT, sc->ddmaid, sc->ddma); + bus_release_resource(dev, SYS_RES_IOPORT, sc->dsid, sc->ds); + bus_release_resource(dev, SYS_RES_IOPORT, sc->mtid, sc->mt); + snd_mtxfree(sc->lock); + free(sc, M_ENVY24); + return 0; +} + +static device_method_t envy24_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, envy24_pci_probe), + DEVMETHOD(device_attach, envy24_pci_attach), + DEVMETHOD(device_detach, envy24_pci_detach), + { 0, 0 } +}; + +static driver_t envy24_driver = { + "pcm", + envy24_methods, +#if __FreeBSD_version > 500000 + PCM_SOFTC_SIZE, +#else + sizeof(struct snddev_info), +#endif +}; + +DRIVER_MODULE(snd_envy24, pci, envy24_driver, pcm_devclass, 0, 0); +MODULE_DEPEND(snd_envy24, snd_pcm, PCM_MINVER, PCM_PREFVER, PCM_MAXVER); +MODULE_DEPEND(snd_envy24, snd_ak452x, PCM_MINVER, PCM_PREFVER, PCM_MAXVER); +MODULE_VERSION(snd_envy24, 1); diff --git a/sys/dev/sound/pci/envy24ht.h b/sys/dev/sound/pci/envy24ht.h new file mode 100644 index 000000000000..8830145842b6 --- /dev/null +++ b/sys/dev/sound/pci/envy24ht.h @@ -0,0 +1,482 @@ +/* + * Copyright (c) 2001 Katsurajima Naoto + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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, WHETHERIN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THEPOSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + + +/* -------------------------------------------------------------------- */ + +/* PCI device ID */ +#define PCIV_ENVY24 0x1412 +#define PCID_ENVY24 0x1712 + +/* PCI Registers */ + +#define PCIR_CCS 0x10 /* Controller I/O Base Address */ +#define PCIR_DDMA 0x14 /* DDMA I/O Base Address */ +#define PCIR_DS 0x18 /* DMA Path Registers I/O Base Address */ +#define PCIR_MT 0x1c /* Professional Multi-Track I/O Base Address */ + +#define PCIR_LAC 0x40 /* Legacy Audio Control */ +#define PCIM_LAC_DISABLE 0x8000 /* Legacy Audio Hardware disabled */ +#define PCIM_LAC_SBDMA0 0x0000 /* SB DMA Channel Select: 0 */ +#define PCIM_LAC_SBDMA1 0x0040 /* SB DMA Channel Select: 1 */ +#define PCIM_LAC_SBDMA3 0x00c0 /* SB DMA Channel Select: 3 */ +#define PCIM_LAC_IOADDR10 0x0020 /* I/O Address Alias Control */ +#define PCIM_LAC_MPU401 0x0008 /* MPU-401 I/O enable */ +#define PCIM_LAC_GAME 0x0004 /* Game Port enable (200h) */ +#define PCIM_LAC_FM 0x0002 /* FM I/O enable (AdLib 388h base) */ +#define PCIM_LAC_SB 0x0001 /* SB I/O enable */ + +#define PCIR_LCC 0x42 /* Legacy Configuration Control */ +#define PCIM_LCC_VINT 0xff00 /* Interrupt vector to be snooped */ +#define PCIM_LCC_SVIDRW 0x0080 /* SVID read/write enable */ +#define PCIM_LCC_SNPSB 0x0040 /* snoop SB 22C/24Ch I/O write cycle */ +#define PCIM_LCC_SNPPIC 0x0020 /* snoop PIC I/O R/W cycle */ +#define PCIM_LCC_SNPPCI 0x0010 /* snoop PCI bus interrupt acknowledge cycle */ +#define PCIM_LCC_SBBASE 0x0008 /* SB base 240h(1)/220h(0) */ +#define PCIM_LCC_MPUBASE 0x0006 /* MPU-401 base 300h-330h */ +#define PCIM_LCC_LDMA 0x0001 /* Legacy DMA enable */ + +#define PCIR_SCFG 0x60 /* System Configuration Register */ +#define PCIM_SCFG_XIN2 0xc0 /* XIN2 Clock Source Configuration */ + /* 00: 22.5792MHz(44.1kHz*512) */ + /* 01: 16.9344MHz(44.1kHz*384) */ + /* 10: from external clock synthesizer chip */ +#define PCIM_SCFG_MPU 0x20 /* 1(0)/2(1) MPU-401 UART(s) */ +#define PCIM_SCFG_AC97 0x10 /* 0: AC'97 codec exist */ + /* 1: AC'97 codec not exist */ +#define PCIM_SCFG_ADC 0x0c /* 1-4 stereo ADC connected */ +#define PCIM_SCFG_DAC 0x03 /* 1-4 stereo DAC connected */ + +#define PCIR_ACL 0x61 /* AC-Link Configuration Register */ +#define PCIM_ACL_MTC 0x80 /* Multi-track converter type: 0:AC'97 1:I2S */ +#define PCIM_ACL_OMODE 0x02 /* AC 97 codec SDATA_OUT 0:split 1:packed */ +#define PCIM_ACL_IMODE 0x01 /* AC 97 codec SDATA_IN 0:split 1:packed */ + +#define PCIR_I2S 0x62 /* I2S Converters Features Register */ +#define PCIM_I2S_VOL 0x80 /* I2S codec Volume and mute */ +#define PCIM_I2S_96KHZ 0x40 /* I2S converter 96kHz sampling rate support */ +#define PCIM_I2S_RES 0x30 /* Converter resolution */ +#define PCIM_I2S_16BIT 0x00 /* 16bit */ +#define PCIM_I2S_18BIT 0x10 /* 18bit */ +#define PCIM_I2S_20BIT 0x20 /* 20bit */ +#define PCIM_I2S_24BIT 0x30 /* 24bit */ +#define PCIM_I2S_ID 0x0f /* Other I2S IDs */ + +#define PCIR_SPDIF 0x63 /* S/PDIF Configuration Register */ +#define PCIM_SPDIF_ID 0xfc /* S/PDIF chip ID */ +#define PCIM_SPDIF_IN 0x02 /* S/PDIF Stereo In is present */ +#define PCIM_SPDIF_OUT 0x01 /* S/PDIF Stereo Out is present */ + +#define PCIR_POWER_STAT 0x84 /* Power Management Control and Status */ + +/* Controller Registers */ + +#define ENVY24_CCS_CTL 0x00 /* Control/Status Register */ +#define ENVY24_CCS_CTL_RESET 0x80 /* Entire Chip soft reset */ +#define ENVY24_CCS_CTL_DMAINT 0x40 /* DS DMA Channel-C interrupt */ +#define ENVY24_CCS_CTL_DOSVOL 0x10 /* set the DOS WT volume control */ +#define ENVY24_CCS_CTL_EDGE 0x08 /* SERR# edge (only one PCI clock width) */ +#define ENVY24_CCS_CTL_SBINT 0x02 /* SERR# assertion for SB interrupt */ +#define ENVY24_CCS_CTL_NATIVE 0x01 /* Mode select: 0:SB mode 1:native mode */ + +#define ENVY24_CCS_IMASK 0x01 /* Interrupt Mask Register */ +#define ENVY24_CCS_IMASK_PMIDI 0x80 /* Primary MIDI */ +#define ENVY24_CCS_IMASK_TIMER 0x40 /* Timer */ +#define ENVY24_CCS_IMASK_SMIDI 0x20 /* Secondary MIDI */ +#define ENVY24_CCS_IMASK_PMT 0x10 /* Professional Multi-track */ +#define ENVY24_CCS_IMASK_FM 0x08 /* FM/MIDI trapping */ +#define ENVY24_CCS_IMASK_PDMA 0x04 /* Playback DS DMA */ +#define ENVY24_CCS_IMASK_RDMA 0x02 /* Consumer record DMA */ +#define ENVY24_CCS_IMASK_SB 0x01 /* Consumer/SB mode playback */ + +#define ENVY24_CCS_ISTAT 0x02 /* Interrupt Status Register */ +#define ENVY24_CCS_ISTAT_PMIDI 0x80 /* Primary MIDI */ +#define ENVY24_CCS_ISTAT_TIMER 0x40 /* Timer */ +#define ENVY24_CCS_ISTAT_SMIDI 0x20 /* Secondary MIDI */ +#define ENVY24_CCS_ISTAT_PMT 0x10 /* Professional Multi-track */ +#define ENVY24_CCS_ISTAT_FM 0x08 /* FM/MIDI trapping */ +#define ENVY24_CCS_ISTAT_PDMA 0x04 /* Playback DS DMA */ +#define ENVY24_CCS_ISTAT_RDMA 0x02 /* Consumer record DMA */ +#define ENVY24_CCS_ISTAT_SB 0x01 /* Consumer/SB mode playback */ + +#define ENVY24_CCS_INDEX 0x03 /* Envy24 Index Register */ +#define ENVY24_CCS_DATA 0x04 /* Envy24 Data Register */ + +#define ENVY24_CCS_NMI1 0x05 /* NMI Status Register 1 */ +#define ENVY24_CCS_NMI1_PCI 0x80 /* PCI I/O read/write cycle */ +#define ENVY24_CCS_NMI1_SB 0x40 /* SB 22C/24C write */ +#define ENVY24_CCS_NMI1_SBDMA 0x10 /* SB interrupt (SB DMA/SB F2 command) */ +#define ENVY24_CCS_NMI1_DSDMA 0x08 /* DS channel C DMA interrupt */ +#define ENVY24_CCS_NMI1_MIDI 0x04 /* MIDI 330h or [PCI_10]h+Ch write */ +#define ENVY24_CCS_NMI1_FM 0x01 /* FM data register write */ + +#define ENVY24_CCS_NMIDAT 0x06 /* NMI Data Register */ +#define ENVY24_CCS_NMIIDX 0x07 /* NMI Index Register */ +#define ENVY24_CCS_AC97IDX 0x08 /* Consumer AC'97 Index Register */ + +#define ENVY24_CCS_AC97CMD 0x09 /* Consumer AC'97 Command/Status Register */ +#define ENVY24_CCS_AC97CMD_COLD 0x80 /* Cold reset */ +#define ENVY24_CCS_AC97CMD_WARM 0x40 /* Warm reset */ +#define ENVY24_CCS_AC97CMD_WRCODEC 0x20 /* Write to AC'97 codec registers */ +#define ENVY24_CCS_AC97CMD_RDCODEC 0x10 /* Read from AC'97 codec registers */ +#define ENVY24_CCS_AC97CMD_READY 0x08 /* AC'97 codec ready status bit */ +#define ENVY24_CCS_AC97CMD_PVSR 0x02 /* VSR for Playback */ +#define ENVY24_CCS_AC97CMD_RVSR 0x01 /* VSR for Record */ + +#define ENVY24_CCS_AC97DAT 0x0a /* Consumer AC'97 Data Port Register */ +#define ENVY24_CCS_PMIDIDAT 0x0c /* Primary MIDI UART Data Register */ +#define ENVY24_CCS_PMIDICMD 0x0d /* Primary MIDI UART Command/Status Register */ + +#define ENVY24_CCS_NMI2 0x0e /* NMI Status Register 2 */ +#define ENVY24_CCS_NMI2_FMBANK 0x30 /* FM bank indicator */ +#define ENVY24_CCS_NMI2_FM0 0x10 /* FM bank 0 (388h/220h/228h) */ +#define ENVY24_CCS_NMI2_FM1 0x20 /* FM bank 1 (38ah/222h) */ +#define ENVY24_CCS_NMI2_PICIO 0x0f /* PIC I/O cycle */ +#define ENVY24_CCS_NMI2_PIC20W 0x01 /* 20h write */ +#define ENVY24_CCS_NMI2_PICA0W 0x02 /* a0h write */ +#define ENVY24_CCS_NMI2_PIC21W 0x05 /* 21h write */ +#define ENVY24_CCS_NMI2_PICA1W 0x06 /* a1h write */ +#define ENVY24_CCS_NMI2_PIC20R 0x09 /* 20h read */ +#define ENVY24_CCS_NMI2_PICA0R 0x0a /* a0h read */ +#define ENVY24_CCS_NMI2_PIC21R 0x0d /* 21h read */ +#define ENVY24_CCS_NMI2_PICA1R 0x0e /* a1h read */ + +#define ENVY24_CCS_JOY 0x0f /* Game port register */ + +#define ENVY24_CCS_I2CDEV 0x10 /* I2C Port Device Address Register */ +#define ENVY24_CCS_I2CDEV_ADDR 0xfe /* I2C device address */ +#define ENVY24_CCS_I2CDEV_ROM 0xa0 /* reserved for the external I2C E2PROM */ +#define ENVY24_CCS_I2CDEV_WR 0x01 /* write */ +#define ENVY24_CCS_I2CDEV_RD 0x00 /* read */ + +#define ENVY24_CCS_I2CADDR 0x11 /* I2C Port Byte Address Register */ +#define ENVY24_CCS_I2CDATA 0x12 /* I2C Port Read/Write Data Register */ + +#define ENVY24_CCS_I2CSTAT 0x13 /* I2C Port Control and Status Register */ +#define ENVY24_CCS_I2CSTAT_ROM 0x80 /* external E2PROM exists */ +#define ENVY24_CCS_I2CSTAT_BSY 0x01 /* I2C port read/write status busy */ + +#define ENVY24_CCS_CDMABASE 0x14 /* Consumer Record DMA Current/Base Address Register */ +#define ENVY24_CCS_CDMACNT 0x18 /* Consumer Record DMA Current/Base Count Register */ +#define ENVY24_CCS_SERR 0x1b /* PCI Configuration SERR# Shadow Register */ +#define ENVY24_CCS_SMIDIDAT 0x1c /* Secondary MIDI UART Data Register */ +#define ENVY24_CCS_SMIDICMD 0x1d /* Secondary MIDI UART Command/Status Register */ + +#define ENVY24_CCS_TIMER 0x1e /* Timer Register */ +#define ENVY24_CCS_TIMER_EN 0x8000 /* Timer count enable */ +#define ENVY24_CCS_TIMER_MASK 0x7fff /* Timer counter mask */ + +/* Controller Indexed Registers */ + +#define ENVY24_CCI_PTCHIGH 0x00 /* Playback Terminal Count Register (High Byte) */ +#define ENVY24_CCI_PTCLOW 0x01 /* Playback Terminal Count Register (Low Byte) */ + +#define ENVY24_CCI_PCTL 0x02 /* Playback Control Register */ +#define ENVY24_CCI_PCTL_TURBO 0x80 /* 4x up sampling in the host by software */ +#define ENVY24_CCI_PCTL_U8 0x10 /* 8 bits unsigned */ +#define ENVY24_CCI_PCTL_S16 0x00 /* 16 bits signed */ +#define ENVY24_CCI_PCTL_STEREO 0x08 /* stereo */ +#define ENVY24_CCI_PCTL_MONO 0x00 /* mono */ +#define ENVY24_CCI_PCTL_FLUSH 0x04 /* FIFO flush (sticky bit. Requires toggling) */ +#define ENVY24_CCI_PCTL_PAUSE 0x02 /* Pause */ +#define ENVY24_CCI_PCTL_ENABLE 0x01 /* Playback enable */ + +#define ENVY24_CCI_PLVOL 0x03 /* Playback Left Volume/Pan Register */ +#define ENVY24_CCI_PRVOL 0x04 /* Playback Right Volume/Pan Register */ +#define ENVY24_CCI_VOL_MASK 0x3f /* Volume value mask */ + +#define ENVY24_CCI_SOFTVOL 0x05 /* Soft Volume/Mute Control Register */ +#define ENVY24_CCI_PSRLOW 0x06 /* Playback Sampling Rate Register (Low Byte) */ +#define ENVY24_CCI_PSRMID 0x07 /* Playback Sampling Rate Register (Middle Byte) */ +#define ENVY24_CCI_PSRHIGH 0x08 /* Playback Sampling Rate Register (High Byte) */ +#define ENVY24_CCI_RTCHIGH 0x10 /* Record Terminal Count Register (High Byte) */ +#define ENVY24_CCI_RTCLOW 0x11 /* Record Terminal Count Register (Low Byte) */ + +#define ENVY24_CCI_RCTL 0x12 /* Record Control Register */ +#define ENVY24_CCI_RCTL_DRTN 0x80 /* Digital return enable */ +#define ENVY24_CCI_RCTL_U8 0x04 /* 8 bits unsigned */ +#define ENVY24_CCI_RCTL_S16 0x00 /* 16 bits signed */ +#define ENVY24_CCI_RCTL_STEREO 0x00 /* stereo */ +#define ENVY24_CCI_RCTL_MONO 0x02 /* mono */ +#define ENVY24_CCI_RCTL_ENABLE 0x01 /* Record enable */ + +#define ENVY24_CCI_GPIODAT 0x20 /* GPIO Data Register */ +#define ENVY24_CCI_GPIOMASK 0x21 /* GPIO Write Mask Register */ + +#define ENVY24_CCI_GPIOCTL 0x22 /* GPIO Direction Control Register */ +#define ENVY24_CCI_GPIO_OUT 1 /* output */ +#define ENVY24_CCI_GPIO_IN 0 /* input */ + +#define ENVY24_CCI_CPDWN 0x30 /* Consumer Section Power Down Register */ +#define ENVY24_CCI_CPDWN_XTAL 0x80 /* Crystal clock generation power down for XTAL_1 */ +#define ENVY24_CCI_CPDWN_GAME 0x40 /* Game port analog power down */ +#define ENVY24_CCI_CPDWN_I2C 0x10 /* I2C port clock */ +#define ENVY24_CCI_CPDWN_MIDI 0x08 /* MIDI clock */ +#define ENVY24_CCI_CPDWN_AC97 0x04 /* AC'97 clock */ +#define ENVY24_CCI_CPDWN_DS 0x02 /* DS Block clock */ +#define ENVY24_CCI_CPDWN_PCI 0x01 /* PCI clock for SB, DMA controller */ + +#define ENVY24_CCI_MTPDWN 0x31 /* Multi-Track Section Power Down Register */ +#define ENVY24_CCI_MTPDWN_XTAL 0x80 /* Crystal clock generation power down for XTAL_2 */ +#define ENVY24_CCI_MTPDWN_SPDIF 0x04 /* S/PDIF clock */ +#define ENVY24_CCI_MTPDWN_MIX 0x02 /* Professional digital mixer clock */ +#define ENVY24_CCI_MTPDWN_I2S 0x01 /* Multi-track I2S serial interface clock */ + +/* DDMA Registers */ + +#define ENVY24_DDMA_ADDR0 0x00 /* DMA Base and Current Address bit 0-7 */ +#define ENVY24_DDMA_ADDR8 0x01 /* DMA Base and Current Address bit 8-15 */ +#define ENVY24_DDMA_ADDR16 0x02 /* DMA Base and Current Address bit 16-23 */ +#define ENVY24_DDMA_ADDR24 0x03 /* DMA Base and Current Address bit 24-31 */ +#define ENVY24_DDMA_CNT0 0x04 /* DMA Base and Current Count 0-7 */ +#define ENVY24_DDMA_CNT8 0x05 /* DMA Base and Current Count 8-15 */ +#define ENVY24_DDMA_CNT16 0x06 /* (not supported) */ +#define ENVY24_DDMA_CMD 0x08 /* Status and Command */ +#define ENVY24_DDMA_MODE 0x0b /* Mode */ +#define ENVY24_DDMA_RESET 0x0c /* Master reset */ +#define ENVY24_DDMA_CHAN 0x0f /* Channel Mask */ + +/* Consumer Section DMA Channel Registers */ + +#define ENVY24_CS_INTMASK 0x00 /* DirectSound DMA Interrupt Mask Register */ +#define ENVY24_CS_INTSTAT 0x02 /* DirectSound DMA Interrupt Status Register */ +#define ENVY24_CS_CHDAT 0x04 /* Channel Data register */ + +#define ENVY24_CS_CHIDX 0x08 /* Channel Index Register */ +#define ENVY24_CS_CHIDX_NUM 0xf0 /* Channel number */ +#define ENVY24_CS_CHIDX_ADDR0 0x00 /* Buffer_0 DMA base address */ +#define ENVY24_CS_CHIDX_CNT0 0x01 /* Buffer_0 DMA base count */ +#define ENVY24_CS_CHIDX_ADDR1 0x02 /* Buffer_1 DMA base address */ +#define ENVY24_CS_CHIDX_CNT1 0x03 /* Buffer_1 DMA base count */ +#define ENVY24_CS_CHIDX_CTL 0x04 /* Channel Control and Status register */ +#define ENVY24_CS_CHIDX_RATE 0x05 /* Channel Sampling Rate */ +#define ENVY24_CS_CHIDX_VOL 0x06 /* Channel left and right volume/pan control */ +/* Channel Control and Status Register at Index 4h */ +#define ENVY24_CS_CTL_BUF 0x80 /* indicating that the current active buffer */ +#define ENVY24_CS_CTL_AUTO1 0x40 /* Buffer_1 auto init. enable */ +#define ENVY24_CS_CTL_AUTO0 0x20 /* Buffer_0 auto init. enable */ +#define ENVY24_CS_CTL_FLUSH 0x10 /* Flush FIFO */ +#define ENVY24_CS_CTL_STEREO 0x08 /* stereo(or mono) */ +#define ENVY24_CS_CTL_U8 0x04 /* 8-bit unsigned(or 16-bit signed) */ +#define ENVY24_CS_CTL_PAUSE 0x02 /* DMA request 1:pause */ +#define ENVY24_CS_CTL_START 0x01 /* DMA request 1: start, 0:stop */ +/* Consumer mode Left/Right Volume Register at Index 06h */ +#define ENVY24_CS_VOL_RIGHT 0x3f00 +#define ENVY24_CS_VOL_LEFT 0x003f + +/* Professional Multi-Track Control Registers */ + +#define ENVY24_MT_INT 0x00 /* DMA Interrupt Mask and Status Register */ +#define ENVY24_MT_INT_RMASK 0x80 /* Multi-track record interrupt mask */ +#define ENVY24_MT_INT_PMASK 0x40 /* Multi-track playback interrupt mask */ +#define ENVY24_MT_INT_RSTAT 0x02 /* Multi-track record interrupt status */ +#define ENVY24_MT_INT_PSTAT 0x01 /* Multi-track playback interrupt status */ + +#define ENVY24_MT_RATE 0x01 /* Sampling Rate Select Register */ +#define ENVY24_MT_RATE_SPDIF 0x10 /* S/PDIF input clock as the master */ +#define ENVY24_MT_RATE_48000 0x00 +#define ENVY24_MT_RATE_24000 0x01 +#define ENVY24_MT_RATE_12000 0x02 +#define ENVY24_MT_RATE_9600 0x03 +#define ENVY24_MT_RATE_32000 0x04 +#define ENVY24_MT_RATE_16000 0x05 +#define ENVY24_MT_RATE_8000 0x06 +#define ENVY24_MT_RATE_96000 0x07 +#define ENVY24_MT_RATE_64000 0x0f +#define ENVY24_MT_RATE_44100 0x08 +#define ENVY24_MT_RATE_22050 0x09 +#define ENVY24_MT_RATE_11025 0x0a +#define ENVY24_MT_RATE_88200 0x0b +#define ENVY24_MT_RATE_MASK 0x0f + +#define ENVY24_MT_I2S 0x02 /* I2S Data Format Register */ +#define ENVY24_MT_I2S_MLR128 0x08 /* MCLK/LRCLK ratio 128x(or 256x) */ +#define ENVY24_MT_I2S_SLR48 0x04 /* SCLK/LRCLK ratio 48bpf(or 64bpf) */ +#define ENVY24_MT_I2S_FORM 0x00 /* I2S data format */ + +#define ENVY24_MT_AC97IDX 0x04 /* Index Register for AC'97 Codecs */ + +#define ENVY24_MT_AC97CMD 0x05 /* Command and Status Register for AC'97 Codecs */ +#define ENVY24_MT_AC97CMD_CLD 0x80 /* Cold reset */ +#define ENVY24_MT_AC97CMD_WRM 0x40 /* Warm reset */ +#define ENVY24_MT_AC97CMD_WR 0x20 /* write to AC'97 codec register */ +#define ENVY24_MT_AC97CMD_RD 0x10 /* read AC'97 CODEC register */ +#define ENVY24_MT_AC97CMD_RDY 0x08 /* AC'97 codec ready status bit */ +#define ENVY24_MT_AC97CMD_ID 0x03 /* ID(0-3) for external AC 97 registers */ + +#define ENVY24_MT_AC97DLO 0x06 /* AC'97 codec register data low byte */ +#define ENVY24_MT_AC97DHI 0x07 /* AC'97 codec register data high byte */ +#define ENVY24_MT_PADDR 0x10 /* Playback DMA Current/Base Address Register */ +#define ENVY24_MT_PCNT 0x14 /* Playback DMA Current/Base Count Register */ +#define ENVY24_MT_PTERM 0x16 /* Playback Current/Base Terminal Count Register */ +#define ENVY24_MT_PCTL 0x18 /* Playback and Record Control Register */ +#define ENVY24_MT_PCTL_RSTART 0x04 /* 1: Record start; 0: Record stop */ +#define ENVY24_MT_PCTL_PAUSE 0x02 /* 1: Pause; 0: Resume */ +#define ENVY24_MT_PCTL_PSTART 0x01 /* 1: Playback start; 0: Playback stop */ + +#define ENVY24_MT_RADDR 0x20 /* Record DMA Current/Base Address Register */ +#define ENVY24_MT_RCNT 0x24 /* Record DMA Current/Base Count Register */ +#define ENVY24_MT_RTERM 0x26 /* Record Current/Base Terminal Count Register */ +#define ENVY24_MT_RCTL 0x28 /* Record Control Register */ +#define ENVY24_MT_RCTL_RSTART 0x01 /* 1: Record start; 0: Record stop */ + +#define ENVY24_MT_PSDOUT 0x30 /* Routing Control Register for Data to PSDOUT[0:3] */ +#define ENVY24_MT_SPDOUT 0x32 /* Routing Control Register for SPDOUT */ +#define ENVY24_MT_RECORD 0x34 /* Captured (Recorded) data Routing Selection Register */ + +#define BUS_SPACE_MAXADDR_ENVY24 0x0fffffff /* Address space beyond 256MB is not supported */ +#define BUS_SPACE_MAXSIZE_ENVY24 0x3fffc /* 64k x 4byte(1dword) */ + +#define ENVY24_MT_VOLUME 0x38 /* Left/Right Volume Control Data Register */ +#define ENVY24_MT_VOLUME_L 0x007f /* Left Volume Mask */ +#define ENVY24_MT_VOLUME_R 0x7f00 /* Right Volume Mask */ + +#define ENVY24_MT_VOLIDX 0x3a /* Volume Control Stream Index Register */ +#define ENVY24_MT_VOLRATE 0x3b /* Volume Control Rate Register */ +#define ENVY24_MT_MONAC97 0x3c /* Digital Mixer Monitor Routing Control Register */ +#define ENVY24_MT_PEAKIDX 0x3e /* Peak Meter Index Register */ +#define ENVY24_MT_PEAKDAT 0x3f /* Peak Meter Data Register */ + +/* -------------------------------------------------------------------- */ + +/* ENVY24 mixer channel defines */ +/* + ENVY24 mixer has original line matrix. So, general mixer command is not + able to use for this. If system has consumer AC'97 output, AC'97 line is + used as master mixer, and it is able to control. +*/ +#define ENVY24_CHAN_NUM 11 /* Play * 5 + Record * 5 + Mix * 1 */ + +#define ENVY24_CHAN_PLAY_DAC1 0 +#define ENVY24_CHAN_PLAY_DAC2 1 +#define ENVY24_CHAN_PLAY_DAC3 2 +#define ENVY24_CHAN_PLAY_DAC4 3 +#define ENVY24_CHAN_PLAY_SPDIF 4 +#define ENVY24_CHAN_REC_ADC1 5 +#define ENVY24_CHAN_REC_ADC2 6 +#define ENVY24_CHAN_REC_ADC3 7 +#define ENVY24_CHAN_REC_ADC4 8 +#define ENVY24_CHAN_REC_SPDIF 9 +#define ENVY24_CHAN_REC_MIX 10 + +#define ENVY24_MIX_MASK 0x3ff +#define ENVY24_MIX_REC_MASK 0x3e0 + +/* volume value constants */ +#define ENVY24_VOL_MAX 0 /* 0db(negate) */ +#define ENVY24_VOL_MIN 96 /* -144db(negate) */ +#define ENVY24_VOL_MUTE 127 /* mute */ + +/* -------------------------------------------------------------------- */ + +/* ENVY24 routing control defines */ +/* + ENVY24 has input->output data routing matrix switch. But original ENVY24 + matrix control is so complex. So, in this driver, matrix control is + defined 4 parameters. + + 1: output DAC channels (include S/PDIF output) + 2: output data classes + a. direct output from DMA + b. MIXER output which mixed the DMA outputs and input channels + (NOTICE: this class is able to set only DAC-1 and S/PDIF output) + c. direct input from ADC + d. direct input from S/PDIF + 3: input ADC channel selection(when 2:c. is selected) + 4: left/right reverse + + These parameters matrix is bit reduced from original ENVY24 matrix + pattern(ex. route different ADC input to one DAC). But almost case + this is enough to use. +*/ +#define ENVY24_ROUTE_DAC_1 0 +#define ENVY24_ROUTE_DAC_2 1 +#define ENVY24_ROUTE_DAC_3 2 +#define ENVY24_ROUTE_DAC_4 3 +#define ENVY24_ROUTE_DAC_SPDIF 4 + +#define ENVY24_ROUTE_CLASS_DMA 0 +#define ENVY24_ROUTE_CLASS_MIX 1 +#define ENVY24_ROUTE_CLASS_ADC 2 +#define ENVY24_ROUTE_CLASS_SPDIF 3 + +#define ENVY24_ROUTE_ADC_1 0 +#define ENVY24_ROUTE_ADC_2 1 +#define ENVY24_ROUTE_ADC_3 2 +#define ENVY24_ROUTE_ADC_4 3 + +#define ENVY24_ROUTE_NORMAL 0 +#define ENVY24_ROUTE_REVERSE 1 +#define ENVY24_ROUTE_LEFT 0 +#define ENVY24_ROUTE_RIGHT 1 + +/* -------------------------------------------------------------------- */ + +/* + These map values are refferd from ALSA sound driver. +*/ +/* ENVY24 configuration E2PROM map */ +#define ENVY24_E2PROM_SUBVENDOR 0x00 +#define ENVY24_E2PROM_SUBDEVICE 0x02 +#define ENVY24_E2PROM_SIZE 0x04 +#define ENVY24_E2PROM_VERSION 0x05 +#define ENVY24_E2PROM_SCFG 0x06 +#define ENVY24_E2PROM_ACL 0x07 +#define ENVY24_E2PROM_I2S 0x08 +#define ENVY24_E2PROM_SPDIF 0x09 +#define ENVY24_E2PROM_GPIOMASK 0x0a +#define ENVY24_E2PROM_GPIOSTATE 0x0b +#define ENVY24_E2PROM_GPIODIR 0x0c +#define ENVY24_E2PROM_AC97MAIN 0x0d +#define ENVY24_E2PROM_AC97PCM 0x0f +#define ENVY24_E2PROM_AC97REC 0x11 +#define ENVY24_E2PROM_AC97RECSRC 0x13 +#define ENVY24_E2PROM_DACID 0x14 +#define ENVY24_E2PROM_ADCID 0x18 +#define ENVY24_E2PROM_EXTRA 0x1c + +/* GPIO connect map of M-Audio Delta series */ +#define ENVY24_GPIO_CS84X4_PRO 0x01 +#define ENVY24_GPIO_CS8414_STATUS 0x02 +#define ENVY24_GPIO_CS84X4_CLK 0x04 +#define ENVY24_GPIO_CS84X4_DATA 0x08 +#define ENVY24_GPIO_AK4524_CDTI 0x10 /* this value is duplicated to input select */ +#define ENVY24_GPIO_AK4524_CCLK 0x20 +#define ENVY24_GPIO_AK4524_CS0 0x40 +#define ENVY24_GPIO_AK4524_CS1 0x80 + +/* M-Audio Delta series S/PDIF(CS84[01]4) control pin values */ +#define ENVY24_CS8404_PRO_RATE 0x18 +#define ENVY24_CS8404_PRO_RATE32 0x00 +#define ENVY24_CS8404_PRO_RATE441 0x10 +#define ENVY24_CS8404_PRO_RATE48 0x08 + +/* M-Audio Delta series parameter */ +#define ENVY24_DELTA_AK4524_CIF 0 + +/* end of file */ diff --git a/sys/dev/sound/pci/spicds.c b/sys/dev/sound/pci/spicds.c new file mode 100644 index 000000000000..2ca3501ddf04 --- /dev/null +++ b/sys/dev/sound/pci/spicds.c @@ -0,0 +1,247 @@ +/* + * Copyright (c) 2001 Katsurajima Naoto + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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, WHETHERIN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THEPOSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#include + +#include + +MALLOC_DEFINE(M_AK452X, "ak452x", "ak452x codec"); + +#define AK452X_NAMELEN 16 +struct ak452x_info { + device_t dev; + ak452x_ctrl ctrl; + void *devinfo; + int num; /* number of this device */ + unsigned int type; /* codec type */ + unsigned int cif; /* Controll data Interface Format (0/1) */ + unsigned int format; /* data format and master clock frequency */ + unsigned int dvc; /* De-emphasis and Volume Control */ + unsigned int left, right; + char name[AK452X_NAMELEN]; + void *lock; +}; + +static void +ak452x_wrbit(struct ak452x_info *codec, int bit) +{ + unsigned int cs, cdti; + if (codec->cif) + cs = 1; + else + cs = 0; + if (bit) + cdti = 1; + else + cdti = 0; + codec->ctrl(codec->devinfo, cs, 0, cdti); + DELAY(1); + codec->ctrl(codec->devinfo, cs, 1, cdti); + DELAY(1); + + return; +} + +static void +ak452x_wrcd(struct ak452x_info *codec, int reg, u_int8_t val) +{ + int mask; + +#if(0) + device_printf(codec->dev, "ak452x_wrcd(codec, 0x%02x, 0x%02x)\n", reg, val); +#endif + /* start */ + if (codec->cif) + codec->ctrl(codec->devinfo, 1, 1, 0); + else + codec->ctrl(codec->devinfo, 0, 1, 0); + DELAY(1); + /* chip address */ + ak452x_wrbit(codec, 1); + ak452x_wrbit(codec, 0); + /* write */ + ak452x_wrbit(codec, 1); + /* register address */ + for (mask = 0x10; mask != 0; mask >>= 1) + ak452x_wrbit(codec, reg & mask); + /* data */ + for (mask = 0x80; mask != 0; mask >>= 1) + ak452x_wrbit(codec, val & mask); + /* stop */ + DELAY(1); + if (codec->cif) { + codec->ctrl(codec->devinfo, 0, 1, 0); + DELAY(1); + codec->ctrl(codec->devinfo, 1, 1, 0); + } + else { + codec->ctrl(codec->devinfo, 1, 1, 0); + } + + return; +} + +struct ak452x_info * +ak452x_create(device_t dev, void *devinfo, int num, ak452x_ctrl ctrl) +{ + struct ak452x_info *codec; + +#if(0) + device_printf(dev, "ak452x_create(dev, devinfo, %d, ctrl)\n", num); +#endif + codec = (struct ak452x_info *)malloc(sizeof *codec, M_AK452X, M_NOWAIT); + if (codec == NULL) + return NULL; + + snprintf(codec->name, AK452X_NAMELEN, "%s:ak452x%d", device_get_nameunit(dev), num); + codec->lock = snd_mtxcreate(codec->name); + codec->dev = dev; + codec->ctrl = ctrl; + codec->devinfo = devinfo; + codec->num = num; + codec->type = AK452X_TYPE_4524; + codec->cif = 0; + codec->format = AK452X_FORMAT_I2S | AK452X_FORMAT_256FSN | AK452X_FORMAT_1X; + codec->dvc = AK452X_DVC_DEMOFF | AK452X_DVC_ZTM1024 | AK452X_DVC_ZCE; + + return codec; +} + +void +ak452x_destroy(struct ak452x_info *codec) +{ + snd_mtxfree(codec->lock); + free(codec, M_AK452X); +} + +void +ak452x_settype(struct ak452x_info *codec, unsigned int type) +{ + snd_mtxlock(codec->lock); + codec->type = type; + snd_mtxunlock(codec->lock); +} + +void +ak452x_setcif(struct ak452x_info *codec, unsigned int cif) +{ + snd_mtxlock(codec->lock); + codec->cif = cif; + snd_mtxunlock(codec->lock); +} + +void +ak452x_setformat(struct ak452x_info *codec, unsigned int format) +{ + snd_mtxlock(codec->lock); + codec->format = format; + snd_mtxunlock(codec->lock); +} + +void +ak452x_setdvc(struct ak452x_info *codec, unsigned int dvc) +{ + snd_mtxlock(codec->lock); + codec->type = dvc; + snd_mtxunlock(codec->lock); +} + +void +ak452x_init(struct ak452x_info *codec) +{ +#if(0) + device_printf(codec->dev, "ak452x_init(codec)\n"); +#endif + snd_mtxlock(codec->lock); + /* power off */ + ak452x_wrcd(codec, AK4524_POWER, 0); + /* set parameter */ + ak452x_wrcd(codec, AK4524_FORMAT, codec->format); + ak452x_wrcd(codec, AK4524_DVC, codec->dvc); + /* power on */ + ak452x_wrcd(codec, AK4524_POWER, AK452X_POWER_PWDA | AK452X_POWER_PWAD | AK452X_POWER_PWVR); + /* free reset register */ + ak452x_wrcd(codec, AK4524_RESET, AK452X_RESET_RSDA | AK452X_RESET_RSAD); + snd_mtxunlock(codec->lock); +} + +void +ak452x_reinit(struct ak452x_info *codec) +{ + snd_mtxlock(codec->lock); + /* reset */ + ak452x_wrcd(codec, AK4524_RESET, 0); + /* set parameter */ + ak452x_wrcd(codec, AK4524_FORMAT, codec->format); + ak452x_wrcd(codec, AK4524_DVC, codec->dvc); + /* free reset register */ + ak452x_wrcd(codec, AK4524_RESET, AK452X_RESET_RSDA | AK452X_RESET_RSAD); + snd_mtxunlock(codec->lock); +} + +void +ak452x_set(struct ak452x_info *codec, int dir, unsigned int left, unsigned int right) +{ +#if(0) + device_printf(codec->dev, "ak452x_set(codec, %d, %d, %d)\n", dir, left, right); +#endif + snd_mtxlock(codec->lock); + if (left >= 100) + left = 127; + else + left = left * 127 / 100; + if (right >= 100) + right = 127; + else + right = right * 127 / 100; + if (dir == PCMDIR_REC && codec->type == AK452X_TYPE_4524) { +#if(0) + device_printf(codec->dev, "ak452x_set(): AK4524(REC) %d/%d\n", left, right); +#endif + ak452x_wrcd(codec, AK4524_LIPGA, left); + ak452x_wrcd(codec, AK4524_RIPGA, right); + } + if (dir == PCMDIR_PLAY && codec->type == AK452X_TYPE_4524) { +#if(0) + device_printf(codec->dev, "ak452x_set(): AK4524(PLAY) %d/%d\n", left, right); +#endif + ak452x_wrcd(codec, AK4524_LOATT, left); + ak452x_wrcd(codec, AK4524_ROATT, right); + } + if (dir == PCMDIR_PLAY && codec->type == AK452X_TYPE_4528) { +#if(0) + device_printf(codec->dev, "ak452x_set(): AK4528(PLAY) %d/%d\n", left, right); +#endif + ak452x_wrcd(codec, AK4528_LOATT, left); + ak452x_wrcd(codec, AK4528_ROATT, right); + } + snd_mtxunlock(codec->lock); +} + +MODULE_DEPEND(snd_ak452x, snd_pcm, PCM_MINVER, PCM_PREFVER, PCM_MAXVER); +MODULE_VERSION(snd_ak452x, 1); diff --git a/sys/dev/sound/pci/spicds.h b/sys/dev/sound/pci/spicds.h new file mode 100644 index 000000000000..1df631fa70e8 --- /dev/null +++ b/sys/dev/sound/pci/spicds.h @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2001 Katsurajima Naoto + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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, WHETHERIN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THEPOSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +/* supported CODECs */ +#define AK452X_TYPE_4524 0 +#define AK452X_TYPE_4528 1 + +/* AK4524/AK4528 control registers */ +#define AK4524_POWER 0x00 +#define AK4528_POWER 0x00 +#define AK452X_POWER_PWDA 0x01 +#define AK452X_POWER_PWAD 0x02 +#define AK452X_POWER_PWVR 0x04 +#define AK4524_RESET 0x01 +#define AK4528_RESET 0x01 +#define AK452X_RESET_RSDA 0x01 +#define AK452X_RESET_RSAD 0x02 +#define AK4524_FORMAT 0x02 +#define AK4528_FORMAT 0x02 +#define AK452X_FORMAT_1X 0x00 +#define AK452X_FORMAT_2X 0x01 +#define AK452X_FORMAT_4X1 0x02 +#define AK452X_FORMAT_4X2 0x03 +#define AK452X_FORMAT_256FSN 0x00 +#define AK452X_FORMAT_512FSN 0x04 +#define AK452X_FORMAT_1024FSN 0x08 +#define AK452X_FORMAT_384FSN 0x10 +#define AK452X_FORMAT_768FSN 0x14 +#define AK452X_FORMAT_OM24IL16 0x00 +#define AK452X_FORMAT_OM24IL20 0x20 +#define AK452X_FORMAT_OM24IM24 0x40 +#define AK452X_FORMAT_I2S 0x60 +#define AK452X_FORMAT_OM24IL24 0x80 +#define AK4524_DVC 0x03 +#define AK452X_DVC_DEM441 0x00 +#define AK452X_DVC_DEMOFF 0x01 +#define AK452X_DVC_DEM48 0x02 +#define AK452X_DVC_DEM32 0x03 +#define AK452X_DVC_ZTM256 0x00 +#define AK452X_DVC_ZTM512 0x04 +#define AK452X_DVC_ZTM1024 0x08 +#define AK452X_DVC_ZTM2048 0x0c +#define AK452X_DVC_ZCE 0x10 +#define AK452X_DVC_HPFL 0x04 +#define AK452X_DVC_HPFR 0x08 +#define AK452X_DVC_SMUTE 0x80 +#define AK4524_LIPGA 0x04 +#define AK4524_RIPGA 0x05 +#define AK4524_LOATT 0x06 +#define AK4524_ROATT 0x07 +#define AK4528_LOATT 0x04 +#define AK4528_ROATT 0x05 + +struct ak452x_info; + +typedef void (*ak452x_ctrl)(void *, unsigned int, unsigned int, unsigned int); + +struct ak452x_info *ak452x_create(device_t dev, void *devinfo, int num, ak452x_ctrl); +void ak452x_destroy(struct ak452x_info *codec); +void ak452x_settype(struct ak452x_info *codec, unsigned int type); +void ak452x_setcif(struct ak452x_info *codec, unsigned int cif); +void ak452x_setformat(struct ak452x_info *codec, unsigned int format); +void ak452x_setdvc(struct ak452x_info *codec, unsigned int dvc); +void ak452x_init(struct ak452x_info *codec); +void ak452x_reinit(struct ak452x_info *codec); +void ak452x_set(struct ak452x_info *codec, int dir, unsigned int left, unsigned int right); diff --git a/sys/modules/sound/driver/ak452x/Makefile b/sys/modules/sound/driver/ak452x/Makefile new file mode 100644 index 000000000000..50a0e67abb30 --- /dev/null +++ b/sys/modules/sound/driver/ak452x/Makefile @@ -0,0 +1,8 @@ +# $FreeBSD$ + +.PATH: ${.CURDIR}/../../../../dev/sound/pci +KMOD = snd_ak452x +SRCS = device_if.h bus_if.h isa_if.h pci_if.h +SRCS += ak452x.c + +.include diff --git a/sys/modules/sound/driver/envy24/Makefile b/sys/modules/sound/driver/envy24/Makefile new file mode 100644 index 000000000000..37f9d48e6204 --- /dev/null +++ b/sys/modules/sound/driver/envy24/Makefile @@ -0,0 +1,8 @@ +# $FreeBSD$ + +.PATH: ${.CURDIR}/../../../../dev/sound/pci +KMOD = snd_envy24 +SRCS = device_if.h bus_if.h isa_if.h pci_if.h +SRCS += envy24.c + +.include