blob: 8d772a5d6e055c244529b72d4739eddc0737f267 [file]
/*
* hda-emu - simple HD-audio codec emulator for debugging snd-hda-intel driver
*
* Main routine
*
* Copyright (c) Takashi Iwai <tiwai@suse.de>
*
* 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 <stdio.h>
#include <stdarg.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include <getopt.h>
#include <errno.h>
#include <signal.h>
#include <unistd.h>
#include <time.h>
#include "hda-types.h"
#include "hda-log.h"
#include <sound/driver.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/hda_codec.h>
#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_hdac_stream_format(channels, 32, rate);
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", &reg, &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", &reg, &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;
}