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 
loongson_card_hw_params(struct snd_pcm_substream * substream,struct snd_pcm_hw_params * params)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 
loongson_card_parse_acpi(struct loongson_card_data * data)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);
848fba13f0SDan Carpenter 	if (ret || !is_acpi_device_node(args.fwnode)) {
85d2402860SYingkun Meng 		dev_err(card->dev, "No matching phy in ACPI table\n");
868fba13f0SDan 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);
988fba13f0SDan Carpenter 	if (ret || !is_acpi_device_node(args.fwnode)) {
99d2402860SYingkun Meng 		dev_err(card->dev, "No matching phy in ACPI table\n");
1008fba13f0SDan 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 
loongson_card_parse_of(struct loongson_card_data * data)115d2402860SYingkun Meng static int loongson_card_parse_of(struct loongson_card_data *data)
116d2402860SYingkun Meng {
117d2402860SYingkun Meng 	struct device_node *cpu, *codec;
118d2402860SYingkun Meng 	struct snd_soc_card *card = &data->snd_card;
119d2402860SYingkun Meng 	struct device *dev = card->dev;
120d2402860SYingkun Meng 	int ret, i;
121d2402860SYingkun Meng 
122d2402860SYingkun Meng 	cpu = of_get_child_by_name(dev->of_node, "cpu");
123d2402860SYingkun Meng 	if (!cpu) {
124d2402860SYingkun Meng 		dev_err(dev, "platform property missing or invalid\n");
125d2402860SYingkun Meng 		return -EINVAL;
126d2402860SYingkun Meng 	}
127d2402860SYingkun Meng 	codec = of_get_child_by_name(dev->of_node, "codec");
128d2402860SYingkun Meng 	if (!codec) {
129d2402860SYingkun Meng 		dev_err(dev, "audio-codec property missing or invalid\n");
130d2402860SYingkun Meng 		ret = -EINVAL;
131d2402860SYingkun Meng 		goto err;
132d2402860SYingkun Meng 	}
133d2402860SYingkun Meng 
134d2402860SYingkun Meng 	for (i = 0; i < card->num_links; i++) {
135db588ea1SKuninori Morimoto 		ret = snd_soc_of_get_dlc(cpu, NULL, loongson_dai_links[i].cpus, 0);
136db588ea1SKuninori Morimoto 		if (ret < 0) {
137db588ea1SKuninori Morimoto 			dev_err(dev, "getting cpu dlc error (%d)\n", ret);
138db588ea1SKuninori Morimoto 			goto err;
139d2402860SYingkun Meng 		}
140db588ea1SKuninori Morimoto 
141db588ea1SKuninori Morimoto 		ret = snd_soc_of_get_dlc(codec, NULL, loongson_dai_links[i].codecs, 0);
142db588ea1SKuninori Morimoto 		if (ret < 0) {
143db588ea1SKuninori Morimoto 			dev_err(dev, "getting codec dlc error (%d)\n", ret);
144db588ea1SKuninori Morimoto 			goto err;
145db588ea1SKuninori Morimoto 		}
146db588ea1SKuninori Morimoto 	}
147db588ea1SKuninori Morimoto 
148d2402860SYingkun Meng 	of_node_put(cpu);
149d2402860SYingkun Meng 	of_node_put(codec);
150d2402860SYingkun Meng 
151d2402860SYingkun Meng 	return 0;
152d2402860SYingkun Meng 
153d2402860SYingkun Meng err:
154d2402860SYingkun Meng 	of_node_put(cpu);
155d2402860SYingkun Meng 	of_node_put(codec);
156d2402860SYingkun Meng 	return ret;
157d2402860SYingkun Meng }
158d2402860SYingkun Meng 
loongson_asoc_card_probe(struct platform_device * pdev)159d2402860SYingkun Meng static int loongson_asoc_card_probe(struct platform_device *pdev)
160d2402860SYingkun Meng {
161d2402860SYingkun Meng 	struct loongson_card_data *ls_priv;
162d2402860SYingkun Meng 	struct snd_soc_card *card;
163d2402860SYingkun Meng 	int ret;
164d2402860SYingkun Meng 
165d2402860SYingkun Meng 	ls_priv = devm_kzalloc(&pdev->dev, sizeof(*ls_priv), GFP_KERNEL);
166d2402860SYingkun Meng 	if (!ls_priv)
167d2402860SYingkun Meng 		return -ENOMEM;
168d2402860SYingkun Meng 
169d2402860SYingkun Meng 	card = &ls_priv->snd_card;
170d2402860SYingkun Meng 
171d2402860SYingkun Meng 	card->dev = &pdev->dev;
172d2402860SYingkun Meng 	card->owner = THIS_MODULE;
173d2402860SYingkun Meng 	card->dai_link = loongson_dai_links;
174d2402860SYingkun Meng 	card->num_links = ARRAY_SIZE(loongson_dai_links);
175d2402860SYingkun Meng 	snd_soc_card_set_drvdata(card, ls_priv);
176d2402860SYingkun Meng 
177d2402860SYingkun Meng 	ret = device_property_read_string(&pdev->dev, "model", &card->name);
178d2402860SYingkun Meng 	if (ret) {
179d2402860SYingkun Meng 		dev_err(&pdev->dev, "Error parsing card name: %d\n", ret);
180d2402860SYingkun Meng 		return ret;
181d2402860SYingkun Meng 	}
182d2402860SYingkun Meng 	ret = device_property_read_u32(&pdev->dev, "mclk-fs", &ls_priv->mclk_fs);
183d2402860SYingkun Meng 	if (ret) {
184d2402860SYingkun Meng 		dev_err(&pdev->dev, "Error parsing mclk-fs: %d\n", ret);
185d2402860SYingkun Meng 		return ret;
186d2402860SYingkun Meng 	}
187d2402860SYingkun Meng 
188d2402860SYingkun Meng 	if (has_acpi_companion(&pdev->dev))
189d2402860SYingkun Meng 		ret = loongson_card_parse_acpi(ls_priv);
190d2402860SYingkun Meng 	else
191d2402860SYingkun Meng 		ret = loongson_card_parse_of(ls_priv);
192d2402860SYingkun Meng 	if (ret < 0)
193d2402860SYingkun Meng 		return ret;
194d2402860SYingkun Meng 
195d2402860SYingkun Meng 	ret = devm_snd_soc_register_card(&pdev->dev, card);
196d2402860SYingkun Meng 
197d2402860SYingkun Meng 	return ret;
198d2402860SYingkun Meng }
199d2402860SYingkun Meng 
200d2402860SYingkun Meng static const struct of_device_id loongson_asoc_dt_ids[] = {
201d2402860SYingkun Meng 	{ .compatible = "loongson,ls-audio-card" },
202d2402860SYingkun Meng 	{ /* sentinel */ },
203d2402860SYingkun Meng };
204d2402860SYingkun Meng MODULE_DEVICE_TABLE(of, loongson_asoc_dt_ids);
205d2402860SYingkun Meng 
206d2402860SYingkun Meng static struct platform_driver loongson_audio_driver = {
207d2402860SYingkun Meng 	.probe = loongson_asoc_card_probe,
208d2402860SYingkun Meng 	.driver = {
209d2402860SYingkun Meng 		.name = "loongson-asoc-card",
210d2402860SYingkun Meng 		.pm = &snd_soc_pm_ops,
211*c17bd30dSYingKun Meng 		.of_match_table = loongson_asoc_dt_ids,
212d2402860SYingkun Meng 	},
213d2402860SYingkun Meng };
214d2402860SYingkun Meng module_platform_driver(loongson_audio_driver);
215d2402860SYingkun Meng 
216d2402860SYingkun Meng MODULE_DESCRIPTION("Loongson ASoc Sound Card driver");
217d2402860SYingkun Meng MODULE_AUTHOR("Loongson Technology Corporation Limited");
218d2402860SYingkun Meng MODULE_LICENSE("GPL");
219