1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * n810.c -- SoC audio for Nokia N810 |
4 | * |
5 | * Copyright (C) 2008 Nokia Corporation |
6 | * |
7 | * Contact: Jarkko Nikula <jarkko.nikula@bitmer.com> |
8 | */ |
9 | |
10 | #include <linux/clk.h> |
11 | #include <linux/i2c.h> |
12 | #include <linux/platform_device.h> |
13 | #include <sound/core.h> |
14 | #include <sound/pcm.h> |
15 | #include <sound/soc.h> |
16 | |
17 | #include <asm/mach-types.h> |
18 | #include <linux/gpio/consumer.h> |
19 | #include <linux/module.h> |
20 | #include <linux/platform_data/asoc-ti-mcbsp.h> |
21 | |
22 | #include "omap-mcbsp.h" |
23 | |
24 | static struct gpio_desc *n810_headset_amp; |
25 | static struct gpio_desc *n810_speaker_amp; |
26 | |
27 | enum { |
28 | N810_JACK_DISABLED, |
29 | N810_JACK_HP, |
30 | N810_JACK_HS, |
31 | N810_JACK_MIC, |
32 | }; |
33 | |
34 | static struct clk *sys_clkout2; |
35 | static struct clk *sys_clkout2_src; |
36 | static struct clk *func96m_clk; |
37 | |
38 | static int n810_spk_func; |
39 | static int n810_jack_func; |
40 | static int n810_dmic_func; |
41 | |
42 | static void n810_ext_control(struct snd_soc_dapm_context *dapm) |
43 | { |
44 | int hp = 0, line1l = 0; |
45 | |
46 | switch (n810_jack_func) { |
47 | case N810_JACK_HS: |
48 | line1l = 1; |
49 | fallthrough; |
50 | case N810_JACK_HP: |
51 | hp = 1; |
52 | break; |
53 | case N810_JACK_MIC: |
54 | line1l = 1; |
55 | break; |
56 | } |
57 | |
58 | snd_soc_dapm_mutex_lock(dapm); |
59 | |
60 | if (n810_spk_func) |
61 | snd_soc_dapm_enable_pin_unlocked(dapm, pin: "Ext Spk" ); |
62 | else |
63 | snd_soc_dapm_disable_pin_unlocked(dapm, pin: "Ext Spk" ); |
64 | |
65 | if (hp) |
66 | snd_soc_dapm_enable_pin_unlocked(dapm, pin: "Headphone Jack" ); |
67 | else |
68 | snd_soc_dapm_disable_pin_unlocked(dapm, pin: "Headphone Jack" ); |
69 | if (line1l) |
70 | snd_soc_dapm_enable_pin_unlocked(dapm, pin: "HS Mic" ); |
71 | else |
72 | snd_soc_dapm_disable_pin_unlocked(dapm, pin: "HS Mic" ); |
73 | |
74 | if (n810_dmic_func) |
75 | snd_soc_dapm_enable_pin_unlocked(dapm, pin: "DMic" ); |
76 | else |
77 | snd_soc_dapm_disable_pin_unlocked(dapm, pin: "DMic" ); |
78 | |
79 | snd_soc_dapm_sync_unlocked(dapm); |
80 | |
81 | snd_soc_dapm_mutex_unlock(dapm); |
82 | } |
83 | |
84 | static int n810_startup(struct snd_pcm_substream *substream) |
85 | { |
86 | struct snd_pcm_runtime *runtime = substream->runtime; |
87 | struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); |
88 | |
89 | snd_pcm_hw_constraint_single(runtime, SNDRV_PCM_HW_PARAM_CHANNELS, val: 2); |
90 | |
91 | n810_ext_control(dapm: &rtd->card->dapm); |
92 | return clk_prepare_enable(clk: sys_clkout2); |
93 | } |
94 | |
95 | static void n810_shutdown(struct snd_pcm_substream *substream) |
96 | { |
97 | clk_disable_unprepare(clk: sys_clkout2); |
98 | } |
99 | |
100 | static int n810_hw_params(struct snd_pcm_substream *substream, |
101 | struct snd_pcm_hw_params *params) |
102 | { |
103 | struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); |
104 | struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); |
105 | int err; |
106 | |
107 | /* Set the codec system clock for DAC and ADC */ |
108 | err = snd_soc_dai_set_sysclk(dai: codec_dai, clk_id: 0, freq: 12000000, |
109 | SND_SOC_CLOCK_IN); |
110 | |
111 | return err; |
112 | } |
113 | |
114 | static const struct snd_soc_ops n810_ops = { |
115 | .startup = n810_startup, |
116 | .hw_params = n810_hw_params, |
117 | .shutdown = n810_shutdown, |
118 | }; |
119 | |
120 | static int n810_get_spk(struct snd_kcontrol *kcontrol, |
121 | struct snd_ctl_elem_value *ucontrol) |
122 | { |
123 | ucontrol->value.enumerated.item[0] = n810_spk_func; |
124 | |
125 | return 0; |
126 | } |
127 | |
128 | static int n810_set_spk(struct snd_kcontrol *kcontrol, |
129 | struct snd_ctl_elem_value *ucontrol) |
130 | { |
131 | struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); |
132 | |
133 | if (n810_spk_func == ucontrol->value.enumerated.item[0]) |
134 | return 0; |
135 | |
136 | n810_spk_func = ucontrol->value.enumerated.item[0]; |
137 | n810_ext_control(dapm: &card->dapm); |
138 | |
139 | return 1; |
140 | } |
141 | |
142 | static int n810_get_jack(struct snd_kcontrol *kcontrol, |
143 | struct snd_ctl_elem_value *ucontrol) |
144 | { |
145 | ucontrol->value.enumerated.item[0] = n810_jack_func; |
146 | |
147 | return 0; |
148 | } |
149 | |
150 | static int n810_set_jack(struct snd_kcontrol *kcontrol, |
151 | struct snd_ctl_elem_value *ucontrol) |
152 | { |
153 | struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); |
154 | |
155 | if (n810_jack_func == ucontrol->value.enumerated.item[0]) |
156 | return 0; |
157 | |
158 | n810_jack_func = ucontrol->value.enumerated.item[0]; |
159 | n810_ext_control(dapm: &card->dapm); |
160 | |
161 | return 1; |
162 | } |
163 | |
164 | static int n810_get_input(struct snd_kcontrol *kcontrol, |
165 | struct snd_ctl_elem_value *ucontrol) |
166 | { |
167 | ucontrol->value.enumerated.item[0] = n810_dmic_func; |
168 | |
169 | return 0; |
170 | } |
171 | |
172 | static int n810_set_input(struct snd_kcontrol *kcontrol, |
173 | struct snd_ctl_elem_value *ucontrol) |
174 | { |
175 | struct snd_soc_card *card = snd_kcontrol_chip(kcontrol); |
176 | |
177 | if (n810_dmic_func == ucontrol->value.enumerated.item[0]) |
178 | return 0; |
179 | |
180 | n810_dmic_func = ucontrol->value.enumerated.item[0]; |
181 | n810_ext_control(dapm: &card->dapm); |
182 | |
183 | return 1; |
184 | } |
185 | |
186 | static int n810_spk_event(struct snd_soc_dapm_widget *w, |
187 | struct snd_kcontrol *k, int event) |
188 | { |
189 | if (SND_SOC_DAPM_EVENT_ON(event)) |
190 | gpiod_set_value(desc: n810_speaker_amp, value: 1); |
191 | else |
192 | gpiod_set_value(desc: n810_speaker_amp, value: 0); |
193 | |
194 | return 0; |
195 | } |
196 | |
197 | static int n810_jack_event(struct snd_soc_dapm_widget *w, |
198 | struct snd_kcontrol *k, int event) |
199 | { |
200 | if (SND_SOC_DAPM_EVENT_ON(event)) |
201 | gpiod_set_value(desc: n810_headset_amp, value: 1); |
202 | else |
203 | gpiod_set_value(desc: n810_headset_amp, value: 0); |
204 | |
205 | return 0; |
206 | } |
207 | |
208 | static const struct snd_soc_dapm_widget aic33_dapm_widgets[] = { |
209 | SND_SOC_DAPM_SPK("Ext Spk" , n810_spk_event), |
210 | SND_SOC_DAPM_HP("Headphone Jack" , n810_jack_event), |
211 | SND_SOC_DAPM_MIC("DMic" , NULL), |
212 | SND_SOC_DAPM_MIC("HS Mic" , NULL), |
213 | }; |
214 | |
215 | static const struct snd_soc_dapm_route audio_map[] = { |
216 | {"Headphone Jack" , NULL, "HPLOUT" }, |
217 | {"Headphone Jack" , NULL, "HPROUT" }, |
218 | |
219 | {"Ext Spk" , NULL, "LLOUT" }, |
220 | {"Ext Spk" , NULL, "RLOUT" }, |
221 | |
222 | {"DMic Rate 64" , NULL, "DMic" }, |
223 | {"DMic" , NULL, "Mic Bias" }, |
224 | |
225 | /* |
226 | * Note that the mic bias is coming from Retu/Vilma and we don't have |
227 | * control over it atm. The analog HS mic is not working. <- TODO |
228 | */ |
229 | {"LINE1L" , NULL, "HS Mic" }, |
230 | }; |
231 | |
232 | static const char *spk_function[] = {"Off" , "On" }; |
233 | static const char *jack_function[] = {"Off" , "Headphone" , "Headset" , "Mic" }; |
234 | static const char *input_function[] = {"ADC" , "Digital Mic" }; |
235 | static const struct soc_enum n810_enum[] = { |
236 | SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(spk_function), spk_function), |
237 | SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(jack_function), jack_function), |
238 | SOC_ENUM_SINGLE_EXT(ARRAY_SIZE(input_function), input_function), |
239 | }; |
240 | |
241 | static const struct snd_kcontrol_new aic33_n810_controls[] = { |
242 | SOC_ENUM_EXT("Speaker Function" , n810_enum[0], |
243 | n810_get_spk, n810_set_spk), |
244 | SOC_ENUM_EXT("Jack Function" , n810_enum[1], |
245 | n810_get_jack, n810_set_jack), |
246 | SOC_ENUM_EXT("Input Select" , n810_enum[2], |
247 | n810_get_input, n810_set_input), |
248 | }; |
249 | |
250 | /* Digital audio interface glue - connects codec <--> CPU */ |
251 | SND_SOC_DAILINK_DEFS(aic33, |
252 | DAILINK_COMP_ARRAY(COMP_CPU("48076000.mcbsp" )), |
253 | DAILINK_COMP_ARRAY(COMP_CODEC("tlv320aic3x-codec.1-0018" , |
254 | "tlv320aic3x-hifi" )), |
255 | DAILINK_COMP_ARRAY(COMP_PLATFORM("48076000.mcbsp" ))); |
256 | |
257 | static struct snd_soc_dai_link n810_dai = { |
258 | .name = "TLV320AIC33" , |
259 | .stream_name = "AIC33" , |
260 | .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | |
261 | SND_SOC_DAIFMT_CBM_CFM, |
262 | .ops = &n810_ops, |
263 | SND_SOC_DAILINK_REG(aic33), |
264 | }; |
265 | |
266 | /* Audio machine driver */ |
267 | static struct snd_soc_card snd_soc_n810 = { |
268 | .name = "N810" , |
269 | .owner = THIS_MODULE, |
270 | .dai_link = &n810_dai, |
271 | .num_links = 1, |
272 | |
273 | .controls = aic33_n810_controls, |
274 | .num_controls = ARRAY_SIZE(aic33_n810_controls), |
275 | .dapm_widgets = aic33_dapm_widgets, |
276 | .num_dapm_widgets = ARRAY_SIZE(aic33_dapm_widgets), |
277 | .dapm_routes = audio_map, |
278 | .num_dapm_routes = ARRAY_SIZE(audio_map), |
279 | .fully_routed = true, |
280 | }; |
281 | |
282 | static struct platform_device *n810_snd_device; |
283 | |
284 | static int __init n810_soc_init(void) |
285 | { |
286 | int err; |
287 | struct device *dev; |
288 | |
289 | if (!of_have_populated_dt() || |
290 | (!of_machine_is_compatible(compat: "nokia,n810" ) && |
291 | !of_machine_is_compatible(compat: "nokia,n810-wimax" ))) |
292 | return -ENODEV; |
293 | |
294 | n810_snd_device = platform_device_alloc(name: "soc-audio" , id: -1); |
295 | if (!n810_snd_device) |
296 | return -ENOMEM; |
297 | |
298 | platform_set_drvdata(pdev: n810_snd_device, data: &snd_soc_n810); |
299 | err = platform_device_add(pdev: n810_snd_device); |
300 | if (err) |
301 | goto err1; |
302 | |
303 | dev = &n810_snd_device->dev; |
304 | |
305 | sys_clkout2_src = clk_get(dev, id: "sys_clkout2_src" ); |
306 | if (IS_ERR(ptr: sys_clkout2_src)) { |
307 | dev_err(dev, "Could not get sys_clkout2_src clock\n" ); |
308 | err = PTR_ERR(ptr: sys_clkout2_src); |
309 | goto err2; |
310 | } |
311 | sys_clkout2 = clk_get(dev, id: "sys_clkout2" ); |
312 | if (IS_ERR(ptr: sys_clkout2)) { |
313 | dev_err(dev, "Could not get sys_clkout2\n" ); |
314 | err = PTR_ERR(ptr: sys_clkout2); |
315 | goto err3; |
316 | } |
317 | /* |
318 | * Configure 12 MHz output on SYS_CLKOUT2. Therefore we must use |
319 | * 96 MHz as its parent in order to get 12 MHz |
320 | */ |
321 | func96m_clk = clk_get(dev, id: "func_96m_ck" ); |
322 | if (IS_ERR(ptr: func96m_clk)) { |
323 | dev_err(dev, "Could not get func 96M clock\n" ); |
324 | err = PTR_ERR(ptr: func96m_clk); |
325 | goto err4; |
326 | } |
327 | clk_set_parent(clk: sys_clkout2_src, parent: func96m_clk); |
328 | clk_set_rate(clk: sys_clkout2, rate: 12000000); |
329 | |
330 | n810_headset_amp = devm_gpiod_get(dev: &n810_snd_device->dev, |
331 | con_id: "headphone" , flags: GPIOD_OUT_LOW); |
332 | if (IS_ERR(ptr: n810_headset_amp)) { |
333 | err = PTR_ERR(ptr: n810_headset_amp); |
334 | goto err4; |
335 | } |
336 | |
337 | n810_speaker_amp = devm_gpiod_get(dev: &n810_snd_device->dev, |
338 | con_id: "speaker" , flags: GPIOD_OUT_LOW); |
339 | if (IS_ERR(ptr: n810_speaker_amp)) { |
340 | err = PTR_ERR(ptr: n810_speaker_amp); |
341 | goto err4; |
342 | } |
343 | |
344 | return 0; |
345 | err4: |
346 | clk_put(clk: sys_clkout2); |
347 | err3: |
348 | clk_put(clk: sys_clkout2_src); |
349 | err2: |
350 | platform_device_del(pdev: n810_snd_device); |
351 | err1: |
352 | platform_device_put(pdev: n810_snd_device); |
353 | |
354 | return err; |
355 | } |
356 | |
357 | static void __exit n810_soc_exit(void) |
358 | { |
359 | clk_put(clk: sys_clkout2_src); |
360 | clk_put(clk: sys_clkout2); |
361 | clk_put(clk: func96m_clk); |
362 | |
363 | platform_device_unregister(n810_snd_device); |
364 | } |
365 | |
366 | module_init(n810_soc_init); |
367 | module_exit(n810_soc_exit); |
368 | |
369 | MODULE_AUTHOR("Jarkko Nikula <jarkko.nikula@bitmer.com>" ); |
370 | MODULE_DESCRIPTION("ALSA SoC Nokia N810" ); |
371 | MODULE_LICENSE("GPL" ); |
372 | |