Commit | Line | Data |
---|---|---|
3a280ed1 | 1 | // SPDX-License-Identifier: GPL-2.0 |
0f83f929 KC |
2 | /* |
3 | * mt8173-rt5650.c -- MT8173 machine driver with RT5650 codecs | |
4 | * | |
5 | * Copyright (c) 2016 MediaTek Inc. | |
6 | * Author: Koro Chen <koro.chen@mediatek.com> | |
0f83f929 KC |
7 | */ |
8 | ||
9 | #include <linux/module.h> | |
0f83f929 KC |
10 | #include <sound/soc.h> |
11 | #include <sound/jack.h> | |
0d1d7a66 | 12 | #include "../../codecs/rt5645.h" |
0f83f929 KC |
13 | |
14 | #define MCLK_FOR_CODECS 12288000 | |
15 | ||
0c198ed2 PL |
16 | enum mt8173_rt5650_mclk { |
17 | MT8173_RT5650_MCLK_EXTERNAL = 0, | |
18 | MT8173_RT5650_MCLK_INTERNAL, | |
19 | }; | |
20 | ||
21 | struct mt8173_rt5650_platform_data { | |
22 | enum mt8173_rt5650_mclk pll_from; | |
23 | /* 0 = external oscillator; 1 = internal source from mt8173 */ | |
24 | }; | |
25 | ||
26 | static struct mt8173_rt5650_platform_data mt8173_rt5650_priv = { | |
27 | .pll_from = MT8173_RT5650_MCLK_EXTERNAL, | |
28 | }; | |
29 | ||
0f83f929 | 30 | static const struct snd_soc_dapm_widget mt8173_rt5650_widgets[] = { |
0a8facac | 31 | SND_SOC_DAPM_SPK("Ext Spk", NULL), |
0f83f929 KC |
32 | SND_SOC_DAPM_MIC("Int Mic", NULL), |
33 | SND_SOC_DAPM_HP("Headphone", NULL), | |
34 | SND_SOC_DAPM_MIC("Headset Mic", NULL), | |
35 | }; | |
36 | ||
37 | static const struct snd_soc_dapm_route mt8173_rt5650_routes[] = { | |
0a8facac ADR |
38 | {"Ext Spk", NULL, "SPOL"}, |
39 | {"Ext Spk", NULL, "SPOR"}, | |
0f83f929 KC |
40 | {"DMIC L1", NULL, "Int Mic"}, |
41 | {"DMIC R1", NULL, "Int Mic"}, | |
42 | {"Headphone", NULL, "HPOL"}, | |
43 | {"Headphone", NULL, "HPOR"}, | |
0f83f929 KC |
44 | {"IN1P", NULL, "Headset Mic"}, |
45 | {"IN1N", NULL, "Headset Mic"}, | |
46 | }; | |
47 | ||
48 | static const struct snd_kcontrol_new mt8173_rt5650_controls[] = { | |
0a8facac | 49 | SOC_DAPM_PIN_SWITCH("Ext Spk"), |
0f83f929 KC |
50 | SOC_DAPM_PIN_SWITCH("Int Mic"), |
51 | SOC_DAPM_PIN_SWITCH("Headphone"), | |
52 | SOC_DAPM_PIN_SWITCH("Headset Mic"), | |
53 | }; | |
54 | ||
0ef5533f ANY |
55 | static struct snd_soc_jack_pin mt8173_rt5650_jack_pins[] = { |
56 | { | |
57 | .pin = "Headphone", | |
58 | .mask = SND_JACK_HEADPHONE, | |
59 | }, | |
60 | { | |
61 | .pin = "Headset Mic", | |
62 | .mask = SND_JACK_MICROPHONE, | |
63 | }, | |
64 | }; | |
65 | ||
0f83f929 KC |
66 | static int mt8173_rt5650_hw_params(struct snd_pcm_substream *substream, |
67 | struct snd_pcm_hw_params *params) | |
68 | { | |
de9e7013 | 69 | struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); |
0c198ed2 | 70 | unsigned int mclk_clock; |
0b7990e3 | 71 | struct snd_soc_dai *codec_dai; |
0f83f929 KC |
72 | int i, ret; |
73 | ||
0c198ed2 PL |
74 | switch (mt8173_rt5650_priv.pll_from) { |
75 | case MT8173_RT5650_MCLK_EXTERNAL: | |
76 | /* mclk = 12.288M */ | |
77 | mclk_clock = MCLK_FOR_CODECS; | |
78 | break; | |
79 | case MT8173_RT5650_MCLK_INTERNAL: | |
80 | /* mclk = sampling rate*256 */ | |
81 | mclk_clock = params_rate(params) * 256; | |
82 | break; | |
83 | default: | |
84 | /* mclk = 12.288M */ | |
85 | mclk_clock = MCLK_FOR_CODECS; | |
86 | break; | |
87 | } | |
88 | ||
c8654520 | 89 | for_each_rtd_codec_dais(rtd, i, codec_dai) { |
0c198ed2 PL |
90 | /* pll from mclk */ |
91 | ret = snd_soc_dai_set_pll(codec_dai, 0, 0, mclk_clock, | |
0f83f929 KC |
92 | params_rate(params) * 512); |
93 | if (ret) | |
94 | return ret; | |
95 | ||
96 | /* sysclk from pll */ | |
97 | ret = snd_soc_dai_set_sysclk(codec_dai, 1, | |
98 | params_rate(params) * 512, | |
99 | SND_SOC_CLOCK_IN); | |
100 | if (ret) | |
101 | return ret; | |
102 | } | |
103 | return 0; | |
104 | } | |
105 | ||
424dfbf2 | 106 | static const struct snd_soc_ops mt8173_rt5650_ops = { |
0f83f929 KC |
107 | .hw_params = mt8173_rt5650_hw_params, |
108 | }; | |
109 | ||
c8b60c6d | 110 | static struct snd_soc_jack mt8173_rt5650_jack, mt8173_rt5650_hdmi_jack; |
0f83f929 KC |
111 | |
112 | static int mt8173_rt5650_init(struct snd_soc_pcm_runtime *runtime) | |
113 | { | |
114 | struct snd_soc_card *card = runtime->card; | |
de9e7013 KM |
115 | struct snd_soc_component *component = snd_soc_rtd_to_codec(runtime, 0)->component; |
116 | const char *codec_capture_dai = snd_soc_rtd_to_codec(runtime, 1)->name; | |
0f83f929 KC |
117 | int ret; |
118 | ||
79223bf1 | 119 | rt5645_sel_asrc_clk_src(component, |
d349caeb | 120 | RT5645_DA_STEREO_FILTER, |
0f83f929 | 121 | RT5645_CLK_SEL_I2S1_ASRC); |
d349caeb PL |
122 | |
123 | if (!strcmp(codec_capture_dai, "rt5645-aif1")) { | |
79223bf1 | 124 | rt5645_sel_asrc_clk_src(component, |
d349caeb PL |
125 | RT5645_AD_STEREO_FILTER, |
126 | RT5645_CLK_SEL_I2S1_ASRC); | |
127 | } else if (!strcmp(codec_capture_dai, "rt5645-aif2")) { | |
79223bf1 | 128 | rt5645_sel_asrc_clk_src(component, |
d349caeb PL |
129 | RT5645_AD_STEREO_FILTER, |
130 | RT5645_CLK_SEL_I2S2_ASRC); | |
131 | } else { | |
132 | dev_warn(card->dev, | |
133 | "Only one dai codec found in DTS, enabled rt5645 AD filter\n"); | |
79223bf1 | 134 | rt5645_sel_asrc_clk_src(component, |
d349caeb PL |
135 | RT5645_AD_STEREO_FILTER, |
136 | RT5645_CLK_SEL_I2S1_ASRC); | |
137 | } | |
138 | ||
0f83f929 | 139 | /* enable jack detection */ |
0ef5533f ANY |
140 | ret = snd_soc_card_jack_new_pins(card, "Headset Jack", |
141 | SND_JACK_HEADPHONE | SND_JACK_MICROPHONE | | |
142 | SND_JACK_BTN_0 | SND_JACK_BTN_1 | | |
143 | SND_JACK_BTN_2 | SND_JACK_BTN_3, | |
144 | &mt8173_rt5650_jack, | |
145 | mt8173_rt5650_jack_pins, | |
146 | ARRAY_SIZE(mt8173_rt5650_jack_pins)); | |
0f83f929 KC |
147 | if (ret) { |
148 | dev_err(card->dev, "Can't new Headset Jack %d\n", ret); | |
149 | return ret; | |
150 | } | |
151 | ||
79223bf1 | 152 | return rt5645_set_jack_detect(component, |
0f83f929 KC |
153 | &mt8173_rt5650_jack, |
154 | &mt8173_rt5650_jack, | |
155 | &mt8173_rt5650_jack); | |
156 | } | |
157 | ||
c8b60c6d TBS |
158 | static int mt8173_rt5650_hdmi_init(struct snd_soc_pcm_runtime *rtd) |
159 | { | |
160 | int ret; | |
161 | ||
162 | ret = snd_soc_card_jack_new(rtd->card, "HDMI Jack", SND_JACK_LINEOUT, | |
19aed2d6 | 163 | &mt8173_rt5650_hdmi_jack); |
c8b60c6d TBS |
164 | if (ret) |
165 | return ret; | |
166 | ||
de9e7013 | 167 | return snd_soc_component_set_jack(snd_soc_rtd_to_codec(rtd, 0)->component, |
55c5cc63 | 168 | &mt8173_rt5650_hdmi_jack, NULL); |
c8b60c6d TBS |
169 | } |
170 | ||
0f83f929 KC |
171 | enum { |
172 | DAI_LINK_PLAYBACK, | |
173 | DAI_LINK_CAPTURE, | |
97e1145a | 174 | DAI_LINK_HDMI, |
0f83f929 | 175 | DAI_LINK_CODEC_I2S, |
97e1145a | 176 | DAI_LINK_HDMI_I2S, |
0f83f929 KC |
177 | }; |
178 | ||
166b3f05 KM |
179 | SND_SOC_DAILINK_DEFS(playback, |
180 | DAILINK_COMP_ARRAY(COMP_CPU("DL1")), | |
181 | DAILINK_COMP_ARRAY(COMP_DUMMY()), | |
182 | DAILINK_COMP_ARRAY(COMP_EMPTY())); | |
183 | ||
184 | SND_SOC_DAILINK_DEFS(capture, | |
185 | DAILINK_COMP_ARRAY(COMP_CPU("VUL")), | |
186 | DAILINK_COMP_ARRAY(COMP_DUMMY()), | |
187 | DAILINK_COMP_ARRAY(COMP_EMPTY())); | |
188 | ||
189 | SND_SOC_DAILINK_DEFS(hdmi_pcm, | |
190 | DAILINK_COMP_ARRAY(COMP_CPU("HDMI")), | |
191 | DAILINK_COMP_ARRAY(COMP_DUMMY()), | |
192 | DAILINK_COMP_ARRAY(COMP_EMPTY())); | |
193 | ||
194 | SND_SOC_DAILINK_DEFS(codec, | |
195 | DAILINK_COMP_ARRAY(COMP_CPU("I2S")), | |
196 | DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "rt5645-aif1"), /* Playback */ | |
197 | COMP_CODEC(NULL, "rt5645-aif1")),/* Capture */ | |
198 | DAILINK_COMP_ARRAY(COMP_EMPTY())); | |
199 | ||
200 | SND_SOC_DAILINK_DEFS(hdmi_be, | |
201 | DAILINK_COMP_ARRAY(COMP_CPU("HDMIO")), | |
202 | DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "i2s-hifi")), | |
203 | DAILINK_COMP_ARRAY(COMP_EMPTY())); | |
204 | ||
0f83f929 KC |
205 | /* Digital audio interface glue - connects codec <---> CPU */ |
206 | static struct snd_soc_dai_link mt8173_rt5650_dais[] = { | |
207 | /* Front End DAI links */ | |
208 | [DAI_LINK_PLAYBACK] = { | |
209 | .name = "rt5650 Playback", | |
210 | .stream_name = "rt5650 Playback", | |
0f83f929 KC |
211 | .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, |
212 | .dynamic = 1, | |
213 | .dpcm_playback = 1, | |
166b3f05 | 214 | SND_SOC_DAILINK_REG(playback), |
0f83f929 KC |
215 | }, |
216 | [DAI_LINK_CAPTURE] = { | |
217 | .name = "rt5650 Capture", | |
218 | .stream_name = "rt5650 Capture", | |
0f83f929 KC |
219 | .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, |
220 | .dynamic = 1, | |
221 | .dpcm_capture = 1, | |
166b3f05 | 222 | SND_SOC_DAILINK_REG(capture), |
0f83f929 | 223 | }, |
97e1145a PL |
224 | [DAI_LINK_HDMI] = { |
225 | .name = "HDMI", | |
226 | .stream_name = "HDMI PCM", | |
97e1145a PL |
227 | .trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST}, |
228 | .dynamic = 1, | |
229 | .dpcm_playback = 1, | |
166b3f05 | 230 | SND_SOC_DAILINK_REG(hdmi_pcm), |
97e1145a | 231 | }, |
0f83f929 KC |
232 | /* Back End DAI links */ |
233 | [DAI_LINK_CODEC_I2S] = { | |
234 | .name = "Codec", | |
0f83f929 | 235 | .no_pcm = 1, |
0f83f929 KC |
236 | .init = mt8173_rt5650_init, |
237 | .dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF | | |
238 | SND_SOC_DAIFMT_CBS_CFS, | |
239 | .ops = &mt8173_rt5650_ops, | |
240 | .ignore_pmdown_time = 1, | |
241 | .dpcm_playback = 1, | |
242 | .dpcm_capture = 1, | |
166b3f05 | 243 | SND_SOC_DAILINK_REG(codec), |
0f83f929 | 244 | }, |
97e1145a PL |
245 | [DAI_LINK_HDMI_I2S] = { |
246 | .name = "HDMI BE", | |
97e1145a | 247 | .no_pcm = 1, |
97e1145a | 248 | .dpcm_playback = 1, |
c8b60c6d | 249 | .init = mt8173_rt5650_hdmi_init, |
166b3f05 | 250 | SND_SOC_DAILINK_REG(hdmi_be), |
97e1145a | 251 | }, |
0f83f929 KC |
252 | }; |
253 | ||
254 | static struct snd_soc_card mt8173_rt5650_card = { | |
255 | .name = "mtk-rt5650", | |
256 | .owner = THIS_MODULE, | |
257 | .dai_link = mt8173_rt5650_dais, | |
258 | .num_links = ARRAY_SIZE(mt8173_rt5650_dais), | |
259 | .controls = mt8173_rt5650_controls, | |
260 | .num_controls = ARRAY_SIZE(mt8173_rt5650_controls), | |
261 | .dapm_widgets = mt8173_rt5650_widgets, | |
262 | .num_dapm_widgets = ARRAY_SIZE(mt8173_rt5650_widgets), | |
263 | .dapm_routes = mt8173_rt5650_routes, | |
264 | .num_dapm_routes = ARRAY_SIZE(mt8173_rt5650_routes), | |
265 | }; | |
266 | ||
267 | static int mt8173_rt5650_dev_probe(struct platform_device *pdev) | |
268 | { | |
269 | struct snd_soc_card *card = &mt8173_rt5650_card; | |
270 | struct device_node *platform_node; | |
d349caeb PL |
271 | struct device_node *np; |
272 | const char *codec_capture_dai; | |
7fe072b4 | 273 | struct snd_soc_dai_link *dai_link; |
0f83f929 KC |
274 | int i, ret; |
275 | ||
276 | platform_node = of_parse_phandle(pdev->dev.of_node, | |
277 | "mediatek,platform", 0); | |
278 | if (!platform_node) { | |
279 | dev_err(&pdev->dev, "Property 'platform' missing or invalid\n"); | |
280 | return -EINVAL; | |
281 | } | |
282 | ||
7fe072b4 | 283 | for_each_card_prelinks(card, i, dai_link) { |
166b3f05 | 284 | if (dai_link->platforms->name) |
0f83f929 | 285 | continue; |
166b3f05 | 286 | dai_link->platforms->of_node = platform_node; |
0f83f929 KC |
287 | } |
288 | ||
166b3f05 | 289 | mt8173_rt5650_dais[DAI_LINK_CODEC_I2S].codecs[0].of_node = |
0f83f929 | 290 | of_parse_phandle(pdev->dev.of_node, "mediatek,audio-codec", 0); |
166b3f05 | 291 | if (!mt8173_rt5650_dais[DAI_LINK_CODEC_I2S].codecs[0].of_node) { |
0f83f929 KC |
292 | dev_err(&pdev->dev, |
293 | "Property 'audio-codec' missing or invalid\n"); | |
efe2178d ML |
294 | ret = -EINVAL; |
295 | goto put_platform_node; | |
0f83f929 | 296 | } |
166b3f05 KM |
297 | mt8173_rt5650_dais[DAI_LINK_CODEC_I2S].codecs[1].of_node = |
298 | mt8173_rt5650_dais[DAI_LINK_CODEC_I2S].codecs[0].of_node; | |
d349caeb | 299 | |
3a0a7b26 JH |
300 | np = of_get_child_by_name(pdev->dev.of_node, "codec-capture"); |
301 | if (np) { | |
3c8b5861 | 302 | ret = snd_soc_of_get_dai_name(np, &codec_capture_dai, 0); |
3a0a7b26 | 303 | of_node_put(np); |
d349caeb PL |
304 | if (ret < 0) { |
305 | dev_err(&pdev->dev, | |
306 | "%s codec_capture_dai name fail %d\n", | |
307 | __func__, ret); | |
efe2178d | 308 | goto put_platform_node; |
d349caeb | 309 | } |
166b3f05 KM |
310 | mt8173_rt5650_dais[DAI_LINK_CODEC_I2S].codecs[1].dai_name = |
311 | codec_capture_dai; | |
d349caeb PL |
312 | } |
313 | ||
0c198ed2 PL |
314 | if (device_property_present(&pdev->dev, "mediatek,mclk")) { |
315 | ret = device_property_read_u32(&pdev->dev, | |
316 | "mediatek,mclk", | |
317 | &mt8173_rt5650_priv.pll_from); | |
318 | if (ret) { | |
319 | dev_err(&pdev->dev, | |
320 | "%s snd_soc_register_card fail %d\n", | |
321 | __func__, ret); | |
322 | } | |
323 | } | |
324 | ||
166b3f05 | 325 | mt8173_rt5650_dais[DAI_LINK_HDMI_I2S].codecs->of_node = |
97e1145a | 326 | of_parse_phandle(pdev->dev.of_node, "mediatek,audio-codec", 1); |
166b3f05 | 327 | if (!mt8173_rt5650_dais[DAI_LINK_HDMI_I2S].codecs->of_node) { |
97e1145a PL |
328 | dev_err(&pdev->dev, |
329 | "Property 'audio-codec' missing or invalid\n"); | |
efe2178d ML |
330 | ret = -EINVAL; |
331 | goto put_platform_node; | |
97e1145a | 332 | } |
0f83f929 | 333 | card->dev = &pdev->dev; |
0f83f929 KC |
334 | |
335 | ret = devm_snd_soc_register_card(&pdev->dev, card); | |
49343378 | 336 | |
efe2178d | 337 | put_platform_node: |
49343378 | 338 | of_node_put(platform_node); |
0f83f929 KC |
339 | return ret; |
340 | } | |
341 | ||
342 | static const struct of_device_id mt8173_rt5650_dt_match[] = { | |
343 | { .compatible = "mediatek,mt8173-rt5650", }, | |
344 | { } | |
345 | }; | |
346 | MODULE_DEVICE_TABLE(of, mt8173_rt5650_dt_match); | |
347 | ||
348 | static struct platform_driver mt8173_rt5650_driver = { | |
349 | .driver = { | |
350 | .name = "mtk-rt5650", | |
351 | .of_match_table = mt8173_rt5650_dt_match, | |
0f83f929 | 352 | .pm = &snd_soc_pm_ops, |
0f83f929 KC |
353 | }, |
354 | .probe = mt8173_rt5650_dev_probe, | |
355 | }; | |
356 | ||
357 | module_platform_driver(mt8173_rt5650_driver); | |
358 | ||
359 | /* Module information */ | |
360 | MODULE_DESCRIPTION("MT8173 RT5650 SoC machine driver"); | |
361 | MODULE_AUTHOR("Koro Chen <koro.chen@mediatek.com>"); | |
362 | MODULE_LICENSE("GPL v2"); | |
363 | MODULE_ALIAS("platform:mtk-rt5650"); | |
364 |