summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorbrad <brad@openbsd.org>2006-06-14 19:34:52 +0000
committerbrad <brad@openbsd.org>2006-06-14 19:34:52 +0000
commitbc0fbb04fbb50c94a082fe7bb1eec62aec9ac913 (patch)
treeadda7bab7910ab934dee8d465e1d2ec169bc00ec
parentmust use RTS/CTS protection when sending frames at OFDM rates in a BSS (diff)
downloadwireguard-openbsd-bc0fbb04fbb50c94a082fe7bb1eec62aec9ac913.tar.xz
wireguard-openbsd-bc0fbb04fbb50c94a082fe7bb1eec62aec9ac913.zip
* move mixer functions from azalia.c to azalia_codec.c, and
rename them like azalia_mixer_foo() to azalia_generic_mixer_foo() * enable codec-specific code to hook any mixer operations such as creating mixer items, or special handling for a specific item. * provide a custom mixer table specific to ALC260 * provide a custom mixer table specific to Fujitsu LOOX From kent NetBSD ok jason@
-rw-r--r--sys/dev/pci/azalia.c984
-rw-r--r--sys/dev/pci/azalia.h12
-rw-r--r--sys/dev/pci/azalia_codec.c1397
3 files changed, 1343 insertions, 1050 deletions
diff --git a/sys/dev/pci/azalia.c b/sys/dev/pci/azalia.c
index c1a877e94de..2c1901800fb 100644
--- a/sys/dev/pci/azalia.c
+++ b/sys/dev/pci/azalia.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: azalia.c,v 1.10 2006/06/07 18:19:20 jason Exp $ */
+/* $OpenBSD: azalia.c,v 1.11 2006/06/14 19:34:52 brad Exp $ */
/* $NetBSD: azalia.c,v 1.20 2006/05/07 08:31:44 kent Exp $ */
/*-
@@ -183,6 +183,7 @@ typedef struct azalia_t {
bus_space_handle_t ioh;
bus_size_t map_size;
bus_dma_tag_t dmat;
+ uint32_t subid;
codec_t codecs[15];
int ncodecs; /* number of codecs */
@@ -198,8 +199,6 @@ typedef struct azalia_t {
int nistreams, nostreams, nbstreams;
stream_t pstream;
stream_t rstream;
-
- int running;
} azalia_t;
#define XNAME(sc) ((sc)->dev.dv_xname)
#define AZ_READ_1(z, r) bus_space_read_1((z)->iot, (z)->ioh, HDA_##r)
@@ -230,7 +229,6 @@ int azalia_free_dmamem(const azalia_t *, azalia_dma_t*);
int azalia_codec_init(codec_t *);
int azalia_codec_delete(codec_t *);
-int azalia_codec_construct_format(codec_t *);
void azalia_codec_add_bits(codec_t *, int, uint32_t, int);
void azalia_codec_add_format(codec_t *, int, int, int, uint32_t,
int32_t);
@@ -238,18 +236,6 @@ int azalia_codec_comresp(const codec_t *, nid_t, uint32_t,
uint32_t, uint32_t *);
int azalia_codec_connect_stream(codec_t *, int, uint16_t, int);
-int azalia_mixer_init(codec_t *);
-int azalia_mixer_delete(codec_t *);
-int azalia_mixer_get(const codec_t *, mixer_ctrl_t *);
-int azalia_mixer_set(codec_t *, const mixer_ctrl_t *);
-int azalia_mixer_ensure_capacity(codec_t *, size_t);
-u_char azalia_mixer_from_device_value(const codec_t *,
- const mixer_item_t *, uint32_t );
-uint32_t azalia_mixer_to_device_value(const codec_t *,
- const mixer_item_t *, u_char);
-boolean_t azalia_mixer_validate_value(const codec_t *,
- const mixer_item_t *, u_char);
-
int azalia_widget_init(widget_t *, const codec_t *, int);
int azalia_widget_init_audio(widget_t *, const codec_t *);
int azalia_widget_print_audio(const widget_t *, const char *);
@@ -411,6 +397,7 @@ azalia_pci_attach(struct device *parent, struct device *self, void *aux)
azalia_pci_detach(self, 0);
return;
}
+ sc->subid = pci_conf_read(pa->pa_pc, pa->pa_tag, PCI_SUBSYS_ID_REG);
azalia_attach_intr(self);
}
@@ -949,7 +936,7 @@ azalia_free_dmamem(const azalia_t *az, azalia_dma_t* d)
int
azalia_codec_init(codec_t *this)
{
- uint32_t rev, result;
+ uint32_t rev, id, result;
int err, addr, n, i;
this->comresp = azalia_codec_comresp;
@@ -962,14 +949,16 @@ azalia_codec_init(codec_t *this)
if (err)
return err;
err = this->comresp(this, CORB_NID_ROOT, CORB_GET_PARAMETER,
- COP_VENDOR_ID, &result);
+ COP_VENDOR_ID, &id);
if (err)
return err;
- azalia_codec_init_vtbl(this, result);
+ this->vid = id;
+ this->subid = this->az->subid;
+ azalia_codec_init_vtbl(this);
printf("%s: codec:", XNAME(this->az));
if (this->name == NULL)
- printf(" 0x04x/0x%04x", result >> 16, result & 0xffff);
+ printf(" 0x04x/0x%04x", id >> 16, id & 0xffff);
else
printf(" %s", this->name);
printf(" (rev. %u.%u), HDA version %u.%u\n",
@@ -1086,13 +1075,13 @@ azalia_codec_init(codec_t *this)
if (err)
return err;
- return azalia_mixer_init(this);
+ return this->mixer_init(this);
}
int
azalia_codec_delete(codec_t *this)
{
- azalia_mixer_delete(this);
+ this->mixer_delete(this);
if (this->formats != NULL) {
free(this->formats, M_DEVBUF);
this->formats = NULL;
@@ -1329,940 +1318,6 @@ exit:
}
/* ================================================================
- * HDA mixer functions
- * ================================================================ */
-
-int
-azalia_mixer_init(codec_t *this)
-{
- /*
- * pin "<color>%2.2x"
- * audio output "dac%2.2x"
- * audio input "adc%2.2x"
- * mixer "mixer%2.2x"
- * selector "sel%2.2x"
- */
- mixer_item_t *m;
- int nadcs;
- int err, i, j, k;
-
- nadcs = 0;
- this->maxmixers = 10;
- this->nmixers = 0;
- this->mixers = malloc(sizeof(mixer_item_t) * this->maxmixers,
- M_DEVBUF, M_NOWAIT);
- if (this->mixers == NULL) {
- printf("%s: out of memory in %s\n", XNAME(this->az),
- __func__);
- return ENOMEM;
- }
- bzero(this->mixers, sizeof(mixer_item_t) * this->maxmixers);
-
- /* register classes */
- DPRINTF(("%s: register classes\n", __func__));
-#define AZ_CLASS_INPUT 0
-#define AZ_CLASS_OUTPUT 1
-#define AZ_CLASS_RECORD 2
- m = &this->mixers[AZ_CLASS_INPUT];
- m->devinfo.index = AZ_CLASS_INPUT;
- strlcpy(m->devinfo.label.name, AudioCinputs,
- sizeof(m->devinfo.label.name));
- m->devinfo.type = AUDIO_MIXER_CLASS;
- m->devinfo.mixer_class = AZ_CLASS_INPUT;
- m->devinfo.next = AUDIO_MIXER_LAST;
- m->devinfo.prev = AUDIO_MIXER_LAST;
- m->nid = 0;
-
- m = &this->mixers[AZ_CLASS_OUTPUT];
- m->devinfo.index = AZ_CLASS_OUTPUT;
- strlcpy(m->devinfo.label.name, AudioCoutputs,
- sizeof(m->devinfo.label.name));
- m->devinfo.type = AUDIO_MIXER_CLASS;
- m->devinfo.mixer_class = AZ_CLASS_OUTPUT;
- m->devinfo.next = AUDIO_MIXER_LAST;
- m->devinfo.prev = AUDIO_MIXER_LAST;
- m->nid = 0;
-
- m = &this->mixers[AZ_CLASS_RECORD];
- m->devinfo.index = AZ_CLASS_RECORD;
- strlcpy(m->devinfo.label.name, AudioCrecord,
- sizeof(m->devinfo.label.name));
- m->devinfo.type = AUDIO_MIXER_CLASS;
- m->devinfo.mixer_class = AZ_CLASS_RECORD;
- m->devinfo.next = AUDIO_MIXER_LAST;
- m->devinfo.prev = AUDIO_MIXER_LAST;
- m->nid = 0;
-
- this->nmixers = AZ_CLASS_RECORD + 1;
-
-#define MIXER_REG_PROLOG \
- mixer_devinfo_t *d; \
- err = azalia_mixer_ensure_capacity(this, this->nmixers + 1); \
- if (err) \
- return err; \
- m = &this->mixers[this->nmixers]; \
- d = &m->devinfo; \
- d->index = this->nmixers; \
- m->nid = i
-
- FOR_EACH_WIDGET(this, i) {
- const widget_t *w;
-
- w = &this->w[i];
-
- if (w->type == COP_AWTYPE_AUDIO_INPUT)
- nadcs++;
-
- /* selector */
- if (w->type != COP_AWTYPE_AUDIO_MIXER && w->nconnections >= 2) {
- MIXER_REG_PROLOG;
- DPRINTF(("%s: selector %s\n", __func__, w->name));
- snprintf(d->label.name, sizeof(d->label.name),
- "%s.source", w->name);
- d->type = AUDIO_MIXER_ENUM;
- if (w->type == COP_AWTYPE_AUDIO_MIXER)
- d->mixer_class = AZ_CLASS_RECORD;
- else if (w->type == COP_AWTYPE_AUDIO_SELECTOR)
- d->mixer_class = AZ_CLASS_INPUT;
- else
- d->mixer_class = AZ_CLASS_OUTPUT;
- d->next = AUDIO_MIXER_LAST;
- d->prev = AUDIO_MIXER_LAST;
- m->target = MI_TARGET_CONNLIST;
- for (j = 0, k = 0; j < w->nconnections && k < 32; j++) {
- if (!VALID_WIDGET_NID(w->connections[j], this))
- continue;
- d->un.e.member[k].ord = k;
- DPRINTF(("%s: selector %d=%s\n", __func__, j,
- this->w[w->connections[j]].name));
- strlcpy(d->un.e.member[k].label.name,
- this->w[w->connections[j]].name,
- MAX_AUDIO_DEV_LEN);
- k++;
- }
- d->un.e.num_mem = k;
- this->nmixers++;
- }
-
- /* output mute */
- if (w->widgetcap & COP_AWCAP_OUTAMP &&
- w->outamp_cap & COP_AMPCAP_MUTE) {
- MIXER_REG_PROLOG;
- DPRINTF(("%s: output mute %s\n", __func__, w->name));
- snprintf(d->label.name, sizeof(d->label.name),
- "%s.mute", w->name);
- d->type = AUDIO_MIXER_ENUM;
- if (w->type == COP_AWTYPE_AUDIO_MIXER)
- d->mixer_class = AZ_CLASS_OUTPUT;
- else if (w->type == COP_AWTYPE_AUDIO_SELECTOR)
- d->mixer_class = AZ_CLASS_OUTPUT;
- else if (w->type == COP_AWTYPE_PIN_COMPLEX)
- d->mixer_class = AZ_CLASS_OUTPUT;
- else
- d->mixer_class = AZ_CLASS_INPUT;
- d->next = AUDIO_MIXER_LAST;
- d->prev = AUDIO_MIXER_LAST;
- m->target = MI_TARGET_OUTAMP;
- d->un.e.num_mem = 2;
- d->un.e.member[0].ord = 0;
- strlcpy(d->un.e.member[0].label.name, AudioNoff,
- MAX_AUDIO_DEV_LEN);
- d->un.e.member[1].ord = 1;
- strlcpy(d->un.e.member[1].label.name, AudioNon,
- MAX_AUDIO_DEV_LEN);
- this->nmixers++;
- }
-
- /* output gain */
- if (w->widgetcap & COP_AWCAP_OUTAMP
- && COP_AMPCAP_NUMSTEPS(w->outamp_cap)) {
- MIXER_REG_PROLOG;
- DPRINTF(("%s: output gain %s\n", __func__, w->name));
- snprintf(d->label.name, sizeof(d->label.name),
- "%s", w->name);
- d->type = AUDIO_MIXER_VALUE;
- if (w->type == COP_AWTYPE_AUDIO_MIXER)
- d->mixer_class = AZ_CLASS_OUTPUT;
- else if (w->type == COP_AWTYPE_AUDIO_SELECTOR)
- d->mixer_class = AZ_CLASS_OUTPUT;
- else if (w->type == COP_AWTYPE_PIN_COMPLEX)
- d->mixer_class = AZ_CLASS_OUTPUT;
- else
- d->mixer_class = AZ_CLASS_INPUT;
- d->next = AUDIO_MIXER_LAST;
- d->prev = AUDIO_MIXER_LAST;
- m->target = MI_TARGET_OUTAMP;
- d->un.v.num_channels = WIDGET_CHANNELS(w);
-#ifdef MAX_VOLUME_255
- d->un.v.units.name[0] = 0;
- d->un.v.delta = AUDIO_MAX_GAIN /
- COP_AMPCAP_NUMSTEPS(w->outamp_cap);
-#else
- snprintf(d->un.v.units.name, sizeof(d->un.v.units.name),
- "0.25x%ddB", COP_AMPCAP_STEPSIZE(w->outamp_cap)+1);
- d->un.v.delta = 1;
-#endif
- this->nmixers++;
- }
-
- /* input mute */
- if (w->widgetcap & COP_AWCAP_INAMP &&
- w->inamp_cap & COP_AMPCAP_MUTE) {
- DPRINTF(("%s: input mute %s\n", __func__, w->name));
- for (j = 0; j < w->nconnections; j++) {
- MIXER_REG_PROLOG;
- if (!VALID_WIDGET_NID(w->connections[j], this))
- continue;
- DPRINTF(("%s: input mute %s.%s\n", __func__,
- w->name, this->w[w->connections[j]].name));
- snprintf(d->label.name, sizeof(d->label.name),
- "%s.%s.mute", w->name,
- this->w[w->connections[j]].name);
- d->type = AUDIO_MIXER_ENUM;
- if (w->type == COP_AWTYPE_PIN_COMPLEX)
- d->mixer_class = AZ_CLASS_OUTPUT;
- else if (w->type == COP_AWTYPE_AUDIO_INPUT)
- d->mixer_class = AZ_CLASS_RECORD;
- else
- d->mixer_class = AZ_CLASS_INPUT;
- d->next = AUDIO_MIXER_LAST;
- d->prev = AUDIO_MIXER_LAST;
- m->target = j;
- d->un.e.num_mem = 2;
- d->un.e.member[0].ord = 0;
- strlcpy(d->un.e.member[0].label.name,
- AudioNoff, MAX_AUDIO_DEV_LEN);
- d->un.e.member[1].ord = 1;
- strlcpy(d->un.e.member[1].label.name,
- AudioNon, MAX_AUDIO_DEV_LEN);
- this->nmixers++;
- }
- }
-
- /* input gain */
- if (w->widgetcap & COP_AWCAP_INAMP
- && COP_AMPCAP_NUMSTEPS(w->inamp_cap)) {
- DPRINTF(("%s: input gain %s\n", __func__, w->name));
- for (j = 0; j < w->nconnections; j++) {
- MIXER_REG_PROLOG;
- if (!VALID_WIDGET_NID(w->connections[j], this))
- continue;
- DPRINTF(("%s: input gain %s.%s\n", __func__,
- w->name, this->w[w->connections[j]].name));
- snprintf(d->label.name, sizeof(d->label.name),
- "%s.%s", w->name,
- this->w[w->connections[j]].name);
- d->type = AUDIO_MIXER_VALUE;
- if (w->type == COP_AWTYPE_PIN_COMPLEX)
- d->mixer_class = AZ_CLASS_OUTPUT;
- else if (w->type == COP_AWTYPE_AUDIO_INPUT)
- d->mixer_class = AZ_CLASS_RECORD;
- else
- d->mixer_class = AZ_CLASS_INPUT;
- d->next = AUDIO_MIXER_LAST;
- d->prev = AUDIO_MIXER_LAST;
- m->target = j;
- d->un.v.num_channels = WIDGET_CHANNELS(w);
-#ifdef MAX_VOLUME_255
- d->un.v.units.name[0] = 0;
- d->un.v.delta = AUDIO_MAX_GAIN /
- COP_AMPCAP_NUMSTEPS(w->inamp_cap);
-#else
- snprintf(d->un.v.units.name,
- sizeof(d->un.v.units.name), "0.25x%ddB",
- COP_AMPCAP_STEPSIZE(w->inamp_cap)+1);
- d->un.v.delta = 1;
-#endif
- this->nmixers++;
- }
- }
-
- /* pin direction */
- if (w->type == COP_AWTYPE_PIN_COMPLEX &&
- w->d.pin.cap & COP_PINCAP_OUTPUT &&
- w->d.pin.cap & COP_PINCAP_INPUT) {
- MIXER_REG_PROLOG;
- DPRINTF(("%s: pin dir %s\n", __func__, w->name));
- snprintf(d->label.name, sizeof(d->label.name),
- "%s.dir", w->name);
- d->type = AUDIO_MIXER_ENUM;
- d->mixer_class = AZ_CLASS_OUTPUT;
- d->next = AUDIO_MIXER_LAST;
- d->prev = AUDIO_MIXER_LAST;
- m->target = MI_TARGET_PINDIR;
- d->un.e.num_mem = 2;
- d->un.e.member[0].ord = 0;
- strlcpy(d->un.e.member[0].label.name, AudioNinput,
- MAX_AUDIO_DEV_LEN);
- d->un.e.member[1].ord = 1;
- strlcpy(d->un.e.member[1].label.name, AudioNoutput,
- MAX_AUDIO_DEV_LEN);
- this->nmixers++;
- }
-
- /* pin headphone-boost */
- if (w->type == COP_AWTYPE_PIN_COMPLEX &&
- w->d.pin.cap & COP_PINCAP_HEADPHONE) {
- MIXER_REG_PROLOG;
- DPRINTF(("%s: hpboost %s\n", __func__, w->name));
- snprintf(d->label.name, sizeof(d->label.name),
- "%s.boost", w->name);
- d->type = AUDIO_MIXER_ENUM;
- d->mixer_class = AZ_CLASS_OUTPUT;
- d->next = AUDIO_MIXER_LAST;
- d->prev = AUDIO_MIXER_LAST;
- m->target = MI_TARGET_PINBOOST;
- d->un.e.num_mem = 2;
- d->un.e.member[0].ord = 0;
- strlcpy(d->un.e.member[0].label.name, AudioNoff,
- MAX_AUDIO_DEV_LEN);
- d->un.e.member[1].ord = 1;
- strlcpy(d->un.e.member[1].label.name, AudioNon,
- MAX_AUDIO_DEV_LEN);
- this->nmixers++;
- }
-
- /* volume knob */
- if (w->type == COP_AWTYPE_VOLUME_KNOB &&
- w->d.volume.cap & COP_VKCAP_DELTA) {
- MIXER_REG_PROLOG;
- DPRINTF(("%s: volume knob %s\n", __func__, w->name));
- strlcpy(d->label.name, w->name, sizeof(d->label.name));
- d->type = AUDIO_MIXER_VALUE;
- d->mixer_class = AZ_CLASS_OUTPUT;
- d->next = AUDIO_MIXER_LAST;
- d->prev = AUDIO_MIXER_LAST;
- m->target = MI_TARGET_VOLUME;
- d->un.v.num_channels = 1;
- d->un.v.units.name[0] = 0;
-#ifdef MAX_VOLUME_255
- d->un.v.delta = AUDIO_MAX_GAIN /
- COP_VKCAP_NUMSTEPS(w->d.volume.cap);
-#else
- d->un.v.delta = 1;
-#endif
- this->nmixers++;
- }
- }
-
- /* if the codec has multiple DAC groups, create "inputs.usingdac" */
- if (this->ndacgroups > 1) {
- MIXER_REG_PROLOG;
- DPRINTF(("%s: create inputs.usingdac\n", __func__));
- strlcpy(d->label.name, "usingdac", sizeof(d->label.name));
- d->type = AUDIO_MIXER_ENUM;
- d->mixer_class = AZ_CLASS_INPUT;
- d->next = AUDIO_MIXER_LAST;
- d->prev = AUDIO_MIXER_LAST;
- m->target = MI_TARGET_DAC;
- for (i = 0; i < this->ndacgroups && i < 32; i++) {
- d->un.e.member[i].ord = i;
- for (j = 0; j < this->dacgroups[i].nconv; j++) {
- if (j * 2 >= MAX_AUDIO_DEV_LEN)
- break;
- snprintf(d->un.e.member[i].label.name + j*2,
- MAX_AUDIO_DEV_LEN - j*2, "%2.2x",
- this->dacgroups[i].conv[j]);
- }
- }
- d->un.e.num_mem = i;
- this->nmixers++;
- }
-
- /* if the codec has multiple ADCs, create "record.usingadc" */
- if (this->nadcs > 1) {
- MIXER_REG_PROLOG;
- DPRINTF(("%s: create inputs.usingadc\n", __func__));
- strlcpy(d->label.name, "usingadc", sizeof(d->label.name));
- d->type = AUDIO_MIXER_ENUM;
- d->mixer_class = AZ_CLASS_RECORD;
- d->next = AUDIO_MIXER_LAST;
- d->prev = AUDIO_MIXER_LAST;
- m->target = MI_TARGET_ADC;
- for (i = 0; i < this->nadcs && i < 32; i++) {
- d->un.e.member[i].ord = i;
- strlcpy(d->un.e.member[i].label.name,
- this->w[this->adcs[i]].name, MAX_AUDIO_DEV_LEN);
- }
- d->un.e.num_mem = i;
- this->nmixers++;
- }
-
- /* unmute all */
- DPRINTF(("%s: unmute\n", __func__));
- for (i = 0; i < this->nmixers; i++) {
- mixer_ctrl_t mc;
-
- if (!IS_MI_TARGET_INAMP(this->mixers[i].target) &&
- this->mixers[i].target != MI_TARGET_OUTAMP)
- continue;
- if (this->mixers[i].devinfo.type != AUDIO_MIXER_ENUM)
- continue;
- mc.dev = i;
- mc.type = AUDIO_MIXER_ENUM;
- mc.un.ord = 0;
- azalia_mixer_set(this, &mc);
- }
-
- /*
- * for bidirectional pins,
- * green=front, orange=surround, gray=c/lfe, black=side --> output
- * blue=line-in, pink=mic-in --> input
- */
- DPRINTF(("%s: process bidirectional pins\n", __func__));
- for (i = 0; i < this->nmixers; i++) {
- mixer_ctrl_t mc;
-
- if (this->mixers[i].target != MI_TARGET_PINDIR)
- continue;
- mc.dev = i;
- mc.type = AUDIO_MIXER_ENUM;
- switch (this->w[this->mixers[i].nid].d.pin.color) {
- case CORB_CD_GREEN:
- case CORB_CD_ORANGE:
- case CORB_CD_GRAY:
- case CORB_CD_BLACK:
- mc.un.ord = 1;
- break;
- default:
- mc.un.ord = 0;
- }
- azalia_mixer_set(this, &mc);
- }
-
- /* set unextreme volume */
- DPRINTF(("%s: set volume\n", __func__));
- for (i = 0; i < this->nmixers; i++) {
- mixer_ctrl_t mc;
-
- if (!IS_MI_TARGET_INAMP(this->mixers[i].target) &&
- this->mixers[i].target != MI_TARGET_OUTAMP &&
- this->mixers[i].target != MI_TARGET_VOLUME)
- continue;
- if (this->mixers[i].devinfo.type != AUDIO_MIXER_VALUE)
- continue;
- mc.dev = i;
- mc.type = AUDIO_MIXER_VALUE;
- mc.un.value.num_channels = 1;
- mc.un.value.level[0] = AUDIO_MAX_GAIN / 2;
- if (this->mixers[i].target != MI_TARGET_VOLUME &&
- WIDGET_CHANNELS(&this->w[this->mixers[i].nid]) == 2) {
- mc.un.value.num_channels = 2;
- mc.un.value.level[1] = AUDIO_MAX_GAIN / 2;
- }
- azalia_mixer_set(this, &mc);
- }
-
- return 0;
-}
-
-int
-azalia_mixer_delete(codec_t *this)
-{
- if (this->mixers == NULL)
- return 0;
- free(this->mixers, M_DEVBUF);
- this->mixers = NULL;
- return 0;
-}
-
-int
-azalia_mixer_get(const codec_t *this, mixer_ctrl_t *mc)
-{
- const mixer_item_t *m;
- uint32_t result;
- int err;
-
- if (mc->dev >= this->nmixers)
- return ENXIO;
- m = &this->mixers[mc->dev];
- mc->type = m->devinfo.type;
- if (mc->type == AUDIO_MIXER_CLASS)
- return 0; /* nothing to do */
-
- /* inamp mute */
- if (IS_MI_TARGET_INAMP(m->target) && m->devinfo.type == AUDIO_MIXER_ENUM) {
- err = this->comresp(this, m->nid, CORB_GET_AMPLIFIER_GAIN_MUTE,
- CORB_GAGM_INPUT | CORB_GAGM_LEFT |
- MI_TARGET_INAMP(m->target), &result);
- if (err)
- return err;
- mc->un.ord = result & CORB_GAGM_MUTE ? 1 : 0;
- }
-
- /* inamp gain */
- else if (IS_MI_TARGET_INAMP(m->target) && m->devinfo.type == AUDIO_MIXER_VALUE) {
- err = this->comresp(this, m->nid, CORB_GET_AMPLIFIER_GAIN_MUTE,
- CORB_GAGM_INPUT | CORB_GAGM_LEFT |
- MI_TARGET_INAMP(m->target), &result);
- if (err)
- return err;
- mc->un.value.level[0] = azalia_mixer_from_device_value(this, m,
- CORB_GAGM_GAIN(result));
- mc->un.value.num_channels = WIDGET_CHANNELS(&this->w[m->nid]);
- if (mc->un.value.num_channels == 2) {
- err = this->comresp(this, m->nid,
- CORB_GET_AMPLIFIER_GAIN_MUTE, CORB_GAGM_INPUT |
- CORB_GAGM_RIGHT | MI_TARGET_INAMP(m->target),
- &result);
- if (err)
- return err;
- mc->un.value.level[1] = azalia_mixer_from_device_value
- (this, m, CORB_GAGM_GAIN(result));
- }
- }
-
- /* outamp mute */
- else if (m->target == MI_TARGET_OUTAMP && m->devinfo.type == AUDIO_MIXER_ENUM) {
- err = this->comresp(this, m->nid, CORB_GET_AMPLIFIER_GAIN_MUTE,
- CORB_GAGM_OUTPUT | CORB_GAGM_LEFT | 0, &result);
- if (err)
- return err;
- mc->un.ord = result & CORB_GAGM_MUTE ? 1 : 0;
- }
-
- /* outamp gain */
- else if (m->target == MI_TARGET_OUTAMP && m->devinfo.type == AUDIO_MIXER_VALUE) {
- err = this->comresp(this, m->nid, CORB_GET_AMPLIFIER_GAIN_MUTE,
- CORB_GAGM_OUTPUT | CORB_GAGM_LEFT | 0, &result);
- if (err)
- return err;
- mc->un.value.level[0] = azalia_mixer_from_device_value(this, m,
- CORB_GAGM_GAIN(result));
- mc->un.value.num_channels = WIDGET_CHANNELS(&this->w[m->nid]);
- if (mc->un.value.num_channels == 2) {
- err = this->comresp(this, m->nid,
- CORB_GET_AMPLIFIER_GAIN_MUTE,
- CORB_GAGM_OUTPUT | CORB_GAGM_RIGHT | 0, &result);
- if (err)
- return err;
- mc->un.value.level[1] = azalia_mixer_from_device_value
- (this, m, CORB_GAGM_GAIN(result));
- }
- }
-
- /* selection */
- else if (m->target == MI_TARGET_CONNLIST) {
- int i;
- err = this->comresp(this, m->nid,
- CORB_GET_CONNECTION_SELECT_CONTROL, 0, &result);
- if (err)
- return err;
- result = CORB_CSC_INDEX(result);
- mc->un.ord = -1;
- for (i = 0; i <= result; i++) {
- if (!VALID_WIDGET_NID(this->w[m->nid].connections[i], this))
- continue;
- mc->un.ord++;
- }
- }
-
- /* pin I/O */
- else if (m->target == MI_TARGET_PINDIR) {
- err = this->comresp(this, m->nid,
- CORB_GET_PIN_WIDGET_CONTROL, 0, &result);
- if (err)
- return err;
- mc->un.ord = result & CORB_PWC_OUTPUT ? 1 : 0;
- }
-
- /* pin headphone-boost */
- else if (m->target == MI_TARGET_PINBOOST) {
- err = this->comresp(this, m->nid,
- CORB_GET_PIN_WIDGET_CONTROL, 0, &result);
- if (err)
- return err;
- mc->un.ord = result & CORB_PWC_HEADPHONE ? 1 : 0;
- }
-
- /* DAC group selection */
- else if (m->target == MI_TARGET_DAC) {
- mc->un.ord = this->cur_dac;
- }
-
- /* ADC selection */
- else if (m->target == MI_TARGET_ADC) {
- mc->un.ord = this->cur_adc;
- }
-
- /* Volume knob */
- else if (m->target == MI_TARGET_VOLUME) {
- err = this->comresp(this, m->nid, CORB_GET_VOLUME_KNOB,
- 0, &result);
- if (err)
- return err;
- mc->un.value.level[0] = azalia_mixer_from_device_value(this, m,
- CORB_VKNOB_VOLUME(result));
- mc->un.value.num_channels = 1;
- }
-
- else {
- printf("%s: internal error in %s: %x\n", XNAME(this->az),
- __func__, m->target);
- return -1;
- }
- return 0;
-}
-
-int
-azalia_mixer_set(codec_t *this, const mixer_ctrl_t *mc)
-{
- const mixer_item_t *m;
- uint32_t result, value;
- int err;
-
- if (mc->dev >= this->nmixers)
- return ENXIO;
- m = &this->mixers[mc->dev];
- if (mc->type != m->devinfo.type)
- return EINVAL;
- if (mc->type == AUDIO_MIXER_CLASS)
- return 0; /* nothing to do */
-
- /* inamp mute */
- if (IS_MI_TARGET_INAMP(m->target) && m->devinfo.type == AUDIO_MIXER_ENUM) {
- /* We have to set stereo mute separately to keep each gain value. */
- err = this->comresp(this, m->nid, CORB_GET_AMPLIFIER_GAIN_MUTE,
- CORB_GAGM_INPUT | CORB_GAGM_LEFT |
- MI_TARGET_INAMP(m->target), &result);
- if (err)
- return err;
- value = CORB_AGM_INPUT | CORB_AGM_LEFT |
- (m->target << CORB_AGM_INDEX_SHIFT) |
- CORB_GAGM_GAIN(result);
- if (mc->un.ord)
- value |= CORB_AGM_MUTE;
- err = this->comresp(this, m->nid, CORB_SET_AMPLIFIER_GAIN_MUTE,
- value, &result);
- if (err)
- return err;
- if (WIDGET_CHANNELS(&this->w[m->nid]) == 2) {
- err = this->comresp(this, m->nid,
- CORB_GET_AMPLIFIER_GAIN_MUTE, CORB_GAGM_INPUT |
- CORB_GAGM_RIGHT | MI_TARGET_INAMP(m->target),
- &result);
- if (err)
- return err;
- value = CORB_AGM_INPUT | CORB_AGM_RIGHT |
- (m->target << CORB_AGM_INDEX_SHIFT) |
- CORB_GAGM_GAIN(result);
- if (mc->un.ord)
- value |= CORB_AGM_MUTE;
- err = this->comresp(this, m->nid,
- CORB_SET_AMPLIFIER_GAIN_MUTE, value, &result);
- if (err)
- return err;
- }
- }
-
- /* inamp gain */
- else if (IS_MI_TARGET_INAMP(m->target) && m->devinfo.type == AUDIO_MIXER_VALUE) {
- if (mc->un.value.num_channels < 1)
- return EINVAL;
- if (!azalia_mixer_validate_value(this, m, mc->un.value.level[0]))
- return EINVAL;
- err = this->comresp(this, m->nid, CORB_GET_AMPLIFIER_GAIN_MUTE,
- CORB_GAGM_INPUT | CORB_GAGM_LEFT |
- MI_TARGET_INAMP(m->target), &result);
- if (err)
- return err;
- value = azalia_mixer_to_device_value(this, m,
- mc->un.value.level[0]);
- value = CORB_AGM_INPUT | CORB_AGM_LEFT |
- (m->target << CORB_AGM_INDEX_SHIFT) |
- (result & CORB_GAGM_MUTE ? CORB_AGM_MUTE : 0) |
- (value & CORB_AGM_GAIN_MASK);
- err = this->comresp(this, m->nid, CORB_SET_AMPLIFIER_GAIN_MUTE,
- value, &result);
- if (err)
- return err;
- if (mc->un.value.num_channels >= 2 &&
- WIDGET_CHANNELS(&this->w[m->nid]) == 2) {
- if (!azalia_mixer_validate_value(this, m,
- mc->un.value.level[1]))
- return EINVAL;
- err = this->comresp(this, m->nid,
- CORB_GET_AMPLIFIER_GAIN_MUTE, CORB_GAGM_INPUT |
- CORB_GAGM_RIGHT | MI_TARGET_INAMP(m->target),
- &result);
- if (err)
- return err;
- value = azalia_mixer_to_device_value(this, m,
- mc->un.value.level[1]);
- value = CORB_AGM_INPUT | CORB_AGM_RIGHT |
- (m->target << CORB_AGM_INDEX_SHIFT) |
- (result & CORB_GAGM_MUTE ? CORB_AGM_MUTE : 0) |
- (value & CORB_AGM_GAIN_MASK);
- err = this->comresp(this, m->nid,
- CORB_SET_AMPLIFIER_GAIN_MUTE, value, &result);
- if (err)
- return err;
- }
- }
-
- /* outamp mute */
- else if (m->target == MI_TARGET_OUTAMP && m->devinfo.type == AUDIO_MIXER_ENUM) {
- err = this->comresp(this, m->nid, CORB_GET_AMPLIFIER_GAIN_MUTE,
- CORB_GAGM_OUTPUT | CORB_GAGM_LEFT, &result);
- if (err)
- return err;
- value = CORB_AGM_OUTPUT | CORB_AGM_LEFT | CORB_GAGM_GAIN(result);
- if (mc->un.ord)
- value |= CORB_AGM_MUTE;
- err = this->comresp(this, m->nid, CORB_SET_AMPLIFIER_GAIN_MUTE,
- value, &result);
- if (err)
- return err;
- if (WIDGET_CHANNELS(&this->w[m->nid]) == 2) {
- err = this->comresp(this, m->nid,
- CORB_GET_AMPLIFIER_GAIN_MUTE,
- CORB_GAGM_OUTPUT | CORB_GAGM_RIGHT, &result);
- if (err)
- return err;
- value = CORB_AGM_OUTPUT | CORB_AGM_RIGHT |
- CORB_GAGM_GAIN(result);
- if (mc->un.ord)
- value |= CORB_AGM_MUTE;
- err = this->comresp(this, m->nid,
- CORB_SET_AMPLIFIER_GAIN_MUTE, value, &result);
- if (err)
- return err;
- }
- }
-
- /* outamp gain */
- else if (m->target == MI_TARGET_OUTAMP && m->devinfo.type == AUDIO_MIXER_VALUE) {
- if (mc->un.value.num_channels < 1)
- return EINVAL;
- if (!azalia_mixer_validate_value(this, m, mc->un.value.level[0]))
- return EINVAL;
- err = this->comresp(this, m->nid, CORB_GET_AMPLIFIER_GAIN_MUTE,
- CORB_GAGM_OUTPUT | CORB_GAGM_LEFT, &result);
- if (err)
- return err;
- value = azalia_mixer_to_device_value(this, m,
- mc->un.value.level[0]);
- value = CORB_AGM_OUTPUT | CORB_AGM_LEFT |
- (result & CORB_GAGM_MUTE ? CORB_AGM_MUTE : 0) |
- (value & CORB_AGM_GAIN_MASK);
- err = this->comresp(this, m->nid, CORB_SET_AMPLIFIER_GAIN_MUTE,
- value, &result);
- if (err)
- return err;
- if (mc->un.value.num_channels >= 2 &&
- WIDGET_CHANNELS(&this->w[m->nid]) == 2) {
- if (!azalia_mixer_validate_value(this, m,
- mc->un.value.level[1]))
- return EINVAL;
- err = this->comresp(this, m->nid,
- CORB_GET_AMPLIFIER_GAIN_MUTE, CORB_GAGM_OUTPUT |
- CORB_GAGM_RIGHT, &result);
- if (err)
- return err;
- value = azalia_mixer_to_device_value(this, m,
- mc->un.value.level[1]);
- value = CORB_AGM_OUTPUT | CORB_AGM_RIGHT |
- (result & CORB_GAGM_MUTE ? CORB_AGM_MUTE : 0) |
- (value & CORB_AGM_GAIN_MASK);
- err = this->comresp(this, m->nid,
- CORB_SET_AMPLIFIER_GAIN_MUTE, value, &result);
- if (err)
- return err;
- }
- }
-
- /* selection */
- else if (m->target == MI_TARGET_CONNLIST) {
- int i;
- for (i = 0, value = 0; i < this->w[m->nid].nconnections; i++) {
- if (!VALID_WIDGET_NID(this->w[m->nid].connections[i], this))
- continue;
- if (value == mc->un.ord)
- break;
- value++;
- }
- if (i >= this->w[m->nid].nconnections)
- return EINVAL;
- err = this->comresp(this, m->nid,
- CORB_SET_CONNECTION_SELECT_CONTROL, i, &result);
- if (err)
- return err;
- }
-
- /* pin I/O */
- else if (m->target == MI_TARGET_PINDIR) {
- if (mc->un.ord >= 2)
- return EINVAL;
- err = this->comresp(this, m->nid,
- CORB_GET_PIN_WIDGET_CONTROL, 0, &result);
- if (err)
- return err;
- if (mc->un.ord == 0) {
- result &= ~CORB_PWC_OUTPUT;
- result |= CORB_PWC_INPUT;
- } else {
- result &= ~CORB_PWC_INPUT;
- result |= CORB_PWC_OUTPUT;
- }
- err = this->comresp(this, m->nid,
- CORB_SET_PIN_WIDGET_CONTROL, result, &result);
- if (err)
- return err;
- }
-
- /* pin headphone-boost */
- else if (m->target == MI_TARGET_PINBOOST) {
- if (mc->un.ord >= 2)
- return EINVAL;
- err = this->comresp(this, m->nid,
- CORB_GET_PIN_WIDGET_CONTROL, 0, &result);
- if (err)
- return err;
- if (mc->un.ord == 0) {
- result &= ~CORB_PWC_HEADPHONE;
- } else {
- result |= CORB_PWC_HEADPHONE;
- }
- err = this->comresp(this, m->nid,
- CORB_SET_PIN_WIDGET_CONTROL, result, &result);
- if (err)
- return err;
- }
-
- /* DAC group selection */
- else if (m->target == MI_TARGET_DAC) {
- if (this->az->running)
- return EBUSY;
- if (mc->un.ord >= this->ndacgroups)
- return EINVAL;
- this->cur_dac = mc->un.ord;
- return azalia_codec_construct_format(this);
- }
-
- /* ADC selection */
- else if (m->target == MI_TARGET_ADC) {
- if (this->az->running)
- return EBUSY;
- if (mc->un.ord >= this->nadcs)
- return EINVAL;
- this->cur_adc = mc->un.ord;
- /* use this->adcs[this->cur_adc] */
- return azalia_codec_construct_format(this);
- }
-
- /* Volume knob */
- else if (m->target == MI_TARGET_VOLUME) {
- if (mc->un.value.num_channels != 1)
- return EINVAL;
- if (!azalia_mixer_validate_value(this, m, mc->un.value.level[0]))
- return EINVAL;
- value = azalia_mixer_to_device_value(this, m,
- mc->un.value.level[0]) | CORB_VKNOB_DIRECT;
- err = this->comresp(this, m->nid, CORB_SET_VOLUME_KNOB,
- value, &result);
- if (err)
- return err;
- }
-
- else {
- printf("%s: internal error in %s: %x\n", XNAME(this->az),
- __func__, m->target);
- return -1;
- }
- return 0;
-}
-
-int
-azalia_mixer_ensure_capacity(codec_t *this, size_t newsize)
-{
- size_t newmax;
- void *newbuf;
-
- if (this->maxmixers >= newsize)
- return 0;
- newmax = this->maxmixers + 10;
- if (newmax < newsize)
- newmax = newsize;
- newbuf = malloc(sizeof(mixer_item_t) * newmax, M_DEVBUF, M_NOWAIT);
- if (newbuf == NULL) {
- printf("%s: out of memory in %s\n", XNAME(this->az),
- __func__);
- return ENOMEM;
- }
- bzero(newbuf, sizeof(mixer_item_t) * newmax);
- bcopy(this->mixers, newbuf, this->maxmixers * sizeof(mixer_item_t));
- free(this->mixers, M_DEVBUF);
- this->mixers = newbuf;
- this->maxmixers = newmax;
- return 0;
-}
-
-u_char
-azalia_mixer_from_device_value(const codec_t *this, const mixer_item_t *m,
- uint32_t dv)
-{
-#ifdef MAX_VOLUME_255
- uint32_t dmax;
-
- if (IS_MI_TARGET_INAMP(m->target))
- dmax = COP_AMPCAP_NUMSTEPS(this->w[m->nid].inamp_cap);
- else if (m->target == MI_TARGET_OUTAMP)
- dmax = COP_AMPCAP_NUMSTEPS(this->w[m->nid].outamp_cap);
- else if (m->target == MI_TARGET_VOLUME)
- dmax = COP_VKCAP_NUMSTEPS(this->w[m->nid].d.volume.cap);
- else {
- printf("unknown target: %d\n", m->target);
- dmax = 255;
- }
- return dv * AUDIO_MAX_GAIN / dmax;
-#else
- return dv;
-#endif
-}
-
-uint32_t
-azalia_mixer_to_device_value(const codec_t *this, const mixer_item_t *m,
- u_char uv)
-{
-#ifdef MAX_VOLUME_255
- uint32_t dmax;
-
- if (IS_MI_TARGET_INAMP(m->target))
- dmax = COP_AMPCAP_NUMSTEPS(this->w[m->nid].inamp_cap);
- else if (m->target == MI_TARGET_OUTAMP)
- dmax = COP_AMPCAP_NUMSTEPS(this->w[m->nid].outamp_cap);
- else if (m->target == MI_TARGET_VOLUME)
- dmax = COP_VKCAP_NUMSTEPS(this->w[m->nid].d.volume.cap);
- else {
- printf("unknown target: %d\n", m->target);
- dmax = 255;
- }
- return uv * dmax / AUDIO_MAX_GAIN;
-#else
- return uv;
-#endif
-}
-
-boolean_t
-azalia_mixer_validate_value(const codec_t *this, const mixer_item_t *m,
- u_char uv)
-{
-#ifdef MAX_VOLUME_255
- return TRUE;
-#else
- uint32_t dmax;
-
- if (IS_MI_TARGET_INAMP(m->target))
- dmax = COP_AMPCAP_NUMSTEPS(this->w[m->nid].inamp_cap);
- else if (m->target == MI_TARGET_OUTAMP)
- dmax = COP_AMPCAP_NUMSTEPS(this->w[m->nid].outamp_cap);
- else if (m->target == MI_TARGET_VOLUME)
- dmax = COP_VKCAP_NUMSTEPS(this->w[m->nid].d.volume.cap);
- return uv <= dmax;
-#endif
-}
-
-/* ================================================================
* HDA widget functions
* ================================================================ */
@@ -2489,7 +1544,6 @@ azalia_widget_init_connection(widget_t *this, const codec_t *codec)
length = COP_CLL_LENGTH(result);
if (length == 0)
return 0;
- DPRINTF(("%s: CLE=0x%x\n", __func__, result));
this->nconnections = length;
this->connections = malloc(sizeof(nid_t) * (length + 3),
M_DEVBUF, M_NOWAIT);
@@ -2503,7 +1557,6 @@ azalia_widget_init_connection(widget_t *this, const codec_t *codec)
CORB_GET_CONNECTION_LIST_ENTRY, i, &result);
if (err)
return err;
- DPRINTF(("%s: long[%d]=0x%x\n", __func__, i, result));
this->connections[i++] = CORB_CLE_LONG_0(result);
this->connections[i++] = CORB_CLE_LONG_1(result);
}
@@ -2513,7 +1566,6 @@ azalia_widget_init_connection(widget_t *this, const codec_t *codec)
CORB_GET_CONNECTION_LIST_ENTRY, i, &result);
if (err)
return err;
- DPRINTF(("%s: short[%d]=0x%x\n", __func__, i, result));
this->connections[i++] = CORB_CLE_SHORT_0(result);
this->connections[i++] = CORB_CLE_SHORT_1(result);
this->connections[i++] = CORB_CLE_SHORT_2(result);
@@ -2723,10 +1775,12 @@ int
azalia_open(void *v, int flags)
{
azalia_t *az;
+ codec_t *codec;
DPRINTF(("%s: flags=0x%x\n", __func__, flags));
az = v;
- az->running++;
+ codec = &az->codecs[az->codecno];
+ codec->running++;
return 0;
}
@@ -2734,10 +1788,12 @@ void
azalia_close(void *v)
{
azalia_t *az;
+ codec_t *codec;
DPRINTF(("%s\n", __func__));
az = v;
- az->running--;
+ codec = &az->codecs[az->codecno];
+ codec->running--;
}
int
@@ -2928,7 +1984,7 @@ azalia_set_port(void *v, mixer_ctrl_t *mc)
az = v;
co = &az->codecs[az->codecno];
- return azalia_mixer_set(co, mc);
+ return co->set_port(co, mc);
}
int
@@ -2939,14 +1995,14 @@ azalia_get_port(void *v, mixer_ctrl_t *mc)
az = v;
co = &az->codecs[az->codecno];
- return azalia_mixer_get(co, mc);
+ return co->get_port(co, mc);
}
int
azalia_query_devinfo(void *v, mixer_devinfo_t *mdev)
{
azalia_t *az;
- codec_t *co;
+ const codec_t *co;
az = v;
co = &az->codecs[az->codecno];
diff --git a/sys/dev/pci/azalia.h b/sys/dev/pci/azalia.h
index 9e601926a77..0c79899a1ca 100644
--- a/sys/dev/pci/azalia.h
+++ b/sys/dev/pci/azalia.h
@@ -1,4 +1,4 @@
-/* $OpenBSD: azalia.h,v 1.6 2006/05/11 23:34:35 brad Exp $ */
+/* $OpenBSD: azalia.h,v 1.7 2006/06/14 19:34:52 brad Exp $ */
/* $NetBSD: azalia.h,v 1.6 2006/01/16 14:15:26 kent Exp $ */
/*-
@@ -518,8 +518,14 @@ typedef struct codec_t {
int (*comresp)(const struct codec_t *, nid_t, uint32_t, uint32_t, uint32_t *);
int (*init_dacgroup)(struct codec_t *);
int (*init_widget)(const struct codec_t *, widget_t *, nid_t);
+ int (*mixer_init)(struct codec_t *);
+ int (*mixer_delete)(struct codec_t *);
+ int (*set_port)(struct codec_t *, mixer_ctrl_t *);
+ int (*get_port)(struct codec_t *, mixer_ctrl_t *);
struct azalia_t *az;
+ uint32_t vid; /* codec vendor/device ID */
+ uint32_t subid; /* PCI subvendor/device ID */
const char *name;
int address;
int nfunctions;
@@ -536,6 +542,7 @@ typedef struct codec_t {
int nadcs;
nid_t adcs[32];
int cur_adc; /* currently selected ADC index */
+ int running;
int nmixers, maxmixers;
mixer_item_t *mixers;
@@ -546,4 +553,5 @@ typedef struct codec_t {
} codec_t;
-int azalia_codec_init_vtbl(codec_t *, uint32_t);
+int azalia_codec_init_vtbl(codec_t *);
+int azalia_codec_construct_format(codec_t *);
diff --git a/sys/dev/pci/azalia_codec.c b/sys/dev/pci/azalia_codec.c
index 4fcadd111be..4eb51865d0e 100644
--- a/sys/dev/pci/azalia_codec.c
+++ b/sys/dev/pci/azalia_codec.c
@@ -1,4 +1,4 @@
-/* $OpenBSD: azalia_codec.c,v 1.8 2006/05/18 03:34:50 brad Exp $ */
+/* $OpenBSD: azalia_codec.c,v 1.9 2006/06/14 19:34:52 brad Exp $ */
/* $NetBSD: azalia_codec.c,v 1.8 2006/05/10 11:17:27 kent Exp $ */
/*-
@@ -43,62 +43,97 @@ __KERNEL_RCSID(0, "$NetBSD: azalia_codec.c,v 1.3 2005/09/29 04:14:03 kent Exp $"
#endif
#include <sys/param.h>
+#include <sys/device.h>
+#include <sys/malloc.h>
#include <sys/systm.h>
#include <uvm/uvm_param.h>
#include <dev/pci/azalia.h>
+#define XNAME(co) (((struct device *)co->az)->dv_xname)
+#define AZ_CLASS_INPUT 0
+#define AZ_CLASS_OUTPUT 1
+#define AZ_CLASS_RECORD 2
+#define ENUM_OFFON .un.e={2, {{{AudioNoff}, 0}, {{AudioNon}, 1}}}
+#define ENUM_IO .un.e={2, {{{"input"}, 0}, {{"output"}, 1}}}
-int azalia_codec_init_dacgroup(codec_t *);
-int azalia_codec_add_dacgroup(codec_t *, int, uint32_t);
-int azalia_codec_find_pin(const codec_t *, int, int, uint32_t);
-int azalia_codec_find_dac(const codec_t *, int, int);
-int alc260_init_dacgroup(codec_t *);
-int alc260_init_widget(const codec_t *, widget_t *, nid_t);
-int alc880_init_dacgroup(codec_t *);
-int alc882_init_dacgroup(codec_t *);
-int alc882_init_widget(const codec_t *, widget_t *, nid_t);
+
+int azalia_generic_codec_init_dacgroup(codec_t *);
+int azalia_generic_codec_add_dacgroup(codec_t *, int, uint32_t);
+int azalia_generic_codec_find_pin(const codec_t *, int, int, uint32_t);
+int azalia_generic_codec_find_dac(const codec_t *, int, int);
+
+int azalia_generic_mixer_init(codec_t *);
+int azalia_generic_mixer_fix_indexes(codec_t *);
+int azalia_generic_mixer_default(codec_t *);
+int azalia_generic_mixer_delete(codec_t *);
+int azalia_generic_mixer_ensure_capacity(codec_t *, size_t);
+int azalia_generic_mixer_get(const codec_t *, nid_t, int, mixer_ctrl_t *);
+int azalia_generic_mixer_set(codec_t *, nid_t, int, const mixer_ctrl_t *);
+u_char azalia_generic_mixer_from_device_value
+ (const codec_t *, nid_t, int, uint32_t );
+uint32_t azalia_generic_mixer_to_device_value
+ (const codec_t *, nid_t, int, u_char);
+boolean_t azalia_generic_mixer_validate_value
+ (const codec_t *, nid_t, int, u_char);
+int azalia_generic_set_port(codec_t *, mixer_ctrl_t *);
+int azalia_generic_get_port(codec_t *, mixer_ctrl_t *);
+
+int azalia_alc260_mixer_init(codec_t *);
+int azalia_alc260_init_dacgroup(codec_t *);
+int azalia_alc260_set_port(codec_t *, mixer_ctrl_t *);
+int azalia_alc880_init_dacgroup(codec_t *);
+int azalia_alc882_init_dacgroup(codec_t *);
+int azalia_alc882_init_widget(const codec_t *, widget_t *, nid_t);
#if 0
-int ad1981hd_init_widget(const codec_t *, widget_t *, nid_t);
+int azalia_ad1981hd_init_widget(const codec_t *, widget_t *, nid_t);
#endif
-int stac9221_init_dacgroup(codec_t *);
+int azalia_stac9221_init_dacgroup(codec_t *);
int
-azalia_codec_init_vtbl(codec_t *this, uint32_t vid)
+azalia_codec_init_vtbl(codec_t *this)
{
- switch (vid) {
+ /**
+ * We can refer this->vid and this->subid.
+ */
+ DPRINTF(("%s: vid=%08x subid=%08x\n", __func__, this->vid, this->subid));
+ this->name = NULL;
+ this->init_dacgroup = azalia_generic_codec_init_dacgroup;
+ this->mixer_init = azalia_generic_mixer_init;
+ this->mixer_delete = azalia_generic_mixer_delete;
+ this->set_port = azalia_generic_set_port;
+ this->get_port = azalia_generic_get_port;
+ switch (this->vid) {
case 0x10ec0260:
this->name = "Realtek ALC260";
- this->init_dacgroup = alc260_init_dacgroup;
- this->init_widget = alc260_init_widget;
+ this->mixer_init = azalia_alc260_mixer_init;
+ this->init_dacgroup = azalia_alc260_init_dacgroup;
+ this->set_port = azalia_alc260_set_port;
break;
case 0x10ec0880:
this->name = "Realtek ALC880";
- this->init_dacgroup = alc880_init_dacgroup;
+ this->init_dacgroup = azalia_alc880_init_dacgroup;
break;
case 0x10ec0882:
this->name = "Realtek ALC882";
- this->init_dacgroup = alc882_init_dacgroup;
- this->init_widget = alc882_init_widget;
+ this->init_dacgroup = azalia_alc882_init_dacgroup;
+ this->init_widget = azalia_alc882_init_widget;
break;
#if 0
case 0x11d41981:
/* http://www.analog.com/en/prod/0,2877,AD1981HD,00.html */
this->name = "Analog Devices AD1981HD";
- this->init_widget = ad1981hd_init_widget;
+ this->init_widget = azalia_ad1981hd_init_widget;
break;
#endif
case 0x83847680:
this->name = "Sigmatel STAC9221";
- this->init_dacgroup = stac9221_init_dacgroup;
+ this->init_dacgroup = azalia_stac9221_init_dacgroup;
break;
case 0x83847683:
this->name = "Sigmatel STAC9221D";
- this->init_dacgroup = stac9221_init_dacgroup;
+ this->init_dacgroup = azalia_stac9221_init_dacgroup;
break;
- default:
- this->name = NULL;
- this->init_dacgroup = azalia_codec_init_dacgroup;
}
return 0;
}
@@ -108,7 +143,7 @@ azalia_codec_init_vtbl(codec_t *this, uint32_t vid)
* ---------------------------------------------------------------- */
int
-azalia_codec_init_dacgroup(codec_t *this)
+azalia_generic_codec_init_dacgroup(codec_t *this)
{
int i, j, assoc, group;
@@ -121,8 +156,8 @@ azalia_codec_init_dacgroup(codec_t *this)
*/
this->ndacgroups = 0;
for (assoc = 0; assoc < CORB_CD_ASSOCIATION_MAX; assoc++) {
- azalia_codec_add_dacgroup(this, assoc, 0);
- azalia_codec_add_dacgroup(this, assoc, COP_AWCAP_DIGITAL);
+ azalia_generic_codec_add_dacgroup(this, assoc, 0);
+ azalia_generic_codec_add_dacgroup(this, assoc, COP_AWCAP_DIGITAL);
}
/* find DACs which do not connect with any pins by default */
@@ -166,16 +201,16 @@ azalia_codec_init_dacgroup(codec_t *this)
}
int
-azalia_codec_add_dacgroup(codec_t *this, int assoc, uint32_t digital)
+azalia_generic_codec_add_dacgroup(codec_t *this, int assoc, uint32_t digital)
{
int i, j, n, dac, seq;
n = 0;
for (seq = 0 ; seq < CORB_CD_SEQUENCE_MAX; seq++) {
- i = azalia_codec_find_pin(this, assoc, seq, digital);
+ i = azalia_generic_codec_find_pin(this, assoc, seq, digital);
if (i < 0)
continue;
- dac = azalia_codec_find_dac(this, i, 0);
+ dac = azalia_generic_codec_find_dac(this, i, 0);
if (dac < 0)
continue;
/* duplication check */
@@ -211,7 +246,7 @@ azalia_codec_add_dacgroup(codec_t *this, int assoc, uint32_t digital)
}
int
-azalia_codec_find_pin(const codec_t *this, int assoc, int seq, uint32_t digital)
+azalia_generic_codec_find_pin(const codec_t *this, int assoc, int seq, uint32_t digital)
{
int i;
@@ -232,7 +267,7 @@ azalia_codec_find_pin(const codec_t *this, int assoc, int seq, uint32_t digital)
}
int
-azalia_codec_find_dac(const codec_t *this, int index, int depth)
+azalia_generic_codec_find_dac(const codec_t *this, int index, int depth)
{
const widget_t *w;
int i, j, ret;
@@ -248,7 +283,7 @@ azalia_codec_find_dac(const codec_t *this, int index, int depth)
}
if (w->selected >= 0) {
j = w->connections[w->selected];
- ret = azalia_codec_find_dac(this, j, depth);
+ ret = azalia_generic_codec_find_dac(this, j, depth);
if (ret >= 0) {
DPRINTF(("%s: DAC path: nid=0x%x index=%d\n",
__func__, w->nid, index));
@@ -257,7 +292,7 @@ azalia_codec_find_dac(const codec_t *this, int index, int depth)
}
for (i = 0; i < w->nconnections; i++) {
j = w->connections[i];
- ret = azalia_codec_find_dac(this, j, depth);
+ ret = azalia_generic_codec_find_dac(this, j, depth);
if (ret >= 0) {
DPRINTF(("%s: DAC path: nid=0x%x index=%d\n",
__func__, w->nid, index));
@@ -268,11 +303,1205 @@ azalia_codec_find_dac(const codec_t *this, int index, int depth)
}
/* ----------------------------------------------------------------
+ * Generic mixer functions
+ * ---------------------------------------------------------------- */
+
+int
+azalia_generic_mixer_init(codec_t *this)
+{
+ /*
+ * pin "<color>%2.2x"
+ * audio output "dac%2.2x"
+ * audio input "adc%2.2x"
+ * mixer "mixer%2.2x"
+ * selector "sel%2.2x"
+ */
+ mixer_item_t *m;
+ int nadcs;
+ int err, i, j, k;
+
+ nadcs = 0;
+ this->maxmixers = 10;
+ this->nmixers = 0;
+ this->mixers = malloc(sizeof(mixer_item_t) * this->maxmixers,
+ M_DEVBUF, M_NOWAIT);
+ if (this->mixers == NULL) {
+ printf("%s: out of memory in %s\n", XNAME(this), __func__);
+ return ENOMEM;
+ }
+ bzero(this->mixers, sizeof(mixer_item_t) * this->maxmixers);
+
+ /* register classes */
+ DPRINTF(("%s: register classes\n", __func__));
+ m = &this->mixers[AZ_CLASS_INPUT];
+ m->devinfo.index = AZ_CLASS_INPUT;
+ strlcpy(m->devinfo.label.name, AudioCinputs,
+ sizeof(m->devinfo.label.name));
+ m->devinfo.type = AUDIO_MIXER_CLASS;
+ m->devinfo.mixer_class = AZ_CLASS_INPUT;
+ m->devinfo.next = AUDIO_MIXER_LAST;
+ m->devinfo.prev = AUDIO_MIXER_LAST;
+ m->nid = 0;
+
+ m = &this->mixers[AZ_CLASS_OUTPUT];
+ m->devinfo.index = AZ_CLASS_OUTPUT;
+ strlcpy(m->devinfo.label.name, AudioCoutputs,
+ sizeof(m->devinfo.label.name));
+ m->devinfo.type = AUDIO_MIXER_CLASS;
+ m->devinfo.mixer_class = AZ_CLASS_OUTPUT;
+ m->devinfo.next = AUDIO_MIXER_LAST;
+ m->devinfo.prev = AUDIO_MIXER_LAST;
+ m->nid = 0;
+
+ m = &this->mixers[AZ_CLASS_RECORD];
+ m->devinfo.index = AZ_CLASS_RECORD;
+ strlcpy(m->devinfo.label.name, AudioCrecord,
+ sizeof(m->devinfo.label.name));
+ m->devinfo.type = AUDIO_MIXER_CLASS;
+ m->devinfo.mixer_class = AZ_CLASS_RECORD;
+ m->devinfo.next = AUDIO_MIXER_LAST;
+ m->devinfo.prev = AUDIO_MIXER_LAST;
+ m->nid = 0;
+
+ this->nmixers = AZ_CLASS_RECORD + 1;
+
+#define MIXER_REG_PROLOG \
+ mixer_devinfo_t *d; \
+ err = azalia_generic_mixer_ensure_capacity(this, this->nmixers + 1); \
+ if (err) \
+ return err; \
+ m = &this->mixers[this->nmixers]; \
+ d = &m->devinfo; \
+ m->nid = i
+
+ FOR_EACH_WIDGET(this, i) {
+ const widget_t *w;
+
+ w = &this->w[i];
+
+ if (w->type == COP_AWTYPE_AUDIO_INPUT)
+ nadcs++;
+
+ /* selector */
+ if (w->type != COP_AWTYPE_AUDIO_MIXER && w->nconnections >= 2) {
+ MIXER_REG_PROLOG;
+ DPRINTF(("%s: selector %s\n", __func__, w->name));
+ snprintf(d->label.name, sizeof(d->label.name),
+ "%s.source", w->name);
+ d->type = AUDIO_MIXER_ENUM;
+ if (w->type == COP_AWTYPE_AUDIO_MIXER)
+ d->mixer_class = AZ_CLASS_RECORD;
+ else if (w->type == COP_AWTYPE_AUDIO_SELECTOR)
+ d->mixer_class = AZ_CLASS_INPUT;
+ else
+ d->mixer_class = AZ_CLASS_OUTPUT;
+ m->target = MI_TARGET_CONNLIST;
+ for (j = 0, k = 0; j < w->nconnections && k < 32; j++) {
+ if (!VALID_WIDGET_NID(w->connections[j], this))
+ continue;
+ DPRINTF(("%s: selector %d=%s\n", __func__, j,
+ this->w[w->connections[j]].name));
+ d->un.e.member[k].ord = j;
+ strlcpy(d->un.e.member[k].label.name,
+ this->w[w->connections[j]].name,
+ MAX_AUDIO_DEV_LEN);
+ k++;
+ }
+ d->un.e.num_mem = k;
+ this->nmixers++;
+ }
+
+ /* output mute */
+ if (w->widgetcap & COP_AWCAP_OUTAMP &&
+ w->outamp_cap & COP_AMPCAP_MUTE) {
+ MIXER_REG_PROLOG;
+ DPRINTF(("%s: output mute %s\n", __func__, w->name));
+ snprintf(d->label.name, sizeof(d->label.name),
+ "%s.mute", w->name);
+ d->type = AUDIO_MIXER_ENUM;
+ if (w->type == COP_AWTYPE_AUDIO_MIXER)
+ d->mixer_class = AZ_CLASS_OUTPUT;
+ else if (w->type == COP_AWTYPE_AUDIO_SELECTOR)
+ d->mixer_class = AZ_CLASS_OUTPUT;
+ else if (w->type == COP_AWTYPE_PIN_COMPLEX)
+ d->mixer_class = AZ_CLASS_OUTPUT;
+ else
+ d->mixer_class = AZ_CLASS_INPUT;
+ m->target = MI_TARGET_OUTAMP;
+ d->un.e.num_mem = 2;
+ d->un.e.member[0].ord = 0;
+ strlcpy(d->un.e.member[0].label.name, AudioNoff,
+ MAX_AUDIO_DEV_LEN);
+ d->un.e.member[1].ord = 1;
+ strlcpy(d->un.e.member[1].label.name, AudioNon,
+ MAX_AUDIO_DEV_LEN);
+ this->nmixers++;
+ }
+
+ /* output gain */
+ if (w->widgetcap & COP_AWCAP_OUTAMP
+ && COP_AMPCAP_NUMSTEPS(w->outamp_cap)) {
+ MIXER_REG_PROLOG;
+ DPRINTF(("%s: output gain %s\n", __func__, w->name));
+ snprintf(d->label.name, sizeof(d->label.name),
+ "%s", w->name);
+ d->type = AUDIO_MIXER_VALUE;
+ if (w->type == COP_AWTYPE_AUDIO_MIXER)
+ d->mixer_class = AZ_CLASS_OUTPUT;
+ else if (w->type == COP_AWTYPE_AUDIO_SELECTOR)
+ d->mixer_class = AZ_CLASS_OUTPUT;
+ else if (w->type == COP_AWTYPE_PIN_COMPLEX)
+ d->mixer_class = AZ_CLASS_OUTPUT;
+ else
+ d->mixer_class = AZ_CLASS_INPUT;
+ m->target = MI_TARGET_OUTAMP;
+ d->un.v.num_channels = WIDGET_CHANNELS(w);
+#ifdef MAX_VOLUME_255
+ d->un.v.units.name[0] = 0;
+ d->un.v.delta = AUDIO_MAX_GAIN /
+ COP_AMPCAP_NUMSTEPS(w->outamp_cap);
+#else
+ snprintf(d->un.v.units.name, sizeof(d->un.v.units.name),
+ "0.25x%ddB", COP_AMPCAP_STEPSIZE(w->outamp_cap)+1);
+ d->un.v.delta = 1;
+#endif
+ this->nmixers++;
+ }
+
+ /* input mute */
+ if (w->widgetcap & COP_AWCAP_INAMP &&
+ w->inamp_cap & COP_AMPCAP_MUTE) {
+ DPRINTF(("%s: input mute %s\n", __func__, w->name));
+ if (w->type != COP_AWTYPE_AUDIO_SELECTOR &&
+ w->type != COP_AWTYPE_AUDIO_MIXER) {
+ MIXER_REG_PROLOG;
+ snprintf(d->label.name, sizeof(d->label.name),
+ "%s.mute", w->name);
+ d->type = AUDIO_MIXER_ENUM;
+ if (w->type == COP_AWTYPE_PIN_COMPLEX)
+ d->mixer_class = AZ_CLASS_OUTPUT;
+ else if (w->type == COP_AWTYPE_AUDIO_INPUT)
+ d->mixer_class = AZ_CLASS_RECORD;
+ else
+ d->mixer_class = AZ_CLASS_INPUT;
+ m->target = 0;
+ d->un.e.num_mem = 2;
+ d->un.e.member[0].ord = 0;
+ strlcpy(d->un.e.member[0].label.name,
+ AudioNoff, MAX_AUDIO_DEV_LEN);
+ d->un.e.member[1].ord = 1;
+ strlcpy(d->un.e.member[1].label.name,
+ AudioNon, MAX_AUDIO_DEV_LEN);
+ this->nmixers++;
+ } else {
+ for (j = 0; j < w->nconnections; j++) {
+ MIXER_REG_PROLOG;
+ if (!VALID_WIDGET_NID(w->connections[j], this))
+ continue;
+ DPRINTF(("%s: input mute %s.%s\n", __func__,
+ w->name, this->w[w->connections[j]].name));
+ snprintf(d->label.name, sizeof(d->label.name),
+ "%s.%s.mute", w->name,
+ this->w[w->connections[j]].name);
+ d->type = AUDIO_MIXER_ENUM;
+ if (w->type == COP_AWTYPE_PIN_COMPLEX)
+ d->mixer_class = AZ_CLASS_OUTPUT;
+ else if (w->type == COP_AWTYPE_AUDIO_INPUT)
+ d->mixer_class = AZ_CLASS_RECORD;
+ else
+ d->mixer_class = AZ_CLASS_INPUT;
+ m->target = j;
+ d->un.e.num_mem = 2;
+ d->un.e.member[0].ord = 0;
+ strlcpy(d->un.e.member[0].label.name,
+ AudioNoff, MAX_AUDIO_DEV_LEN);
+ d->un.e.member[1].ord = 1;
+ strlcpy(d->un.e.member[1].label.name,
+ AudioNon, MAX_AUDIO_DEV_LEN);
+ this->nmixers++;
+ }
+ }
+ }
+
+ /* input gain */
+ if (w->widgetcap & COP_AWCAP_INAMP
+ && COP_AMPCAP_NUMSTEPS(w->inamp_cap)) {
+ DPRINTF(("%s: input gain %s\n", __func__, w->name));
+ if (w->type != COP_AWTYPE_AUDIO_SELECTOR &&
+ w->type != COP_AWTYPE_AUDIO_MIXER) {
+ MIXER_REG_PROLOG;
+ snprintf(d->label.name, sizeof(d->label.name),
+ "%s", w->name);
+ d->type = AUDIO_MIXER_VALUE;
+ if (w->type == COP_AWTYPE_PIN_COMPLEX)
+ d->mixer_class = AZ_CLASS_OUTPUT;
+ else if (w->type == COP_AWTYPE_AUDIO_INPUT)
+ d->mixer_class = AZ_CLASS_RECORD;
+ else
+ d->mixer_class = AZ_CLASS_INPUT;
+ m->target = 0;
+ d->un.v.num_channels = WIDGET_CHANNELS(w);
+#ifdef MAX_VOLUME_255
+ d->un.v.units.name[0] = 0;
+ d->un.v.delta = AUDIO_MAX_GAIN /
+ COP_AMPCAP_NUMSTEPS(w->inamp_cap);
+#else
+ snprintf(d->un.v.units.name,
+ sizeof(d->un.v.units.name), "0.25x%ddB",
+ COP_AMPCAP_STEPSIZE(w->inamp_cap)+1);
+ d->un.v.delta = 1;
+#endif
+ this->nmixers++;
+ } else {
+ for (j = 0; j < w->nconnections; j++) {
+ MIXER_REG_PROLOG;
+ if (!VALID_WIDGET_NID(w->connections[j], this))
+ continue;
+ DPRINTF(("%s: input gain %s.%s\n", __func__,
+ w->name, this->w[w->connections[j]].name));
+ snprintf(d->label.name, sizeof(d->label.name),
+ "%s.%s", w->name,
+ this->w[w->connections[j]].name);
+ d->type = AUDIO_MIXER_VALUE;
+ if (w->type == COP_AWTYPE_PIN_COMPLEX)
+ d->mixer_class = AZ_CLASS_OUTPUT;
+ else if (w->type == COP_AWTYPE_AUDIO_INPUT)
+ d->mixer_class = AZ_CLASS_RECORD;
+ else
+ d->mixer_class = AZ_CLASS_INPUT;
+ m->target = j;
+ d->un.v.num_channels = WIDGET_CHANNELS(w);
+#ifdef MAX_VOLUME_255
+ d->un.v.units.name[0] = 0;
+ d->un.v.delta = AUDIO_MAX_GAIN /
+ COP_AMPCAP_NUMSTEPS(w->inamp_cap);
+#else
+ snprintf(d->un.v.units.name,
+ sizeof(d->un.v.units.name), "0.25x%ddB",
+ COP_AMPCAP_STEPSIZE(w->inamp_cap)+1);
+ d->un.v.delta = 1;
+#endif
+ this->nmixers++;
+ }
+ }
+ }
+
+ /* pin direction */
+ if (w->type == COP_AWTYPE_PIN_COMPLEX &&
+ w->d.pin.cap & COP_PINCAP_OUTPUT &&
+ w->d.pin.cap & COP_PINCAP_INPUT) {
+ MIXER_REG_PROLOG;
+ DPRINTF(("%s: pin dir %s\n", __func__, w->name));
+ snprintf(d->label.name, sizeof(d->label.name),
+ "%s.dir", w->name);
+ d->type = AUDIO_MIXER_ENUM;
+ d->mixer_class = AZ_CLASS_OUTPUT;
+ m->target = MI_TARGET_PINDIR;
+ d->un.e.num_mem = 2;
+ d->un.e.member[0].ord = 0;
+ strlcpy(d->un.e.member[0].label.name, AudioNinput,
+ MAX_AUDIO_DEV_LEN);
+ d->un.e.member[1].ord = 1;
+ strlcpy(d->un.e.member[1].label.name, AudioNoutput,
+ MAX_AUDIO_DEV_LEN);
+ this->nmixers++;
+ }
+
+ /* pin headphone-boost */
+ if (w->type == COP_AWTYPE_PIN_COMPLEX &&
+ w->d.pin.cap & COP_PINCAP_HEADPHONE) {
+ MIXER_REG_PROLOG;
+ DPRINTF(("%s: hpboost %s\n", __func__, w->name));
+ snprintf(d->label.name, sizeof(d->label.name),
+ "%s.boost", w->name);
+ d->type = AUDIO_MIXER_ENUM;
+ d->mixer_class = AZ_CLASS_OUTPUT;
+ m->target = MI_TARGET_PINBOOST;
+ d->un.e.num_mem = 2;
+ d->un.e.member[0].ord = 0;
+ strlcpy(d->un.e.member[0].label.name, AudioNoff,
+ MAX_AUDIO_DEV_LEN);
+ d->un.e.member[1].ord = 1;
+ strlcpy(d->un.e.member[1].label.name, AudioNon,
+ MAX_AUDIO_DEV_LEN);
+ this->nmixers++;
+ }
+
+ /* volume knob */
+ if (w->type == COP_AWTYPE_VOLUME_KNOB &&
+ w->d.volume.cap & COP_VKCAP_DELTA) {
+ MIXER_REG_PROLOG;
+ DPRINTF(("%s: volume knob %s\n", __func__, w->name));
+ strlcpy(d->label.name, w->name, sizeof(d->label.name));
+ d->type = AUDIO_MIXER_VALUE;
+ d->mixer_class = AZ_CLASS_OUTPUT;
+ m->target = MI_TARGET_VOLUME;
+ d->un.v.num_channels = 1;
+ d->un.v.units.name[0] = 0;
+#ifdef MAX_VOLUME_255
+ d->un.v.delta = AUDIO_MAX_GAIN /
+ COP_VKCAP_NUMSTEPS(w->d.volume.cap);
+#else
+ d->un.v.delta = 1;
+#endif
+ this->nmixers++;
+ }
+ }
+
+ /* if the codec has multiple DAC groups, create "inputs.usingdac" */
+ if (this->ndacgroups > 1) {
+ MIXER_REG_PROLOG;
+ DPRINTF(("%s: create inputs.usingdac\n", __func__));
+ strlcpy(d->label.name, "usingdac", sizeof(d->label.name));
+ d->type = AUDIO_MIXER_ENUM;
+ d->mixer_class = AZ_CLASS_INPUT;
+ m->target = MI_TARGET_DAC;
+ for (i = 0; i < this->ndacgroups && i < 32; i++) {
+ d->un.e.member[i].ord = i;
+ for (j = 0; j < this->dacgroups[i].nconv; j++) {
+ if (j * 2 >= MAX_AUDIO_DEV_LEN)
+ break;
+ snprintf(d->un.e.member[i].label.name + j*2,
+ MAX_AUDIO_DEV_LEN - j*2, "%2.2x",
+ this->dacgroups[i].conv[j]);
+ }
+ }
+ d->un.e.num_mem = i;
+ this->nmixers++;
+ }
+
+ /* if the codec has multiple ADCs, create "record.usingadc" */
+ if (this->nadcs > 1) {
+ MIXER_REG_PROLOG;
+ DPRINTF(("%s: create inputs.usingadc\n", __func__));
+ strlcpy(d->label.name, "usingadc", sizeof(d->label.name));
+ d->type = AUDIO_MIXER_ENUM;
+ d->mixer_class = AZ_CLASS_RECORD;
+ m->target = MI_TARGET_ADC;
+ for (i = 0; i < this->nadcs && i < 32; i++) {
+ d->un.e.member[i].ord = i;
+ strlcpy(d->un.e.member[i].label.name,
+ this->w[this->adcs[i]].name, MAX_AUDIO_DEV_LEN);
+ }
+ d->un.e.num_mem = i;
+ this->nmixers++;
+ }
+
+ azalia_generic_mixer_fix_indexes(this);
+ azalia_generic_mixer_default(this);
+ return 0;
+}
+
+int
+azalia_generic_mixer_ensure_capacity(codec_t *this, size_t newsize)
+{
+ size_t newmax;
+ void *newbuf;
+
+ if (this->maxmixers >= newsize)
+ return 0;
+ newmax = this->maxmixers + 10;
+ if (newmax < newsize)
+ newmax = newsize;
+ newbuf = malloc(sizeof(mixer_item_t) * newmax, M_DEVBUF, M_NOWAIT);
+ if (newbuf == NULL) {
+ printf("%s: out of memory in %s\n", XNAME(this), __func__);
+ return ENOMEM;
+ }
+ bzero(newbuf, sizeof(mixer_item_t) * newmax);
+ bcopy(this->mixers, newbuf, this->maxmixers * sizeof(mixer_item_t));
+ free(this->mixers, M_DEVBUF);
+ this->mixers = newbuf;
+ this->maxmixers = newmax;
+ return 0;
+}
+
+int
+azalia_generic_mixer_fix_indexes(codec_t *this)
+{
+ int i;
+ mixer_devinfo_t *d;
+
+ for (i = 0; i < this->nmixers; i++) {
+ d = &this->mixers[i].devinfo;
+#ifdef DIAGNOSTIC
+ if (d->index != 0 && d->index != i)
+ printf("%s: index mismatch %d %d\n", __func__,
+ d->index, i);
+#endif
+ d->index = i;
+ if (d->prev == 0)
+ d->prev = AUDIO_MIXER_LAST;
+ if (d->next == 0)
+ d->next = AUDIO_MIXER_LAST;
+ }
+ return 0;
+}
+
+int
+azalia_generic_mixer_default(codec_t *this)
+{
+ int i;
+ mixer_item_t *m;
+ /* unmute all */
+ DPRINTF(("%s: unmute\n", __func__));
+ for (i = 0; i < this->nmixers; i++) {
+ mixer_ctrl_t mc;
+
+ m = &this->mixers[i];
+ if (!IS_MI_TARGET_INAMP(m->target) &&
+ m->target != MI_TARGET_OUTAMP)
+ continue;
+ if (m->devinfo.type != AUDIO_MIXER_ENUM)
+ continue;
+ mc.dev = i;
+ mc.type = AUDIO_MIXER_ENUM;
+ mc.un.ord = 0;
+ azalia_generic_mixer_set(this, m->nid, m->target, &mc);
+ }
+
+ /*
+ * for bidirectional pins,
+ * green=front, orange=surround, gray=c/lfe, black=side --> output
+ * blue=line-in, pink=mic-in --> input
+ */
+ DPRINTF(("%s: process bidirectional pins\n", __func__));
+ for (i = 0; i < this->nmixers; i++) {
+ mixer_ctrl_t mc;
+
+ m = &this->mixers[i];
+ if (m->target != MI_TARGET_PINDIR)
+ continue;
+ mc.dev = i;
+ mc.type = AUDIO_MIXER_ENUM;
+ switch (this->w[m->nid].d.pin.color) {
+ case CORB_CD_GREEN:
+ case CORB_CD_ORANGE:
+ case CORB_CD_GRAY:
+ case CORB_CD_BLACK:
+ mc.un.ord = 1;
+ break;
+ default:
+ mc.un.ord = 0;
+ }
+ azalia_generic_mixer_set(this, m->nid, m->target, &mc);
+ }
+
+ /* set unextreme volume */
+ DPRINTF(("%s: set volume\n", __func__));
+ for (i = 0; i < this->nmixers; i++) {
+ mixer_ctrl_t mc;
+
+ m = &this->mixers[i];
+ if (!IS_MI_TARGET_INAMP(m->target) &&
+ m->target != MI_TARGET_OUTAMP &&
+ m->target != MI_TARGET_VOLUME)
+ continue;
+ if (m->devinfo.type != AUDIO_MIXER_VALUE)
+ continue;
+ mc.dev = i;
+ mc.type = AUDIO_MIXER_VALUE;
+ mc.un.value.num_channels = 1;
+ mc.un.value.level[0] = AUDIO_MAX_GAIN / 2;
+ if (m->target != MI_TARGET_VOLUME &&
+ WIDGET_CHANNELS(&this->w[m->nid]) == 2) {
+ mc.un.value.num_channels = 2;
+ mc.un.value.level[1] = AUDIO_MAX_GAIN / 2;
+ }
+ azalia_generic_mixer_set(this, m->nid, m->target, &mc);
+ }
+
+ return 0;
+}
+
+int
+azalia_generic_mixer_delete(codec_t *this)
+{
+ if (this->mixers == NULL)
+ return 0;
+ free(this->mixers, M_DEVBUF);
+ this->mixers = NULL;
+ return 0;
+}
+
+/**
+ * @param mc mc->type must be set by the caller before the call
+ */
+int
+azalia_generic_mixer_get(const codec_t *this, nid_t nid, int target, mixer_ctrl_t *mc)
+{
+ uint32_t result;
+ int err;
+
+ /* inamp mute */
+ if (IS_MI_TARGET_INAMP(target) && mc->type == AUDIO_MIXER_ENUM) {
+ err = this->comresp(this, nid, CORB_GET_AMPLIFIER_GAIN_MUTE,
+ CORB_GAGM_INPUT | CORB_GAGM_LEFT |
+ MI_TARGET_INAMP(target), &result);
+ if (err)
+ return err;
+ mc->un.ord = result & CORB_GAGM_MUTE ? 1 : 0;
+ }
+
+ /* inamp gain */
+ else if (IS_MI_TARGET_INAMP(target) && mc->type == AUDIO_MIXER_VALUE) {
+ err = this->comresp(this, nid, CORB_GET_AMPLIFIER_GAIN_MUTE,
+ CORB_GAGM_INPUT | CORB_GAGM_LEFT |
+ MI_TARGET_INAMP(target), &result);
+ if (err)
+ return err;
+ mc->un.value.level[0] = azalia_generic_mixer_from_device_value(this,
+ nid, target, CORB_GAGM_GAIN(result));
+ mc->un.value.num_channels = WIDGET_CHANNELS(&this->w[nid]);
+ if (mc->un.value.num_channels == 2) {
+ err = this->comresp(this, nid,
+ CORB_GET_AMPLIFIER_GAIN_MUTE, CORB_GAGM_INPUT |
+ CORB_GAGM_RIGHT | MI_TARGET_INAMP(target),
+ &result);
+ if (err)
+ return err;
+ mc->un.value.level[1] = azalia_generic_mixer_from_device_value
+ (this, nid, target, CORB_GAGM_GAIN(result));
+ }
+ }
+
+ /* outamp mute */
+ else if (target == MI_TARGET_OUTAMP && mc->type == AUDIO_MIXER_ENUM) {
+ err = this->comresp(this, nid, CORB_GET_AMPLIFIER_GAIN_MUTE,
+ CORB_GAGM_OUTPUT | CORB_GAGM_LEFT | 0, &result);
+ if (err)
+ return err;
+ mc->un.ord = result & CORB_GAGM_MUTE ? 1 : 0;
+ }
+
+ /* outamp gain */
+ else if (target == MI_TARGET_OUTAMP && mc->type == AUDIO_MIXER_VALUE) {
+ err = this->comresp(this, nid, CORB_GET_AMPLIFIER_GAIN_MUTE,
+ CORB_GAGM_OUTPUT | CORB_GAGM_LEFT | 0, &result);
+ if (err)
+ return err;
+ mc->un.value.level[0] = azalia_generic_mixer_from_device_value(this,
+ nid, target, CORB_GAGM_GAIN(result));
+ mc->un.value.num_channels = WIDGET_CHANNELS(&this->w[nid]);
+ if (mc->un.value.num_channels == 2) {
+ err = this->comresp(this, nid,
+ CORB_GET_AMPLIFIER_GAIN_MUTE,
+ CORB_GAGM_OUTPUT | CORB_GAGM_RIGHT | 0, &result);
+ if (err)
+ return err;
+ mc->un.value.level[1] = azalia_generic_mixer_from_device_value
+ (this, nid, target, CORB_GAGM_GAIN(result));
+ }
+ }
+
+ /* selection */
+ else if (target == MI_TARGET_CONNLIST) {
+ err = this->comresp(this, nid,
+ CORB_GET_CONNECTION_SELECT_CONTROL, 0, &result);
+ if (err)
+ return err;
+ result = CORB_CSC_INDEX(result);
+ if (!VALID_WIDGET_NID(this->w[nid].connections[result], this))
+ mc->un.ord = -1;
+ else
+ mc->un.ord = result;
+ }
+
+ /* pin I/O */
+ else if (target == MI_TARGET_PINDIR) {
+ err = this->comresp(this, nid,
+ CORB_GET_PIN_WIDGET_CONTROL, 0, &result);
+ if (err)
+ return err;
+ mc->un.ord = result & CORB_PWC_OUTPUT ? 1 : 0;
+ }
+
+ /* pin headphone-boost */
+ else if (target == MI_TARGET_PINBOOST) {
+ err = this->comresp(this, nid,
+ CORB_GET_PIN_WIDGET_CONTROL, 0, &result);
+ if (err)
+ return err;
+ mc->un.ord = result & CORB_PWC_HEADPHONE ? 1 : 0;
+ }
+
+ /* DAC group selection */
+ else if (target == MI_TARGET_DAC) {
+ mc->un.ord = this->cur_dac;
+ }
+
+ /* ADC selection */
+ else if (target == MI_TARGET_ADC) {
+ mc->un.ord = this->cur_adc;
+ }
+
+ /* Volume knob */
+ else if (target == MI_TARGET_VOLUME) {
+ err = this->comresp(this, nid, CORB_GET_VOLUME_KNOB,
+ 0, &result);
+ if (err)
+ return err;
+ mc->un.value.level[0] = azalia_generic_mixer_from_device_value(this,
+ nid, target, CORB_VKNOB_VOLUME(result));
+ mc->un.value.num_channels = 1;
+ }
+
+ else {
+ printf("%s: internal error in %s: target=%x\n",
+ XNAME(this), __func__, target);
+ return -1;
+ }
+ return 0;
+}
+
+int
+azalia_generic_mixer_set(codec_t *this, nid_t nid, int target, const mixer_ctrl_t *mc)
+{
+ uint32_t result, value;
+ int err;
+
+ /* inamp mute */
+ if (IS_MI_TARGET_INAMP(target) && mc->type == AUDIO_MIXER_ENUM) {
+ /* We have to set stereo mute separately to keep each gain value. */
+ err = this->comresp(this, nid, CORB_GET_AMPLIFIER_GAIN_MUTE,
+ CORB_GAGM_INPUT | CORB_GAGM_LEFT |
+ MI_TARGET_INAMP(target), &result);
+ if (err)
+ return err;
+ value = CORB_AGM_INPUT | CORB_AGM_LEFT |
+ (target << CORB_AGM_INDEX_SHIFT) |
+ CORB_GAGM_GAIN(result);
+ if (mc->un.ord)
+ value |= CORB_AGM_MUTE;
+ err = this->comresp(this, nid, CORB_SET_AMPLIFIER_GAIN_MUTE,
+ value, &result);
+ if (err)
+ return err;
+ if (WIDGET_CHANNELS(&this->w[nid]) == 2) {
+ err = this->comresp(this, nid,
+ CORB_GET_AMPLIFIER_GAIN_MUTE, CORB_GAGM_INPUT |
+ CORB_GAGM_RIGHT | MI_TARGET_INAMP(target),
+ &result);
+ if (err)
+ return err;
+ value = CORB_AGM_INPUT | CORB_AGM_RIGHT |
+ (target << CORB_AGM_INDEX_SHIFT) |
+ CORB_GAGM_GAIN(result);
+ if (mc->un.ord)
+ value |= CORB_AGM_MUTE;
+ err = this->comresp(this, nid,
+ CORB_SET_AMPLIFIER_GAIN_MUTE, value, &result);
+ if (err)
+ return err;
+ }
+ }
+
+ /* inamp gain */
+ else if (IS_MI_TARGET_INAMP(target) && mc->type == AUDIO_MIXER_VALUE) {
+ if (mc->un.value.num_channels < 1)
+ return EINVAL;
+ if (!azalia_generic_mixer_validate_value(this, nid, target,
+ mc->un.value.level[0]))
+ return EINVAL;
+ err = this->comresp(this, nid, CORB_GET_AMPLIFIER_GAIN_MUTE,
+ CORB_GAGM_INPUT | CORB_GAGM_LEFT |
+ MI_TARGET_INAMP(target), &result);
+ if (err)
+ return err;
+ value = azalia_generic_mixer_to_device_value(this, nid, target,
+ mc->un.value.level[0]);
+ value = CORB_AGM_INPUT | CORB_AGM_LEFT |
+ (target << CORB_AGM_INDEX_SHIFT) |
+ (result & CORB_GAGM_MUTE ? CORB_AGM_MUTE : 0) |
+ (value & CORB_AGM_GAIN_MASK);
+ err = this->comresp(this, nid, CORB_SET_AMPLIFIER_GAIN_MUTE,
+ value, &result);
+ if (err)
+ return err;
+ if (mc->un.value.num_channels >= 2 &&
+ WIDGET_CHANNELS(&this->w[nid]) == 2) {
+ if (!azalia_generic_mixer_validate_value(this, nid, target,
+ mc->un.value.level[1]))
+ return EINVAL;
+ err = this->comresp(this, nid,
+ CORB_GET_AMPLIFIER_GAIN_MUTE, CORB_GAGM_INPUT |
+ CORB_GAGM_RIGHT | MI_TARGET_INAMP(target),
+ &result);
+ if (err)
+ return err;
+ value = azalia_generic_mixer_to_device_value(this, nid, target,
+ mc->un.value.level[1]);
+ value = CORB_AGM_INPUT | CORB_AGM_RIGHT |
+ (target << CORB_AGM_INDEX_SHIFT) |
+ (result & CORB_GAGM_MUTE ? CORB_AGM_MUTE : 0) |
+ (value & CORB_AGM_GAIN_MASK);
+ err = this->comresp(this, nid,
+ CORB_SET_AMPLIFIER_GAIN_MUTE, value, &result);
+ if (err)
+ return err;
+ }
+ }
+
+ /* outamp mute */
+ else if (target == MI_TARGET_OUTAMP && mc->type == AUDIO_MIXER_ENUM) {
+ err = this->comresp(this, nid, CORB_GET_AMPLIFIER_GAIN_MUTE,
+ CORB_GAGM_OUTPUT | CORB_GAGM_LEFT, &result);
+ if (err)
+ return err;
+ value = CORB_AGM_OUTPUT | CORB_AGM_LEFT | CORB_GAGM_GAIN(result);
+ if (mc->un.ord)
+ value |= CORB_AGM_MUTE;
+ err = this->comresp(this, nid, CORB_SET_AMPLIFIER_GAIN_MUTE,
+ value, &result);
+ if (err)
+ return err;
+ if (WIDGET_CHANNELS(&this->w[nid]) == 2) {
+ err = this->comresp(this, nid,
+ CORB_GET_AMPLIFIER_GAIN_MUTE,
+ CORB_GAGM_OUTPUT | CORB_GAGM_RIGHT, &result);
+ if (err)
+ return err;
+ value = CORB_AGM_OUTPUT | CORB_AGM_RIGHT |
+ CORB_GAGM_GAIN(result);
+ if (mc->un.ord)
+ value |= CORB_AGM_MUTE;
+ err = this->comresp(this, nid,
+ CORB_SET_AMPLIFIER_GAIN_MUTE, value, &result);
+ if (err)
+ return err;
+ }
+ }
+
+ /* outamp gain */
+ else if (target == MI_TARGET_OUTAMP && mc->type == AUDIO_MIXER_VALUE) {
+ if (mc->un.value.num_channels < 1)
+ return EINVAL;
+ if (!azalia_generic_mixer_validate_value(this, nid, target,
+ mc->un.value.level[0]))
+ return EINVAL;
+ err = this->comresp(this, nid, CORB_GET_AMPLIFIER_GAIN_MUTE,
+ CORB_GAGM_OUTPUT | CORB_GAGM_LEFT, &result);
+ if (err)
+ return err;
+ value = azalia_generic_mixer_to_device_value(this, nid, target,
+ mc->un.value.level[0]);
+ value = CORB_AGM_OUTPUT | CORB_AGM_LEFT |
+ (result & CORB_GAGM_MUTE ? CORB_AGM_MUTE : 0) |
+ (value & CORB_AGM_GAIN_MASK);
+ err = this->comresp(this, nid, CORB_SET_AMPLIFIER_GAIN_MUTE,
+ value, &result);
+ if (err)
+ return err;
+ if (mc->un.value.num_channels >= 2 &&
+ WIDGET_CHANNELS(&this->w[nid]) == 2) {
+ if (!azalia_generic_mixer_validate_value(this, nid, target,
+ mc->un.value.level[1]))
+ return EINVAL;
+ err = this->comresp(this, nid,
+ CORB_GET_AMPLIFIER_GAIN_MUTE, CORB_GAGM_OUTPUT |
+ CORB_GAGM_RIGHT, &result);
+ if (err)
+ return err;
+ value = azalia_generic_mixer_to_device_value(this, nid, target,
+ mc->un.value.level[1]);
+ value = CORB_AGM_OUTPUT | CORB_AGM_RIGHT |
+ (result & CORB_GAGM_MUTE ? CORB_AGM_MUTE : 0) |
+ (value & CORB_AGM_GAIN_MASK);
+ err = this->comresp(this, nid,
+ CORB_SET_AMPLIFIER_GAIN_MUTE, value, &result);
+ if (err)
+ return err;
+ }
+ }
+
+ /* selection */
+ else if (target == MI_TARGET_CONNLIST) {
+ if (mc->un.ord < 0 ||
+ mc->un.ord >= this->w[nid].nconnections ||
+ !VALID_WIDGET_NID(this->w[nid].connections[mc->un.ord], this))
+ return EINVAL;
+ err = this->comresp(this, nid,
+ CORB_SET_CONNECTION_SELECT_CONTROL, mc->un.ord, &result);
+ if (err)
+ return err;
+ }
+
+ /* pin I/O */
+ else if (target == MI_TARGET_PINDIR) {
+ if (mc->un.ord >= 2)
+ return EINVAL;
+ err = this->comresp(this, nid,
+ CORB_GET_PIN_WIDGET_CONTROL, 0, &result);
+ if (err)
+ return err;
+ if (mc->un.ord == 0) {
+ result &= ~CORB_PWC_OUTPUT;
+ result |= CORB_PWC_INPUT;
+ } else {
+ result &= ~CORB_PWC_INPUT;
+ result |= CORB_PWC_OUTPUT;
+ }
+ err = this->comresp(this, nid,
+ CORB_SET_PIN_WIDGET_CONTROL, result, &result);
+ if (err)
+ return err;
+ }
+
+ /* pin headphone-boost */
+ else if (target == MI_TARGET_PINBOOST) {
+ if (mc->un.ord >= 2)
+ return EINVAL;
+ err = this->comresp(this, nid,
+ CORB_GET_PIN_WIDGET_CONTROL, 0, &result);
+ if (err)
+ return err;
+ if (mc->un.ord == 0) {
+ result &= ~CORB_PWC_HEADPHONE;
+ } else {
+ result |= CORB_PWC_HEADPHONE;
+ }
+ err = this->comresp(this, nid,
+ CORB_SET_PIN_WIDGET_CONTROL, result, &result);
+ if (err)
+ return err;
+ }
+
+ /* DAC group selection */
+ else if (target == MI_TARGET_DAC) {
+ if (this->running)
+ return EBUSY;
+ if (mc->un.ord >= this->ndacgroups)
+ return EINVAL;
+ this->cur_dac = mc->un.ord;
+ return azalia_codec_construct_format(this);
+ }
+
+ /* ADC selection */
+ else if (target == MI_TARGET_ADC) {
+ if (this->running)
+ return EBUSY;
+ if (mc->un.ord >= this->nadcs)
+ return EINVAL;
+ this->cur_adc = mc->un.ord;
+ /* use this->adcs[this->cur_adc] */
+ return azalia_codec_construct_format(this);
+ }
+
+ /* Volume knob */
+ else if (target == MI_TARGET_VOLUME) {
+ if (mc->un.value.num_channels != 1)
+ return EINVAL;
+ if (!azalia_generic_mixer_validate_value(this, nid,
+ target, mc->un.value.level[0]))
+ return EINVAL;
+ value = azalia_generic_mixer_to_device_value(this, nid, target,
+ mc->un.value.level[0]) | CORB_VKNOB_DIRECT;
+ err = this->comresp(this, nid, CORB_SET_VOLUME_KNOB,
+ value, &result);
+ if (err)
+ return err;
+ }
+
+ else {
+ printf("%s: internal error in %s: target=%x\n",
+ XNAME(this), __func__, target);
+ return -1;
+ }
+ return 0;
+}
+
+u_char
+azalia_generic_mixer_from_device_value(const codec_t *this, nid_t nid, int target,
+ uint32_t dv)
+{
+#ifdef MAX_VOLUME_255
+ uint32_t dmax;
+
+ if (IS_MI_TARGET_INAMP(target))
+ dmax = COP_AMPCAP_NUMSTEPS(this->w[nid].inamp_cap);
+ else if (target == MI_TARGET_OUTAMP)
+ dmax = COP_AMPCAP_NUMSTEPS(this->w[nid].outamp_cap);
+ else if (target == MI_TARGET_VOLUME)
+ dmax = COP_VKCAP_NUMSTEPS(this->w[nid].d.volume.cap);
+ else {
+ printf("unknown target: %d\n", target);
+ dmax = 255;
+ }
+ return dv * AUDIO_MAX_GAIN / dmax;
+#else
+ return dv;
+#endif
+}
+
+uint32_t
+azalia_generic_mixer_to_device_value(const codec_t *this, nid_t nid, int target,
+ u_char uv)
+{
+#ifdef MAX_VOLUME_255
+ uint32_t dmax;
+
+ if (IS_MI_TARGET_INAMP(target))
+ dmax = COP_AMPCAP_NUMSTEPS(this->w[nid].inamp_cap);
+ else if (target == MI_TARGET_OUTAMP)
+ dmax = COP_AMPCAP_NUMSTEPS(this->w[nid].outamp_cap);
+ else if (target == MI_TARGET_VOLUME)
+ dmax = COP_VKCAP_NUMSTEPS(this->w[nid].d.volume.cap);
+ else {
+ printf("unknown target: %d\n", target);
+ dmax = 255;
+ }
+ return uv * dmax / AUDIO_MAX_GAIN;
+#else
+ return uv;
+#endif
+}
+
+boolean_t
+azalia_generic_mixer_validate_value(const codec_t *this, nid_t nid, int target,
+ u_char uv)
+{
+#ifdef MAX_VOLUME_255
+ return TRUE;
+#else
+ uint32_t dmax;
+
+ if (IS_MI_TARGET_INAMP(target))
+ dmax = COP_AMPCAP_NUMSTEPS(this->w[nid].inamp_cap);
+ else if (target == MI_TARGET_OUTAMP)
+ dmax = COP_AMPCAP_NUMSTEPS(this->w[nid].outamp_cap);
+ else if (target == MI_TARGET_VOLUME)
+ dmax = COP_VKCAP_NUMSTEPS(this->w[nid].d.volume.cap);
+ return uv <= dmax;
+#endif
+}
+
+int
+azalia_generic_set_port(codec_t *this, mixer_ctrl_t *mc)
+{
+ const mixer_item_t *m;
+
+ if (mc->dev >= this->nmixers)
+ return ENXIO;
+ m = &this->mixers[mc->dev];
+ if (mc->type != m->devinfo.type)
+ return EINVAL;
+ if (mc->type == AUDIO_MIXER_CLASS)
+ return 0; /* nothing to do */
+ return azalia_generic_mixer_set(this, m->nid, m->target, mc);
+}
+
+int
+azalia_generic_get_port(codec_t *this, mixer_ctrl_t *mc)
+{
+ const mixer_item_t *m;
+
+ if (mc->dev >= this->nmixers)
+ return ENXIO;
+ m = &this->mixers[mc->dev];
+ mc->type = m->devinfo.type;
+ if (mc->type == AUDIO_MIXER_CLASS)
+ return 0; /* nothing to do */
+ return azalia_generic_mixer_get(this, m->nid, m->target, mc);
+}
+
+
+/* ----------------------------------------------------------------
* Realtek ALC260
+ *
+ * Fujitsu LOOX T70M/T
+ * Internal Speaker: 0x10
+ * Front Headphone: 0x14
+ * Front mic: 0x12
* ---------------------------------------------------------------- */
+#define ALC260_FUJITSU_ID 0x132610cf
+static const mixer_item_t alc260_mixer_items[] = {
+ {{AZ_CLASS_INPUT, {AudioCinputs}, AUDIO_MIXER_CLASS, AZ_CLASS_INPUT, 0, 0}, 0},
+ {{AZ_CLASS_OUTPUT, {AudioCoutputs}, AUDIO_MIXER_CLASS, AZ_CLASS_OUTPUT, 0, 0}, 0},
+ {{AZ_CLASS_RECORD, {AudioCrecord}, AUDIO_MIXER_CLASS, AZ_CLASS_RECORD, 0, 0}, 0},
+
+ {{0, {AudioNmaster}, AUDIO_MIXER_VALUE, AZ_CLASS_OUTPUT,
+ 0, 0, .un.v={{""}, 2, 3}}, 0x08, MI_TARGET_OUTAMP}, /* and 0x09, 0x0a(mono) */
+ {{0, {AudioNmaster".mute"}, AUDIO_MIXER_ENUM, AZ_CLASS_OUTPUT,
+ 0, 0, ENUM_OFFON}, 0x0f, MI_TARGET_OUTAMP},
+ {{0, {AudioNheadphone".mute"}, AUDIO_MIXER_ENUM, AZ_CLASS_OUTPUT,
+ 0, 0, ENUM_OFFON}, 0x10, MI_TARGET_OUTAMP},
+ {{0, {AudioNheadphone".boost"}, AUDIO_MIXER_ENUM, AZ_CLASS_OUTPUT,
+ 0, 0, ENUM_OFFON}, 0x10, MI_TARGET_PINBOOST},
+ {{0, {AudioNmono".mute"}, AUDIO_MIXER_ENUM, AZ_CLASS_OUTPUT,
+ 0, 0, ENUM_OFFON}, 0x11, MI_TARGET_OUTAMP},
+ {{0, {"mic1.mute"}, AUDIO_MIXER_ENUM, AZ_CLASS_OUTPUT,
+ 0, 0, ENUM_OFFON}, 0x12, MI_TARGET_OUTAMP},
+ {{0, {"mic1"}, AUDIO_MIXER_ENUM, AZ_CLASS_OUTPUT,
+ 0, 0, ENUM_IO}, 0x12, MI_TARGET_PINDIR},
+ {{0, {"mic2.mute"}, AUDIO_MIXER_ENUM, AZ_CLASS_OUTPUT,
+ 0, 0, ENUM_OFFON}, 0x13, MI_TARGET_OUTAMP},
+ {{0, {"mic2"}, AUDIO_MIXER_ENUM, AZ_CLASS_OUTPUT,
+ 0, 0, ENUM_IO}, 0x13, MI_TARGET_PINDIR},
+ {{0, {"line1.mute"}, AUDIO_MIXER_ENUM, AZ_CLASS_OUTPUT,
+ 0, 0, ENUM_OFFON}, 0x14, MI_TARGET_OUTAMP},
+ {{0, {"line1"}, AUDIO_MIXER_ENUM, AZ_CLASS_OUTPUT,
+ 0, 0, ENUM_IO}, 0x14, MI_TARGET_PINDIR},
+ {{0, {"line2.mute"}, AUDIO_MIXER_ENUM, AZ_CLASS_OUTPUT,
+ 0, 0, ENUM_OFFON}, 0x15, MI_TARGET_OUTAMP},
+ {{0, {"line2"}, AUDIO_MIXER_ENUM, AZ_CLASS_OUTPUT,
+ 0, 0, ENUM_IO}, 0x15, MI_TARGET_PINDIR},
+
+ {{0, {AudioNdac".mute"}, AUDIO_MIXER_ENUM, AZ_CLASS_INPUT,
+ 0, 0, ENUM_OFFON}, 0x08, MI_TARGET_INAMP(0)}, /* and 0x09, 0x0a(mono) */
+ {{0, {"mic1.mute"}, AUDIO_MIXER_ENUM, AZ_CLASS_INPUT,
+ 0, 0, ENUM_OFFON}, 0x07, MI_TARGET_INAMP(0)},
+ {{0, {"mic1"}, AUDIO_MIXER_VALUE, AZ_CLASS_INPUT,
+ 0, 0, .un.v={{""}, 2, 3}}, 0x07, MI_TARGET_INAMP(0)},
+ {{0, {"mic2.mute"}, AUDIO_MIXER_ENUM, AZ_CLASS_INPUT,
+ 0, 0, ENUM_OFFON}, 0x07, MI_TARGET_INAMP(1)},
+ {{0, {"mic2"}, AUDIO_MIXER_VALUE, AZ_CLASS_INPUT,
+ 0, 0, .un.v={{""}, 2, 3}}, 0x07, MI_TARGET_INAMP(1)},
+ {{0, {"line1.mute"}, AUDIO_MIXER_ENUM, AZ_CLASS_INPUT,
+ 0, 0, ENUM_OFFON}, 0x07, MI_TARGET_INAMP(2)},
+ {{0, {"line1"}, AUDIO_MIXER_VALUE, AZ_CLASS_INPUT,
+ 0, 0, .un.v={{""}, 2, 3}}, 0x07, MI_TARGET_INAMP(2)},
+ {{0, {"line2.mute"}, AUDIO_MIXER_ENUM, AZ_CLASS_INPUT,
+ 0, 0, ENUM_OFFON}, 0x07, MI_TARGET_INAMP(3)},
+ {{0, {"line2"}, AUDIO_MIXER_VALUE, AZ_CLASS_INPUT,
+ 0, 0, .un.v={{""}, 2, 3}}, 0x07, MI_TARGET_INAMP(3)},
+ {{0, {AudioNcd".mute"}, AUDIO_MIXER_ENUM, AZ_CLASS_INPUT,
+ 0, 0, ENUM_OFFON}, 0x07, MI_TARGET_INAMP(4)},
+ {{0, {AudioNcd}, AUDIO_MIXER_VALUE, AZ_CLASS_INPUT,
+ 0, 0, .un.v={{""}, 2, 3}}, 0x07, MI_TARGET_INAMP(4)},
+ {{0, {AudioNspeaker".mute"}, AUDIO_MIXER_ENUM, AZ_CLASS_INPUT,
+ 0, 0, ENUM_OFFON}, 0x07, MI_TARGET_INAMP(5)},
+ {{0, {AudioNspeaker}, AUDIO_MIXER_VALUE, AZ_CLASS_INPUT,
+ 0, 0, .un.v={{""}, 2, 3}}, 0x07, MI_TARGET_INAMP(5)},
+
+ {{0, {"adc04.source"}, AUDIO_MIXER_ENUM, AZ_CLASS_RECORD, 0, 0,
+ .un.e={5, {{{"mic1"}, 0}, {{"mic2"}, 1}, {{"line1"}, 2},
+ {{"line2"}, 3}, {{AudioNcd}, 4}}}},
+ 0x04, MI_TARGET_CONNLIST},
+ {{0, {"adc04.mute"}, AUDIO_MIXER_ENUM, AZ_CLASS_RECORD, 0, 0,
+ ENUM_OFFON}, 0x04, MI_TARGET_INAMP(0)},
+ {{0, {"adc04"}, AUDIO_MIXER_VALUE, AZ_CLASS_RECORD, 0, 0, .un.v={{""}, 2, 7}},
+ 0x04, MI_TARGET_INAMP(0)},
+ {{0, {"adc05.source"}, AUDIO_MIXER_ENUM, AZ_CLASS_RECORD, 0, 0,
+ .un.e={6, {{{"mic1"}, 0}, {{"mic2"}, 1}, {{"line1"}, 2},
+ {{"line2"}, 3}, {{AudioNcd}, 4}, {{AudioNmixerout}, 5}}}},
+ 0x05, MI_TARGET_CONNLIST},
+ {{0, {"adc05.mute"}, AUDIO_MIXER_ENUM, AZ_CLASS_RECORD, 0, 0,
+ ENUM_OFFON}, 0x05, MI_TARGET_INAMP(0)},
+ {{0, {"adc05"}, AUDIO_MIXER_VALUE, AZ_CLASS_RECORD, 0, 0,
+ .un.v={{""}, 2, 7}}, 0x05, MI_TARGET_INAMP(0)},
+
+ {{0, {"usingdac"}, AUDIO_MIXER_ENUM, AZ_CLASS_INPUT, 0, 0,
+ .un.e={2, {{{"analog"}, 0}, {{"digital"}, 1}}}}, 0, MI_TARGET_DAC},
+ {{0, {"usingadc"}, AUDIO_MIXER_ENUM, AZ_CLASS_RECORD, 0, 0,
+ .un.e={3, {{{"adc04"}, 0}, {{"adc05"}, 1}, {{"digital"}, 2}}}}, 0, MI_TARGET_ADC},
+};
+
+static const mixer_item_t alc260_loox_mixer_items[] = {
+ {{AZ_CLASS_INPUT, {AudioCinputs}, AUDIO_MIXER_CLASS, AZ_CLASS_INPUT, 0, 0}, 0},
+ {{AZ_CLASS_OUTPUT, {AudioCoutputs}, AUDIO_MIXER_CLASS, AZ_CLASS_OUTPUT, 0, 0}, 0},
+ {{AZ_CLASS_RECORD, {AudioCrecord}, AUDIO_MIXER_CLASS, AZ_CLASS_RECORD, 0, 0}, 0},
+
+ {{0, {AudioNmaster}, AUDIO_MIXER_VALUE, AZ_CLASS_OUTPUT,
+ 0, 0, .un.v={{""}, 2, 3}}, 0x08, MI_TARGET_OUTAMP}, /* and 0x09, 0x0a(mono) */
+ {{0, {AudioNmaster".mute"}, AUDIO_MIXER_ENUM, AZ_CLASS_OUTPUT,
+ 0, 0, ENUM_OFFON}, 0x10, MI_TARGET_OUTAMP},
+ {{0, {AudioNmaster".boost"}, AUDIO_MIXER_ENUM, AZ_CLASS_OUTPUT,
+ 0, 0, ENUM_OFFON}, 0x10, MI_TARGET_PINBOOST},
+ {{0, {AudioNheadphone".mute"}, AUDIO_MIXER_ENUM, AZ_CLASS_OUTPUT,
+ 0, 0, ENUM_OFFON}, 0x14, MI_TARGET_OUTAMP},
+ {{0, {AudioNheadphone".boost"}, AUDIO_MIXER_ENUM, AZ_CLASS_OUTPUT,
+ 0, 0, ENUM_OFFON}, 0x14, MI_TARGET_PINBOOST},
+
+ {{0, {AudioNdac".mute"}, AUDIO_MIXER_ENUM, AZ_CLASS_INPUT,
+ 0, 0, ENUM_OFFON}, 0x08, MI_TARGET_INAMP(0)}, /* and 0x09, 0x0a(mono) */
+ {{0, {AudioNmicrophone".mute"}, AUDIO_MIXER_ENUM, AZ_CLASS_INPUT,
+ 0, 0, ENUM_OFFON}, 0x07, MI_TARGET_INAMP(0)},
+ {{0, {AudioNmicrophone}, AUDIO_MIXER_VALUE, AZ_CLASS_INPUT,
+ 0, 0, .un.v={{""}, 2, 3}}, 0x07, MI_TARGET_INAMP(0)},
+ {{0, {AudioNcd".mute"}, AUDIO_MIXER_ENUM, AZ_CLASS_INPUT,
+ 0, 0, ENUM_OFFON}, 0x07, MI_TARGET_INAMP(4)},
+ {{0, {AudioNcd}, AUDIO_MIXER_VALUE, AZ_CLASS_INPUT,
+ 0, 0, .un.v={{""}, 2, 3}}, 0x07, MI_TARGET_INAMP(4)},
+ {{0, {AudioNspeaker".mute"}, AUDIO_MIXER_ENUM, AZ_CLASS_INPUT,
+ 0, 0, ENUM_OFFON}, 0x07, MI_TARGET_INAMP(5)},
+ {{0, {AudioNspeaker}, AUDIO_MIXER_VALUE, AZ_CLASS_INPUT,
+ 0, 0, .un.v={{""}, 2, 3}}, 0x07, MI_TARGET_INAMP(5)},
+
+ {{0, {"adc04.source"}, AUDIO_MIXER_ENUM, AZ_CLASS_RECORD, 0, 0,
+ .un.e={2, {{{AudioNmicrophone}, 0}, {{AudioNcd}, 4}}}}, 0x04, MI_TARGET_CONNLIST},
+ {{0, {"adc04.mute"}, AUDIO_MIXER_ENUM, AZ_CLASS_RECORD, 0, 0,
+ ENUM_OFFON}, 0x04, MI_TARGET_INAMP(0)},
+ {{0, {"adc04"}, AUDIO_MIXER_VALUE, AZ_CLASS_RECORD, 0, 0,
+ .un.v={{""}, 2, 7}}, 0x04, MI_TARGET_INAMP(0)},
+ {{0, {"adc05.source"}, AUDIO_MIXER_ENUM, AZ_CLASS_RECORD, 0, 0,
+ .un.e={3, {{{AudioNmicrophone}, 0}, {{AudioNcd}, 4}, {{AudioNmixerout}, 5}}}},
+ 0x05, MI_TARGET_CONNLIST},
+ {{0, {"adc05.mute"}, AUDIO_MIXER_ENUM, AZ_CLASS_RECORD, 0, 0,
+ ENUM_OFFON}, 0x05, MI_TARGET_INAMP(0)},
+ {{0, {"adc05"}, AUDIO_MIXER_VALUE, AZ_CLASS_RECORD, 0, 0,
+ .un.v={{""}, 2, 7}}, 0x05, MI_TARGET_INAMP(0)},
+
+ {{0, {"usingdac"}, AUDIO_MIXER_ENUM, AZ_CLASS_INPUT, 0, 0,
+ .un.e={2, {{{"analog"}, 0}, {{"digital"}, 1}}}}, 0, MI_TARGET_DAC},
+ {{0, {"usingadc"}, AUDIO_MIXER_ENUM, AZ_CLASS_RECORD, 0, 0,
+ .un.e={3, {{{"adc04"}, 0}, {{"adc05"}, 1}, {{"digital"}, 2}}}}, 0, MI_TARGET_ADC},
+};
+
+int
+azalia_alc260_mixer_init(codec_t *this)
+{
+ const mixer_item_t *mi;
+ mixer_ctrl_t mc;
+
+ switch (this->subid) {
+ case ALC260_FUJITSU_ID:
+ this->nmixers = sizeof(alc260_loox_mixer_items) / sizeof(mixer_item_t);
+ mi = alc260_loox_mixer_items;
+ break;
+ default:
+ this->nmixers = sizeof(alc260_mixer_items) / sizeof(mixer_item_t);
+ mi = alc260_mixer_items;
+ }
+ this->mixers = malloc(sizeof(mixer_item_t) * this->nmixers,
+ M_DEVBUF, M_NOWAIT);
+ if (this->mixers == NULL) {
+ printf("%s: out of memory in %s\n", XNAME(this), __func__);
+ return ENOMEM;
+ }
+ bzero(this->mixers, sizeof(mixer_item_t) * this->nmixers);
+ memcpy(this->mixers, mi, sizeof(mixer_item_t) * this->nmixers);
+ azalia_generic_mixer_fix_indexes(this);
+ azalia_generic_mixer_default(this);
+
+ mc.dev = -1; /* no need for generic_mixer_set() */
+ mc.type = AUDIO_MIXER_ENUM;
+ mc.un.ord = 1; /* pindir: output */
+ azalia_generic_mixer_set(this, 0x0f, MI_TARGET_PINDIR, &mc); /* lineout */
+ azalia_generic_mixer_set(this, 0x10, MI_TARGET_PINDIR, &mc); /* headphones */
+ mc.un.ord = 0; /* pindir: input */
+ azalia_generic_mixer_set(this, 0x12, MI_TARGET_PINDIR, &mc); /* mic1 */
+ azalia_generic_mixer_set(this, 0x13, MI_TARGET_PINDIR, &mc); /* mic2 */
+ azalia_generic_mixer_set(this, 0x14, MI_TARGET_PINDIR, &mc); /* line1 */
+ azalia_generic_mixer_set(this, 0x15, MI_TARGET_PINDIR, &mc); /* line2 */
+ mc.un.ord = 0; /* mute: off */
+ azalia_generic_mixer_set(this, 0x08, MI_TARGET_INAMP(0), &mc);
+ azalia_generic_mixer_set(this, 0x08, MI_TARGET_INAMP(1), &mc);
+ azalia_generic_mixer_set(this, 0x09, MI_TARGET_INAMP(0), &mc);
+ azalia_generic_mixer_set(this, 0x09, MI_TARGET_INAMP(1), &mc);
+ azalia_generic_mixer_set(this, 0x0a, MI_TARGET_INAMP(0), &mc);
+ azalia_generic_mixer_set(this, 0x0a, MI_TARGET_INAMP(1), &mc);
+ if (this->subid == ALC260_FUJITSU_ID) {
+ mc.un.ord = 1; /* pindir: output */
+ azalia_generic_mixer_set(this, 0x14, MI_TARGET_PINDIR, &mc); /* line1 */
+ mc.un.ord = 4; /* connlist: cd */
+ azalia_generic_mixer_set(this, 0x05, MI_TARGET_CONNLIST, &mc);
+ }
+ return 0;
+}
+
int
-alc260_init_dacgroup(codec_t *this)
+azalia_alc260_init_dacgroup(codec_t *this)
{
static const convgroup_t dacs[2] = {
{1, {0x02}}, /* analog 2ch */
@@ -290,51 +1519,51 @@ alc260_init_dacgroup(codec_t *this)
}
int
-alc260_init_widget(const codec_t *this, widget_t *w, nid_t nid)
+azalia_alc260_set_port(codec_t *this, mixer_ctrl_t *mc)
{
- switch (nid) {
- case 0x0b: /* selector for 0x12 */
- strlcpy(w->name, AudioNmicrophone "1", sizeof(w->name));
- break;
- case 0x0c: /* selector for 0x13 */
- strlcpy(w->name, AudioNmicrophone "2", sizeof(w->name));
- break;
- case 0x0d: /* selector for 0x14 */
- strlcpy(w->name, AudioNline "1", sizeof(w->name));
- break;
- case 0x0e: /* selector for 0x15 */
- strlcpy(w->name, AudioNline "2", sizeof(w->name));
- break;
- case 0x0f:
- strlcpy(w->name, AudioNline, sizeof(w->name));
- break;
- case 0x10:
- /* AudioNheadphone is too long */
- strlcpy(w->name, "hp", sizeof(w->name));
- break;
- case 0x11:
- strlcpy(w->name, AudioNmono, sizeof(w->name));
- break;
- case 0x12:
- strlcpy(w->name, AudioNmicrophone "1", sizeof(w->name));
- break;
- case 0x13:
- strlcpy(w->name, AudioNmicrophone "2", sizeof(w->name));
- break;
- case 0x14:
- strlcpy(w->name, AudioNline "1", sizeof(w->name));
- break;
- case 0x15:
- strlcpy(w->name, AudioNline "2", sizeof(w->name));
- break;
- case 0x16:
- strlcpy(w->name, AudioNcd, sizeof(w->name));
- break;
- case 0x17:
- strlcpy(w->name, AudioNspeaker, sizeof(w->name));
- break;
+ const mixer_item_t *m;
+ mixer_ctrl_t mc2;
+ int err;
+
+ if (mc->dev >= this->nmixers)
+ return ENXIO;
+ m = &this->mixers[mc->dev];
+ if (mc->type != m->devinfo.type)
+ return EINVAL;
+ if (mc->type == AUDIO_MIXER_CLASS)
+ return 0;
+ if (m->nid == 0x08 && m->target == MI_TARGET_OUTAMP) {
+ DPRINTF(("%s: hook for outputs.master\n", __func__));
+ err = azalia_generic_mixer_set(this, m->nid, m->target, mc);
+ if (!err) {
+ azalia_generic_mixer_set(this, 0x09, m->target, mc);
+ mc2 = *mc;
+ mc2.un.value.num_channels = 1;
+ mc2.un.value.level[0] = (mc2.un.value.level[0]
+ + mc2.un.value.level[1]) / 2;
+ azalia_generic_mixer_set(this, 0x0a, m->target, &mc2);
+ }
+ return err;
+ } else if (m->nid == 0x08 && m->target == MI_TARGET_INAMP(0)) {
+ DPRINTF(("%s: hook for inputs.dac.mute\n", __func__));
+ err = azalia_generic_mixer_set(this, m->nid, m->target, mc);
+ if (!err) {
+ azalia_generic_mixer_set(this, 0x09, m->target, mc);
+ azalia_generic_mixer_set(this, 0x0a, m->target, mc);
+ }
+ return err;
+ } else if (m->nid == 0x04 &&
+ m->target == MI_TARGET_CONNLIST &&
+ m->devinfo.un.e.num_mem == 2) {
+ if (1 <= mc->un.ord && mc->un.ord <= 3)
+ return EINVAL;
+ } else if (m->nid == 0x05 &&
+ m->target == MI_TARGET_CONNLIST &&
+ m->devinfo.un.e.num_mem == 3) {
+ if (1 <= mc->un.ord && mc->un.ord <= 3)
+ return EINVAL;
}
- return 0;
+ return azalia_generic_mixer_set(this, m->nid, m->target, mc);
}
/* ----------------------------------------------------------------
@@ -342,7 +1571,7 @@ alc260_init_widget(const codec_t *this, widget_t *w, nid_t nid)
* ---------------------------------------------------------------- */
int
-alc880_init_dacgroup(codec_t *this)
+azalia_alc880_init_dacgroup(codec_t *this)
{
static const convgroup_t dacs[2] = {
{4, {0x02, 0x04, 0x03, 0x05}}, /* analog 8ch */
@@ -364,7 +1593,7 @@ alc880_init_dacgroup(codec_t *this)
* ---------------------------------------------------------------- */
int
-alc882_init_dacgroup(codec_t *this)
+azalia_alc882_init_dacgroup(codec_t *this)
{
static const convgroup_t dacs[3] = {
{4, {0x02, 0x04, 0x03, 0x05}}, /* analog 8ch */
@@ -385,7 +1614,7 @@ alc882_init_dacgroup(codec_t *this)
}
int
-alc882_init_widget(const codec_t *this, widget_t *w, nid_t nid)
+azalia_alc882_init_widget(const codec_t *this, widget_t *w, nid_t nid)
{
switch (nid) {
case 0x14:
@@ -429,7 +1658,7 @@ alc882_init_widget(const codec_t *this, widget_t *w, nid_t nid)
#if 0
int
-ad1981hd_init_widget(const codec_t *this, widget_t *w, nid_t nid)
+azalia_ad1981hd_init_widget(const codec_t *this, widget_t *w, nid_t nid)
{
switch (nid) {
case 0x05:
@@ -472,7 +1701,7 @@ ad1981hd_init_widget(const codec_t *this, widget_t *w, nid_t nid)
* ---------------------------------------------------------------- */
int
-stac9221_init_dacgroup(codec_t *this)
+azalia_stac9221_init_dacgroup(codec_t *this)
{
static const convgroup_t dacs[3] = {
{4, {0x02, 0x03, 0x05, 0x04}}, /* analog 8ch */