1// SPDX-License-Identifier: GPL-2.0
2/*
3 * mt8173-rt5650-rt5514.c  --  MT8173 machine driver with RT5650/5514 codecs
4 *
5 * Copyright (c) 2016 MediaTek Inc.
6 * Author: Koro Chen <koro.chen@mediatek.com>
7 */
8
9#include <linux/module.h>
10#include <linux/gpio.h>
11#include <linux/of_gpio.h>
12#include <sound/soc.h>
13#include <sound/jack.h>
14#include "../../codecs/rt5645.h"
15
16#define MCLK_FOR_CODECS		12288000
17
18static const struct snd_soc_dapm_widget mt8173_rt5650_rt5514_widgets[] = {
19	SND_SOC_DAPM_SPK("Speaker", NULL),
20	SND_SOC_DAPM_MIC("Int Mic", NULL),
21	SND_SOC_DAPM_HP("Headphone", NULL),
22	SND_SOC_DAPM_MIC("Headset Mic", NULL),
23};
24
25static const struct snd_soc_dapm_route mt8173_rt5650_rt5514_routes[] = {
26	{"Speaker", NULL, "SPOL"},
27	{"Speaker", NULL, "SPOR"},
28	{"Sub DMIC1L", NULL, "Int Mic"},
29	{"Sub DMIC1R", NULL, "Int Mic"},
30	{"Headphone", NULL, "HPOL"},
31	{"Headphone", NULL, "HPOR"},
32	{"IN1P", NULL, "Headset Mic"},
33	{"IN1N", NULL, "Headset Mic"},
34};
35
36static const struct snd_kcontrol_new mt8173_rt5650_rt5514_controls[] = {
37	SOC_DAPM_PIN_SWITCH("Speaker"),
38	SOC_DAPM_PIN_SWITCH("Int Mic"),
39	SOC_DAPM_PIN_SWITCH("Headphone"),
40	SOC_DAPM_PIN_SWITCH("Headset Mic"),
41};
42
43static struct snd_soc_jack_pin mt8173_rt5650_rt5514_jack_pins[] = {
44	{
45		.pin	= "Headphone",
46		.mask	= SND_JACK_HEADPHONE,
47	},
48	{
49		.pin	= "Headset Mic",
50		.mask	= SND_JACK_MICROPHONE,
51	},
52};
53
54static int mt8173_rt5650_rt5514_hw_params(struct snd_pcm_substream *substream,
55					  struct snd_pcm_hw_params *params)
56{
57	struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
58	struct snd_soc_dai *codec_dai;
59	int i, ret;
60
61	for_each_rtd_codec_dais(rtd, i, codec_dai) {
62		/* pll from mclk 12.288M */
63		ret = snd_soc_dai_set_pll(codec_dai, 0, 0, MCLK_FOR_CODECS,
64					  params_rate(params) * 512);
65		if (ret)
66			return ret;
67
68		/* sysclk from pll */
69		ret = snd_soc_dai_set_sysclk(codec_dai, 1,
70					     params_rate(params) * 512,
71					     SND_SOC_CLOCK_IN);
72		if (ret)
73			return ret;
74	}
75	return 0;
76}
77
78static const struct snd_soc_ops mt8173_rt5650_rt5514_ops = {
79	.hw_params = mt8173_rt5650_rt5514_hw_params,
80};
81
82static struct snd_soc_jack mt8173_rt5650_rt5514_jack;
83
84static int mt8173_rt5650_rt5514_init(struct snd_soc_pcm_runtime *runtime)
85{
86	struct snd_soc_card *card = runtime->card;
87	struct snd_soc_component *component = asoc_rtd_to_codec(runtime, 0)->component;
88	int ret;
89
90	rt5645_sel_asrc_clk_src(component,
91				RT5645_DA_STEREO_FILTER |
92				RT5645_AD_STEREO_FILTER,
93				RT5645_CLK_SEL_I2S1_ASRC);
94
95	/* enable jack detection */
96	ret = snd_soc_card_jack_new_pins(card, "Headset Jack",
97					 SND_JACK_HEADPHONE | SND_JACK_MICROPHONE |
98					 SND_JACK_BTN_0 | SND_JACK_BTN_1 |
99					 SND_JACK_BTN_2 | SND_JACK_BTN_3,
100					 &mt8173_rt5650_rt5514_jack,
101					 mt8173_rt5650_rt5514_jack_pins,
102					 ARRAY_SIZE(mt8173_rt5650_rt5514_jack_pins));
103	if (ret) {
104		dev_err(card->dev, "Can't new Headset Jack %d\n", ret);
105		return ret;
106	}
107
108	return rt5645_set_jack_detect(component,
109				      &mt8173_rt5650_rt5514_jack,
110				      &mt8173_rt5650_rt5514_jack,
111				      &mt8173_rt5650_rt5514_jack);
112}
113
114enum {
115	DAI_LINK_PLAYBACK,
116	DAI_LINK_CAPTURE,
117	DAI_LINK_CODEC_I2S,
118};
119
120SND_SOC_DAILINK_DEFS(playback,
121	DAILINK_COMP_ARRAY(COMP_CPU("DL1")),
122	DAILINK_COMP_ARRAY(COMP_DUMMY()),
123	DAILINK_COMP_ARRAY(COMP_EMPTY()));
124
125SND_SOC_DAILINK_DEFS(capture,
126	DAILINK_COMP_ARRAY(COMP_CPU("VUL")),
127	DAILINK_COMP_ARRAY(COMP_DUMMY()),
128	DAILINK_COMP_ARRAY(COMP_EMPTY()));
129
130SND_SOC_DAILINK_DEFS(codec,
131	DAILINK_COMP_ARRAY(COMP_CPU("I2S")),
132	DAILINK_COMP_ARRAY(COMP_CODEC(NULL, "rt5645-aif1"),
133			   COMP_CODEC(NULL, "rt5514-aif1")),
134	DAILINK_COMP_ARRAY(COMP_EMPTY()));
135
136/* Digital audio interface glue - connects codec <---> CPU */
137static struct snd_soc_dai_link mt8173_rt5650_rt5514_dais[] = {
138	/* Front End DAI links */
139	[DAI_LINK_PLAYBACK] = {
140		.name = "rt5650_rt5514 Playback",
141		.stream_name = "rt5650_rt5514 Playback",
142		.trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
143		.dynamic = 1,
144		.dpcm_playback = 1,
145		SND_SOC_DAILINK_REG(playback),
146	},
147	[DAI_LINK_CAPTURE] = {
148		.name = "rt5650_rt5514 Capture",
149		.stream_name = "rt5650_rt5514 Capture",
150		.trigger = {SND_SOC_DPCM_TRIGGER_POST, SND_SOC_DPCM_TRIGGER_POST},
151		.dynamic = 1,
152		.dpcm_capture = 1,
153		SND_SOC_DAILINK_REG(capture),
154	},
155	/* Back End DAI links */
156	[DAI_LINK_CODEC_I2S] = {
157		.name = "Codec",
158		.no_pcm = 1,
159		.init = mt8173_rt5650_rt5514_init,
160		.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
161			   SND_SOC_DAIFMT_CBS_CFS,
162		.ops = &mt8173_rt5650_rt5514_ops,
163		.ignore_pmdown_time = 1,
164		.dpcm_playback = 1,
165		.dpcm_capture = 1,
166		SND_SOC_DAILINK_REG(codec),
167	},
168};
169
170static struct snd_soc_codec_conf mt8173_rt5650_rt5514_codec_conf[] = {
171	{
172		.name_prefix = "Sub",
173	},
174};
175
176static struct snd_soc_card mt8173_rt5650_rt5514_card = {
177	.name = "mtk-rt5650-rt5514",
178	.owner = THIS_MODULE,
179	.dai_link = mt8173_rt5650_rt5514_dais,
180	.num_links = ARRAY_SIZE(mt8173_rt5650_rt5514_dais),
181	.codec_conf = mt8173_rt5650_rt5514_codec_conf,
182	.num_configs = ARRAY_SIZE(mt8173_rt5650_rt5514_codec_conf),
183	.controls = mt8173_rt5650_rt5514_controls,
184	.num_controls = ARRAY_SIZE(mt8173_rt5650_rt5514_controls),
185	.dapm_widgets = mt8173_rt5650_rt5514_widgets,
186	.num_dapm_widgets = ARRAY_SIZE(mt8173_rt5650_rt5514_widgets),
187	.dapm_routes = mt8173_rt5650_rt5514_routes,
188	.num_dapm_routes = ARRAY_SIZE(mt8173_rt5650_rt5514_routes),
189};
190
191static int mt8173_rt5650_rt5514_dev_probe(struct platform_device *pdev)
192{
193	struct snd_soc_card *card = &mt8173_rt5650_rt5514_card;
194	struct device_node *platform_node;
195	struct snd_soc_dai_link *dai_link;
196	int i, ret;
197
198	platform_node = of_parse_phandle(pdev->dev.of_node,
199					 "mediatek,platform", 0);
200	if (!platform_node) {
201		dev_err(&pdev->dev, "Property 'platform' missing or invalid\n");
202		return -EINVAL;
203	}
204
205	for_each_card_prelinks(card, i, dai_link) {
206		if (dai_link->platforms->name)
207			continue;
208		dai_link->platforms->of_node = platform_node;
209	}
210
211	mt8173_rt5650_rt5514_dais[DAI_LINK_CODEC_I2S].codecs[0].of_node =
212		of_parse_phandle(pdev->dev.of_node, "mediatek,audio-codec", 0);
213	if (!mt8173_rt5650_rt5514_dais[DAI_LINK_CODEC_I2S].codecs[0].of_node) {
214		dev_err(&pdev->dev,
215			"Property 'audio-codec' missing or invalid\n");
216		ret = -EINVAL;
217		goto out;
218	}
219	mt8173_rt5650_rt5514_dais[DAI_LINK_CODEC_I2S].codecs[1].of_node =
220		of_parse_phandle(pdev->dev.of_node, "mediatek,audio-codec", 1);
221	if (!mt8173_rt5650_rt5514_dais[DAI_LINK_CODEC_I2S].codecs[1].of_node) {
222		dev_err(&pdev->dev,
223			"Property 'audio-codec' missing or invalid\n");
224		ret = -EINVAL;
225		goto out;
226	}
227	mt8173_rt5650_rt5514_codec_conf[0].dlc.of_node =
228		mt8173_rt5650_rt5514_dais[DAI_LINK_CODEC_I2S].codecs[1].of_node;
229
230	card->dev = &pdev->dev;
231
232	ret = devm_snd_soc_register_card(&pdev->dev, card);
233
234out:
235	of_node_put(platform_node);
236	return ret;
237}
238
239static const struct of_device_id mt8173_rt5650_rt5514_dt_match[] = {
240	{ .compatible = "mediatek,mt8173-rt5650-rt5514", },
241	{ }
242};
243MODULE_DEVICE_TABLE(of, mt8173_rt5650_rt5514_dt_match);
244
245static struct platform_driver mt8173_rt5650_rt5514_driver = {
246	.driver = {
247		   .name = "mtk-rt5650-rt5514",
248		   .of_match_table = mt8173_rt5650_rt5514_dt_match,
249		   .pm = &snd_soc_pm_ops,
250	},
251	.probe = mt8173_rt5650_rt5514_dev_probe,
252};
253
254module_platform_driver(mt8173_rt5650_rt5514_driver);
255
256/* Module information */
257MODULE_DESCRIPTION("MT8173 RT5650 and RT5514 SoC machine driver");
258MODULE_AUTHOR("Koro Chen <koro.chen@mediatek.com>");
259MODULE_LICENSE("GPL v2");
260MODULE_ALIAS("platform:mtk-rt5650-rt5514");
261
262