diff options
Diffstat (limited to 'sound/pci/hda/hda_hwdep.c')
-rw-r--r-- | sound/pci/hda/hda_hwdep.c | 255 |
1 files changed, 227 insertions, 28 deletions
diff --git a/sound/pci/hda/hda_hwdep.c b/sound/pci/hda/hda_hwdep.c index 300ab407cf42..1c57505c2874 100644 --- a/sound/pci/hda/hda_hwdep.c +++ b/sound/pci/hda/hda_hwdep.c | |||
@@ -30,6 +30,12 @@ | |||
30 | #include <sound/hda_hwdep.h> | 30 | #include <sound/hda_hwdep.h> |
31 | #include <sound/minors.h> | 31 | #include <sound/minors.h> |
32 | 32 | ||
33 | /* hint string pair */ | ||
34 | struct hda_hint { | ||
35 | const char *key; | ||
36 | const char *val; /* contained in the same alloc as key */ | ||
37 | }; | ||
38 | |||
33 | /* | 39 | /* |
34 | * write/read an out-of-bound verb | 40 | * write/read an out-of-bound verb |
35 | */ | 41 | */ |
@@ -99,16 +105,17 @@ static int hda_hwdep_open(struct snd_hwdep *hw, struct file *file) | |||
99 | 105 | ||
100 | static void clear_hwdep_elements(struct hda_codec *codec) | 106 | static void clear_hwdep_elements(struct hda_codec *codec) |
101 | { | 107 | { |
102 | char **head; | ||
103 | int i; | 108 | int i; |
104 | 109 | ||
105 | /* clear init verbs */ | 110 | /* clear init verbs */ |
106 | snd_array_free(&codec->init_verbs); | 111 | snd_array_free(&codec->init_verbs); |
107 | /* clear hints */ | 112 | /* clear hints */ |
108 | head = codec->hints.list; | 113 | for (i = 0; i < codec->hints.used; i++) { |
109 | for (i = 0; i < codec->hints.used; i++, head++) | 114 | struct hda_hint *hint = snd_array_elem(&codec->hints, i); |
110 | kfree(*head); | 115 | kfree(hint->key); /* we don't need to free hint->val */ |
116 | } | ||
111 | snd_array_free(&codec->hints); | 117 | snd_array_free(&codec->hints); |
118 | snd_array_free(&codec->user_pins); | ||
112 | } | 119 | } |
113 | 120 | ||
114 | static void hwdep_free(struct snd_hwdep *hwdep) | 121 | static void hwdep_free(struct snd_hwdep *hwdep) |
@@ -140,7 +147,8 @@ int /*__devinit*/ snd_hda_create_hwdep(struct hda_codec *codec) | |||
140 | #endif | 147 | #endif |
141 | 148 | ||
142 | snd_array_init(&codec->init_verbs, sizeof(struct hda_verb), 32); | 149 | snd_array_init(&codec->init_verbs, sizeof(struct hda_verb), 32); |
143 | snd_array_init(&codec->hints, sizeof(char *), 32); | 150 | snd_array_init(&codec->hints, sizeof(struct hda_hint), 32); |
151 | snd_array_init(&codec->user_pins, sizeof(struct hda_pincfg), 16); | ||
144 | 152 | ||
145 | return 0; | 153 | return 0; |
146 | } | 154 | } |
@@ -153,7 +161,13 @@ int /*__devinit*/ snd_hda_create_hwdep(struct hda_codec *codec) | |||
153 | 161 | ||
154 | static int clear_codec(struct hda_codec *codec) | 162 | static int clear_codec(struct hda_codec *codec) |
155 | { | 163 | { |
156 | snd_hda_codec_reset(codec); | 164 | int err; |
165 | |||
166 | err = snd_hda_codec_reset(codec); | ||
167 | if (err < 0) { | ||
168 | snd_printk(KERN_ERR "The codec is being used, can't free.\n"); | ||
169 | return err; | ||
170 | } | ||
157 | clear_hwdep_elements(codec); | 171 | clear_hwdep_elements(codec); |
158 | return 0; | 172 | return 0; |
159 | } | 173 | } |
@@ -162,20 +176,29 @@ static int reconfig_codec(struct hda_codec *codec) | |||
162 | { | 176 | { |
163 | int err; | 177 | int err; |
164 | 178 | ||
179 | snd_hda_power_up(codec); | ||
165 | snd_printk(KERN_INFO "hda-codec: reconfiguring\n"); | 180 | snd_printk(KERN_INFO "hda-codec: reconfiguring\n"); |
166 | snd_hda_codec_reset(codec); | 181 | err = snd_hda_codec_reset(codec); |
182 | if (err < 0) { | ||
183 | snd_printk(KERN_ERR | ||
184 | "The codec is being used, can't reconfigure.\n"); | ||
185 | goto error; | ||
186 | } | ||
167 | err = snd_hda_codec_configure(codec); | 187 | err = snd_hda_codec_configure(codec); |
168 | if (err < 0) | 188 | if (err < 0) |
169 | return err; | 189 | goto error; |
170 | /* rebuild PCMs */ | 190 | /* rebuild PCMs */ |
171 | err = snd_hda_codec_build_pcms(codec); | 191 | err = snd_hda_codec_build_pcms(codec); |
172 | if (err < 0) | 192 | if (err < 0) |
173 | return err; | 193 | goto error; |
174 | /* rebuild mixers */ | 194 | /* rebuild mixers */ |
175 | err = snd_hda_codec_build_controls(codec); | 195 | err = snd_hda_codec_build_controls(codec); |
176 | if (err < 0) | 196 | if (err < 0) |
177 | return err; | 197 | goto error; |
178 | return 0; | 198 | err = snd_card_register(codec->bus->card); |
199 | error: | ||
200 | snd_hda_power_down(codec); | ||
201 | return err; | ||
179 | } | 202 | } |
180 | 203 | ||
181 | /* | 204 | /* |
@@ -271,47 +294,195 @@ static ssize_t type##_store(struct device *dev, \ | |||
271 | CODEC_ACTION_STORE(reconfig); | 294 | CODEC_ACTION_STORE(reconfig); |
272 | CODEC_ACTION_STORE(clear); | 295 | CODEC_ACTION_STORE(clear); |
273 | 296 | ||
297 | static ssize_t init_verbs_show(struct device *dev, | ||
298 | struct device_attribute *attr, | ||
299 | char *buf) | ||
300 | { | ||
301 | struct snd_hwdep *hwdep = dev_get_drvdata(dev); | ||
302 | struct hda_codec *codec = hwdep->private_data; | ||
303 | int i, len = 0; | ||
304 | for (i = 0; i < codec->init_verbs.used; i++) { | ||
305 | struct hda_verb *v = snd_array_elem(&codec->init_verbs, i); | ||
306 | len += snprintf(buf + len, PAGE_SIZE - len, | ||
307 | "0x%02x 0x%03x 0x%04x\n", | ||
308 | v->nid, v->verb, v->param); | ||
309 | } | ||
310 | return len; | ||
311 | } | ||
312 | |||
274 | static ssize_t init_verbs_store(struct device *dev, | 313 | static ssize_t init_verbs_store(struct device *dev, |
275 | struct device_attribute *attr, | 314 | struct device_attribute *attr, |
276 | const char *buf, size_t count) | 315 | const char *buf, size_t count) |
277 | { | 316 | { |
278 | struct snd_hwdep *hwdep = dev_get_drvdata(dev); | 317 | struct snd_hwdep *hwdep = dev_get_drvdata(dev); |
279 | struct hda_codec *codec = hwdep->private_data; | 318 | struct hda_codec *codec = hwdep->private_data; |
280 | char *p; | 319 | struct hda_verb *v; |
281 | struct hda_verb verb, *v; | 320 | int nid, verb, param; |
282 | 321 | ||
283 | verb.nid = simple_strtoul(buf, &p, 0); | 322 | if (sscanf(buf, "%i %i %i", &nid, &verb, ¶m) != 3) |
284 | verb.verb = simple_strtoul(p, &p, 0); | 323 | return -EINVAL; |
285 | verb.param = simple_strtoul(p, &p, 0); | 324 | if (!nid || !verb) |
286 | if (!verb.nid || !verb.verb || !verb.param) | ||
287 | return -EINVAL; | 325 | return -EINVAL; |
288 | v = snd_array_new(&codec->init_verbs); | 326 | v = snd_array_new(&codec->init_verbs); |
289 | if (!v) | 327 | if (!v) |
290 | return -ENOMEM; | 328 | return -ENOMEM; |
291 | *v = verb; | 329 | v->nid = nid; |
330 | v->verb = verb; | ||
331 | v->param = param; | ||
292 | return count; | 332 | return count; |
293 | } | 333 | } |
294 | 334 | ||
335 | static ssize_t hints_show(struct device *dev, | ||
336 | struct device_attribute *attr, | ||
337 | char *buf) | ||
338 | { | ||
339 | struct snd_hwdep *hwdep = dev_get_drvdata(dev); | ||
340 | struct hda_codec *codec = hwdep->private_data; | ||
341 | int i, len = 0; | ||
342 | for (i = 0; i < codec->hints.used; i++) { | ||
343 | struct hda_hint *hint = snd_array_elem(&codec->hints, i); | ||
344 | len += snprintf(buf + len, PAGE_SIZE - len, | ||
345 | "%s = %s\n", hint->key, hint->val); | ||
346 | } | ||
347 | return len; | ||
348 | } | ||
349 | |||
350 | static struct hda_hint *get_hint(struct hda_codec *codec, const char *key) | ||
351 | { | ||
352 | int i; | ||
353 | |||
354 | for (i = 0; i < codec->hints.used; i++) { | ||
355 | struct hda_hint *hint = snd_array_elem(&codec->hints, i); | ||
356 | if (!strcmp(hint->key, key)) | ||
357 | return hint; | ||
358 | } | ||
359 | return NULL; | ||
360 | } | ||
361 | |||
362 | static void remove_trail_spaces(char *str) | ||
363 | { | ||
364 | char *p; | ||
365 | if (!*str) | ||
366 | return; | ||
367 | p = str + strlen(str) - 1; | ||
368 | for (; isspace(*p); p--) { | ||
369 | *p = 0; | ||
370 | if (p == str) | ||
371 | return; | ||
372 | } | ||
373 | } | ||
374 | |||
375 | #define MAX_HINTS 1024 | ||
376 | |||
295 | static ssize_t hints_store(struct device *dev, | 377 | static ssize_t hints_store(struct device *dev, |
296 | struct device_attribute *attr, | 378 | struct device_attribute *attr, |
297 | const char *buf, size_t count) | 379 | const char *buf, size_t count) |
298 | { | 380 | { |
299 | struct snd_hwdep *hwdep = dev_get_drvdata(dev); | 381 | struct snd_hwdep *hwdep = dev_get_drvdata(dev); |
300 | struct hda_codec *codec = hwdep->private_data; | 382 | struct hda_codec *codec = hwdep->private_data; |
301 | char *p; | 383 | char *key, *val; |
302 | char **hint; | 384 | struct hda_hint *hint; |
303 | 385 | ||
304 | if (!*buf || isspace(*buf) || *buf == '#' || *buf == '\n') | 386 | while (isspace(*buf)) |
387 | buf++; | ||
388 | if (!*buf || *buf == '#' || *buf == '\n') | ||
305 | return count; | 389 | return count; |
306 | p = kstrndup_noeol(buf, 1024); | 390 | if (*buf == '=') |
307 | if (!p) | 391 | return -EINVAL; |
392 | key = kstrndup_noeol(buf, 1024); | ||
393 | if (!key) | ||
308 | return -ENOMEM; | 394 | return -ENOMEM; |
309 | hint = snd_array_new(&codec->hints); | 395 | /* extract key and val */ |
396 | val = strchr(key, '='); | ||
397 | if (!val) { | ||
398 | kfree(key); | ||
399 | return -EINVAL; | ||
400 | } | ||
401 | *val++ = 0; | ||
402 | while (isspace(*val)) | ||
403 | val++; | ||
404 | remove_trail_spaces(key); | ||
405 | remove_trail_spaces(val); | ||
406 | hint = get_hint(codec, key); | ||
407 | if (hint) { | ||
408 | /* replace */ | ||
409 | kfree(hint->key); | ||
410 | hint->key = key; | ||
411 | hint->val = val; | ||
412 | return count; | ||
413 | } | ||
414 | /* allocate a new hint entry */ | ||
415 | if (codec->hints.used >= MAX_HINTS) | ||
416 | hint = NULL; | ||
417 | else | ||
418 | hint = snd_array_new(&codec->hints); | ||
310 | if (!hint) { | 419 | if (!hint) { |
311 | kfree(p); | 420 | kfree(key); |
312 | return -ENOMEM; | 421 | return -ENOMEM; |
313 | } | 422 | } |
314 | *hint = p; | 423 | hint->key = key; |
424 | hint->val = val; | ||
425 | return count; | ||
426 | } | ||
427 | |||
428 | static ssize_t pin_configs_show(struct hda_codec *codec, | ||
429 | struct snd_array *list, | ||
430 | char *buf) | ||
431 | { | ||
432 | int i, len = 0; | ||
433 | for (i = 0; i < list->used; i++) { | ||
434 | struct hda_pincfg *pin = snd_array_elem(list, i); | ||
435 | len += sprintf(buf + len, "0x%02x 0x%08x\n", | ||
436 | pin->nid, pin->cfg); | ||
437 | } | ||
438 | return len; | ||
439 | } | ||
440 | |||
441 | static ssize_t init_pin_configs_show(struct device *dev, | ||
442 | struct device_attribute *attr, | ||
443 | char *buf) | ||
444 | { | ||
445 | struct snd_hwdep *hwdep = dev_get_drvdata(dev); | ||
446 | struct hda_codec *codec = hwdep->private_data; | ||
447 | return pin_configs_show(codec, &codec->init_pins, buf); | ||
448 | } | ||
449 | |||
450 | static ssize_t user_pin_configs_show(struct device *dev, | ||
451 | struct device_attribute *attr, | ||
452 | char *buf) | ||
453 | { | ||
454 | struct snd_hwdep *hwdep = dev_get_drvdata(dev); | ||
455 | struct hda_codec *codec = hwdep->private_data; | ||
456 | return pin_configs_show(codec, &codec->user_pins, buf); | ||
457 | } | ||
458 | |||
459 | static ssize_t driver_pin_configs_show(struct device *dev, | ||
460 | struct device_attribute *attr, | ||
461 | char *buf) | ||
462 | { | ||
463 | struct snd_hwdep *hwdep = dev_get_drvdata(dev); | ||
464 | struct hda_codec *codec = hwdep->private_data; | ||
465 | return pin_configs_show(codec, &codec->driver_pins, buf); | ||
466 | } | ||
467 | |||
468 | #define MAX_PIN_CONFIGS 32 | ||
469 | |||
470 | static ssize_t user_pin_configs_store(struct device *dev, | ||
471 | struct device_attribute *attr, | ||
472 | const char *buf, size_t count) | ||
473 | { | ||
474 | struct snd_hwdep *hwdep = dev_get_drvdata(dev); | ||
475 | struct hda_codec *codec = hwdep->private_data; | ||
476 | int nid, cfg; | ||
477 | int err; | ||
478 | |||
479 | if (sscanf(buf, "%i %i", &nid, &cfg) != 2) | ||
480 | return -EINVAL; | ||
481 | if (!nid) | ||
482 | return -EINVAL; | ||
483 | err = snd_hda_add_pincfg(codec, &codec->user_pins, nid, cfg); | ||
484 | if (err < 0) | ||
485 | return err; | ||
315 | return count; | 486 | return count; |
316 | } | 487 | } |
317 | 488 | ||
@@ -330,8 +501,11 @@ static struct device_attribute codec_attrs[] = { | |||
330 | CODEC_ATTR_RO(mfg), | 501 | CODEC_ATTR_RO(mfg), |
331 | CODEC_ATTR_RW(name), | 502 | CODEC_ATTR_RW(name), |
332 | CODEC_ATTR_RW(modelname), | 503 | CODEC_ATTR_RW(modelname), |
333 | CODEC_ATTR_WO(init_verbs), | 504 | CODEC_ATTR_RW(init_verbs), |
334 | CODEC_ATTR_WO(hints), | 505 | CODEC_ATTR_RW(hints), |
506 | CODEC_ATTR_RO(init_pin_configs), | ||
507 | CODEC_ATTR_RW(user_pin_configs), | ||
508 | CODEC_ATTR_RO(driver_pin_configs), | ||
335 | CODEC_ATTR_WO(reconfig), | 509 | CODEC_ATTR_WO(reconfig), |
336 | CODEC_ATTR_WO(clear), | 510 | CODEC_ATTR_WO(clear), |
337 | }; | 511 | }; |
@@ -350,4 +524,29 @@ int snd_hda_hwdep_add_sysfs(struct hda_codec *codec) | |||
350 | return 0; | 524 | return 0; |
351 | } | 525 | } |
352 | 526 | ||
527 | /* | ||
528 | * Look for hint string | ||
529 | */ | ||
530 | const char *snd_hda_get_hint(struct hda_codec *codec, const char *key) | ||
531 | { | ||
532 | struct hda_hint *hint = get_hint(codec, key); | ||
533 | return hint ? hint->val : NULL; | ||
534 | } | ||
535 | EXPORT_SYMBOL_HDA(snd_hda_get_hint); | ||
536 | |||
537 | int snd_hda_get_bool_hint(struct hda_codec *codec, const char *key) | ||
538 | { | ||
539 | const char *p = snd_hda_get_hint(codec, key); | ||
540 | if (!p || !*p) | ||
541 | return -ENOENT; | ||
542 | switch (toupper(*p)) { | ||
543 | case 'T': /* true */ | ||
544 | case 'Y': /* yes */ | ||
545 | case '1': | ||
546 | return 1; | ||
547 | } | ||
548 | return 0; | ||
549 | } | ||
550 | EXPORT_SYMBOL_HDA(snd_hda_get_bool_hint); | ||
551 | |||
353 | #endif /* CONFIG_SND_HDA_RECONFIG */ | 552 | #endif /* CONFIG_SND_HDA_RECONFIG */ |