blob: 4b8d64498fb16a5ff3d27321a92f139e61fefb98 [file] [log] [blame]
Linus Torvalds1da177e2005-04-16 15:20:36 -07001/*
2 * Universal Interface for Intel High Definition Audio Codec
3 *
4 * Generic proc interface
5 *
6 * Copyright (c) 2004 Takashi Iwai <tiwai@suse.de>
7 *
8 *
9 * This driver is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * This driver is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22 */
23
Linus Torvalds1da177e2005-04-16 15:20:36 -070024#include <linux/init.h>
Linus Torvalds1da177e2005-04-16 15:20:36 -070025#include <sound/core.h>
26#include "hda_codec.h"
Adrian Bunk18612042005-11-23 13:14:50 +010027#include "hda_local.h"
Linus Torvalds1da177e2005-04-16 15:20:36 -070028
29static const char *get_wid_type_name(unsigned int wid_value)
30{
31 static char *names[16] = {
32 [AC_WID_AUD_OUT] = "Audio Output",
33 [AC_WID_AUD_IN] = "Audio Input",
34 [AC_WID_AUD_MIX] = "Audio Mixer",
35 [AC_WID_AUD_SEL] = "Audio Selector",
36 [AC_WID_PIN] = "Pin Complex",
37 [AC_WID_POWER] = "Power Widget",
38 [AC_WID_VOL_KNB] = "Volume Knob Widget",
39 [AC_WID_BEEP] = "Beep Generator Widget",
40 [AC_WID_VENDOR] = "Vendor Defined Widget",
41 };
42 wid_value &= 0xf;
43 if (names[wid_value])
44 return names[wid_value];
45 else
Takashi Iwai3bc89522006-10-17 20:41:38 +020046 return "UNKNOWN Widget";
Linus Torvalds1da177e2005-04-16 15:20:36 -070047}
48
Takashi Iwaic8b6bf9b2005-11-17 14:57:47 +010049static void print_amp_caps(struct snd_info_buffer *buffer,
Linus Torvalds1da177e2005-04-16 15:20:36 -070050 struct hda_codec *codec, hda_nid_t nid, int dir)
51{
52 unsigned int caps;
Jaroslav Kysela7f0e2f82006-07-05 17:39:14 +020053 caps = snd_hda_param_read(codec, nid,
54 dir == HDA_OUTPUT ?
55 AC_PAR_AMP_OUT_CAP : AC_PAR_AMP_IN_CAP);
Linus Torvalds1da177e2005-04-16 15:20:36 -070056 if (caps == -1 || caps == 0) {
57 snd_iprintf(buffer, "N/A\n");
58 return;
59 }
Takashi Iwaid01ce992007-07-27 16:52:19 +020060 snd_iprintf(buffer, "ofs=0x%02x, nsteps=0x%02x, stepsize=0x%02x, "
61 "mute=%x\n",
Linus Torvalds1da177e2005-04-16 15:20:36 -070062 caps & AC_AMPCAP_OFFSET,
63 (caps & AC_AMPCAP_NUM_STEPS) >> AC_AMPCAP_NUM_STEPS_SHIFT,
64 (caps & AC_AMPCAP_STEP_SIZE) >> AC_AMPCAP_STEP_SIZE_SHIFT,
65 (caps & AC_AMPCAP_MUTE) >> AC_AMPCAP_MUTE_SHIFT);
66}
67
Takashi Iwaic8b6bf9b2005-11-17 14:57:47 +010068static void print_amp_vals(struct snd_info_buffer *buffer,
Linus Torvalds1da177e2005-04-16 15:20:36 -070069 struct hda_codec *codec, hda_nid_t nid,
Takashi Iwai3e289f12005-06-10 19:45:09 +020070 int dir, int stereo, int indices)
Linus Torvalds1da177e2005-04-16 15:20:36 -070071{
72 unsigned int val;
Takashi Iwai3e289f12005-06-10 19:45:09 +020073 int i;
74
Jaroslav Kysela7f0e2f82006-07-05 17:39:14 +020075 dir = dir == HDA_OUTPUT ? AC_AMP_GET_OUTPUT : AC_AMP_GET_INPUT;
Takashi Iwai3e289f12005-06-10 19:45:09 +020076 for (i = 0; i < indices; i++) {
77 snd_iprintf(buffer, " [");
78 if (stereo) {
Takashi Iwaid01ce992007-07-27 16:52:19 +020079 val = snd_hda_codec_read(codec, nid, 0,
80 AC_VERB_GET_AMP_GAIN_MUTE,
Takashi Iwai3e289f12005-06-10 19:45:09 +020081 AC_AMP_GET_LEFT | dir | i);
82 snd_iprintf(buffer, "0x%02x ", val);
83 }
Takashi Iwaid01ce992007-07-27 16:52:19 +020084 val = snd_hda_codec_read(codec, nid, 0,
85 AC_VERB_GET_AMP_GAIN_MUTE,
Takashi Iwai3e289f12005-06-10 19:45:09 +020086 AC_AMP_GET_RIGHT | dir | i);
87 snd_iprintf(buffer, "0x%02x]", val);
Linus Torvalds1da177e2005-04-16 15:20:36 -070088 }
Takashi Iwai3e289f12005-06-10 19:45:09 +020089 snd_iprintf(buffer, "\n");
Linus Torvalds1da177e2005-04-16 15:20:36 -070090}
91
Takashi Iwaib90d7762006-11-07 16:10:06 +010092static void print_pcm_rates(struct snd_info_buffer *buffer, unsigned int pcm)
93{
94 static unsigned int rates[] = {
95 8000, 11025, 16000, 22050, 32000, 44100, 48000, 88200,
96 96000, 176400, 192000, 384000
97 };
98 int i;
99
100 pcm &= AC_SUPPCM_RATES;
101 snd_iprintf(buffer, " rates [0x%x]:", pcm);
102 for (i = 0; i < ARRAY_SIZE(rates); i++)
103 if (pcm & (1 << i))
104 snd_iprintf(buffer, " %d", rates[i]);
105 snd_iprintf(buffer, "\n");
106}
107
108static void print_pcm_bits(struct snd_info_buffer *buffer, unsigned int pcm)
109{
110 static unsigned int bits[] = { 8, 16, 20, 24, 32 };
111 int i;
112
113 pcm = (pcm >> 16) & 0xff;
114 snd_iprintf(buffer, " bits [0x%x]:", pcm);
115 for (i = 0; i < ARRAY_SIZE(bits); i++)
116 if (pcm & (1 << i))
117 snd_iprintf(buffer, " %d", bits[i]);
118 snd_iprintf(buffer, "\n");
119}
120
121static void print_pcm_formats(struct snd_info_buffer *buffer,
122 unsigned int streams)
123{
124 snd_iprintf(buffer, " formats [0x%x]:", streams & 0xf);
125 if (streams & AC_SUPFMT_PCM)
126 snd_iprintf(buffer, " PCM");
127 if (streams & AC_SUPFMT_FLOAT32)
128 snd_iprintf(buffer, " FLOAT");
129 if (streams & AC_SUPFMT_AC3)
130 snd_iprintf(buffer, " AC3");
131 snd_iprintf(buffer, "\n");
132}
133
Takashi Iwaic8b6bf9b2005-11-17 14:57:47 +0100134static void print_pcm_caps(struct snd_info_buffer *buffer,
Linus Torvalds1da177e2005-04-16 15:20:36 -0700135 struct hda_codec *codec, hda_nid_t nid)
136{
137 unsigned int pcm = snd_hda_param_read(codec, nid, AC_PAR_PCM);
138 unsigned int stream = snd_hda_param_read(codec, nid, AC_PAR_STREAM);
139 if (pcm == -1 || stream == -1) {
140 snd_iprintf(buffer, "N/A\n");
141 return;
142 }
Takashi Iwaib90d7762006-11-07 16:10:06 +0100143 print_pcm_rates(buffer, pcm);
144 print_pcm_bits(buffer, pcm);
145 print_pcm_formats(buffer, stream);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700146}
147
148static const char *get_jack_location(u32 cfg)
149{
150 static char *bases[7] = {
151 "N/A", "Rear", "Front", "Left", "Right", "Top", "Bottom",
152 };
153 static unsigned char specials_idx[] = {
154 0x07, 0x08,
155 0x17, 0x18, 0x19,
156 0x37, 0x38
157 };
158 static char *specials[] = {
159 "Rear Panel", "Drive Bar",
160 "Riser", "HDMI", "ATAPI",
161 "Mobile-In", "Mobile-Out"
162 };
163 int i;
164 cfg = (cfg & AC_DEFCFG_LOCATION) >> AC_DEFCFG_LOCATION_SHIFT;
165 if ((cfg & 0x0f) < 7)
166 return bases[cfg & 0x0f];
167 for (i = 0; i < ARRAY_SIZE(specials_idx); i++) {
168 if (cfg == specials_idx[i])
169 return specials[i];
170 }
171 return "UNKNOWN";
172}
173
174static const char *get_jack_connection(u32 cfg)
175{
176 static char *names[16] = {
177 "Unknown", "1/8", "1/4", "ATAPI",
178 "RCA", "Optical","Digital", "Analog",
179 "DIN", "XLR", "RJ11", "Comb",
180 NULL, NULL, NULL, "Other"
181 };
182 cfg = (cfg & AC_DEFCFG_CONN_TYPE) >> AC_DEFCFG_CONN_TYPE_SHIFT;
183 if (names[cfg])
184 return names[cfg];
185 else
186 return "UNKNOWN";
187}
188
189static const char *get_jack_color(u32 cfg)
190{
191 static char *names[16] = {
192 "Unknown", "Black", "Grey", "Blue",
193 "Green", "Red", "Orange", "Yellow",
194 "Purple", "Pink", NULL, NULL,
195 NULL, NULL, "White", "Other",
196 };
197 cfg = (cfg & AC_DEFCFG_COLOR) >> AC_DEFCFG_COLOR_SHIFT;
198 if (names[cfg])
199 return names[cfg];
200 else
201 return "UNKNOWN";
202}
203
Takashi Iwaic8b6bf9b2005-11-17 14:57:47 +0100204static void print_pin_caps(struct snd_info_buffer *buffer,
Linus Torvalds1da177e2005-04-16 15:20:36 -0700205 struct hda_codec *codec, hda_nid_t nid)
206{
Takashi Iwaib0c95f52005-04-20 13:44:08 +0200207 static char *jack_conns[4] = { "Jack", "N/A", "Fixed", "Both" };
Linus Torvalds1da177e2005-04-16 15:20:36 -0700208 static char *jack_types[16] = {
209 "Line Out", "Speaker", "HP Out", "CD",
210 "SPDIF Out", "Digital Out", "Modem Line", "Modem Hand",
211 "Line In", "Aux", "Mic", "Telephony",
212 "SPDIF In", "Digitial In", "Reserved", "Other"
213 };
214 static char *jack_locations[4] = { "Ext", "Int", "Sep", "Oth" };
Takashi Iwaie97a5162007-10-26 14:56:36 +0200215 unsigned int caps, val;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700216
217 caps = snd_hda_param_read(codec, nid, AC_PAR_PIN_CAP);
218 snd_iprintf(buffer, " Pincap 0x08%x:", caps);
219 if (caps & AC_PINCAP_IN)
220 snd_iprintf(buffer, " IN");
221 if (caps & AC_PINCAP_OUT)
222 snd_iprintf(buffer, " OUT");
223 if (caps & AC_PINCAP_HP_DRV)
224 snd_iprintf(buffer, " HP");
Takashi Iwai58854922006-06-21 19:19:25 +0200225 if (caps & AC_PINCAP_EAPD)
226 snd_iprintf(buffer, " EAPD");
227 if (caps & AC_PINCAP_PRES_DETECT)
228 snd_iprintf(buffer, " Detect");
Linus Torvalds1da177e2005-04-16 15:20:36 -0700229 snd_iprintf(buffer, "\n");
230 caps = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_CONFIG_DEFAULT, 0);
Takashi Iwaib0c95f52005-04-20 13:44:08 +0200231 snd_iprintf(buffer, " Pin Default 0x%08x: [%s] %s at %s %s\n", caps,
232 jack_conns[(caps & AC_DEFCFG_PORT_CONN) >> AC_DEFCFG_PORT_CONN_SHIFT],
Linus Torvalds1da177e2005-04-16 15:20:36 -0700233 jack_types[(caps & AC_DEFCFG_DEVICE) >> AC_DEFCFG_DEVICE_SHIFT],
234 jack_locations[(caps >> (AC_DEFCFG_LOCATION_SHIFT + 4)) & 3],
235 get_jack_location(caps));
236 snd_iprintf(buffer, " Conn = %s, Color = %s\n",
237 get_jack_connection(caps),
238 get_jack_color(caps));
Takashi Iwaie97a5162007-10-26 14:56:36 +0200239 if (caps & AC_PINCAP_EAPD) {
240 val = snd_hda_codec_read(codec, nid, 0,
241 AC_VERB_GET_EAPD_BTLENABLE, 0);
242 snd_iprintf(buffer, " EAPD: 0x%x\n", val);
243 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700244}
245
246
Takashi Iwaid01ce992007-07-27 16:52:19 +0200247static void print_codec_info(struct snd_info_entry *entry,
248 struct snd_info_buffer *buffer)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700249{
250 struct hda_codec *codec = entry->private_data;
251 char buf[32];
252 hda_nid_t nid;
253 int i, nodes;
254
255 snd_hda_get_codec_name(codec, buf, sizeof(buf));
256 snd_iprintf(buffer, "Codec: %s\n", buf);
257 snd_iprintf(buffer, "Address: %d\n", codec->addr);
258 snd_iprintf(buffer, "Vendor Id: 0x%x\n", codec->vendor_id);
259 snd_iprintf(buffer, "Subsystem Id: 0x%x\n", codec->subsystem_id);
260 snd_iprintf(buffer, "Revision Id: 0x%x\n", codec->revision_id);
Jonathan Phenixe25c05f2007-06-19 18:31:28 +0200261
262 if (codec->mfg)
263 snd_iprintf(buffer, "Modem Function Group: 0x%x\n", codec->mfg);
264 else
265 snd_iprintf(buffer, "No Modem Function Group found\n");
266
Takashi Iwaiec9e1c52005-09-07 13:29:22 +0200267 if (! codec->afg)
268 return;
Takashi Iwaicb53c622007-08-10 17:21:45 +0200269 snd_hda_power_up(codec);
Takashi Iwaib90d7762006-11-07 16:10:06 +0100270 snd_iprintf(buffer, "Default PCM:\n");
Linus Torvalds1da177e2005-04-16 15:20:36 -0700271 print_pcm_caps(buffer, codec, codec->afg);
272 snd_iprintf(buffer, "Default Amp-In caps: ");
273 print_amp_caps(buffer, codec, codec->afg, HDA_INPUT);
274 snd_iprintf(buffer, "Default Amp-Out caps: ");
275 print_amp_caps(buffer, codec, codec->afg, HDA_OUTPUT);
276
277 nodes = snd_hda_get_sub_nodes(codec, codec->afg, &nid);
278 if (! nid || nodes < 0) {
279 snd_iprintf(buffer, "Invalid AFG subtree\n");
Takashi Iwaicb53c622007-08-10 17:21:45 +0200280 snd_hda_power_down(codec);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700281 return;
282 }
283 for (i = 0; i < nodes; i++, nid++) {
Takashi Iwaid01ce992007-07-27 16:52:19 +0200284 unsigned int wid_caps =
285 snd_hda_param_read(codec, nid,
286 AC_PAR_AUDIO_WIDGET_CAP);
287 unsigned int wid_type =
288 (wid_caps & AC_WCAP_TYPE) >> AC_WCAP_TYPE_SHIFT;
Takashi Iwai3e289f12005-06-10 19:45:09 +0200289 int conn_len = 0;
290 hda_nid_t conn[HDA_MAX_CONNECTIONS];
Takashi Iwaie97a5162007-10-26 14:56:36 +0200291 unsigned int pinctls;
Takashi Iwai3e289f12005-06-10 19:45:09 +0200292
Linus Torvalds1da177e2005-04-16 15:20:36 -0700293 snd_iprintf(buffer, "Node 0x%02x [%s] wcaps 0x%x:", nid,
294 get_wid_type_name(wid_type), wid_caps);
295 if (wid_caps & AC_WCAP_STEREO)
296 snd_iprintf(buffer, " Stereo");
297 else
298 snd_iprintf(buffer, " Mono");
299 if (wid_caps & AC_WCAP_DIGITAL)
300 snd_iprintf(buffer, " Digital");
301 if (wid_caps & AC_WCAP_IN_AMP)
302 snd_iprintf(buffer, " Amp-In");
303 if (wid_caps & AC_WCAP_OUT_AMP)
304 snd_iprintf(buffer, " Amp-Out");
305 snd_iprintf(buffer, "\n");
306
Takashi Iwaie1716132007-11-16 17:52:39 +0100307 /* volume knob is a special widget that always have connection
308 * list
309 */
310 if (wid_type == AC_WID_VOL_KNB)
311 wid_caps |= AC_WCAP_CONN_LIST;
312
Takashi Iwai3e289f12005-06-10 19:45:09 +0200313 if (wid_caps & AC_WCAP_CONN_LIST)
314 conn_len = snd_hda_get_connections(codec, nid, conn,
315 HDA_MAX_CONNECTIONS);
316
Linus Torvalds1da177e2005-04-16 15:20:36 -0700317 if (wid_caps & AC_WCAP_IN_AMP) {
318 snd_iprintf(buffer, " Amp-In caps: ");
319 print_amp_caps(buffer, codec, nid, HDA_INPUT);
320 snd_iprintf(buffer, " Amp-In vals: ");
321 print_amp_vals(buffer, codec, nid, HDA_INPUT,
Takashi Iwai3e289f12005-06-10 19:45:09 +0200322 wid_caps & AC_WCAP_STEREO, conn_len);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700323 }
324 if (wid_caps & AC_WCAP_OUT_AMP) {
325 snd_iprintf(buffer, " Amp-Out caps: ");
326 print_amp_caps(buffer, codec, nid, HDA_OUTPUT);
327 snd_iprintf(buffer, " Amp-Out vals: ");
328 print_amp_vals(buffer, codec, nid, HDA_OUTPUT,
Takashi Iwai3e289f12005-06-10 19:45:09 +0200329 wid_caps & AC_WCAP_STEREO, 1);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700330 }
331
Takashi Iwaie97a5162007-10-26 14:56:36 +0200332 switch (wid_type) {
333 case AC_WID_PIN:
Linus Torvalds1da177e2005-04-16 15:20:36 -0700334 print_pin_caps(buffer, codec, nid);
Takashi Iwaid01ce992007-07-27 16:52:19 +0200335 pinctls = snd_hda_codec_read(codec, nid, 0,
336 AC_VERB_GET_PIN_WIDGET_CONTROL,
337 0);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700338 snd_iprintf(buffer, " Pin-ctls: 0x%02x:", pinctls);
339 if (pinctls & AC_PINCTL_IN_EN)
340 snd_iprintf(buffer, " IN");
341 if (pinctls & AC_PINCTL_OUT_EN)
342 snd_iprintf(buffer, " OUT");
343 if (pinctls & AC_PINCTL_HP_EN)
344 snd_iprintf(buffer, " HP");
345 snd_iprintf(buffer, "\n");
Takashi Iwaie97a5162007-10-26 14:56:36 +0200346 break;
347 case AC_WID_VOL_KNB:
Takashi Iwaie1716132007-11-16 17:52:39 +0100348 pinctls = snd_hda_param_read(codec, nid,
349 AC_PAR_VOL_KNB_CAP);
350 snd_iprintf(buffer, " Volume-Knob: delta=%d, "
351 "steps=%d, ",
352 (pinctls >> 7) & 1, pinctls & 0x7f);
353 pinctls = snd_hda_codec_read(codec, nid, 0,
354 AC_VERB_GET_VOLUME_KNOB_CONTROL, 0);
355 snd_iprintf(buffer, "direct=%d, val=%d\n",
356 (pinctls >> 7) & 1, pinctls & 0x7f);
Takashi Iwaie97a5162007-10-26 14:56:36 +0200357 break;
358 case AC_WID_AUD_OUT:
359 case AC_WID_AUD_IN:
360 if (wid_caps & AC_WCAP_FORMAT_OVRD) {
361 snd_iprintf(buffer, " PCM:\n");
362 print_pcm_caps(buffer, codec, nid);
363 }
364 break;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700365 }
366
Takashi Iwaib7027cc2005-11-02 18:13:41 +0100367 if (wid_caps & AC_WCAP_POWER)
368 snd_iprintf(buffer, " Power: 0x%x\n",
369 snd_hda_codec_read(codec, nid, 0,
Takashi Iwaid01ce992007-07-27 16:52:19 +0200370 AC_VERB_GET_POWER_STATE,
371 0));
Takashi Iwaib7027cc2005-11-02 18:13:41 +0100372
Linus Torvalds1da177e2005-04-16 15:20:36 -0700373 if (wid_caps & AC_WCAP_CONN_LIST) {
Takashi Iwai3e289f12005-06-10 19:45:09 +0200374 int c, curr = -1;
ChenLi Tienc3010982005-03-24 12:02:54 +0100375 if (conn_len > 1 && wid_type != AC_WID_AUD_MIX)
376 curr = snd_hda_codec_read(codec, nid, 0,
377 AC_VERB_GET_CONNECT_SEL, 0);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700378 snd_iprintf(buffer, " Connection: %d\n", conn_len);
Takashi Iwaie1716132007-11-16 17:52:39 +0100379 if (conn_len > 0) {
380 snd_iprintf(buffer, " ");
381 for (c = 0; c < conn_len; c++) {
382 snd_iprintf(buffer, " 0x%02x", conn[c]);
383 if (c == curr)
384 snd_iprintf(buffer, "*");
385 }
386 snd_iprintf(buffer, "\n");
ChenLi Tienc3010982005-03-24 12:02:54 +0100387 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700388 }
389 }
Takashi Iwaicb53c622007-08-10 17:21:45 +0200390 snd_hda_power_down(codec);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700391}
392
393/*
394 * create a proc read
395 */
396int snd_hda_codec_proc_new(struct hda_codec *codec)
397{
398 char name[32];
Takashi Iwaic8b6bf9b2005-11-17 14:57:47 +0100399 struct snd_info_entry *entry;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700400 int err;
401
402 snprintf(name, sizeof(name), "codec#%d", codec->addr);
403 err = snd_card_proc_new(codec->bus->card, name, &entry);
404 if (err < 0)
405 return err;
406
Takashi Iwaibf850202006-04-28 15:13:41 +0200407 snd_info_set_text_ops(entry, codec, print_codec_info);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700408 return 0;
409}
410