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