diff options
Diffstat (limited to 'sound/pci/hda/hda_codec.c')
-rw-r--r-- | sound/pci/hda/hda_codec.c | 192 |
1 files changed, 184 insertions, 8 deletions
diff --git a/sound/pci/hda/hda_codec.c b/sound/pci/hda/hda_codec.c index 684307372d73..7a8fcc4c15f8 100644 --- a/sound/pci/hda/hda_codec.c +++ b/sound/pci/hda/hda_codec.c | |||
@@ -19,6 +19,7 @@ | |||
19 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | 19 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
20 | */ | 20 | */ |
21 | 21 | ||
22 | #include <linux/mm.h> | ||
22 | #include <linux/init.h> | 23 | #include <linux/init.h> |
23 | #include <linux/delay.h> | 24 | #include <linux/delay.h> |
24 | #include <linux/slab.h> | 25 | #include <linux/slab.h> |
@@ -2304,7 +2305,7 @@ typedef int (*map_slave_func_t)(void *, struct snd_kcontrol *); | |||
2304 | 2305 | ||
2305 | /* apply the function to all matching slave ctls in the mixer list */ | 2306 | /* apply the function to all matching slave ctls in the mixer list */ |
2306 | static int map_slaves(struct hda_codec *codec, const char * const *slaves, | 2307 | static int map_slaves(struct hda_codec *codec, const char * const *slaves, |
2307 | map_slave_func_t func, void *data) | 2308 | const char *suffix, map_slave_func_t func, void *data) |
2308 | { | 2309 | { |
2309 | struct hda_nid_item *items; | 2310 | struct hda_nid_item *items; |
2310 | const char * const *s; | 2311 | const char * const *s; |
@@ -2317,7 +2318,14 @@ static int map_slaves(struct hda_codec *codec, const char * const *slaves, | |||
2317 | sctl->id.iface != SNDRV_CTL_ELEM_IFACE_MIXER) | 2318 | sctl->id.iface != SNDRV_CTL_ELEM_IFACE_MIXER) |
2318 | continue; | 2319 | continue; |
2319 | for (s = slaves; *s; s++) { | 2320 | for (s = slaves; *s; s++) { |
2320 | if (!strcmp(sctl->id.name, *s)) { | 2321 | char tmpname[sizeof(sctl->id.name)]; |
2322 | const char *name = *s; | ||
2323 | if (suffix) { | ||
2324 | snprintf(tmpname, sizeof(tmpname), "%s %s", | ||
2325 | name, suffix); | ||
2326 | name = tmpname; | ||
2327 | } | ||
2328 | if (!strcmp(sctl->id.name, name)) { | ||
2321 | err = func(data, sctl); | 2329 | err = func(data, sctl); |
2322 | if (err) | 2330 | if (err) |
2323 | return err; | 2331 | return err; |
@@ -2333,12 +2341,65 @@ static int check_slave_present(void *data, struct snd_kcontrol *sctl) | |||
2333 | return 1; | 2341 | return 1; |
2334 | } | 2342 | } |
2335 | 2343 | ||
2344 | /* guess the value corresponding to 0dB */ | ||
2345 | static int get_kctl_0dB_offset(struct snd_kcontrol *kctl) | ||
2346 | { | ||
2347 | int _tlv[4]; | ||
2348 | const int *tlv = NULL; | ||
2349 | int val = -1; | ||
2350 | |||
2351 | if (kctl->vd[0].access & SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK) { | ||
2352 | /* FIXME: set_fs() hack for obtaining user-space TLV data */ | ||
2353 | mm_segment_t fs = get_fs(); | ||
2354 | set_fs(get_ds()); | ||
2355 | if (!kctl->tlv.c(kctl, 0, sizeof(_tlv), _tlv)) | ||
2356 | tlv = _tlv; | ||
2357 | set_fs(fs); | ||
2358 | } else if (kctl->vd[0].access & SNDRV_CTL_ELEM_ACCESS_TLV_READ) | ||
2359 | tlv = kctl->tlv.p; | ||
2360 | if (tlv && tlv[0] == SNDRV_CTL_TLVT_DB_SCALE) | ||
2361 | val = -tlv[2] / tlv[3]; | ||
2362 | return val; | ||
2363 | } | ||
2364 | |||
2365 | /* call kctl->put with the given value(s) */ | ||
2366 | static int put_kctl_with_value(struct snd_kcontrol *kctl, int val) | ||
2367 | { | ||
2368 | struct snd_ctl_elem_value *ucontrol; | ||
2369 | ucontrol = kzalloc(sizeof(*ucontrol), GFP_KERNEL); | ||
2370 | if (!ucontrol) | ||
2371 | return -ENOMEM; | ||
2372 | ucontrol->value.integer.value[0] = val; | ||
2373 | ucontrol->value.integer.value[1] = val; | ||
2374 | kctl->put(kctl, ucontrol); | ||
2375 | kfree(ucontrol); | ||
2376 | return 0; | ||
2377 | } | ||
2378 | |||
2379 | /* initialize the slave volume with 0dB */ | ||
2380 | static int init_slave_0dB(void *data, struct snd_kcontrol *slave) | ||
2381 | { | ||
2382 | int offset = get_kctl_0dB_offset(slave); | ||
2383 | if (offset > 0) | ||
2384 | put_kctl_with_value(slave, offset); | ||
2385 | return 0; | ||
2386 | } | ||
2387 | |||
2388 | /* unmute the slave */ | ||
2389 | static int init_slave_unmute(void *data, struct snd_kcontrol *slave) | ||
2390 | { | ||
2391 | return put_kctl_with_value(slave, 1); | ||
2392 | } | ||
2393 | |||
2336 | /** | 2394 | /** |
2337 | * snd_hda_add_vmaster - create a virtual master control and add slaves | 2395 | * snd_hda_add_vmaster - create a virtual master control and add slaves |
2338 | * @codec: HD-audio codec | 2396 | * @codec: HD-audio codec |
2339 | * @name: vmaster control name | 2397 | * @name: vmaster control name |
2340 | * @tlv: TLV data (optional) | 2398 | * @tlv: TLV data (optional) |
2341 | * @slaves: slave control names (optional) | 2399 | * @slaves: slave control names (optional) |
2400 | * @suffix: suffix string to each slave name (optional) | ||
2401 | * @init_slave_vol: initialize slaves to unmute/0dB | ||
2402 | * @ctl_ret: store the vmaster kcontrol in return | ||
2342 | * | 2403 | * |
2343 | * Create a virtual master control with the given name. The TLV data | 2404 | * Create a virtual master control with the given name. The TLV data |
2344 | * must be either NULL or a valid data. | 2405 | * must be either NULL or a valid data. |
@@ -2349,13 +2410,18 @@ static int check_slave_present(void *data, struct snd_kcontrol *sctl) | |||
2349 | * | 2410 | * |
2350 | * This function returns zero if successful or a negative error code. | 2411 | * This function returns zero if successful or a negative error code. |
2351 | */ | 2412 | */ |
2352 | int snd_hda_add_vmaster(struct hda_codec *codec, char *name, | 2413 | int __snd_hda_add_vmaster(struct hda_codec *codec, char *name, |
2353 | unsigned int *tlv, const char * const *slaves) | 2414 | unsigned int *tlv, const char * const *slaves, |
2415 | const char *suffix, bool init_slave_vol, | ||
2416 | struct snd_kcontrol **ctl_ret) | ||
2354 | { | 2417 | { |
2355 | struct snd_kcontrol *kctl; | 2418 | struct snd_kcontrol *kctl; |
2356 | int err; | 2419 | int err; |
2357 | 2420 | ||
2358 | err = map_slaves(codec, slaves, check_slave_present, NULL); | 2421 | if (ctl_ret) |
2422 | *ctl_ret = NULL; | ||
2423 | |||
2424 | err = map_slaves(codec, slaves, suffix, check_slave_present, NULL); | ||
2359 | if (err != 1) { | 2425 | if (err != 1) { |
2360 | snd_printdd("No slave found for %s\n", name); | 2426 | snd_printdd("No slave found for %s\n", name); |
2361 | return 0; | 2427 | return 0; |
@@ -2367,13 +2433,119 @@ int snd_hda_add_vmaster(struct hda_codec *codec, char *name, | |||
2367 | if (err < 0) | 2433 | if (err < 0) |
2368 | return err; | 2434 | return err; |
2369 | 2435 | ||
2370 | err = map_slaves(codec, slaves, (map_slave_func_t)snd_ctl_add_slave, | 2436 | err = map_slaves(codec, slaves, suffix, |
2371 | kctl); | 2437 | (map_slave_func_t)snd_ctl_add_slave, kctl); |
2372 | if (err < 0) | 2438 | if (err < 0) |
2373 | return err; | 2439 | return err; |
2440 | |||
2441 | /* init with master mute & zero volume */ | ||
2442 | put_kctl_with_value(kctl, 0); | ||
2443 | if (init_slave_vol) | ||
2444 | map_slaves(codec, slaves, suffix, | ||
2445 | tlv ? init_slave_0dB : init_slave_unmute, kctl); | ||
2446 | |||
2447 | if (ctl_ret) | ||
2448 | *ctl_ret = kctl; | ||
2374 | return 0; | 2449 | return 0; |
2375 | } | 2450 | } |
2376 | EXPORT_SYMBOL_HDA(snd_hda_add_vmaster); | 2451 | EXPORT_SYMBOL_HDA(__snd_hda_add_vmaster); |
2452 | |||
2453 | /* | ||
2454 | * mute-LED control using vmaster | ||
2455 | */ | ||
2456 | static int vmaster_mute_mode_info(struct snd_kcontrol *kcontrol, | ||
2457 | struct snd_ctl_elem_info *uinfo) | ||
2458 | { | ||
2459 | static const char * const texts[] = { | ||
2460 | "Off", "On", "Follow Master" | ||
2461 | }; | ||
2462 | unsigned int index; | ||
2463 | |||
2464 | uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; | ||
2465 | uinfo->count = 1; | ||
2466 | uinfo->value.enumerated.items = 3; | ||
2467 | index = uinfo->value.enumerated.item; | ||
2468 | if (index >= 3) | ||
2469 | index = 2; | ||
2470 | strcpy(uinfo->value.enumerated.name, texts[index]); | ||
2471 | return 0; | ||
2472 | } | ||
2473 | |||
2474 | static int vmaster_mute_mode_get(struct snd_kcontrol *kcontrol, | ||
2475 | struct snd_ctl_elem_value *ucontrol) | ||
2476 | { | ||
2477 | struct hda_vmaster_mute_hook *hook = snd_kcontrol_chip(kcontrol); | ||
2478 | ucontrol->value.enumerated.item[0] = hook->mute_mode; | ||
2479 | return 0; | ||
2480 | } | ||
2481 | |||
2482 | static int vmaster_mute_mode_put(struct snd_kcontrol *kcontrol, | ||
2483 | struct snd_ctl_elem_value *ucontrol) | ||
2484 | { | ||
2485 | struct hda_vmaster_mute_hook *hook = snd_kcontrol_chip(kcontrol); | ||
2486 | unsigned int old_mode = hook->mute_mode; | ||
2487 | |||
2488 | hook->mute_mode = ucontrol->value.enumerated.item[0]; | ||
2489 | if (hook->mute_mode > HDA_VMUTE_FOLLOW_MASTER) | ||
2490 | hook->mute_mode = HDA_VMUTE_FOLLOW_MASTER; | ||
2491 | if (old_mode == hook->mute_mode) | ||
2492 | return 0; | ||
2493 | snd_hda_sync_vmaster_hook(hook); | ||
2494 | return 1; | ||
2495 | } | ||
2496 | |||
2497 | static struct snd_kcontrol_new vmaster_mute_mode = { | ||
2498 | .iface = SNDRV_CTL_ELEM_IFACE_MIXER, | ||
2499 | .name = "Mute-LED Mode", | ||
2500 | .info = vmaster_mute_mode_info, | ||
2501 | .get = vmaster_mute_mode_get, | ||
2502 | .put = vmaster_mute_mode_put, | ||
2503 | }; | ||
2504 | |||
2505 | /* | ||
2506 | * Add a mute-LED hook with the given vmaster switch kctl | ||
2507 | * "Mute-LED Mode" control is automatically created and associated with | ||
2508 | * the given hook. | ||
2509 | */ | ||
2510 | int snd_hda_add_vmaster_hook(struct hda_codec *codec, | ||
2511 | struct hda_vmaster_mute_hook *hook, | ||
2512 | bool expose_enum_ctl) | ||
2513 | { | ||
2514 | struct snd_kcontrol *kctl; | ||
2515 | |||
2516 | if (!hook->hook || !hook->sw_kctl) | ||
2517 | return 0; | ||
2518 | snd_ctl_add_vmaster_hook(hook->sw_kctl, hook->hook, codec); | ||
2519 | hook->codec = codec; | ||
2520 | hook->mute_mode = HDA_VMUTE_FOLLOW_MASTER; | ||
2521 | if (!expose_enum_ctl) | ||
2522 | return 0; | ||
2523 | kctl = snd_ctl_new1(&vmaster_mute_mode, hook); | ||
2524 | if (!kctl) | ||
2525 | return -ENOMEM; | ||
2526 | return snd_hda_ctl_add(codec, 0, kctl); | ||
2527 | } | ||
2528 | EXPORT_SYMBOL_HDA(snd_hda_add_vmaster_hook); | ||
2529 | |||
2530 | /* | ||
2531 | * Call the hook with the current value for synchronization | ||
2532 | * Should be called in init callback | ||
2533 | */ | ||
2534 | void snd_hda_sync_vmaster_hook(struct hda_vmaster_mute_hook *hook) | ||
2535 | { | ||
2536 | if (!hook->hook || !hook->codec) | ||
2537 | return; | ||
2538 | switch (hook->mute_mode) { | ||
2539 | case HDA_VMUTE_FOLLOW_MASTER: | ||
2540 | snd_ctl_sync_vmaster_hook(hook->sw_kctl); | ||
2541 | break; | ||
2542 | default: | ||
2543 | hook->hook(hook->codec, hook->mute_mode); | ||
2544 | break; | ||
2545 | } | ||
2546 | } | ||
2547 | EXPORT_SYMBOL_HDA(snd_hda_sync_vmaster_hook); | ||
2548 | |||
2377 | 2549 | ||
2378 | /** | 2550 | /** |
2379 | * snd_hda_mixer_amp_switch_info - Info callback for a standard AMP mixer switch | 2551 | * snd_hda_mixer_amp_switch_info - Info callback for a standard AMP mixer switch |
@@ -5272,6 +5444,10 @@ int snd_hda_suspend(struct hda_bus *bus) | |||
5272 | list_for_each_entry(codec, &bus->codec_list, list) { | 5444 | list_for_each_entry(codec, &bus->codec_list, list) { |
5273 | if (hda_codec_is_power_on(codec)) | 5445 | if (hda_codec_is_power_on(codec)) |
5274 | hda_call_codec_suspend(codec); | 5446 | hda_call_codec_suspend(codec); |
5447 | else /* forcibly change the power to D3 even if not used */ | ||
5448 | hda_set_power_state(codec, | ||
5449 | codec->afg ? codec->afg : codec->mfg, | ||
5450 | AC_PWRST_D3); | ||
5275 | if (codec->patch_ops.post_suspend) | 5451 | if (codec->patch_ops.post_suspend) |
5276 | codec->patch_ops.post_suspend(codec); | 5452 | codec->patch_ops.post_suspend(codec); |
5277 | } | 5453 | } |