1 // SPDX-License-Identifier: GPL-2.0
3 // Loongson ASoC Audio Machine driver
5 // Copyright (C) 2023 Loongson Technology Corporation Limited
6 // Author: Yingkun Meng <mengyingkun@loongson.cn>
9 #include <linux/module.h>
10 #include <sound/soc.h>
11 #include <sound/soc-acpi.h>
12 #include <linux/acpi.h>
13 #include <linux/pci.h>
14 #include <sound/pcm_params.h>
16 static char codec_name
[SND_ACPI_I2C_ID_LEN
];
18 struct loongson_card_data
{
19 struct snd_soc_card snd_card
;
23 static int loongson_card_hw_params(struct snd_pcm_substream
*substream
,
24 struct snd_pcm_hw_params
*params
)
26 struct snd_soc_pcm_runtime
*rtd
= snd_soc_substream_to_rtd(substream
);
27 struct loongson_card_data
*ls_card
= snd_soc_card_get_drvdata(rtd
->card
);
28 struct snd_soc_dai
*codec_dai
= snd_soc_rtd_to_codec(rtd
, 0);
29 struct snd_soc_dai
*cpu_dai
= snd_soc_rtd_to_cpu(rtd
, 0);
32 if (!ls_card
->mclk_fs
)
35 mclk
= ls_card
->mclk_fs
* params_rate(params
);
36 ret
= snd_soc_dai_set_sysclk(cpu_dai
, 0, mclk
, SND_SOC_CLOCK_OUT
);
38 dev_err(codec_dai
->dev
, "cpu_dai clock not set\n");
42 ret
= snd_soc_dai_set_sysclk(codec_dai
, 0, mclk
, SND_SOC_CLOCK_IN
);
44 dev_err(codec_dai
->dev
, "codec_dai clock not set\n");
51 static const struct snd_soc_ops loongson_ops
= {
52 .hw_params
= loongson_card_hw_params
,
55 SND_SOC_DAILINK_DEFS(analog
,
56 DAILINK_COMP_ARRAY(COMP_CPU("loongson-i2s")),
57 DAILINK_COMP_ARRAY(COMP_EMPTY()),
58 DAILINK_COMP_ARRAY(COMP_EMPTY()));
60 static struct snd_soc_dai_link loongson_dai_links
[] = {
62 .name
= "Loongson Audio Port",
63 .stream_name
= "Loongson Audio",
64 .dai_fmt
= SND_SOC_DAIFMT_I2S
| SND_SOC_DAIFMT_IB_NF
65 | SND_SOC_DAIFMT_CBC_CFC
,
66 SND_SOC_DAILINK_REG(analog
),
71 static struct acpi_device
*loongson_card_acpi_find_device(struct snd_soc_card
*card
,
74 struct fwnode_handle
*fwnode
= card
->dev
->fwnode
;
75 struct fwnode_reference_args args
;
78 memset(&args
, 0, sizeof(args
));
79 status
= acpi_node_get_property_reference(fwnode
, name
, 0, &args
);
80 if (status
|| !is_acpi_device_node(args
.fwnode
)) {
81 dev_err(card
->dev
, "No matching phy in ACPI table\n");
85 return to_acpi_device_node(args
.fwnode
);
88 static int loongson_card_parse_acpi(struct loongson_card_data
*data
)
90 struct snd_soc_card
*card
= &data
->snd_card
;
91 const char *codec_dai_name
;
92 struct acpi_device
*adev
;
93 struct device
*phy_dev
;
96 /* fixup platform name based on reference node */
97 adev
= loongson_card_acpi_find_device(card
, "cpu");
101 phy_dev
= acpi_get_first_physical_node(adev
);
103 return -EPROBE_DEFER
;
105 /* fixup codec name based on reference node */
106 adev
= loongson_card_acpi_find_device(card
, "codec");
109 snprintf(codec_name
, sizeof(codec_name
), "i2c-%s", acpi_dev_name(adev
));
111 device_property_read_string(card
->dev
, "codec-dai-name", &codec_dai_name
);
113 for (i
= 0; i
< card
->num_links
; i
++) {
114 loongson_dai_links
[i
].platforms
->name
= dev_name(phy_dev
);
115 loongson_dai_links
[i
].codecs
->name
= codec_name
;
116 loongson_dai_links
[i
].codecs
->dai_name
= codec_dai_name
;
122 static int loongson_card_parse_of(struct loongson_card_data
*data
)
124 struct device_node
*cpu
, *codec
;
125 struct snd_soc_card
*card
= &data
->snd_card
;
126 struct device
*dev
= card
->dev
;
129 cpu
= of_get_child_by_name(dev
->of_node
, "cpu");
131 dev_err(dev
, "platform property missing or invalid\n");
134 codec
= of_get_child_by_name(dev
->of_node
, "codec");
136 dev_err(dev
, "audio-codec property missing or invalid\n");
141 for (i
= 0; i
< card
->num_links
; i
++) {
142 ret
= snd_soc_of_get_dlc(cpu
, NULL
, loongson_dai_links
[i
].cpus
, 0);
144 dev_err(dev
, "getting cpu dlc error (%d)\n", ret
);
147 loongson_dai_links
[i
].platforms
->of_node
= loongson_dai_links
[i
].cpus
->of_node
;
149 ret
= snd_soc_of_get_dlc(codec
, NULL
, loongson_dai_links
[i
].codecs
, 0);
151 dev_err(dev
, "getting codec dlc error (%d)\n", ret
);
167 static int loongson_asoc_card_probe(struct platform_device
*pdev
)
169 struct loongson_card_data
*ls_priv
;
170 struct device
*dev
= &pdev
->dev
;
171 struct snd_soc_card
*card
;
174 ls_priv
= devm_kzalloc(dev
, sizeof(*ls_priv
), GFP_KERNEL
);
178 card
= &ls_priv
->snd_card
;
181 card
->owner
= THIS_MODULE
;
182 card
->dai_link
= loongson_dai_links
;
183 card
->num_links
= ARRAY_SIZE(loongson_dai_links
);
184 snd_soc_card_set_drvdata(card
, ls_priv
);
186 ret
= device_property_read_string(dev
, "model", &card
->name
);
188 return dev_err_probe(dev
, ret
, "Error parsing card name\n");
190 ret
= device_property_read_u32(dev
, "mclk-fs", &ls_priv
->mclk_fs
);
192 return dev_err_probe(dev
, ret
, "Error parsing mclk-fs\n");
194 ret
= has_acpi_companion(dev
) ? loongson_card_parse_acpi(ls_priv
)
195 : loongson_card_parse_of(ls_priv
);
197 return dev_err_probe(dev
, ret
, "Error parsing acpi/of properties\n");
199 return devm_snd_soc_register_card(dev
, card
);
202 static const struct of_device_id loongson_asoc_dt_ids
[] = {
203 { .compatible
= "loongson,ls-audio-card" },
206 MODULE_DEVICE_TABLE(of
, loongson_asoc_dt_ids
);
208 static struct platform_driver loongson_audio_driver
= {
209 .probe
= loongson_asoc_card_probe
,
211 .name
= "loongson-asoc-card",
212 .pm
= &snd_soc_pm_ops
,
213 .of_match_table
= loongson_asoc_dt_ids
,
216 module_platform_driver(loongson_audio_driver
);
218 MODULE_DESCRIPTION("Loongson ASoc Sound Card driver");
219 MODULE_AUTHOR("Loongson Technology Corporation Limited");
220 MODULE_LICENSE("GPL");