1*d2402860SYingkun Meng // SPDX-License-Identifier: GPL-2.0
2*d2402860SYingkun Meng //
3*d2402860SYingkun Meng // Loongson ASoC Audio Machine driver
4*d2402860SYingkun Meng //
5*d2402860SYingkun Meng // Copyright (C) 2023 Loongson Technology Corporation Limited
6*d2402860SYingkun Meng // Author: Yingkun Meng <mengyingkun@loongson.cn>
7*d2402860SYingkun Meng //
8*d2402860SYingkun Meng 
9*d2402860SYingkun Meng #include <linux/module.h>
10*d2402860SYingkun Meng #include <sound/soc.h>
11*d2402860SYingkun Meng #include <sound/soc-acpi.h>
12*d2402860SYingkun Meng #include <linux/acpi.h>
13*d2402860SYingkun Meng #include <linux/pci.h>
14*d2402860SYingkun Meng #include <sound/pcm_params.h>
15*d2402860SYingkun Meng 
16*d2402860SYingkun Meng static char codec_name[SND_ACPI_I2C_ID_LEN];
17*d2402860SYingkun Meng 
18*d2402860SYingkun Meng struct loongson_card_data {
19*d2402860SYingkun Meng 	struct snd_soc_card snd_card;
20*d2402860SYingkun Meng 	unsigned int mclk_fs;
21*d2402860SYingkun Meng };
22*d2402860SYingkun Meng 
23*d2402860SYingkun Meng static int loongson_card_hw_params(struct snd_pcm_substream *substream,
24*d2402860SYingkun Meng 				   struct snd_pcm_hw_params *params)
25*d2402860SYingkun Meng {
26*d2402860SYingkun Meng 	struct snd_soc_pcm_runtime *rtd = substream->private_data;
27*d2402860SYingkun Meng 	struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0);
28*d2402860SYingkun Meng 	struct snd_soc_dai *codec_dai = asoc_rtd_to_codec(rtd, 0);
29*d2402860SYingkun Meng 	struct loongson_card_data *ls_card = snd_soc_card_get_drvdata(rtd->card);
30*d2402860SYingkun Meng 	int ret, mclk;
31*d2402860SYingkun Meng 
32*d2402860SYingkun Meng 	if (ls_card->mclk_fs) {
33*d2402860SYingkun Meng 		mclk = ls_card->mclk_fs * params_rate(params);
34*d2402860SYingkun Meng 		ret = snd_soc_dai_set_sysclk(cpu_dai, 0, mclk,
35*d2402860SYingkun Meng 					     SND_SOC_CLOCK_OUT);
36*d2402860SYingkun Meng 		if (ret < 0) {
37*d2402860SYingkun Meng 			dev_err(codec_dai->dev, "cpu_dai clock not set\n");
38*d2402860SYingkun Meng 			return ret;
39*d2402860SYingkun Meng 		}
40*d2402860SYingkun Meng 
41*d2402860SYingkun Meng 		ret = snd_soc_dai_set_sysclk(codec_dai, 0, mclk,
42*d2402860SYingkun Meng 					     SND_SOC_CLOCK_IN);
43*d2402860SYingkun Meng 		if (ret < 0) {
44*d2402860SYingkun Meng 			dev_err(codec_dai->dev, "codec_dai clock not set\n");
45*d2402860SYingkun Meng 			return ret;
46*d2402860SYingkun Meng 		}
47*d2402860SYingkun Meng 	}
48*d2402860SYingkun Meng 	return 0;
49*d2402860SYingkun Meng }
50*d2402860SYingkun Meng 
51*d2402860SYingkun Meng static const struct snd_soc_ops loongson_ops = {
52*d2402860SYingkun Meng 	.hw_params = loongson_card_hw_params,
53*d2402860SYingkun Meng };
54*d2402860SYingkun Meng 
55*d2402860SYingkun Meng SND_SOC_DAILINK_DEFS(analog,
56*d2402860SYingkun Meng 	DAILINK_COMP_ARRAY(COMP_CPU("loongson-i2s")),
57*d2402860SYingkun Meng 	DAILINK_COMP_ARRAY(COMP_EMPTY()),
58*d2402860SYingkun Meng 	DAILINK_COMP_ARRAY(COMP_EMPTY()));
59*d2402860SYingkun Meng 
60*d2402860SYingkun Meng static struct snd_soc_dai_link loongson_dai_links[] = {
61*d2402860SYingkun Meng 	{
62*d2402860SYingkun Meng 		.name = "Loongson Audio Port",
63*d2402860SYingkun Meng 		.stream_name = "Loongson Audio",
64*d2402860SYingkun Meng 		.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_IB_NF
65*d2402860SYingkun Meng 			| SND_SOC_DAIFMT_CBC_CFC,
66*d2402860SYingkun Meng 		SND_SOC_DAILINK_REG(analog),
67*d2402860SYingkun Meng 		.ops = &loongson_ops,
68*d2402860SYingkun Meng 	},
69*d2402860SYingkun Meng };
70*d2402860SYingkun Meng 
71*d2402860SYingkun Meng static int loongson_card_parse_acpi(struct loongson_card_data *data)
72*d2402860SYingkun Meng {
73*d2402860SYingkun Meng 	struct snd_soc_card *card = &data->snd_card;
74*d2402860SYingkun Meng 	struct fwnode_handle *fwnode = card->dev->fwnode;
75*d2402860SYingkun Meng 	struct fwnode_reference_args args;
76*d2402860SYingkun Meng 	const char *codec_dai_name;
77*d2402860SYingkun Meng 	struct acpi_device *adev;
78*d2402860SYingkun Meng 	struct device *phy_dev;
79*d2402860SYingkun Meng 	int ret, i;
80*d2402860SYingkun Meng 
81*d2402860SYingkun Meng 	/* fixup platform name based on reference node */
82*d2402860SYingkun Meng 	memset(&args, 0, sizeof(args));
83*d2402860SYingkun Meng 	ret = acpi_node_get_property_reference(fwnode, "cpu", 0, &args);
84*d2402860SYingkun Meng 	if (ACPI_FAILURE(ret) || !is_acpi_device_node(args.fwnode)) {
85*d2402860SYingkun Meng 		dev_err(card->dev, "No matching phy in ACPI table\n");
86*d2402860SYingkun Meng 		return ret;
87*d2402860SYingkun Meng 	}
88*d2402860SYingkun Meng 	adev = to_acpi_device_node(args.fwnode);
89*d2402860SYingkun Meng 	phy_dev = acpi_get_first_physical_node(adev);
90*d2402860SYingkun Meng 	if (!phy_dev)
91*d2402860SYingkun Meng 		return -EPROBE_DEFER;
92*d2402860SYingkun Meng 	for (i = 0; i < card->num_links; i++)
93*d2402860SYingkun Meng 		loongson_dai_links[i].platforms->name = dev_name(phy_dev);
94*d2402860SYingkun Meng 
95*d2402860SYingkun Meng 	/* fixup codec name based on reference node */
96*d2402860SYingkun Meng 	memset(&args, 0, sizeof(args));
97*d2402860SYingkun Meng 	ret = acpi_node_get_property_reference(fwnode, "codec", 0, &args);
98*d2402860SYingkun Meng 	if (ACPI_FAILURE(ret) || !is_acpi_device_node(args.fwnode)) {
99*d2402860SYingkun Meng 		dev_err(card->dev, "No matching phy in ACPI table\n");
100*d2402860SYingkun Meng 		return ret;
101*d2402860SYingkun Meng 	}
102*d2402860SYingkun Meng 	adev = to_acpi_device_node(args.fwnode);
103*d2402860SYingkun Meng 	snprintf(codec_name, sizeof(codec_name), "i2c-%s", acpi_dev_name(adev));
104*d2402860SYingkun Meng 	for (i = 0; i < card->num_links; i++)
105*d2402860SYingkun Meng 		loongson_dai_links[i].codecs->name = codec_name;
106*d2402860SYingkun Meng 
107*d2402860SYingkun Meng 	device_property_read_string(card->dev, "codec-dai-name",
108*d2402860SYingkun Meng 				    &codec_dai_name);
109*d2402860SYingkun Meng 	for (i = 0; i < card->num_links; i++)
110*d2402860SYingkun Meng 		loongson_dai_links[i].codecs->dai_name = codec_dai_name;
111*d2402860SYingkun Meng 
112*d2402860SYingkun Meng 	return 0;
113*d2402860SYingkun Meng }
114*d2402860SYingkun Meng 
115*d2402860SYingkun Meng static int loongson_card_parse_of(struct loongson_card_data *data)
116*d2402860SYingkun Meng {
117*d2402860SYingkun Meng 	const char *cpu_dai_name, *codec_dai_name;
118*d2402860SYingkun Meng 	struct device_node *cpu, *codec;
119*d2402860SYingkun Meng 	struct snd_soc_card *card = &data->snd_card;
120*d2402860SYingkun Meng 	struct device *dev = card->dev;
121*d2402860SYingkun Meng 	struct of_phandle_args args;
122*d2402860SYingkun Meng 	int ret, i;
123*d2402860SYingkun Meng 
124*d2402860SYingkun Meng 	cpu = of_get_child_by_name(dev->of_node, "cpu");
125*d2402860SYingkun Meng 	if (!cpu) {
126*d2402860SYingkun Meng 		dev_err(dev, "platform property missing or invalid\n");
127*d2402860SYingkun Meng 		return -EINVAL;
128*d2402860SYingkun Meng 	}
129*d2402860SYingkun Meng 	codec = of_get_child_by_name(dev->of_node, "codec");
130*d2402860SYingkun Meng 	if (!codec) {
131*d2402860SYingkun Meng 		dev_err(dev, "audio-codec property missing or invalid\n");
132*d2402860SYingkun Meng 		ret = -EINVAL;
133*d2402860SYingkun Meng 		goto err;
134*d2402860SYingkun Meng 	}
135*d2402860SYingkun Meng 
136*d2402860SYingkun Meng 	ret = of_parse_phandle_with_args(cpu, "sound-dai",
137*d2402860SYingkun Meng 					 "#sound-dai-cells", 0, &args);
138*d2402860SYingkun Meng 	if (ret) {
139*d2402860SYingkun Meng 		dev_err(dev, "codec node missing #sound-dai-cells\n");
140*d2402860SYingkun Meng 		goto err;
141*d2402860SYingkun Meng 	}
142*d2402860SYingkun Meng 	for (i = 0; i < card->num_links; i++)
143*d2402860SYingkun Meng 		loongson_dai_links[i].cpus->of_node = args.np;
144*d2402860SYingkun Meng 
145*d2402860SYingkun Meng 	ret = of_parse_phandle_with_args(codec, "sound-dai",
146*d2402860SYingkun Meng 					 "#sound-dai-cells", 0, &args);
147*d2402860SYingkun Meng 	if (ret) {
148*d2402860SYingkun Meng 		dev_err(dev, "codec node missing #sound-dai-cells\n");
149*d2402860SYingkun Meng 		goto err;
150*d2402860SYingkun Meng 	}
151*d2402860SYingkun Meng 	for (i = 0; i < card->num_links; i++)
152*d2402860SYingkun Meng 		loongson_dai_links[i].codecs->of_node = args.np;
153*d2402860SYingkun Meng 
154*d2402860SYingkun Meng 	snd_soc_of_get_dai_name(cpu, &cpu_dai_name);
155*d2402860SYingkun Meng 	snd_soc_of_get_dai_name(codec, &codec_dai_name);
156*d2402860SYingkun Meng 	for (i = 0; i < card->num_links; i++) {
157*d2402860SYingkun Meng 		loongson_dai_links[i].cpus->dai_name = cpu_dai_name;
158*d2402860SYingkun Meng 		loongson_dai_links[i].codecs->dai_name = codec_dai_name;
159*d2402860SYingkun Meng 	}
160*d2402860SYingkun Meng 	of_node_put(cpu);
161*d2402860SYingkun Meng 	of_node_put(codec);
162*d2402860SYingkun Meng 
163*d2402860SYingkun Meng 	return 0;
164*d2402860SYingkun Meng 
165*d2402860SYingkun Meng err:
166*d2402860SYingkun Meng 	of_node_put(cpu);
167*d2402860SYingkun Meng 	of_node_put(codec);
168*d2402860SYingkun Meng 	return ret;
169*d2402860SYingkun Meng }
170*d2402860SYingkun Meng 
171*d2402860SYingkun Meng static int loongson_asoc_card_probe(struct platform_device *pdev)
172*d2402860SYingkun Meng {
173*d2402860SYingkun Meng 	struct loongson_card_data *ls_priv;
174*d2402860SYingkun Meng 	struct snd_soc_card *card;
175*d2402860SYingkun Meng 	int ret;
176*d2402860SYingkun Meng 
177*d2402860SYingkun Meng 	ls_priv = devm_kzalloc(&pdev->dev, sizeof(*ls_priv), GFP_KERNEL);
178*d2402860SYingkun Meng 	if (!ls_priv)
179*d2402860SYingkun Meng 		return -ENOMEM;
180*d2402860SYingkun Meng 
181*d2402860SYingkun Meng 	card = &ls_priv->snd_card;
182*d2402860SYingkun Meng 
183*d2402860SYingkun Meng 	card->dev = &pdev->dev;
184*d2402860SYingkun Meng 	card->owner = THIS_MODULE;
185*d2402860SYingkun Meng 	card->dai_link = loongson_dai_links;
186*d2402860SYingkun Meng 	card->num_links = ARRAY_SIZE(loongson_dai_links);
187*d2402860SYingkun Meng 	snd_soc_card_set_drvdata(card, ls_priv);
188*d2402860SYingkun Meng 
189*d2402860SYingkun Meng 	ret = device_property_read_string(&pdev->dev, "model", &card->name);
190*d2402860SYingkun Meng 	if (ret) {
191*d2402860SYingkun Meng 		dev_err(&pdev->dev, "Error parsing card name: %d\n", ret);
192*d2402860SYingkun Meng 		return ret;
193*d2402860SYingkun Meng 	}
194*d2402860SYingkun Meng 	ret = device_property_read_u32(&pdev->dev, "mclk-fs", &ls_priv->mclk_fs);
195*d2402860SYingkun Meng 	if (ret) {
196*d2402860SYingkun Meng 		dev_err(&pdev->dev, "Error parsing mclk-fs: %d\n", ret);
197*d2402860SYingkun Meng 		return ret;
198*d2402860SYingkun Meng 	}
199*d2402860SYingkun Meng 
200*d2402860SYingkun Meng 	if (has_acpi_companion(&pdev->dev))
201*d2402860SYingkun Meng 		ret = loongson_card_parse_acpi(ls_priv);
202*d2402860SYingkun Meng 	else
203*d2402860SYingkun Meng 		ret = loongson_card_parse_of(ls_priv);
204*d2402860SYingkun Meng 	if (ret < 0)
205*d2402860SYingkun Meng 		return ret;
206*d2402860SYingkun Meng 
207*d2402860SYingkun Meng 	ret = devm_snd_soc_register_card(&pdev->dev, card);
208*d2402860SYingkun Meng 
209*d2402860SYingkun Meng 	return ret;
210*d2402860SYingkun Meng }
211*d2402860SYingkun Meng 
212*d2402860SYingkun Meng static const struct of_device_id loongson_asoc_dt_ids[] = {
213*d2402860SYingkun Meng 	{ .compatible = "loongson,ls-audio-card" },
214*d2402860SYingkun Meng 	{ /* sentinel */ },
215*d2402860SYingkun Meng };
216*d2402860SYingkun Meng MODULE_DEVICE_TABLE(of, loongson_asoc_dt_ids);
217*d2402860SYingkun Meng 
218*d2402860SYingkun Meng static struct platform_driver loongson_audio_driver = {
219*d2402860SYingkun Meng 	.probe = loongson_asoc_card_probe,
220*d2402860SYingkun Meng 	.driver = {
221*d2402860SYingkun Meng 		.name = "loongson-asoc-card",
222*d2402860SYingkun Meng 		.pm = &snd_soc_pm_ops,
223*d2402860SYingkun Meng 		.of_match_table = of_match_ptr(loongson_asoc_dt_ids),
224*d2402860SYingkun Meng 	},
225*d2402860SYingkun Meng };
226*d2402860SYingkun Meng module_platform_driver(loongson_audio_driver);
227*d2402860SYingkun Meng 
228*d2402860SYingkun Meng MODULE_DESCRIPTION("Loongson ASoc Sound Card driver");
229*d2402860SYingkun Meng MODULE_AUTHOR("Loongson Technology Corporation Limited");
230*d2402860SYingkun Meng MODULE_LICENSE("GPL");
231