/* * Copyright (c) 2006 Konstantin Dimitrov * 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 THE POSSIBILITY OF * SUCH DAMAGE. * */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include "mixer_if.h" MALLOC_DEFINE(M_ENVY24HT, "envy24ht", "envy24ht audio"); /* -------------------------------------------------------------------- */ struct sc_info; #define ENVY24HT_PLAY_CHNUM 8 #define ENVY24HT_REC_CHNUM 2 #define ENVY24HT_PLAY_BUFUNIT (4 /* byte/sample */ * 8 /* channel */) #define ENVY24HT_REC_BUFUNIT (4 /* byte/sample */ * 2 /* channel */) #define ENVY24HT_SAMPLE_NUM 4096 #define ENVY24HT_TIMEOUT 1000 #define ENVY24HT_DEFAULT_FORMAT (AFMT_STEREO | AFMT_S16_LE) #define ENVY24HT_NAMELEN 32 struct envy24ht_sample { volatile uint32_t buffer; }; typedef struct envy24ht_sample 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 */ uint32_t format; uint32_t speed; uint32_t blk; /* hw block size(dword) */ /* format conversion structure */ uint8_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, void *, int, int); void (*destroy)(void *); void (*init)(void *); void (*reinit)(void *); void (*setvolume)(void *, int, unsigned int, unsigned int); void (*setrate)(void *, int, int); }; /* system configuration information */ struct cfg_info { char *name; uint16_t subvendor, subdevice; uint8_t scfg, acl, i2s, spdif; uint32_t gpiomask, gpiostate, gpiodir; uint32_t cdti, cclk, cs; uint8_t cif, type, free; struct codec_entry *codec; }; /* device private data */ struct sc_info { device_t dev; struct mtx *lock; /* Control/Status registor */ struct resource *cs; int csid; /* MultiTrack registor */ struct resource *mt; int mtid; /* 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 */ uint32_t src; uint8_t left[ENVY24HT_CHAN_NUM]; uint8_t right[ENVY24HT_CHAN_NUM]; /* Play/Record DMA fifo */ sample32_t *pbuf; sample32_t *rbuf; uint32_t psize, rsize; /* DMA buffer size(byte) */ uint16_t blk[2]; /* transfer check blocksize(dword) */ bus_dmamap_t pmap, rmap; /* current status */ uint32_t speed; int run[2]; uint16_t intr[2]; struct pcmchan_caps caps[2]; /* channel info table */ unsigned chnum; struct sc_chinfo chan[11]; }; #define ENVY24_DEBUG #ifdef ENVY24_DEBUG static int envy24_debug = 1; #define DPRINTF(sc, ...) do { \ if (envy24_debug > 0) \ device_printf((sc)->dev, __VA_ARGS__); \ } while(0) #define DPRINTFN(sc, n, ...) do { \ if (envy24_debug > (n)) \ device_printf((sc)->dev, __VA_ARGS__); \ } while(0) #else #define DPRINTF(sc, ...) #define DPRINTFN(sc, n, ...) #endif /* -------------------------------------------------------------------- */ /* * prototypes */ /* DMA emulator */ static void envy24ht_p8u(struct sc_chinfo *); static void envy24ht_p16sl(struct sc_chinfo *); static void envy24ht_p32sl(struct sc_chinfo *); static void envy24ht_r16sl(struct sc_chinfo *); static void envy24ht_r32sl(struct sc_chinfo *); /* channel interface */ static void *envy24htchan_init(kobj_t, void *, struct snd_dbuf *, struct pcm_channel *, int); static int envy24htchan_setformat(kobj_t, void *, uint32_t); static int envy24htchan_setspeed(kobj_t, void *, uint32_t); static int envy24htchan_setblocksize(kobj_t, void *, uint32_t); static int envy24htchan_trigger(kobj_t, void *, int); static int envy24htchan_getptr(kobj_t, void *); static struct pcmchan_caps *envy24htchan_getcaps(kobj_t, void *); /* mixer interface */ static int envy24htmixer_init(struct snd_mixer *); static int envy24htmixer_reinit(struct snd_mixer *); static int envy24htmixer_uninit(struct snd_mixer *); static int envy24htmixer_set(struct snd_mixer *, unsigned, unsigned, unsigned); static uint32_t envy24htmixer_setrecsrc(struct snd_mixer *, uint32_t); /* SPI codec access interface */ static void *envy24ht_spi_create(device_t, void *, int, int); static void envy24ht_spi_destroy(void *); static void envy24ht_spi_init(void *); static void envy24ht_spi_reinit(void *); static void envy24ht_spi_setvolume(void *, int, unsigned int, unsigned int); /* -------------------------------------------------------------------- */ /* * system constant tables */ /* API -> hardware channel map */ static unsigned envy24ht_chanmap[ENVY24HT_CHAN_NUM] = { ENVY24HT_CHAN_PLAY_DAC1, /* 1 */ ENVY24HT_CHAN_PLAY_DAC2, /* 2 */ ENVY24HT_CHAN_PLAY_DAC3, /* 3 */ ENVY24HT_CHAN_PLAY_DAC4, /* 4 */ ENVY24HT_CHAN_PLAY_SPDIF, /* 0 */ ENVY24HT_CHAN_REC_MIX, /* 5 */ ENVY24HT_CHAN_REC_SPDIF, /* 6 */ ENVY24HT_CHAN_REC_ADC1, /* 7 */ ENVY24HT_CHAN_REC_ADC2, /* 8 */ ENVY24HT_CHAN_REC_ADC3, /* 9 */ ENVY24HT_CHAN_REC_ADC4, /* 10 */ }; /* mixer -> API channel map. see above */ static int envy24ht_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 uint32_t envy24ht_speed[] = { 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 9600, 8000, 0 }; /* known boards configuration */ static struct codec_entry spi_codec = { envy24ht_spi_create, envy24ht_spi_destroy, envy24ht_spi_init, envy24ht_spi_reinit, envy24ht_spi_setvolume, NULL, /* setrate */ }; static struct cfg_info cfg_table[] = { { "Envy24HT audio (Terratec Aureon 7.1 Space)", 0x153b, 0x1145, 0x0b, 0x80, 0xfc, 0xc3, 0x21efff, 0x7fffff, 0x5e1000, 0x40000, 0x80000, 0x1000, 0x00, 0x02, 0, &spi_codec, }, { "Envy24HT audio (Terratec Aureon 5.1 Sky)", 0x153b, 0x1147, 0x0a, 0x80, 0xfc, 0xc3, 0x21efff, 0x7fffff, 0x5e1000, 0x40000, 0x80000, 0x1000, 0x00, 0x02, 0, &spi_codec, }, { "Envy24HT audio (Terratec Aureon 7.1 Universe)", 0x153b, 0x1153, 0x0b, 0x80, 0xfc, 0xc3, 0x21efff, 0x7fffff, 0x5e1000, 0x40000, 0x80000, 0x1000, 0x00, 0x02, 0, &spi_codec, }, { "Envy24HT audio (AudioTrak Prodigy 7.1)", 0x4933, 0x4553, 0x0b, 0x80, 0xfc, 0xc3, 0x21efff, 0x7fffff, 0x5e1000, 0x40000, 0x80000, 0x1000, 0x00, 0x02, 0, &spi_codec, }, { "Envy24HT audio (Terratec PHASE 28)", 0x153b, 0x1149, 0x0b, 0x80, 0xfc, 0xc3, 0x21efff, 0x7fffff, 0x5e1000, 0x40000, 0x80000, 0x1000, 0x00, 0x02, 0, &spi_codec, }, { "Envy24HT-S audio (Terratec PHASE 22)", 0x153b, 0x1150, 0x10, 0x80, 0xf0, 0xc3, 0x7ffbc7, 0x7fffff, 0x438, 0x20, 0x10, 0x400, 0x00, 0x00, 0, &spi_codec, }, { "Envy24HT audio (AudioTrak Prodigy 7.1 LT)", 0x3132, 0x4154, 0x0b, 0x80, 0xfc, 0xc3, 0x7ff8ff, 0x7fffff, 0x700, 0x400, 0x200, 0x100, 0x00, 0x02, 0, &spi_codec, }, { "Envy24HT audio (M-Audio Revolution 7.1)", 0x1412, 0x3630, 0x43, 0x80, 0xf8, 0xc1, 0x3fff85, 0x72, 0x4000fa, 0x08, 0x02, 0x20, 0x00, 0x04, 0, &spi_codec, }, { "Envy24GT audio (M-Audio Revolution 5.1)", 0x1412, 0x3631, 0x42, 0x80, 0xf8, 0xc1, 0x3fff85, 0x72, 0x4000fa, 0x08, 0x02, 0x10, 0x00, 0x03, 0, &spi_codec, }, { "Envy24HT audio (M-Audio Audiophile 192)", 0x1412, 0x3632, 0x68, 0x80, 0xf8, 0xc3, 0x45, 0x4000b5, 0x7fffba, 0x08, 0x02, 0x10, 0x00, 0x03, 0, &spi_codec, }, { "Envy24HT audio (ONKYO SE-150PCI)", 0x160b, 0x0001, 0x68, 0x80, 0xf8, 0xc3, 0x45, 0x4000b5, 0x7fffba, 0x08, 0x02, 0x10, 0x00, 0x03, 0, &spi_codec, }, { "Envy24HT audio (Generic)", 0, 0, 0x0b, 0x80, 0xfc, 0xc3, 0x21efff, 0x7fffff, 0x5e1000, 0x40000, 0x80000, 0x1000, 0x00, 0x02, 0, &spi_codec, /* default codec routines */ } }; static uint32_t envy24ht_recfmt[] = { AFMT_STEREO | AFMT_S16_LE, AFMT_STEREO | AFMT_S32_LE, 0 }; static struct pcmchan_caps envy24ht_reccaps = {8000, 96000, envy24ht_recfmt, 0}; static uint32_t envy24ht_playfmt[] = { AFMT_STEREO | AFMT_U8, AFMT_STEREO | AFMT_S16_LE, AFMT_STEREO | AFMT_S32_LE, 0 }; static struct pcmchan_caps envy24ht_playcaps = {8000, 96000, envy24ht_playfmt, 0}; struct envy24ht_emldma { uint32_t format; void (*emldma)(struct sc_chinfo *); int unit; }; static struct envy24ht_emldma envy24ht_pemltab[] = { {AFMT_STEREO | AFMT_U8, envy24ht_p8u, 2}, {AFMT_STEREO | AFMT_S16_LE, envy24ht_p16sl, 4}, {AFMT_STEREO | AFMT_S32_LE, envy24ht_p32sl, 8}, {0, NULL, 0} }; static struct envy24ht_emldma envy24ht_remltab[] = { {AFMT_STEREO | AFMT_S16_LE, envy24ht_r16sl, 4}, {AFMT_STEREO | AFMT_S32_LE, envy24ht_r32sl, 8}, {0, NULL, 0} }; /* -------------------------------------------------------------------- */ /* common routines */ static uint32_t envy24ht_rdcs(struct sc_info *sc, int regno, int size) { switch (size) { case 1: return (bus_read_1(sc->cs, regno)); case 2: return (bus_read_2(sc->cs, regno)); case 4: return (bus_read_4(sc->cs, regno)); default: return (0xffffffff); } } static void envy24ht_wrcs(struct sc_info *sc, int regno, uint32_t data, int size) { switch (size) { case 1: bus_write_1(sc->cs, regno, data); break; case 2: bus_write_2(sc->cs, regno, data); break; case 4: bus_write_4(sc->cs, regno, data); break; } } static uint32_t envy24ht_rdmt(struct sc_info *sc, int regno, int size) { switch (size) { case 1: return (bus_read_1(sc->mt, regno)); case 2: return (bus_read_2(sc->mt, regno)); case 4: return (bus_read_4(sc->mt, regno)); default: return (0xffffffff); } } static void envy24ht_wrmt(struct sc_info *sc, int regno, uint32_t data, int size) { switch (size) { case 1: bus_write_1(sc->mt, regno, data); break; case 2: bus_write_2(sc->mt, regno, data); break; case 4: bus_write_4(sc->mt, regno, data); break; } } /* -------------------------------------------------------------------- */ /* I2C port/E2PROM access routines */ static int envy24ht_rdi2c(struct sc_info *sc, uint32_t dev, uint32_t addr) { uint32_t data; int i; DPRINTF(sc, "%s(sc, 0x%02x, 0x%02x)\n", __func__, dev, addr); for (i = 0; i < ENVY24HT_TIMEOUT; i++) { data = envy24ht_rdcs(sc, ENVY24HT_CCS_I2CSTAT, 1); if ((data & ENVY24HT_CCS_I2CSTAT_BSY) == 0) break; DELAY(32); /* 31.25kHz */ } if (i == ENVY24HT_TIMEOUT) { return (-1); } envy24ht_wrcs(sc, ENVY24HT_CCS_I2CADDR, addr, 1); envy24ht_wrcs(sc, ENVY24HT_CCS_I2CDEV, (dev & ENVY24HT_CCS_I2CDEV_ADDR) | ENVY24HT_CCS_I2CDEV_RD, 1); for (i = 0; i < ENVY24HT_TIMEOUT; i++) { data = envy24ht_rdcs(sc, ENVY24HT_CCS_I2CSTAT, 1); if ((data & ENVY24HT_CCS_I2CSTAT_BSY) == 0) break; DELAY(32); /* 31.25kHz */ } if (i == ENVY24HT_TIMEOUT) { return (-1); } data = envy24ht_rdcs(sc, ENVY24HT_CCS_I2CDATA, 1); DPRINTF(sc, "%s(): return 0x%x\n", __func__, data); return ((int)data); } #if 0 static int envy24ht_wri2c(struct sc_info *sc, uint32_t dev, uint32_t addr, uint32_t data) { uint32_t tmp; int i; DPRINTF(sc, "%s(sc, 0x%02x, 0x%02x)\n", __func__, dev, addr); for (i = 0; i < ENVY24HT_TIMEOUT; i++) { tmp = envy24ht_rdcs(sc, ENVY24HT_CCS_I2CSTAT, 1); if ((tmp & ENVY24HT_CCS_I2CSTAT_BSY) == 0) break; DELAY(32); /* 31.25kHz */ } if (i == ENVY24HT_TIMEOUT) { return (-1); } envy24ht_wrcs(sc, ENVY24HT_CCS_I2CADDR, addr, 1); envy24ht_wrcs(sc, ENVY24HT_CCS_I2CDATA, data, 1); envy24ht_wrcs(sc, ENVY24HT_CCS_I2CDEV, (dev & ENVY24HT_CCS_I2CDEV_ADDR) | ENVY24HT_CCS_I2CDEV_WR, 1); for (i = 0; i < ENVY24HT_TIMEOUT; i++) { data = envy24ht_rdcs(sc, ENVY24HT_CCS_I2CSTAT, 1); if ((data & ENVY24HT_CCS_I2CSTAT_BSY) == 0) break; DELAY(32); /* 31.25kHz */ } if (i == ENVY24HT_TIMEOUT) { return (-1); } return (0); } #endif static int envy24ht_rdrom(struct sc_info *sc, uint32_t addr) { uint32_t data; DPRINTF(sc, "%s(sc, 0x%02x)\n", __func__, addr); data = envy24ht_rdcs(sc, ENVY24HT_CCS_I2CSTAT, 1); if ((data & ENVY24HT_CCS_I2CSTAT_ROM) == 0) { DPRINTF(sc, "%s(): E2PROM not presented\n", __func__); return (-1); } return (envy24ht_rdi2c(sc, ENVY24HT_CCS_I2CDEV_ROM, addr)); } static struct cfg_info * envy24ht_rom2cfg(struct sc_info *sc) { struct cfg_info *buff; int size; int i; DPRINTF(sc, "%s\n", __func__); buff = malloc(sizeof(*buff), M_ENVY24HT, M_NOWAIT); if (buff == NULL) { DPRINTF(sc, "%s: malloc()\n", __func__); return (NULL); } buff->free = 1; size = envy24ht_rdrom(sc, ENVY24HT_E2PROM_SIZE); if ((size < ENVY24HT_E2PROM_GPIOSTATE + 3) || (size == 0x78)) { DPRINTF(sc, "%s: ENVY24HT_E2PROM_SIZE-->%d\n", __func__, size); /* no valid e2prom, using default values */ buff->subvendor = envy24ht_rdrom(sc, ENVY24HT_E2PROM_SUBVENDOR) << 8; buff->subvendor += envy24ht_rdrom(sc, ENVY24HT_E2PROM_SUBVENDOR + 1); buff->subdevice = envy24ht_rdrom(sc, ENVY24HT_E2PROM_SUBDEVICE) << 8; buff->subdevice += envy24ht_rdrom(sc, ENVY24HT_E2PROM_SUBDEVICE + 1); buff->scfg = 0x0b; buff->acl = 0x80; buff->i2s = 0xfc; buff->spdif = 0xc3; buff->gpiomask = 0x21efff; buff->gpiostate = 0x7fffff; buff->gpiodir = 0x5e1000; buff->cdti = 0x40000; buff->cclk = 0x80000; buff->cs = 0x1000; buff->cif = 0x00; buff->type = 0x02; } else { buff->subvendor = envy24ht_rdrom(sc, ENVY24HT_E2PROM_SUBVENDOR) << 8; buff->subvendor += envy24ht_rdrom(sc, ENVY24HT_E2PROM_SUBVENDOR + 1); buff->subdevice = envy24ht_rdrom(sc, ENVY24HT_E2PROM_SUBDEVICE) << 8; buff->subdevice += envy24ht_rdrom(sc, ENVY24HT_E2PROM_SUBDEVICE + 1); buff->scfg = envy24ht_rdrom(sc, ENVY24HT_E2PROM_SCFG); buff->acl = envy24ht_rdrom(sc, ENVY24HT_E2PROM_ACL); buff->i2s = envy24ht_rdrom(sc, ENVY24HT_E2PROM_I2S); buff->spdif = envy24ht_rdrom(sc, ENVY24HT_E2PROM_SPDIF); buff->gpiomask = envy24ht_rdrom(sc, ENVY24HT_E2PROM_GPIOMASK) | envy24ht_rdrom(sc, ENVY24HT_E2PROM_GPIOMASK + 1) << 8 | envy24ht_rdrom(sc, ENVY24HT_E2PROM_GPIOMASK + 2) << 16; buff->gpiostate = envy24ht_rdrom(sc, ENVY24HT_E2PROM_GPIOSTATE) | envy24ht_rdrom(sc, ENVY24HT_E2PROM_GPIOSTATE + 1) << 8 | envy24ht_rdrom(sc, ENVY24HT_E2PROM_GPIOSTATE + 2) << 16; buff->gpiodir = envy24ht_rdrom(sc, ENVY24HT_E2PROM_GPIODIR) | envy24ht_rdrom(sc, ENVY24HT_E2PROM_GPIODIR + 1) << 8 | envy24ht_rdrom(sc, ENVY24HT_E2PROM_GPIODIR + 2) << 16; } for (i = 0; i < sizeof(cfg_table)/sizeof(cfg_table[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 envy24ht_cfgfree(struct cfg_info *cfg) { if (cfg == NULL) return; if (cfg->free) free(cfg, M_ENVY24HT); return; } /* -------------------------------------------------------------------- */ /* AC'97 codec access routines */ #if 0 static int envy24ht_coldcd(struct sc_info *sc) { uint32_t data; int i; DPRINTF(sc, "%s()\n", __func__); envy24ht_wrmt(sc, ENVY24HT_MT_AC97CMD, ENVY24HT_MT_AC97CMD_CLD, 1); DELAY(10); envy24ht_wrmt(sc, ENVY24HT_MT_AC97CMD, 0, 1); DELAY(1000); for (i = 0; i < ENVY24HT_TIMEOUT; i++) { data = envy24ht_rdmt(sc, ENVY24HT_MT_AC97CMD, 1); if (data & ENVY24HT_MT_AC97CMD_RDY) { return (0); } } return (-1); } static int envy24ht_slavecd(struct sc_info *sc) { uint32_t data; int i; DPRINTF(sc, "%s()\n", __func__); envy24ht_wrmt(sc, ENVY24HT_MT_AC97CMD, ENVY24HT_MT_AC97CMD_CLD | ENVY24HT_MT_AC97CMD_WRM, 1); DELAY(10); envy24ht_wrmt(sc, ENVY24HT_MT_AC97CMD, 0, 1); DELAY(1000); for (i = 0; i < ENVY24HT_TIMEOUT; i++) { data = envy24ht_rdmt(sc, ENVY24HT_MT_AC97CMD, 1); if (data & ENVY24HT_MT_AC97CMD_RDY) { return (0); } } return (-1); } static int envy24ht_rdcd(kobj_t obj, void *devinfo, int regno) { struct sc_info *sc = (struct sc_info *)devinfo; uint32_t data; int i; DPRINTF(sc, "%s(obj, sc, 0x%02x)\n", __func__, regno); envy24ht_wrmt(sc, ENVY24HT_MT_AC97IDX, (uint32_t)regno, 1); envy24ht_wrmt(sc, ENVY24HT_MT_AC97CMD, ENVY24HT_MT_AC97CMD_RD, 1); for (i = 0; i < ENVY24HT_TIMEOUT; i++) { data = envy24ht_rdmt(sc, ENVY24HT_MT_AC97CMD, 1); if ((data & ENVY24HT_MT_AC97CMD_RD) == 0) break; } data = envy24ht_rdmt(sc, ENVY24HT_MT_AC97DLO, 2); DPRINTF(sc, "%s(): return 0x%x\n", __func__, data); return ((int)data); } static int envy24ht_wrcd(kobj_t obj, void *devinfo, int regno, uint16_t data) { struct sc_info *sc = (struct sc_info *)devinfo; uint32_t cmd; int i; DPRINTF(sc, "%s(obj, sc, 0x%02x, 0x%04x)\n", __func__, regno, data); envy24ht_wrmt(sc, ENVY24HT_MT_AC97IDX, (uint32_t)regno, 1); envy24ht_wrmt(sc, ENVY24HT_MT_AC97DLO, (uint32_t)data, 2); envy24ht_wrmt(sc, ENVY24HT_MT_AC97CMD, ENVY24HT_MT_AC97CMD_WR, 1); for (i = 0; i < ENVY24HT_TIMEOUT; i++) { cmd = envy24ht_rdmt(sc, ENVY24HT_MT_AC97CMD, 1); if ((cmd & ENVY24HT_MT_AC97CMD_WR) == 0) break; } return (0); } static kobj_method_t envy24ht_ac97_methods[] = { KOBJMETHOD(ac97_read, envy24ht_rdcd), KOBJMETHOD(ac97_write, envy24ht_wrcd), {0, 0} }; AC97_DECLARE(envy24ht_ac97); #endif /* -------------------------------------------------------------------- */ /* GPIO access routines */ static uint32_t envy24ht_gpiord(struct sc_info *sc) { if (sc->cfg->subvendor == 0x153b && sc->cfg->subdevice == 0x1150) return (envy24ht_rdcs(sc, ENVY24HT_CCS_GPIO_LDATA, 2)); else return (envy24ht_rdcs(sc, ENVY24HT_CCS_GPIO_HDATA, 1) << 16 | envy24ht_rdcs(sc, ENVY24HT_CCS_GPIO_LDATA, 2)); } static void envy24ht_gpiowr(struct sc_info *sc, uint32_t data) { DPRINTF(sc, "%s(sc, 0x%02x)\n", __func__, data & 0x7FFFFF); envy24ht_wrcs(sc, ENVY24HT_CCS_GPIO_LDATA, data, 2); if (sc->cfg->subdevice != 0x1150) envy24ht_wrcs(sc, ENVY24HT_CCS_GPIO_HDATA, data >> 16, 1); } #if 0 static uint32_t envy24ht_gpiogetmask(struct sc_info *sc) { return (envy24ht_rdcs(sc, ENVY24HT_CCS_GPIO_HMASK, 1) << 16 | envy24ht_rdcs(sc, ENVY24HT_CCS_GPIO_LMASK, 2)); } #endif static void envy24ht_gpiosetmask(struct sc_info *sc, uint32_t mask) { envy24ht_wrcs(sc, ENVY24HT_CCS_GPIO_LMASK, mask, 2); if (sc->cfg->subdevice != 0x1150) envy24ht_wrcs(sc, ENVY24HT_CCS_GPIO_HMASK, mask >> 16, 1); } #if 0 static uint32_t envy24ht_gpiogetdir(struct sc_info *sc) { return (envy24ht_rdcs(sc, ENVY24HT_CCS_GPIO_CTLDIR, 4)); } #endif static void envy24ht_gpiosetdir(struct sc_info *sc, uint32_t dir) { if (sc->cfg->subvendor == 0x153b && sc->cfg->subdevice == 0x1150) envy24ht_wrcs(sc, ENVY24HT_CCS_GPIO_CTLDIR, dir, 2); else envy24ht_wrcs(sc, ENVY24HT_CCS_GPIO_CTLDIR, dir, 4); } /* -------------------------------------------------------------------- */ /* SPI codec access interface routine */ struct envy24ht_spi_codec { struct spicds_info *info; struct sc_info *parent; int dir; int num; int cs, cclk, cdti; }; static void envy24ht_spi_ctl(void *codec, unsigned int cs, unsigned int cclk, unsigned int cdti) { uint32_t data = 0; struct envy24ht_spi_codec *ptr = codec; DPRINTF(ptr->parent, "--> %d, %d, %d\n", cs, cclk, cdti); data = envy24ht_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; envy24ht_gpiowr(ptr->parent, data); } static void * envy24ht_spi_create(device_t dev, void *info, int dir, int num) { struct sc_info *sc = info; struct envy24ht_spi_codec *buff; DPRINTF(sc, "%s(dev, sc, %d, %d)\n", __func__, dir, num); buff = malloc(sizeof(*buff), M_ENVY24HT, M_NOWAIT); if (buff == NULL) return (NULL); if (dir == PCMDIR_REC && sc->adc[num] != NULL) buff->info = ((struct envy24ht_spi_codec *)sc->adc[num])->info; else if (dir == PCMDIR_PLAY && sc->dac[num] != NULL) buff->info = ((struct envy24ht_spi_codec *)sc->dac[num])->info; else buff->info = spicds_create(dev, buff, num, envy24ht_spi_ctl); if (buff->info == NULL) { free(buff, M_ENVY24HT); return (NULL); } buff->parent = sc; buff->dir = dir; buff->num = num; return (void *)buff; } static void envy24ht_spi_destroy(void *codec) { struct envy24ht_spi_codec *ptr = codec; if (ptr == NULL) return; DPRINTF(ptr->parent, "%s()\n", __func__); if (ptr->dir == PCMDIR_PLAY) { if (ptr->parent->dac[ptr->num] != NULL) spicds_destroy(ptr->info); } else { if (ptr->parent->adc[ptr->num] != NULL) spicds_destroy(ptr->info); } free(codec, M_ENVY24HT); } static void envy24ht_spi_init(void *codec) { struct envy24ht_spi_codec *ptr = codec; if (ptr == NULL) return; DPRINTF(ptr->parent, "%s()\n", __func__); ptr->cs = ptr->parent->cfg->cs; ptr->cclk = ptr->parent->cfg->cclk; ptr->cdti = ptr->parent->cfg->cdti; spicds_settype(ptr->info, ptr->parent->cfg->type); spicds_setcif(ptr->info, ptr->parent->cfg->cif); if (ptr->parent->cfg->type == SPICDS_TYPE_AK4524 || \ ptr->parent->cfg->type == SPICDS_TYPE_AK4528) { spicds_setformat(ptr->info, AK452X_FORMAT_I2S | AK452X_FORMAT_256FSN | AK452X_FORMAT_1X); spicds_setdvc(ptr->info, AK452X_DVC_DEMOFF); } /* for the time being, init only first codec */ if (ptr->num == 0) spicds_init(ptr->info); } static void envy24ht_spi_reinit(void *codec) { struct envy24ht_spi_codec *ptr = codec; if (ptr == NULL) return; DPRINTF(ptr->parent, "%s()\n", __func__); spicds_reinit(ptr->info); } static void envy24ht_spi_setvolume(void *codec, int dir, unsigned int left, unsigned int right) { struct envy24ht_spi_codec *ptr = codec; if (ptr == NULL) return; DPRINTF(ptr->parent, "%s()\n", __func__); spicds_set(ptr->info, dir, left, right); } /* -------------------------------------------------------------------- */ /* hardware access routeines */ static struct { uint32_t speed; uint32_t code; } envy24ht_speedtab[] = { {48000, ENVY24HT_MT_RATE_48000}, {24000, ENVY24HT_MT_RATE_24000}, {12000, ENVY24HT_MT_RATE_12000}, {9600, ENVY24HT_MT_RATE_9600}, {32000, ENVY24HT_MT_RATE_32000}, {16000, ENVY24HT_MT_RATE_16000}, {8000, ENVY24HT_MT_RATE_8000}, {96000, ENVY24HT_MT_RATE_96000}, {64000, ENVY24HT_MT_RATE_64000}, {44100, ENVY24HT_MT_RATE_44100}, {22050, ENVY24HT_MT_RATE_22050}, {11025, ENVY24HT_MT_RATE_11025}, {88200, ENVY24HT_MT_RATE_88200}, {0, 0x10} }; static int envy24ht_setspeed(struct sc_info *sc, uint32_t speed) { uint32_t code; int i = 0; #if(0) DPRINTF(sc, "%s(sc, %d)\n", __func__, speed); if (speed == 0) { code = ENVY24HT_MT_RATE_SPDIF; /* external master clock */ envy24ht_slavecd(sc); } else { #endif for (i = 0; envy24ht_speedtab[i].speed != 0; i++) { if (envy24ht_speedtab[i].speed == speed) break; } code = envy24ht_speedtab[i].code; #if 0 } #endif DPRINTF(sc, "%s(): speed %d/code 0x%04x\n", __func__, envy24ht_speedtab[i].speed, code); if (code < 0x10) { envy24ht_wrmt(sc, ENVY24HT_MT_RATE, code, 1); code = envy24ht_rdmt(sc, ENVY24HT_MT_RATE, 1); code &= ENVY24HT_MT_RATE_MASK; for (i = 0; envy24ht_speedtab[i].code < 0x10; i++) { if (envy24ht_speedtab[i].code == code) break; } speed = envy24ht_speedtab[i].speed; } else speed = 0; DPRINTF(sc, "%s(): return %d\n", __func__, speed); return (speed); } static void envy24ht_setvolume(struct sc_info *sc, unsigned ch) { #if(0) DPRINTF(sc, "%s(sc, %d)\n", __func__, ch); envy24ht_wrmt(sc, ENVY24HT_MT_VOLIDX, ch * 2, 1); envy24ht_wrmt(sc, ENVY24HT_MT_VOLUME, 0x7f00 | sc->left[ch], 2); envy24ht_wrmt(sc, ENVY24HT_MT_VOLIDX, ch * 2 + 1, 1); envy24ht_wrmt(sc, ENVY24HT_MT_VOLUME, (sc->right[ch] << 8) | 0x7f, 2); #endif } static void envy24ht_mutevolume(struct sc_info *sc, unsigned ch) { #if 0 uint32_t vol; DPRINTF(sc, "%s(sc, %d)\n", __func__, ch); vol = ENVY24HT_VOL_MUTE << 8 | ENVY24HT_VOL_MUTE; envy24ht_wrmt(sc, ENVY24HT_MT_VOLIDX, ch * 2, 1); envy24ht_wrmt(sc, ENVY24HT_MT_VOLUME, vol, 2); envy24ht_wrmt(sc, ENVY24HT_MT_VOLIDX, ch * 2 + 1, 1); envy24ht_wrmt(sc, ENVY24HT_MT_VOLUME, vol, 2); #endif } static uint32_t envy24ht_gethwptr(struct sc_info *sc, int dir) { int unit, regno; uint32_t ptr, rtn; DPRINTF(sc, "%s(sc, %d)\n", __func__, dir); if (dir == PCMDIR_PLAY) { rtn = sc->psize / 4; unit = ENVY24HT_PLAY_BUFUNIT / 4; regno = ENVY24HT_MT_PCNT; } else { rtn = sc->rsize / 4; unit = ENVY24HT_REC_BUFUNIT / 4; regno = ENVY24HT_MT_RCNT; } ptr = envy24ht_rdmt(sc, regno, 2); rtn -= (ptr + 1); rtn /= unit; DPRINTF(sc, "%s(): return %d\n", __func__, rtn); return (rtn); } static void envy24ht_updintr(struct sc_info *sc, int dir) { int regptr, regintr; uint32_t mask, intr; uint32_t ptr, size, cnt; uint16_t blk; DPRINTF(sc, "%s(sc, %d)\n", __func__, dir); if (dir == PCMDIR_PLAY) { blk = sc->blk[0]; size = sc->psize / 4; regptr = ENVY24HT_MT_PCNT; regintr = ENVY24HT_MT_PTERM; mask = ~ENVY24HT_MT_INT_PMASK; } else { blk = sc->blk[1]; size = sc->rsize / 4; regptr = ENVY24HT_MT_RCNT; regintr = ENVY24HT_MT_RTERM; mask = ~ENVY24HT_MT_INT_RMASK; } ptr = size - envy24ht_rdmt(sc, regptr, 2) - 1; #if 0 cnt = blk - ptr % blk - 1; if (cnt == 0) cnt = blk - 1; #else cnt = blk - 1; #endif DPRINTF(sc, "%s():ptr = %d, blk = %d, cnt = %d\n", __func__, ptr, blk, cnt); envy24ht_wrmt(sc, regintr, cnt, 2); intr = envy24ht_rdmt(sc, ENVY24HT_MT_INT_MASK, 1); DPRINTF(sc, "%s():intr = 0x%02x, mask = 0x%02x\n", __func__, intr, mask); envy24ht_wrmt(sc, ENVY24HT_MT_INT_MASK, intr & mask, 1); DPRINTF(sc, "%s():INT-->0x%02x\n", __func__, envy24ht_rdmt(sc, ENVY24HT_MT_INT_MASK, 1)); } #if 0 static void envy24ht_maskintr(struct sc_info *sc, int dir) { uint32_t mask, intr; DPRINTF(sc, "%s(sc, %d)\n", __func__, dir); if (dir == PCMDIR_PLAY) mask = ENVY24HT_MT_INT_PMASK; else mask = ENVY24HT_MT_INT_RMASK; intr = envy24ht_rdmt(sc, ENVY24HT_MT_INT, 1); envy24ht_wrmt(sc, ENVY24HT_MT_INT, intr | mask, 1); } #endif static int envy24ht_checkintr(struct sc_info *sc, int dir) { uint32_t mask, stat, intr, rtn; DPRINTF(sc, "%s(sc, %d)\n", __func__, dir); intr = envy24ht_rdmt(sc, ENVY24HT_MT_INT_STAT, 1); if (dir == PCMDIR_PLAY) { if ((rtn = intr & ENVY24HT_MT_INT_PSTAT) != 0) { mask = ~ENVY24HT_MT_INT_RSTAT; envy24ht_wrmt(sc, 0x1a, 0x01, 1); envy24ht_wrmt(sc, ENVY24HT_MT_INT_STAT, (intr & mask) | ENVY24HT_MT_INT_PSTAT | 0x08, 1); stat = envy24ht_rdmt(sc, ENVY24HT_MT_INT_MASK, 1); envy24ht_wrmt(sc, ENVY24HT_MT_INT_MASK, stat | ENVY24HT_MT_INT_PMASK, 1); } } else { if ((rtn = intr & ENVY24HT_MT_INT_RSTAT) != 0) { mask = ~ENVY24HT_MT_INT_PSTAT; #if 0 stat = ENVY24HT_MT_INT_RSTAT | ENVY24HT_MT_INT_RMASK; #endif envy24ht_wrmt(sc, ENVY24HT_MT_INT_STAT, (intr & mask) | ENVY24HT_MT_INT_RSTAT, 1); stat = envy24ht_rdmt(sc, ENVY24HT_MT_INT_MASK, 1); envy24ht_wrmt(sc, ENVY24HT_MT_INT_MASK, stat | ENVY24HT_MT_INT_RMASK, 1); } } return (rtn); } static void envy24ht_start(struct sc_info *sc, int dir) { uint32_t stat, sw; DPRINTF(sc, "%s(sc, %d)\n", __func__, dir); if (dir == PCMDIR_PLAY) sw = ENVY24HT_MT_PCTL_PSTART; else sw = ENVY24HT_MT_PCTL_RSTART; stat = envy24ht_rdmt(sc, ENVY24HT_MT_PCTL, 1); envy24ht_wrmt(sc, ENVY24HT_MT_PCTL, stat | sw, 1); #if(0) DELAY(100); DPRINTF(sc, "PADDR:0x%08x\n", __func__, envy24ht_rdmt(sc, ENVY24HT_MT_PADDR, 4)); DPRINTF(sc, "PCNT:%ld\n", __func__, envy24ht_rdmt(sc, ENVY24HT_MT_PCNT, 2)); #endif } static void envy24ht_stop(struct sc_info *sc, int dir) { uint32_t stat, sw; DPRINTF(sc, "%s(sc, %d)\n", __func__, dir); if (dir == PCMDIR_PLAY) sw = ~ENVY24HT_MT_PCTL_PSTART; else sw = ~ENVY24HT_MT_PCTL_RSTART; stat = envy24ht_rdmt(sc, ENVY24HT_MT_PCTL, 1); envy24ht_wrmt(sc, ENVY24HT_MT_PCTL, stat & sw, 1); } #if 0 static int envy24ht_route(struct sc_info *sc, int dac, int class, int adc, int rev) { return (0); } #endif /* -------------------------------------------------------------------- */ /* buffer copy routines */ static void envy24ht_p32sl(struct sc_chinfo *ch) { int length; sample32_t *dmabuf; uint32_t *data; int src, dst, ssize, dsize, slot; int i; length = sndbuf_getready(ch->buffer) / 8; dmabuf = ch->parent->pbuf; data = (uint32_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 * ENVY24HT_PLAY_CHNUM + slot].buffer = data[src]; dmabuf[dst * ENVY24HT_PLAY_CHNUM + slot + 1].buffer = data[src + 1]; dst++; dst %= dsize; src += 2; src %= ssize; } } static void envy24ht_p16sl(struct sc_chinfo *ch) { int length; sample32_t *dmabuf; uint16_t *data; int src, dst, ssize, dsize, slot; int i; DPRINTF(ch->parent, "%s()\n", __func__); length = sndbuf_getready(ch->buffer) / 4; dmabuf = ch->parent->pbuf; data = (uint16_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; DPRINTF(ch->parent, "%s():%u-->%u(%u)\n", __func__, src, dst, length); for (i = 0; i < length; i++) { dmabuf[dst * ENVY24HT_PLAY_CHNUM + slot].buffer = (uint32_t)data[src] << 16; dmabuf[dst * ENVY24HT_PLAY_CHNUM + slot + 1].buffer = (uint32_t)data[src + 1] << 16; #if(0) if (i < 16) { printf("%08x", dmabuf[dst * ENVY24HT_PLAY_CHNUM + slot]); printf("%08x", dmabuf[dst * ENVY24HT_PLAY_CHNUM + slot + 1]); } #endif dst++; dst %= dsize; src += 2; src %= ssize; } #if(0) printf("\n"); #endif } static void envy24ht_p8u(struct sc_chinfo *ch) { int length; sample32_t *dmabuf; uint8_t *data; int src, dst, ssize, dsize, slot; int i; length = sndbuf_getready(ch->buffer) / 2; dmabuf = ch->parent->pbuf; data = (uint8_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 * ENVY24HT_PLAY_CHNUM + slot].buffer = ((uint32_t)data[src] ^ 0x80) << 24; dmabuf[dst * ENVY24HT_PLAY_CHNUM + slot + 1].buffer = ((uint32_t)data[src + 1] ^ 0x80) << 24; dst++; dst %= dsize; src += 2; src %= ssize; } } static void envy24ht_r32sl(struct sc_chinfo *ch) { int length; sample32_t *dmabuf; uint32_t *data; int src, dst, ssize, dsize, slot; int i; length = sndbuf_getfree(ch->buffer) / 8; dmabuf = ch->parent->rbuf; data = (uint32_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 - ENVY24HT_CHAN_REC_ADC1) * 2; for (i = 0; i < length; i++) { data[dst] = dmabuf[src * ENVY24HT_REC_CHNUM + slot].buffer; data[dst + 1] = dmabuf[src * ENVY24HT_REC_CHNUM + slot + 1].buffer; dst += 2; dst %= dsize; src++; src %= ssize; } } static void envy24ht_r16sl(struct sc_chinfo *ch) { int length; sample32_t *dmabuf; uint16_t *data; int src, dst, ssize, dsize, slot; int i; length = sndbuf_getfree(ch->buffer) / 4; dmabuf = ch->parent->rbuf; data = (uint16_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 - ENVY24HT_CHAN_REC_ADC1) * 2; for (i = 0; i < length; i++) { data[dst] = dmabuf[src * ENVY24HT_REC_CHNUM + slot].buffer; data[dst + 1] = dmabuf[src * ENVY24HT_REC_CHNUM + slot + 1].buffer; dst += 2; dst %= dsize; src++; src %= ssize; } } /* -------------------------------------------------------------------- */ /* channel interface */ static void * envy24htchan_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; DPRINTF(sc, "%s(obj, devinfo, b, c, %d)\n", __func__, dir); snd_mtxlock(sc->lock); #if 0 if ((sc->chnum > ENVY24HT_CHAN_PLAY_SPDIF && dir != PCMDIR_REC) || (sc->chnum < ENVY24HT_CHAN_REC_ADC1 && dir != PCMDIR_PLAY)) { snd_mtxunlock(sc->lock); return (NULL); } #endif num = sc->chnum; ch = &sc->chan[num]; ch->size = 8 * ENVY24HT_SAMPLE_NUM; ch->data = malloc(ch->size, M_ENVY24HT, 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 = envy24ht_chanmap[num]; snd_mtxunlock(sc->lock); sndbuf_setup(ch->buffer, ch->data, ch->size); snd_mtxlock(sc->lock); /* these 2 values are dummy */ ch->unit = 4; ch->blk = 10240; } snd_mtxunlock(sc->lock); return (ch); } static int envy24htchan_free(kobj_t obj, void *data) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; DPRINTF(sc, "%s()\n", __func__); snd_mtxlock(sc->lock); if (ch->data != NULL) { free(ch->data, M_ENVY24HT); ch->data = NULL; } snd_mtxunlock(sc->lock); return (0); } static int envy24htchan_setformat(kobj_t obj, void *data, uint32_t format) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; struct envy24ht_emldma *emltab; #if 0 unsigned int bcnt, bsize; #endif int i; DPRINTF(sc, "%s(obj, data, 0x%08x)\n", __func__, format); snd_mtxlock(sc->lock); /* check and get format related information */ if (ch->dir == PCMDIR_PLAY) emltab = envy24ht_pemltab; else emltab = envy24ht_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 * ENVY24HT_SAMPLE_NUM; #if 0 if (ch->dir == PCMDIR_PLAY) bsize = ch->blk * 4 / ENVY24HT_PLAY_BUFUNIT; else bsize = ch->blk * 4 / ENVY24HT_REC_BUFUNIT; bsize *= ch->unit; bcnt = ch->size / bsize; sndbuf_resize(ch->buffer, bcnt, bsize); #endif snd_mtxunlock(sc->lock); DPRINTF(sc, "%s(): return 0x%08x\n", __func__, 0); 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 envy24htchan_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 envy24htchan_setspeed(kobj_t obj, void *data, uint32_t speed) { struct sc_chinfo *ch = data; uint32_t val, prev; int i; DPRINTF(ch->parent, "%s(obj, data, %d)\n", __func__, speed); prev = 0x7fffffff; for (i = 0; (val = envy24ht_speed[i]) != 0; i++) { if (abs(val - speed) < abs(prev - speed)) prev = val; else break; } ch->speed = prev; DPRINTF(ch->parent, "%s(): return %d\n", __func__, ch->speed); return (ch->speed); } static int envy24htchan_setblocksize(kobj_t obj, void *data, uint32_t blocksize) { struct sc_chinfo *ch; struct sc_info *sc; uint32_t size, prev; unsigned int bcnt, bsize; ch = data; sc = ch->parent; DPRINTF(sc, "%s(obj, data, %d)\n", __func__, blocksize); prev = 0x7fffffff; #if 0 snd_mtxlock(sc->lock); #endif 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 *= ENVY24HT_PLAY_BUFUNIT / 4; else ch->blk *= ENVY24HT_REC_BUFUNIT / 4; /* set channel buffer information */ #if 0 ch->size = ch->unit * ENVY24HT_SAMPLE_NUM; #endif if (ch->dir == PCMDIR_PLAY) bsize = ch->blk * 4 / ENVY24HT_PLAY_BUFUNIT; else bsize = ch->blk * 4 / ENVY24HT_REC_BUFUNIT; bsize *= ch->unit; bcnt = ch->size / bsize; sndbuf_resize(ch->buffer, bcnt, bsize); #if 0 snd_mtxunlock(sc->lock); #endif DPRINTF(sc, "%s(): return %d\n", __func__, prev); return (prev); } /* semantic note: must start at beginning of buffer */ static int envy24htchan_trigger(kobj_t obj, void *data, int go) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; uint32_t ptr; int slot; #if 0 int i; #endif DPRINTF(sc, "%s(obj, data, %d)\n", __func__, go); snd_mtxlock(sc->lock); if (ch->dir == PCMDIR_PLAY) slot = 0; else slot = 1; switch (go) { case PCMTRIG_START: DPRINTF(sc, "%s(): start\n", __func__); /* check or set channel speed */ if (sc->run[0] == 0 && sc->run[1] == 0) { sc->speed = envy24ht_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) break; 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 = envy24ht_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); envy24ht_setvolume(sc, ch->num); } envy24ht_updintr(sc, ch->dir); if (sc->run[slot] == 1) envy24ht_start(sc, ch->dir); ch->run = 1; break; case PCMTRIG_EMLDMAWR: DPRINTF(sc, "%s(): emldmawr\n", __func__); if (ch->run != 1) break; ch->emldma(ch); break; case PCMTRIG_EMLDMARD: DPRINTF(sc, "%s(): emldmard\n", __func__); if (ch->run != 1) break; ch->emldma(ch); break; case PCMTRIG_ABORT: if (ch->run) { DPRINTF(sc, "%s(): abort\n", __func__); ch->run = 0; sc->run[slot]--; if (ch->dir == PCMDIR_PLAY) envy24ht_mutevolume(sc, ch->num); if (sc->run[slot] == 0) { envy24ht_stop(sc, ch->dir); sc->intr[slot] = 0; } /*else if (ch->blk == sc->blk[slot]) { sc->blk[slot] = ENVY24HT_SAMPLE_NUM / 2; for (i = 0; i < ENVY24HT_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]) envy24ht_updintr(sc, ch->dir); }*/ } break; } snd_mtxunlock(sc->lock); return (0); } static int envy24htchan_getptr(kobj_t obj, void *data) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; uint32_t ptr; int rtn; DPRINTF(sc, "%s()\n", __func__); snd_mtxlock(sc->lock); ptr = envy24ht_gethwptr(sc, ch->dir); rtn = ptr * ch->unit; snd_mtxunlock(sc->lock); DPRINTF(sc, "%s(): return %d\n", __func__, rtn); return (rtn); } static struct pcmchan_caps * envy24htchan_getcaps(kobj_t obj, void *data) { struct sc_chinfo *ch = data; struct sc_info *sc = ch->parent; struct pcmchan_caps *rtn; DPRINTF(sc, "%s()\n", __func__); snd_mtxlock(sc->lock); if (ch->dir == PCMDIR_PLAY) { if (sc->run[0] == 0) rtn = &envy24ht_playcaps; else rtn = &sc->caps[0]; } else { if (sc->run[1] == 0) rtn = &envy24ht_reccaps; else rtn = &sc->caps[1]; } snd_mtxunlock(sc->lock); return (rtn); } static kobj_method_t envy24htchan_methods[] = { KOBJMETHOD(channel_init, envy24htchan_init), KOBJMETHOD(channel_free, envy24htchan_free), KOBJMETHOD(channel_setformat, envy24htchan_setformat), KOBJMETHOD(channel_setspeed, envy24htchan_setspeed), KOBJMETHOD(channel_setblocksize, envy24htchan_setblocksize), KOBJMETHOD(channel_trigger, envy24htchan_trigger), KOBJMETHOD(channel_getptr, envy24htchan_getptr), KOBJMETHOD(channel_getcaps, envy24htchan_getcaps), { 0, 0 } }; CHANNEL_DECLARE(envy24htchan); /* -------------------------------------------------------------------- */ /* mixer interface */ static int envy24htmixer_init(struct snd_mixer *m) { struct sc_info *sc = mix_getdevinfo(m); struct snddev_info *d = NULL; DPRINTF(sc, "%s()\n", __func__); if (sc == NULL) return (-1); /* set volume control rate */ snd_mtxlock(sc->lock); #if 0 /* 0x30 is default value */ envy24ht_wrmt(sc, ENVY24HT_MT_VOLRATE, 0x30, 1); #endif mix_setdevs(m, ENVY24HT_MIX_MASK); mix_setrecdevs(m, ENVY24HT_MIX_REC_MASK); d = device_get_softc(sc->dev); if (d != NULL) d->flags |= SD_F_SOFTPCMVOL; snd_mtxunlock(sc->lock); return (0); } static int envy24htmixer_reinit(struct snd_mixer *m) { struct sc_info *sc = mix_getdevinfo(m); if (sc == NULL) return (-1); DPRINTF(sc, "%s()\n", __func__); return (0); } static int envy24htmixer_uninit(struct snd_mixer *m) { struct sc_info *sc = mix_getdevinfo(m); if (sc == NULL) return (-1); DPRINTF(sc, "%s()\n", __func__); return (0); } static int envy24htmixer_set(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right) { struct sc_info *sc = mix_getdevinfo(m); int ch = envy24ht_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 = envy24ht_chanmap[ch]; DPRINTF(sc, "%s(m, %d, %d, %d)\n", __func__, dev, left, right); 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) > ENVY24HT_VOL_MIN) sc->left[hwch] = ENVY24HT_VOL_MUTE; if ((sc->right[hwch] = 100 - right) > ENVY24HT_VOL_MIN) sc->right[hwch] = ENVY24HT_VOL_MUTE; /* set volume for record channel and running play channel */ if (hwch > ENVY24HT_CHAN_PLAY_SPDIF || sc->chan[ch].run) envy24ht_setvolume(sc, hwch); } snd_mtxunlock(sc->lock); return (right << 8 | left); } static uint32_t envy24htmixer_setrecsrc(struct snd_mixer *m, uint32_t src) { struct sc_info *sc = mix_getdevinfo(m); int ch = envy24ht_mixmap[src]; DPRINTF(sc, "%s(m, %d)\n", __func__, src); if (ch > ENVY24HT_CHAN_PLAY_SPDIF) sc->src = ch; return (src); } static kobj_method_t envy24htmixer_methods[] = { KOBJMETHOD(mixer_init, envy24htmixer_init), KOBJMETHOD(mixer_reinit, envy24htmixer_reinit), KOBJMETHOD(mixer_uninit, envy24htmixer_uninit), KOBJMETHOD(mixer_set, envy24htmixer_set), KOBJMETHOD(mixer_setrecsrc, envy24htmixer_setrecsrc), { 0, 0 } }; MIXER_DECLARE(envy24htmixer); /* -------------------------------------------------------------------- */ /* The interrupt handler */ static void envy24ht_intr(void *p) { struct sc_info *sc = (struct sc_info *)p; struct sc_chinfo *ch; uint32_t ptr, dsize, feed; int i; DPRINTF(sc, "%s()\n", __func__); snd_mtxlock(sc->lock); if (envy24ht_checkintr(sc, PCMDIR_PLAY)) { DPRINTF(sc, "%s(): play\n", __func__); dsize = sc->psize / 4; ptr = dsize - envy24ht_rdmt(sc, ENVY24HT_MT_PCNT, 2) - 1; DPRINTF(sc, "%s(): ptr = %d-->", __func__, ptr); 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 = ENVY24HT_CHAN_PLAY_DAC1; i <= ENVY24HT_CHAN_PLAY_SPDIF; i++) { ch = &sc->chan[i]; if (ch->run) DPRINTF(sc, "%s(): chan[%d].blk = %d\n", __func__, i, ch->blk); if (ch->run && ch->blk <= feed) { snd_mtxunlock(sc->lock); chn_intr(ch->channel); snd_mtxlock(sc->lock); } } sc->intr[0] = ptr; envy24ht_updintr(sc, PCMDIR_PLAY); } if (envy24ht_checkintr(sc, PCMDIR_REC)) { DPRINTF(sc, "%s(): rec\n", __func__); dsize = sc->rsize / 4; ptr = dsize - envy24ht_rdmt(sc, ENVY24HT_MT_RCNT, 2) - 1; ptr -= ptr % sc->blk[1]; feed = (ptr + dsize - sc->intr[1]) % dsize; for (i = ENVY24HT_CHAN_REC_ADC1; i <= ENVY24HT_CHAN_REC_SPDIF; i++) { ch = &sc->chan[i]; if (ch->run && ch->blk <= feed) { snd_mtxunlock(sc->lock); chn_intr(ch->channel); snd_mtxlock(sc->lock); } } sc->intr[1] = ptr; envy24ht_updintr(sc, PCMDIR_REC); } snd_mtxunlock(sc->lock); } /* * Probe and attach the card */ static int envy24ht_pci_probe(device_t dev) { uint16_t sv, sd; int i; #ifdef ENVY24_DEBUG if (envy24_debug > 0) device_printf(dev, "%s\n", __func__); #endif if (pci_get_device(dev) == PCID_ENVY24HT && pci_get_vendor(dev) == PCIV_ENVY24) { sv = pci_get_subvendor(dev); sd = pci_get_subdevice(dev); for (i = 0; i < sizeof(cfg_table)/sizeof(cfg_table[0]); i++) { if (cfg_table[i].subvendor == sv && cfg_table[i].subdevice == sd) { break; } } if (i == sizeof(cfg_table)/sizeof(cfg_table[0])) { #ifdef ENVY24_DEBUG if (envy24_debug > 0) device_printf(dev, "Unknown/unsupported Envy24/Envy24HT\n"); #endif return (ENXIO); } device_set_desc(dev, cfg_table[i].name); return (BUS_PROBE_DEFAULT); } #ifdef ENVY24_DEBUG if (envy24_debug > 0) device_printf(dev, "%s: return ENXIO\n", __func__); #endif return ENXIO; } struct envy24ht_dmamap_arg { bus_addr_t envy24_busaddr; }; static void envy24ht_dmasetmap(void *arg, bus_dma_segment_t *segs, int nseg, int error) { struct envy24ht_dmamap_arg *ctx; KASSERT(nseg == 1, ("Too many segments returned!")); if (error != 0) return; ctx = arg; ctx->envy24_busaddr = segs[0].ds_addr; } static void envy24ht_dmafree(struct sc_info *sc) { #ifdef ENVY24_DEBUG DPRINTF(sc, "%s:", __func__); if (sc->rmap) printf(" sc->rmap(0x%08x)", (uint32_t)sc->rmap); else printf(" sc->rmap(null)"); if (sc->pmap) printf(" sc->pmap(0x%08x)", (uint32_t)sc->pmap); else printf(" sc->pmap(null)"); if (sc->rbuf) printf(" sc->rbuf(0x%08x)", (uint32_t)sc->rbuf); else printf(" sc->rbuf(null)"); if (sc->pbuf) printf(" sc->pbuf(0x%08x)\n", (uint32_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; } static int envy24ht_dmainit(struct sc_info *sc) { struct envy24ht_dmamap_arg pctx, rctx; DPRINTF(sc, "%s\n", __func__); /* init values */ sc->psize = ENVY24HT_PLAY_BUFUNIT * ENVY24HT_SAMPLE_NUM; sc->rsize = ENVY24HT_REC_BUFUNIT * ENVY24HT_SAMPLE_NUM; sc->pbuf = NULL; sc->rbuf = NULL; sc->pmap = sc->rmap = NULL; sc->blk[0] = sc->blk[1] = 0; /* allocate DMA buffer */ DPRINTF(sc, "%s: bus_dmamem_alloc(): sc->pbuf\n", __func__); if (bus_dmamem_alloc(sc->dmat, (void **)&sc->pbuf, BUS_DMA_WAITOK | BUS_DMA_COHERENT | BUS_DMA_ZERO, &sc->pmap)) goto bad; DPRINTF(sc, "%s: bus_dmamem_alloc(): sc->rbuf\n", __func__); if (bus_dmamem_alloc(sc->dmat, (void **)&sc->rbuf, BUS_DMA_WAITOK | BUS_DMA_COHERENT | BUS_DMA_ZERO, &sc->rmap)) goto bad; DPRINTF(sc, "%s: bus_dmamem_load(): sc->pmap\n", __func__); pctx.envy24_busaddr = 0; if (bus_dmamap_load(sc->dmat, sc->pmap, sc->pbuf, sc->psize, envy24ht_dmasetmap, &pctx, 0) || pctx.envy24_busaddr == 0) goto bad; DPRINTF(sc, "%s: bus_dmamem_load(): sc->rmap\n", __func__); rctx.envy24_busaddr = 0; if (bus_dmamap_load(sc->dmat, sc->rmap, sc->rbuf, sc->rsize, envy24ht_dmasetmap, &rctx, 0) || rctx.envy24_busaddr == 0) goto bad; /* set values to register */ DPRINTF(sc, "pbuf(0x%08x)\n", pctx.envy24_busaddr); envy24ht_wrmt(sc, ENVY24HT_MT_PADDR, pctx.envy24_busaddr, 4); DPRINTF(sc, "PADDR-->(0x%08x)\n", envy24ht_rdmt(sc, ENVY24HT_MT_PADDR, 4)); DPRINTF(sc, "psize(%u)\n", sc->psize / 4 - 1); envy24ht_wrmt(sc, ENVY24HT_MT_PCNT, sc->psize / 4 - 1, 2); DPRINTF(sc, "PCNT-->(%u)\n", envy24ht_rdmt(sc, ENVY24HT_MT_PCNT, 2)); envy24ht_wrmt(sc, ENVY24HT_MT_RADDR, rctx.envy24_busaddr, 4); envy24ht_wrmt(sc, ENVY24HT_MT_RCNT, sc->rsize / 4 - 1, 2); return (0); bad: envy24ht_dmafree(sc); return (ENOSPC); } static void envy24ht_putcfg(struct sc_info *sc) { device_printf(sc->dev, "system configuration\n"); printf(" SubVendorID: 0x%04x, SubDeviceID: 0x%04x\n", sc->cfg->subvendor, sc->cfg->subdevice); printf(" XIN2 Clock Source: "); switch (sc->cfg->scfg & ENVY24HT_CCSM_SCFG_XIN2) { case 0x00: printf("24.576MHz(96kHz*256)\n"); break; case 0x40: printf("49.152MHz(192kHz*256)\n"); break; case 0x80: printf("reserved\n"); break; default: printf("illeagal system setting\n"); } printf(" MPU-401 UART(s) #: "); if (sc->cfg->scfg & ENVY24HT_CCSM_SCFG_MPU) printf("1\n"); else printf("not implemented\n"); switch (sc->adcn) { case 0x01 || 0x02: printf(" ADC #: "); printf("%d\n", sc->adcn); break; case 0x03: printf(" ADC #: "); printf("%d", 1); printf(" and SPDIF receiver connected\n"); break; default: printf(" no physical inputs\n"); } printf(" DAC #: "); printf("%d\n", sc->dacn); printf(" Multi-track converter type: "); if ((sc->cfg->acl & ENVY24HT_CCSM_ACL_MTC) == 0) { printf("AC'97(SDATA_OUT:"); if (sc->cfg->acl & ENVY24HT_CCSM_ACL_OMODE) printf("packed"); else printf("split"); printf(")\n"); } else { printf("I2S("); if (sc->cfg->i2s & ENVY24HT_CCSM_I2S_VOL) printf("with volume, "); if (sc->cfg->i2s & ENVY24HT_CCSM_I2S_192KHZ) printf("192KHz support, "); else if (sc->cfg->i2s & ENVY24HT_CCSM_I2S_96KHZ) printf("192KHz support, "); else printf("48KHz support, "); switch (sc->cfg->i2s & ENVY24HT_CCSM_I2S_RES) { case ENVY24HT_CCSM_I2S_16BIT: printf("16bit resolution, "); break; case ENVY24HT_CCSM_I2S_18BIT: printf("18bit resolution, "); break; case ENVY24HT_CCSM_I2S_20BIT: printf("20bit resolution, "); break; case ENVY24HT_CCSM_I2S_24BIT: printf("24bit resolution, "); break; } printf("ID#0x%x)\n", sc->cfg->i2s & ENVY24HT_CCSM_I2S_ID); } printf(" S/PDIF(IN/OUT): "); if (sc->cfg->spdif & ENVY24HT_CCSM_SPDIF_IN) printf("1/"); else printf("0/"); if (sc->cfg->spdif & ENVY24HT_CCSM_SPDIF_OUT) printf("1 "); else printf("0 "); if (sc->cfg->spdif & (ENVY24HT_CCSM_SPDIF_IN | ENVY24HT_CCSM_SPDIF_OUT)) printf("ID# 0x%02x\n", (sc->cfg->spdif & ENVY24HT_CCSM_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 envy24ht_init(struct sc_info *sc) { uint32_t data; int i; uint32_t sv, sd; DPRINTF(sc, "%s\n", __func__); /* reset chip */ #if 0 envy24ht_wrcs(sc, ENVY24HT_CCS_CTL, ENVY24HT_CCS_CTL_RESET, 1); DELAY(200); envy24ht_wrcs(sc, ENVY24HT_CCS_CTL, ENVY24HT_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); #endif /* check system configuration */ sc->cfg = NULL; for (i = 0; i < sizeof(cfg_table)/sizeof(cfg_table[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) { DPRINTF(sc, "Set configuration from table\n"); sc->cfg = &cfg_table[i]; break; } } if (sc->cfg == NULL) { /* 2nd: read configuration from table */ sc->cfg = envy24ht_rom2cfg(sc); } /* XXX need to be fixed */ sc->adcn = ((sc->cfg->scfg & ENVY24HT_CCSM_SCFG_ADC) >> 2) + 1; sc->dacn = (sc->cfg->scfg & ENVY24HT_CCSM_SCFG_DAC) + 1; if (1 /* bootverbose */) { envy24ht_putcfg(sc); } /* set system configuration */ envy24ht_wrcs(sc, ENVY24HT_CCS_SCFG, sc->cfg->scfg, 1); envy24ht_wrcs(sc, ENVY24HT_CCS_ACL, sc->cfg->acl, 1); envy24ht_wrcs(sc, ENVY24HT_CCS_I2S, sc->cfg->i2s, 1); envy24ht_wrcs(sc, ENVY24HT_CCS_SPDIF, sc->cfg->spdif, 1); envy24ht_gpiosetmask(sc, sc->cfg->gpiomask); envy24ht_gpiosetdir(sc, sc->cfg->gpiodir); envy24ht_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 */ DPRINTF(sc, "%s: initialize DMA buffer\n", __func__); if (envy24ht_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 = envy24ht_playfmt; sc->caps[1].fmtlist = envy24ht_recfmt; /* set channel router */ #if 0 envy24ht_route(sc, ENVY24HT_ROUTE_DAC_1, ENVY24HT_ROUTE_CLASS_MIX, 0, 0); envy24ht_route(sc, ENVY24HT_ROUTE_DAC_SPDIF, ENVY24HT_ROUTE_CLASS_DMA, 0, 0); envy24ht_route(sc, ENVY24HT_ROUTE_DAC_SPDIF, ENVY24HT_ROUTE_CLASS_MIX, 0, 0); #endif /* set macro interrupt mask */ data = envy24ht_rdcs(sc, ENVY24HT_CCS_IMASK, 1); envy24ht_wrcs(sc, ENVY24HT_CCS_IMASK, data & ~ENVY24HT_CCS_IMASK_PMT, 1); data = envy24ht_rdcs(sc, ENVY24HT_CCS_IMASK, 1); DPRINTF(sc, "%s: CCS_IMASK-->0x%02x\n", __func__, data); return (0); } static int envy24ht_alloc_resource(struct sc_info *sc) { int error; /* allocate I/O port resource */ sc->csid = PCIR_CCS; sc->cs = bus_alloc_resource_any(sc->dev, SYS_RES_IOPORT, &sc->csid, RF_ACTIVE); if (sc->cs == NULL) { device_printf(sc->dev, "couldn't allocate controller resource\n"); return (ENXIO); } sc->mtid = ENVY24HT_PCIR_MT; sc->mt = bus_alloc_resource_any(sc->dev, SYS_RES_IOPORT, &sc->mtid, RF_ACTIVE); if (sc->mt == NULL) { device_printf(sc->dev, "couldn't allocate multi-track resource\n"); return (ENXIO); } DPRINTF(sc, "IO port register values\nCCS: 0x%x\nMT: 0x%x\n", pci_read_config(sc->dev, PCIR_CCS, 4), pci_read_config(sc->dev, ENVY24HT_PCIR_MT, 4)); /* allocate interupt resource */ sc->irqid = 0; sc->irq = bus_alloc_resource_any(sc->dev, SYS_RES_IRQ, &sc->irqid, RF_ACTIVE | RF_SHAREABLE); if (sc->irq == NULL) { device_printf(sc->dev, "couldn't allocate IRQ resource\n"); return (ENXIO); } error = snd_setup_intr(sc->dev, sc->irq, 0, envy24ht_intr, sc, &sc->ih); if (error != 0) { device_printf(sc->dev, "couldn't set up interrupt handler\n"); return (ENXIO); } /* allocate DMA resource */ /* * XXX * Should seperate playback/record channel DMA map. * Should recompute number of needed DMA map size. * Requesting a contiguous 256KB(BUS_SPACE_MAXSIZE_ENVY24) DMA map * is waste of precious system resource and it would be failed * on many systems as Envy24's DMA address space should be within * 256MB. */ error = bus_dma_tag_create( bus_get_dma_tag(sc->dev), /* parent */ sizeof(uint32_t), 0, /* alignment, boundary */ BUS_SPACE_MAXADDR_ENVY24, /* lowaddr */ BUS_SPACE_MAXADDR, /* highaddr */ NULL, NULL, /* filter, filterarg */ BUS_SPACE_MAXSIZE_ENVY24, /* maxsize */ 1, /* nsegments */ BUS_SPACE_MAXSIZE_ENVY24, /* maxsegsize */ 0, /* flags */ NULL, NULL, /* lockfunc, lockarg */ &sc->dmat); if (error != 0) device_printf(sc->dev, "unable to create dma tag\n"); return (error); } static int envy24ht_pci_attach(device_t dev) { struct sc_info *sc; char status[SND_STATUSLEN]; char name[ENVY24HT_NAMELEN]; int err; int i; #ifdef ENVY24_DEBUG if (envy24_debug > 0) device_printf(dev, "%s\n", __func__); #endif /* get sc_info data area */ if ((sc = malloc(sizeof(*sc), M_ENVY24HT, M_WAITOK)) == NULL) { device_printf(dev, "cannot allocate softc\n"); return (ENXIO); } bzero(sc, sizeof(*sc)); snprintf(name, sizeof(name), "%s:envy24ht", device_get_nameunit(dev)); sc->lock = snd_mtxcreate(name, name); sc->dev = dev; /* initialize PCI interface */ pci_enable_io(dev, SYS_RES_IOPORT); pci_enable_busmaster(dev); /* allocate resources */ err = envy24ht_alloc_resource(sc); if (err) goto bad; /* initialize card */ err = envy24ht_init(sc); if (err) { device_printf(dev, "unable to initialize the card\n"); goto bad; } /* set multi track mixer */ mixer_init(dev, &envy24htmixer_class, sc); /* set channel information */ #if 0 err = pcm_register(dev, sc, 5, 2 + sc->adcn); #else err = pcm_register(dev, sc, 1, 2 + sc->adcn); #endif if (err != 0) goto bad; sc->chnum = 0; #if 0 for (i = 0; i < 5; i++) { pcm_addchan(dev, PCMDIR_PLAY, &envy24htchan_class, sc); sc->chnum++; } #else pcm_addchan(dev, PCMDIR_PLAY, &envy24htchan_class, sc); sc->chnum++; #endif for (i = 0; i < 2 + sc->adcn; i++) { pcm_addchan(dev, PCMDIR_REC, &envy24htchan_class, sc); sc->chnum++; } /* set status iformation */ snprintf(status, SND_STATUSLEN, "at io 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->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); envy24ht_dmafree(sc); if (sc->dmat) bus_dma_tag_destroy(sc->dmat); 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]); } envy24ht_cfgfree(sc->cfg); if (sc->cs) bus_release_resource(dev, SYS_RES_IOPORT, sc->csid, sc->cs); if (sc->mt) bus_release_resource(dev, SYS_RES_IOPORT, sc->mtid, sc->mt); if (sc->lock) snd_mtxfree(sc->lock); free(sc, M_ENVY24HT); return (err); } static int envy24ht_pci_detach(device_t dev) { struct sc_info *sc; int r; int i; sc = pcm_getdevinfo(dev); if (sc == NULL) return (0); DPRINTF(sc, "%s\n", __func__); r = pcm_unregister(dev); if (r) return (r); envy24ht_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]); } envy24ht_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->mtid, sc->mt); snd_mtxfree(sc->lock); free(sc, M_ENVY24HT); return (0); } static device_method_t envy24ht_methods[] = { /* Device interface */ DEVMETHOD(device_probe, envy24ht_pci_probe), DEVMETHOD(device_attach, envy24ht_pci_attach), DEVMETHOD(device_detach, envy24ht_pci_detach), { NULL, NULL } }; static driver_t envy24ht_driver = { "pcm", envy24ht_methods, #if __FreeBSD_version > 500000 PCM_SOFTC_SIZE, #else sizeof(struct snddev_info), #endif }; DRIVER_MODULE(snd_envy24ht, pci, envy24ht_driver, pcm_devclass, 0, 0); MODULE_DEPEND(snd_envy24ht, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_DEPEND(snd_envy24ht, snd_spicds, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); MODULE_VERSION(snd_envy24ht, 1);