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