diff options
Diffstat (limited to 'sound/soc/soc-dapm.c')
-rw-r--r-- | sound/soc/soc-dapm.c | 499 |
1 files changed, 408 insertions, 91 deletions
diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c index 21c69074aa17..f79711b9fa5b 100644 --- a/sound/soc/soc-dapm.c +++ b/sound/soc/soc-dapm.c | |||
@@ -37,6 +37,7 @@ | |||
37 | #include <linux/bitops.h> | 37 | #include <linux/bitops.h> |
38 | #include <linux/platform_device.h> | 38 | #include <linux/platform_device.h> |
39 | #include <linux/jiffies.h> | 39 | #include <linux/jiffies.h> |
40 | #include <linux/debugfs.h> | ||
40 | #include <sound/core.h> | 41 | #include <sound/core.h> |
41 | #include <sound/pcm.h> | 42 | #include <sound/pcm.h> |
42 | #include <sound/pcm_params.h> | 43 | #include <sound/pcm_params.h> |
@@ -52,19 +53,41 @@ | |||
52 | 53 | ||
53 | /* dapm power sequences - make this per codec in the future */ | 54 | /* dapm power sequences - make this per codec in the future */ |
54 | static int dapm_up_seq[] = { | 55 | static int dapm_up_seq[] = { |
55 | snd_soc_dapm_pre, snd_soc_dapm_supply, snd_soc_dapm_micbias, | 56 | [snd_soc_dapm_pre] = 0, |
56 | snd_soc_dapm_mic, snd_soc_dapm_mux, snd_soc_dapm_value_mux, | 57 | [snd_soc_dapm_supply] = 1, |
57 | snd_soc_dapm_dac, snd_soc_dapm_mixer, snd_soc_dapm_mixer_named_ctl, | 58 | [snd_soc_dapm_micbias] = 2, |
58 | snd_soc_dapm_pga, snd_soc_dapm_adc, snd_soc_dapm_hp, snd_soc_dapm_spk, | 59 | [snd_soc_dapm_aif_in] = 3, |
59 | snd_soc_dapm_post | 60 | [snd_soc_dapm_aif_out] = 3, |
61 | [snd_soc_dapm_mic] = 4, | ||
62 | [snd_soc_dapm_mux] = 5, | ||
63 | [snd_soc_dapm_value_mux] = 5, | ||
64 | [snd_soc_dapm_dac] = 6, | ||
65 | [snd_soc_dapm_mixer] = 7, | ||
66 | [snd_soc_dapm_mixer_named_ctl] = 7, | ||
67 | [snd_soc_dapm_pga] = 8, | ||
68 | [snd_soc_dapm_adc] = 9, | ||
69 | [snd_soc_dapm_hp] = 10, | ||
70 | [snd_soc_dapm_spk] = 10, | ||
71 | [snd_soc_dapm_post] = 11, | ||
60 | }; | 72 | }; |
61 | 73 | ||
62 | static int dapm_down_seq[] = { | 74 | static int dapm_down_seq[] = { |
63 | snd_soc_dapm_pre, snd_soc_dapm_adc, snd_soc_dapm_hp, snd_soc_dapm_spk, | 75 | [snd_soc_dapm_pre] = 0, |
64 | snd_soc_dapm_pga, snd_soc_dapm_mixer_named_ctl, snd_soc_dapm_mixer, | 76 | [snd_soc_dapm_adc] = 1, |
65 | snd_soc_dapm_dac, snd_soc_dapm_mic, snd_soc_dapm_micbias, | 77 | [snd_soc_dapm_hp] = 2, |
66 | snd_soc_dapm_mux, snd_soc_dapm_value_mux, snd_soc_dapm_supply, | 78 | [snd_soc_dapm_spk] = 2, |
67 | snd_soc_dapm_post | 79 | [snd_soc_dapm_pga] = 4, |
80 | [snd_soc_dapm_mixer_named_ctl] = 5, | ||
81 | [snd_soc_dapm_mixer] = 5, | ||
82 | [snd_soc_dapm_dac] = 6, | ||
83 | [snd_soc_dapm_mic] = 7, | ||
84 | [snd_soc_dapm_micbias] = 8, | ||
85 | [snd_soc_dapm_mux] = 9, | ||
86 | [snd_soc_dapm_value_mux] = 9, | ||
87 | [snd_soc_dapm_aif_in] = 10, | ||
88 | [snd_soc_dapm_aif_out] = 10, | ||
89 | [snd_soc_dapm_supply] = 11, | ||
90 | [snd_soc_dapm_post] = 12, | ||
68 | }; | 91 | }; |
69 | 92 | ||
70 | static void pop_wait(u32 pop_time) | 93 | static void pop_wait(u32 pop_time) |
@@ -130,8 +153,12 @@ static int snd_soc_dapm_set_bias_level(struct snd_soc_device *socdev, | |||
130 | 153 | ||
131 | if (card->set_bias_level) | 154 | if (card->set_bias_level) |
132 | ret = card->set_bias_level(card, level); | 155 | ret = card->set_bias_level(card, level); |
133 | if (ret == 0 && codec->set_bias_level) | 156 | if (ret == 0) { |
134 | ret = codec->set_bias_level(codec, level); | 157 | if (codec->set_bias_level) |
158 | ret = codec->set_bias_level(codec, level); | ||
159 | else | ||
160 | codec->bias_level = level; | ||
161 | } | ||
135 | 162 | ||
136 | return ret; | 163 | return ret; |
137 | } | 164 | } |
@@ -206,6 +233,8 @@ static void dapm_set_path_status(struct snd_soc_dapm_widget *w, | |||
206 | case snd_soc_dapm_micbias: | 233 | case snd_soc_dapm_micbias: |
207 | case snd_soc_dapm_vmid: | 234 | case snd_soc_dapm_vmid: |
208 | case snd_soc_dapm_supply: | 235 | case snd_soc_dapm_supply: |
236 | case snd_soc_dapm_aif_in: | ||
237 | case snd_soc_dapm_aif_out: | ||
209 | p->connect = 1; | 238 | p->connect = 1; |
210 | break; | 239 | break; |
211 | /* does effect routing - dynamically connected */ | 240 | /* does effect routing - dynamically connected */ |
@@ -268,7 +297,7 @@ static int dapm_connect_mixer(struct snd_soc_codec *codec, | |||
268 | static int dapm_update_bits(struct snd_soc_dapm_widget *widget) | 297 | static int dapm_update_bits(struct snd_soc_dapm_widget *widget) |
269 | { | 298 | { |
270 | int change, power; | 299 | int change, power; |
271 | unsigned short old, new; | 300 | unsigned int old, new; |
272 | struct snd_soc_codec *codec = widget->codec; | 301 | struct snd_soc_codec *codec = widget->codec; |
273 | 302 | ||
274 | /* check for valid widgets */ | 303 | /* check for valid widgets */ |
@@ -479,8 +508,14 @@ static int is_connected_output_ep(struct snd_soc_dapm_widget *widget) | |||
479 | if (widget->id == snd_soc_dapm_supply) | 508 | if (widget->id == snd_soc_dapm_supply) |
480 | return 0; | 509 | return 0; |
481 | 510 | ||
482 | if (widget->id == snd_soc_dapm_adc && widget->active) | 511 | switch (widget->id) { |
483 | return 1; | 512 | case snd_soc_dapm_adc: |
513 | case snd_soc_dapm_aif_out: | ||
514 | if (widget->active) | ||
515 | return 1; | ||
516 | default: | ||
517 | break; | ||
518 | } | ||
484 | 519 | ||
485 | if (widget->connected) { | 520 | if (widget->connected) { |
486 | /* connected pin ? */ | 521 | /* connected pin ? */ |
@@ -519,8 +554,14 @@ static int is_connected_input_ep(struct snd_soc_dapm_widget *widget) | |||
519 | return 0; | 554 | return 0; |
520 | 555 | ||
521 | /* active stream ? */ | 556 | /* active stream ? */ |
522 | if (widget->id == snd_soc_dapm_dac && widget->active) | 557 | switch (widget->id) { |
523 | return 1; | 558 | case snd_soc_dapm_dac: |
559 | case snd_soc_dapm_aif_in: | ||
560 | if (widget->active) | ||
561 | return 1; | ||
562 | default: | ||
563 | break; | ||
564 | } | ||
524 | 565 | ||
525 | if (widget->connected) { | 566 | if (widget->connected) { |
526 | /* connected pin ? */ | 567 | /* connected pin ? */ |
@@ -689,53 +730,211 @@ static int dapm_supply_check_power(struct snd_soc_dapm_widget *w) | |||
689 | return power; | 730 | return power; |
690 | } | 731 | } |
691 | 732 | ||
692 | /* | 733 | static int dapm_seq_compare(struct snd_soc_dapm_widget *a, |
693 | * Scan a single DAPM widget for a complete audio path and update the | 734 | struct snd_soc_dapm_widget *b, |
694 | * power status appropriately. | 735 | int sort[]) |
695 | */ | ||
696 | static int dapm_power_widget(struct snd_soc_codec *codec, int event, | ||
697 | struct snd_soc_dapm_widget *w) | ||
698 | { | 736 | { |
699 | int ret; | 737 | if (sort[a->id] != sort[b->id]) |
738 | return sort[a->id] - sort[b->id]; | ||
739 | if (a->reg != b->reg) | ||
740 | return a->reg - b->reg; | ||
700 | 741 | ||
701 | switch (w->id) { | 742 | return 0; |
702 | case snd_soc_dapm_pre: | 743 | } |
703 | if (!w->event) | ||
704 | return 0; | ||
705 | 744 | ||
706 | if (event == SND_SOC_DAPM_STREAM_START) { | 745 | /* Insert a widget in order into a DAPM power sequence. */ |
707 | ret = w->event(w, | 746 | static void dapm_seq_insert(struct snd_soc_dapm_widget *new_widget, |
708 | NULL, SND_SOC_DAPM_PRE_PMU); | 747 | struct list_head *list, |
748 | int sort[]) | ||
749 | { | ||
750 | struct snd_soc_dapm_widget *w; | ||
751 | |||
752 | list_for_each_entry(w, list, power_list) | ||
753 | if (dapm_seq_compare(new_widget, w, sort) < 0) { | ||
754 | list_add_tail(&new_widget->power_list, &w->power_list); | ||
755 | return; | ||
756 | } | ||
757 | |||
758 | list_add_tail(&new_widget->power_list, list); | ||
759 | } | ||
760 | |||
761 | /* Apply the coalesced changes from a DAPM sequence */ | ||
762 | static void dapm_seq_run_coalesced(struct snd_soc_codec *codec, | ||
763 | struct list_head *pending) | ||
764 | { | ||
765 | struct snd_soc_dapm_widget *w; | ||
766 | int reg, power, ret; | ||
767 | unsigned int value = 0; | ||
768 | unsigned int mask = 0; | ||
769 | unsigned int cur_mask; | ||
770 | |||
771 | reg = list_first_entry(pending, struct snd_soc_dapm_widget, | ||
772 | power_list)->reg; | ||
773 | |||
774 | list_for_each_entry(w, pending, power_list) { | ||
775 | cur_mask = 1 << w->shift; | ||
776 | BUG_ON(reg != w->reg); | ||
777 | |||
778 | if (w->invert) | ||
779 | power = !w->power; | ||
780 | else | ||
781 | power = w->power; | ||
782 | |||
783 | mask |= cur_mask; | ||
784 | if (power) | ||
785 | value |= cur_mask; | ||
786 | |||
787 | pop_dbg(codec->pop_time, | ||
788 | "pop test : Queue %s: reg=0x%x, 0x%x/0x%x\n", | ||
789 | w->name, reg, value, mask); | ||
790 | |||
791 | /* power up pre event */ | ||
792 | if (w->power && w->event && | ||
793 | (w->event_flags & SND_SOC_DAPM_PRE_PMU)) { | ||
794 | pop_dbg(codec->pop_time, "pop test : %s PRE_PMU\n", | ||
795 | w->name); | ||
796 | ret = w->event(w, NULL, SND_SOC_DAPM_PRE_PMU); | ||
709 | if (ret < 0) | 797 | if (ret < 0) |
710 | return ret; | 798 | pr_err("%s: pre event failed: %d\n", |
711 | } else if (event == SND_SOC_DAPM_STREAM_STOP) { | 799 | w->name, ret); |
712 | ret = w->event(w, | 800 | } |
713 | NULL, SND_SOC_DAPM_PRE_PMD); | 801 | |
802 | /* power down pre event */ | ||
803 | if (!w->power && w->event && | ||
804 | (w->event_flags & SND_SOC_DAPM_PRE_PMD)) { | ||
805 | pop_dbg(codec->pop_time, "pop test : %s PRE_PMD\n", | ||
806 | w->name); | ||
807 | ret = w->event(w, NULL, SND_SOC_DAPM_PRE_PMD); | ||
714 | if (ret < 0) | 808 | if (ret < 0) |
715 | return ret; | 809 | pr_err("%s: pre event failed: %d\n", |
810 | w->name, ret); | ||
716 | } | 811 | } |
717 | return 0; | ||
718 | 812 | ||
719 | case snd_soc_dapm_post: | 813 | /* Lower PGA volume to reduce pops */ |
720 | if (!w->event) | 814 | if (w->id == snd_soc_dapm_pga && !w->power) |
721 | return 0; | 815 | dapm_set_pga(w, w->power); |
816 | } | ||
722 | 817 | ||
723 | if (event == SND_SOC_DAPM_STREAM_START) { | 818 | if (reg >= 0) { |
819 | pop_dbg(codec->pop_time, | ||
820 | "pop test : Applying 0x%x/0x%x to %x in %dms\n", | ||
821 | value, mask, reg, codec->pop_time); | ||
822 | pop_wait(codec->pop_time); | ||
823 | snd_soc_update_bits(codec, reg, mask, value); | ||
824 | } | ||
825 | |||
826 | list_for_each_entry(w, pending, power_list) { | ||
827 | /* Raise PGA volume to reduce pops */ | ||
828 | if (w->id == snd_soc_dapm_pga && w->power) | ||
829 | dapm_set_pga(w, w->power); | ||
830 | |||
831 | /* power up post event */ | ||
832 | if (w->power && w->event && | ||
833 | (w->event_flags & SND_SOC_DAPM_POST_PMU)) { | ||
834 | pop_dbg(codec->pop_time, "pop test : %s POST_PMU\n", | ||
835 | w->name); | ||
724 | ret = w->event(w, | 836 | ret = w->event(w, |
725 | NULL, SND_SOC_DAPM_POST_PMU); | 837 | NULL, SND_SOC_DAPM_POST_PMU); |
726 | if (ret < 0) | 838 | if (ret < 0) |
727 | return ret; | 839 | pr_err("%s: post event failed: %d\n", |
728 | } else if (event == SND_SOC_DAPM_STREAM_STOP) { | 840 | w->name, ret); |
729 | ret = w->event(w, | 841 | } |
730 | NULL, SND_SOC_DAPM_POST_PMD); | 842 | |
843 | /* power down post event */ | ||
844 | if (!w->power && w->event && | ||
845 | (w->event_flags & SND_SOC_DAPM_POST_PMD)) { | ||
846 | pop_dbg(codec->pop_time, "pop test : %s POST_PMD\n", | ||
847 | w->name); | ||
848 | ret = w->event(w, NULL, SND_SOC_DAPM_POST_PMD); | ||
731 | if (ret < 0) | 849 | if (ret < 0) |
732 | return ret; | 850 | pr_err("%s: post event failed: %d\n", |
851 | w->name, ret); | ||
733 | } | 852 | } |
734 | return 0; | 853 | } |
854 | } | ||
735 | 855 | ||
736 | default: | 856 | /* Apply a DAPM power sequence. |
737 | return dapm_generic_apply_power(w); | 857 | * |
858 | * We walk over a pre-sorted list of widgets to apply power to. In | ||
859 | * order to minimise the number of writes to the device required | ||
860 | * multiple widgets will be updated in a single write where possible. | ||
861 | * Currently anything that requires more than a single write is not | ||
862 | * handled. | ||
863 | */ | ||
864 | static void dapm_seq_run(struct snd_soc_codec *codec, struct list_head *list, | ||
865 | int event, int sort[]) | ||
866 | { | ||
867 | struct snd_soc_dapm_widget *w, *n; | ||
868 | LIST_HEAD(pending); | ||
869 | int cur_sort = -1; | ||
870 | int cur_reg = SND_SOC_NOPM; | ||
871 | int ret; | ||
872 | |||
873 | list_for_each_entry_safe(w, n, list, power_list) { | ||
874 | ret = 0; | ||
875 | |||
876 | /* Do we need to apply any queued changes? */ | ||
877 | if (sort[w->id] != cur_sort || w->reg != cur_reg) { | ||
878 | if (!list_empty(&pending)) | ||
879 | dapm_seq_run_coalesced(codec, &pending); | ||
880 | |||
881 | INIT_LIST_HEAD(&pending); | ||
882 | cur_sort = -1; | ||
883 | cur_reg = SND_SOC_NOPM; | ||
884 | } | ||
885 | |||
886 | switch (w->id) { | ||
887 | case snd_soc_dapm_pre: | ||
888 | if (!w->event) | ||
889 | list_for_each_entry_safe_continue(w, n, list, | ||
890 | power_list); | ||
891 | |||
892 | if (event == SND_SOC_DAPM_STREAM_START) | ||
893 | ret = w->event(w, | ||
894 | NULL, SND_SOC_DAPM_PRE_PMU); | ||
895 | else if (event == SND_SOC_DAPM_STREAM_STOP) | ||
896 | ret = w->event(w, | ||
897 | NULL, SND_SOC_DAPM_PRE_PMD); | ||
898 | break; | ||
899 | |||
900 | case snd_soc_dapm_post: | ||
901 | if (!w->event) | ||
902 | list_for_each_entry_safe_continue(w, n, list, | ||
903 | power_list); | ||
904 | |||
905 | if (event == SND_SOC_DAPM_STREAM_START) | ||
906 | ret = w->event(w, | ||
907 | NULL, SND_SOC_DAPM_POST_PMU); | ||
908 | else if (event == SND_SOC_DAPM_STREAM_STOP) | ||
909 | ret = w->event(w, | ||
910 | NULL, SND_SOC_DAPM_POST_PMD); | ||
911 | break; | ||
912 | |||
913 | case snd_soc_dapm_input: | ||
914 | case snd_soc_dapm_output: | ||
915 | case snd_soc_dapm_hp: | ||
916 | case snd_soc_dapm_mic: | ||
917 | case snd_soc_dapm_line: | ||
918 | case snd_soc_dapm_spk: | ||
919 | /* No register support currently */ | ||
920 | ret = dapm_generic_apply_power(w); | ||
921 | break; | ||
922 | |||
923 | default: | ||
924 | /* Queue it up for application */ | ||
925 | cur_sort = sort[w->id]; | ||
926 | cur_reg = w->reg; | ||
927 | list_move(&w->power_list, &pending); | ||
928 | break; | ||
929 | } | ||
930 | |||
931 | if (ret < 0) | ||
932 | pr_err("Failed to apply widget power: %d\n", | ||
933 | ret); | ||
738 | } | 934 | } |
935 | |||
936 | if (!list_empty(&pending)) | ||
937 | dapm_seq_run_coalesced(codec, &pending); | ||
739 | } | 938 | } |
740 | 939 | ||
741 | /* | 940 | /* |
@@ -751,23 +950,22 @@ static int dapm_power_widgets(struct snd_soc_codec *codec, int event) | |||
751 | { | 950 | { |
752 | struct snd_soc_device *socdev = codec->socdev; | 951 | struct snd_soc_device *socdev = codec->socdev; |
753 | struct snd_soc_dapm_widget *w; | 952 | struct snd_soc_dapm_widget *w; |
953 | LIST_HEAD(up_list); | ||
954 | LIST_HEAD(down_list); | ||
754 | int ret = 0; | 955 | int ret = 0; |
755 | int i, power; | 956 | int power; |
756 | int sys_power = 0; | 957 | int sys_power = 0; |
757 | 958 | ||
758 | INIT_LIST_HEAD(&codec->up_list); | ||
759 | INIT_LIST_HEAD(&codec->down_list); | ||
760 | |||
761 | /* Check which widgets we need to power and store them in | 959 | /* Check which widgets we need to power and store them in |
762 | * lists indicating if they should be powered up or down. | 960 | * lists indicating if they should be powered up or down. |
763 | */ | 961 | */ |
764 | list_for_each_entry(w, &codec->dapm_widgets, list) { | 962 | list_for_each_entry(w, &codec->dapm_widgets, list) { |
765 | switch (w->id) { | 963 | switch (w->id) { |
766 | case snd_soc_dapm_pre: | 964 | case snd_soc_dapm_pre: |
767 | list_add_tail(&codec->down_list, &w->power_list); | 965 | dapm_seq_insert(w, &down_list, dapm_down_seq); |
768 | break; | 966 | break; |
769 | case snd_soc_dapm_post: | 967 | case snd_soc_dapm_post: |
770 | list_add_tail(&codec->up_list, &w->power_list); | 968 | dapm_seq_insert(w, &up_list, dapm_up_seq); |
771 | break; | 969 | break; |
772 | 970 | ||
773 | default: | 971 | default: |
@@ -782,16 +980,31 @@ static int dapm_power_widgets(struct snd_soc_codec *codec, int event) | |||
782 | continue; | 980 | continue; |
783 | 981 | ||
784 | if (power) | 982 | if (power) |
785 | list_add_tail(&w->power_list, &codec->up_list); | 983 | dapm_seq_insert(w, &up_list, dapm_up_seq); |
786 | else | 984 | else |
787 | list_add_tail(&w->power_list, | 985 | dapm_seq_insert(w, &down_list, dapm_down_seq); |
788 | &codec->down_list); | ||
789 | 986 | ||
790 | w->power = power; | 987 | w->power = power; |
791 | break; | 988 | break; |
792 | } | 989 | } |
793 | } | 990 | } |
794 | 991 | ||
992 | /* If there are no DAPM widgets then try to figure out power from the | ||
993 | * event type. | ||
994 | */ | ||
995 | if (list_empty(&codec->dapm_widgets)) { | ||
996 | switch (event) { | ||
997 | case SND_SOC_DAPM_STREAM_START: | ||
998 | case SND_SOC_DAPM_STREAM_RESUME: | ||
999 | sys_power = 1; | ||
1000 | break; | ||
1001 | case SND_SOC_DAPM_STREAM_NOP: | ||
1002 | sys_power = codec->bias_level != SND_SOC_BIAS_STANDBY; | ||
1003 | default: | ||
1004 | break; | ||
1005 | } | ||
1006 | } | ||
1007 | |||
795 | /* If we're changing to all on or all off then prepare */ | 1008 | /* If we're changing to all on or all off then prepare */ |
796 | if ((sys_power && codec->bias_level == SND_SOC_BIAS_STANDBY) || | 1009 | if ((sys_power && codec->bias_level == SND_SOC_BIAS_STANDBY) || |
797 | (!sys_power && codec->bias_level == SND_SOC_BIAS_ON)) { | 1010 | (!sys_power && codec->bias_level == SND_SOC_BIAS_ON)) { |
@@ -802,32 +1015,10 @@ static int dapm_power_widgets(struct snd_soc_codec *codec, int event) | |||
802 | } | 1015 | } |
803 | 1016 | ||
804 | /* Power down widgets first; try to avoid amplifying pops. */ | 1017 | /* Power down widgets first; try to avoid amplifying pops. */ |
805 | for (i = 0; i < ARRAY_SIZE(dapm_down_seq); i++) { | 1018 | dapm_seq_run(codec, &down_list, event, dapm_down_seq); |
806 | list_for_each_entry(w, &codec->down_list, power_list) { | ||
807 | /* is widget in stream order */ | ||
808 | if (w->id != dapm_down_seq[i]) | ||
809 | continue; | ||
810 | |||
811 | ret = dapm_power_widget(codec, event, w); | ||
812 | if (ret != 0) | ||
813 | pr_err("Failed to power down %s: %d\n", | ||
814 | w->name, ret); | ||
815 | } | ||
816 | } | ||
817 | 1019 | ||
818 | /* Now power up. */ | 1020 | /* Now power up. */ |
819 | for (i = 0; i < ARRAY_SIZE(dapm_up_seq); i++) { | 1021 | dapm_seq_run(codec, &up_list, event, dapm_up_seq); |
820 | list_for_each_entry(w, &codec->up_list, power_list) { | ||
821 | /* is widget in stream order */ | ||
822 | if (w->id != dapm_up_seq[i]) | ||
823 | continue; | ||
824 | |||
825 | ret = dapm_power_widget(codec, event, w); | ||
826 | if (ret != 0) | ||
827 | pr_err("Failed to power up %s: %d\n", | ||
828 | w->name, ret); | ||
829 | } | ||
830 | } | ||
831 | 1022 | ||
832 | /* If we just powered the last thing off drop to standby bias */ | 1023 | /* If we just powered the last thing off drop to standby bias */ |
833 | if (codec->bias_level == SND_SOC_BIAS_PREPARE && !sys_power) { | 1024 | if (codec->bias_level == SND_SOC_BIAS_PREPARE && !sys_power) { |
@@ -845,6 +1036,9 @@ static int dapm_power_widgets(struct snd_soc_codec *codec, int event) | |||
845 | pr_err("Failed to apply active bias: %d\n", ret); | 1036 | pr_err("Failed to apply active bias: %d\n", ret); |
846 | } | 1037 | } |
847 | 1038 | ||
1039 | pop_dbg(codec->pop_time, "DAPM sequencing finished, waiting %dms\n", | ||
1040 | codec->pop_time); | ||
1041 | |||
848 | return 0; | 1042 | return 0; |
849 | } | 1043 | } |
850 | 1044 | ||
@@ -881,6 +1075,8 @@ static void dbg_dump_dapm(struct snd_soc_codec* codec, const char *action) | |||
881 | case snd_soc_dapm_mixer: | 1075 | case snd_soc_dapm_mixer: |
882 | case snd_soc_dapm_mixer_named_ctl: | 1076 | case snd_soc_dapm_mixer_named_ctl: |
883 | case snd_soc_dapm_supply: | 1077 | case snd_soc_dapm_supply: |
1078 | case snd_soc_dapm_aif_in: | ||
1079 | case snd_soc_dapm_aif_out: | ||
884 | if (w->name) { | 1080 | if (w->name) { |
885 | in = is_connected_input_ep(w); | 1081 | in = is_connected_input_ep(w); |
886 | dapm_clear_walk(w->codec); | 1082 | dapm_clear_walk(w->codec); |
@@ -906,6 +1102,93 @@ static void dbg_dump_dapm(struct snd_soc_codec* codec, const char *action) | |||
906 | } | 1102 | } |
907 | #endif | 1103 | #endif |
908 | 1104 | ||
1105 | #ifdef CONFIG_DEBUG_FS | ||
1106 | static int dapm_widget_power_open_file(struct inode *inode, struct file *file) | ||
1107 | { | ||
1108 | file->private_data = inode->i_private; | ||
1109 | return 0; | ||
1110 | } | ||
1111 | |||
1112 | static ssize_t dapm_widget_power_read_file(struct file *file, | ||
1113 | char __user *user_buf, | ||
1114 | size_t count, loff_t *ppos) | ||
1115 | { | ||
1116 | struct snd_soc_dapm_widget *w = file->private_data; | ||
1117 | char *buf; | ||
1118 | int in, out; | ||
1119 | ssize_t ret; | ||
1120 | struct snd_soc_dapm_path *p = NULL; | ||
1121 | |||
1122 | buf = kmalloc(PAGE_SIZE, GFP_KERNEL); | ||
1123 | if (!buf) | ||
1124 | return -ENOMEM; | ||
1125 | |||
1126 | in = is_connected_input_ep(w); | ||
1127 | dapm_clear_walk(w->codec); | ||
1128 | out = is_connected_output_ep(w); | ||
1129 | dapm_clear_walk(w->codec); | ||
1130 | |||
1131 | ret = snprintf(buf, PAGE_SIZE, "%s: %s in %d out %d\n", | ||
1132 | w->name, w->power ? "On" : "Off", in, out); | ||
1133 | |||
1134 | if (w->sname) | ||
1135 | ret += snprintf(buf + ret, PAGE_SIZE - ret, " stream %s %s\n", | ||
1136 | w->sname, | ||
1137 | w->active ? "active" : "inactive"); | ||
1138 | |||
1139 | list_for_each_entry(p, &w->sources, list_sink) { | ||
1140 | if (p->connect) | ||
1141 | ret += snprintf(buf + ret, PAGE_SIZE - ret, | ||
1142 | " in %s %s\n", | ||
1143 | p->name ? p->name : "static", | ||
1144 | p->source->name); | ||
1145 | } | ||
1146 | list_for_each_entry(p, &w->sinks, list_source) { | ||
1147 | if (p->connect) | ||
1148 | ret += snprintf(buf + ret, PAGE_SIZE - ret, | ||
1149 | " out %s %s\n", | ||
1150 | p->name ? p->name : "static", | ||
1151 | p->sink->name); | ||
1152 | } | ||
1153 | |||
1154 | ret = simple_read_from_buffer(user_buf, count, ppos, buf, ret); | ||
1155 | |||
1156 | kfree(buf); | ||
1157 | return ret; | ||
1158 | } | ||
1159 | |||
1160 | static const struct file_operations dapm_widget_power_fops = { | ||
1161 | .open = dapm_widget_power_open_file, | ||
1162 | .read = dapm_widget_power_read_file, | ||
1163 | }; | ||
1164 | |||
1165 | void snd_soc_dapm_debugfs_init(struct snd_soc_codec *codec) | ||
1166 | { | ||
1167 | struct snd_soc_dapm_widget *w; | ||
1168 | struct dentry *d; | ||
1169 | |||
1170 | if (!codec->debugfs_dapm) | ||
1171 | return; | ||
1172 | |||
1173 | list_for_each_entry(w, &codec->dapm_widgets, list) { | ||
1174 | if (!w->name) | ||
1175 | continue; | ||
1176 | |||
1177 | d = debugfs_create_file(w->name, 0444, | ||
1178 | codec->debugfs_dapm, w, | ||
1179 | &dapm_widget_power_fops); | ||
1180 | if (!d) | ||
1181 | printk(KERN_WARNING | ||
1182 | "ASoC: Failed to create %s debugfs file\n", | ||
1183 | w->name); | ||
1184 | } | ||
1185 | } | ||
1186 | #else | ||
1187 | void snd_soc_dapm_debugfs_init(struct snd_soc_codec *codec) | ||
1188 | { | ||
1189 | } | ||
1190 | #endif | ||
1191 | |||
909 | /* test and update the power status of a mux widget */ | 1192 | /* test and update the power status of a mux widget */ |
910 | static int dapm_mux_update_power(struct snd_soc_dapm_widget *widget, | 1193 | static int dapm_mux_update_power(struct snd_soc_dapm_widget *widget, |
911 | struct snd_kcontrol *kcontrol, int mask, | 1194 | struct snd_kcontrol *kcontrol, int mask, |
@@ -1138,8 +1421,8 @@ static int snd_soc_dapm_add_route(struct snd_soc_codec *codec, | |||
1138 | if (wsink->id == snd_soc_dapm_input) { | 1421 | if (wsink->id == snd_soc_dapm_input) { |
1139 | if (wsource->id == snd_soc_dapm_micbias || | 1422 | if (wsource->id == snd_soc_dapm_micbias || |
1140 | wsource->id == snd_soc_dapm_mic || | 1423 | wsource->id == snd_soc_dapm_mic || |
1141 | wsink->id == snd_soc_dapm_line || | 1424 | wsource->id == snd_soc_dapm_line || |
1142 | wsink->id == snd_soc_dapm_output) | 1425 | wsource->id == snd_soc_dapm_output) |
1143 | wsink->ext = 1; | 1426 | wsink->ext = 1; |
1144 | } | 1427 | } |
1145 | if (wsource->id == snd_soc_dapm_output) { | 1428 | if (wsource->id == snd_soc_dapm_output) { |
@@ -1171,6 +1454,8 @@ static int snd_soc_dapm_add_route(struct snd_soc_codec *codec, | |||
1171 | case snd_soc_dapm_pre: | 1454 | case snd_soc_dapm_pre: |
1172 | case snd_soc_dapm_post: | 1455 | case snd_soc_dapm_post: |
1173 | case snd_soc_dapm_supply: | 1456 | case snd_soc_dapm_supply: |
1457 | case snd_soc_dapm_aif_in: | ||
1458 | case snd_soc_dapm_aif_out: | ||
1174 | list_add(&path->list, &codec->dapm_paths); | 1459 | list_add(&path->list, &codec->dapm_paths); |
1175 | list_add(&path->list_sink, &wsink->sources); | 1460 | list_add(&path->list_sink, &wsink->sources); |
1176 | list_add(&path->list_source, &wsource->sinks); | 1461 | list_add(&path->list_source, &wsource->sinks); |
@@ -1273,9 +1558,11 @@ int snd_soc_dapm_new_widgets(struct snd_soc_codec *codec) | |||
1273 | dapm_new_mux(codec, w); | 1558 | dapm_new_mux(codec, w); |
1274 | break; | 1559 | break; |
1275 | case snd_soc_dapm_adc: | 1560 | case snd_soc_dapm_adc: |
1561 | case snd_soc_dapm_aif_out: | ||
1276 | w->power_check = dapm_adc_check_power; | 1562 | w->power_check = dapm_adc_check_power; |
1277 | break; | 1563 | break; |
1278 | case snd_soc_dapm_dac: | 1564 | case snd_soc_dapm_dac: |
1565 | case snd_soc_dapm_aif_in: | ||
1279 | w->power_check = dapm_dac_check_power; | 1566 | w->power_check = dapm_dac_check_power; |
1280 | break; | 1567 | break; |
1281 | case snd_soc_dapm_pga: | 1568 | case snd_soc_dapm_pga: |
@@ -1372,7 +1659,7 @@ int snd_soc_dapm_put_volsw(struct snd_kcontrol *kcontrol, | |||
1372 | int max = mc->max; | 1659 | int max = mc->max; |
1373 | unsigned int mask = (1 << fls(max)) - 1; | 1660 | unsigned int mask = (1 << fls(max)) - 1; |
1374 | unsigned int invert = mc->invert; | 1661 | unsigned int invert = mc->invert; |
1375 | unsigned short val, val2, val_mask; | 1662 | unsigned int val, val2, val_mask; |
1376 | int ret; | 1663 | int ret; |
1377 | 1664 | ||
1378 | val = (ucontrol->value.integer.value[0] & mask); | 1665 | val = (ucontrol->value.integer.value[0] & mask); |
@@ -1436,7 +1723,7 @@ int snd_soc_dapm_get_enum_double(struct snd_kcontrol *kcontrol, | |||
1436 | { | 1723 | { |
1437 | struct snd_soc_dapm_widget *widget = snd_kcontrol_chip(kcontrol); | 1724 | struct snd_soc_dapm_widget *widget = snd_kcontrol_chip(kcontrol); |
1438 | struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; | 1725 | struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; |
1439 | unsigned short val, bitmask; | 1726 | unsigned int val, bitmask; |
1440 | 1727 | ||
1441 | for (bitmask = 1; bitmask < e->max; bitmask <<= 1) | 1728 | for (bitmask = 1; bitmask < e->max; bitmask <<= 1) |
1442 | ; | 1729 | ; |
@@ -1464,8 +1751,8 @@ int snd_soc_dapm_put_enum_double(struct snd_kcontrol *kcontrol, | |||
1464 | { | 1751 | { |
1465 | struct snd_soc_dapm_widget *widget = snd_kcontrol_chip(kcontrol); | 1752 | struct snd_soc_dapm_widget *widget = snd_kcontrol_chip(kcontrol); |
1466 | struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; | 1753 | struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; |
1467 | unsigned short val, mux; | 1754 | unsigned int val, mux; |
1468 | unsigned short mask, bitmask; | 1755 | unsigned int mask, bitmask; |
1469 | int ret = 0; | 1756 | int ret = 0; |
1470 | 1757 | ||
1471 | for (bitmask = 1; bitmask < e->max; bitmask <<= 1) | 1758 | for (bitmask = 1; bitmask < e->max; bitmask <<= 1) |
@@ -1523,7 +1810,7 @@ int snd_soc_dapm_get_value_enum_double(struct snd_kcontrol *kcontrol, | |||
1523 | { | 1810 | { |
1524 | struct snd_soc_dapm_widget *widget = snd_kcontrol_chip(kcontrol); | 1811 | struct snd_soc_dapm_widget *widget = snd_kcontrol_chip(kcontrol); |
1525 | struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; | 1812 | struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; |
1526 | unsigned short reg_val, val, mux; | 1813 | unsigned int reg_val, val, mux; |
1527 | 1814 | ||
1528 | reg_val = snd_soc_read(widget->codec, e->reg); | 1815 | reg_val = snd_soc_read(widget->codec, e->reg); |
1529 | val = (reg_val >> e->shift_l) & e->mask; | 1816 | val = (reg_val >> e->shift_l) & e->mask; |
@@ -1563,8 +1850,8 @@ int snd_soc_dapm_put_value_enum_double(struct snd_kcontrol *kcontrol, | |||
1563 | { | 1850 | { |
1564 | struct snd_soc_dapm_widget *widget = snd_kcontrol_chip(kcontrol); | 1851 | struct snd_soc_dapm_widget *widget = snd_kcontrol_chip(kcontrol); |
1565 | struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; | 1852 | struct soc_enum *e = (struct soc_enum *)kcontrol->private_value; |
1566 | unsigned short val, mux; | 1853 | unsigned int val, mux; |
1567 | unsigned short mask; | 1854 | unsigned int mask; |
1568 | int ret = 0; | 1855 | int ret = 0; |
1569 | 1856 | ||
1570 | if (ucontrol->value.enumerated.item[0] > e->max - 1) | 1857 | if (ucontrol->value.enumerated.item[0] > e->max - 1) |
@@ -1880,6 +2167,36 @@ void snd_soc_dapm_free(struct snd_soc_device *socdev) | |||
1880 | } | 2167 | } |
1881 | EXPORT_SYMBOL_GPL(snd_soc_dapm_free); | 2168 | EXPORT_SYMBOL_GPL(snd_soc_dapm_free); |
1882 | 2169 | ||
2170 | /* | ||
2171 | * snd_soc_dapm_shutdown - callback for system shutdown | ||
2172 | */ | ||
2173 | void snd_soc_dapm_shutdown(struct snd_soc_device *socdev) | ||
2174 | { | ||
2175 | struct snd_soc_codec *codec = socdev->card->codec; | ||
2176 | struct snd_soc_dapm_widget *w; | ||
2177 | LIST_HEAD(down_list); | ||
2178 | int powerdown = 0; | ||
2179 | |||
2180 | list_for_each_entry(w, &codec->dapm_widgets, list) { | ||
2181 | if (w->power) { | ||
2182 | dapm_seq_insert(w, &down_list, dapm_down_seq); | ||
2183 | w->power = 0; | ||
2184 | powerdown = 1; | ||
2185 | } | ||
2186 | } | ||
2187 | |||
2188 | /* If there were no widgets to power down we're already in | ||
2189 | * standby. | ||
2190 | */ | ||
2191 | if (powerdown) { | ||
2192 | snd_soc_dapm_set_bias_level(socdev, SND_SOC_BIAS_PREPARE); | ||
2193 | dapm_seq_run(codec, &down_list, 0, dapm_down_seq); | ||
2194 | snd_soc_dapm_set_bias_level(socdev, SND_SOC_BIAS_STANDBY); | ||
2195 | } | ||
2196 | |||
2197 | snd_soc_dapm_set_bias_level(socdev, SND_SOC_BIAS_OFF); | ||
2198 | } | ||
2199 | |||
1883 | /* Module information */ | 2200 | /* Module information */ |
1884 | MODULE_AUTHOR("Liam Girdwood, lrg@slimlogic.co.uk"); | 2201 | MODULE_AUTHOR("Liam Girdwood, lrg@slimlogic.co.uk"); |
1885 | MODULE_DESCRIPTION("Dynamic Audio Power Management core for ALSA SoC"); | 2202 | MODULE_DESCRIPTION("Dynamic Audio Power Management core for ALSA SoC"); |