diff options
Diffstat (limited to 'sys/dev')
-rw-r--r-- | sys/dev/pci/envy.c | 957 | ||||
-rw-r--r-- | sys/dev/pci/envyreg.h | 178 | ||||
-rw-r--r-- | sys/dev/pci/envyvar.h | 88 | ||||
-rw-r--r-- | sys/dev/pci/files.pci | 7 |
4 files changed, 1229 insertions, 1 deletions
diff --git a/sys/dev/pci/envy.c b/sys/dev/pci/envy.c new file mode 100644 index 00000000000..9c68145566b --- /dev/null +++ b/sys/dev/pci/envy.c @@ -0,0 +1,957 @@ +/* + * Copyright (c) 2007 Alexandre Ratchov <alex@caoua.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/device.h> +#include <sys/ioctl.h> +#include <sys/audioio.h> +#include <sys/malloc.h> +#include <dev/pci/pcivar.h> +#include <dev/pci/pcidevs.h> +#include <dev/pci/envyvar.h> +#include <dev/pci/envyreg.h> +#include <dev/audio_if.h> +#include <machine/bus.h> + +#ifdef ENVY_DEBUG +#define DPRINTF(...) do { if (envydebug) printf(__VA_ARGS__); } while(0) +#define DPRINTFN(n, ...) do { if (envydebug > (n)) printf(__VA_ARGS__); } while(0) +int envydebug = 1; +#else +#define DPRINTF(...) do {} while(0) +#define DPRINTFN(n, ...) do {} while(0) +#endif +#define DEVNAME(sc) ((sc)->dev.dv_xname) + +int envymatch(struct device *, void *, void *); +void envyattach(struct device *, struct device *, void *); +int envydetach(struct device *, int); + +int envy_ccs_read(struct envy_softc *, int); +void envy_ccs_write(struct envy_softc *, int, int); +int envy_cci_read(struct envy_softc *, int); +void envy_cci_write(struct envy_softc *, int, int); +void envy_i2c_wait(struct envy_softc *); +int envy_i2c_read(struct envy_softc *, int, int); +void envy_i2c_write(struct envy_softc *, int, int, int); +int envy_gpio_read(struct envy_softc *); +void envy_gpio_write(struct envy_softc *, int); +void envy_eeprom_read(struct envy_softc *, unsigned char *); +void envy_reset(struct envy_softc *); +int envy_ak_read(struct envy_softc *, int, int); +void envy_ak_write(struct envy_softc *, int, int, int); +int envy_intr(void *); + +int envy_lineout_getsrc(struct envy_softc *, int); +void envy_lineout_setsrc(struct envy_softc *, int, int); +int envy_spdout_getsrc(struct envy_softc *, int); +void envy_spdout_setsrc(struct envy_softc *, int, int); +void envy_mon_getvol(struct envy_softc *, int, int *, int *); +void envy_mon_setvol(struct envy_softc *, int, int, int); + +int envy_open(void *, int); +void envy_close(void *); +void *envy_allocm(void *, int, size_t, int, int); +void envy_freem(void *, void *, int); +int envy_query_encoding(void *, struct audio_encoding *); +int envy_set_params(void *, int, int, struct audio_params *, + struct audio_params *); +int envy_round_blocksize(void *, int); +size_t envy_round_buffersize(void *, int, size_t); +int envy_trigger_output(void *, void *, void *, int, + void (*)(void *), void *, struct audio_params *); +int envy_trigger_input(void *, void *, void *, int, + void (*)(void *), void *, struct audio_params *); +int envy_halt_output(void *); +int envy_halt_input(void *); +int envy_getdev(void *, struct audio_device *); +int envy_query_devinfo(void *, struct mixer_devinfo *); +int envy_get_port(void *, struct mixer_ctrl *); +int envy_set_port(void *, struct mixer_ctrl *); +int envy_get_props(void *); + +struct cfattach envy_ca = { + sizeof(struct envy_softc), envymatch, envyattach, envydetach +}; + +struct cfdriver envy_cd = { + NULL, "envy", DV_DULL +}; + +struct audio_hw_if envy_hw_if = { + envy_open, /* open */ + envy_close, /* close */ + NULL, /* drain */ + envy_query_encoding, /* query_encoding */ + envy_set_params, /* set_params */ + envy_round_blocksize, /* round_blocksize */ + NULL, /* commit_settings */ + NULL, /* init_output */ + NULL, /* init_input */ + NULL, /* start_output */ + NULL, /* start_input */ + envy_halt_output, /* halt_output */ + envy_halt_input, /* halt_input */ + NULL, /* speaker_ctl */ + envy_getdev, /* getdev */ + NULL, /* setfd */ + envy_set_port, /* set_port */ + envy_get_port, /* get_port */ + envy_query_devinfo, /* query_devinfo */ + envy_allocm, /* malloc */ + envy_freem, /* free */ + envy_round_buffersize, /* round_buffersize */ + NULL, /* mappage */ + envy_get_props, /* get_props */ + envy_trigger_output, /* trigger_output */ + envy_trigger_input, /* trigger_input */ +}; + +/* + * correspondence between rates (in frames per second) + * and values of rate register + */ +struct { + int rate, reg; +} envy_rates[] = { + { 8000, 0x6}, { 9600, 0x3}, {11025, 0xa}, {12000, 2}, {16000, 5}, + {22050, 0x9}, {24000, 0x1}, {32000, 0x4}, {44100, 8}, {48000, 0}, + {64000, 0xf}, {88200, 0xb}, {96000, 0x7}, {-1, -1} +}; + +int +envy_ccs_read(struct envy_softc *sc, int reg) +{ + return bus_space_read_1(sc->ccs_iot, sc->ccs_ioh, reg); +} + +void +envy_ccs_write(struct envy_softc *sc, int reg, int val) +{ + bus_space_write_1(sc->ccs_iot, sc->ccs_ioh, reg, val); +} + +int +envy_cci_read(struct envy_softc *sc, int index) +{ + int val; + envy_ccs_write(sc, ENVY_CCI_INDEX, index); + val = envy_ccs_read(sc, ENVY_CCI_DATA); + return val; +} + +void +envy_cci_write(struct envy_softc *sc, int index, int data) +{ + envy_ccs_write(sc, ENVY_CCI_INDEX, index); + envy_ccs_write(sc, ENVY_CCI_DATA, data); +} + +void +envy_i2c_wait(struct envy_softc *sc) +{ + int timeout = 50, st; + + for (;;) { + st = envy_ccs_read(sc, ENVY_I2C_CTL); + if (!(st & ENVY_I2C_CTL_BUSY)) + break; + if (timeout == 0) { + printf("%s: i2c busy timeout\n", DEVNAME(sc)); + break; + } + delay(50); + timeout--; + } +} + +int +envy_i2c_read(struct envy_softc *sc, int dev, int addr) +{ + envy_i2c_wait(sc); + envy_ccs_write(sc, ENVY_I2C_ADDR, addr); + envy_i2c_wait(sc); + envy_ccs_write(sc, ENVY_I2C_DEV, dev << 1); + envy_i2c_wait(sc); + return envy_ccs_read(sc, ENVY_I2C_DATA); +} + +void +envy_i2c_write(struct envy_softc *sc, int dev, int addr, int data) +{ + if (dev == 0x50) { + printf("%s: writing on eeprom is evil...\n", DEVNAME(sc)); + return; + } + envy_i2c_wait(sc); + envy_ccs_write(sc, ENVY_I2C_ADDR, addr); + envy_i2c_wait(sc); + envy_ccs_write(sc, ENVY_I2C_DATA, data); + envy_i2c_wait(sc); + envy_ccs_write(sc, ENVY_I2C_DEV, (dev << 1) | 1); +} + +void +envy_eeprom_read(struct envy_softc *sc, unsigned char *eeprom) +{ + int i; + + for (i = 0; i < ENVY_EEPROM_MAXSZ; i++) { + eeprom[i] = envy_i2c_read(sc, ENVY_I2C_DEV_EEPROM, i); + } +#ifdef ENVY_DEBUG + printf("%s: eeprom: ", DEVNAME(sc)); + for (i = 0; i < ENVY_EEPROM_MAXSZ; i++) { + printf(" %02x", (unsigned)eeprom[i]); + } + printf("\n"); +#endif +} + +int +envy_ak_read(struct envy_softc *sc, int dev, int addr) { + return sc->ak[dev].reg[addr]; +} + +void +envy_ak_write(struct envy_softc *sc, int dev, int addr, int data) +{ + int bits, i, reg; + + sc->ak[dev].reg[addr] = data; + + reg = envy_cci_read(sc, ENVY_GPIO_DATA); + reg &= ~ENVY_GPIO_CSMASK; + reg |= ENVY_GPIO_CS(dev); + envy_cci_write(sc, ENVY_GPIO_DATA, reg); + delay(1); + + bits = 0xa000 | (addr << 8) | data; + for (i = 0; i < 16; i++) { + reg &= ~(ENVY_GPIO_CLK | ENVY_GPIO_DOUT); + reg |= (bits & 0x8000) ? ENVY_GPIO_DOUT : 0; + envy_cci_write(sc, ENVY_GPIO_DATA, reg); + delay(1); + + reg |= ENVY_GPIO_CLK; + envy_cci_write(sc, ENVY_GPIO_DATA, reg); + delay(1); + bits <<= 1; + } + + reg |= ENVY_GPIO_CSMASK; + envy_cci_write(sc, ENVY_GPIO_DATA, reg); + delay(1); +} + +void +envy_reset(struct envy_softc *sc) +{ + char eeprom[ENVY_EEPROM_MAXSZ]; + int dev; + + /* + * full reset + */ + envy_ccs_write(sc, ENVY_CTL, ENVY_CTL_RESET | ENVY_CTL_NATIVE); + delay(200); + envy_ccs_write(sc, ENVY_CTL, ENVY_CTL_NATIVE); + delay(200); + + /* + * read config from eprom and write it to registers + */ + envy_eeprom_read(sc, eeprom); + pci_conf_write(sc->pci_pc, sc->pci_tag, ENVY_CONF, + eeprom[ENVY_EEPROM_CONF] | + (eeprom[ENVY_EEPROM_ACLINK] << 8) | + (eeprom[ENVY_EEPROM_I2S] << 16) | + (eeprom[ENVY_EEPROM_SPDIF] << 24)); + envy_cci_write(sc, ENVY_GPIO_MASK, eeprom[ENVY_EEPROM_GPIOMASK]); + envy_cci_write(sc, ENVY_GPIO_DIR, eeprom[ENVY_EEPROM_GPIODIR]); + envy_cci_write(sc, ENVY_GPIO_DATA, eeprom[ENVY_EEPROM_GPIOST]); + + DPRINTF("%s: gpio_mask = %02x\n", DEVNAME(sc), + envy_cci_read(sc, ENVY_GPIO_MASK)); + DPRINTF("%s: gpio_dir = %02x\n", DEVNAME(sc), + envy_cci_read(sc, ENVY_GPIO_DIR)); + DPRINTF("%s: gpio_state = %02x\n", DEVNAME(sc), + envy_cci_read(sc, ENVY_GPIO_DATA)); + + /* + * reset ak4524 codecs + */ + for (dev = 0; dev < 4; dev++) { + envy_ak_write(sc, dev, AK_RST, 0x0); + delay(300); + envy_ak_write(sc, dev, AK_RST, AK_RST_AD | AK_RST_DA); + envy_ak_write(sc, dev, AK_FMT, AK_FMT_IIS24); + } + + /* + * clear all interrupts and unmask used ones + */ + envy_ccs_write(sc, ENVY_CCS_INTSTAT, 0xff); + envy_ccs_write(sc, ENVY_CCS_INTMASK, ~ENVY_CCS_INT_MT); +} + +int +envy_intr(void *self) +{ + struct envy_softc *sc = (struct envy_softc *)self; + int st; + + st = bus_space_read_1(sc->mt_iot, sc->mt_ioh, ENVY_MT_INTR); + if (!(st & (ENVY_MT_INTR_PACK | ENVY_MT_INTR_RACK))) { + return 0; + } + if (st & ENVY_MT_INTR_PACK) { + st = ENVY_MT_INTR_PACK; + bus_space_write_1(sc->mt_iot, sc->mt_ioh, ENVY_MT_INTR, st); + sc->ointr(sc->oarg); + } + if (st & ENVY_MT_INTR_RACK) { + st = ENVY_MT_INTR_RACK; + bus_space_write_1(sc->mt_iot, sc->mt_ioh, ENVY_MT_INTR, st); + sc->iintr(sc->iarg); + } + return 1; +} + +int +envy_lineout_getsrc(struct envy_softc *sc, int out) { + int reg, shift, src; + + reg = bus_space_read_2(sc->mt_iot, sc->mt_ioh, ENVY_MT_OUTSRC); + DPRINTF("%s: outsrc=%x\n", DEVNAME(sc), reg); + shift = (out & 1) ? (out & ~1) + 8 : out; + src = (reg >> shift) & 3; + if (src == ENVY_MT_OUTSRC_DMA) { + return ENVY_MIX_OUTSRC_DMA; + } else if (src == ENVY_MT_OUTSRC_MON) { + return ENVY_MIX_OUTSRC_MON; + } + reg = bus_space_read_4(sc->mt_iot, sc->mt_ioh, ENVY_MT_INSEL); + DPRINTF("%s: insel=%x\n", DEVNAME(sc), reg); + reg = (reg >> (out * 4)) & 0xf; + if (src == ENVY_MT_OUTSRC_LINE) + return ENVY_MIX_OUTSRC_LINEIN + (reg & 7); + else + return ENVY_MIX_OUTSRC_SPDIN + (reg >> 3); +} + +void +envy_lineout_setsrc(struct envy_softc *sc, int out, int src) { + int reg, shift, mask, sel; + + if (src < ENVY_MIX_OUTSRC_DMA) { + /* + * linein and spdin are used as output source so we + * must select the input source channel number + */ + if (src < ENVY_MIX_OUTSRC_SPDIN) + sel = src - ENVY_MIX_OUTSRC_LINEIN; + else + sel = (src - ENVY_MIX_OUTSRC_SPDIN) << 3; + + shift = out * ENVY_MT_INSEL_BITS; + mask = ENVY_MT_INSEL_MASK << shift; + reg = bus_space_read_4(sc->mt_iot, sc->mt_ioh, ENVY_MT_INSEL); + reg = (reg & ~mask) | (sel << shift); + bus_space_write_4(sc->mt_iot, sc->mt_ioh, ENVY_MT_INSEL, reg); + DPRINTF("%s: insel <- %x\n", DEVNAME(sc), reg); + } + + /* + * set the lineout route register + */ + if (src < ENVY_MIX_OUTSRC_SPDIN) { + sel = ENVY_MT_OUTSRC_LINE; + } else if (src < ENVY_MIX_OUTSRC_DMA) { + sel = ENVY_MT_OUTSRC_SPD; + } else if (src == ENVY_MIX_OUTSRC_DMA) { + sel = ENVY_MT_OUTSRC_DMA; + } else { + sel = ENVY_MT_OUTSRC_MON; + } + shift = (out & 1) ? (out & ~1) + 8 : out; + mask = ENVY_MT_INSEL_MASK << shift; + reg = bus_space_read_2(sc->mt_iot, sc->mt_ioh, ENVY_MT_OUTSRC); + reg = (reg & ~mask) | (sel << shift); + bus_space_write_2(sc->mt_iot, sc->mt_ioh, ENVY_MT_OUTSRC, reg); + DPRINTF("%s: outsrc <- %x\n", DEVNAME(sc), reg); +} + + +int +envy_spdout_getsrc(struct envy_softc *sc, int out) { + int reg, src, sel; + + reg = bus_space_read_2(sc->mt_iot, sc->mt_ioh, ENVY_MT_SPDROUTE); + DPRINTF("%s: spdroute=%x\n", DEVNAME(sc), reg); + src = (out == 0) ? reg : reg >> 2; + src &= ENVY_MT_SPDSRC_MASK; + if (src == ENVY_MT_SPDSRC_DMA) { + return ENVY_MIX_OUTSRC_DMA; + } else if (src == ENVY_MT_SPDSRC_MON) { + return ENVY_MIX_OUTSRC_MON; + } + + sel = (out == 0) ? reg >> 8 : reg >> 12; + sel &= ENVY_MT_SPDSEL_MASK; + if (src == ENVY_MT_SPDSRC_LINE) + return ENVY_MIX_OUTSRC_LINEIN + (sel & 7); + else + return ENVY_MIX_OUTSRC_SPDIN + (sel >> 3); +} + +void +envy_spdout_setsrc(struct envy_softc *sc, int out, int src) { + int reg, shift, mask, sel; + + reg = bus_space_read_2(sc->mt_iot, sc->mt_ioh, ENVY_MT_SPDROUTE); + if (src < ENVY_MIX_OUTSRC_DMA) { + /* + * linein and spdin are used as output source so we + * must select the input source channel number + */ + if (src < ENVY_MIX_OUTSRC_SPDIN) + sel = src - ENVY_MIX_OUTSRC_LINEIN; + else + sel = (src - ENVY_MIX_OUTSRC_SPDIN) << 3; + + shift = 8 + out * ENVY_MT_SPDSEL_BITS; + mask = ENVY_MT_SPDSEL_MASK << shift; + reg = (reg & ~mask) | (sel << shift); + } + + /* + * set the lineout route register + */ + if (src < ENVY_MIX_OUTSRC_SPDIN) { + sel = ENVY_MT_OUTSRC_LINE; + } else if (src < ENVY_MIX_OUTSRC_DMA) { + sel = ENVY_MT_OUTSRC_SPD; + } else if (src == ENVY_MIX_OUTSRC_DMA) { + sel = ENVY_MT_OUTSRC_DMA; + } else { + sel = ENVY_MT_OUTSRC_MON; + } + shift = out * 2; + mask = ENVY_MT_SPDSRC_MASK << shift; + reg = (reg & ~mask) | (sel << shift); + bus_space_write_2(sc->mt_iot, sc->mt_ioh, ENVY_MT_SPDROUTE, reg); + DPRINTF("%s: spdroute <- %x\n", DEVNAME(sc), reg); +} + +void +envy_mon_getvol(struct envy_softc *sc, int idx, int *l, int *r) { + int reg; + + bus_space_write_2(sc->mt_iot, sc->mt_ioh, ENVY_MT_MONIDX, idx); + reg = bus_space_read_2(sc->mt_iot, sc->mt_ioh, ENVY_MT_MONDATA); + *l = 0x7f - ((reg) & 0x7f); + *r = 0x7f - ((reg >> 8) & 0x7f); +} + +void +envy_mon_setvol(struct envy_softc *sc, int idx, int l, int r) { + int reg; + + bus_space_write_2(sc->mt_iot, sc->mt_ioh, ENVY_MT_MONIDX, idx); + reg = (0x7f - l) | ((0x7f - r) << 8); + DPRINTF("%s: mon=%d <- %d,%d\n", DEVNAME(sc), reg, l, r); + bus_space_write_2(sc->mt_iot, sc->mt_ioh, ENVY_MT_MONDATA, reg); +} + +int +envymatch(struct device *parent, void *match, void *aux) { + struct pci_attach_args *pa = (struct pci_attach_args *)aux; + + if (PCI_VENDOR(pa->pa_id) == PCI_VENDOR_ICENSEMBLE && + PCI_PRODUCT(pa->pa_id) == PCI_PRODUCT_ICENSEMBLE_ICE1712) { + return 1; + } + return 0; +} + +void +envyattach(struct device *parent, struct device *self, void *aux) +{ + struct envy_softc *sc = (struct envy_softc *)self; + struct pci_attach_args *pa = (struct pci_attach_args *)aux; + pci_intr_handle_t ih; + const char *intrstr; + + sc->pci_tag = pa->pa_tag; + sc->pci_pc = pa->pa_pc; + sc->pci_dmat = pa->pa_dmat; + sc->pci_ih = NULL; + sc->ibuf.addr = sc->obuf.addr = NULL; + sc->ccs_iosz = 0; + sc->mt_iosz = 0; + + if (pci_mapreg_map(pa, ENVY_CTL_BAR, PCI_MAPREG_TYPE_IO, 0, + &sc->ccs_iot, &sc->ccs_ioh, NULL, &sc->ccs_iosz, 0)) { + printf(": failed to map ctl i/o space\n"); + sc->ccs_iosz = 0; + return; + } + if (pci_mapreg_map(pa, ENVY_MT_BAR, PCI_MAPREG_TYPE_IO, 0, + &sc->mt_iot, &sc->mt_ioh, NULL, &sc->mt_iosz, 0)) { + printf(": failed to map mt i/o space\n"); + sc->mt_iosz = 0; + return; + } + if (pci_intr_map(pa, &ih)) { + printf(": can't map interrupt\n"); + } + intrstr = pci_intr_string(sc->pci_pc, ih); + sc->pci_ih = pci_intr_establish(sc->pci_pc, ih, IPL_AUDIO, + envy_intr, sc, sc->dev.dv_xname); + if (sc->pci_ih == NULL) { + printf(": can't establish interrupt"); + if (intrstr) + printf(" at %s", intrstr); + printf("\n"); + return; + } + printf(": %s\n", intrstr); + envy_reset(sc); + sc->audio = audio_attach_mi(&envy_hw_if, sc, &sc->dev); +} + +int +envydetach(struct device *self, int flags) +{ + struct envy_softc *sc = (struct envy_softc *)self; + + if (sc->pci_ih != NULL) { + pci_intr_disestablish(sc->pci_pc, sc->pci_ih); + sc->pci_ih = NULL; + } + if (sc->ccs_iosz) { + bus_space_unmap(sc->ccs_iot, sc->ccs_ioh, sc->ccs_iosz); + } + if (sc->mt_iosz) { + bus_space_unmap(sc->ccs_iot, sc->mt_ioh, sc->mt_iosz); + } + return 0; +} + +int +envy_open(void *self, int flags) +{ + return 0; +} + +void +envy_close(void *self) +{ +} + +void * +envy_allocm(void *self, int dir, size_t size, int type, int flags) +{ + struct envy_softc *sc = (struct envy_softc *)self; + int err, rsegs, basereg, wait; + struct envy_buf *buf; + + if (dir == AUMODE_RECORD) { + buf = &sc->ibuf; + basereg = ENVY_MT_RADDR; + } else { + buf = &sc->obuf; + basereg = ENVY_MT_PADDR; + } + if (buf->addr != NULL) { + DPRINTF("%s: multiple alloc, dir = %d\n", DEVNAME(sc), dir); + return NULL; + } + buf->size = size; + wait = (flags & M_NOWAIT) ? BUS_DMA_NOWAIT : BUS_DMA_WAITOK; + +#define ENVY_ALIGN 4 +#define ENVY_BOUNDARY 0 + + err = bus_dmamem_alloc(sc->pci_dmat, buf->size, ENVY_ALIGN, + ENVY_BOUNDARY, &buf->seg, 1, &rsegs, wait); + if (err) { + DPRINTF("%s: dmamem_alloc: failed %d\n", DEVNAME(sc), err); + goto err_ret; + } + + err = bus_dmamem_map(sc->pci_dmat, &buf->seg, rsegs, buf->size, + &buf->addr, wait | BUS_DMA_COHERENT); + if (err) { + DPRINTF("%s: dmamem_map: failed %d\n", DEVNAME(sc), err); + goto err_free; + } + + err = bus_dmamap_create(sc->pci_dmat, buf->size, 1, buf->size, 0, + wait, &buf->map); + if (err) { + DPRINTF("%s: dmamap_create: failed %d\n", DEVNAME(sc), err); + goto err_unmap; + } + + err = bus_dmamap_load(sc->pci_dmat, buf->map, buf->addr, + buf->size, NULL, wait); + if (err) { + DPRINTF("%s: dmamap_load: failed %d\n", DEVNAME(sc), err); + goto err_destroy; + } + bus_space_write_4(sc->mt_iot, sc->mt_ioh, basereg, buf->seg.ds_addr); + DPRINTF("%s: allocated %d bytes dir=%d, ka=%p, da=%p\n", + DEVNAME(sc), buf->size, dir, buf->addr, buf->seg.ds_addr); + return buf->addr; + + err_destroy: + bus_dmamap_destroy(sc->pci_dmat, buf->map); + err_unmap: + bus_dmamem_unmap(sc->pci_dmat, buf->addr, buf->size); + err_free: + bus_dmamem_free(sc->pci_dmat, &buf->seg, 1); + err_ret: + return NULL; +} + +void +envy_freem(void *self, void *addr, int type) +{ + struct envy_buf *buf; + struct envy_softc *sc = (struct envy_softc *)self; + int dir; + + if (sc->ibuf.addr == addr) { + buf = &sc->ibuf; + dir = AUMODE_RECORD; + } else if (sc->obuf.addr == addr) { + buf = &sc->obuf; + dir = AUMODE_PLAY; + } else { + DPRINTF("%s: no buf to free\n", DEVNAME(sc)); + return; + } + bus_dmamap_destroy(sc->pci_dmat, buf->map); + bus_dmamem_unmap(sc->pci_dmat, buf->addr, buf->size); + bus_dmamem_free(sc->pci_dmat, &buf->seg, 1); + buf->addr = NULL; + DPRINTF("%s: freed buffer (mode=%d)\n", DEVNAME(sc), dir); +} + +int +envy_query_encoding(void *self, struct audio_encoding *enc) +{ + if (enc->index == 0) { + strlcpy(enc->name, AudioEslinear_le, sizeof(enc->name)); + enc->encoding = AUDIO_ENCODING_SLINEAR_LE; + enc->precision = 32; + enc->flags = 0; + return 0; + } + return EINVAL; +} + +int +envy_set_params(void *self, int setmode, int usemode, + struct audio_params *p, struct audio_params *r) +{ + struct envy_softc *sc = (struct envy_softc *)self; + int i, rate, reg; + + if (setmode == 0) { + DPRINTF("%s: no params to set\n", DEVNAME(sc)); + return 0; + } + if (setmode == (AUMODE_PLAY | AUMODE_RECORD) && + p->sample_rate != r->sample_rate) { + DPRINTF("%s: play/rec rates mismatch\n", DEVNAME(sc)); + return EINVAL; + } + rate = (setmode & AUMODE_PLAY) ? p->sample_rate : r->sample_rate; + for (i = 0; envy_rates[i].rate < rate; i++) { + if (envy_rates[i].rate == -1) { + i--; + DPRINTF("%s: rate: %d -> %d\n", DEVNAME(sc), rate, i); + break; + } + } + reg = bus_space_read_1(sc->mt_iot, sc->mt_ioh, ENVY_MT_RATE); + reg &= ~ENVY_MT_RATEMASK; + reg |= envy_rates[i].reg; + bus_space_write_1(sc->mt_iot, sc->mt_ioh, ENVY_MT_RATE, reg); + if (setmode & AUMODE_PLAY) { + p->encoding = AUDIO_ENCODING_SLINEAR; + p->precision = 32; + p->channels = ENVY_PCHANS; + } + if (setmode & AUMODE_RECORD) { + r->encoding = AUDIO_ENCODING_SLINEAR; + r->precision = 32; + r->channels = ENVY_RCHANS; + } + return 0; +} + +int +envy_round_blocksize(void *self, int blksz) +{ + /* + * XXX: sizes depend on the mode but we don't have + * access to the mode here; So we use the greatest + * common divisor of input and output blocksizes, until + * upper layer is fixed + */ +#define ENVY_GCD (6 * 5 * 4) + return (blksz / ENVY_GCD) * ENVY_GCD; +} + +size_t +envy_round_buffersize(void *self, int dir, size_t bufsz) +{ + /* + * XXX: same remark as above + */ + return (bufsz / ENVY_GCD) * ENVY_GCD; +} + +int +envy_trigger_output(void *self, void *start, void *end, int blksz, + void (*intr)(void *), void *arg, struct audio_params *param) +{ + struct envy_softc *sc = (struct envy_softc *)self; + size_t bufsz; + int st; + + bufsz = end - start; + if (bufsz % (ENVY_PCHANS * 4) != 0) { + DPRINTF("%s: %d: bad output bufsz\n", DEVNAME(sc), bufsz); + return EINVAL; + } + if (blksz % (ENVY_PCHANS * 4) != 0) { + DPRINTF("%s: %d: bad output blksz\n", DEVNAME(sc), blksz); + return EINVAL; + } + bus_space_write_2(sc->mt_iot, sc->mt_ioh, + ENVY_MT_PBUFSZ, bufsz / 4 - 1); + bus_space_write_2(sc->mt_iot, sc->mt_ioh, + ENVY_MT_PBLKSZ, blksz / 4 - 1); + + sc->ointr = intr; + sc->oarg = arg; + + st = ENVY_MT_INTR_PACK; + bus_space_write_1(sc->mt_iot, sc->mt_ioh, ENVY_MT_INTR, st); + + st = bus_space_read_1(sc->mt_iot, sc->mt_ioh, ENVY_MT_CTL); + st |= ENVY_MT_CTL_PSTART; + bus_space_write_1(sc->mt_iot, sc->mt_ioh, ENVY_MT_CTL, st); + return 0; +} + +int +envy_trigger_input(void *self, void *start, void *end, int blksz, + void (*intr)(void *), void *arg, struct audio_params *param) +{ + struct envy_softc *sc = (struct envy_softc *)self; + size_t bufsz; + int st; + + bufsz = end - start; + if (bufsz % (ENVY_RCHANS * 4) != 0) { + DPRINTF("%s: %d: bad input bufsz\n", DEVNAME(sc), bufsz); + return EINVAL; + } + if (blksz % (ENVY_RCHANS * 4) != 0) { + DPRINTF("%s: %d: bad input blksz\n", DEVNAME(sc), blksz); + return EINVAL; + } + bus_space_write_2(sc->mt_iot, sc->mt_ioh, + ENVY_MT_RBUFSZ, bufsz / 4 - 1); + bus_space_write_2(sc->mt_iot, sc->mt_ioh, + ENVY_MT_RBLKSZ, blksz / 4 - 1); + + sc->iintr = intr; + sc->iarg = arg; + + st = ENVY_MT_INTR_RACK; + bus_space_write_1(sc->mt_iot, sc->mt_ioh, ENVY_MT_INTR, st); + + st = bus_space_read_1(sc->mt_iot, sc->mt_ioh, ENVY_MT_CTL); + st |= ENVY_MT_CTL_RSTART; + bus_space_write_1(sc->mt_iot, sc->mt_ioh, ENVY_MT_CTL, st); + return 0; +} + +int +envy_halt_output(void *self) +{ + struct envy_softc *sc = (struct envy_softc *)self; + int st; + + st = bus_space_read_1(sc->mt_iot, sc->mt_ioh, ENVY_MT_CTL); + st &= ~ENVY_MT_CTL_PSTART; + bus_space_write_1(sc->mt_iot, sc->mt_ioh, ENVY_MT_CTL, 0); + return 0; +} + +int +envy_halt_input(void *self) +{ + struct envy_softc *sc = (struct envy_softc *)self; + int st; + + st = bus_space_read_1(sc->mt_iot, sc->mt_ioh, ENVY_MT_CTL); + st &= ~ENVY_MT_CTL_RSTART; + bus_space_write_1(sc->mt_iot, sc->mt_ioh, ENVY_MT_CTL, 0); + return 0; +} + +int +envy_getdev(void *self, struct audio_device *dev) +{ + strlcpy(dev->name, "Envy24", MAX_AUDIO_DEV_LEN); + strlcpy(dev->version, "-", MAX_AUDIO_DEV_LEN); /* XXX eeprom version */ + strlcpy(dev->config, "envy", MAX_AUDIO_DEV_LEN); + return 0; +} + +int +envy_query_devinfo(void *self, struct mixer_devinfo *dev) +{ + int i, n, out; + char *classes[] = { + AudioCinputs, AudioCoutputs, "source", AudioCmonitor + }; + /* XXX: define AudioCsource */ + + dev->prev = dev->next = AUDIO_MIXER_LAST; + if (dev->index < ENVY_MIX_OUTSRC) { + dev->mixer_class = dev->index - ENVY_MIX_CLASSIN; + strlcpy(dev->label.name, + classes[dev->index - ENVY_MIX_CLASSIN], MAX_AUDIO_DEV_LEN); + return 0; + } + if (dev->index < ENVY_MIX_MONITOR) { + n = 0; + out = dev->index - ENVY_MIX_OUTSRC; + dev->type = AUDIO_MIXER_ENUM; + dev->mixer_class = ENVY_MIX_CLASSMIX; + for (i = 0; i < 10; i++) { + dev->un.e.member[n].ord = n; + snprintf(dev->un.e.member[n++].label.name, + MAX_AUDIO_DEV_LEN, "in%d", i); + } + dev->un.e.member[n].ord = n; + snprintf(dev->un.e.member[n++].label.name, + MAX_AUDIO_DEV_LEN, "play%d", out); + if (out < 2) { + dev->un.e.member[n].ord = n; + snprintf(dev->un.e.member[n++].label.name, + MAX_AUDIO_DEV_LEN, "mon%d", out); + } + snprintf(dev->label.name, MAX_AUDIO_DEV_LEN, "out%d", out); + dev->un.s.num_mem = n; + return 0; + } + if (dev->index < ENVY_MIX_INVAL) { + out = dev->index - ENVY_MIX_MONITOR; + dev->type = AUDIO_MIXER_VALUE; + dev->mixer_class = ENVY_MIX_CLASSMON; + dev->un.v.delta = 2; + dev->un.v.num_channels = 2; + snprintf(dev->label.name, MAX_AUDIO_DEV_LEN, + "%s%d", out < 10 ? "play" : "rec", out % 10); + strlcpy(dev->un.v.units.name, AudioNvolume, MAX_AUDIO_DEV_LEN); + return 0; + } + return ENXIO; +} + +int +envy_get_port(void *self, struct mixer_ctrl *ctl) +{ + struct envy_softc *sc = (struct envy_softc *)self; + int out, l, r; + + if (ctl->dev < ENVY_MIX_OUTSRC) { + return EINVAL; + } + if (ctl->dev < ENVY_MIX_OUTSRC + 8) { + out = ctl->dev - ENVY_MIX_OUTSRC; + ctl->un.ord = envy_lineout_getsrc(sc, out); + return 0; + } + if (ctl->dev < ENVY_MIX_MONITOR) { + out = ctl->dev - (ENVY_MIX_OUTSRC + 8); + ctl->un.ord = envy_spdout_getsrc(sc, out); + return 0; + } + if (ctl->dev < ENVY_MIX_INVAL) { + out = ctl->dev - ENVY_MIX_MONITOR; + envy_mon_getvol(sc, out, &l, &r); + ctl->un.value.num_channels = 2; + ctl->un.value.level[0] = 2 * l; + ctl->un.value.level[1] = 2 * r; + return 0; + } + return ENXIO; +} + +int +envy_set_port(void *self, struct mixer_ctrl *ctl) +{ + struct envy_softc *sc = (struct envy_softc *)self; + int out, maxsrc, l, r; + + if (ctl->dev < ENVY_MIX_OUTSRC) { + return EINVAL; + } + if (ctl->dev < ENVY_MIX_OUTSRC + 8) { + out = ctl->dev - ENVY_MIX_OUTSRC; + maxsrc = (out < 2 || out >= 8) ? 12 : 11; + if (ctl->un.ord < 0 || ctl->un.ord >= maxsrc) + return EINVAL; + envy_lineout_setsrc(sc, out, ctl->un.ord); + return 0; + } + if (ctl->dev < ENVY_MIX_MONITOR) { + out = ctl->dev - (ENVY_MIX_OUTSRC + 8); + if (ctl->un.ord < 0 || ctl->un.ord >= 12) + return EINVAL; + envy_spdout_setsrc(sc, out, ctl->un.ord); + return 0; + } + if (ctl->dev < ENVY_MIX_INVAL) { + out = ctl->dev - ENVY_MIX_MONITOR; + if (ctl->un.value.num_channels != 2) { + return EINVAL; + } + l = ctl->un.value.level[0] / 2; + r = ctl->un.value.level[1] / 2; + envy_mon_setvol(sc, out, l, r); + return 0; + } + return ENXIO; +} + +int +envy_get_props(void *self) +{ + return AUDIO_PROP_FULLDUPLEX; +} diff --git a/sys/dev/pci/envyreg.h b/sys/dev/pci/envyreg.h new file mode 100644 index 00000000000..4283d2822f8 --- /dev/null +++ b/sys/dev/pci/envyreg.h @@ -0,0 +1,178 @@ +/* + * Copyright (c) 2007 Alexandre Ratchov <alex@caoua.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#ifndef SYS_DEV_PCI_ENVYREG_H +#define SYS_DEV_PCI_ENVYREG_H + +/* + * BARs at PCI config space + */ +#define ENVY_CTL_BAR 0x10 +#define ENVY_MT_BAR 0x1c +#define ENVY_CONF 0x60 + +/* + * CCS "control" register + */ +#define ENVY_CTL 0x00 +#define ENVY_CTL_RESET 0x80 +#define ENVY_CTL_NATIVE 0x01 +#define ENVY_CCS_INTMASK 0x01 +#define ENVY_CCS_INT_MT 0x10 +#define ENVY_CCS_INT_MIDI1 0x80 +#define ENVY_CCS_INT_TMR 0x80 +#define ENVY_CCS_INT_MIDI0 0x80 +#define ENVY_CCS_INTSTAT 0x02 + +/* + * CCS registers to access indirect registers (CCI) + */ +#define ENVY_CCI_INDEX 0x3 +#define ENVY_CCI_DATA 0x4 + +/* + * CCS regisers to access iic bus + */ +#define ENVY_I2C_DEV 0x10 +#define ENVY_I2C_DEV_SHIFT 0x01 +#define ENVY_I2C_DEV_WRITE 0x01 +#define ENVY_I2C_DEV_EEPROM 0x50 +#define ENVY_I2C_ADDR 0x11 +#define ENVY_I2C_DATA 0x12 +#define ENVY_I2C_CTL 0x13 +#define ENVY_I2C_CTL_BUSY 0x1 + +/* + * CCI registers to access GPIO pins + */ +#define ENVY_GPIO_DATA 0x20 +#define ENVY_GPIO_MASK 0x21 +#define ENVY_GPIO_DIR 0x22 + +/* + * GPIO pin numbers + */ +#define ENVY_GPIO_CLK 0x2 +#define ENVY_GPIO_DOUT 0x8 +#define ENVY_GPIO_CSMASK 0x70 +#define ENVY_GPIO_CS(dev) ((dev) << 4) + +/* + * EEPROM bytes signification + */ +#define ENVY_EEPROM_CONF 6 +#define ENVY_EEPROM_ACLINK 7 +#define ENVY_EEPROM_I2S 8 +#define ENVY_EEPROM_SPDIF 9 +#define ENVY_EEPROM_GPIOMASK 10 +#define ENVY_EEPROM_GPIOST 11 +#define ENVY_EEPROM_GPIODIR 12 + +#define ENVY_EEPROM_MAXSZ 32 + +/* + * MT registers for play/record params + */ +#define ENVY_MT_INTR 0 +#define ENVY_MT_INTR_PACK 0x01 +#define ENVY_MT_INTR_RACK 0x02 +#define ENVY_MT_INTR_PMASK 0x40 +#define ENVY_MT_INTR_RMASK 0x80 +#define ENVY_MT_RATE 1 +#define ENVY_MT_RATEMASK 0x0f +#define ENVY_MT_PADDR 0x10 +#define ENVY_MT_PBUFSZ 0x14 +#define ENVY_MT_PBLKSZ 0x16 +#define ENVY_MT_CTL 0x18 +#define ENVY_MT_CTL_PSTART 0x01 +#define ENVY_MT_CTL_PPAUSE 0x02 +#define ENVY_MT_CTL_RSTART 0x04 +#define ENVY_MT_CTL_RPAUSE 0x08 +#define ENVY_MT_RADDR 0x20 +#define ENVY_MT_RBUFSZ 0x24 +#define ENVY_MT_RBLKSZ 0x26 + +/* + * MT registers for monitor gains + */ +#define ENVY_MT_MONDATA 0x38 +#define ENVY_MT_MONVAL_BITS 7 +#define ENVY_MT_MONVAL_MASK ((1 << ENVY_MT_MONVAL_BITS) - 1) +#define ENVY_MT_MONIDX 0x3a + +/* + * MT registers to access the digital mixer + */ +#define ENVY_MT_OUTSRC 0x30 +#define ENVY_MT_OUTSRC_DMA 0x00 +#define ENVY_MT_OUTSRC_MON 0x01 +#define ENVY_MT_OUTSRC_LINE 0x02 +#define ENVY_MT_OUTSRC_SPD 0x03 +#define ENVY_MT_OUTSRC_MASK 0x04 +#define ENVY_MT_SPDROUTE 0x32 +#define ENVY_MT_SPDSRC_DMA 0x00 +#define ENVY_MT_SPDSRC_MON 0x01 +#define ENVY_MT_SPDSRC_LINE 0x02 +#define ENVY_MT_SPDSRC_SPD 0x03 +#define ENVY_MT_SPDSRC_MASK 0x04 +#define ENVY_MT_SPDSEL_BITS 0x4 +#define ENVY_MT_SPDSEL_MASK ((1 << ENVY_MT_SPDSEL_BITS) - 1) +#define ENVY_MT_INSEL 0x34 +#define ENVY_MT_INSEL_BITS 0x4 +#define ENVY_MT_INSEL_MASK ((1 << ENVY_MT_INSEL_BITS) - 1) + +/* + * AK4524 control registers + */ +#define AK_PWR 0x00 +#define AK_PWR_DA 0x01 +#define AK_PWR_AD 0x02 +#define AK_PWR_VREF 0x04 +#define AK_RST 0x01 +#define AK_RST_DA 0x01 +#define AK_RST_AD 0x02 +#define AK_FMT 0x02 +#define AK_FMT_NORM 0 +#define AK_FMT_DBL 0x01 +#define AK_FMT_QUAD 0x02 +#define AK_FMT_QAUDFILT 0x04 +#define AK_FMT_256 0 +#define AK_FMT_512 0x04 +#define AK_FMT_1024 0x08 +#define AK_FMT_384 0x10 +#define AK_FMT_768 0x14 +#define AK_FMT_LSB16 0 +#define AK_FMT_LSB20 0x20 +#define AK_FMT_MSB24 0x40 +#define AK_FMT_IIS24 0x60 +#define AK_FMT_LSB24 0x80 +#define AK_DEEMVOL 0x03 +#define AK_MUTE 0x80 +#define AK_ADC_GAIN0 0x04 +#define AK_ADC_GAIN1 0x05 +#define AK_DAC_GAIN0 0x06 +#define AK_DAC_GAIN1 0x07 + +/* + * default formats + */ +#define ENVY_RFRAME_SIZE (4 * 12) +#define ENVY_PFRAME_SIZE (4 * 10) +#define ENVY_RBUF_SIZE (ENVY_RFRAME_SIZE * 0x1000) +#define ENVY_PBUF_SIZE (ENVY_PFRAME_SIZE * 0x1000) +#define ENVY_RCHANS 12 +#define ENVY_PCHANS 10 + +#endif /* !defined(SYS_DEV_PCI_ENVYREG_H) */ diff --git a/sys/dev/pci/envyvar.h b/sys/dev/pci/envyvar.h new file mode 100644 index 00000000000..4143cecad3c --- /dev/null +++ b/sys/dev/pci/envyvar.h @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2007 Alexandre Ratchov <alex@caoua.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef SYS_DEV_PCI_ENVYVAR_H +#define SYS_DEV_PCI_ENVYVAR_H + +#include <sys/types.h> +#include <sys/device.h> +#include <dev/audio_if.h> + +struct envy_buf { + bus_dma_segment_t seg; + bus_dmamap_t map; + caddr_t addr; + size_t size; +}; + +/* + * ak4524 codecs + */ +struct envy_ak { + unsigned char reg[8]; /* shadow for ak4524 registers */ +}; + +/* + * the envy24 have multiple components with their respective mixers + * depending on the configuration of the sound card, so we use + * "submixers" for them + */ +struct envy_submix { + int ndevs; + void (*devinfo)(struct mixer_devinfo *, int); + int (*set)(struct mixer_devinfo *, int); + int (*get)(struct mixer_devinfo *, int); +}; + +#define ENVY_SUBMIX_NMAX 0x10 + +struct envy_softc { + struct device dev; + struct device *audio; + struct envy_buf ibuf, obuf; + pcitag_t pci_tag; + pci_chipset_tag_t pci_pc; + pci_intr_handle_t *pci_ih; + bus_dma_tag_t pci_dmat; + bus_space_tag_t ccs_iot; + bus_space_handle_t ccs_ioh; + bus_size_t ccs_iosz; + bus_space_tag_t mt_iot; + bus_space_handle_t mt_ioh; + bus_size_t mt_iosz; + struct envy_ak ak[4]; + struct envy_submix submix[ENVY_SUBMIX_NMAX]; + int nsubmix; + void (*iintr)(void *); + void *iarg; + void (*ointr)(void *); + void *oarg; +}; + +#define ENVY_MIX_CLASSIN 0 +#define ENVY_MIX_CLASSOUT 1 +#define ENVY_MIX_CLASSMIX 2 +#define ENVY_MIX_CLASSMON 3 +#define ENVY_MIX_OUTSRC 4 +#define ENVY_MIX_MONITOR 14 +#define ENVY_MIX_INVAL 34 + +#define ENVY_MIX_OUTSRC_LINEIN 0 +#define ENVY_MIX_OUTSRC_SPDIN 8 +#define ENVY_MIX_OUTSRC_DMA 10 +#define ENVY_MIX_OUTSRC_MON 11 + +#endif /* !defined(SYS_DEV_PCI_ENVYVAR_H) */ diff --git a/sys/dev/pci/files.pci b/sys/dev/pci/files.pci index 9af8017417c..e1413ae838c 100644 --- a/sys/dev/pci/files.pci +++ b/sys/dev/pci/files.pci @@ -1,4 +1,4 @@ -# $OpenBSD: files.pci,v 1.245 2007/09/12 12:59:55 mglocker Exp $ +# $OpenBSD: files.pci,v 1.246 2007/10/28 13:42:31 ratchov Exp $ # $NetBSD: files.pci,v 1.20 1996/09/24 17:47:15 christos Exp $ # # Config file and device description for machine-independent PCI code. @@ -131,6 +131,11 @@ attach azalia at pci file dev/pci/azalia.c azalia file dev/pci/azalia_codec.c azalia +# VIA Envy24 (aka ICE1712) +device envy: audio, auconv, mulaw +attach envy at pci +file dev/pci/envy.c envy + # Creative Labs EMU10k1 (SBLive! series and PCI512) device emu: audio, auconv, mulaw, ac97 attach emu at pci |