1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * omap-abe-twl6040.c -- SoC audio for TI OMAP based boards with ABE and |
4 | * twl6040 codec |
5 | * |
6 | * Author: Misael Lopez Cruz <misael.lopez@ti.com> |
7 | */ |
8 | |
9 | #include <linux/clk.h> |
10 | #include <linux/platform_device.h> |
11 | #include <linux/mfd/twl6040.h> |
12 | #include <linux/module.h> |
13 | #include <linux/of.h> |
14 | |
15 | #include <sound/core.h> |
16 | #include <sound/pcm.h> |
17 | #include <sound/soc.h> |
18 | #include <sound/jack.h> |
19 | |
20 | #include "omap-dmic.h" |
21 | #include "omap-mcpdm.h" |
22 | #include "../codecs/twl6040.h" |
23 | |
24 | SND_SOC_DAILINK_DEFS(link0, |
25 | DAILINK_COMP_ARRAY(COMP_EMPTY()), |
26 | DAILINK_COMP_ARRAY(COMP_CODEC("twl6040-codec" , |
27 | "twl6040-legacy" )), |
28 | DAILINK_COMP_ARRAY(COMP_EMPTY())); |
29 | |
30 | SND_SOC_DAILINK_DEFS(link1, |
31 | DAILINK_COMP_ARRAY(COMP_EMPTY()), |
32 | DAILINK_COMP_ARRAY(COMP_CODEC("dmic-codec" , |
33 | "dmic-hifi" )), |
34 | DAILINK_COMP_ARRAY(COMP_EMPTY())); |
35 | |
36 | struct abe_twl6040 { |
37 | struct snd_soc_card card; |
38 | struct snd_soc_dai_link dai_links[2]; |
39 | int jack_detection; /* board can detect jack events */ |
40 | int mclk_freq; /* MCLK frequency speed for twl6040 */ |
41 | }; |
42 | |
43 | static struct platform_device *dmic_codec_dev; |
44 | |
45 | static int omap_abe_hw_params(struct snd_pcm_substream *substream, |
46 | struct snd_pcm_hw_params *params) |
47 | { |
48 | struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); |
49 | struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); |
50 | struct snd_soc_card *card = rtd->card; |
51 | struct abe_twl6040 *priv = snd_soc_card_get_drvdata(card); |
52 | int clk_id, freq; |
53 | int ret; |
54 | |
55 | clk_id = twl6040_get_clk_id(component: codec_dai->component); |
56 | if (clk_id == TWL6040_SYSCLK_SEL_HPPLL) |
57 | freq = priv->mclk_freq; |
58 | else if (clk_id == TWL6040_SYSCLK_SEL_LPPLL) |
59 | freq = 32768; |
60 | else |
61 | return -EINVAL; |
62 | |
63 | /* set the codec mclk */ |
64 | ret = snd_soc_dai_set_sysclk(dai: codec_dai, clk_id, freq, |
65 | SND_SOC_CLOCK_IN); |
66 | if (ret) { |
67 | printk(KERN_ERR "can't set codec system clock\n" ); |
68 | return ret; |
69 | } |
70 | return ret; |
71 | } |
72 | |
73 | static const struct snd_soc_ops omap_abe_ops = { |
74 | .hw_params = omap_abe_hw_params, |
75 | }; |
76 | |
77 | static int omap_abe_dmic_hw_params(struct snd_pcm_substream *substream, |
78 | struct snd_pcm_hw_params *params) |
79 | { |
80 | struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); |
81 | struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); |
82 | int ret = 0; |
83 | |
84 | ret = snd_soc_dai_set_sysclk(dai: cpu_dai, clk_id: OMAP_DMIC_SYSCLK_PAD_CLKS, |
85 | freq: 19200000, SND_SOC_CLOCK_IN); |
86 | if (ret < 0) { |
87 | printk(KERN_ERR "can't set DMIC cpu system clock\n" ); |
88 | return ret; |
89 | } |
90 | ret = snd_soc_dai_set_sysclk(dai: cpu_dai, clk_id: OMAP_DMIC_ABE_DMIC_CLK, freq: 2400000, |
91 | SND_SOC_CLOCK_OUT); |
92 | if (ret < 0) { |
93 | printk(KERN_ERR "can't set DMIC output clock\n" ); |
94 | return ret; |
95 | } |
96 | return 0; |
97 | } |
98 | |
99 | static const struct snd_soc_ops omap_abe_dmic_ops = { |
100 | .hw_params = omap_abe_dmic_hw_params, |
101 | }; |
102 | |
103 | /* Headset jack */ |
104 | static struct snd_soc_jack hs_jack; |
105 | |
106 | /*Headset jack detection DAPM pins */ |
107 | static struct snd_soc_jack_pin hs_jack_pins[] = { |
108 | { |
109 | .pin = "Headset Mic" , |
110 | .mask = SND_JACK_MICROPHONE, |
111 | }, |
112 | { |
113 | .pin = "Headset Stereophone" , |
114 | .mask = SND_JACK_HEADPHONE, |
115 | }, |
116 | }; |
117 | |
118 | /* SDP4430 machine DAPM */ |
119 | static const struct snd_soc_dapm_widget twl6040_dapm_widgets[] = { |
120 | /* Outputs */ |
121 | SND_SOC_DAPM_HP("Headset Stereophone" , NULL), |
122 | SND_SOC_DAPM_SPK("Earphone Spk" , NULL), |
123 | SND_SOC_DAPM_SPK("Ext Spk" , NULL), |
124 | SND_SOC_DAPM_LINE("Line Out" , NULL), |
125 | SND_SOC_DAPM_SPK("Vibrator" , NULL), |
126 | |
127 | /* Inputs */ |
128 | SND_SOC_DAPM_MIC("Headset Mic" , NULL), |
129 | SND_SOC_DAPM_MIC("Main Handset Mic" , NULL), |
130 | SND_SOC_DAPM_MIC("Sub Handset Mic" , NULL), |
131 | SND_SOC_DAPM_LINE("Line In" , NULL), |
132 | |
133 | /* Digital microphones */ |
134 | SND_SOC_DAPM_MIC("Digital Mic" , NULL), |
135 | }; |
136 | |
137 | static const struct snd_soc_dapm_route audio_map[] = { |
138 | /* Routings for outputs */ |
139 | {"Headset Stereophone" , NULL, "HSOL" }, |
140 | {"Headset Stereophone" , NULL, "HSOR" }, |
141 | |
142 | {"Earphone Spk" , NULL, "EP" }, |
143 | |
144 | {"Ext Spk" , NULL, "HFL" }, |
145 | {"Ext Spk" , NULL, "HFR" }, |
146 | |
147 | {"Line Out" , NULL, "AUXL" }, |
148 | {"Line Out" , NULL, "AUXR" }, |
149 | |
150 | {"Vibrator" , NULL, "VIBRAL" }, |
151 | {"Vibrator" , NULL, "VIBRAR" }, |
152 | |
153 | /* Routings for inputs */ |
154 | {"HSMIC" , NULL, "Headset Mic" }, |
155 | {"Headset Mic" , NULL, "Headset Mic Bias" }, |
156 | |
157 | {"MAINMIC" , NULL, "Main Handset Mic" }, |
158 | {"Main Handset Mic" , NULL, "Main Mic Bias" }, |
159 | |
160 | {"SUBMIC" , NULL, "Sub Handset Mic" }, |
161 | {"Sub Handset Mic" , NULL, "Main Mic Bias" }, |
162 | |
163 | {"AFML" , NULL, "Line In" }, |
164 | {"AFMR" , NULL, "Line In" }, |
165 | }; |
166 | |
167 | static int omap_abe_twl6040_init(struct snd_soc_pcm_runtime *rtd) |
168 | { |
169 | struct snd_soc_component *component = snd_soc_rtd_to_codec(rtd, 0)->component; |
170 | struct snd_soc_card *card = rtd->card; |
171 | struct abe_twl6040 *priv = snd_soc_card_get_drvdata(card); |
172 | int hs_trim; |
173 | int ret; |
174 | |
175 | /* |
176 | * Configure McPDM offset cancellation based on the HSOTRIM value from |
177 | * twl6040. |
178 | */ |
179 | hs_trim = twl6040_get_trim_value(component, trim: TWL6040_TRIM_HSOTRIM); |
180 | omap_mcpdm_configure_dn_offsets(rtd, TWL6040_HSF_TRIM_LEFT(hs_trim), |
181 | TWL6040_HSF_TRIM_RIGHT(hs_trim)); |
182 | |
183 | /* Headset jack detection only if it is supported */ |
184 | if (priv->jack_detection) { |
185 | ret = snd_soc_card_jack_new_pins(card: rtd->card, id: "Headset Jack" , |
186 | type: SND_JACK_HEADSET, jack: &hs_jack, |
187 | pins: hs_jack_pins, |
188 | ARRAY_SIZE(hs_jack_pins)); |
189 | if (ret) |
190 | return ret; |
191 | |
192 | twl6040_hs_jack_detect(component, jack: &hs_jack, report: SND_JACK_HEADSET); |
193 | } |
194 | |
195 | return 0; |
196 | } |
197 | |
198 | static const struct snd_soc_dapm_route dmic_audio_map[] = { |
199 | {"DMic" , NULL, "Digital Mic" }, |
200 | {"Digital Mic" , NULL, "Digital Mic1 Bias" }, |
201 | }; |
202 | |
203 | static int omap_abe_dmic_init(struct snd_soc_pcm_runtime *rtd) |
204 | { |
205 | struct snd_soc_dapm_context *dapm = &rtd->card->dapm; |
206 | |
207 | return snd_soc_dapm_add_routes(dapm, route: dmic_audio_map, |
208 | ARRAY_SIZE(dmic_audio_map)); |
209 | } |
210 | |
211 | static int omap_abe_probe(struct platform_device *pdev) |
212 | { |
213 | struct device_node *node = pdev->dev.of_node; |
214 | struct snd_soc_card *card; |
215 | struct device_node *dai_node; |
216 | struct abe_twl6040 *priv; |
217 | int num_links = 0; |
218 | int ret = 0; |
219 | |
220 | if (!node) { |
221 | dev_err(&pdev->dev, "of node is missing.\n" ); |
222 | return -ENODEV; |
223 | } |
224 | |
225 | priv = devm_kzalloc(dev: &pdev->dev, size: sizeof(struct abe_twl6040), GFP_KERNEL); |
226 | if (priv == NULL) |
227 | return -ENOMEM; |
228 | |
229 | card = &priv->card; |
230 | card->dev = &pdev->dev; |
231 | card->owner = THIS_MODULE; |
232 | card->dapm_widgets = twl6040_dapm_widgets; |
233 | card->num_dapm_widgets = ARRAY_SIZE(twl6040_dapm_widgets); |
234 | card->dapm_routes = audio_map; |
235 | card->num_dapm_routes = ARRAY_SIZE(audio_map); |
236 | |
237 | if (snd_soc_of_parse_card_name(card, propname: "ti,model" )) { |
238 | dev_err(&pdev->dev, "Card name is not provided\n" ); |
239 | return -ENODEV; |
240 | } |
241 | |
242 | ret = snd_soc_of_parse_audio_routing(card, propname: "ti,audio-routing" ); |
243 | if (ret) { |
244 | dev_err(&pdev->dev, "Error while parsing DAPM routing\n" ); |
245 | return ret; |
246 | } |
247 | |
248 | dai_node = of_parse_phandle(np: node, phandle_name: "ti,mcpdm" , index: 0); |
249 | if (!dai_node) { |
250 | dev_err(&pdev->dev, "McPDM node is not provided\n" ); |
251 | return -EINVAL; |
252 | } |
253 | |
254 | priv->dai_links[0].name = "DMIC" ; |
255 | priv->dai_links[0].stream_name = "TWL6040" ; |
256 | priv->dai_links[0].cpus = link0_cpus; |
257 | priv->dai_links[0].num_cpus = 1; |
258 | priv->dai_links[0].cpus->of_node = dai_node; |
259 | priv->dai_links[0].platforms = link0_platforms; |
260 | priv->dai_links[0].num_platforms = 1; |
261 | priv->dai_links[0].platforms->of_node = dai_node; |
262 | priv->dai_links[0].codecs = link0_codecs; |
263 | priv->dai_links[0].num_codecs = 1; |
264 | priv->dai_links[0].init = omap_abe_twl6040_init; |
265 | priv->dai_links[0].ops = &omap_abe_ops; |
266 | |
267 | dai_node = of_parse_phandle(np: node, phandle_name: "ti,dmic" , index: 0); |
268 | if (dai_node) { |
269 | num_links = 2; |
270 | priv->dai_links[1].name = "TWL6040" ; |
271 | priv->dai_links[1].stream_name = "DMIC Capture" ; |
272 | priv->dai_links[1].cpus = link1_cpus; |
273 | priv->dai_links[1].num_cpus = 1; |
274 | priv->dai_links[1].cpus->of_node = dai_node; |
275 | priv->dai_links[1].platforms = link1_platforms; |
276 | priv->dai_links[1].num_platforms = 1; |
277 | priv->dai_links[1].platforms->of_node = dai_node; |
278 | priv->dai_links[1].codecs = link1_codecs; |
279 | priv->dai_links[1].num_codecs = 1; |
280 | priv->dai_links[1].init = omap_abe_dmic_init; |
281 | priv->dai_links[1].ops = &omap_abe_dmic_ops; |
282 | } else { |
283 | num_links = 1; |
284 | } |
285 | |
286 | priv->jack_detection = of_property_read_bool(np: node, propname: "ti,jack-detection" ); |
287 | of_property_read_u32(np: node, propname: "ti,mclk-freq" , out_value: &priv->mclk_freq); |
288 | if (!priv->mclk_freq) { |
289 | dev_err(&pdev->dev, "MCLK frequency not provided\n" ); |
290 | return -EINVAL; |
291 | } |
292 | |
293 | card->fully_routed = 1; |
294 | |
295 | card->dai_link = priv->dai_links; |
296 | card->num_links = num_links; |
297 | |
298 | snd_soc_card_set_drvdata(card, data: priv); |
299 | |
300 | ret = devm_snd_soc_register_card(dev: &pdev->dev, card); |
301 | if (ret) |
302 | dev_err(&pdev->dev, "devm_snd_soc_register_card() failed: %d\n" , |
303 | ret); |
304 | |
305 | return ret; |
306 | } |
307 | |
308 | static const struct of_device_id omap_abe_of_match[] = { |
309 | {.compatible = "ti,abe-twl6040" , }, |
310 | { }, |
311 | }; |
312 | MODULE_DEVICE_TABLE(of, omap_abe_of_match); |
313 | |
314 | static struct platform_driver omap_abe_driver = { |
315 | .driver = { |
316 | .name = "omap-abe-twl6040" , |
317 | .pm = &snd_soc_pm_ops, |
318 | .of_match_table = omap_abe_of_match, |
319 | }, |
320 | .probe = omap_abe_probe, |
321 | }; |
322 | |
323 | static int __init omap_abe_init(void) |
324 | { |
325 | int ret; |
326 | |
327 | dmic_codec_dev = platform_device_register_simple(name: "dmic-codec" , id: -1, NULL, |
328 | num: 0); |
329 | if (IS_ERR(ptr: dmic_codec_dev)) { |
330 | pr_err("%s: dmic-codec device registration failed\n" , __func__); |
331 | return PTR_ERR(ptr: dmic_codec_dev); |
332 | } |
333 | |
334 | ret = platform_driver_register(&omap_abe_driver); |
335 | if (ret) { |
336 | pr_err("%s: platform driver registration failed\n" , __func__); |
337 | platform_device_unregister(dmic_codec_dev); |
338 | } |
339 | |
340 | return ret; |
341 | } |
342 | module_init(omap_abe_init); |
343 | |
344 | static void __exit omap_abe_exit(void) |
345 | { |
346 | platform_driver_unregister(&omap_abe_driver); |
347 | platform_device_unregister(dmic_codec_dev); |
348 | } |
349 | module_exit(omap_abe_exit); |
350 | |
351 | MODULE_AUTHOR("Misael Lopez Cruz <misael.lopez@ti.com>" ); |
352 | MODULE_DESCRIPTION("ALSA SoC for OMAP boards with ABE and twl6040 codec" ); |
353 | MODULE_LICENSE("GPL" ); |
354 | MODULE_ALIAS("platform:omap-abe-twl6040" ); |
355 | |