/* * Purpose: Codec handling for Intel High Definition Audio (HDA/Azalia). */ /* * * This file is part of Open Sound System. * * Copyright (C) 4Front Technologies 1996-2008. * * This this source file is released under GPL v2 license (no other versions). * See the COPYING file included in the main directory of this source * distribution for the license terms and conditions. * */ #include "oss_hdaudio_cfg.h" #include "hdaudio.h" #include "hdaudio_codec.h" #include "hdaudio_codecids.h" extern int hdaudio_snoopy; extern int hdaudio_jacksense; extern int hdaudio_noskip; static codec_t NULL_codec = { 0 }; /* TODO: Temporary workaround - to be removed */ /* Si3055 functions (implemented in hdaudio_si3055.c) */ extern void hdaudio_si3055_endpoint_init(hdaudio_mixer_t *mixer, int cad); extern void hdaudio_si3055_set_rate(hdaudio_mixer_t *mixer, int cad, int rate); extern int hdaudio_si3055_set_offhook(hdaudio_mixer_t *mixer, int cad, int offhook); static int attach_codec (hdaudio_mixer_t * mixer, int cad, char *hw_info, unsigned int pci_subdevice, int group_type); int hdaudio_mixer_get_outendpoints (hdaudio_mixer_t * mixer, hdaudio_endpointinfo_t ** endpoints, int size) { int i; if (size != sizeof (hdaudio_endpointinfo_t)) { cmn_err (CE_WARN, "Bad endpoint size\n"); return OSS_EIO; } *endpoints = (hdaudio_endpointinfo_t *) & mixer->outendpoints; for (i = 0; i < mixer->num_outendpoints; i++) { hdaudio_endpointinfo_t *ep = *endpoints + i; ep->skip = mixer->codecs[ep->cad]->widgets[ep->base_wid].skip; if (hdaudio_snoopy) { char *s = ep->name + strlen(ep->name); sprintf(s, ":%d:%d", ep->cad, ep->base_wid); } } return mixer->num_outendpoints; } int hdaudio_mixer_get_inendpoints (hdaudio_mixer_t * mixer, hdaudio_endpointinfo_t ** endpoints, int size) { int i; if (size != sizeof (hdaudio_endpointinfo_t)) { cmn_err (CE_WARN, "Bad endpoint size\n"); return OSS_EIO; } *endpoints = (hdaudio_endpointinfo_t *) & mixer->inendpoints; for (i = 0; i < mixer->num_inendpoints; i++) { hdaudio_endpointinfo_t *ep = *endpoints + i; ep->skip = mixer->codecs[ep->cad]->widgets[ep->base_wid].skip; if (hdaudio_snoopy) { char *s = ep->name + strlen(ep->name); sprintf(s, ":%d:%d", ep->cad, ep->base_wid); } } return mixer->num_inendpoints; } /*ARGSUSED*/ static int hda_mixer_ioctl (int dev, int audiodev, unsigned int cmd, ioctl_arg arg) { if (cmd == SOUND_MIXER_READ_DEVMASK || cmd == SOUND_MIXER_READ_RECMASK || cmd == SOUND_MIXER_READ_RECSRC || cmd == SOUND_MIXER_READ_STEREODEVS) return *arg = 0; return OSS_EINVAL; } static mixer_driver_t hda_mixer_driver = { hda_mixer_ioctl }; static void propagate_names (hdaudio_mixer_t * mixer) { int c, w; int i; /* * Check if the same name can be used for all the widgets on an unique path. */ for (i = 0; i < 20; i++) for (c = 0; c < mixer->ncodecs; c++) if (mixer->codecs[c] != &NULL_codec) { for (w = 1; w < mixer->codecs[c]->nwidgets; w++) { widget_t *widget = &mixer->codecs[c]->widgets[w]; widget_t *src_widget = &mixer->codecs[c]->widgets[widget->connections[0]]; if (widget->nconn != 1) continue; #if 0 if (src_widget->wid_type == NT_PIN || src_widget->wid_type == NT_DAC) continue; if (src_widget->wid_type != NT_MIXER && src_widget->wid_type != NT_VENDOR) /* Mixer */ continue; #endif strcpy (widget->name, src_widget->name); /* * Copy widget's RGB color to all widgets in the path */ if (widget->rgbcolor == 0) widget->rgbcolor = src_widget->rgbcolor; } } #if 0 // Debugging code for (c = 0; c < mixer->ncodecs; c++) if (mixer->codecs[c] != &NULL_codec) for (w = 1; w < mixer->codecs[c]->nwidgets; w++) { widget_t *widget = &mixer->codecs[c]->widgets[w]; cmn_err(CE_CONT, "w= %02x rgb=%06x: %s (%s)\n", w, widget->rgbcolor, widget->name, widget->color); } #endif } static void check_names (hdaudio_mixer_t * mixer) { int c, c2, w, w2; int n, start; /* * Make sure all widgets have unique names. */ for (c = 0; c < mixer->ncodecs; c++) if (mixer->codecs[c] != &NULL_codec) { for (w = 1; w < mixer->codecs[c]->nwidgets; w++) { char tmp[16]; n = 0; if (mixer->codecs[c]->widgets[w].skip) /* Not available */ continue; strcpy (tmp, mixer->codecs[c]->widgets[w].name); start = w + 1; for (c2 = c; c2 < mixer->ncodecs; c2++) { for (w2 = start; w2 < mixer->codecs[c2]->nwidgets; w2++) { if (mixer->codecs[c2]->widgets[w2].skip) /* Not available */ continue; if (strcmp (tmp, mixer->codecs[c2]->widgets[w2].name) == 0) n++; } start = 1; } if (n > 0) /* Duplicates found */ { n = 0; start = w; for (c2 = c; c2 < mixer->ncodecs; c2++) { for (w2 = start; w2 < mixer->codecs[c2]->nwidgets; w2++) { if (mixer->codecs[c2]->widgets[w2].skip) /* Not available */ continue; if (strcmp (tmp, mixer->codecs[c2]->widgets[w2].name) == 0) { n++; sprintf (mixer->codecs[c2]->widgets[w2].name, "%s%d", tmp, n); } } start = 1; } } } } } int hdaudio_amp_maxval (unsigned int ampcaps) { int step, range, maxval; range = ((ampcaps >> AMPCAP_NUMSTEPS_SHIFT) & AMPCAP_NUMSTEPS_MASK) + 1; step = ((ampcaps >> AMPCAP_STEPSIZE_SHIFT) & AMPCAP_STEPSIZE_MASK) + 1; maxval = range * step * 25; /* Now in 0.01 dB steps */ maxval = (maxval / 10) - 1; /* Truncate to centibel (0.1 dB) scaling */ return maxval; } static int scaleout_vol (int v, unsigned int ampcaps) { int step, range, maxval; range = ((ampcaps >> AMPCAP_NUMSTEPS_SHIFT) & AMPCAP_NUMSTEPS_MASK) + 1; step = ((ampcaps >> AMPCAP_STEPSIZE_SHIFT) & AMPCAP_STEPSIZE_MASK) + 1; maxval = range * step * 25; /* Now in 0.01 dB steps */ maxval = (maxval / 10) - 1; /* Truncate to centibel (0.1 dB) scaling */ if (v > maxval) v = maxval; v *= 10; /* centibels -> millibels */ v = (v + (25 * step) / 2) / (25 * step); if (v > 0x7f || v >= range) { v = range - 1; } if (v < 0) v = 0; return v; } static int scalein_vol (int v, unsigned int ampcaps) { int step, range, maxval; range = ((ampcaps >> AMPCAP_NUMSTEPS_SHIFT) & AMPCAP_NUMSTEPS_MASK) + 1; step = ((ampcaps >> AMPCAP_STEPSIZE_SHIFT) & AMPCAP_STEPSIZE_MASK) + 1; maxval = range * step * 25; /* Now in 0.01 dB steps */ maxval = (maxval / 10) - 1; /* Truncate to centibel (0.1 dB) scaling */ v *= step * 25; /* Convert to millibels */ v = v / 10 - 1; /* millibels -> centibels */ if (v > maxval) { v = maxval; } if (v < 0) v = 0; return v; } static int handle_insrcselect (hdaudio_mixer_t * mixer, widget_t * widget, int value) { /* * Emulated (recording) source selection based on input amp mute controls. * * Mute all inputs other than the selected one. */ int i; widget->current_selector = value; for (i = 0; i < widget->nconn; i++) { int v = (i == value) ? 0 : 0x80; corb_write (mixer, widget->cad, widget->wid, 0, SET_GAIN (0, 1, 1, 1, i), v); } return value; } int hdaudio_set_control (int dev, int ctrl, unsigned int cmd, int value) { hdaudio_mixer_t *mixer = mixer_devs[dev]->devc; unsigned int cad, wid, linked_wid, typ, ix, left, right, a, b, v; widget_t *widget; ix = ctrl & 0xff; typ = (ctrl >> 8) & 0xff; wid = (ctrl >> 16) & 0xff; cad = (ctrl >> 24) & 0xff; if (cad >= mixer->ncodecs) return OSS_EIO; if (wid >= mixer->codecs[cad]->nwidgets) return OSS_EIO; widget = &mixer->codecs[cad]->widgets[wid]; if (mixer->codecs[cad]->vendor_flags & VF_VAIO_HACK) linked_wid = (wid == 0x02) ? 0x05 : ((wid == 0x05) ? 0x02 : 0); else linked_wid = 0; if (cmd == SNDCTL_MIX_READ) switch (typ) { case CT_INGAINSEL: if (!corb_read (mixer, cad, wid, 0, GET_GAIN (0, 0), ix, &a, &b)) return OSS_EIO; return a & 0x7f; // TODO: Handle mute break; case CT_INMONO: if (!corb_read (mixer, cad, wid, 0, GET_GAIN (0, 0), ix, &a, &b)) return OSS_EIO; if (a & 0x80) left = 0; else left = scalein_vol (a, widget->inamp_caps); return left | (left << 16); break; case CT_INSTEREO: if (!corb_read (mixer, cad, wid, 0, GET_GAIN (0, 1), ix, &a, &b)) return OSS_EIO; if (a & 0x80) left = 0; else left = scalein_vol (a, widget->inamp_caps); if (!corb_read (mixer, cad, wid, 0, GET_GAIN (0, 0), ix, &a, &b)) return OSS_EIO; if (a & 0x80) right = 0; else right = scalein_vol (a, widget->inamp_caps); return left | (right << 16); break; case CT_INMUTE: if (!corb_read (mixer, cad, wid, 0, GET_GAIN (0, 0), ix, &a, &b)) return OSS_EIO; return (a >> 7) & 0x01; break; case CT_INSRC: /* Inverse mute */ if (!corb_read (mixer, cad, wid, 0, GET_GAIN (0, 0), ix, &a, &b)) return OSS_EIO; return !((a >> 7) & 0x01); break; case CT_SELECT: return widget->current_selector; break; case CT_INSRCSELECT: /* Emulated selector based on input mute controls */ return widget->current_selector; case CT_OUTGAINSEL: if (!corb_read (mixer, cad, wid, 0, GET_GAIN (1, 1), 0, &a, &b)) return OSS_EIO; return a; // TODO: Handle mute break; case CT_OUTMONO: if (!corb_read (mixer, cad, wid, 0, GET_GAIN (1, 1), 0, &a, &b)) return OSS_EIO; left = a & 0x7f; if (a & 0x80) left = 0; else left = scalein_vol (a, widget->outamp_caps); return left | (left << 16); break; case CT_OUTSTEREO: if (!corb_read (mixer, cad, wid, 0, GET_GAIN (1, 1), 0, &a, &b)) return OSS_EIO; left = a & 0x7f; if (a & 0x80) left = 0; else left = scalein_vol (a, widget->outamp_caps); if (!corb_read (mixer, cad, wid, 0, GET_GAIN (1, 0), 0, &a, &b)) return OSS_EIO; right = a & 0x7f; if (a & 0x80) right = 0; else right = scalein_vol (a, widget->outamp_caps); return left | (right << 16); break; case CT_OUTMUTE: if (!corb_read (mixer, cad, wid, 0, GET_GAIN (1, 0), 0, &a, &b)) return OSS_EIO; return (a >> 7) & 0x01; break; default: return OSS_EINVAL; } if (cmd == SNDCTL_MIX_WRITE) { switch (typ) { case CT_INMONO: v = (value & 0xffff); if (v == 0) v = 0x80; /* Muted */ else v = scaleout_vol (v, widget->inamp_caps); corb_write (mixer, cad, wid, 0, SET_GAIN (0, 1, 1, 1, ix), v); return value; break; case CT_INSTEREO: v = (value & 0xffff); if (v == 0) v = 0x80; /* Muted */ else v = scaleout_vol (v, widget->inamp_caps); corb_write (mixer, cad, wid, 0, SET_GAIN (0, 1, 1, 0, ix), v); v = ((value >> 16) & 0xffff); if (v == 0) v = 0x80; /* Muted */ else v = scaleout_vol (v, widget->inamp_caps); corb_write (mixer, cad, wid, 0, SET_GAIN (0, 1, 0, 1, ix), v); return value; break; case CT_INGAINSEL: v = (value & 0x7f); // TODO: Handle mute corb_write (mixer, cad, wid, 0, SET_GAIN (0, 1, 1, 0, ix), v); corb_write (mixer, cad, wid, 0, SET_GAIN (0, 1, 0, 1, ix), v); return value; break; case CT_INMUTE: v = 0; if (value) v = 0x80; corb_write (mixer, cad, wid, 0, SET_GAIN (0, 1, 1, 1, ix), v); return value; break; case CT_INSRC: /* Inverse mute */ v = 0; if (!value) v = 0x80; corb_write (mixer, cad, wid, 0, SET_GAIN (0, 1, 1, 1, ix), v); return value; break; case CT_SELECT: if (value < 0) value = 0; widget->current_selector = value; if (value < widget->nconn) { /* Output source select */ corb_write (mixer, cad, wid, 0, SET_SELECTOR, value); /* Enable output and HP amp. Set vref=Ground */ corb_write (mixer, cad, wid, 0, SET_PINCTL, 0xc0); } else { /* Input select * Program the correct VRef Values */ if (widget->pin_type == PIN_IN) /* Line-in */ { corb_write (mixer, cad, wid, 0, SET_PINCTL, 0x20); /*Ground*/ } else /* Mic-in */ { corb_write (mixer, cad, wid, 0, SET_PINCTL, 0x24); /*Vref=8 0% */ } } return value; break; case CT_INSRCSELECT: /* Emulated selector based on input mute mask */ if (value < 0) value = 0; if (value >= widget->nconn) value = widget->nconn; return handle_insrcselect (mixer, widget, value); break; case CT_OUTGAINSEL: // TODO: Handle mute corb_write (mixer, cad, wid, 0, SET_GAIN (1, 0, 1, 0, ix), value); corb_write (mixer, cad, wid, 0, SET_GAIN (1, 0, 0, 1, ix), value); return value; break; case CT_OUTMONO: v = (value & 0xffff); if (v == 0) v = 0x80; /* Muted */ else v = scaleout_vol (v, widget->outamp_caps); corb_write (mixer, cad, wid, 0, SET_GAIN (1, 0, 1, 0, ix), v); if (linked_wid) corb_write (mixer, cad, wid, 0, SET_GAIN (1, 0, 1, 0, ix), v); return value; break; case CT_OUTSTEREO: v = (value & 0xffff); if (v == 0) v = 0x80; /* Muted */ else v = scaleout_vol (v, widget->outamp_caps); corb_write (mixer, cad, wid, 0, SET_GAIN (1, 0, 1, 0, ix), v); if (linked_wid) corb_write (mixer, cad, linked_wid, 0, SET_GAIN (1, 0, 1, 0, ix), v); v = ((value >> 16) & 0xffff); if (v == 0) v = 0x80; /* Muted */ else v = scaleout_vol (v, widget->outamp_caps); corb_write (mixer, cad, wid, 0, SET_GAIN (1, 0, 0, 1, ix), v); if (linked_wid) corb_write (mixer, cad, linked_wid, 0, SET_GAIN (1, 0, 0, 1, ix), v); return value; break; case CT_OUTMUTE: v = 0; if (value) v = 0x80; corb_write (mixer, cad, wid, 0, SET_GAIN (1, 0, 1, 1, ix), v); if (linked_wid) corb_write (mixer, cad, linked_wid, 0, SET_GAIN (1, 0, 1, 1, ix), v); return value; break; default: return OSS_EINVAL; } } return OSS_EINVAL; } static int perform_pin_sense (hdaudio_mixer_t * mixer) { int cad, wid; unsigned int a, b; int plugged_in; codec_t *codec; for (cad = 0; cad < mixer->ncodecs; cad++) if (mixer->codecs[cad] != &NULL_codec) { codec = mixer->codecs[cad]; for (wid = 1; wid < mixer->codecs[cad]->nwidgets; wid++) if (mixer->codecs[cad]->widgets[wid].wid_type == NT_PIN) /* Pin complex */ { widget_t *widget = &mixer->codecs[cad]->widgets[wid]; widget->impsense = -1; widget->sensed_pin = widget->pin_type; plugged_in = 1; /* Load the sense information */ if ((widget->pincaps & PINCAP_JACKSENSE_CAPABLE) || (widget->pincaps & PINCAP_IMPSENSE_CAPABLE)) { int tmout = 0; if (!corb_read (mixer, cad, wid, 0, GET_PIN_SENSE, 0, &a, &b)) a = 0x7fffffff; /* No jack present */ if (a & (1UL << 31)) /* Jack present */ if (widget->pincaps & PINCAP_TRIGGERR_RQRD) /* Trigger required */ { corb_write (mixer, cad, wid, 0, TRIGGER_PIN_SENSE, 0); do { oss_udelay (10); if (!corb_read (mixer, cad, wid, 0, GET_PIN_SENSE, 0, &a, &b)) break; } while (tmout++ < 10000 && ((a & 0x7fffffff) == 0x7fffffff)); } if (!corb_read (mixer, cad, wid, 0, GET_PIN_SENSE, 0, &a, &b)) continue; } else continue; /* Precence detect */ if (widget->pincaps & PINCAP_JACKSENSE_CAPABLE) { #if 0 if (a & (1UL << 31)) cmn_err (CE_WARN, "%s jack is plugged in\n", widget->name); else cmn_err (CE_WARN, "%s NOT plugged in\n", widget->name); #endif if (!(a & (1UL << 31))) plugged_in = 0; } widget->plugged_in = plugged_in; if (plugged_in) codec->num_jacks_plugged++; /* Impedance sensing */ widget->impsense = a & 0x7fffffff; if (plugged_in && (widget->pincaps & PINCAP_IMPSENSE_CAPABLE)) if (hdaudio_jacksense > 0) if (widget->impsense != 0x7fffffff) /* Sense operation finished */ { /* cmn_err (CE_WARN, "%s sense %08x (%d)\n", widget->name, a, a & 0x7fffffff); */ if (widget->impsense >= 1000000) /* High impedance (line-in) pin */ { /* cmn_err(CE_CONT, " --> Line level input\n"); */ widget->pin_type = widget->sensed_pin = PIN_IN; } else if (widget->impsense <= 10000) /* Low impedance (speaker/hp out) */ { /* cmn_err(CE_CONT, " --> Output pin\n"); */ widget->pin_type = widget->sensed_pin = PIN_OUT; } else /* Something between low and high (mic?) */ { /* cmn_err(CE_CONT, " --> Mic level input\n"); */ widget->pin_type = widget->sensed_pin = PIN_MIC; } } } } /* * Set all pins to correct mode */ for (cad = 0; cad < mixer->ncodecs; cad++) if (mixer->codecs[cad] != &NULL_codec) for (wid = 1; wid < mixer->codecs[cad]->nwidgets; wid++) { if (mixer->codecs[cad]->widgets[wid].wid_type == NT_PIN) /* Pin complex */ { widget_t *widget = &mixer->codecs[cad]->widgets[wid]; if (widget->pin_type == PIN_IN || widget->pin_type == PIN_UNKNOWN) { /* Input PIN */ corb_write (mixer, cad, wid, 0, SET_PINCTL, 0x20); } if (widget->pin_type == PIN_MIC) { /* Input PIN (mic) */ /* TODO: Handle mic amp */ corb_write (mixer, cad, wid, 0, SET_PINCTL, 0x24); } else { /* Output PIN */ corb_write (mixer, cad, wid, 0, SET_PINCTL, 0xc0); } if (widget->pincaps & PINCAP_EAPD) { unsigned int eapd, dummy; DDB (cmn_err (CE_CONT, "Pin widget %d is EAPD capable.\n", widget->wid)); if (corb_read (mixer, cad, wid, 0, GET_EAPD, 0, &eapd, &dummy)) { eapd |= 0x02; /* EAPD enable */ corb_write (mixer, cad, wid, 0, SET_EAPD, eapd); } } } } return 1; } static int hdaudio_mix_init (int dev) { hdaudio_mixer_t *mixer = mixer_devs[dev]->devc; char tmp[32]; int err; int working_codecs=0; int cad; /* * First pass. Count the number of active codecs. */ for (cad = 0; cad < mixer->ncodecs; cad++) if (mixer->codecs[cad] != &NULL_codec) { codec_t *codec = mixer->codecs[cad]; if (codec == &NULL_codec || codec->nwidgets == 0) /* Codec not active */ continue; /* * Enable the new generic mixer driver */ if (codec->mixer_init == NULL) { codec->mixer_init = hdaudio_generic_mixer_init; } if (codec->active && codec->mixer_init != NULL_mixer_init) working_codecs++; } /* * Second pass. Initialize the mixer interfaces for all active codecs. */ for (cad = 0; cad < mixer->ncodecs; cad++) if (mixer->codecs[cad] != &NULL_codec) { codec_t *codec = mixer->codecs[cad]; int group = 0; if (codec == &NULL_codec || codec->nwidgets == 0) /* Codec not active */ continue; if (working_codecs > 1) { sprintf (tmp, "codec%d", cad + 1); if ((group = mixer_ext_create_group (dev, 0, tmp)) < 0) { return group; } } if (codec->active && codec->mixer_init != NULL_mixer_init) { if ((err = codec->mixer_init (dev, mixer, cad, group)) < 0) { if (err != OSS_EAGAIN) return err; /* * We got EAGAIN whic means that we should fall * to the old generic mixer. */ if ((err = hdaudio_generic_mixer_init (dev, mixer, cad, group)) < 0) { return err; } } else continue; } } if (mixer->client_mixer_init != 0) mixer->client_mixer_init (dev); return 0; } static void copy_endpoints(hdaudio_mixer_t * mixer, codec_t *codec, int pass) { int i; /* * Install output endpoints from the codec to the global endpoint table. */ for (i = 0; i < codec->num_outendpoints; i++) { hdaudio_endpointinfo_t *ep = &codec->outendpoints[i]; ep->skip = mixer->codecs[ep->cad]->widgets[ep->base_wid].skip; } for (i=0;inum_outendpoints;i++) { int ix = (codec->multich_map >> (i * 4)) & 0x0f; hdaudio_endpointinfo_t *ep = &codec->outendpoints[ix]; ep->skip = mixer->codecs[ep->cad]->widgets[ep->base_wid].skip; if (ep->skip || ep->already_used) continue; switch (pass) { case 0: /* Pick analog endpoints */ if (ep->is_digital) continue; break; case 1: /* Pick digital endpoints */ if (!ep->is_digital) continue; break; } if (mixer->copied_outendpoints >= HDA_MAX_OUTSTREAMS) { cmn_err (CE_WARN, "Too many output endpoints (%d)\n", mixer->copied_outendpoints); continue; } memcpy(&mixer->outendpoints[mixer->copied_outendpoints++], ep, sizeof(*ep)); ep->already_used=1; } mixer->num_outendpoints = mixer->copied_outendpoints; /* * Install input endpoints from the codec to the global endpoint table. */ for (i = 0; i < codec->num_inendpoints; i++) { hdaudio_endpointinfo_t *ep = &codec->inendpoints[i]; ep->skip = mixer->codecs[ep->cad]->widgets[ep->base_wid].skip; } for (i=0;inum_inendpoints;i++) { hdaudio_endpointinfo_t *ep = &codec->inendpoints[i]; ep->skip = mixer->codecs[ep->cad]->widgets[ep->base_wid].skip; if (ep->skip || ep->already_used) continue; switch (pass) { case 0: /* Pick analog endpoints */ if (ep->is_digital) continue; break; case 1: /* Pick digital endpoints */ if (!ep->is_digital) continue; break; } if (mixer->copied_inendpoints >= HDA_MAX_INSTREAMS) { cmn_err (CE_WARN, "Too many output endpoints (%d)\n", mixer->copied_inendpoints); continue; } memcpy(&mixer->inendpoints[mixer->copied_inendpoints++], ep, sizeof(*ep)); ep->already_used=1; } mixer->num_inendpoints = mixer->copied_inendpoints; } /*ARGSUSED*/ hdaudio_mixer_t * hdaudio_mixer_create (char *name, void *devc, oss_device_t * osdev, hdmixer_write_t writefunc, hdmixer_read_t readfunc, unsigned int codecmask, unsigned int vendor_id, unsigned int subvendor_id) { hdaudio_mixer_t *mixer; int i, func; int ncodecs = 0; char tmp[128]; int mixer_dev; char *hw_info = osdev->hw_info; /* For printing hardware information */ if ((mixer = PMALLOC (osdev, sizeof (*mixer))) == NULL) { cmn_err (CE_WARN, "hdaudio_mixer_create: Out of memory\n"); return NULL; } memset (mixer, 0, sizeof (*mixer)); mixer->devc = devc; mixer->osdev = osdev; mixer->mixer_dev = 0; strncpy (mixer->name, name, sizeof (mixer->name)); mixer->name[sizeof (mixer->name) - 1] = 0; for (i = 0; i < MAX_CODECS; i++) mixer->codecs[i] = &NULL_codec; mixer->read = readfunc; mixer->write = writefunc; mixer->codecmask = codecmask; sprintf (hw_info, "HD Audio controller %s\n" "Vendor ID 0x%08x\n" "Subvendor ID 0x%08x\n", name, vendor_id, subvendor_id); hw_info += strlen (hw_info); if (hdaudio_snoopy) { sprintf(hw_info, "**** Warning: Diagnostic mode enabled (hdaudio_snoopy) ****\n"); hw_info += strlen (hw_info); } /* * Search first all audio function groups for all codecs and then * handle modem function groups. */ for (func=1;func<=2;func++) for (i = 0; i < 16; i++) if (mixer->codecmask & (1 << i)) { if (attach_codec (mixer, i, hw_info, subvendor_id, func) >= 0) ncodecs++; hw_info += strlen (hw_info); } if (ncodecs == 0) { cmn_err (CE_WARN, "No hdaudio codecs were detected\n"); return NULL; } /* * The attach_codec routine copied all analog endpoints to the global endpoint * table. Now pick possible digital endpoints from the active codecs. */ for (i = 0; i < 16; i++) if (mixer->codecmask & (1 << i)) if (mixer->codecs[i]->active) { copy_endpoints(mixer, mixer->codecs[i], 1); /* Copy digital endpoints from codec to mixer */ } if (!mixer->remap_avail) check_names (mixer); propagate_names (mixer); perform_pin_sense (mixer); if (mixer->chip_name == NULL) mixer->chip_name = "None"; DDB (cmn_err (CE_CONT, "Mixer: %s %s\n", mixer->name, mixer->chip_name)); //sprintf (tmp, "%s %s", mixer->name, mixer->chip_name); sprintf (tmp, "High Definition Audio %s", mixer->chip_name); if ((mixer_dev = oss_install_mixer (OSS_MIXER_DRIVER_VERSION, osdev, osdev, tmp, &hda_mixer_driver, sizeof (mixer_driver_t), mixer)) < 0) { return NULL; } mixer_devs[mixer_dev]->hw_devc = devc; mixer_devs[mixer_dev]->priority = 10; /* Known motherboard device */ mixer->mixer_dev = mixer_dev; mixer_ext_set_init_fn (mixer->mixer_dev, hdaudio_mix_init, mixer->ncontrols * 4); touch_mixer (mixer->mixer_dev); return mixer; } /*ARGSUSED*/ static int find_playvol_widget (hdaudio_mixer_t * mixer, int cad, int wid) { int this_wid = wid; return this_wid; } /*ARGSUSED*/ static int find_recvol_widget (hdaudio_mixer_t * mixer, int cad, int wid) { int this_wid = wid; return this_wid; } /*ARGSUSED*/ static int find_recsrc_widget (hdaudio_mixer_t * mixer, int cad, int wid) { int this_wid = wid; return this_wid; } static int attach_node (hdaudio_mixer_t * mixer, int cad, int wid, int parent) { static const char *widget_types[16] = { "Audio output", "Audio input", "Audio mixer", "Audio selector", "Pin complex", "Power widget", "Volume knob", "Beep generator", "Reserved8", "Reserved9", "ReservedA", "ReservedB", "ReservedC", "ReservedD", "ReservedE", "Vendor defined audio" }; static const char *widget_id[16] = { "pcm", "rec", "mix", "select", "jack", "power", "vol", "beep", "unkn", "unkn", "unkn", "unkn", "unkn", "unkn", "unkn", "vendor" }; static const int bit_sizes[] = { 8, 16, 20, 24, 32 }; static const unsigned int bit_fmts[] = { AFMT_U8, AFMT_S16_LE, AFMT_S32_LE, AFMT_S32_LE, AFMT_S32_LE }; static const int srates[] = { 8000, 11025, 16000, 22050, 32000, 44100, 48000, 88200, 96000, 176400, 192000, 384000 }; unsigned int widget_caps, b, pstate, group_type_in; unsigned int inamp_caps, outamp_caps; int group_type, wid_type; int i; codec_t *codec = mixer->codecs[cad]; widget_t *widget; if (codec == &NULL_codec) return 0; if (!corb_read (mixer, cad, wid, 0, GET_PARAMETER, HDA_GROUP_TYPE, &group_type_in, &b)) group_type_in = 0; if (wid >= MAX_WIDGETS) { cmn_err (CE_WARN, "Too many widgets for codec %d (%d/%d)\n", cad, wid, MAX_WIDGETS); return 0; } mixer->ncontrols++; codec->nwidgets = wid + 1; widget = &codec->widgets[wid]; widget->cad = cad; widget->wid = wid; widget->rgbcolor = 0; group_type = group_type_in & 0xff; widget->group_type = group_type_in; DDB (cmn_err (CE_CONT, "Node %d, parent %d type %d, unsol capa %d\n", wid, parent, group_type, !!(group_type_in & 0x100))); if (!corb_read (mixer, cad, wid, 0, GET_PARAMETER, HDA_WIDGET_CAPS, &widget_caps, &b)) { cmn_err (CE_WARN, "GET_PARAMETER HDA_WIDGET_CAPS failed\n"); return 0; } if (widget_caps & WCAP_AMP_CAP_OVERRIDE) /* Amp param override? */ { if (!corb_read (mixer, widget->cad, widget->wid, 0, GET_PARAMETER, HDA_OUTPUTAMP_CAPS, &outamp_caps, &b)) { cmn_err (CE_WARN, "GET_PARAMETER HDA_OUTPUTAMP_CAPS failed\n"); return OSS_EIO; } widget->outamp_caps = outamp_caps; if (!corb_read (mixer, widget->cad, widget->wid, 0, GET_PARAMETER, HDA_INPUTAMP_CAPS, &inamp_caps, &b)) { cmn_err (CE_WARN, "GET_PARAMETER HDA_INPUTAMP_CAPS failed\n"); return OSS_EIO; } widget->inamp_caps = inamp_caps; } else { widget->outamp_caps = outamp_caps = codec->default_outamp_caps; widget->inamp_caps = inamp_caps = codec->default_inamp_caps; } if (!corb_read (mixer, cad, wid, 0, GET_POWER_STATE, 0, &pstate, &b)) return 0; /* power up each of the widgets if there is a Power Capability (1<<10) */ if (widget_caps & WCAP_POWER_CTL) corb_write (mixer, cad, wid, 0, SET_POWER_STATE, 0); widget->widget_caps = widget_caps; wid_type = (widget_caps >> 20) & 0x0f; DDB (cmn_err (CE_CONT, "\tWidget type %d (%s)(%s)\n", wid_type, widget_types[wid_type], widget_id[wid_type])); DDB (cmn_err (CE_CONT, "\tPower State %d\n", pstate)); if (widget_caps & WCAP_CONN_LIST) { unsigned int clen; /* Handle connection list */ if (!corb_read (mixer, cad, wid, 0, GET_PARAMETER, HDA_CONNLIST_LEN, &clen, &b)) { cmn_err (CE_WARN, "GET_PARAMETER HDA_CONNLIST_LEN failed\n"); return 0; } if (clen & 0x80) { cmn_err (CE_WARN, "Long form connection list not supported\n"); return 0; } if (clen > 0) { if (clen > MAX_CONN) { cmn_err (CE_WARN, "Too many connections\n"); return 0; } DDB (cmn_err (CE_CONT, "\tConn list (%d): ", clen)); for (i = 0; i < clen; i += 4) { int j; unsigned int a; if (!corb_read (mixer, cad, wid, 0, GET_CONNECTION_LIST_ENTRY, i, &a, &b)) { cmn_err (CE_WARN, "GET_CONNECTION_LIST_ENTRY failed\n"); return 0; } for (j = 0; j < 4 && (i + j) < clen; j++) { int v, is_range = 0; if (widget->nconn >= MAX_CONN) { cmn_err (CE_WARN, "Too many connections for widget %d (%d)\n", widget->wid, widget->nconn); break; } v = (a >> (j * 8)) & 0xff; DDB (cmn_err (CE_CONT, "%d ", v)); if (v & 0x80) { is_range = 1; v &= ~0x80; } if (v < 0 || v >= MAX_WIDGETS) { cmn_err (CE_NOTE, "Connection %d for widget %d is out of range (%d) - Skipped\n", j, widget->wid, v); continue; } if (is_range) /* Range: prev...v */ { int x; codec->widgets[v].references[codec->widgets[v]. refcount++] = wid; if (widget->nconn < 1) { cmn_err (CE_CONT, "Bad range connection for widget %d\n", widget->wid); continue; } for (x = widget->connections[widget->nconn - 1] + 1; x <= v; x++) { if (widget->nconn >= MAX_CONN) { cmn_err (CE_WARN, "Too many connections(B) for widget %d (%d)\n", widget->wid, widget->nconn); break; } widget->connections[widget->nconn++] = x; codec->widgets[x].references[codec->widgets[x]. refcount++] = wid; } } else { widget->connections[widget->nconn++] = v; codec->widgets[v].references[codec->widgets[v]. refcount++] = wid; } } } DDB (cmn_err (CE_CONT, "\n")); } } widget->wid_type = wid_type; strcpy (widget->name, widget_id[wid_type]); if (group_type == 0) /* Not group but a widget */ switch (wid_type) { case NT_DAC: /* Audio output */ case NT_ADC: /* Audio input */ { unsigned int sizes; int j; hdaudio_endpointinfo_t *endpoint; if (wid_type == 0) { /* Output endpoint */ if (mixer->num_outendpoints >= HDA_MAX_OUTSTREAMS) { cmn_err (CE_WARN, "Too many output endpoints\n"); return 0; } endpoint = &codec->outendpoints[codec->num_outendpoints++]; endpoint->stream_number = endpoint->default_stream_number = ++mixer->num_outendpoints; endpoint->ix = codec->num_outendpoints - 1; endpoint->iddle_stream = 0; } else { /* Input endpoint */ if (mixer->num_inendpoints >= HDA_MAX_INSTREAMS) { cmn_err (CE_WARN, "Too many input endpoints\n"); return 0; } endpoint = &codec->inendpoints[codec->num_inendpoints++]; endpoint->stream_number = endpoint->default_stream_number = ++mixer->num_inendpoints; endpoint->ix = codec->num_inendpoints - 1; endpoint->iddle_stream = 0; } endpoint->cad = cad; endpoint->base_wid = wid; endpoint->recsrc_wid = wid; endpoint->volume_wid = wid; endpoint->nrates=0; endpoint->name = widget->name; widget->endpoint = endpoint; /* * Find the widgets that manage rec/play volumes and recording * source selection. */ switch (wid_type) { case NT_DAC: endpoint->volume_wid = find_playvol_widget (mixer, cad, wid); break; case NT_ADC: endpoint->volume_wid = find_recvol_widget (mixer, cad, wid); endpoint->recsrc_wid = find_recsrc_widget (mixer, cad, wid); break; } if (widget->widget_caps & WCAP_STEREO) endpoint->channels = 2; else endpoint->channels = 1; sizes = codec->sizes; if (widget->widget_caps & WCAP_DIGITAL) /* Digital */ { endpoint->is_digital = 1; if (wid_type == NT_ADC) strcpy (widget->name, "spdifin"); else { strcpy (widget->name, "spdifout"); corb_write (mixer, cad, wid, 0, SET_SPDIF_CONTROL1, 1); /* Digital output enable */ endpoint->iddle_stream = FRONT_STREAM; } endpoint->fmt_mask |= AFMT_AC3; if (sizes & (1 << 20)) /* 32 bits */ { endpoint->fmt_mask |= AFMT_SPDIF_RAW; } } else { endpoint->is_digital = 0; } if (widget->widget_caps & WCAP_FORMAT_OVERRIDE) /* Override */ { if (!corb_read (mixer, cad, wid, 0, GET_PARAMETER, HDA_PCM_SIZES, &sizes, &b)) { cmn_err (CE_WARN, "GET_PARAMETER HDA_PCM_SIZES failed\n"); return 0; } } widget->sizes = sizes; endpoint->sizemask = sizes; if (sizes == 0) { corb_read (mixer, cad, codec->afg, 0, GET_PARAMETER, HDA_PCM_SIZES, &sizes, &b); widget->sizes = sizes; endpoint->sizemask = sizes; } DDB (cmn_err (CE_CONT, "\tPCM Size/Rate %08x\n", sizes)); for (j = 0; j < 5; j++) if (sizes & (1 << (j + 16))) { DDB (cmn_err (CE_CONT, "\t\tSupports %d bits\n", bit_sizes[j])); endpoint->fmt_mask |= bit_fmts[j]; } for (j = 0; j < 12; j++) if (sizes & (1 << j)) { DDB (cmn_err (CE_CONT, "\t\tSupports %d Hz\n", srates[j])); if (endpoint->nrates < 20) { endpoint->rates[endpoint->nrates++] = srates[j]; } } if ((widget->widget_caps & WCAP_DIGITAL) && wid_type == NT_DAC) /* Digital output */ { /* * Select front output as the default stream number. In * this way copy of the analog front signal will automatically * be delivered to the S/PDIF outpput when the S/PDIF device * is not being used for some other purpose. */ corb_write (mixer, cad, wid, 0, SET_CONVERTER, FRONT_STREAM << 4); } else { /* Select the iddle stream (0) for analog outputs */ corb_write (mixer, cad, wid, 0, SET_CONVERTER, IDDLE_STREAM << 4); } /* Select 48 kHz/16 bits/stereo */ corb_write (mixer, cad, wid, 0, SET_CONVERTER_FORMAT, 0x0009); } break; case NT_KNOB: /* Volume knob */ /* Clear the direct control bit */ corb_write (mixer, cad, wid, 0, SET_VOLUME_KNOB_CONTROL, 0x00); break; case NT_PIN: /* Pin complex */ { unsigned int conf; unsigned int pincaps; if (!corb_read (mixer, cad, wid, 0, GET_CONFIG_DEFAULT, 0, &conf, &b)) return 0; if (!corb_read (mixer, cad, wid, 0, GET_PARAMETER, HDA_PIN_CAPS, &pincaps, &b)) { cmn_err (CE_WARN, "GET_PARAMETER HDA_PIN_CAPS failed\n"); return 0; } widget->pincaps = pincaps; #if 0 if (widget->widget_caps & WCAP_UNSOL_CAPABLE) corb_write (mixer, cad, wid, 0, SET_UNSOLICITED, 0x80 | wid); #endif if (conf != 0) { int default_device = (conf >> 20) & 0x0f; int default_loc = (conf >> 24) & 0x3f; int conn = (conf >> 30) & 0x03; int color; int no_color=0; char *name = NULL, *loc = "", *color_name = NULL; color = (conf >> 12) & 0x0f; if (pincaps & (1 << 6)) cmn_err (CE_WARN, "Balanced I/O not supported\n"); if (!(pincaps & (1 << 5))) /* No input */ widget->pin_type = PIN_OUT; if (!(pincaps & (1 << 4))) widget->pin_type = PIN_IN; DDB (cmn_err (CE_CONT, "\tConfig default %08x\n", conf)); if ((default_loc & 0x0f) == 0x1) /* Rear panel - default */ loc = ""; if ((default_loc & 0x0f) == 0x2) /* Front panel */ loc = "fp-"; if ((default_loc & 0xf0) == 0x10) /* Internal func - eg cd/tad/spk */ loc = "int-"; if (conn == 1 && !(hdaudio_noskip & 1)) /* Pin not connected to anything */ { widget->skip = 1; widget->skip_output = 1; } switch (default_device) { case 0x0: name = "lineout"; widget->pin_type = PIN_OUT; break; case 0x1: name = "speaker"; no_color=1; widget->pin_type = PIN_OUT; break; case 0x2: name = "headphone"; widget->pin_type = PIN_OUT; break; case 0x3: name = "cd"; no_color=1; widget->pin_type = PIN_IN; break; case 0x4: name = "spdifout"; no_color=1; widget->pin_type = PIN_OUT; break; case 0x5: name = "digout"; no_color=1; widget->pin_type = PIN_OUT; break; case 0x6: name = "modem"; no_color=1; break; case 0x7: name = "phone"; no_color=1; break; case 0x8: name = "linein"; widget->pin_type = PIN_IN; break; case 0x9: name = "aux"; break; case 0xa: name = "mic"; widget->pin_type = PIN_MIC; break; case 0xb: name = "telephony"; no_color=1; break; case 0xc: name = "spdifin"; no_color=1; break; case 0xd: name = "digin"; no_color=1; break; case 0xe: name = "reserved"; no_color=1; break; case 0xf: /* Unused pin widget */ if (hdaudio_noskip & 2) break; widget->skip = 1; widget->skip_output = 1; break; } /* process only colored jacks and skip fixed function jacks */ switch (color) { case 0x1: color_name = "black"; widget->rgbcolor = OSS_RGB_BLACK; break; case 0x2: color_name = "gray"; widget->rgbcolor = OSS_RGB_GRAY; break; case 0x3: color_name = "blue"; widget->rgbcolor = OSS_RGB_BLUE; break; case 0x4: color_name = "green"; widget->rgbcolor = OSS_RGB_GREEN; break; case 0x5: color_name = "red"; widget->rgbcolor = OSS_RGB_RED; break; case 0x6: color_name = "orange"; widget->rgbcolor = OSS_RGB_ORANGE; break; case 0x7: color_name = "yellow"; widget->rgbcolor = OSS_RGB_YELLOW; break; case 0x8: color_name = "purple"; widget->rgbcolor = OSS_RGB_PURPLE; break; case 0x9: color_name = "pink"; widget->rgbcolor = OSS_RGB_PINK; break; case 0xe: color_name = "white"; widget->rgbcolor = OSS_RGB_WHITE; break; default: if (name != NULL) color_name = name; else color_name = "internal"; } if (no_color) widget->rgbcolor = 0; if (default_device == 0xf) /* Not present */ { widget->rgbcolor=0; color_name = "internal"; } sprintf (widget->color, "%s%s", loc, color_name); /* * By Hannu 20080111 * Use jack color as the widget name if no name was defined or if the default * function is lineout. This fixes the problem of several jacks being named * as lineout. */ if (name == NULL || default_device == 0x00) name = color_name; sprintf (widget->name, "%s%s", loc, name); } } break; } #if 0 if (!corb_read (mixer, cad, wid, 0, GET_PARAMETER, HDA_NODE_COUNT, &node_count, &b)) { cmn_err (CE_WARN, "GET_PARAMETER HDA_NODE_COUNT1 failed\n"); return 0; } first_node = (node_count >> 16) & 0xff; num_nodes = node_count & 0xff; DDB (cmn_err (CE_CONT, "\tFirst node %d, num nodes %d\n", first_node, num_nodes)); if (first_node > 0) for (i = first_node; i < first_node + num_nodes; i++) if (!attach_node (mixer, cad, i, wid)) return 0; #endif /* * Handle hardcodded widget names */ if (codec->remap != NULL) { int w; mixer->remap_avail=1; for (w = 0; codec->remap[w] != NULL; w++) if (w == wid) { char *s = codec->remap[w]; if (*s != 0) { strcpy (widget->name, s); if (*widget->color == 0) { strcpy (widget->color, widget->name); } } break; } } return 1; } /*ARGSUSED*/ static int attach_group (hdaudio_mixer_t * mixer, int cad, int wid, int parent, int group_type) { unsigned int a, b, gt; int i, first_node, num_nodes; codec_t *codec = mixer->codecs[cad]; if (codec == &NULL_codec) return 0; if (!corb_read (mixer, cad, wid, 0, GET_PARAMETER, HDA_OUTPUTAMP_CAPS, &codec->default_outamp_caps, &b)) { cmn_err (CE_WARN, "GET_PARAMETER HDA_OUTPUTAMP_CAPS failed\n"); return 0; } if (!corb_read (mixer, cad, wid, 0, GET_PARAMETER, HDA_INPUTAMP_CAPS, &codec->default_inamp_caps, &b)) { cmn_err (CE_WARN, "GET_PARAMETER HDA_INPUTTAMP_CAPS failed\n"); return 0; } if (!corb_read (mixer, cad, wid, 0, GET_PARAMETER, HDA_NODE_COUNT, &a, &b)) { cmn_err (CE_WARN, "GET_PARAMETER HDA_NODE_COUNT2 failed\n"); return 0; } first_node = (a >> 16) & 0xff; num_nodes = a & 0xff; corb_read (mixer, cad, wid, 0, GET_PARAMETER, HDA_GROUP_TYPE, >, &b); DDB (cmn_err (CE_CONT, "\tGroup %d First node %d, num nodes %d\n", wid, first_node, num_nodes)); /* * Ignore other than audio function groups. Codecs probably allocate * higher widget number for the modem group than the audio group. So in this * way we can have smaller MAX_WIDGETS which in turn conserves memory. */ if ((gt & 0xff) != group_type) return 0; if (corb_read (mixer, cad, wid, 0, GET_PARAMETER, HDA_PCM_SIZES, &a, &b)) { codec->sizes = a; } if (first_node > 0) for (i = first_node; i < first_node + num_nodes; i++) if (!attach_node (mixer, cad, i, wid)) return 0; if (num_nodes >= 1) codec->active=1; return 1; } static void polish_widget_list (hdaudio_mixer_t * mixer, int cad) { widget_t *widget; codec_t *codec; int wid, conn, loop; int skip = 0; int do_jacksense = 0; if (hdaudio_noskip & 4) return; if (mixer->codecs[cad] == &NULL_codec) { cmn_err (CE_WARN, "Bad codec %d\n", cad); } codec = mixer->codecs[cad]; for (wid = 0; wid < codec->nwidgets; wid++) { widget = &codec->widgets[wid]; } /* * Use jack sensing information to remove unconnected I/O pints from the mixer * interface. */ do_jacksense = 1; if (codec->num_jacks_plugged < 1 || hdaudio_jacksense < 1) do_jacksense = 0; if (do_jacksense) for (wid = 0; wid < codec->nwidgets; wid++) { widget = &codec->widgets[wid]; if (widget->wid_type != NT_PIN) /* Not a pin widget */ continue; if (!widget->plugged_in) widget->skip = 1; } /* * Check all widgets and mark them unusable (skip=1) if all of their input * connections are marked to be skipped. * * This needs to be done number of times so that the skip status propagates * to the end of the longest path. */ for (loop = 0; loop < codec->nwidgets / 4; loop++) /* nwidgets/4 iterations */ for (wid = 0; wid < codec->nwidgets; wid++) { widget = &codec->widgets[wid]; if (widget->skip || widget->used) continue; skip = 1; /* For now */ for (conn = 0; conn < widget->nconn; conn++) { if (!codec->widgets[widget->connections[conn]].skip) skip = 0; /* Cannot be skipped */ } if (skip && widget->nconn > 0) { widget->skip = 1; widget->used = 1; } } /* * Do the same backwards. Remove widgets that don't have connectivity to any * of the pin widgets. */ for (loop = 0; loop < codec->nwidgets / 4; loop++) /* nwidgets/4 iterations */ for (wid = 0; wid < codec->nwidgets; wid++) { widget = &codec->widgets[wid]; if (widget->skip_output || widget->used) continue; skip = 1; /* For now */ for (conn = 0; conn < widget->refcount; conn++) { if (!codec->widgets[widget->references[conn]].skip_output) skip = 0; /* Cannot be skipped */ } if (skip && widget->refcount > 0) { widget->skip_output = 1; widget->used = 1; } } /* * Final pass. */ for (wid = 0; wid < codec->nwidgets; wid++) { widget = &codec->widgets[wid]; if (widget->skip_output) { widget->skip = 1; } } } /* ARGSUSED */ static int attach_codec (hdaudio_mixer_t * mixer, int cad, char *hw_info, unsigned int pci_subdevice, int group_type) { unsigned int a, b, x; int subix, ix, i; int first_node, num_nodes; int has_audio_group = 0; codec_t *codec; if (cad >= MAX_CODECS) { cmn_err (CE_WARN, "attach_codec: Too many codecs %d\n", cad); return OSS_EIO; } mixer->ncodecs = cad + 1; if (mixer->codecs[cad] == &NULL_codec) { if ((codec = PMALLOC (mixer->osdev, sizeof (*codec))) == NULL) { cmn_err (CE_CONT, "Cannot allocate codec descriptor\n"); return OSS_ENOMEM; } memset (codec, 0, sizeof (*codec)); mixer->codecs[cad] = codec; } else { codec = mixer->codecs[cad]; } corb_write (mixer, cad, 0, 0, SET_POWER_STATE, 0); /* Power up everything */ if (!corb_read (mixer, cad, 0, 0, GET_PARAMETER, HDA_VENDOR, &a, &b)) { if (group_type == 1) { sprintf (hw_info, " Codec %2d: Not present\n", cad); cmn_err (CE_NOTE, "attach_codec: Codec #%d is not physically present\n", cad); } return OSS_EIO; } codec->vendor_id = a; /* * Find out the primary group list */ if (!corb_read (mixer, cad, 0, 0, GET_PARAMETER, HDA_NODE_COUNT, &x, &b)) { cmn_err (CE_WARN, "GET_PARAMETER HDA_NODE_COUNT3 failed\n"); return OSS_EIO; } codec->first_node = first_node = (x >> 16) & 0xff; num_nodes = x & 0xff; /* * Check if this one is an audio codec (has an audio function group) */ for (i = first_node; i < first_node + num_nodes; i++) { unsigned int gt; corb_read (mixer, cad, i, 0, GET_PARAMETER, HDA_GROUP_TYPE, >, &b); /* * Handle only the function group type requested by the upper layer code. */ if ((gt & 0xff) != 1) continue; has_audio_group = 1; } /* * Find codec specific settings */ for (ix = 0; codecs[ix].id != 0; ix++) if (codecs[ix].id == a) break; DDB (cmn_err (CE_CONT, "HD audio Codec ID: %08x (%s)\n", a, codecs[ix].name)); if (codecs[ix].id == 0) /* Unknown codec */ { if (group_type == 1) sprintf (hw_info, " Codec %2d: Unknown (0x%08x", cad, a); cmn_err (CE_NOTE, "HDA codec 0x%08x not known yet\n", a); /* * Create hexadecimal codec ID */ if (has_audio_group && mixer->chip_name == NULL) if ((mixer->chip_name = PMALLOC (mixer->osdev, 32)) != NULL) { sprintf (mixer->chip_name, "0x%08x", a); } } else { if (group_type == 1) sprintf (hw_info, " Codec %2d: %s (0x%08x", cad, codecs[ix].name, a); } if (has_audio_group && mixer->chip_name == NULL) { mixer->chip_name = codecs[ix].name; } if (codecs[ix].remap != NULL) codec->remap = codecs[ix].remap; if (codecs[ix].flags != 0) codec->vendor_flags = codecs[ix].flags; if (codecs[ix].mixer_init != NULL) codec->mixer_init = codecs[ix].mixer_init; codec->multich_map = codecs[ix].multich_map; if (corb_read (mixer, cad, 0, 0, GET_PARAMETER, HDA_REVISION, &a, &b)) { DDB (cmn_err (CE_CONT, "HDA codec revision %d.%d (%d.%d) (0x%08x)\n", (a >> 20) & 0xf, (a >> 16) & 0xf, (a >> 8) & 0xff, a & 0xff, a)); } else DDB (cmn_err (CE_CONT, "Can't get codec revision\n")); codec->codec_desc = &codecs[ix]; DDB (cmn_err (CE_CONT, "**** Codec %d ****\n", cad)); DDB (cmn_err (CE_CONT, "First node %d, num nodes %d\n", first_node, num_nodes)); for (i = first_node; i < first_node + num_nodes; i++) { corb_read (mixer, cad, i, 0, GET_PARAMETER, HDA_GROUP_TYPE, &a, &b); if ((a & 0xff) == group_type) /* Proper function group type */ { codec->afg = i; if (corb_read (mixer, cad, i, 0, GET_SUBSYSTEM_ID, 0, &a, &b)) { DDB (cmn_err (CE_CONT, "Subsystem ID = 0x%08x\n", a)); if (group_type == 1) { /* Append subvendor ID to hw_info */ hw_info += strlen (hw_info); sprintf (hw_info, "/0x%08x", a); } codec->subvendor_id = a; for (subix = 0; subdevices[subix].id != 0; subix++) if (subdevices[subix].id == a) { if (subdevices[subix].main_id != 0) if (subdevices[subix].main_id != codec->vendor_id) continue; if (subdevices[subix].pci_subdevice != 0) if (subdevices[subix].pci_subdevice != pci_subdevice) continue; DDB (cmn_err (CE_CONT, "Subdevice known as %s\n", subdevices[subix].name)); if (group_type == 1) { hw_info += strlen (hw_info); sprintf (hw_info, " %s", subdevices[subix].name); } if (subdevices[subix].remap != NULL) { codec->remap = subdevices[subix].remap; } if (subdevices[subix].multich_map != 0) codec->multich_map = subdevices[subix].multich_map; if (subdevices[subix].flags != 0) codec->vendor_flags = subdevices[subix].flags; if (subdevices[subix].mixer_init != NULL) { codec->mixer_init = subdevices[subix].mixer_init; } } } break; } } hw_info += strlen (hw_info); if (group_type == 1) strcpy (hw_info, ")\n"); if (codec->multich_map == 0) { codec->multich_map = 0x76543210; /* Sequential order */ } for (i = first_node; i < first_node + num_nodes; i++) { if (!attach_group (mixer, cad, i, 0, group_type)) continue; /* power up the AFG! */ corb_write (mixer, cad, i, 0, SET_POWER_STATE, 0); } /* Initialize and setup manually endpoints for Si3055. */ if ((mixer->codecs[cad]->vendor_flags & VF_SI3055_HACK) && (group_type == 2)) { hdaudio_si3055_endpoint_init(mixer, cad); } if (has_audio_group) { polish_widget_list (mixer, cad); } copy_endpoints(mixer, codec, 0); /* Copy analog endpoints from codec to mixer */ return (has_audio_group) ? 0 : OSS_EIO; } int hdaudio_mixer_get_mixdev (hdaudio_mixer_t * mixer) { return mixer->mixer_dev; } void hdaudio_mixer_set_initfunc (hdaudio_mixer_t * mixer, mixer_create_controls_t func) { mixer->client_mixer_init = func; } #define BASE44k (1<<14) static const struct hdaudio_rate_def _hdaudio_rates[] = { /* 48 kHz based rates */ {192000, (3 << 11)}, /* 4x */ {96000, (1 << 11)}, /* 2x */ {48000, 0}, /* 1x */ {24000, (1 << 8)}, /* 1/2x */ {16000, (2 << 8)}, /* 1/3x */ {12000, (3 << 8)}, /* 1/4x */ {9600, (4 << 8)}, /* 1/5x */ {8000, (5 << 8)}, /* 1/6x */ /* TODO: These rates didn't work for some reason. */ /* 44.1 kHz based rates */ {176400, BASE44k | (3 << 11)}, /* 4x */ {88200, BASE44k | (1 << 11)}, /* 2x */ {44100, BASE44k}, /* 1x */ {22050, BASE44k | (1 << 8)}, /* 1/2x */ {14700, BASE44k | (2 << 8)}, /* 1/3x */ {11025, BASE44k | (3 << 8)}, /* 1/4x */ {8820, BASE44k | (4 << 8)}, /* 1/5x */ {7350, BASE44k | (5 << 8)}, /* 1/6x */ {0} }; const struct hdaudio_rate_def *hdaudio_rates = _hdaudio_rates; int hdaudio_codec_setup_endpoint (hdaudio_mixer_t * mixer, hdaudio_endpointinfo_t * endpoint, int rate, int channels, int fmt, int stream_number, unsigned int *setupbits) { unsigned int tmp, spdif, dummy; int i; endpoint->auto_muted = 0; if (!corb_read (mixer, endpoint->cad, endpoint->base_wid, 0, GET_SPDIF_CONTROL, 0, &spdif, &dummy)) spdif = 0; spdif &= ~(1 << 5); /* Audio */ tmp = 0; if (fmt == AFMT_AC3) channels = 2; tmp |= channels - 1; switch (fmt) { case AFMT_U8: break; case AFMT_S16_LE: tmp |= 0x00000010; break; case AFMT_S32_LE: if (endpoint->sizemask & (1 << 20)) /* 32 bits */ tmp |= 0x00000040; else if (endpoint->sizemask & (1 << 19)) /* 24 bits */ tmp |= 0x00000030; else if (endpoint->sizemask & (1 << 18)) /* 20 bits */ tmp |= 0x00000020; else { cmn_err (CE_WARN, "Bad bit size\n"); return OSS_EIO; } break; case AFMT_AC3: tmp &= 0xff; tmp |= 0x11; /* 16 bits stereo */ spdif |= (1 << 5); /* Data */ break; case AFMT_SPDIF_RAW: tmp &= 0xff; tmp |= 0x81; /* 32 bits stereo */ break; default: cmn_err (CE_WARN, "Bad format %x\n", fmt); return OSS_EIO; } corb_write (mixer, endpoint->cad, endpoint->base_wid, 0, SET_SPDIF_CONTROL1, spdif & 0xff); /* * Finally the sample rate setup */ for (i = 0; hdaudio_rates[i].rate != 0; i++) if (hdaudio_rates[i].rate == rate) { tmp |= hdaudio_rates[i].mask; break; } *setupbits = tmp; if (mixer->codecs[endpoint->cad]->vendor_flags & VF_SI3055_HACK) { hdaudio_si3055_set_rate(mixer, endpoint->cad, rate); } corb_write (mixer, endpoint->cad, endpoint->base_wid, 0, SET_CONVERTER_FORMAT, tmp); corb_write (mixer, endpoint->cad, endpoint->base_wid, 0, SET_CONVERTER, stream_number << 4); if (channels > 2) { /* * Set up the converters for the other stereo pairs */ #if 1 // TODO: Test this int n = (channels + 1) / 2; for (i = 1; i < n; i++) { hdaudio_endpointinfo_t *ep; ep = &endpoint[i]; corb_write (mixer, ep->cad, ep->base_wid, 0, SET_CONVERTER_FORMAT, tmp); corb_write (mixer, ep->cad, ep->base_wid, 0, SET_CONVERTER, (stream_number << 4) | (i * 2)); } #endif } if (mixer->codecs[endpoint->cad]->vendor_flags & VF_VAIO_HACK) { /* * STAC9872 specific hack. In Sony VAIO configurations, the DAC widget * used for the headphone jack needs to duplicate the stream playing on * the DAC widget for the speaker when not in multichannel mode. */ if (channels <= 2 && endpoint->base_wid == 0x05) { corb_write (mixer, endpoint->cad, 0x02, 0, SET_CONVERTER_FORMAT, tmp); corb_write (mixer, endpoint->cad, 0x02, 0, SET_CONVERTER, stream_number << 4); } } #if 1 if (mixer->codecs[endpoint->cad]->vendor_flags & VF_ALC88X_HACK) { /* * ALC88x specfic hack. These codecs cannot play S/PDIF unless the front * DAC widget is playing the same stream. * * Analog front output (widget 0x14) will be automatically muted. */ if (endpoint->is_digital) { unsigned int v, b; hdaudio_codec_setup_endpoint (mixer, &mixer->outendpoints[0], rate, channels, fmt, stream_number, &tmp); if (fmt == AFMT_AC3) if (corb_read (mixer, endpoint->cad, 0x14, 0, GET_GAIN (1, 0), 0, &v, &b)) { endpoint->auto_muted = !(v & 0x80); v |= 0x80; /* Mute */ corb_write (mixer, endpoint->cad, 0x14, 0, SET_GAIN (1, 0, 1, 1, 0), v); } } } #endif return 0; } int hdaudio_codec_reset_endpoint (hdaudio_mixer_t * mixer, hdaudio_endpointinfo_t * endpoint, int channels) { int i; unsigned int v, b; int n = (channels + 1) / 2; /* * Set all converters to play stream iddle stream (usually 0=silence). */ corb_write (mixer, endpoint->cad, endpoint->base_wid, 0, SET_CONVERTER, endpoint->iddle_stream << 4); #if 1 // TODO: Test this if (channels > 2) for (i = 1; i < n; i++) { hdaudio_endpointinfo_t *ep; ep = &endpoint[i]; corb_write (mixer, ep->cad, ep->base_wid, 0, SET_CONVERTER, ep->iddle_stream << 4); } #endif if (mixer->codecs[endpoint->cad]->vendor_flags & VF_VAIO_HACK) /* Also set headphone DAC to play the iddle stream */ if (channels <= 2 && endpoint->base_wid == 0x05) { corb_write (mixer, endpoint->cad, 0x02, 0, SET_CONVERTER, endpoint->iddle_stream << 4); } if (mixer->codecs[endpoint->cad]->vendor_flags & VF_ALC88X_HACK) if (endpoint->is_digital && endpoint->auto_muted) /* Restore automatic analog mute back to normal */ { if (corb_read (mixer, endpoint->cad, 0x14, 0, GET_GAIN (1, 0), 0, &v, &b)) { v &= ~0x80; /* Unmute */ corb_write (mixer, endpoint->cad, 0x14, 0, SET_GAIN (1, 0, 1, 1, 0), v); endpoint->auto_muted = 0; } } return 0; } /*ARGSUSED*/ void hda_codec_unsol (hdaudio_mixer_t * mixer, unsigned int upper, unsigned int lower) { DDB (cmn_err (CE_CONT, "Unsol event %08x %08x\n", upper, lower)); } int hda_codec_getname (hdaudio_mixer_t * mixer, hda_name_t * name) { widget_t *widget; if (name->cad >= mixer->ncodecs) return OSS_EIO; if (mixer->codecs[name->cad] == &NULL_codec) return OSS_EIO; #if 1 if (name->wid >= mixer->codecs[name->cad]->nwidgets) return OSS_EIO; #endif widget = &mixer->codecs[name->cad]->widgets[name->wid]; strcpy (name->name, widget->name); return 0; } int hda_codec_getwidget (hdaudio_mixer_t * mixer, hda_widget_info_t * info) { widget_t *widget; if (info->cad >= mixer->ncodecs) return OSS_EIO; if (mixer->codecs[info->cad] == &NULL_codec) return OSS_EIO; widget = &mixer->codecs[info->cad]->widgets[info->wid]; if (info->wid >= mixer->codecs[info->cad]->nwidgets) return OSS_EIO; if (widget == NULL) return OSS_EIO; memcpy (info->info, widget, sizeof (*widget)); return 0; } int hdaudio_codec_audio_ioctl (hdaudio_mixer_t * mixer, hdaudio_endpointinfo_t * endpoint, unsigned int cmd, ioctl_arg arg) { //widget_t *base_widget = &mixer->codecs[endpoint->cad]->widgets[endpoint->base_wid]; widget_t *recsrc_widget = &mixer->codecs[endpoint->cad]->widgets[endpoint->recsrc_wid]; widget_t *volume_widget = &mixer->codecs[endpoint->cad]->widgets[endpoint->volume_wid]; char tmp[128], *t; unsigned int linked_wid, a, b; int i, v, left, right; int nsteps; if (mixer->codecs[endpoint->cad]->vendor_flags & VF_VAIO_HACK) linked_wid = (endpoint->volume_wid == 0x02) ? 0x05 : ((endpoint->volume_wid == 0x05) ? 0x02 : 0); else linked_wid = 0; switch (cmd) { case SNDCTL_DSP_GET_RECSRC_NAMES: *tmp = 0; t = tmp; for (i = 0; i < recsrc_widget->nconn; i++) { if (*tmp) /* Not empty */ *t++ = ' '; strcpy (t, mixer->codecs[recsrc_widget->cad]->widgets[recsrc_widget-> connections[i]]. name); t += strlen (t); } return oss_encode_enum ((oss_mixer_enuminfo *) arg, tmp, 0); break; case SNDCTL_DSP_GET_RECSRC: if (!corb_read (mixer, recsrc_widget->cad, recsrc_widget->wid, 0, GET_SELECTOR, 0, &a, &b)) return OSS_EIO; return *arg = a; break; case SNDCTL_DSP_SET_RECSRC: a = *arg; if (a > recsrc_widget->nconn) return OSS_EIO; corb_write (mixer, recsrc_widget->cad, recsrc_widget->wid, 0, SET_SELECTOR, a); recsrc_widget->current_selector = a; mixer_devs[mixer->mixer_dev]->modify_counter++; return *arg = a; break; case SNDCTL_DSP_GETRECVOL: nsteps = (volume_widget->inamp_caps >> 8) & 0x3f; if (nsteps < 1) nsteps = 1; if (!corb_read (mixer, volume_widget->cad, volume_widget->wid, 0, GET_GAIN (0, 1), 0, &a, &b)) return OSS_EIO; if (a & 0x80) /* Muted */ left = 0; else left = ((a & 0x7f) * 100) / nsteps; if (!corb_read (mixer, volume_widget->cad, volume_widget->wid, 0, GET_GAIN (0, 0), 0, &a, &b)) return OSS_EIO; if (a & 0x80) /* Muted */ right = 0; else right = ((a & 0x7f) * 100) / nsteps; v = left | (right << 8); return *arg = v; break; case SNDCTL_DSP_SETRECVOL: v = *arg; left = v & 0xff; right = (v >> 8) & 0xff; if (left < 0) left = 0; if (left > 100) left = 100; if (right < 0) right = 0; if (right > 100) right = 100; v = left | (right << 8); nsteps = (volume_widget->inamp_caps >> 8) & 0x3f; if (nsteps < 1) nsteps = 1; a = (left * nsteps) / 100; if (left == 0) a |= 0x80; /* Mute */ corb_write (mixer, volume_widget->cad, volume_widget->wid, 0, SET_GAIN (0, 1, 1, 0, 0), a); a = (right * nsteps) / 100; if (right == 0) a |= 0x80; /* Mute */ corb_write (mixer, volume_widget->cad, volume_widget->wid, 0, SET_GAIN (0, 1, 0, 1, 0), a); mixer_devs[mixer->mixer_dev]->modify_counter++; return *arg = v; break; case SNDCTL_DSP_GETPLAYVOL: nsteps = (volume_widget->outamp_caps >> 8) & 0x3f; if (nsteps < 1) nsteps = 1; if (!corb_read (mixer, volume_widget->cad, volume_widget->wid, 0, GET_GAIN (1, 1), 0, &a, &b)) return OSS_EIO; if (a & 0x80) /* Muted */ left = 0; else left = ((a & 0x7f) * 100) / nsteps; if (!corb_read (mixer, volume_widget->cad, volume_widget->wid, 0, GET_GAIN (1, 0), 0, &a, &b)) return OSS_EIO; if (a & 0x80) /* Muted */ right = 0; else right = ((a & 0x7f) * 100) / nsteps; v = left | (right << 8); return *arg = v; break; case SNDCTL_DSP_SETPLAYVOL: v = *arg; left = v & 0xff; right = (v >> 8) & 0xff; if (left < 0) left = 0; if (left > 100) left = 100; if (right < 0) right = 0; if (right > 100) right = 100; v = left | (right << 8); nsteps = (volume_widget->outamp_caps >> 8) & 0x3f; if (nsteps < 1) nsteps = 1; a = (left * nsteps) / 100; if (left == 0) a |= 0x80; /* Mute */ corb_write (mixer, volume_widget->cad, volume_widget->wid, 0, SET_GAIN (1, 0, 1, 0, 0), a); if (linked_wid) corb_write (mixer, volume_widget->cad, linked_wid, 0, SET_GAIN (1, 0, 1, 0, 0), a); a = (right * nsteps) / 100; if (right == 0) a |= 0x80; /* Mute */ corb_write (mixer, volume_widget->cad, volume_widget->wid, 0, SET_GAIN (1, 0, 0, 1, 1), a); if (linked_wid) corb_write (mixer, volume_widget->cad, linked_wid, 0, SET_GAIN (1, 0, 0, 1, 1), a); mixer_devs[mixer->mixer_dev]->modify_counter++; return *arg = v; break; case SNDCTL_DSP_MODEM_OFFHOOK: if (!endpoint->is_modem) { return OSS_EINVAL; } v = *arg; if (mixer->codecs[endpoint->cad]->vendor_flags & VF_SI3055_HACK) { v = hdaudio_si3055_set_offhook(mixer, endpoint->cad, v); } else { return OSS_ENOTSUP; } return *arg = v; break; } return OSS_EINVAL; } /* * Support routines for dedicated mixer drivers */ void hda_codec_add_group (int dev, hdaudio_mixer_t * mixer, int cad, int *group, int parent_group, const char *name) { int grp; if ((grp = mixer_ext_create_group (dev, parent_group, name)) > 0) *group = grp; } int hda_codec_add_pingroup (int dev, hdaudio_mixer_t * mixer, int cad, int wid, int *group, int top_group, int *parent_group, const char *name, int *n, const char *parent_name, int group_size) { int grp; widget_t *widget; codec_t *codec; if (cad < 0 || cad >= mixer->ncodecs) return OSS_ENXIO; codec = mixer->codecs[cad]; if (wid < 0 || wid >= codec->nwidgets) return OSS_ENXIO; widget = &codec->widgets[wid]; if (widget->used || widget->skip) return 0; if (group_size <= 1 || ((*n % group_size) == 0 && *n > 0)) { if ((grp = mixer_ext_create_group (dev, top_group, parent_name)) > 0) *parent_group = grp; *n = 0; } if ((grp = mixer_ext_create_group (dev, *parent_group, name)) > 0) *group = grp; (*n)++; return 1; } int hda_codec_add_adcgroup (int dev, hdaudio_mixer_t * mixer, int cad, int wid, int *group, int top_group, int *parent_group, const char *name, int *n, const char *parent_name, int group_size) { int grp; widget_t *widget; codec_t *codec; if (cad < 0 || cad >= mixer->ncodecs) return OSS_ENXIO; codec = mixer->codecs[cad]; if (wid < 0 || wid >= codec->nwidgets) return OSS_ENXIO; widget = &codec->widgets[wid]; if (widget->used || widget->skip) return 0; if (group_size <= 1 || ((*n % group_size) == 0 && *n > 0)) { if ((grp = mixer_ext_create_group (dev, top_group, parent_name)) > 0) *parent_group = grp; *n = 0; } if ((grp = mixer_ext_create_group (dev, *parent_group, name)) > 0) *group = grp; (*n)++; return 1; } int hda_codec_add_miscgroup (int dev, hdaudio_mixer_t * mixer, int cad, int wid, int *group, int top_group, int *parent_group, const char *name, int *n, const char *parent_name, int group_size) { int grp; widget_t *widget; codec_t *codec; if (cad < 0 || cad >= mixer->ncodecs) return OSS_ENXIO; codec = mixer->codecs[cad]; if (wid < 0 || wid >= codec->nwidgets) return OSS_ENXIO; widget = &codec->widgets[wid]; if (widget->used || widget->skip) return 0; if (group_size <= 1 || ((*n % group_size) == 0 && *n > 0)) { if ((grp = mixer_ext_create_group (dev, top_group, parent_name)) > 0) *parent_group = grp; *n = 0; } if ((grp = mixer_ext_create_group (dev, *parent_group, name)) > 0) *group = grp; (*n)++; return 1; } int create_outgain_selector (hdaudio_mixer_t * mixer, widget_t * widget, int group, const char *name) { int num = MIXNUM (widget, CT_OUTGAINSEL, 0); int ctl, i; int maxval; int offs, step, range; oss_mixext *ent; char tmp[128], *t; range = ((widget->outamp_caps >> AMPCAP_NUMSTEPS_SHIFT) & AMPCAP_NUMSTEPS_MASK) + 1; step = ((widget->outamp_caps >> AMPCAP_STEPSIZE_SHIFT) & AMPCAP_STEPSIZE_MASK) + 1; offs = (widget->outamp_caps >> AMPCAP_OFFSET_SHIFT) & AMPCAP_OFFSET_MASK; maxval = range; if (widget->outamp_caps & AMPCAP_MUTE) maxval += 1; if ((ctl = mixer_ext_create_control (mixer->mixer_dev, group, num, hdaudio_set_control, MIXT_ENUM, name, maxval, MIXF_READABLE | MIXF_WRITEABLE)) < 0) return ctl; t = tmp; *t = 0; for (i = 0; i < range; i++) { int v; v = (i - offs) * step * 4; if (*tmp != 0) *t++ = ' '; sprintf (t, "%d.%ddB", v / 10, v % 10); t += strlen (t); } if (widget->outamp_caps & AMPCAP_MUTE) { if (*tmp != 0) *t++ = ' '; strcpy (t, "mute"); t += strlen (t); } mixer_ext_set_strings (mixer->mixer_dev, ctl, tmp, 0); /* Copy RGB color */ if (widget->rgbcolor != 0) if ((ent = mixer_find_ext (mixer->mixer_dev, ctl)) != NULL) ent->rgbcolor = widget->rgbcolor; return 0; } int create_ingain_selector (hdaudio_mixer_t * mixer, codec_t * codec, widget_t * widget, int group, int ix, const char *name) { int num = MIXNUM (widget, CT_INGAINSEL, ix); int ctl, i; int maxval; int offs, step, range; oss_mixext *ent; char tmp[128], *t; range = ((widget->inamp_caps >> AMPCAP_NUMSTEPS_SHIFT) & AMPCAP_NUMSTEPS_MASK) + 1; step = ((widget->inamp_caps >> AMPCAP_STEPSIZE_SHIFT) & AMPCAP_STEPSIZE_MASK) + 1; offs = (widget->inamp_caps >> AMPCAP_OFFSET_SHIFT) & AMPCAP_OFFSET_MASK; maxval = range; if (widget->inamp_caps & AMPCAP_MUTE) maxval += 1; if ((ctl = mixer_ext_create_control (mixer->mixer_dev, group, num, hdaudio_set_control, MIXT_ENUM, name, maxval, MIXF_READABLE | MIXF_WRITEABLE)) < 0) return ctl; t = tmp; *t = 0; for (i = 0; i < range; i++) { int v; v = (i - offs) * step * 4; if (*tmp != 0) *t++ = ' '; sprintf (t, "%d.%ddB", v / 10, v % 10); t += strlen (t); } if (widget->inamp_caps & AMPCAP_MUTE) { if (*tmp != 0) *t++ = ' '; strcpy (t, "mute"); t += strlen (t); } mixer_ext_set_strings (mixer->mixer_dev, ctl, tmp, 0); /* Copy RGB color */ if (widget->color != 0) if ((ent = mixer_find_ext (mixer->mixer_dev, ctl)) != NULL) ent->rgbcolor = widget->rgbcolor; return 0; } int hda_codec_add_outamp (int dev, hdaudio_mixer_t * mixer, int cad, int wid, int group, const char *name, int percent, unsigned int flags) { widget_t *widget; codec_t *codec; int typ, num, maxval, val, ctl = 0; int range, step; oss_mixext *ent; extern int mixer_muted; if (cad < 0 || cad >= mixer->ncodecs) return OSS_ENXIO; codec = mixer->codecs[cad]; if (wid < 0 || wid >= codec->nwidgets) return OSS_ENXIO; widget = &codec->widgets[wid]; range = ((widget-> outamp_caps >> AMPCAP_NUMSTEPS_SHIFT) & AMPCAP_NUMSTEPS_MASK) + 1; step = ((widget-> outamp_caps >> AMPCAP_STEPSIZE_SHIFT) & AMPCAP_STEPSIZE_MASK) + 1; if (step > 20 /* 5dB */ && range < 5) { create_outgain_selector (mixer, widget, group, name); } else { maxval = hdaudio_amp_maxval (widget->outamp_caps); if (widget->widget_caps & WCAP_STEREO) { typ = MIXT_STEREOSLIDER16; num = MIXNUM (widget, CT_OUTSTEREO, 0); } else { typ = MIXT_MONOSLIDER16; num = MIXNUM (widget, CT_OUTMONO, 0); } if ((ctl = mixer_ext_create_control (mixer->mixer_dev, group, num, hdaudio_set_control, typ, name, maxval, flags | MIXF_READABLE | MIXF_WRITEABLE | MIXF_CENTIBEL)) < 0) return ctl; /* setup volume */ val = (maxval * percent) / 100; val = val | (val << 16); if (mixer_muted) val = 0; /* Copy RGB color */ if (widget->rgbcolor != 0) if ((ent = mixer_find_ext (dev, ctl)) != NULL) ent->rgbcolor = widget->rgbcolor; hdaudio_set_control (mixer->mixer_dev, num, SNDCTL_MIX_WRITE, val); } return ctl; } int hda_codec_add_outmute (int dev, hdaudio_mixer_t * mixer, int cad, int wid, int group, const char *name, int muted) { widget_t *widget; codec_t *codec; int ctl = 0; oss_mixext *ent; if (cad < 0 || cad >= mixer->ncodecs) return OSS_ENXIO; codec = mixer->codecs[cad]; if (wid < 0 || wid >= codec->nwidgets) return OSS_ENXIO; widget = &codec->widgets[wid]; if (widget->outamp_caps & AMPCAP_MUTE) /* Only mute control */ { // name = "mute"; if ((ctl = mixer_ext_create_control (mixer->mixer_dev, group, MIXNUM (widget, CT_OUTMUTE, 0), hdaudio_set_control, MIXT_MUTE, name, 2, MIXF_READABLE | MIXF_WRITEABLE)) < 0) return ctl; /* Copy RGB color */ if (widget->rgbcolor != 0) if ((ent = mixer_find_ext (dev, ctl)) != NULL) ent->rgbcolor = widget->rgbcolor; hdaudio_set_control (mixer->mixer_dev, MIXNUM (widget, CT_OUTMUTE, 0), SNDCTL_MIX_WRITE, muted); return ctl; } return 0; } int hda_codec_add_inamp (int dev, hdaudio_mixer_t * mixer, int cad, int wid, int ix, int group, const char *name, int percent, int flags) { widget_t *widget; widget_t *src_widget; codec_t *codec; int typ, num, maxval, val, ctl = 0, range, step; oss_mixext *ent; extern int mixer_muted; if (cad < 0 || cad >= mixer->ncodecs) return OSS_ENXIO; codec = mixer->codecs[cad]; if (wid < 0 || wid >= codec->nwidgets) return OSS_ENXIO; widget = &codec->widgets[wid]; range = ((widget-> outamp_caps >> AMPCAP_NUMSTEPS_SHIFT) & AMPCAP_NUMSTEPS_MASK) + 1; step = ((widget-> outamp_caps >> AMPCAP_STEPSIZE_SHIFT) & AMPCAP_STEPSIZE_MASK) + 1; if (step > 20 /* 5dB */ && range < 5) { return create_ingain_selector (mixer, codec, widget, group, ix, name); } maxval = hdaudio_amp_maxval (widget->inamp_caps); if (widget->widget_caps & WCAP_STEREO) { typ = MIXT_STEREOSLIDER16; num = MIXNUM (widget, CT_INSTEREO, ix); } else { typ = MIXT_MONOSLIDER16; num = MIXNUM (widget, CT_INMONO, ix); } if (codec->widgets[widget->connections[ix]].skip) { hdaudio_set_control (mixer->mixer_dev, num, SNDCTL_MIX_WRITE, 0); return 0; } if ((ctl = mixer_ext_create_control (mixer->mixer_dev, group, num, hdaudio_set_control, typ, name, maxval, MIXF_READABLE | MIXF_WRITEABLE | MIXF_CENTIBEL | flags)) < 0) return ctl; /* Setup initial volume */ val = (maxval * percent) / 100; val = val | (val << 16); if (mixer_muted) val = 0; hdaudio_set_control (mixer->mixer_dev, num, SNDCTL_MIX_WRITE, val); /* Copy RGB color */ src_widget = &codec->widgets[widget->connections[ix]]; if (src_widget->rgbcolor != 0) if ((ent = mixer_find_ext (dev, ctl)) != NULL) ent->rgbcolor = src_widget->rgbcolor; return ctl; } int hda_codec_add_inmute (int dev, hdaudio_mixer_t * mixer, int cad, int wid, int ix, int group, const char *name, int muted, unsigned int flags) { widget_t *widget; codec_t *codec; oss_mixext *ent; int ctl = 0; if (cad < 0 || cad >= mixer->ncodecs) return OSS_ENXIO; codec = mixer->codecs[cad]; if (wid < 0 || wid >= codec->nwidgets) return OSS_ENXIO; widget = &codec->widgets[wid]; if (codec->widgets[widget->connections[ix]].skip) { int num = MIXNUM (widget, CT_INMUTE, ix); hdaudio_set_control (mixer->mixer_dev, num, SNDCTL_MIX_WRITE, 1); return 0; } if ((ctl = mixer_ext_create_control (mixer->mixer_dev, group, MIXNUM (widget, CT_INMUTE, ix), hdaudio_set_control, MIXT_MUTE, name, 2, flags | MIXF_READABLE | MIXF_WRITEABLE)) < 0) return ctl; /* Copy RGB color */ if (widget->rgbcolor != 0) if ((ent = mixer_find_ext (dev, ctl)) != NULL) ent->rgbcolor = widget->rgbcolor; hdaudio_set_control (mixer->mixer_dev, MIXNUM (widget, CT_INMUTE, ix), SNDCTL_MIX_WRITE, muted); return ctl; } int hda_codec_set_inmute (int dev, hdaudio_mixer_t * mixer, int cad, int wid, int ix, int group, const char *name, int muted) { widget_t *widget; codec_t *codec; if (cad < 0 || cad >= mixer->ncodecs) return OSS_ENXIO; codec = mixer->codecs[cad]; if (wid < 0 || wid >= codec->nwidgets) return OSS_ENXIO; widget = &codec->widgets[wid]; // cmn_err(CE_CONT, "Set inmute 0x%02x:%d=%d\n", wid, ix, muted); return hdaudio_set_control (mixer->mixer_dev, MIXNUM (widget, CT_INMUTE, ix), SNDCTL_MIX_WRITE, muted); } int hda_codec_add_insrc (int dev, hdaudio_mixer_t * mixer, int cad, int wid, int ix, int group, const char *name, int unselected) { widget_t *widget; codec_t *codec; oss_mixext *ent; int ctl = 0; if (cad < 0 || cad >= mixer->ncodecs) return OSS_ENXIO; codec = mixer->codecs[cad]; if (wid < 0 || wid >= codec->nwidgets) return OSS_ENXIO; widget = &codec->widgets[wid]; if (codec->widgets[widget->connections[ix]].skip) { int num = MIXNUM (widget, CT_INMUTE, ix); hdaudio_set_control (mixer->mixer_dev, num, SNDCTL_MIX_WRITE, 1); return 0; } if ((ctl = mixer_ext_create_control (mixer->mixer_dev, group, MIXNUM (widget, CT_INSRC, ix), hdaudio_set_control, MIXT_ONOFF, name, 2, MIXF_READABLE | MIXF_WRITEABLE)) < 0) return ctl; /* Copy RGB color */ if (widget->rgbcolor != 0) if ((ent = mixer_find_ext (dev, ctl)) != NULL) ent->rgbcolor = widget->rgbcolor; hdaudio_set_control (mixer->mixer_dev, MIXNUM (widget, CT_INSRC, ix), SNDCTL_MIX_WRITE, unselected); return ctl; } int hda_codec_add_insrcselect (int dev, hdaudio_mixer_t * mixer, int cad, int wid, int group, int *ctl, const char *name, int initial_selection) { widget_t *widget; codec_t *codec; int i; oss_mixext *ext; *ctl = 0; if (cad < 0 || cad >= mixer->ncodecs) return OSS_ENXIO; codec = mixer->codecs[cad]; if (wid < 0 || wid >= codec->nwidgets) return OSS_ENXIO; widget = &codec->widgets[wid]; if ((*ctl = mixer_ext_create_control (mixer->mixer_dev, group, MIXNUM (widget, CT_INSRCSELECT, 0), hdaudio_set_control, MIXT_ENUM, name, widget->nconn, MIXF_READABLE | MIXF_WRITEABLE)) < 0) return 0; ext = mixer_find_ext (mixer->mixer_dev, *ctl); if (ext == NULL) { cmn_err (CE_WARN, "Cannot locate the mixer extension (x)\n"); return OSS_EIO; } /* Copy RGB color */ if (widget->color != 0) ext->rgbcolor = widget->rgbcolor; memset (ext->enum_present, 0, sizeof (ext->enum_present)); for (i = 0; i < widget->nconn; i++) { /* * ensure that the connection list has a valid widget id - some * devices have bogus connection lists */ if (codec->widgets[widget->connections[i]].wid < codec->first_node) continue; /* * Show only widgets that are not marked to be ignored. * Also hide I/O pins that are known to be outputs. */ if (!codec->widgets[widget->connections[i]].skip && codec->widgets[widget->connections[i]].sensed_pin != PIN_OUT) ext->enum_present[i / 8] |= (1 << (i % 8)); } hdaudio_set_control (mixer->mixer_dev, MIXNUM (widget, CT_INSRCSELECT, 0), SNDCTL_MIX_WRITE, initial_selection); return 1; } int hda_codec_add_select (int dev, hdaudio_mixer_t * mixer, int cad, int wid, int group, const char *name, int *ctl, int initial_select) { widget_t *widget; codec_t *codec; oss_mixext *ext; int i; *ctl = 0; if (cad < 0 || cad >= mixer->ncodecs) return OSS_ENXIO; codec = mixer->codecs[cad]; if (wid < 0 || wid >= codec->nwidgets) return OSS_ENXIO; widget = &codec->widgets[wid]; if ((*ctl = mixer_ext_create_control (mixer->mixer_dev, group, MIXNUM (widget, CT_SELECT, 0), hdaudio_set_control, MIXT_ENUM, name, widget->nconn, MIXF_READABLE | MIXF_WRITEABLE)) < 0) return *ctl; ext = mixer_find_ext (mixer->mixer_dev, *ctl); if (ext == NULL) { cmn_err (CE_WARN, "Cannot locate the mixer extension (x)\n"); return OSS_EIO; } /* Copy RGB color */ if (widget->color != 0) ext->rgbcolor = widget->rgbcolor; memset (ext->enum_present, 0, sizeof (ext->enum_present)); for (i = 0; i < widget->nconn; i++) { /* * ensure that the connection list has a valid widget id - some * devices have bogus connection lists */ if (codec->widgets[widget->connections[i]].wid < codec->first_node) continue; /* * Show only widgets that are not marked to be ignored. * Also hide I/O pins that are known to be outputs. */ if (!codec->widgets[widget->connections[i]].skip && codec->widgets[widget->connections[i]].sensed_pin != PIN_OUT) ext->enum_present[i / 8] |= (1 << (i % 8)); } if (initial_select > -1) widget->current_selector = initial_select; if (widget->current_selector >= widget->nconn) widget->current_selector = 0; corb_write (mixer, widget->cad, widget->wid, 0, SET_SELECTOR, widget->current_selector); return *ctl; } int hda_codec_add_pinselect (int dev, hdaudio_mixer_t * mixer, int cad, int wid, int group, const char *name, int *ctl, int initial_select) { widget_t *widget; codec_t *codec; unsigned int conf, b; oss_mixext *ext; int i; *ctl = 0; if (cad < 0 || cad >= mixer->ncodecs) return OSS_ENXIO; codec = mixer->codecs[cad]; if (wid < 0 || wid >= codec->nwidgets) return OSS_ENXIO; widget = &codec->widgets[wid]; if ((*ctl = mixer_ext_create_control (mixer->mixer_dev, group, MIXNUM (widget, CT_SELECT, 0), hdaudio_set_control, MIXT_ENUM, name, widget->nconn + 1, MIXF_READABLE | MIXF_WRITEABLE)) < 0) return *ctl; ext = mixer_find_ext (mixer->mixer_dev, *ctl); if (ext == NULL) { cmn_err (CE_WARN, "Cannot locate the mixer extension (x)\n"); return OSS_EIO; } /* Copy RGB color */ if (widget->color != 0) ext->rgbcolor = widget->rgbcolor; memset (ext->enum_present, 0, sizeof (ext->enum_present)); for (i = 0; i < widget->nconn; i++) { /* * ensure that the connection list has a valid widget id - some * devices have bogus connection lists */ if (codec->widgets[widget->connections[i]].wid < codec->first_node) continue; /* * Show only widgets that are not marked to be ignored. * Also hide I/O pins that are known to be outputs. */ if (!codec->widgets[widget->connections[i]].skip && codec->widgets[widget->connections[i]].sensed_pin != PIN_OUT) ext->enum_present[i / 8] |= (1 << (i % 8)); } /* * Enable the input selection (if available) */ i = widget->nconn; if (widget->pincaps & PINCAP_INPUT_CAPABLE) ext->enum_present[i / 8] |= (1 << (i % 8)); /* * Set the initial value. * * Use the default sequence as an index to the output source selectors. */ if (widget->sensed_pin == PIN_OUT) { if (corb_read (mixer, widget->cad, widget->wid, 0, GET_CONFIG_DEFAULT, 0, &conf, &b)) { int association, sequence; association = (conf >> 4) & 0x0f; sequence = conf & 0x0f; if (association != 0) { widget->current_selector = sequence; } } } else if (widget->sensed_pin == PIN_IN || widget->sensed_pin == PIN_MIC) widget->current_selector = widget->nconn; /* Turn on input mode */ if (initial_select > -1) widget->current_selector = initial_select; if (widget->current_selector < 0 || widget->current_selector >= widget->nconn + 1) widget->current_selector = 0; if (widget->current_selector < widget->nconn) { /* Output source select */ corb_write (mixer, cad, wid, 0, SET_SELECTOR, widget->current_selector); /* Enable output and HP amp. Set vref=Ground */ corb_write (mixer, cad, wid, 0, SET_PINCTL, 0xc0); } else { /* Input select * Program the correct VRef Values */ if (widget->pin_type == PIN_IN) /* Line-in */ { corb_write (mixer, cad, wid, 0, SET_PINCTL, 0x20); /*Ground*/ } else /* Mic-in */ { corb_write (mixer, cad, wid, 0, SET_PINCTL, 0x24); /*Vref=8 0% */ } } return *ctl; } void hda_codec_set_select (int dev, hdaudio_mixer_t * mixer, int cad, int wid, int value) { codec_t *codec; widget_t *widget; if (cad < 0 || cad >= mixer->ncodecs) return; codec = mixer->codecs[cad]; if (wid < 0 || wid >= codec->nwidgets) return; widget = &codec->widgets[wid]; widget->current_selector = value; corb_write (mixer, cad, wid, 0, SET_SELECTOR, value); } void hda_codec_set_pinselect (int dev, hdaudio_mixer_t * mixer, int cad, int wid, int value) { codec_t *codec; widget_t *widget; if (cad < 0 || cad >= mixer->ncodecs) return; codec = mixer->codecs[cad]; if (wid < 0 || wid >= codec->nwidgets) return; widget = &codec->widgets[wid]; widget->current_selector = value; if (widget->current_selector < widget->nconn) { /* Output source select */ corb_write (mixer, cad, wid, 0, SET_SELECTOR, widget->current_selector); /* Enable output and HP amp. Set vref=Ground */ corb_write (mixer, cad, wid, 0, SET_PINCTL, 0xc0); } else { /* Input select * Program the correct VRef Values */ if (widget->pin_type == PIN_IN) /* Line-in */ { corb_write (mixer, cad, wid, 0, SET_PINCTL, 0x20); /*Ground*/ } else /* Mic-in */ { corb_write (mixer, cad, wid, 0, SET_PINCTL, 0x24); /*Vref=8 0% */ } } } int hda_codec_add_choices (int dev, hdaudio_mixer_t * mixer, int ctl, const char *choiselist) { mixer_ext_set_strings (dev, ctl, choiselist, 0); return 0; } void hda_codec_set_color(int dev, hdaudio_mixer_t *mixer, int ctl, int color) { oss_mixext *ext; if ((ext = mixer_find_ext (mixer->mixer_dev, ctl)) != NULL) { ext->rgbcolor = color; //cmn_err(CE_CONT, "Mixer %s rgb->%06x\n", ext->extname, ext->rgbcolor); } }