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