/* * hda-emu - simple HD-audio codec emulator for debugging snd-hda-intel driver * * Main routine * * Copyright (c) Takashi Iwai * * This driver is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This driver is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include #include #include #include #include #include #include #include "hda-types.h" #include "hda-log.h" #include #include #include #include #include "hda/hda_local.h" #ifdef HAVE_HDA_BEEP #include "hda/hda_beep.h" #endif /* fixup for 4.2+ kernels */ #ifdef NEW_HDA_INFRA #ifndef HAVE_BUS_OPS #define snd_hda_calc_stream_format snd_hdac_calc_stream_format #undef STREAM_FORMAT_WITH_CODEC #define HAVE_HDA_ATTACH_PCM 1 #endif #endif #ifndef HAVE_POWER_SAVE #define snd_hda_power_up(x) #define snd_hda_power_down(x) #endif extern int cmd_loop(FILE *fp); /* * interface to kernel */ static struct snd_card card = { .ctl_files = LIST_HEAD_INIT(card.ctl_files), .files_lock = MYLOCK_UNLOCKED, }; static struct xhda_codec proc; static struct hda_bus *bus; #ifdef NEW_HDA_INFRA #ifndef HAVE_BUS_OPS static struct hda_bus _bus; #endif #endif static struct hda_codec *_codec; static int ignore_invalid_ftype; static int cmd_send(struct hda_bus *bus, unsigned int cmd) { unsigned int nid = (cmd >> 20) & 0x7f; unsigned int verb = (cmd >> 8) & 0xfff; unsigned int parm = cmd & 0xff; int err; hda_log(HDA_LOG_VERB, "send: NID=0x%x, VERB=0x%x", nid, verb); switch (verb & 0xf00) { case AC_VERB_GET_AMP_GAIN_MUTE: hda_log(HDA_LOG_VERB, "(%s,%s:%s#%d), PARM=0x%x", get_verb_name(&proc, cmd), (verb & (1 << 7) ? "O" : "I"), (verb & (1 << 5) ? "L" : "R"), verb & 0x0f, parm); break; case AC_VERB_SET_AMP_GAIN_MUTE: hda_log(HDA_LOG_VERB, "(%s,%s%s:%s%s#%d), PARM=0x%x", get_verb_name(&proc, cmd), (verb & (1 << 7) ? "O" : ""), (verb & (1 << 6) ? "I" : ""), (verb & (1 << 5) ? "L" : ""), (verb & (1 << 4) ? "R" : ""), verb & 0x0f, parm); break; case AC_VERB_SET_PROC_COEF: case AC_VERB_SET_COEF_INDEX: hda_log(HDA_LOG_VERB, "(%s), PARM=0x%x", get_verb_name(&proc, cmd), (verb & 0xff) << 8 | parm); break; default: hda_log(HDA_LOG_VERB, "(%s), PARM=0x%x", get_verb_name(&proc, cmd), parm); if (verb == 0xf00) hda_log(HDA_LOG_VERB, "(%s)", get_parameter_name(&proc, cmd)); break; } hda_log(HDA_LOG_VERB, "\n"); err = hda_cmd(&proc, cmd); if (err < 0) { if (verb != AC_VERB_PARAMETERS || parm != AC_PAR_FUNCTION_TYPE || !ignore_invalid_ftype) hda_log(HDA_LOG_ERR, "invalid command: " "NID=0x%x, verb=0x%x, parm=0x%x\n", nid, verb, parm); return err; } return 0; } #ifdef HAVE_GET_RESPONSE_WITH_CADDR static unsigned int resp_get_caddr(struct hda_bus *bus, unsigned int caddr) { hda_log(HDA_LOG_VERB, "receive: 0x%x\n", proc.rc); return proc.rc; } #else static unsigned int resp_get(struct hda_bus *bus) { hda_log(HDA_LOG_VERB, "receive: 0x%x\n", proc.rc); return proc.rc; } #endif #ifdef OLD_HDA_CMD static int old_cmd_send(struct hda_codec *codec, hda_nid_t nid, int direct, unsigned int verb, unsigned int para) { u32 val; val = (u32)(codec->addr & 0x0f) << 28; val |= (u32)direct << 27; val |= (u32)nid << 20; val |= verb << 8; val |= para; return cmd_send(codec->bus, val); } static unsigned int old_resp_get(struct hda_codec *codec) { return resp_get(codec->bus); } #endif void hda_exec_verb(int nid, int verb, int parm) { u32 val; val = (u32)(proc.addr & 0x0f) << 28; val |= (u32)nid << 20; val |= verb << 8; val |= parm; if (hda_cmd(&proc, val) < 0) { hda_log(HDA_LOG_ERR, "invalid command: " "NID=0x%x, verb=0x%x, parm=0x%x\n", nid, verb, parm); } else { hda_log(HDA_LOG_VERB, "Command response:: 0x%x\n", proc.rc); } } /* * power_save module option handling */ #ifdef HDA_OLD_POWER_SAVE extern int *power_save_parameter; /* defined in kernel/hda_codec.c */ #else static int power_save; #endif int hda_get_power_save(void) { #ifdef HDA_OLD_POWER_SAVE return *power_save_parameter; #else return power_save; #endif } void hda_set_power_save(int val) { #ifdef HDA_OLD_POWER_SAVE *power_save_parameter = val; #else power_save = val; #ifdef NEW_HDA_INFRA snd_hda_set_power_save(bus, val * 1000); #endif #endif /* HDA_OLD_POWER_SAVE */ } int hda_get_dump_coef(void) { return _codec->dump_coef; } void hda_set_dump_coef(int val) { _codec->dump_coef = val; } /* * proc dump */ struct snd_info_buffer { FILE *fp; unsigned int nid; int printing; int processing; }; extern void (*snd_iprintf_dumper)(struct snd_info_buffer *buf, const char *fmt, va_list ap); static void log_dump_proc_file(struct snd_info_buffer *buf, const char *fmt, va_list ap) { char line[512]; int node, in_process; char *p; if (buf->nid <= 0) { vfprintf(buf->fp, fmt, ap); return; } vsnprintf(line, sizeof(line), fmt, ap); if (buf->processing) { if (buf->printing == 1) fputs(line, buf->fp); p = strchr(line, '\n'); if (p && !p[1]) buf->processing = 0; return; } in_process = buf->processing; p = strchr(line, '\n'); if (p && !p[1]) buf->processing = 0; else buf->processing = 1; if (in_process) { if (buf->printing == 1) fputs(line, buf->fp); return; } switch (buf->printing) { case 0: if (sscanf(line, "Node 0x%02x ", &node) == 1 && node == buf->nid) { fputs(line, buf->fp); buf->printing = 1; } break; case 1: if (!strncmp(line, "Node 0x", 7)) buf->printing = 2; else fputs(line, buf->fp); break; } } void hda_log_dump_proc(unsigned int nid, const char *file) { struct snd_info_buffer buf; FILE *fp; int saved_level; if (!card.proc) return; if (file) { fp = fopen(file, "w"); if (!fp) { hda_log(HDA_LOG_ERR, "Cannot open dump file %s\n", file); return; } buf.fp = fp; } else buf.fp = hda_get_logfp(); buf.nid = nid; if (nid > 1) buf.printing = 0; else buf.printing = 1; snd_hda_power_up(_codec); /* don't show verbs */ saved_level = hda_log_level_set(HDA_LOG_KERN); snd_iprintf_dumper = log_dump_proc_file; card.proc->func(card.proc, &buf); hda_log_level_set(saved_level); snd_hda_power_down(_codec); if (file) fclose(buf.fp); } /* * show available jacks */ static const char *_get_jack_location(u32 cfg) { static char *bases[7] = { "N/A", "Rear", "Front", "Left", "Right", "Top", "Bottom", }; static unsigned char specials_idx[] = { 0x07, 0x08, 0x17, 0x18, 0x19, 0x37, 0x38 }; static char *specials[] = { "Rear Panel", "Drive Bar", "Riser", "HDMI", "ATAPI", "Mobile-In", "Mobile-Out" }; int i; cfg = (cfg & AC_DEFCFG_LOCATION) >> AC_DEFCFG_LOCATION_SHIFT; if ((cfg & 0x0f) < 7) return bases[cfg & 0x0f]; for (i = 0; i < ARRAY_SIZE(specials_idx); i++) { if (cfg == specials_idx[i]) return specials[i]; } return "UNKNOWN"; } void hda_log_list_jacks(int raw) { struct xhda_node *node; unsigned int type, conn; static char *jack_conns[4] = { "Jack", "N/A", "Fixed", "Both" }; static char *jack_types[16] = { "Line Out", "Speaker", "HP Out", "CD", "SPDIF Out", "Digital Out", "Modem Line", "Modem Hand", "Line In", "Aux", "Mic", "Telephony", "SPDIF In", "Digitial In", "Reserved", "Other" }; static char *jack_locations[4] = { "Ext", "Int", "Sep", "Oth" }; for (node = proc.afg.next; node; node = node->next) { unsigned int pin_default; type = (node->wcaps & AC_WCAP_TYPE) >> AC_WCAP_TYPE_SHIFT; if (type != AC_WID_PIN) continue; pin_default = node->pin_default; #ifdef HAVE_SND_HDA_CODEC_GET_PINCFG if (!raw) pin_default = snd_hda_codec_get_pincfg(_codec, node->nid); #endif conn = (pin_default & AC_DEFCFG_PORT_CONN) >> AC_DEFCFG_PORT_CONN_SHIFT; if (conn == AC_JACK_PORT_NONE) continue; /* if (!(node->pincap & AC_PINCAP_PRES_DETECT)) continue;*/ hda_log(HDA_LOG_INFO, "NID 0x%02x: cfg 0x%08x: [%s] %s at %s %s\n", node->nid, pin_default, jack_conns[(pin_default & AC_DEFCFG_PORT_CONN) >> AC_DEFCFG_PORT_CONN_SHIFT], jack_types[(pin_default & AC_DEFCFG_DEVICE) >> AC_DEFCFG_DEVICE_SHIFT], jack_locations[(pin_default >> (AC_DEFCFG_LOCATION_SHIFT + 4)) & 3], _get_jack_location(pin_default)); } } /* * show jack state of the given NID */ void hda_log_jack_state(int nid) { int state = hda_get_jack_state(&proc, nid); if (state < 0) hda_log(HDA_LOG_ERR, "Invalid Jack NID 0x%x\n", nid); else hda_log(HDA_LOG_INFO, "Jack state [0x%x] = %d\n", nid, state); } /* * change the jack state of the given NID */ static void issue_unsol(int caddr, int res); void hda_log_set_jack(int nid, int val) { int err = hda_set_jack_state(&proc, nid, val); if (err > 0) hda_log_issue_unsol(nid); } void hda_log_issue_unsol(int nid) { int val = hda_get_unsol_state(&proc, nid); if (val & (1 << 7)) issue_unsol(proc.addr, val); } /* * suspend/resume simulation */ void hda_test_suspend(void) { #ifdef NEW_HDA_INFRA struct device *dev = hda_codec_dev(_codec); if (dev->driver && dev->driver->pm) dev->driver->pm->suspend(dev); #else /* !NEW_HDA_INFRA */ #ifdef HAVE_HDA_SUSPEND_PMSG snd_hda_suspend(bus, PMSG_SUSPEND); #else snd_hda_suspend(bus); #endif #endif /* NEW_HDA_INFRA */ } void hda_test_resume(void) { #ifdef NEW_HDA_INFRA struct device *dev = hda_codec_dev(_codec); if (dev->driver && dev->driver->pm) dev->driver->pm->resume(dev); #else /* !NEW_HDA_INFRA */ snd_hda_resume(bus); #endif /* NEW_HDA_INFRA */ } static inline unsigned int random_bit(unsigned int pincap, unsigned int mask, unsigned int bit) { if (pincap & mask) { if (random() & 1) return bit; } return 0; } static void randomize_amp(struct xhda_amp_caps *caps, struct xhda_amp_vals *vals, int nums) { int i, c; for (i = 0; i < nums; i++) for (c = 0; c < 2; c++) { if (caps->nsteps) vals->vals[i][c] = random() % caps->nsteps; else vals->vals[i][c] = 0; if (caps->mute && (random() & 1)) vals->vals[i][c] |= 0x80; } } void hda_test_pm_randomize(void) { struct xhda_node *node; for (node = proc.afg.next; node; node = node->next) { unsigned int type; type = (node->wcaps & AC_WCAP_TYPE) >> AC_WCAP_TYPE_SHIFT; if (type == AC_WID_PIN) node->pinctl = random() & 0xff; if (node->wcaps & AC_WCAP_IN_AMP) randomize_amp(node->amp_in_caps.override ? &node->amp_in_caps : &proc.afg.amp_in_caps, &node->amp_in_vals, node->num_nodes); if (node->wcaps & AC_WCAP_OUT_AMP) randomize_amp(node->amp_out_caps.override ? &node->amp_out_caps : &proc.afg.amp_out_caps, &node->amp_out_vals, 1); } } void hda_test_pm_reinit(void) { struct xhda_node *node; for (node = proc.afg.next; node; node = node->next) { node->pinctl = node->orig_pinctl; node->amp_in_vals = node->orig_amp_in_vals; node->amp_out_vals = node->orig_amp_out_vals; } } /* * unsol even handling */ extern void snd_hdac_bus_queue_event(struct hdac_bus *bus, u32 res, u32 res_ex); static void issue_unsol(int caddr, int res) { int vendor_id; /* no unsol handling during D3 */ if (proc.afg.power_current == 3) return; caddr |= 1 << 4; /* ALC880 has incompatible unsol tag */ #ifdef HAVE_HDA_CORE vendor_id = _codec->core.vendor_id; #else vendor_id = _codec->vendor_id; #endif if (vendor_id == 0x10ec0880) res = (res & 0x3f) << 28; else res = (res & 0x3f) << 26; #if 0 // old API snd_hda_queue_unsol_event(bus, res, caddr); #else snd_hdac_bus_queue_event(&bus->core, res, caddr); #endif } #ifdef CONFIG_SND_HDA_RECONFIG /* * sysfs reset simulation */ static void reset_pcm(void); void hda_codec_reset(void) { snd_hda_codec_reset(_codec); reset_pcm(); } /* * sysfs reconfig simulation */ int hda_codec_reconfig(void) { int err; snd_hda_power_up(_codec); snd_hda_codec_reset(_codec); err = snd_hda_codec_configure(_codec); if (err < 0) goto error; #ifndef NEW_HDA_INFRA /* rebuild PCMs */ err = snd_hda_build_pcms(bus); if (err < 0) goto error; /* rebuild mixers */ err = snd_hda_codec_build_controls(_codec); #endif /* !NEW_HDA_INFRA */ error: snd_hda_power_down(_codec); return err; } #ifdef HAVE_USER_PINCFGS /* * sysfs pin_configs simulations */ static void show_pincfgs(struct snd_array *list) { int i; for (i = 0; i < list->used; i++) { struct hda_pincfg *pin = snd_array_elem(list, i); hda_log(HDA_LOG_INFO, "0x%02x 0x%08x\n", pin->nid, pin->cfg); } } void hda_log_show_driver_pin_configs(void) { show_pincfgs(&_codec->driver_pins); } void hda_log_show_init_pin_configs(void) { show_pincfgs(&_codec->init_pins); } void hda_log_show_user_pin_configs(void) { show_pincfgs(&_codec->user_pins); } void hda_log_set_user_pin_configs(unsigned int nid, unsigned int cfg) { snd_hda_add_pincfg(_codec, &_codec->user_pins, nid, cfg); } #endif /* HAVE_USER_PINCFGS */ extern int _show_hints(struct hda_codec *codec, const char *key); extern int _parse_hints(struct hda_codec *codec, const char *buf); void hda_log_show_hints(char *hint) { _show_hints(_codec, hint); } void hda_log_set_hints(char *hint) { _parse_hints(_codec, hint); } #endif /* CONFIG_SND_HDA_RECONFIG */ /* * PCM */ #define MAX_PCM_STREAMS 16 static int num_pcm_streams; static struct hda_pcm *pcm_streams[MAX_PCM_STREAMS]; #ifndef OLD_HDA_PCM /* get a string corresponding to the given HDA_PCM_TYPE_XXX */ static const char *get_pcm_type_name(int type) { const char *names[] = { [HDA_PCM_TYPE_AUDIO] = "audio", [HDA_PCM_TYPE_SPDIF] = "SPDIF", [HDA_PCM_TYPE_HDMI] = "HDMI", [HDA_PCM_TYPE_MODEM] = "modem", }; if (type >= HDA_PCM_TYPE_AUDIO && type <= HDA_PCM_TYPE_MODEM) return names[type]; else return "unknown"; } #endif /* list registered PCM streams, called from hda-ctlsh.c */ void hda_list_pcms(void) { int i; for (i = 0; i < num_pcm_streams; i++) { struct hda_pcm *p = pcm_streams[i]; if (!p->stream[0].substreams && !p->stream[1].substreams) continue; #ifdef OLD_HDA_PCM hda_log(HDA_LOG_INFO, "%d: %s, play=%d, capt=%d\n", i, p->name, p->stream[0].substreams, p->stream[1].substreams); #else hda_log(HDA_LOG_INFO, "%d: %s:%d (%s), play=%d, capt=%d\n", i, p->name, p->device, get_pcm_type_name(p->pcm_type), p->stream[0].substreams, p->stream[1].substreams); #endif } } /* get the appropriate ALSA SNDRV_PCM_FORMAT_* from the format bits */ static int get_alsa_format(int bits) { if (bits <= 8) return SNDRV_PCM_FORMAT_U8; else if (bits <= 16) return SNDRV_PCM_FORMAT_S16_LE; else return SNDRV_PCM_FORMAT_S32_LE; } static char *fmt_names[64] = { "S8", "U8", "S16_LE", "S16_BE", "U16_LE", "U16_BE", "S24_LE", "S24_BE", "U24_LE", "U24_BE", "S32_LE", "S32_BE", "U32_LE", "U32_BE", "FLOAT_LE", "FLOAT_BE", "FLOAT64_LE", "FLOAT64_BE", "IEC958_LE", "IEC958_BE", "MU_LAW", "A_LAW", "IMA_ADPCM", "MPEG" "GSM", NULL, NULL, NULL, NULL, NULL, NULL, "SPECIAL", "S24_3LE", "S24_3BE", "U24_3LE", "U24_3BE" "S24_3LE", "S20_3BE", "U20_3LE", "U20_3BE", "S18_3LE", "S18_3BE", "U18_3LE", "U18_3BE", }; static int rate_consts[] = { 5512, 8000, 11025, 16000, 22050, 32000, 44100, 48000, 64000, 88200, 96000, 176400, 192000, }; /* test the given PCM stream, called from hda-ctlsh.c */ void hda_test_pcm(int id, int op, int subid, int dir, int rate, int channels, int format) { #ifndef HAVE_HDA_ATTACH_PCM static struct snd_pcm_substream dummy_substream; static struct snd_pcm_runtime dummy_runtime; static struct snd_pcm_str dummy_pstr; #endif struct snd_pcm_substream *substream; struct snd_pcm_runtime *runtime; static struct snd_pcm_str *pstr; struct hda_pcm_stream *hinfo; unsigned int format_val; unsigned int ctls = 0; int i, err; if (id < 0 || id >= num_pcm_streams || !pcm_streams[id]) { hda_log(HDA_LOG_ERR, "Invalid PCM id %d\n", id); return; } if (!pcm_streams[id]->stream[0].substreams && !pcm_streams[id]->stream[1].substreams) { hda_log(HDA_LOG_ERR, "Empty PCM for id %d\n", id); return; } if (!pcm_streams[id]->stream[dir].substreams) { hda_log(HDA_LOG_INFO, "No substream in PCM %s for %s\n", pcm_streams[id]->name, (dir ? "capt" : "play")); return; } if (subid < 0 || subid >= pcm_streams[id]->stream[dir].substreams) { hda_log(HDA_LOG_INFO, "Invalid substream %d for PCM %s for %s\n", subid, pcm_streams[id]->name, (dir ? "capt" : "play")); return; } #ifdef HAVE_HDA_ATTACH_PCM pstr = &pcm_streams[id]->pcm->streams[dir]; substream = &pstr->substream[subid]; runtime = substream->runtime; if (op == PCM_TEST_END) { if (!substream->ref_count) { hda_log(HDA_LOG_ERR, "PCM stream not opened\n"); return; } } else { if (substream->ref_count) { hda_log(HDA_LOG_ERR, "PCM stream already opened\n"); return; } runtime = calloc(1, sizeof(*runtime)); if (!runtime) { hda_log(HDA_LOG_ERR, "cannot malloc\n"); exit(1); } substream->runtime = runtime; substream->ref_count = 1; pstr->substream_opened = 1; } #else substream = &dummy_substream; runtime = &dummy_runtime; pstr = &dummy_pstr; memset(substream, 0, sizeof(*substream)); memset(runtime, 0, sizeof(*runtime)); substream->stream = dir; substream->number = subid; substream->runtime = runtime; substream->ref_count = 1; substream->pstr = pstr; pstr->substream_opened = 1; #endif runtime->rate = rate; runtime->format = get_alsa_format(format); runtime->channels = channels; hinfo = &pcm_streams[id]->stream[dir]; runtime->hw.channels_min = hinfo->channels_min; runtime->hw.channels_max = hinfo->channels_max; runtime->hw.formats = hinfo->formats; runtime->hw.rates = hinfo->rates; #ifdef INDIVIDUAL_SPDIF_CTLS { struct hda_spdif_out *spdif; spdif = snd_hda_spdif_out_of_nid(_codec, hinfo->nid); ctls = spdif ? spdif->ctls : 0; } #elif defined(STREAM_FORMAT_WITH_SPDIF) ctls = _codec->spdif_ctls; #endif if (op != PCM_TEST_END) { hda_log(HDA_LOG_INFO, "Open PCM %s for %s\n", pcm_streams[id]->name, (dir ? "capt" : "play")); snd_hda_power_up(_codec); err = hinfo->ops.open(hinfo, _codec, substream); if (err < 0) { hda_log(HDA_LOG_INFO, "Open error = %d\n", err); goto powerdown; } hda_log(HDA_LOG_INFO, "Available PCM parameters:\n"); hda_log(HDA_LOG_INFO, " channels: %d/%d\n", runtime->hw.channels_min, runtime->hw.channels_max); hda_log(HDA_LOG_INFO, " formats:"); for (i = 0; i < ARRAY_SIZE(fmt_names); i++) { if (runtime->hw.formats & (1ULL << i)) { if (fmt_names[i]) hda_log(HDA_LOG_INFO, " %s", fmt_names[i]); else hda_log(HDA_LOG_INFO, " Uknown#%d", i); } } hda_log(HDA_LOG_INFO, "\n rates:"); for (i = 0; i < ARRAY_SIZE(rate_consts); i++) { if (runtime->hw.rates & (1UL << i)) hda_log(HDA_LOG_INFO, " %d", rate_consts[i]); } hda_log(HDA_LOG_INFO, "\n"); if (channels < runtime->hw.channels_min || channels > runtime->hw.channels_max) { hda_log(HDA_LOG_ERR, "Channels count (%d) not available for %s\n", channels, (dir ? "capture" : "playback")); goto closing; } hda_log(HDA_LOG_INFO, "Prepare PCM, rate=%d, channels=%d, " "format=%d bits\n", rate, channels, format); format_val = snd_hda_calc_stream_format( #if defined(STREAM_FORMAT_WITH_CODEC) _codec, #endif rate, channels, get_alsa_format(format), format #if defined(INDIVIDUAL_SPDIF_CTLS) || defined(STREAM_FORMAT_WITH_SPDIF) , ctls #endif ); if (!format_val) goto closing; hda_log(HDA_LOG_INFO, "PCM format_val = 0x%x\n", format_val); #ifdef HAVE_COMMON_PREPARE err = snd_hda_codec_prepare(_codec, hinfo, subid + 1, format_val, substream); #else err = hinfo->ops.prepare(hinfo, _codec, 1, format_val, substream); #endif } if (op != PCM_TEST_START) { hda_log(HDA_LOG_INFO, "PCM Clean up\n"); #ifdef HAVE_COMMON_PREPARE snd_hda_codec_cleanup(_codec, hinfo, substream); #else hinfo->ops.cleanup(hinfo, _codec, substream); #endif } closing: if (op != PCM_TEST_START) { substream->ref_count = 0; hda_log(HDA_LOG_INFO, "Close PCM\n"); hinfo->ops.close(hinfo, _codec, substream); } powerdown: if (op != PCM_TEST_START) { snd_hda_power_down(_codec); #ifdef HAVE_HDA_ATTACH_PCM substream->runtime = NULL; substream->ref_count = 0; pstr->substream_opened = 0; free(runtime); #endif } } /* attach_pcm callback -- register the stream */ static int attach_pcm(struct hda_bus *bus, struct hda_codec *codec, struct hda_pcm *cpcm) { #ifdef HAVE_HDA_ATTACH_PCM int i, s; #endif if (cpcm->stream[SNDRV_PCM_STREAM_PLAYBACK].substreams || cpcm->stream[SNDRV_PCM_STREAM_CAPTURE].substreams) { #ifdef OLD_HDA_PCM hda_log(HDA_LOG_INFO, "Attach PCM name %s, play #%d, capture #%d\n", cpcm->name, cpcm->stream[SNDRV_PCM_STREAM_PLAYBACK].substreams, cpcm->stream[SNDRV_PCM_STREAM_CAPTURE].substreams); #else cpcm->device = num_pcm_streams; hda_log(HDA_LOG_INFO, "Attach PCM dev %d, name %s, type %s, play #%d, capture #%d\n", cpcm->device, cpcm->name, get_pcm_type_name(cpcm->pcm_type), cpcm->stream[SNDRV_PCM_STREAM_PLAYBACK].substreams, cpcm->stream[SNDRV_PCM_STREAM_CAPTURE].substreams); #endif if (num_pcm_streams >= MAX_PCM_STREAMS) { hda_log(HDA_LOG_ERR, "Too many streams\n"); return 0; } pcm_streams[num_pcm_streams] = cpcm; #ifdef HAVE_HDA_ATTACH_PCM cpcm->pcm = calloc(1, sizeof(*cpcm->pcm)); if (!cpcm->pcm) { hda_log(HDA_LOG_ERR, "cannot malloc\n"); exit(1); } cpcm->pcm->device = cpcm->device; for (s = 0; s < 2; s++) { struct snd_pcm_str *str = &cpcm->pcm->streams[s]; str->substream_count = cpcm->stream[s].substreams; if (!str->substream_count) continue; str->substream = calloc(str->substream_count, sizeof(*str->substream)); if (!str->substream) { hda_log(HDA_LOG_ERR, "cannot malloc\n"); exit(1); } for (i = 0; i < str->substream_count; i++) { str->substream[i].pcm = cpcm->pcm; str->substream[i].pstr = str; str->substream[i].stream = s; str->substream[i].number = i; } } #endif } num_pcm_streams++; return 0; } #ifdef CONFIG_SND_HDA_RECONFIG /* clear the all registered PCM streams */ static void reset_pcm(void) { memset(pcm_streams, 0, sizeof(pcm_streams)); num_pcm_streams = 0; } #endif #ifndef HAVE_HDA_ATTACH_PCM static int azx_pcm_create(struct hda_codec *codec) { int c, err; /* create audio PCMs */ for (c = 0; c < codec->num_pcms; c++) { err = attach_pcm(codec->bus, codec, &codec->pcm_info[c]); if (err < 0) return err; } return 0; } #endif /* * power management */ #ifndef NEW_HDA_INFRA #ifdef HAVE_NEW_PM_NOTIFY static void new_pm_notify(struct hda_bus *bus, bool power_up) { hda_log(HDA_LOG_INFO, "PM-Notified, power_up = %d\n", power_up); } #else static void pm_notify(struct hda_bus *bus) { hda_log(HDA_LOG_INFO, "PM-Notified\n"); } #ifdef OLD_HDA_CMD static void old_pm_notify(struct hda_codec *codec) { pm_notify(codec->bus); } #endif #endif /* HAVE_NEW_PM_NOTIFY */ #endif /* !NEW_HDA_INFRA */ /* * routings */ static int node_type(struct xhda_node *node) { return ((node->wcaps & AC_WCAP_TYPE) >> AC_WCAP_TYPE_SHIFT) & 0xf; } static const char *get_node_type_string(struct xhda_node *node) { static char *names[16] = { [AC_WID_AUD_OUT] = "Out", [AC_WID_AUD_IN] = "In ", [AC_WID_AUD_MIX] = "Mix", [AC_WID_AUD_SEL] = "Sel", [AC_WID_PIN] = "Pin", [AC_WID_POWER] = "Pow", [AC_WID_VOL_KNB] = "Knb", [AC_WID_BEEP] = "Bep", [AC_WID_VENDOR] = "Vdr", }; int type = node_type(node); return names[type] ? names[type] : "\?\?\?"; } static int get_muted(unsigned char *amp, unsigned int wcaps) { if (wcaps & AC_WCAP_STEREO) return (amp[0] & amp[1]) & 0x80; else return amp[0] & 0x80; } static void show_route_lists(struct xhda_route_list *list, unsigned flags) { int i; int show_mute = !!(flags & SHOW_MUTE); for (; list; list = list->next) { hda_nid_t prev_nid = 0; for (i = 0; i < list->num_nodes; i++) { struct xhda_node *node = list->node[i]; int in_mute = 0, out_mute = 0; int idx = 0; if (i > 0) { for (idx = 0; idx < node->num_nodes; idx++) { if (node->node[idx] == prev_nid) break; } if (idx >= node->num_nodes) idx = 0; } if (node->wcaps & AC_WCAP_IN_AMP) in_mute = get_muted(&node->amp_in_vals.vals[idx][0], node->wcaps); if (node->wcaps & AC_WCAP_OUT_AMP) out_mute = get_muted(&node->amp_out_vals.vals[0][0], node->wcaps); if (node_type(node) == AC_WID_PIN) { if (!i) { out_mute = in_mute; in_mute = false; } else { in_mute = out_mute; out_mute = false; } } if (i > 0) { const char *path; if (node_type(node) == AC_WID_AUD_MIX) path = " -- "; else if (node->node[node->curr_conn] == prev_nid) path = " -> "; else path = " -x "; hda_log(HDA_LOG_INFO, "%s%s", path, show_mute ? (in_mute ? "|" : " ") : ""); } hda_log(HDA_LOG_INFO, "%s[%02x]%s", get_node_type_string(node), node->nid, show_mute ? (out_mute ? "|" : " ") : ""); prev_nid = node->nid; } hda_log(HDA_LOG_INFO, "\n"); } } void hda_show_routes(int nid, unsigned flags) { struct xhda_route_list *list; int had_list = 0; if (flags & SHOW_DIR_IN) { list = hda_routes_connected_to(&proc, nid, flags); show_route_lists(list, flags); had_list = list != NULL; hda_free_route_lists(list); } if (flags & SHOW_DIR_OUT) { list = hda_routes_connected_from(&proc, nid, flags); if (list && had_list) hda_log(HDA_LOG_INFO, "\n"); show_route_lists(list, flags); hda_free_route_lists(list); } } /* * pin-config override */ static struct xhda_node *find_node(struct xhda_codec *codec, int nid) { struct xhda_node *node; for (node = &codec->afg; node; node = node->next) { if (node->nid == nid) return node; } return NULL; } static void set_pincfg(struct xhda_codec *codec, int nid, int val, int user) { struct xhda_node *node; #ifdef HAVE_USER_PINCFGS if (user) { hda_log_set_user_pin_configs(nid, val); hda_log(HDA_LOG_INFO, " User Pin 0x%02x to 0x%08x\n", nid, val); return; } #endif node = find_node(codec, nid); if (node) { node->pin_default = val; hda_log(HDA_LOG_INFO, " Pin 0x%02x to 0x%08x\n", nid, val); return; } } static int override_pincfg(struct xhda_codec *codec, char *pincfg, int user) { FILE *fp; char buf[256]; struct xhda_sysfs_list *sys; int is_fw_file, is_in_pincfg; if (strchr(pincfg, '=')) { /* direct pincfg string */ int reg, val; if (sscanf(pincfg, "%i=%i", ®, &val) != 2) { hda_log(HDA_LOG_ERR, "Invalid pincfg %s\n", pincfg); return -EINVAL; } set_pincfg(codec, reg, val, user); return 0; } for (sys = codec->sysfs_list; sys; sys = sys->next) { if (sys->type == XHDA_SYS_PINCFG && !strcmp(sys->id, pincfg)) { struct xhda_sysfs_value *val; hda_log(HDA_LOG_INFO, "Overriding pin-configs via %s\n", pincfg); for (val = sys->entry.vals; val; val = val->next) set_pincfg(codec, val->val[0], val->val[1], user); return 0; } } /* if not found in the given input, try to open it */ fp = fopen(pincfg, "r"); if (!fp) { hda_log(HDA_LOG_ERR, "Cannot find init pincfg %s\n", pincfg); return -EINVAL; } hda_log(HDA_LOG_INFO, "Overriding pin-configs from file %s\n", pincfg); is_fw_file = 0; is_in_pincfg = 0; while (fgets(buf, sizeof(buf), fp)) { int reg, val; if (*buf == '#' || *buf == '\n') continue; if (is_fw_file) { if (*buf == '[') { if (is_in_pincfg) break; is_in_pincfg = !strncmp(buf, "[pincfg]", 8); continue; } else if (!is_in_pincfg) continue; } else { if (!strncmp(buf, "[codec]", 7)) { is_fw_file = 1; continue; } } if (sscanf(buf, "%i %i", ®, &val) != 2) break; set_pincfg(codec, reg, val, user); } fclose (fp); return 0; } static int load_init_hints(struct xhda_codec *codec, char *hints) { #ifdef CONFIG_SND_HDA_RECONFIG FILE *fp; char buf[256]; struct xhda_sysfs_list *sys; int is_fw_file, is_in_hint; if (strchr(hints, '=')) { /* direct hint string */ if (_parse_hints(_codec, hints)) { hda_log(HDA_LOG_ERR, "Invalid hints %s\n", hints); return -EINVAL; } return 0; } for (sys = codec->sysfs_list; sys; sys = sys->next) { if (sys->type == XHDA_SYS_HINTS && !strcmp(sys->id, hints)) { struct xhda_sysfs_hints *val; hda_log(HDA_LOG_INFO, "Add hints from %s\n", hints); for (val = sys->entry.hints; val; val = val->next) _parse_hints(_codec, val->line); return 0; } } /* if not found in the given input, try to open it */ fp = fopen(hints, "r"); if (!fp) { hda_log(HDA_LOG_ERR, "Cannot find hints %s\n", hints); return -EINVAL; } hda_log(HDA_LOG_INFO, "Add hints from file %s\n", hints); is_fw_file = 0; is_in_hint = 0; while (fgets(buf, sizeof(buf), fp)) { if (*buf == '#' || *buf == '\n') continue; if (is_fw_file) { if (*buf == '[') { if (is_in_hint) break; is_in_hint = !strncmp(buf, "[hint]", 8); continue; } else if (!is_in_hint) continue; } else { if (!strncmp(buf, "[codec]", 7)) { is_fw_file = 1; continue; } } _parse_hints(_codec, buf); } fclose(fp); return 0; #else hda_log(HDA_LOG_ERR, "-H option isn't supported for this kernel\n"); return -EINVAL; #endif } /* */ static void usage(void) { fprintf(stderr, "usage: " "hda-emu [options] proc-file\n"); fprintf(stderr, "options:\n"); fprintf(stderr, " -l level specifies log level\n"); fprintf(stderr, " -i index specifies codec index to parse\n"); fprintf(stderr, " -p vendor:dev specifies PCI subsystem ID\n"); fprintf(stderr, " -m model specifies model option string\n"); fprintf(stderr, " -o file log to the given file\n"); fprintf(stderr, " -q don't echo but only to log file\n"); fprintf(stderr, " -C print messages in color (default)\n"); fprintf(stderr, " -M no color print\n"); fprintf(stderr, " -F print prefixes to messages\n"); fprintf(stderr, " -a issues SIGTRAP at codec errors\n"); fprintf(stderr, " -n don't configure codec at start\n"); fprintf(stderr, " -P pincfg initialize pin-configuration from sysfs entry\n"); fprintf(stderr, " -U pincfg initialize user pincfg overrides\n"); fprintf(stderr, " -H hints add initial hints from sysfs entry or file\n"); fprintf(stderr, " -j NID turn on the initial jack-state of the given pin\n"); } #include "kernel/init_hooks.h" static FILE *popen_var(const char *v1, const char *v2, const char *type) { char tmp[4096]; snprintf(tmp, sizeof(tmp), "%s %s", v1, v2); return popen(tmp, type); } static FILE *file_open(const char *fname) { const char *p; if (access(fname, R_OK)) return NULL; p = strrchr(fname, '.'); if (p) { if (!strcmp(p, ".bz2")) return popen_var("bzcat", fname, "r"); if (!strcmp(p, ".gz")) return popen_var("zcat", fname, "r"); } return fopen(fname, "r"); } #ifdef NEW_HDA_INFRA #ifdef HAVE_BUS_OPS static struct hda_bus_ops bus_ops = { .command = cmd_send, .get_response = resp_get_caddr, .attach_pcm = attach_pcm, }; #else int snd_hdac_bus_send_cmd(struct hdac_bus *bus, unsigned int val) { return cmd_send(NULL, val); } int snd_hdac_bus_get_response(struct hdac_bus *bus, unsigned int addr, unsigned int *res) { if (res) *res = resp_get(NULL); return 0; } void snd_hda_bus_reset(struct hda_bus *bus) { snd_hda_bus_reset_codecs(bus); } int snd_hda_attach_pcm_stream(struct hda_bus *bus, struct hda_codec *codec, struct hda_pcm *cpcm) { return attach_pcm(bus, codec, cpcm); } #endif /* HAVE_BUS_OPS */ #else /* !NEW_HDA_INFRA */ static void setup_bus_template(struct hda_bus_template *temp) { #ifdef HAVE_POWER_SAVE #ifndef OLD_POWER_SAVE temp->power_save = &power_save; #endif #endif #ifdef OLD_HDA_CMD temp->ops.command = old_cmd_send; temp->ops.get_response = old_resp_get; #ifdef HAVE_POWER_SAVE temp->ops.pm_notify = old_pm_notify; #endif #else /* !OLD_HDA_CMD */ temp.ops.command = cmd_send; #ifdef HAVE_GET_RESPONSE_WITH_CADDR temp->ops.get_response = resp_get_caddr; #else temp->ops.get_response = resp_get; #endif #ifdef HAVE_HDA_ATTACH_PCM temp->ops.attach_pcm = attach_pcm; #endif #ifdef HAVE_NEW_PM_NOTIFY temp->ops.pm_notify = new_pm_notify; #else temp->ops.pm_notify = pm_notify; #endif #endif /* OLD_HDA_CMD */ } #endif /* NEW_HDA_INFRA */ struct cfg_chain { char *cfg; struct cfg_chain *next; }; static void cfg_chain_add(struct cfg_chain **chain, char *cfg) { struct cfg_chain *item, **prev; item = malloc(sizeof(*item)); if (!item) exit(1); item->cfg = cfg; item->next = NULL; for (prev = chain; *prev; prev = &(*prev)->next) ; *prev = item; } int main(int argc, char **argv) { int c, err; struct cfg_chain *cfg; FILE *fp; int idx = -1; int pci_subvendor = 0; int pci_subdevice = 0; char *opt_model = NULL; char *logfile = NULL; unsigned int log_flags = HDA_LOG_FLAG_COLOR; struct pci_dev mypci; #ifndef NEW_HDA_INFRA struct hda_bus_template temp; #endif struct hda_codec *codec; struct cfg_chain *init_pincfg = NULL; struct cfg_chain *user_pincfg = NULL; struct cfg_chain *init_hints = NULL; int num_active_jacks = 0; int no_configure = 0; unsigned int active_jacks[16]; while ((c = getopt(argc, argv, "al:i:p:m:do:qCMFP:U:H:j:n")) != -1) { switch (c) { case 'a': hda_log_trap_on_error = 1; break; case 'l': hda_log_level_set(atoi(optarg)); break; case 'i': idx = atoi(optarg); break; case 'p': if (sscanf(optarg, "%x:%x", &pci_subvendor, &pci_subdevice) != 2) { fprintf(stderr, "invalid arg %s\n", optarg); return 1; } break; case 'm': opt_model = optarg; break; case 'o': logfile = optarg; break; case 'q': log_flags |= HDA_LOG_FLAG_NO_ECHO; break; case 'C': log_flags |= HDA_LOG_FLAG_COLOR; break; case 'F': log_flags |= HDA_LOG_FLAG_PREFIX; break; case 'M': log_flags &= ~HDA_LOG_FLAG_COLOR; break; case 'P': cfg_chain_add(&init_pincfg, optarg); break; case 'U': cfg_chain_add(&user_pincfg, optarg); break; case 'H': cfg_chain_add(&init_hints, optarg); break; case 'j': if (num_active_jacks >= ARRAY_SIZE(active_jacks)) { fprintf(stderr, "Too many -j options given\n"); return 1; } active_jacks[num_active_jacks++] = strtoul(optarg, NULL, 0); break; case 'n': no_configure = 1; break; default: usage(); return 1; } } if (optind >= argc) { usage(); return 1; } fp = file_open(argv[optind]); if (!fp) { fprintf(stderr, "cannot open %s\n", argv[optind]); return 1; } srandom((unsigned int)time(NULL)); hda_log_init(logfile, log_flags); /* ignore SIGTRAP as default; gdb will override anyway... */ signal(SIGTRAP, SIG_IGN); hda_log(HDA_LOG_INFO, "# Parsing..\n"); if (parse_codec_proc(fp, &proc, idx) < 0) { hda_log(HDA_LOG_INFO, "error at reading proc\n"); return 1; } for (cfg = init_pincfg; cfg; cfg = cfg->next) { if (override_pincfg(&proc, cfg->cfg, 0) < 0) return 1; } if (num_active_jacks) { int i; for (i = 0; i < num_active_jacks; i++) { struct xhda_node *node; node = find_node(&proc, active_jacks[i]); if (node) node->jack_state = 1; } } mypci.vendor = proc.pci_vendor; mypci.device = proc.pci_device; mypci.subsystem_vendor = proc.pci_subvendor; mypci.subsystem_device = proc.pci_subdevice; mypci.revision = proc.pci_revision; /* override PCI SSID */ if (pci_subvendor || pci_subdevice) { mypci.subsystem_vendor = pci_subvendor; mypci.subsystem_device = pci_subdevice; hda_log(HDA_LOG_INFO, "Using PCI SSID %04x:%04x\n", pci_subvendor, pci_subdevice); } #ifndef NEW_HDA_INFRA memset(&temp, 0, sizeof(temp)); temp.pci = &mypci; temp->modelname = opt_model; if (opt_model) hda_log(HDA_LOG_INFO, "Using model option '%s'\n", opt_model); setup_bus_template(&temp); #endif /* !NEW_HDA_INFRA */ gather_codec_hooks(); #ifdef NEW_HDA_INFRA #ifdef HAVE_BUS_OPS err = snd_hda_bus_new(&card, &bus); #else /* HAVE_BUS_OPS */ #ifdef HAVE_HDAC_IO_OPS err = snd_hdac_bus_init(&_bus.core, NULL, NULL, NULL); #else err = snd_hdac_bus_init(&_bus.core, NULL, NULL); #endif bus = &_bus; bus->card = &card; mutex_init(&bus->prepare_mutex); #endif /* HAVE_BUS_OPS */ #else /* NEW_HDA_INFRA */ err = snd_hda_bus_new(&card, &temp, &bus); #endif /* NEW_HDA_INFRA */ if (err < 0) { hda_log(HDA_LOG_ERR, "cannot create snd_hda_bus\n"); return 1; } #ifdef NEW_HDA_INFRA bus->pci = &mypci; bus->modelname = opt_model; #ifdef HAVE_BUS_OPS bus->ops = bus_ops; #endif #endif /* NEW_HDA_INFRA */ ignore_invalid_ftype = 1; #ifdef NEW_HDA_INFRA err = snd_hda_codec_new(bus, &card, proc.addr, &codec); #else /* !NEW_HDA_INFRA */ #ifdef OLD_HDA_CODEC_NEW err = snd_hda_codec_new(bus, proc.addr, &codec); #else err = snd_hda_codec_new(bus, proc.addr, 1, &codec); #endif #endif /* NEW_HDA_INFRA */ ignore_invalid_ftype = 0; if (err) { hda_log(HDA_LOG_ERR, "cannot create codec\n"); return 1; } #ifdef HDA_BEEP_MODE_ON codec->beep_mode = HDA_BEEP_MODE_ON; #endif _codec = codec; #ifdef HAVE_USER_PINCFGS snd_array_init(&codec->user_pins, sizeof(struct hda_pincfg), 16); #endif for (cfg = init_hints; cfg; cfg = cfg->next) { if (load_init_hints(&proc, cfg->cfg) < 0) return 1; } for (cfg = user_pincfg; cfg; cfg = cfg->next) { if (override_pincfg(&proc, cfg->cfg, 1) < 0) return 1; } #ifdef HAVE_HDA_PATCH_LOADER if (!no_configure) snd_hda_codec_configure(codec); #endif #ifndef NEW_HDA_INFRA if (!no_configure) { hda_log(HDA_LOG_INFO, "# Building PCMs...\n"); snd_hda_build_pcms(bus); #ifndef HAVE_HDA_ATTACH_PCM azx_pcm_create(codec); #endif hda_log(HDA_LOG_INFO, "# Init and building controls...\n"); #ifdef CONFIG_SND_HDA_RECONFIG snd_hda_codec_build_controls(codec); #else snd_hda_build_controls(codec->bus); #endif } #endif /* !NEW_HDA_INFRA */ snd_hda_codec_register(codec); /* power-down after init phase */ snd_hda_power_down(codec); cmd_loop(stdin); return 0; }