1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | // |
3 | // Lowland audio support |
4 | // |
5 | // Copyright 2011 Wolfson Microelectronics |
6 | |
7 | #include <sound/soc.h> |
8 | #include <sound/soc-dapm.h> |
9 | #include <sound/jack.h> |
10 | #include <linux/gpio.h> |
11 | #include <linux/module.h> |
12 | |
13 | #include "../codecs/wm5100.h" |
14 | #include "../codecs/wm9081.h" |
15 | |
16 | #define MCLK1_RATE (44100 * 512) |
17 | #define CLKOUT_RATE (44100 * 256) |
18 | |
19 | static struct snd_soc_jack lowland_headset; |
20 | |
21 | /* Headset jack detection DAPM pins */ |
22 | static struct snd_soc_jack_pin lowland_headset_pins[] = { |
23 | { |
24 | .pin = "Headphone" , |
25 | .mask = SND_JACK_HEADPHONE, |
26 | }, |
27 | { |
28 | .pin = "Headset Mic" , |
29 | .mask = SND_JACK_MICROPHONE, |
30 | }, |
31 | { |
32 | .pin = "Line Out" , |
33 | .mask = SND_JACK_LINEOUT, |
34 | }, |
35 | }; |
36 | |
37 | static int lowland_wm5100_init(struct snd_soc_pcm_runtime *rtd) |
38 | { |
39 | struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component; |
40 | int ret; |
41 | |
42 | ret = snd_soc_component_set_sysclk(component, WM5100_CLK_SYSCLK, |
43 | WM5100_CLKSRC_MCLK1, MCLK1_RATE, |
44 | SND_SOC_CLOCK_IN); |
45 | if (ret < 0) { |
46 | pr_err("Failed to set SYSCLK clock source: %d\n" , ret); |
47 | return ret; |
48 | } |
49 | |
50 | /* Clock OPCLK, used by the other audio components. */ |
51 | ret = snd_soc_component_set_sysclk(component, WM5100_CLK_OPCLK, source: 0, |
52 | CLKOUT_RATE, dir: 0); |
53 | if (ret < 0) { |
54 | pr_err("Failed to set OPCLK rate: %d\n" , ret); |
55 | return ret; |
56 | } |
57 | |
58 | ret = snd_soc_card_jack_new_pins(card: rtd->card, id: "Headset" , |
59 | type: SND_JACK_LINEOUT | SND_JACK_HEADSET | |
60 | SND_JACK_BTN_0, |
61 | jack: &lowland_headset, pins: lowland_headset_pins, |
62 | ARRAY_SIZE(lowland_headset_pins)); |
63 | if (ret) |
64 | return ret; |
65 | |
66 | wm5100_detect(component, jack: &lowland_headset); |
67 | |
68 | return 0; |
69 | } |
70 | |
71 | static int lowland_wm9081_init(struct snd_soc_pcm_runtime *rtd) |
72 | { |
73 | struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component; |
74 | |
75 | snd_soc_dapm_nc_pin(dapm: &rtd->card->dapm, pin: "LINEOUT" ); |
76 | |
77 | /* At any time the WM9081 is active it will have this clock */ |
78 | return snd_soc_component_set_sysclk(component, WM9081_SYSCLK_MCLK, source: 0, |
79 | CLKOUT_RATE, dir: 0); |
80 | } |
81 | |
82 | static const struct snd_soc_pcm_stream sub_params = { |
83 | .formats = SNDRV_PCM_FMTBIT_S32_LE, |
84 | .rate_min = 44100, |
85 | .rate_max = 44100, |
86 | .channels_min = 2, |
87 | .channels_max = 2, |
88 | }; |
89 | |
90 | SND_SOC_DAILINK_DEFS(cpu, |
91 | DAILINK_COMP_ARRAY(COMP_CPU("samsung-i2s.0" )), |
92 | DAILINK_COMP_ARRAY(COMP_CODEC("wm5100.1-001a" , "wm5100-aif1" )), |
93 | DAILINK_COMP_ARRAY(COMP_PLATFORM("samsung-i2s.0" ))); |
94 | |
95 | SND_SOC_DAILINK_DEFS(baseband, |
96 | DAILINK_COMP_ARRAY(COMP_CPU("wm5100-aif2" )), |
97 | DAILINK_COMP_ARRAY(COMP_CODEC("wm1250-ev1.1-0027" , "wm1250-ev1" ))); |
98 | |
99 | SND_SOC_DAILINK_DEFS(speaker, |
100 | DAILINK_COMP_ARRAY(COMP_CPU("wm5100-aif3" )), |
101 | DAILINK_COMP_ARRAY(COMP_CODEC("wm9081.1-006c" , "wm9081-hifi" ))); |
102 | |
103 | static struct snd_soc_dai_link lowland_dai[] = { |
104 | { |
105 | .name = "CPU" , |
106 | .stream_name = "CPU" , |
107 | .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | |
108 | SND_SOC_DAIFMT_CBM_CFM, |
109 | .init = lowland_wm5100_init, |
110 | SND_SOC_DAILINK_REG(cpu), |
111 | }, |
112 | { |
113 | .name = "Baseband" , |
114 | .stream_name = "Baseband" , |
115 | .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | |
116 | SND_SOC_DAIFMT_CBM_CFM, |
117 | .ignore_suspend = 1, |
118 | SND_SOC_DAILINK_REG(baseband), |
119 | }, |
120 | { |
121 | .name = "Sub Speaker" , |
122 | .stream_name = "Sub Speaker" , |
123 | .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | |
124 | SND_SOC_DAIFMT_CBM_CFM, |
125 | .ignore_suspend = 1, |
126 | .c2c_params = &sub_params, |
127 | .num_c2c_params = 1, |
128 | .init = lowland_wm9081_init, |
129 | SND_SOC_DAILINK_REG(speaker), |
130 | }, |
131 | }; |
132 | |
133 | static struct snd_soc_codec_conf lowland_codec_conf[] = { |
134 | { |
135 | .dlc = COMP_CODEC_CONF("wm9081.1-006c" ), |
136 | .name_prefix = "Sub" , |
137 | }, |
138 | }; |
139 | |
140 | static const struct snd_kcontrol_new controls[] = { |
141 | SOC_DAPM_PIN_SWITCH("Main Speaker" ), |
142 | SOC_DAPM_PIN_SWITCH("Main DMIC" ), |
143 | SOC_DAPM_PIN_SWITCH("Main AMIC" ), |
144 | SOC_DAPM_PIN_SWITCH("WM1250 Input" ), |
145 | SOC_DAPM_PIN_SWITCH("WM1250 Output" ), |
146 | SOC_DAPM_PIN_SWITCH("Headphone" ), |
147 | SOC_DAPM_PIN_SWITCH("Line Out" ), |
148 | }; |
149 | |
150 | static const struct snd_soc_dapm_widget widgets[] = { |
151 | SND_SOC_DAPM_HP("Headphone" , NULL), |
152 | SND_SOC_DAPM_MIC("Headset Mic" , NULL), |
153 | SND_SOC_DAPM_LINE("Line Out" , NULL), |
154 | |
155 | SND_SOC_DAPM_SPK("Main Speaker" , NULL), |
156 | |
157 | SND_SOC_DAPM_MIC("Main AMIC" , NULL), |
158 | SND_SOC_DAPM_MIC("Main DMIC" , NULL), |
159 | }; |
160 | |
161 | static const struct snd_soc_dapm_route audio_paths[] = { |
162 | { "Sub IN1" , NULL, "HPOUT2L" }, |
163 | { "Sub IN2" , NULL, "HPOUT2R" }, |
164 | |
165 | { "Main Speaker" , NULL, "Sub SPKN" }, |
166 | { "Main Speaker" , NULL, "Sub SPKP" }, |
167 | { "Main Speaker" , NULL, "SPKDAT1" }, |
168 | }; |
169 | |
170 | static struct snd_soc_card lowland = { |
171 | .name = "Lowland" , |
172 | .owner = THIS_MODULE, |
173 | .dai_link = lowland_dai, |
174 | .num_links = ARRAY_SIZE(lowland_dai), |
175 | .codec_conf = lowland_codec_conf, |
176 | .num_configs = ARRAY_SIZE(lowland_codec_conf), |
177 | |
178 | .controls = controls, |
179 | .num_controls = ARRAY_SIZE(controls), |
180 | .dapm_widgets = widgets, |
181 | .num_dapm_widgets = ARRAY_SIZE(widgets), |
182 | .dapm_routes = audio_paths, |
183 | .num_dapm_routes = ARRAY_SIZE(audio_paths), |
184 | }; |
185 | |
186 | static int lowland_probe(struct platform_device *pdev) |
187 | { |
188 | struct snd_soc_card *card = &lowland; |
189 | int ret; |
190 | |
191 | card->dev = &pdev->dev; |
192 | |
193 | ret = devm_snd_soc_register_card(dev: &pdev->dev, card); |
194 | if (ret) |
195 | dev_err_probe(dev: &pdev->dev, err: ret, fmt: "snd_soc_register_card() failed\n" ); |
196 | |
197 | return ret; |
198 | } |
199 | |
200 | static struct platform_driver lowland_driver = { |
201 | .driver = { |
202 | .name = "lowland" , |
203 | .pm = &snd_soc_pm_ops, |
204 | }, |
205 | .probe = lowland_probe, |
206 | }; |
207 | |
208 | module_platform_driver(lowland_driver); |
209 | |
210 | MODULE_DESCRIPTION("Lowland audio support" ); |
211 | MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>" ); |
212 | MODULE_LICENSE("GPL" ); |
213 | MODULE_ALIAS("platform:lowland" ); |
214 | |