4582b3a100
confusions and panic provided that the following conditions are met: 1) WITNESS is enabled (watch/trace). 2) Using modules, instead of statically linked (Not a strict requirement, but easier to reproduce this way). 3) 2 or more modules share the same mtx type ("sound softc"). - They might share the same name (strcmp() == 0), but it always point to different address. 4) Repetitive kldunload/load on any module that shares the same mtx type (Not a strict requirement, but easier to reproduce this way). Consider module A and module B: - From enroll() - subr_witness.c: * Load module A. Everything seems fine right now. wA-w_refcount == 1 ; wA-w_name = "sound softc" * Load module B. * w->w_name == description will always fail. ("sound softc" from A and B point to different address). * wA->w_refcount > 0 && strcmp(description, wA->w_name) == 0 * enroll() will return wA instead of returning (possibly unique) wB. wA->w_refcount++ , == 2. * Unload module A, mtx_destroy(), wA->w_name become invalid, but wA->w_refcount-- become 1 instead of 0. wA will not be removed from witness list. * Some other places call mtx_init(), iterating witness list, found wA, failed on wA->w_name == description * wA->w_refcount > 0 && strcmp(description, wA->w_name) * Panic on strcmp() since wA->w_name no longer point to valid address. Note that this could happened in other places as well, not just sound (eg. consider lots of drivers that share simmilar MTX_NETWORK_LOCK). Solutions (for sound case): 1) Provide unique mtx type string for each mutex creation (chosen) or 2) Put "sound softc" global variable somewhere and use it.
2532 lines
61 KiB
C
2532 lines
61 KiB
C
/*
|
|
* Copyright (c) 2001 Katsurajima Naoto <raven@katsurajima.seya.yokohama.jp>
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHERIN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THEPOSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*
|
|
*/
|
|
|
|
#include <dev/sound/pcm/sound.h>
|
|
#include <dev/sound/pcm/ac97.h>
|
|
#include <dev/sound/pci/spicds.h>
|
|
#include <dev/sound/pci/envy24.h>
|
|
|
|
#include <dev/pci/pcireg.h>
|
|
#include <dev/pci/pcivar.h>
|
|
|
|
#include "mixer_if.h"
|
|
|
|
SND_DECLARE_FILE("$FreeBSD$");
|
|
|
|
MALLOC_DEFINE(M_ENVY24, "envy24", "envy24 audio");
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
struct sc_info;
|
|
|
|
#define ENVY24_PLAY_CHNUM 10
|
|
#define ENVY24_REC_CHNUM 12
|
|
#define ENVY24_PLAY_BUFUNIT (4 /* byte/sample */ * 10 /* channel */)
|
|
#define ENVY24_REC_BUFUNIT (4 /* byte/sample */ * 12 /* channel */)
|
|
#define ENVY24_SAMPLE_NUM 4096
|
|
|
|
#define ENVY24_TIMEOUT 1000
|
|
|
|
#define ENVY24_DEFAULT_FORMAT (AFMT_STEREO | AFMT_S16_LE)
|
|
|
|
#define ENVY24_NAMELEN 32
|
|
|
|
struct envy24_sample {
|
|
volatile u_int32_t buffer;
|
|
};
|
|
|
|
typedef struct envy24_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 */
|
|
u_int32_t format;
|
|
u_int32_t speed;
|
|
u_int32_t blk; /* hw block size(dword) */
|
|
|
|
/* format conversion structure */
|
|
u_int8_t *data;
|
|
unsigned int size; /* data buffer size(byte) */
|
|
int unit; /* sample size(byte) */
|
|
unsigned int offset; /* samples number offset */
|
|
void (*emldma)(struct sc_chinfo *);
|
|
|
|
/* flags */
|
|
int run;
|
|
};
|
|
|
|
/* codec interface entrys */
|
|
struct codec_entry {
|
|
void *(*create)(device_t dev, void *devinfo, int dir, int num);
|
|
void (*destroy)(void *codec);
|
|
void (*init)(void *codec);
|
|
void (*reinit)(void *codec);
|
|
void (*setvolume)(void *codec, int dir, unsigned int left, unsigned int right);
|
|
void (*setrate)(void *codec, int which, int rate);
|
|
};
|
|
|
|
/* system configuration information */
|
|
struct cfg_info {
|
|
char *name;
|
|
u_int16_t subvendor, subdevice;
|
|
u_int8_t scfg, acl, i2s, spdif;
|
|
u_int8_t gpiomask, gpiostate, gpiodir;
|
|
u_int8_t cdti, cclk, cs, cif, type;
|
|
u_int8_t 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;
|
|
bus_space_tag_t cst;
|
|
bus_space_handle_t csh;
|
|
/* DDMA registor */
|
|
struct resource *ddma;
|
|
int ddmaid;
|
|
bus_space_tag_t ddmat;
|
|
bus_space_handle_t ddmah;
|
|
/* Consumer Section DMA Channel Registers */
|
|
struct resource *ds;
|
|
int dsid;
|
|
bus_space_tag_t dst;
|
|
bus_space_handle_t dsh;
|
|
/* MultiTrack registor */
|
|
struct resource *mt;
|
|
int mtid;
|
|
bus_space_tag_t mtt;
|
|
bus_space_handle_t mth;
|
|
/* DMA tag */
|
|
bus_dma_tag_t dmat;
|
|
/* IRQ resource */
|
|
struct resource *irq;
|
|
int irqid;
|
|
void *ih;
|
|
|
|
/* system configuration data */
|
|
struct cfg_info *cfg;
|
|
|
|
/* ADC/DAC number and info */
|
|
int adcn, dacn;
|
|
void *adc[4], *dac[4];
|
|
|
|
/* mixer control data */
|
|
u_int32_t src;
|
|
u_int8_t left[ENVY24_CHAN_NUM];
|
|
u_int8_t right[ENVY24_CHAN_NUM];
|
|
|
|
/* Play/Record DMA fifo */
|
|
sample32_t *pbuf;
|
|
sample32_t *rbuf;
|
|
u_int32_t psize, rsize; /* DMA buffer size(byte) */
|
|
u_int16_t blk[2]; /* transfer check blocksize(dword) */
|
|
bus_dmamap_t pmap, rmap;
|
|
|
|
/* current status */
|
|
u_int32_t speed;
|
|
int run[2];
|
|
u_int16_t intr[2];
|
|
struct pcmchan_caps caps[2];
|
|
|
|
/* channel info table */
|
|
unsigned chnum;
|
|
struct sc_chinfo chan[11];
|
|
};
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
/*
|
|
* prototypes
|
|
*/
|
|
|
|
/* DMA emulator */
|
|
static void envy24_p8u(struct sc_chinfo *);
|
|
static void envy24_p16sl(struct sc_chinfo *);
|
|
static void envy24_p32sl(struct sc_chinfo *);
|
|
static void envy24_r16sl(struct sc_chinfo *);
|
|
static void envy24_r32sl(struct sc_chinfo *);
|
|
|
|
/* channel interface */
|
|
static void *envy24chan_init(kobj_t, void *, struct snd_dbuf *, struct pcm_channel *, int);
|
|
static int envy24chan_setformat(kobj_t, void *, u_int32_t);
|
|
static int envy24chan_setspeed(kobj_t, void *, u_int32_t);
|
|
static int envy24chan_setblocksize(kobj_t, void *, u_int32_t);
|
|
static int envy24chan_trigger(kobj_t, void *, int);
|
|
static int envy24chan_getptr(kobj_t, void *);
|
|
static struct pcmchan_caps *envy24chan_getcaps(kobj_t, void *);
|
|
|
|
/* mixer interface */
|
|
static int envy24mixer_init(struct snd_mixer *);
|
|
static int envy24mixer_reinit(struct snd_mixer *);
|
|
static int envy24mixer_uninit(struct snd_mixer *);
|
|
static int envy24mixer_set(struct snd_mixer *, unsigned, unsigned, unsigned);
|
|
static u_int32_t envy24mixer_setrecsrc(struct snd_mixer *, u_int32_t);
|
|
|
|
/* M-Audio Delta series AK4524 access interface */
|
|
static void *envy24_delta_ak4524_create(device_t, void *, int, int);
|
|
static void envy24_delta_ak4524_destroy(void *);
|
|
static void envy24_delta_ak4524_init(void *);
|
|
static void envy24_delta_ak4524_reinit(void *);
|
|
static void envy24_delta_ak4524_setvolume(void *, int, unsigned int, unsigned int);
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
/*
|
|
system constant tables
|
|
*/
|
|
|
|
/* API -> hardware channel map */
|
|
static unsigned envy24_chanmap[ENVY24_CHAN_NUM] = {
|
|
ENVY24_CHAN_PLAY_SPDIF, /* 0 */
|
|
ENVY24_CHAN_PLAY_DAC1, /* 1 */
|
|
ENVY24_CHAN_PLAY_DAC2, /* 2 */
|
|
ENVY24_CHAN_PLAY_DAC3, /* 3 */
|
|
ENVY24_CHAN_PLAY_DAC4, /* 4 */
|
|
ENVY24_CHAN_REC_MIX, /* 5 */
|
|
ENVY24_CHAN_REC_SPDIF, /* 6 */
|
|
ENVY24_CHAN_REC_ADC1, /* 7 */
|
|
ENVY24_CHAN_REC_ADC2, /* 8 */
|
|
ENVY24_CHAN_REC_ADC3, /* 9 */
|
|
ENVY24_CHAN_REC_ADC4, /* 10 */
|
|
};
|
|
|
|
/* mixer -> API channel map. see above */
|
|
static int envy24_mixmap[] = {
|
|
-1, /* Master output level. It is depend on codec support */
|
|
-1, /* Treble level of all output channels */
|
|
-1, /* Bass level of all output channels */
|
|
-1, /* Volume of synthesier input */
|
|
0, /* Output level for the audio device */
|
|
-1, /* Output level for the PC speaker */
|
|
7, /* line in jack */
|
|
-1, /* microphone jack */
|
|
-1, /* CD audio input */
|
|
-1, /* Recording monitor */
|
|
1, /* alternative codec */
|
|
-1, /* global recording level */
|
|
-1, /* Input gain */
|
|
-1, /* Output gain */
|
|
8, /* Input source 1 */
|
|
9, /* Input source 2 */
|
|
10, /* Input source 3 */
|
|
6, /* Digital (input) 1 */
|
|
-1, /* Digital (input) 2 */
|
|
-1, /* Digital (input) 3 */
|
|
-1, /* Phone input */
|
|
-1, /* Phone output */
|
|
-1, /* Video/TV (audio) in */
|
|
-1, /* Radio in */
|
|
-1, /* Monitor volume */
|
|
};
|
|
|
|
/* variable rate audio */
|
|
static u_int32_t envy24_speed[] = {
|
|
96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000,
|
|
12000, 11025, 9600, 8000, 0
|
|
};
|
|
|
|
/* known boards configuration */
|
|
static struct codec_entry delta_codec = {
|
|
envy24_delta_ak4524_create,
|
|
envy24_delta_ak4524_destroy,
|
|
envy24_delta_ak4524_init,
|
|
envy24_delta_ak4524_reinit,
|
|
envy24_delta_ak4524_setvolume,
|
|
NULL, /* setrate */
|
|
};
|
|
|
|
static struct cfg_info cfg_table[] = {
|
|
{
|
|
"Envy24 audio (M Audio Delta Dio 2496)",
|
|
0x1412, 0xd631,
|
|
0x10, 0x80, 0xf0, 0x03,
|
|
0xff, 0x00, 0x00,
|
|
0x10, 0x20, 0x40, 0x00, 0x00,
|
|
0x00,
|
|
&delta_codec,
|
|
},
|
|
{
|
|
"Envy24 audio (Terratec DMX 6fire)",
|
|
0x153b, 0x1138,
|
|
0x2f, 0x80, 0xf0, 0x03,
|
|
0xc0, 0xff, 0x7f,
|
|
0x10, 0x20, 0x01, 0x01, 0x00,
|
|
0x00,
|
|
&delta_codec,
|
|
},
|
|
{
|
|
"Envy24 audio (M Audio Audiophile 2496)",
|
|
0x1412, 0xd634,
|
|
0x10, 0x80, 0x72, 0x03,
|
|
0x04, 0xfe, 0xfb,
|
|
0x08, 0x02, 0x20, 0x00, 0x01,
|
|
0x00,
|
|
&delta_codec,
|
|
},
|
|
{
|
|
"Envy24 audio (Generic)",
|
|
0, 0,
|
|
0x0f, 0x00, 0x01, 0x03,
|
|
0xff, 0x00, 0x00,
|
|
0x10, 0x20, 0x40, 0x00, 0x00,
|
|
0x00,
|
|
&delta_codec, /* default codec routines */
|
|
}
|
|
};
|
|
|
|
static u_int32_t envy24_recfmt[] = {
|
|
AFMT_STEREO | AFMT_S16_LE,
|
|
AFMT_STEREO | AFMT_S32_LE,
|
|
0
|
|
};
|
|
static struct pcmchan_caps envy24_reccaps = {8000, 96000, envy24_recfmt, 0};
|
|
|
|
static u_int32_t envy24_playfmt[] = {
|
|
AFMT_STEREO | AFMT_U8,
|
|
AFMT_STEREO | AFMT_S16_LE,
|
|
AFMT_STEREO | AFMT_S32_LE,
|
|
0
|
|
};
|
|
|
|
static struct pcmchan_caps envy24_playcaps = {8000, 96000, envy24_playfmt, 0};
|
|
|
|
struct envy24_emldma {
|
|
u_int32_t format;
|
|
void (*emldma)(struct sc_chinfo *);
|
|
int unit;
|
|
};
|
|
|
|
static struct envy24_emldma envy24_pemltab[] = {
|
|
{AFMT_STEREO | AFMT_U8, envy24_p8u, 2},
|
|
{AFMT_STEREO | AFMT_S16_LE, envy24_p16sl, 4},
|
|
{AFMT_STEREO | AFMT_S32_LE, envy24_p32sl, 8},
|
|
{0, NULL, 0}
|
|
};
|
|
|
|
static struct envy24_emldma envy24_remltab[] = {
|
|
{AFMT_STEREO | AFMT_S16_LE, envy24_r16sl, 4},
|
|
{AFMT_STEREO | AFMT_S32_LE, envy24_r32sl, 8},
|
|
{0, NULL, 0}
|
|
};
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
/* common routines */
|
|
static u_int32_t
|
|
envy24_rdcs(struct sc_info *sc, int regno, int size)
|
|
{
|
|
switch (size) {
|
|
case 1:
|
|
return bus_space_read_1(sc->cst, sc->csh, regno);
|
|
case 2:
|
|
return bus_space_read_2(sc->cst, sc->csh, regno);
|
|
case 4:
|
|
return bus_space_read_4(sc->cst, sc->csh, regno);
|
|
default:
|
|
return 0xffffffff;
|
|
}
|
|
}
|
|
|
|
static void
|
|
envy24_wrcs(struct sc_info *sc, int regno, u_int32_t data, int size)
|
|
{
|
|
switch (size) {
|
|
case 1:
|
|
bus_space_write_1(sc->cst, sc->csh, regno, data);
|
|
break;
|
|
case 2:
|
|
bus_space_write_2(sc->cst, sc->csh, regno, data);
|
|
break;
|
|
case 4:
|
|
bus_space_write_4(sc->cst, sc->csh, regno, data);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static u_int32_t
|
|
envy24_rdmt(struct sc_info *sc, int regno, int size)
|
|
{
|
|
switch (size) {
|
|
case 1:
|
|
return bus_space_read_1(sc->mtt, sc->mth, regno);
|
|
case 2:
|
|
return bus_space_read_2(sc->mtt, sc->mth, regno);
|
|
case 4:
|
|
return bus_space_read_4(sc->mtt, sc->mth, regno);
|
|
default:
|
|
return 0xffffffff;
|
|
}
|
|
}
|
|
|
|
static void
|
|
envy24_wrmt(struct sc_info *sc, int regno, u_int32_t data, int size)
|
|
{
|
|
switch (size) {
|
|
case 1:
|
|
bus_space_write_1(sc->mtt, sc->mth, regno, data);
|
|
break;
|
|
case 2:
|
|
bus_space_write_2(sc->mtt, sc->mth, regno, data);
|
|
break;
|
|
case 4:
|
|
bus_space_write_4(sc->mtt, sc->mth, regno, data);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static u_int32_t
|
|
envy24_rdci(struct sc_info *sc, int regno)
|
|
{
|
|
envy24_wrcs(sc, ENVY24_CCS_INDEX, regno, 1);
|
|
return envy24_rdcs(sc, ENVY24_CCS_DATA, 1);
|
|
}
|
|
|
|
static void
|
|
envy24_wrci(struct sc_info *sc, int regno, u_int32_t data)
|
|
{
|
|
envy24_wrcs(sc, ENVY24_CCS_INDEX, regno, 1);
|
|
envy24_wrcs(sc, ENVY24_CCS_DATA, data, 1);
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
/* I2C port/E2PROM access routines */
|
|
|
|
static int
|
|
envy24_rdi2c(struct sc_info *sc, u_int32_t dev, u_int32_t addr)
|
|
{
|
|
u_int32_t data;
|
|
int i;
|
|
|
|
#if(0)
|
|
device_printf(sc->dev, "envy24_rdi2c(sc, 0x%02x, 0x%02x)\n", dev, addr);
|
|
#endif
|
|
for (i = 0; i < ENVY24_TIMEOUT; i++) {
|
|
data = envy24_rdcs(sc, ENVY24_CCS_I2CSTAT, 1);
|
|
if ((data & ENVY24_CCS_I2CSTAT_BSY) == 0)
|
|
break;
|
|
DELAY(32); /* 31.25kHz */
|
|
}
|
|
if (i == ENVY24_TIMEOUT) {
|
|
return -1;
|
|
}
|
|
envy24_wrcs(sc, ENVY24_CCS_I2CADDR, addr, 1);
|
|
envy24_wrcs(sc, ENVY24_CCS_I2CDEV,
|
|
(dev & ENVY24_CCS_I2CDEV_ADDR) | ENVY24_CCS_I2CDEV_RD, 1);
|
|
for (i = 0; i < ENVY24_TIMEOUT; i++) {
|
|
data = envy24_rdcs(sc, ENVY24_CCS_I2CSTAT, 1);
|
|
if ((data & ENVY24_CCS_I2CSTAT_BSY) == 0)
|
|
break;
|
|
DELAY(32); /* 31.25kHz */
|
|
}
|
|
if (i == ENVY24_TIMEOUT) {
|
|
return -1;
|
|
}
|
|
data = envy24_rdcs(sc, ENVY24_CCS_I2CDATA, 1);
|
|
|
|
#if(0)
|
|
device_printf(sc->dev, "envy24_rdi2c(): return 0x%x\n", data);
|
|
#endif
|
|
return (int)data;
|
|
}
|
|
|
|
#if 0
|
|
static int
|
|
envy24_wri2c(struct sc_info *sc, u_int32_t dev, u_int32_t addr, u_int32_t data)
|
|
{
|
|
u_int32_t tmp;
|
|
int i;
|
|
|
|
#if(0)
|
|
device_printf(sc->dev, "envy24_rdi2c(sc, 0x%02x, 0x%02x)\n", dev, addr);
|
|
#endif
|
|
for (i = 0; i < ENVY24_TIMEOUT; i++) {
|
|
tmp = envy24_rdcs(sc, ENVY24_CCS_I2CSTAT, 1);
|
|
if ((tmp & ENVY24_CCS_I2CSTAT_BSY) == 0)
|
|
break;
|
|
DELAY(32); /* 31.25kHz */
|
|
}
|
|
if (i == ENVY24_TIMEOUT) {
|
|
return -1;
|
|
}
|
|
envy24_wrcs(sc, ENVY24_CCS_I2CADDR, addr, 1);
|
|
envy24_wrcs(sc, ENVY24_CCS_I2CDATA, data, 1);
|
|
envy24_wrcs(sc, ENVY24_CCS_I2CDEV,
|
|
(dev & ENVY24_CCS_I2CDEV_ADDR) | ENVY24_CCS_I2CDEV_WR, 1);
|
|
for (i = 0; i < ENVY24_TIMEOUT; i++) {
|
|
data = envy24_rdcs(sc, ENVY24_CCS_I2CSTAT, 1);
|
|
if ((data & ENVY24_CCS_I2CSTAT_BSY) == 0)
|
|
break;
|
|
DELAY(32); /* 31.25kHz */
|
|
}
|
|
if (i == ENVY24_TIMEOUT) {
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static int
|
|
envy24_rdrom(struct sc_info *sc, u_int32_t addr)
|
|
{
|
|
u_int32_t data;
|
|
|
|
#if(0)
|
|
device_printf(sc->dev, "envy24_rdrom(sc, 0x%02x)\n", addr);
|
|
#endif
|
|
data = envy24_rdcs(sc, ENVY24_CCS_I2CSTAT, 1);
|
|
if ((data & ENVY24_CCS_I2CSTAT_ROM) == 0) {
|
|
#if(0)
|
|
device_printf(sc->dev, "envy24_rdrom(): E2PROM not presented\n");
|
|
#endif
|
|
return -1;
|
|
}
|
|
|
|
return envy24_rdi2c(sc, ENVY24_CCS_I2CDEV_ROM, addr);
|
|
}
|
|
|
|
static struct cfg_info *
|
|
envy24_rom2cfg(struct sc_info *sc)
|
|
{
|
|
struct cfg_info *buff;
|
|
int size;
|
|
int i;
|
|
|
|
#if(0)
|
|
device_printf(sc->dev, "envy24_rom2cfg(sc)\n");
|
|
#endif
|
|
size = envy24_rdrom(sc, ENVY24_E2PROM_SIZE);
|
|
if (size < ENVY24_E2PROM_GPIODIR + 1) {
|
|
#if(0)
|
|
device_printf(sc->dev, "envy24_rom2cfg(): ENVY24_E2PROM_SIZE-->%d\n", size);
|
|
#endif
|
|
return NULL;
|
|
}
|
|
buff = malloc(sizeof(*buff), M_ENVY24, M_NOWAIT);
|
|
if (buff == NULL) {
|
|
#if(0)
|
|
device_printf(sc->dev, "envy24_rom2cfg(): malloc()\n");
|
|
#endif
|
|
return NULL;
|
|
}
|
|
buff->free = 1;
|
|
|
|
buff->subvendor = envy24_rdrom(sc, ENVY24_E2PROM_SUBVENDOR) << 8;
|
|
buff->subvendor += envy24_rdrom(sc, ENVY24_E2PROM_SUBVENDOR + 1);
|
|
buff->subdevice = envy24_rdrom(sc, ENVY24_E2PROM_SUBDEVICE) << 8;
|
|
buff->subdevice += envy24_rdrom(sc, ENVY24_E2PROM_SUBDEVICE + 1);
|
|
buff->scfg = envy24_rdrom(sc, ENVY24_E2PROM_SCFG);
|
|
buff->acl = envy24_rdrom(sc, ENVY24_E2PROM_ACL);
|
|
buff->i2s = envy24_rdrom(sc, ENVY24_E2PROM_I2S);
|
|
buff->spdif = envy24_rdrom(sc, ENVY24_E2PROM_SPDIF);
|
|
buff->gpiomask = envy24_rdrom(sc, ENVY24_E2PROM_GPIOMASK);
|
|
buff->gpiostate = envy24_rdrom(sc, ENVY24_E2PROM_GPIOSTATE);
|
|
buff->gpiodir = envy24_rdrom(sc, ENVY24_E2PROM_GPIODIR);
|
|
|
|
for (i = 0; cfg_table[i].subvendor != 0 || cfg_table[i].subdevice != 0; i++)
|
|
if (cfg_table[i].subvendor == buff->subvendor &&
|
|
cfg_table[i].subdevice == buff->subdevice)
|
|
break;
|
|
buff->name = cfg_table[i].name;
|
|
buff->codec = cfg_table[i].codec;
|
|
|
|
return buff;
|
|
}
|
|
|
|
static void
|
|
envy24_cfgfree(struct cfg_info *cfg) {
|
|
if (cfg == NULL)
|
|
return;
|
|
if (cfg->free)
|
|
free(cfg, M_ENVY24);
|
|
return;
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
/* AC'97 codec access routines */
|
|
|
|
#if 0
|
|
static int
|
|
envy24_coldcd(struct sc_info *sc)
|
|
{
|
|
u_int32_t data;
|
|
int i;
|
|
|
|
#if(0)
|
|
device_printf(sc->dev, "envy24_coldcd()\n");
|
|
#endif
|
|
envy24_wrmt(sc, ENVY24_MT_AC97CMD, ENVY24_MT_AC97CMD_CLD, 1);
|
|
DELAY(10);
|
|
envy24_wrmt(sc, ENVY24_MT_AC97CMD, 0, 1);
|
|
DELAY(1000);
|
|
for (i = 0; i < ENVY24_TIMEOUT; i++) {
|
|
data = envy24_rdmt(sc, ENVY24_MT_AC97CMD, 1);
|
|
if (data & ENVY24_MT_AC97CMD_RDY) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
#endif
|
|
|
|
static int
|
|
envy24_slavecd(struct sc_info *sc)
|
|
{
|
|
u_int32_t data;
|
|
int i;
|
|
|
|
#if(0)
|
|
device_printf(sc->dev, "envy24_slavecd()\n");
|
|
#endif
|
|
envy24_wrmt(sc, ENVY24_MT_AC97CMD,
|
|
ENVY24_MT_AC97CMD_CLD | ENVY24_MT_AC97CMD_WRM, 1);
|
|
DELAY(10);
|
|
envy24_wrmt(sc, ENVY24_MT_AC97CMD, 0, 1);
|
|
DELAY(1000);
|
|
for (i = 0; i < ENVY24_TIMEOUT; i++) {
|
|
data = envy24_rdmt(sc, ENVY24_MT_AC97CMD, 1);
|
|
if (data & ENVY24_MT_AC97CMD_RDY) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
#if 0
|
|
static int
|
|
envy24_rdcd(kobj_t obj, void *devinfo, int regno)
|
|
{
|
|
struct sc_info *sc = (struct sc_info *)devinfo;
|
|
u_int32_t data;
|
|
int i;
|
|
|
|
#if(0)
|
|
device_printf(sc->dev, "envy24_rdcd(obj, sc, 0x%02x)\n", regno);
|
|
#endif
|
|
envy24_wrmt(sc, ENVY24_MT_AC97IDX, (u_int32_t)regno, 1);
|
|
envy24_wrmt(sc, ENVY24_MT_AC97CMD, ENVY24_MT_AC97CMD_RD, 1);
|
|
for (i = 0; i < ENVY24_TIMEOUT; i++) {
|
|
data = envy24_rdmt(sc, ENVY24_MT_AC97CMD, 1);
|
|
if ((data & ENVY24_MT_AC97CMD_RD) == 0)
|
|
break;
|
|
}
|
|
data = envy24_rdmt(sc, ENVY24_MT_AC97DLO, 2);
|
|
|
|
#if(0)
|
|
device_printf(sc->dev, "envy24_rdcd(): return 0x%x\n", data);
|
|
#endif
|
|
return (int)data;
|
|
}
|
|
|
|
static int
|
|
envy24_wrcd(kobj_t obj, void *devinfo, int regno, u_int16_t data)
|
|
{
|
|
struct sc_info *sc = (struct sc_info *)devinfo;
|
|
u_int32_t cmd;
|
|
int i;
|
|
|
|
#if(0)
|
|
device_printf(sc->dev, "envy24_wrcd(obj, sc, 0x%02x, 0x%04x)\n", regno, data);
|
|
#endif
|
|
envy24_wrmt(sc, ENVY24_MT_AC97IDX, (u_int32_t)regno, 1);
|
|
envy24_wrmt(sc, ENVY24_MT_AC97DLO, (u_int32_t)data, 2);
|
|
envy24_wrmt(sc, ENVY24_MT_AC97CMD, ENVY24_MT_AC97CMD_WR, 1);
|
|
for (i = 0; i < ENVY24_TIMEOUT; i++) {
|
|
cmd = envy24_rdmt(sc, ENVY24_MT_AC97CMD, 1);
|
|
if ((cmd & ENVY24_MT_AC97CMD_WR) == 0)
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static kobj_method_t envy24_ac97_methods[] = {
|
|
KOBJMETHOD(ac97_read, envy24_rdcd),
|
|
KOBJMETHOD(ac97_write, envy24_wrcd),
|
|
{0, 0}
|
|
};
|
|
AC97_DECLARE(envy24_ac97);
|
|
#endif
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
/* GPIO access routines */
|
|
|
|
static u_int32_t
|
|
envy24_gpiord(struct sc_info *sc)
|
|
{
|
|
return envy24_rdci(sc, ENVY24_CCI_GPIODAT);
|
|
}
|
|
|
|
static void
|
|
envy24_gpiowr(struct sc_info *sc, u_int32_t data)
|
|
{
|
|
#if(0)
|
|
device_printf(sc->dev, "envy24_gpiowr(sc, 0x%02x)\n", data & 0xff);
|
|
return;
|
|
#endif
|
|
envy24_wrci(sc, ENVY24_CCI_GPIODAT, data);
|
|
return;
|
|
}
|
|
|
|
#if 0
|
|
static u_int32_t
|
|
envy24_gpiogetmask(struct sc_info *sc)
|
|
{
|
|
return envy24_rdci(sc, ENVY24_CCI_GPIOMASK);
|
|
}
|
|
#endif
|
|
|
|
static void
|
|
envy24_gpiosetmask(struct sc_info *sc, u_int32_t mask)
|
|
{
|
|
envy24_wrci(sc, ENVY24_CCI_GPIOMASK, mask);
|
|
return;
|
|
}
|
|
|
|
#if 0
|
|
static u_int32_t
|
|
envy24_gpiogetdir(struct sc_info *sc)
|
|
{
|
|
return envy24_rdci(sc, ENVY24_CCI_GPIOCTL);
|
|
}
|
|
#endif
|
|
|
|
static void
|
|
envy24_gpiosetdir(struct sc_info *sc, u_int32_t dir)
|
|
{
|
|
envy24_wrci(sc, ENVY24_CCI_GPIOCTL, dir);
|
|
return;
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
/* M-Audio Delta series AK4524 access interface routine */
|
|
|
|
struct envy24_delta_ak4524_codec {
|
|
struct spicds_info *info;
|
|
struct sc_info *parent;
|
|
int dir;
|
|
int num;
|
|
int cs, cclk, cdti;
|
|
};
|
|
|
|
static void
|
|
envy24_delta_ak4524_ctl(void *codec, unsigned int cs, unsigned int cclk, unsigned int cdti)
|
|
{
|
|
u_int32_t data = 0;
|
|
struct envy24_delta_ak4524_codec *ptr = codec;
|
|
|
|
#if(0)
|
|
device_printf(ptr->parent->dev, "--> %d, %d, %d\n", cs, cclk, cdti);
|
|
#endif
|
|
data = envy24_gpiord(ptr->parent);
|
|
data &= ~(ptr->cs | ptr->cclk | ptr->cdti);
|
|
if (cs) data += ptr->cs;
|
|
if (cclk) data += ptr->cclk;
|
|
if (cdti) data += ptr->cdti;
|
|
envy24_gpiowr(ptr->parent, data);
|
|
return;
|
|
}
|
|
|
|
static void *
|
|
envy24_delta_ak4524_create(device_t dev, void *info, int dir, int num)
|
|
{
|
|
struct sc_info *sc = info;
|
|
struct envy24_delta_ak4524_codec *buff = NULL;
|
|
|
|
#if(0)
|
|
device_printf(sc->dev, "envy24_delta_ak4524_create(dev, sc, %d, %d)\n", dir, num);
|
|
#endif
|
|
|
|
buff = malloc(sizeof(*buff), M_ENVY24, M_NOWAIT);
|
|
if (buff == NULL)
|
|
return NULL;
|
|
|
|
if (dir == PCMDIR_REC && sc->adc[num] != NULL)
|
|
buff->info = ((struct envy24_delta_ak4524_codec *)sc->adc[num])->info;
|
|
else if (dir == PCMDIR_PLAY && sc->dac[num] != NULL)
|
|
buff->info = ((struct envy24_delta_ak4524_codec *)sc->dac[num])->info;
|
|
else
|
|
buff->info = spicds_create(dev, buff, num, envy24_delta_ak4524_ctl);
|
|
if (buff->info == NULL) {
|
|
free(buff, M_ENVY24);
|
|
return NULL;
|
|
}
|
|
|
|
buff->parent = sc;
|
|
buff->dir = dir;
|
|
buff->num = num;
|
|
|
|
return (void *)buff;
|
|
}
|
|
|
|
static void
|
|
envy24_delta_ak4524_destroy(void *codec)
|
|
{
|
|
struct envy24_delta_ak4524_codec *ptr = codec;
|
|
if (ptr == NULL)
|
|
return;
|
|
#if(0)
|
|
device_printf(ptr->parent->dev, "envy24_delta_ak4524_destroy()\n");
|
|
#endif
|
|
|
|
if (ptr->dir == PCMDIR_PLAY) {
|
|
if (ptr->parent->dac[ptr->num] != NULL)
|
|
spicds_destroy(ptr->info);
|
|
}
|
|
else {
|
|
if (ptr->parent->adc[ptr->num] != NULL)
|
|
spicds_destroy(ptr->info);
|
|
}
|
|
|
|
free(codec, M_ENVY24);
|
|
}
|
|
|
|
static void
|
|
envy24_delta_ak4524_init(void *codec)
|
|
{
|
|
#if 0
|
|
u_int32_t gpiomask, gpiodir;
|
|
#endif
|
|
struct envy24_delta_ak4524_codec *ptr = codec;
|
|
if (ptr == NULL)
|
|
return;
|
|
#if(0)
|
|
device_printf(ptr->parent->dev, "envy24_delta_ak4524_init()\n");
|
|
#endif
|
|
|
|
/*
|
|
gpiomask = envy24_gpiogetmask(ptr->parent);
|
|
gpiomask &= ~(ENVY24_GPIO_AK4524_CDTI | ENVY24_GPIO_AK4524_CCLK | ENVY24_GPIO_AK4524_CS0 | ENVY24_GPIO_AK4524_CS1);
|
|
envy24_gpiosetmask(ptr->parent, gpiomask);
|
|
gpiodir = envy24_gpiogetdir(ptr->parent);
|
|
gpiodir |= ENVY24_GPIO_AK4524_CDTI | ENVY24_GPIO_AK4524_CCLK | ENVY24_GPIO_AK4524_CS0 | ENVY24_GPIO_AK4524_CS1;
|
|
envy24_gpiosetdir(ptr->parent, gpiodir);
|
|
*/
|
|
ptr->cs = ptr->parent->cfg->cs;
|
|
#if 0
|
|
envy24_gpiosetmask(ptr->parent, ENVY24_GPIO_CS8414_STATUS);
|
|
envy24_gpiosetdir(ptr->parent, ~ENVY24_GPIO_CS8414_STATUS);
|
|
if (ptr->num == 0)
|
|
ptr->cs = ENVY24_GPIO_AK4524_CS0;
|
|
else
|
|
ptr->cs = ENVY24_GPIO_AK4524_CS1;
|
|
ptr->cclk = ENVY24_GPIO_AK4524_CCLK;
|
|
#endif
|
|
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);
|
|
spicds_setformat(ptr->info,
|
|
AK452X_FORMAT_I2S | AK452X_FORMAT_256FSN | AK452X_FORMAT_1X);
|
|
spicds_setdvc(ptr->info, 0);
|
|
/* for the time being, init only first codec */
|
|
if (ptr->num == 0)
|
|
spicds_init(ptr->info);
|
|
}
|
|
|
|
static void
|
|
envy24_delta_ak4524_reinit(void *codec)
|
|
{
|
|
struct envy24_delta_ak4524_codec *ptr = codec;
|
|
if (ptr == NULL)
|
|
return;
|
|
#if(0)
|
|
device_printf(ptr->parent->dev, "envy24_delta_ak4524_reinit()\n");
|
|
#endif
|
|
|
|
spicds_reinit(ptr->info);
|
|
}
|
|
|
|
static void
|
|
envy24_delta_ak4524_setvolume(void *codec, int dir, unsigned int left, unsigned int right)
|
|
{
|
|
struct envy24_delta_ak4524_codec *ptr = codec;
|
|
if (ptr == NULL)
|
|
return;
|
|
#if(0)
|
|
device_printf(ptr->parent->dev, "envy24_delta_ak4524_set()\n");
|
|
#endif
|
|
|
|
spicds_set(ptr->info, dir, left, right);
|
|
}
|
|
|
|
/*
|
|
There is no need for AK452[48] codec to set sample rate
|
|
static void
|
|
envy24_delta_ak4524_setrate(struct envy24_delta_ak4524_codec *codec, int which, int rate)
|
|
{
|
|
}
|
|
*/
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
/* hardware access routeines */
|
|
|
|
static struct {
|
|
u_int32_t speed;
|
|
u_int32_t code;
|
|
} envy24_speedtab[] = {
|
|
{48000, ENVY24_MT_RATE_48000},
|
|
{24000, ENVY24_MT_RATE_24000},
|
|
{12000, ENVY24_MT_RATE_12000},
|
|
{9600, ENVY24_MT_RATE_9600},
|
|
{32000, ENVY24_MT_RATE_32000},
|
|
{16000, ENVY24_MT_RATE_16000},
|
|
{8000, ENVY24_MT_RATE_8000},
|
|
{96000, ENVY24_MT_RATE_96000},
|
|
{64000, ENVY24_MT_RATE_64000},
|
|
{44100, ENVY24_MT_RATE_44100},
|
|
{22050, ENVY24_MT_RATE_22050},
|
|
{11025, ENVY24_MT_RATE_11025},
|
|
{88200, ENVY24_MT_RATE_88200},
|
|
{0, 0x10}
|
|
};
|
|
|
|
static int
|
|
envy24_setspeed(struct sc_info *sc, u_int32_t speed) {
|
|
u_int32_t code;
|
|
int i = 0;
|
|
|
|
#if(0)
|
|
device_printf(sc->dev, "envy24_setspeed(sc, %d)\n", speed);
|
|
#endif
|
|
if (speed == 0) {
|
|
code = ENVY24_MT_RATE_SPDIF; /* external master clock */
|
|
envy24_slavecd(sc);
|
|
}
|
|
else {
|
|
for (i = 0; envy24_speedtab[i].speed != 0; i++) {
|
|
if (envy24_speedtab[i].speed == speed)
|
|
break;
|
|
}
|
|
code = envy24_speedtab[i].code;
|
|
}
|
|
#if(0)
|
|
device_printf(sc->dev, "envy24_setspeed(): speed %d/code 0x%04x\n", envy24_speedtab[i].speed, code);
|
|
#endif
|
|
if (code < 0x10) {
|
|
envy24_wrmt(sc, ENVY24_MT_RATE, code, 1);
|
|
code = envy24_rdmt(sc, ENVY24_MT_RATE, 1);
|
|
code &= ENVY24_MT_RATE_MASK;
|
|
for (i = 0; envy24_speedtab[i].code < 0x10; i++) {
|
|
if (envy24_speedtab[i].code == code)
|
|
break;
|
|
}
|
|
speed = envy24_speedtab[i].speed;
|
|
}
|
|
else
|
|
speed = 0;
|
|
|
|
#if(0)
|
|
device_printf(sc->dev, "envy24_setspeed(): return %d\n", speed);
|
|
#endif
|
|
return speed;
|
|
}
|
|
|
|
static void
|
|
envy24_setvolume(struct sc_info *sc, unsigned ch)
|
|
{
|
|
#if(0)
|
|
device_printf(sc->dev, "envy24_setvolume(sc, %d)\n", ch);
|
|
#endif
|
|
if (sc->cfg->subvendor==0x153b && sc->cfg->subdevice==0x1138 ) {
|
|
envy24_wrmt(sc, ENVY24_MT_VOLIDX, 16, 1);
|
|
envy24_wrmt(sc, ENVY24_MT_VOLUME, 0x7f7f, 2);
|
|
envy24_wrmt(sc, ENVY24_MT_VOLIDX, 17, 1);
|
|
envy24_wrmt(sc, ENVY24_MT_VOLUME, 0x7f7f, 2);
|
|
}
|
|
|
|
envy24_wrmt(sc, ENVY24_MT_VOLIDX, ch * 2, 1);
|
|
envy24_wrmt(sc, ENVY24_MT_VOLUME, 0x7f00 | sc->left[ch], 2);
|
|
envy24_wrmt(sc, ENVY24_MT_VOLIDX, ch * 2 + 1, 1);
|
|
envy24_wrmt(sc, ENVY24_MT_VOLUME, (sc->right[ch] << 8) | 0x7f, 2);
|
|
}
|
|
|
|
static void
|
|
envy24_mutevolume(struct sc_info *sc, unsigned ch)
|
|
{
|
|
u_int32_t vol;
|
|
|
|
#if(0)
|
|
device_printf(sc->dev, "envy24_mutevolume(sc, %d)\n", ch);
|
|
#endif
|
|
vol = ENVY24_VOL_MUTE << 8 | ENVY24_VOL_MUTE;
|
|
envy24_wrmt(sc, ENVY24_MT_VOLIDX, ch * 2, 1);
|
|
envy24_wrmt(sc, ENVY24_MT_VOLUME, vol, 2);
|
|
envy24_wrmt(sc, ENVY24_MT_VOLIDX, ch * 2 + 1, 1);
|
|
envy24_wrmt(sc, ENVY24_MT_VOLUME, vol, 2);
|
|
}
|
|
|
|
static u_int32_t
|
|
envy24_gethwptr(struct sc_info *sc, int dir)
|
|
{
|
|
int unit, regno;
|
|
u_int32_t ptr, rtn;
|
|
|
|
#if(0)
|
|
device_printf(sc->dev, "envy24_gethwptr(sc, %d)\n", dir);
|
|
#endif
|
|
if (dir == PCMDIR_PLAY) {
|
|
rtn = sc->psize / 4;
|
|
unit = ENVY24_PLAY_BUFUNIT / 4;
|
|
regno = ENVY24_MT_PCNT;
|
|
}
|
|
else {
|
|
rtn = sc->rsize / 4;
|
|
unit = ENVY24_REC_BUFUNIT / 4;
|
|
regno = ENVY24_MT_RCNT;
|
|
}
|
|
|
|
ptr = envy24_rdmt(sc, regno, 2);
|
|
rtn -= (ptr + 1);
|
|
rtn /= unit;
|
|
|
|
#if(0)
|
|
device_printf(sc->dev, "envy24_gethwptr(): return %d\n", rtn);
|
|
#endif
|
|
return rtn;
|
|
}
|
|
|
|
static void
|
|
envy24_updintr(struct sc_info *sc, int dir)
|
|
{
|
|
int regptr, regintr;
|
|
u_int32_t mask, intr;
|
|
u_int32_t ptr, size, cnt;
|
|
u_int16_t blk;
|
|
|
|
#if(0)
|
|
device_printf(sc->dev, "envy24_updintr(sc, %d)\n", dir);
|
|
#endif
|
|
if (dir == PCMDIR_PLAY) {
|
|
blk = sc->blk[0];
|
|
size = sc->psize / 4;
|
|
regptr = ENVY24_MT_PCNT;
|
|
regintr = ENVY24_MT_PTERM;
|
|
mask = ~ENVY24_MT_INT_PMASK;
|
|
}
|
|
else {
|
|
blk = sc->blk[1];
|
|
size = sc->rsize / 4;
|
|
regptr = ENVY24_MT_RCNT;
|
|
regintr = ENVY24_MT_RTERM;
|
|
mask = ~ENVY24_MT_INT_RMASK;
|
|
}
|
|
|
|
ptr = size - envy24_rdmt(sc, regptr, 2) - 1;
|
|
/*
|
|
cnt = blk - ptr % blk - 1;
|
|
if (cnt == 0)
|
|
cnt = blk - 1;
|
|
*/
|
|
cnt = blk - 1;
|
|
#if(0)
|
|
device_printf(sc->dev, "envy24_updintr():ptr = %d, blk = %d, cnt = %d\n", ptr, blk, cnt);
|
|
#endif
|
|
envy24_wrmt(sc, regintr, cnt, 2);
|
|
intr = envy24_rdmt(sc, ENVY24_MT_INT, 1);
|
|
#if(0)
|
|
device_printf(sc->dev, "envy24_updintr():intr = 0x%02x, mask = 0x%02x\n", intr, mask);
|
|
#endif
|
|
envy24_wrmt(sc, ENVY24_MT_INT, intr & mask, 1);
|
|
#if(0)
|
|
device_printf(sc->dev, "envy24_updintr():INT-->0x%02x\n",
|
|
envy24_rdmt(sc, ENVY24_MT_INT, 1));
|
|
#endif
|
|
|
|
return;
|
|
}
|
|
|
|
#if 0
|
|
static void
|
|
envy24_maskintr(struct sc_info *sc, int dir)
|
|
{
|
|
u_int32_t mask, intr;
|
|
|
|
#if(0)
|
|
device_printf(sc->dev, "envy24_maskintr(sc, %d)\n", dir);
|
|
#endif
|
|
if (dir == PCMDIR_PLAY)
|
|
mask = ENVY24_MT_INT_PMASK;
|
|
else
|
|
mask = ENVY24_MT_INT_RMASK;
|
|
intr = envy24_rdmt(sc, ENVY24_MT_INT, 1);
|
|
envy24_wrmt(sc, ENVY24_MT_INT, intr | mask, 1);
|
|
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
static int
|
|
envy24_checkintr(struct sc_info *sc, int dir)
|
|
{
|
|
u_int32_t mask, stat, intr, rtn;
|
|
|
|
#if(0)
|
|
device_printf(sc->dev, "envy24_checkintr(sc, %d)\n", dir);
|
|
#endif
|
|
intr = envy24_rdmt(sc, ENVY24_MT_INT, 1);
|
|
if (dir == PCMDIR_PLAY) {
|
|
if ((rtn = intr & ENVY24_MT_INT_PSTAT) != 0) {
|
|
mask = ~ENVY24_MT_INT_RSTAT;
|
|
stat = ENVY24_MT_INT_PSTAT | ENVY24_MT_INT_PMASK;
|
|
envy24_wrmt(sc, ENVY24_MT_INT, (intr & mask) | stat, 1);
|
|
}
|
|
}
|
|
else {
|
|
if ((rtn = intr & ENVY24_MT_INT_RSTAT) != 0) {
|
|
mask = ~ENVY24_MT_INT_PSTAT;
|
|
stat = ENVY24_MT_INT_RSTAT | ENVY24_MT_INT_RMASK;
|
|
envy24_wrmt(sc, ENVY24_MT_INT, (intr & mask) | stat, 1);
|
|
}
|
|
}
|
|
|
|
return rtn;
|
|
}
|
|
|
|
static void
|
|
envy24_start(struct sc_info *sc, int dir)
|
|
{
|
|
u_int32_t stat, sw;
|
|
|
|
#if(0)
|
|
device_printf(sc->dev, "envy24_start(sc, %d)\n", dir);
|
|
#endif
|
|
if (dir == PCMDIR_PLAY)
|
|
sw = ENVY24_MT_PCTL_PSTART;
|
|
else
|
|
sw = ENVY24_MT_PCTL_RSTART;
|
|
|
|
stat = envy24_rdmt(sc, ENVY24_MT_PCTL, 1);
|
|
envy24_wrmt(sc, ENVY24_MT_PCTL, stat | sw, 1);
|
|
#if(0)
|
|
DELAY(100);
|
|
device_printf(sc->dev, "PADDR:0x%08x\n", envy24_rdmt(sc, ENVY24_MT_PADDR, 4));
|
|
device_printf(sc->dev, "PCNT:%ld\n", envy24_rdmt(sc, ENVY24_MT_PCNT, 2));
|
|
#endif
|
|
|
|
return;
|
|
}
|
|
|
|
static void
|
|
envy24_stop(struct sc_info *sc, int dir)
|
|
{
|
|
u_int32_t stat, sw;
|
|
|
|
#if(0)
|
|
device_printf(sc->dev, "envy24_stop(sc, %d)\n", dir);
|
|
#endif
|
|
if (dir == PCMDIR_PLAY)
|
|
sw = ~ENVY24_MT_PCTL_PSTART;
|
|
else
|
|
sw = ~ENVY24_MT_PCTL_RSTART;
|
|
|
|
stat = envy24_rdmt(sc, ENVY24_MT_PCTL, 1);
|
|
envy24_wrmt(sc, ENVY24_MT_PCTL, stat & sw, 1);
|
|
|
|
return;
|
|
}
|
|
|
|
static int
|
|
envy24_route(struct sc_info *sc, int dac, int class, int adc, int rev)
|
|
{
|
|
u_int32_t reg, mask;
|
|
u_int32_t left, right;
|
|
|
|
#if(0)
|
|
device_printf(sc->dev, "envy24_route(sc, %d, %d, %d, %d)\n",
|
|
dac, class, adc, rev);
|
|
#endif
|
|
/* parameter pattern check */
|
|
if (dac < 0 || ENVY24_ROUTE_DAC_SPDIF < dac)
|
|
return -1;
|
|
if (class == ENVY24_ROUTE_CLASS_MIX &&
|
|
(dac != ENVY24_ROUTE_DAC_1 && dac != ENVY24_ROUTE_DAC_SPDIF))
|
|
return -1;
|
|
if (rev) {
|
|
left = ENVY24_ROUTE_RIGHT;
|
|
right = ENVY24_ROUTE_LEFT;
|
|
}
|
|
else {
|
|
left = ENVY24_ROUTE_LEFT;
|
|
right = ENVY24_ROUTE_RIGHT;
|
|
}
|
|
|
|
if (dac == ENVY24_ROUTE_DAC_SPDIF) {
|
|
reg = class | class << 2 |
|
|
((adc << 1 | left) | left << 3) << 8 |
|
|
((adc << 1 | right) | right << 3) << 12;
|
|
#if(0)
|
|
device_printf(sc->dev, "envy24_route(): MT_SPDOUT-->0x%04x\n", reg);
|
|
#endif
|
|
envy24_wrmt(sc, ENVY24_MT_SPDOUT, reg, 2);
|
|
}
|
|
else {
|
|
mask = ~(0x0303 << dac * 2);
|
|
reg = envy24_rdmt(sc, ENVY24_MT_PSDOUT, 2);
|
|
reg = (reg & mask) | ((class | class << 8) << dac * 2);
|
|
#if(0)
|
|
device_printf(sc->dev, "envy24_route(): MT_PSDOUT-->0x%04x\n", reg);
|
|
#endif
|
|
envy24_wrmt(sc, ENVY24_MT_PSDOUT, reg, 2);
|
|
mask = ~(0xff << dac * 8);
|
|
reg = envy24_rdmt(sc, ENVY24_MT_RECORD, 4);
|
|
reg = (reg & mask) |
|
|
(((adc << 1 | left) | left << 3) |
|
|
((adc << 1 | right) | right << 3) << 4) << dac * 8;
|
|
#if(0)
|
|
device_printf(sc->dev, "envy24_route(): MT_RECORD-->0x%08x\n", reg);
|
|
#endif
|
|
envy24_wrmt(sc, ENVY24_MT_RECORD, reg, 4);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
/* buffer copy routines */
|
|
static void
|
|
envy24_p32sl(struct sc_chinfo *ch)
|
|
{
|
|
int length;
|
|
sample32_t *dmabuf;
|
|
u_int32_t *data;
|
|
int src, dst, ssize, dsize, slot;
|
|
int i;
|
|
|
|
length = sndbuf_getready(ch->buffer) / 8;
|
|
dmabuf = ch->parent->pbuf;
|
|
data = (u_int32_t *)ch->data;
|
|
src = sndbuf_getreadyptr(ch->buffer) / 4;
|
|
dst = src / 2 + ch->offset;
|
|
ssize = ch->size / 4;
|
|
dsize = ch->size / 8;
|
|
slot = ch->num * 2;
|
|
|
|
for (i = 0; i < length; i++) {
|
|
dmabuf[dst * ENVY24_PLAY_CHNUM + slot].buffer = data[src];
|
|
dmabuf[dst * ENVY24_PLAY_CHNUM + slot + 1].buffer = data[src + 1];
|
|
dst++;
|
|
dst %= dsize;
|
|
src += 2;
|
|
src %= ssize;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
static void
|
|
envy24_p16sl(struct sc_chinfo *ch)
|
|
{
|
|
int length;
|
|
sample32_t *dmabuf;
|
|
u_int16_t *data;
|
|
int src, dst, ssize, dsize, slot;
|
|
int i;
|
|
|
|
#if(0)
|
|
device_printf(ch->parent->dev, "envy24_p16sl()\n");
|
|
#endif
|
|
length = sndbuf_getready(ch->buffer) / 4;
|
|
dmabuf = ch->parent->pbuf;
|
|
data = (u_int16_t *)ch->data;
|
|
src = sndbuf_getreadyptr(ch->buffer) / 2;
|
|
dst = src / 2 + ch->offset;
|
|
ssize = ch->size / 2;
|
|
dsize = ch->size / 4;
|
|
slot = ch->num * 2;
|
|
#if(0)
|
|
device_printf(ch->parent->dev, "envy24_p16sl():%lu-->%lu(%lu)\n", src, dst, length);
|
|
#endif
|
|
|
|
for (i = 0; i < length; i++) {
|
|
dmabuf[dst * ENVY24_PLAY_CHNUM + slot].buffer = (u_int32_t)data[src] << 16;
|
|
dmabuf[dst * ENVY24_PLAY_CHNUM + slot + 1].buffer = (u_int32_t)data[src + 1] << 16;
|
|
#if(0)
|
|
if (i < 16) {
|
|
printf("%08x", dmabuf[dst * ENVY24_PLAY_CHNUM + slot]);
|
|
printf("%08x", dmabuf[dst * ENVY24_PLAY_CHNUM + slot + 1]);
|
|
}
|
|
#endif
|
|
dst++;
|
|
dst %= dsize;
|
|
src += 2;
|
|
src %= ssize;
|
|
}
|
|
#if(0)
|
|
printf("\n");
|
|
#endif
|
|
|
|
return;
|
|
}
|
|
|
|
static void
|
|
envy24_p8u(struct sc_chinfo *ch)
|
|
{
|
|
int length;
|
|
sample32_t *dmabuf;
|
|
u_int8_t *data;
|
|
int src, dst, ssize, dsize, slot;
|
|
int i;
|
|
|
|
length = sndbuf_getready(ch->buffer) / 2;
|
|
dmabuf = ch->parent->pbuf;
|
|
data = (u_int8_t *)ch->data;
|
|
src = sndbuf_getreadyptr(ch->buffer);
|
|
dst = src / 2 + ch->offset;
|
|
ssize = ch->size;
|
|
dsize = ch->size / 4;
|
|
slot = ch->num * 2;
|
|
|
|
for (i = 0; i < length; i++) {
|
|
dmabuf[dst * ENVY24_PLAY_CHNUM + slot].buffer = ((u_int32_t)data[src] ^ 0x80) << 24;
|
|
dmabuf[dst * ENVY24_PLAY_CHNUM + slot + 1].buffer = ((u_int32_t)data[src + 1] ^ 0x80) << 24;
|
|
dst++;
|
|
dst %= dsize;
|
|
src += 2;
|
|
src %= ssize;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
static void
|
|
envy24_r32sl(struct sc_chinfo *ch)
|
|
{
|
|
int length;
|
|
sample32_t *dmabuf;
|
|
u_int32_t *data;
|
|
int src, dst, ssize, dsize, slot;
|
|
int i;
|
|
|
|
length = sndbuf_getfree(ch->buffer) / 8;
|
|
dmabuf = ch->parent->rbuf;
|
|
data = (u_int32_t *)ch->data;
|
|
dst = sndbuf_getfreeptr(ch->buffer) / 4;
|
|
src = dst / 2 + ch->offset;
|
|
dsize = ch->size / 4;
|
|
ssize = ch->size / 8;
|
|
slot = (ch->num - ENVY24_CHAN_REC_ADC1) * 2;
|
|
|
|
for (i = 0; i < length; i++) {
|
|
data[dst] = dmabuf[src * ENVY24_REC_CHNUM + slot].buffer;
|
|
data[dst + 1] = dmabuf[src * ENVY24_REC_CHNUM + slot + 1].buffer;
|
|
dst += 2;
|
|
dst %= dsize;
|
|
src++;
|
|
src %= ssize;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
static void
|
|
envy24_r16sl(struct sc_chinfo *ch)
|
|
{
|
|
int length;
|
|
sample32_t *dmabuf;
|
|
u_int16_t *data;
|
|
int src, dst, ssize, dsize, slot;
|
|
int i;
|
|
|
|
length = sndbuf_getfree(ch->buffer) / 4;
|
|
dmabuf = ch->parent->rbuf;
|
|
data = (u_int16_t *)ch->data;
|
|
dst = sndbuf_getfreeptr(ch->buffer) / 2;
|
|
src = dst / 2 + ch->offset;
|
|
dsize = ch->size / 2;
|
|
ssize = ch->size / 8;
|
|
slot = (ch->num - ENVY24_CHAN_REC_ADC1) * 2;
|
|
|
|
for (i = 0; i < length; i++) {
|
|
data[dst] = dmabuf[src * ENVY24_REC_CHNUM + slot].buffer;
|
|
data[dst + 1] = dmabuf[src * ENVY24_REC_CHNUM + slot + 1].buffer;
|
|
dst += 2;
|
|
dst %= dsize;
|
|
src++;
|
|
src %= ssize;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
/* channel interface */
|
|
static void *
|
|
envy24chan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir)
|
|
{
|
|
struct sc_info *sc = (struct sc_info *)devinfo;
|
|
struct sc_chinfo *ch;
|
|
unsigned num;
|
|
|
|
#if(0)
|
|
device_printf(sc->dev, "envy24chan_init(obj, devinfo, b, c, %d)\n", dir);
|
|
#endif
|
|
snd_mtxlock(sc->lock);
|
|
if ((sc->chnum > ENVY24_CHAN_PLAY_SPDIF && dir != PCMDIR_REC) ||
|
|
(sc->chnum < ENVY24_CHAN_REC_ADC1 && dir != PCMDIR_PLAY)) {
|
|
snd_mtxunlock(sc->lock);
|
|
return NULL;
|
|
}
|
|
num = sc->chnum;
|
|
|
|
ch = &sc->chan[num];
|
|
ch->size = 8 * ENVY24_SAMPLE_NUM;
|
|
ch->data = malloc(ch->size, M_ENVY24, M_NOWAIT);
|
|
if (ch->data == NULL) {
|
|
ch->size = 0;
|
|
ch = NULL;
|
|
}
|
|
else {
|
|
ch->buffer = b;
|
|
ch->channel = c;
|
|
ch->parent = sc;
|
|
ch->dir = dir;
|
|
/* set channel map */
|
|
ch->num = envy24_chanmap[num];
|
|
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
|
|
envy24chan_free(kobj_t obj, void *data)
|
|
{
|
|
struct sc_chinfo *ch = data;
|
|
struct sc_info *sc = ch->parent;
|
|
|
|
#if(0)
|
|
device_printf(sc->dev, "envy24chan_free()\n");
|
|
#endif
|
|
snd_mtxlock(sc->lock);
|
|
if (ch->data != NULL) {
|
|
free(ch->data, M_ENVY24);
|
|
ch->data = NULL;
|
|
}
|
|
snd_mtxunlock(sc->lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
envy24chan_setformat(kobj_t obj, void *data, u_int32_t format)
|
|
{
|
|
struct sc_chinfo *ch = data;
|
|
struct sc_info *sc = ch->parent;
|
|
struct envy24_emldma *emltab;
|
|
/* unsigned int bcnt, bsize; */
|
|
int i;
|
|
|
|
#if(0)
|
|
device_printf(sc->dev, "envy24chan_setformat(obj, data, 0x%08x)\n", format);
|
|
#endif
|
|
snd_mtxlock(sc->lock);
|
|
/* check and get format related information */
|
|
if (ch->dir == PCMDIR_PLAY)
|
|
emltab = envy24_pemltab;
|
|
else
|
|
emltab = envy24_remltab;
|
|
if (emltab == NULL) {
|
|
snd_mtxunlock(sc->lock);
|
|
return -1;
|
|
}
|
|
for (i = 0; emltab[i].format != 0; i++)
|
|
if (emltab[i].format == format)
|
|
break;
|
|
if (emltab[i].format == 0) {
|
|
snd_mtxunlock(sc->lock);
|
|
return -1;
|
|
}
|
|
|
|
/* set format information */
|
|
ch->format = format;
|
|
ch->emldma = emltab[i].emldma;
|
|
if (ch->unit > emltab[i].unit)
|
|
ch->blk *= ch->unit / emltab[i].unit;
|
|
else
|
|
ch->blk /= emltab[i].unit / ch->unit;
|
|
ch->unit = emltab[i].unit;
|
|
|
|
/* set channel buffer information */
|
|
ch->size = ch->unit * ENVY24_SAMPLE_NUM;
|
|
#if 0
|
|
if (ch->dir == PCMDIR_PLAY)
|
|
bsize = ch->blk * 4 / ENVY24_PLAY_BUFUNIT;
|
|
else
|
|
bsize = ch->blk * 4 / ENVY24_REC_BUFUNIT;
|
|
bsize *= ch->unit;
|
|
bcnt = ch->size / bsize;
|
|
sndbuf_resize(ch->buffer, bcnt, bsize);
|
|
#endif
|
|
snd_mtxunlock(sc->lock);
|
|
|
|
#if(0)
|
|
device_printf(sc->dev, "envy24chan_setformat(): return 0x%08x\n", 0);
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
IMPLEMENT NOTICE: In this driver, setspeed function only do setting
|
|
of speed information value. And real hardware speed setting is done
|
|
at start triggered(see envy24chan_trigger()). So, at this function
|
|
is called, any value that ENVY24 can use is able to set. But, at
|
|
start triggerd, some other channel is running, and that channel's
|
|
speed isn't same with, then trigger function will fail.
|
|
*/
|
|
static int
|
|
envy24chan_setspeed(kobj_t obj, void *data, u_int32_t speed)
|
|
{
|
|
struct sc_chinfo *ch = data;
|
|
u_int32_t val, prev;
|
|
int i;
|
|
|
|
#if(0)
|
|
device_printf(ch->parent->dev, "envy24chan_setspeed(obj, data, %d)\n", speed);
|
|
#endif
|
|
prev = 0x7fffffff;
|
|
for (i = 0; (val = envy24_speed[i]) != 0; i++) {
|
|
if (abs(val - speed) < abs(prev - speed))
|
|
prev = val;
|
|
else
|
|
break;
|
|
}
|
|
ch->speed = prev;
|
|
|
|
#if(0)
|
|
device_printf(ch->parent->dev, "envy24chan_setspeed(): return %d\n", ch->speed);
|
|
#endif
|
|
return ch->speed;
|
|
}
|
|
|
|
static int
|
|
envy24chan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize)
|
|
{
|
|
struct sc_chinfo *ch = data;
|
|
/* struct sc_info *sc = ch->parent; */
|
|
u_int32_t size, prev;
|
|
unsigned int bcnt, bsize;
|
|
|
|
#if(0)
|
|
device_printf(sc->dev, "envy24chan_setblocksize(obj, data, %d)\n", blocksize);
|
|
#endif
|
|
prev = 0x7fffffff;
|
|
/* snd_mtxlock(sc->lock); */
|
|
for (size = ch->size / 2; size > 0; size /= 2) {
|
|
if (abs(size - blocksize) < abs(prev - blocksize))
|
|
prev = size;
|
|
else
|
|
break;
|
|
}
|
|
|
|
ch->blk = prev / ch->unit;
|
|
if (ch->dir == PCMDIR_PLAY)
|
|
ch->blk *= ENVY24_PLAY_BUFUNIT / 4;
|
|
else
|
|
ch->blk *= ENVY24_REC_BUFUNIT / 4;
|
|
/* set channel buffer information */
|
|
/* ch->size = ch->unit * ENVY24_SAMPLE_NUM; */
|
|
if (ch->dir == PCMDIR_PLAY)
|
|
bsize = ch->blk * 4 / ENVY24_PLAY_BUFUNIT;
|
|
else
|
|
bsize = ch->blk * 4 / ENVY24_REC_BUFUNIT;
|
|
bsize *= ch->unit;
|
|
bcnt = ch->size / bsize;
|
|
sndbuf_resize(ch->buffer, bcnt, bsize);
|
|
/* snd_mtxunlock(sc->lock); */
|
|
|
|
#if(0)
|
|
device_printf(sc->dev, "envy24chan_setblocksize(): return %d\n", prev);
|
|
#endif
|
|
return prev;
|
|
}
|
|
|
|
/* semantic note: must start at beginning of buffer */
|
|
static int
|
|
envy24chan_trigger(kobj_t obj, void *data, int go)
|
|
{
|
|
struct sc_chinfo *ch = data;
|
|
struct sc_info *sc = ch->parent;
|
|
u_int32_t ptr;
|
|
int slot;
|
|
#if 0
|
|
int i;
|
|
|
|
device_printf(sc->dev, "envy24chan_trigger(obj, data, %d)\n", go);
|
|
#endif
|
|
snd_mtxlock(sc->lock);
|
|
if (ch->dir == PCMDIR_PLAY)
|
|
slot = 0;
|
|
else
|
|
slot = 1;
|
|
switch (go) {
|
|
case PCMTRIG_START:
|
|
#if(0)
|
|
device_printf(sc->dev, "envy24chan_trigger(): start\n");
|
|
#endif
|
|
/* check or set channel speed */
|
|
if (sc->run[0] == 0 && sc->run[1] == 0) {
|
|
sc->speed = envy24_setspeed(sc, ch->speed);
|
|
sc->caps[0].minspeed = sc->caps[0].maxspeed = sc->speed;
|
|
sc->caps[1].minspeed = sc->caps[1].maxspeed = sc->speed;
|
|
}
|
|
else if (ch->speed != 0 && ch->speed != sc->speed)
|
|
return -1;
|
|
if (ch->speed == 0)
|
|
ch->channel->speed = sc->speed;
|
|
/* start or enable channel */
|
|
sc->run[slot]++;
|
|
if (sc->run[slot] == 1) {
|
|
/* first channel */
|
|
ch->offset = 0;
|
|
sc->blk[slot] = ch->blk;
|
|
}
|
|
else {
|
|
ptr = envy24_gethwptr(sc, ch->dir);
|
|
ch->offset = ((ptr / ch->blk + 1) * ch->blk %
|
|
(ch->size / 4)) * 4 / ch->unit;
|
|
if (ch->blk < sc->blk[slot])
|
|
sc->blk[slot] = ch->blk;
|
|
}
|
|
if (ch->dir == PCMDIR_PLAY) {
|
|
ch->emldma(ch);
|
|
envy24_setvolume(sc, ch->num);
|
|
}
|
|
envy24_updintr(sc, ch->dir);
|
|
if (sc->run[slot] == 1)
|
|
envy24_start(sc, ch->dir);
|
|
ch->run = 1;
|
|
break;
|
|
case PCMTRIG_EMLDMAWR:
|
|
#if(0)
|
|
device_printf(sc->dev, "envy24chan_trigger(): emldmawr\n");
|
|
#endif
|
|
if (ch->run != 1)
|
|
return -1;
|
|
ch->emldma(ch);
|
|
break;
|
|
case PCMTRIG_EMLDMARD:
|
|
#if(0)
|
|
device_printf(sc->dev, "envy24chan_trigger(): emldmard\n");
|
|
#endif
|
|
if (ch->run != 1)
|
|
return -1;
|
|
ch->emldma(ch);
|
|
break;
|
|
case PCMTRIG_ABORT:
|
|
#if(0)
|
|
device_printf(sc->dev, "envy24chan_trigger(): abort\n");
|
|
#endif
|
|
ch->run = 0;
|
|
sc->run[slot]--;
|
|
if (ch->dir == PCMDIR_PLAY)
|
|
envy24_mutevolume(sc, ch->num);
|
|
if (sc->run[slot] == 0) {
|
|
envy24_stop(sc, ch->dir);
|
|
sc->intr[slot] = 0;
|
|
}
|
|
#if 0
|
|
else if (ch->blk == sc->blk[slot]) {
|
|
sc->blk[slot] = ENVY24_SAMPLE_NUM / 2;
|
|
for (i = 0; i < ENVY24_CHAN_NUM; i++) {
|
|
if (sc->chan[i].dir == ch->dir &&
|
|
sc->chan[i].run == 1 &&
|
|
sc->chan[i].blk < sc->blk[slot])
|
|
sc->blk[slot] = sc->chan[i].blk;
|
|
}
|
|
if (ch->blk != sc->blk[slot])
|
|
envy24_updintr(sc, ch->dir);
|
|
}
|
|
#endif
|
|
break;
|
|
}
|
|
snd_mtxunlock(sc->lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
envy24chan_getptr(kobj_t obj, void *data)
|
|
{
|
|
struct sc_chinfo *ch = data;
|
|
struct sc_info *sc = ch->parent;
|
|
u_int32_t ptr;
|
|
int rtn;
|
|
|
|
#if(0)
|
|
device_printf(sc->dev, "envy24chan_getptr()\n");
|
|
#endif
|
|
snd_mtxlock(sc->lock);
|
|
ptr = envy24_gethwptr(sc, ch->dir);
|
|
rtn = ptr * ch->unit;
|
|
snd_mtxunlock(sc->lock);
|
|
|
|
#if(0)
|
|
device_printf(sc->dev, "envy24chan_getptr(): return %d\n",
|
|
rtn);
|
|
#endif
|
|
return rtn;
|
|
}
|
|
|
|
static struct pcmchan_caps *
|
|
envy24chan_getcaps(kobj_t obj, void *data)
|
|
{
|
|
struct sc_chinfo *ch = data;
|
|
struct sc_info *sc = ch->parent;
|
|
struct pcmchan_caps *rtn;
|
|
|
|
#if(0)
|
|
device_printf(sc->dev, "envy24chan_getcaps()\n");
|
|
#endif
|
|
snd_mtxlock(sc->lock);
|
|
if (ch->dir == PCMDIR_PLAY) {
|
|
if (sc->run[0] == 0)
|
|
rtn = &envy24_playcaps;
|
|
else
|
|
rtn = &sc->caps[0];
|
|
}
|
|
else {
|
|
if (sc->run[1] == 0)
|
|
rtn = &envy24_reccaps;
|
|
else
|
|
rtn = &sc->caps[1];
|
|
}
|
|
snd_mtxunlock(sc->lock);
|
|
|
|
return rtn;
|
|
}
|
|
|
|
static kobj_method_t envy24chan_methods[] = {
|
|
KOBJMETHOD(channel_init, envy24chan_init),
|
|
KOBJMETHOD(channel_free, envy24chan_free),
|
|
KOBJMETHOD(channel_setformat, envy24chan_setformat),
|
|
KOBJMETHOD(channel_setspeed, envy24chan_setspeed),
|
|
KOBJMETHOD(channel_setblocksize, envy24chan_setblocksize),
|
|
KOBJMETHOD(channel_trigger, envy24chan_trigger),
|
|
KOBJMETHOD(channel_getptr, envy24chan_getptr),
|
|
KOBJMETHOD(channel_getcaps, envy24chan_getcaps),
|
|
{ 0, 0 }
|
|
};
|
|
CHANNEL_DECLARE(envy24chan);
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
/* mixer interface */
|
|
|
|
static int
|
|
envy24mixer_init(struct snd_mixer *m)
|
|
{
|
|
struct sc_info *sc = mix_getdevinfo(m);
|
|
|
|
#if(0)
|
|
device_printf(sc->dev, "envy24mixer_init()\n");
|
|
#endif
|
|
if (sc == NULL)
|
|
return -1;
|
|
|
|
/* set volume control rate */
|
|
snd_mtxlock(sc->lock);
|
|
envy24_wrmt(sc, ENVY24_MT_VOLRATE, 0x30, 1); /* 0x30 is default value */
|
|
|
|
mix_setdevs(m, ENVY24_MIX_MASK);
|
|
mix_setrecdevs(m, ENVY24_MIX_REC_MASK);
|
|
snd_mtxunlock(sc->lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
envy24mixer_reinit(struct snd_mixer *m)
|
|
{
|
|
struct sc_info *sc = mix_getdevinfo(m);
|
|
|
|
if (sc == NULL)
|
|
return -1;
|
|
#if(0)
|
|
device_printf(sc->dev, "envy24mixer_reinit()\n");
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
envy24mixer_uninit(struct snd_mixer *m)
|
|
{
|
|
struct sc_info *sc = mix_getdevinfo(m);
|
|
|
|
if (sc == NULL)
|
|
return -1;
|
|
#if(0)
|
|
device_printf(sc->dev, "envy24mixer_uninit()\n");
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
envy24mixer_set(struct snd_mixer *m, unsigned dev, unsigned left, unsigned right)
|
|
{
|
|
struct sc_info *sc = mix_getdevinfo(m);
|
|
int ch = envy24_mixmap[dev];
|
|
int hwch;
|
|
int i;
|
|
|
|
if (sc == NULL)
|
|
return -1;
|
|
if (dev == 0 && sc->cfg->codec->setvolume == NULL)
|
|
return -1;
|
|
if (dev != 0 && ch == -1)
|
|
return -1;
|
|
hwch = envy24_chanmap[ch];
|
|
#if(0)
|
|
device_printf(sc->dev, "envy24mixer_set(m, %d, %d, %d)\n",
|
|
dev, left, right);
|
|
#endif
|
|
|
|
snd_mtxlock(sc->lock);
|
|
if (dev == 0) {
|
|
for (i = 0; i < sc->dacn; i++) {
|
|
sc->cfg->codec->setvolume(sc->dac[i], PCMDIR_PLAY, left, right);
|
|
}
|
|
}
|
|
else {
|
|
/* set volume value for hardware */
|
|
if ((sc->left[hwch] = 100 - left) > ENVY24_VOL_MIN)
|
|
sc->left[hwch] = ENVY24_VOL_MUTE;
|
|
if ((sc->right[hwch] = 100 - right) > ENVY24_VOL_MIN)
|
|
sc->right[hwch] = ENVY24_VOL_MUTE;
|
|
|
|
/* set volume for record channel and running play channel */
|
|
if (hwch > ENVY24_CHAN_PLAY_SPDIF || sc->chan[ch].run)
|
|
envy24_setvolume(sc, hwch);
|
|
}
|
|
snd_mtxunlock(sc->lock);
|
|
|
|
return right << 8 | left;
|
|
}
|
|
|
|
static u_int32_t
|
|
envy24mixer_setrecsrc(struct snd_mixer *m, u_int32_t src)
|
|
{
|
|
struct sc_info *sc = mix_getdevinfo(m);
|
|
int ch = envy24_mixmap[src];
|
|
#if(0)
|
|
device_printf(sc->dev, "envy24mixer_setrecsrc(m, %d)\n", src);
|
|
#endif
|
|
|
|
if (ch > ENVY24_CHAN_PLAY_SPDIF)
|
|
sc->src = ch;
|
|
return src;
|
|
}
|
|
|
|
static kobj_method_t envy24mixer_methods[] = {
|
|
KOBJMETHOD(mixer_init, envy24mixer_init),
|
|
KOBJMETHOD(mixer_reinit, envy24mixer_reinit),
|
|
KOBJMETHOD(mixer_uninit, envy24mixer_uninit),
|
|
KOBJMETHOD(mixer_set, envy24mixer_set),
|
|
KOBJMETHOD(mixer_setrecsrc, envy24mixer_setrecsrc),
|
|
{ 0, 0 }
|
|
};
|
|
MIXER_DECLARE(envy24mixer);
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
|
|
/* The interrupt handler */
|
|
static void
|
|
envy24_intr(void *p)
|
|
{
|
|
struct sc_info *sc = (struct sc_info *)p;
|
|
struct sc_chinfo *ch;
|
|
u_int32_t ptr, dsize, feed;
|
|
int i;
|
|
|
|
#if(0)
|
|
device_printf(sc->dev, "envy24_intr()\n");
|
|
#endif
|
|
snd_mtxlock(sc->lock);
|
|
if (envy24_checkintr(sc, PCMDIR_PLAY)) {
|
|
#if(0)
|
|
device_printf(sc->dev, "envy24_intr(): play\n");
|
|
#endif
|
|
dsize = sc->psize / 4;
|
|
ptr = dsize - envy24_rdmt(sc, ENVY24_MT_PCNT, 2) - 1;
|
|
#if(0)
|
|
device_printf(sc->dev, "envy24_intr(): ptr = %d-->", ptr);
|
|
#endif
|
|
ptr -= ptr % sc->blk[0];
|
|
feed = (ptr + dsize - sc->intr[0]) % dsize;
|
|
#if(0)
|
|
printf("%d intr = %d feed = %d\n", ptr, sc->intr[0], feed);
|
|
#endif
|
|
for (i = ENVY24_CHAN_PLAY_DAC1; i <= ENVY24_CHAN_PLAY_SPDIF; i++) {
|
|
ch = &sc->chan[i];
|
|
#if(0)
|
|
if (ch->run)
|
|
device_printf(sc->dev, "envy24_intr(): chan[%d].blk = %d\n", i, ch->blk);
|
|
#endif
|
|
if (ch->run && ch->blk <= feed) {
|
|
snd_mtxunlock(sc->lock);
|
|
chn_intr(ch->channel);
|
|
snd_mtxlock(sc->lock);
|
|
}
|
|
}
|
|
sc->intr[0] = ptr;
|
|
envy24_updintr(sc, PCMDIR_PLAY);
|
|
}
|
|
if (envy24_checkintr(sc, PCMDIR_REC)) {
|
|
#if(0)
|
|
device_printf(sc->dev, "envy24_intr(): rec\n");
|
|
#endif
|
|
dsize = sc->rsize / 4;
|
|
ptr = dsize - envy24_rdmt(sc, ENVY24_MT_RCNT, 2) - 1;
|
|
ptr -= ptr % sc->blk[1];
|
|
feed = (ptr + dsize - sc->intr[1]) % dsize;
|
|
for (i = ENVY24_CHAN_REC_ADC1; i <= ENVY24_CHAN_REC_SPDIF; i++) {
|
|
ch = &sc->chan[i];
|
|
if (ch->run && ch->blk <= feed) {
|
|
snd_mtxunlock(sc->lock);
|
|
chn_intr(ch->channel);
|
|
snd_mtxlock(sc->lock);
|
|
}
|
|
}
|
|
sc->intr[1] = ptr;
|
|
envy24_updintr(sc, PCMDIR_REC);
|
|
}
|
|
snd_mtxunlock(sc->lock);
|
|
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Probe and attach the card
|
|
*/
|
|
|
|
static int
|
|
envy24_pci_probe(device_t dev)
|
|
{
|
|
u_int16_t sv, sd;
|
|
int i;
|
|
|
|
#if(0)
|
|
printf("envy24_pci_probe()\n");
|
|
#endif
|
|
if (pci_get_device(dev) == PCID_ENVY24 &&
|
|
pci_get_vendor(dev) == PCIV_ENVY24) {
|
|
sv = pci_get_subvendor(dev);
|
|
sd = pci_get_subdevice(dev);
|
|
for (i = 0; cfg_table[i].subvendor != 0 || cfg_table[i].subdevice != 0; i++) {
|
|
if (cfg_table[i].subvendor == sv &&
|
|
cfg_table[i].subdevice == sd) {
|
|
break;
|
|
}
|
|
}
|
|
device_set_desc(dev, cfg_table[i].name);
|
|
#if(0)
|
|
printf("envy24_pci_probe(): return 0\n");
|
|
#endif
|
|
return 0;
|
|
}
|
|
else {
|
|
#if(0)
|
|
printf("envy24_pci_probe(): return ENXIO\n");
|
|
#endif
|
|
return ENXIO;
|
|
}
|
|
}
|
|
|
|
static void
|
|
envy24_dmapsetmap(void *arg, bus_dma_segment_t *segs, int nseg, int error)
|
|
{
|
|
/* struct sc_info *sc = (struct sc_info *)arg; */
|
|
|
|
#if(0)
|
|
device_printf(sc->dev, "envy24_dmapsetmap()\n");
|
|
if (bootverbose) {
|
|
printf("envy24(play): setmap %lx, %lx; ",
|
|
(unsigned long)segs->ds_addr,
|
|
(unsigned long)segs->ds_len);
|
|
printf("%p -> %lx\n", sc->pmap, (unsigned long)vtophys(sc->pmap));
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static void
|
|
envy24_dmarsetmap(void *arg, bus_dma_segment_t *segs, int nseg, int error)
|
|
{
|
|
/* struct sc_info *sc = (struct sc_info *)arg; */
|
|
|
|
#if(0)
|
|
device_printf(sc->dev, "envy24_dmarsetmap()\n");
|
|
if (bootverbose) {
|
|
printf("envy24(record): setmap %lx, %lx; ",
|
|
(unsigned long)segs->ds_addr,
|
|
(unsigned long)segs->ds_len);
|
|
printf("%p -> %lx\n", sc->rmap, (unsigned long)vtophys(sc->pmap));
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static void
|
|
envy24_dmafree(struct sc_info *sc)
|
|
{
|
|
#if(0)
|
|
device_printf(sc->dev, "envy24_dmafree():");
|
|
if (sc->rmap) printf(" sc->rmap(0x%08x)", (u_int32_t)sc->rmap);
|
|
else printf(" sc->rmap(null)");
|
|
if (sc->pmap) printf(" sc->pmap(0x%08x)", (u_int32_t)sc->pmap);
|
|
else printf(" sc->pmap(null)");
|
|
if (sc->rbuf) printf(" sc->rbuf(0x%08x)", (u_int32_t)sc->rbuf);
|
|
else printf(" sc->rbuf(null)");
|
|
if (sc->pbuf) printf(" sc->pbuf(0x%08x)\n", (u_int32_t)sc->pbuf);
|
|
else printf(" sc->pbuf(null)\n");
|
|
#endif
|
|
#if(0)
|
|
if (sc->rmap)
|
|
bus_dmamap_unload(sc->dmat, sc->rmap);
|
|
if (sc->pmap)
|
|
bus_dmamap_unload(sc->dmat, sc->pmap);
|
|
if (sc->rbuf)
|
|
bus_dmamem_free(sc->dmat, sc->rbuf, sc->rmap);
|
|
if (sc->pbuf)
|
|
bus_dmamem_free(sc->dmat, sc->pbuf, sc->pmap);
|
|
#else
|
|
bus_dmamap_unload(sc->dmat, sc->rmap);
|
|
bus_dmamap_unload(sc->dmat, sc->pmap);
|
|
bus_dmamem_free(sc->dmat, sc->rbuf, sc->rmap);
|
|
bus_dmamem_free(sc->dmat, sc->pbuf, sc->pmap);
|
|
#endif
|
|
|
|
sc->rmap = sc->pmap = NULL;
|
|
sc->pbuf = NULL;
|
|
sc->rbuf = NULL;
|
|
|
|
return;
|
|
}
|
|
|
|
static int
|
|
envy24_dmainit(struct sc_info *sc)
|
|
{
|
|
u_int32_t addr;
|
|
|
|
#if(0)
|
|
device_printf(sc->dev, "envy24_dmainit()\n");
|
|
#endif
|
|
/* init values */
|
|
sc->psize = ENVY24_PLAY_BUFUNIT * ENVY24_SAMPLE_NUM;
|
|
sc->rsize = ENVY24_REC_BUFUNIT * ENVY24_SAMPLE_NUM;
|
|
sc->pbuf = NULL;
|
|
sc->rbuf = NULL;
|
|
sc->pmap = sc->rmap = NULL;
|
|
sc->blk[0] = sc->blk[1] = 0;
|
|
|
|
/* allocate DMA buffer */
|
|
#if(0)
|
|
device_printf(sc->dev, "envy24_dmainit(): bus_dmamem_alloc(): sc->pbuf\n");
|
|
#endif
|
|
if (bus_dmamem_alloc(sc->dmat, (void **)&sc->pbuf, BUS_DMA_NOWAIT, &sc->pmap))
|
|
goto bad;
|
|
#if(0)
|
|
device_printf(sc->dev, "envy24_dmainit(): bus_dmamem_alloc(): sc->rbuf\n");
|
|
#endif
|
|
if (bus_dmamem_alloc(sc->dmat, (void **)&sc->rbuf, BUS_DMA_NOWAIT, &sc->rmap))
|
|
goto bad;
|
|
#if(0)
|
|
device_printf(sc->dev, "envy24_dmainit(): bus_dmamem_load(): sc->pmap\n");
|
|
#endif
|
|
if (bus_dmamap_load(sc->dmat, sc->pmap, sc->pbuf, sc->psize, envy24_dmapsetmap, sc, 0))
|
|
goto bad;
|
|
#if(0)
|
|
device_printf(sc->dev, "envy24_dmainit(): bus_dmamem_load(): sc->rmap\n");
|
|
#endif
|
|
if (bus_dmamap_load(sc->dmat, sc->rmap, sc->rbuf, sc->rsize, envy24_dmarsetmap, sc, 0))
|
|
goto bad;
|
|
bzero(sc->pbuf, sc->psize);
|
|
bzero(sc->rbuf, sc->rsize);
|
|
|
|
/* set values to register */
|
|
addr = vtophys(sc->pbuf);
|
|
#if(0)
|
|
device_printf(sc->dev, "pbuf(0x%08x)\n", addr);
|
|
#endif
|
|
envy24_wrmt(sc, ENVY24_MT_PADDR, addr, 4);
|
|
#if(0)
|
|
device_printf(sc->dev, "PADDR-->(0x%08x)\n", envy24_rdmt(sc, ENVY24_MT_PADDR, 4));
|
|
device_printf(sc->dev, "psize(%ld)\n", sc->psize / 4 - 1);
|
|
#endif
|
|
envy24_wrmt(sc, ENVY24_MT_PCNT, sc->psize / 4 - 1, 2);
|
|
#if(0)
|
|
device_printf(sc->dev, "PCNT-->(%ld)\n", envy24_rdmt(sc, ENVY24_MT_PCNT, 2));
|
|
#endif
|
|
addr = vtophys(sc->rbuf);
|
|
envy24_wrmt(sc, ENVY24_MT_RADDR, addr, 4);
|
|
envy24_wrmt(sc, ENVY24_MT_RCNT, sc->rsize / 4 - 1, 2);
|
|
|
|
return 0;
|
|
bad:
|
|
envy24_dmafree(sc);
|
|
return ENOSPC;
|
|
}
|
|
|
|
static void
|
|
envy24_putcfg(struct sc_info *sc)
|
|
{
|
|
device_printf(sc->dev, "system configuration\n");
|
|
printf(" SubVendorID: 0x%04x, SubDeviceID: 0x%04x\n",
|
|
sc->cfg->subvendor, sc->cfg->subdevice);
|
|
printf(" XIN2 Clock Source: ");
|
|
switch (sc->cfg->scfg & PCIM_SCFG_XIN2) {
|
|
case 0x00:
|
|
printf("22.5792MHz(44.1kHz*512)\n");
|
|
break;
|
|
case 0x40:
|
|
printf("16.9344MHz(44.1kHz*384)\n");
|
|
break;
|
|
case 0x80:
|
|
printf("from external clock synthesizer chip\n");
|
|
break;
|
|
default:
|
|
printf("illeagal system setting\n");
|
|
}
|
|
printf(" MPU-401 UART(s) #: ");
|
|
if (sc->cfg->scfg & PCIM_SCFG_MPU)
|
|
printf("2\n");
|
|
else
|
|
printf("1\n");
|
|
printf(" AC'97 codec: ");
|
|
if (sc->cfg->scfg & PCIM_SCFG_AC97)
|
|
printf("not exist\n");
|
|
else
|
|
printf("exist\n");
|
|
printf(" ADC #: ");
|
|
printf("%d\n", sc->adcn);
|
|
printf(" DAC #: ");
|
|
printf("%d\n", sc->dacn);
|
|
printf(" Multi-track converter type: ");
|
|
if ((sc->cfg->acl & PCIM_ACL_MTC) == 0) {
|
|
printf("AC'97(SDATA_OUT:");
|
|
if (sc->cfg->acl & PCIM_ACL_OMODE)
|
|
printf("packed");
|
|
else
|
|
printf("split");
|
|
printf("|SDATA_IN:");
|
|
if (sc->cfg->acl & PCIM_ACL_IMODE)
|
|
printf("packed");
|
|
else
|
|
printf("split");
|
|
printf(")\n");
|
|
}
|
|
else {
|
|
printf("I2S(");
|
|
if (sc->cfg->i2s & PCIM_I2S_VOL)
|
|
printf("with volume, ");
|
|
if (sc->cfg->i2s & PCIM_I2S_96KHZ)
|
|
printf("96KHz support, ");
|
|
switch (sc->cfg->i2s & PCIM_I2S_RES) {
|
|
case PCIM_I2S_16BIT:
|
|
printf("16bit resolution, ");
|
|
break;
|
|
case PCIM_I2S_18BIT:
|
|
printf("18bit resolution, ");
|
|
break;
|
|
case PCIM_I2S_20BIT:
|
|
printf("20bit resolution, ");
|
|
break;
|
|
case PCIM_I2S_24BIT:
|
|
printf("24bit resolution, ");
|
|
break;
|
|
}
|
|
printf("ID#0x%x)\n", sc->cfg->i2s & PCIM_I2S_ID);
|
|
}
|
|
printf(" S/PDIF(IN/OUT): ");
|
|
if (sc->cfg->spdif & PCIM_SPDIF_IN)
|
|
printf("1/");
|
|
else
|
|
printf("0/");
|
|
if (sc->cfg->spdif & PCIM_SPDIF_OUT)
|
|
printf("1 ");
|
|
else
|
|
printf("0 ");
|
|
if (sc->cfg->spdif & (PCIM_SPDIF_IN | PCIM_SPDIF_OUT))
|
|
printf("ID# 0x%02x\n", (sc->cfg->spdif & PCIM_SPDIF_ID) >> 2);
|
|
printf(" GPIO(mask/dir/state): 0x%02x/0x%02x/0x%02x\n",
|
|
sc->cfg->gpiomask, sc->cfg->gpiodir, sc->cfg->gpiostate);
|
|
}
|
|
|
|
static int
|
|
envy24_init(struct sc_info *sc)
|
|
{
|
|
u_int32_t data;
|
|
#if(0)
|
|
int rtn;
|
|
#endif
|
|
int i;
|
|
u_int32_t sv, sd;
|
|
|
|
|
|
#if(0)
|
|
device_printf(sc->dev, "envy24_init()\n");
|
|
#endif
|
|
|
|
/* reset chip */
|
|
envy24_wrcs(sc, ENVY24_CCS_CTL, ENVY24_CCS_CTL_RESET | ENVY24_CCS_CTL_NATIVE, 1);
|
|
DELAY(200);
|
|
envy24_wrcs(sc, ENVY24_CCS_CTL, ENVY24_CCS_CTL_NATIVE, 1);
|
|
DELAY(200);
|
|
|
|
/* legacy hardware disable */
|
|
data = pci_read_config(sc->dev, PCIR_LAC, 2);
|
|
data |= PCIM_LAC_DISABLE;
|
|
pci_write_config(sc->dev, PCIR_LAC, data, 2);
|
|
|
|
/* check system configuration */
|
|
sc->cfg = NULL;
|
|
for (i = 0; cfg_table[i].subvendor != 0 || cfg_table[i].subdevice != 0; i++) {
|
|
/* 1st: search configuration from table */
|
|
sv = pci_get_subvendor(sc->dev);
|
|
sd = pci_get_subdevice(sc->dev);
|
|
if (sv == cfg_table[i].subvendor && sd == cfg_table[i].subdevice) {
|
|
#if(0)
|
|
device_printf(sc->dev, "Set configuration from table\n");
|
|
#endif
|
|
sc->cfg = &cfg_table[i];
|
|
break;
|
|
}
|
|
}
|
|
if (sc->cfg == NULL) {
|
|
/* 2nd: read configuration from table */
|
|
sc->cfg = envy24_rom2cfg(sc);
|
|
}
|
|
sc->adcn = ((sc->cfg->scfg & PCIM_SCFG_ADC) >> 2) + 1;
|
|
sc->dacn = (sc->cfg->scfg & PCIM_SCFG_DAC) + 1;
|
|
|
|
if (1 /* bootverbose */) {
|
|
envy24_putcfg(sc);
|
|
}
|
|
|
|
/* set system configuration */
|
|
pci_write_config(sc->dev, PCIR_SCFG, sc->cfg->scfg, 1);
|
|
pci_write_config(sc->dev, PCIR_ACL, sc->cfg->acl, 1);
|
|
pci_write_config(sc->dev, PCIR_I2S, sc->cfg->i2s, 1);
|
|
pci_write_config(sc->dev, PCIR_SPDIF, sc->cfg->spdif, 1);
|
|
envy24_gpiosetmask(sc, sc->cfg->gpiomask);
|
|
envy24_gpiosetdir(sc, sc->cfg->gpiodir);
|
|
envy24_gpiowr(sc, sc->cfg->gpiostate);
|
|
for (i = 0; i < sc->adcn; i++) {
|
|
sc->adc[i] = sc->cfg->codec->create(sc->dev, sc, PCMDIR_REC, i);
|
|
sc->cfg->codec->init(sc->adc[i]);
|
|
}
|
|
for (i = 0; i < sc->dacn; i++) {
|
|
sc->dac[i] = sc->cfg->codec->create(sc->dev, sc, PCMDIR_PLAY, i);
|
|
sc->cfg->codec->init(sc->dac[i]);
|
|
}
|
|
|
|
/* initialize DMA buffer */
|
|
#if(0)
|
|
device_printf(sc->dev, "envy24_init(): initialize DMA buffer\n");
|
|
#endif
|
|
if (envy24_dmainit(sc))
|
|
return ENOSPC;
|
|
|
|
/* initialize status */
|
|
sc->run[0] = sc->run[1] = 0;
|
|
sc->intr[0] = sc->intr[1] = 0;
|
|
sc->speed = 0;
|
|
sc->caps[0].fmtlist = envy24_playfmt;
|
|
sc->caps[1].fmtlist = envy24_recfmt;
|
|
|
|
/* set channel router */
|
|
envy24_route(sc, ENVY24_ROUTE_DAC_1, ENVY24_ROUTE_CLASS_MIX, 0, 0);
|
|
envy24_route(sc, ENVY24_ROUTE_DAC_SPDIF, ENVY24_ROUTE_CLASS_DMA, 0, 0);
|
|
/* envy24_route(sc, ENVY24_ROUTE_DAC_SPDIF, ENVY24_ROUTE_CLASS_MIX, 0, 0); */
|
|
|
|
/* set macro interrupt mask */
|
|
data = envy24_rdcs(sc, ENVY24_CCS_IMASK, 1);
|
|
envy24_wrcs(sc, ENVY24_CCS_IMASK, data & ~ENVY24_CCS_IMASK_PMT, 1);
|
|
data = envy24_rdcs(sc, ENVY24_CCS_IMASK, 1);
|
|
#if(0)
|
|
device_printf(sc->dev, "envy24_init(): CCS_IMASK-->0x%02x\n", data);
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
envy24_alloc_resource(struct sc_info *sc)
|
|
{
|
|
/* allocate I/O port resource */
|
|
sc->csid = PCIR_CCS;
|
|
sc->cs = bus_alloc_resource(sc->dev, SYS_RES_IOPORT,
|
|
&sc->csid, 0, ~0, 1, RF_ACTIVE);
|
|
sc->ddmaid = PCIR_DDMA;
|
|
sc->ddma = bus_alloc_resource(sc->dev, SYS_RES_IOPORT,
|
|
&sc->ddmaid, 0, ~0, 1, RF_ACTIVE);
|
|
sc->dsid = PCIR_DS;
|
|
sc->ds = bus_alloc_resource(sc->dev, SYS_RES_IOPORT,
|
|
&sc->dsid, 0, ~0, 1, RF_ACTIVE);
|
|
sc->mtid = PCIR_MT;
|
|
sc->mt = bus_alloc_resource(sc->dev, SYS_RES_IOPORT,
|
|
&sc->mtid, 0, ~0, 1, RF_ACTIVE);
|
|
if (!sc->cs || !sc->ddma || !sc->ds || !sc->mt) {
|
|
device_printf(sc->dev, "unable to map IO port space\n");
|
|
return ENXIO;
|
|
}
|
|
sc->cst = rman_get_bustag(sc->cs);
|
|
sc->csh = rman_get_bushandle(sc->cs);
|
|
sc->ddmat = rman_get_bustag(sc->ddma);
|
|
sc->ddmah = rman_get_bushandle(sc->ddma);
|
|
sc->dst = rman_get_bustag(sc->ds);
|
|
sc->dsh = rman_get_bushandle(sc->ds);
|
|
sc->mtt = rman_get_bustag(sc->mt);
|
|
sc->mth = rman_get_bushandle(sc->mt);
|
|
#if(0)
|
|
device_printf(sc->dev,
|
|
"IO port register values\nCCS: 0x%lx\nDDMA: 0x%lx\nDS: 0x%lx\nMT: 0x%lx\n",
|
|
pci_read_config(sc->dev, PCIR_CCS, 4),
|
|
pci_read_config(sc->dev, PCIR_DDMA, 4),
|
|
pci_read_config(sc->dev, PCIR_DS, 4),
|
|
pci_read_config(sc->dev, PCIR_MT, 4));
|
|
#endif
|
|
|
|
/* allocate interupt resource */
|
|
sc->irqid = 0;
|
|
sc->irq = bus_alloc_resource(sc->dev, SYS_RES_IRQ, &sc->irqid,
|
|
0, ~0, 1, RF_ACTIVE | RF_SHAREABLE);
|
|
if (!sc->irq ||
|
|
snd_setup_intr(sc->dev, sc->irq, INTR_MPSAFE, envy24_intr, sc, &sc->ih)) {
|
|
device_printf(sc->dev, "unable to map interrupt\n");
|
|
return ENXIO;
|
|
}
|
|
|
|
/* allocate DMA resource */
|
|
if (bus_dma_tag_create(/*parent*/bus_get_dma_tag(sc->dev),
|
|
/*alignment*/4,
|
|
/*boundary*/0,
|
|
/*lowaddr*/BUS_SPACE_MAXADDR_ENVY24,
|
|
/*highaddr*/BUS_SPACE_MAXADDR_ENVY24,
|
|
/*filter*/NULL, /*filterarg*/NULL,
|
|
/*maxsize*/BUS_SPACE_MAXSIZE_ENVY24,
|
|
/*nsegments*/1, /*maxsegsz*/0x3ffff,
|
|
/*flags*/0, /*lockfunc*/busdma_lock_mutex,
|
|
/*lockarg*/&Giant, &sc->dmat) != 0) {
|
|
device_printf(sc->dev, "unable to create dma tag\n");
|
|
return ENXIO;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
envy24_pci_attach(device_t dev)
|
|
{
|
|
u_int32_t data;
|
|
struct sc_info *sc;
|
|
char status[SND_STATUSLEN];
|
|
int err = 0;
|
|
int i;
|
|
|
|
#if(0)
|
|
device_printf(dev, "envy24_pci_attach()\n");
|
|
#endif
|
|
/* get sc_info data area */
|
|
if ((sc = malloc(sizeof(*sc), M_ENVY24, M_NOWAIT)) == NULL) {
|
|
device_printf(dev, "cannot allocate softc\n");
|
|
return ENXIO;
|
|
}
|
|
|
|
bzero(sc, sizeof(*sc));
|
|
sc->lock = snd_mtxcreate(device_get_nameunit(dev), "snd_envy24 softc");
|
|
sc->dev = dev;
|
|
|
|
/* initialize PCI interface */
|
|
data = pci_read_config(dev, PCIR_COMMAND, 2);
|
|
data |= (PCIM_CMD_PORTEN | PCIM_CMD_BUSMASTEREN);
|
|
pci_write_config(dev, PCIR_COMMAND, data, 2);
|
|
data = pci_read_config(dev, PCIR_COMMAND, 2);
|
|
|
|
/* allocate resources */
|
|
err = envy24_alloc_resource(sc);
|
|
if (err) {
|
|
device_printf(dev, "unable to allocate system resources\n");
|
|
goto bad;
|
|
}
|
|
|
|
/* initialize card */
|
|
err = envy24_init(sc);
|
|
if (err) {
|
|
device_printf(dev, "unable to initialize the card\n");
|
|
goto bad;
|
|
}
|
|
|
|
/* set multi track mixer */
|
|
mixer_init(dev, &envy24mixer_class, sc);
|
|
|
|
/* set channel information */
|
|
err = pcm_register(dev, sc, sc->dacn, sc->adcn);
|
|
if (err)
|
|
goto bad;
|
|
sc->chnum = ENVY24_CHAN_PLAY_DAC1;
|
|
for (i = 0; i < sc->dacn; i++) {
|
|
pcm_addchan(dev, PCMDIR_PLAY, &envy24chan_class, sc);
|
|
sc->chnum++;
|
|
}
|
|
sc->chnum = ENVY24_CHAN_REC_ADC1;
|
|
for (i = 0; i < sc->adcn; i++) {
|
|
pcm_addchan(dev, PCMDIR_REC, &envy24chan_class, sc);
|
|
sc->chnum++;
|
|
}
|
|
|
|
/* set status iformation */
|
|
snprintf(status, SND_STATUSLEN,
|
|
"at io 0x%lx:%ld,0x%lx:%ld,0x%lx:%ld,0x%lx:%ld irq %ld",
|
|
rman_get_start(sc->cs),
|
|
rman_get_end(sc->cs) - rman_get_start(sc->cs) + 1,
|
|
rman_get_start(sc->ddma),
|
|
rman_get_end(sc->ddma) - rman_get_start(sc->ddma) + 1,
|
|
rman_get_start(sc->ds),
|
|
rman_get_end(sc->ds) - rman_get_start(sc->ds) + 1,
|
|
rman_get_start(sc->mt),
|
|
rman_get_end(sc->mt) - rman_get_start(sc->mt) + 1,
|
|
rman_get_start(sc->irq));
|
|
pcm_setstatus(dev, status);
|
|
|
|
return 0;
|
|
|
|
bad:
|
|
if (sc->ih)
|
|
bus_teardown_intr(dev, sc->irq, sc->ih);
|
|
if (sc->irq)
|
|
bus_release_resource(dev, SYS_RES_IRQ, sc->irqid, sc->irq);
|
|
envy24_dmafree(sc);
|
|
if (sc->dmat)
|
|
bus_dma_tag_destroy(sc->dmat);
|
|
if (sc->cfg->codec->destroy != NULL) {
|
|
for (i = 0; i < sc->adcn; i++)
|
|
sc->cfg->codec->destroy(sc->adc[i]);
|
|
for (i = 0; i < sc->dacn; i++)
|
|
sc->cfg->codec->destroy(sc->dac[i]);
|
|
}
|
|
envy24_cfgfree(sc->cfg);
|
|
if (sc->cs)
|
|
bus_release_resource(dev, SYS_RES_IOPORT, sc->csid, sc->cs);
|
|
if (sc->ddma)
|
|
bus_release_resource(dev, SYS_RES_IOPORT, sc->ddmaid, sc->ddma);
|
|
if (sc->ds)
|
|
bus_release_resource(dev, SYS_RES_IOPORT, sc->dsid, sc->ds);
|
|
if (sc->mt)
|
|
bus_release_resource(dev, SYS_RES_IOPORT, sc->mtid, sc->mt);
|
|
if (sc->lock)
|
|
snd_mtxfree(sc->lock);
|
|
free(sc, M_ENVY24);
|
|
return err;
|
|
}
|
|
|
|
static int
|
|
envy24_pci_detach(device_t dev)
|
|
{
|
|
struct sc_info *sc;
|
|
int r;
|
|
int i;
|
|
|
|
#if(0)
|
|
device_printf(dev, "envy24_pci_detach()\n");
|
|
#endif
|
|
sc = pcm_getdevinfo(dev);
|
|
if (sc == NULL)
|
|
return 0;
|
|
r = pcm_unregister(dev);
|
|
if (r)
|
|
return r;
|
|
|
|
envy24_dmafree(sc);
|
|
if (sc->cfg->codec->destroy != NULL) {
|
|
for (i = 0; i < sc->adcn; i++)
|
|
sc->cfg->codec->destroy(sc->adc[i]);
|
|
for (i = 0; i < sc->dacn; i++)
|
|
sc->cfg->codec->destroy(sc->dac[i]);
|
|
}
|
|
envy24_cfgfree(sc->cfg);
|
|
bus_dma_tag_destroy(sc->dmat);
|
|
bus_teardown_intr(dev, sc->irq, sc->ih);
|
|
bus_release_resource(dev, SYS_RES_IRQ, sc->irqid, sc->irq);
|
|
bus_release_resource(dev, SYS_RES_IOPORT, sc->csid, sc->cs);
|
|
bus_release_resource(dev, SYS_RES_IOPORT, sc->ddmaid, sc->ddma);
|
|
bus_release_resource(dev, SYS_RES_IOPORT, sc->dsid, sc->ds);
|
|
bus_release_resource(dev, SYS_RES_IOPORT, sc->mtid, sc->mt);
|
|
snd_mtxfree(sc->lock);
|
|
free(sc, M_ENVY24);
|
|
return 0;
|
|
}
|
|
|
|
static device_method_t envy24_methods[] = {
|
|
/* Device interface */
|
|
DEVMETHOD(device_probe, envy24_pci_probe),
|
|
DEVMETHOD(device_attach, envy24_pci_attach),
|
|
DEVMETHOD(device_detach, envy24_pci_detach),
|
|
{ 0, 0 }
|
|
};
|
|
|
|
static driver_t envy24_driver = {
|
|
"pcm",
|
|
envy24_methods,
|
|
#if __FreeBSD_version > 500000
|
|
PCM_SOFTC_SIZE,
|
|
#else
|
|
sizeof(struct snddev_info),
|
|
#endif
|
|
};
|
|
|
|
DRIVER_MODULE(snd_envy24, pci, envy24_driver, pcm_devclass, 0, 0);
|
|
MODULE_DEPEND(snd_envy24, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER);
|
|
MODULE_DEPEND(snd_envy24, snd_spicds, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER);
|
|
MODULE_VERSION(snd_envy24, 1);
|