1 // SPDX-License-Identifier: GPL-2.0 2 // Copyright (c) 2018, Linaro Limited 3 4 #include <linux/soc/qcom/apr.h> 5 #include <linux/module.h> 6 #include <linux/component.h> 7 #include <linux/platform_device.h> 8 #include <linux/of_device.h> 9 #include <sound/soc.h> 10 #include <sound/soc-dapm.h> 11 #include <sound/pcm.h> 12 13 static int apq8096_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, 14 struct snd_pcm_hw_params *params) 15 { 16 struct snd_interval *rate = hw_param_interval(params, 17 SNDRV_PCM_HW_PARAM_RATE); 18 struct snd_interval *channels = hw_param_interval(params, 19 SNDRV_PCM_HW_PARAM_CHANNELS); 20 21 rate->min = rate->max = 48000; 22 channels->min = channels->max = 2; 23 24 return 0; 25 } 26 27 static int apq8096_sbc_parse_of(struct snd_soc_card *card) 28 { 29 struct device_node *np; 30 struct device_node *codec = NULL; 31 struct device_node *platform = NULL; 32 struct device_node *cpu = NULL; 33 struct device *dev = card->dev; 34 struct snd_soc_dai_link *link; 35 int ret, num_links; 36 37 ret = snd_soc_of_parse_card_name(card, "qcom,model"); 38 if (ret) { 39 dev_err(dev, "Error parsing card name: %d\n", ret); 40 return ret; 41 } 42 43 /* DAPM routes */ 44 if (of_property_read_bool(dev->of_node, "qcom,audio-routing")) { 45 ret = snd_soc_of_parse_audio_routing(card, 46 "qcom,audio-routing"); 47 if (ret) 48 return ret; 49 } 50 51 /* Populate links */ 52 num_links = of_get_child_count(dev->of_node); 53 54 /* Allocate the DAI link array */ 55 card->dai_link = kcalloc(num_links, sizeof(*link), GFP_KERNEL); 56 if (!card->dai_link) 57 return -ENOMEM; 58 59 card->num_links = num_links; 60 link = card->dai_link; 61 62 for_each_child_of_node(dev->of_node, np) { 63 cpu = of_get_child_by_name(np, "cpu"); 64 if (!cpu) { 65 dev_err(dev, "Can't find cpu DT node\n"); 66 ret = -EINVAL; 67 goto err; 68 } 69 70 link->cpu_of_node = of_parse_phandle(cpu, "sound-dai", 0); 71 if (!link->cpu_of_node) { 72 dev_err(card->dev, "error getting cpu phandle\n"); 73 ret = -EINVAL; 74 goto err; 75 } 76 77 ret = snd_soc_of_get_dai_name(cpu, &link->cpu_dai_name); 78 if (ret) { 79 dev_err(card->dev, "error getting cpu dai name\n"); 80 goto err; 81 } 82 83 platform = of_get_child_by_name(np, "platform"); 84 codec = of_get_child_by_name(np, "codec"); 85 if (codec && platform) { 86 link->platform_of_node = of_parse_phandle(platform, 87 "sound-dai", 88 0); 89 if (!link->platform_of_node) { 90 dev_err(card->dev, "platform dai not found\n"); 91 ret = -EINVAL; 92 goto err; 93 } 94 95 ret = snd_soc_of_get_dai_link_codecs(dev, codec, link); 96 if (ret < 0) { 97 dev_err(card->dev, "codec dai not found\n"); 98 goto err; 99 } 100 link->no_pcm = 1; 101 link->ignore_pmdown_time = 1; 102 link->be_hw_params_fixup = apq8096_be_hw_params_fixup; 103 } else { 104 link->platform_of_node = link->cpu_of_node; 105 link->codec_dai_name = "snd-soc-dummy-dai"; 106 link->codec_name = "snd-soc-dummy"; 107 link->dynamic = 1; 108 } 109 110 link->ignore_suspend = 1; 111 ret = of_property_read_string(np, "link-name", &link->name); 112 if (ret) { 113 dev_err(card->dev, "error getting codec dai_link name\n"); 114 goto err; 115 } 116 117 link->dpcm_playback = 1; 118 link->dpcm_capture = 1; 119 link->stream_name = link->name; 120 link++; 121 } 122 123 return 0; 124 err: 125 of_node_put(cpu); 126 of_node_put(codec); 127 of_node_put(platform); 128 kfree(card->dai_link); 129 return ret; 130 } 131 132 static int apq8096_bind(struct device *dev) 133 { 134 struct snd_soc_card *card; 135 int ret; 136 137 card = kzalloc(sizeof(*card), GFP_KERNEL); 138 if (!card) 139 return -ENOMEM; 140 141 component_bind_all(dev, card); 142 card->dev = dev; 143 ret = apq8096_sbc_parse_of(card); 144 if (ret) { 145 dev_err(dev, "Error parsing OF data\n"); 146 goto err; 147 } 148 149 ret = snd_soc_register_card(card); 150 if (ret) 151 goto err; 152 153 return 0; 154 155 err: 156 component_unbind_all(dev, card); 157 kfree(card); 158 return ret; 159 } 160 161 static void apq8096_unbind(struct device *dev) 162 { 163 struct snd_soc_card *card = dev_get_drvdata(dev); 164 165 snd_soc_unregister_card(card); 166 component_unbind_all(dev, card); 167 kfree(card->dai_link); 168 kfree(card); 169 } 170 171 static const struct component_master_ops apq8096_ops = { 172 .bind = apq8096_bind, 173 .unbind = apq8096_unbind, 174 }; 175 176 static int apq8016_compare_of(struct device *dev, void *data) 177 { 178 return dev->of_node == data; 179 } 180 181 static void apq8016_release_of(struct device *dev, void *data) 182 { 183 of_node_put(data); 184 } 185 186 static int add_audio_components(struct device *dev, 187 struct component_match **matchptr) 188 { 189 struct device_node *np, *platform, *cpu, *node, *dai_node; 190 191 node = dev->of_node; 192 193 for_each_child_of_node(node, np) { 194 cpu = of_get_child_by_name(np, "cpu"); 195 if (cpu) { 196 dai_node = of_parse_phandle(cpu, "sound-dai", 0); 197 of_node_get(dai_node); 198 component_match_add_release(dev, matchptr, 199 apq8016_release_of, 200 apq8016_compare_of, 201 dai_node); 202 } 203 204 platform = of_get_child_by_name(np, "platform"); 205 if (platform) { 206 dai_node = of_parse_phandle(platform, "sound-dai", 0); 207 component_match_add_release(dev, matchptr, 208 apq8016_release_of, 209 apq8016_compare_of, 210 dai_node); 211 } 212 } 213 214 return 0; 215 } 216 217 static int apq8096_platform_probe(struct platform_device *pdev) 218 { 219 struct component_match *match = NULL; 220 int ret; 221 222 ret = add_audio_components(&pdev->dev, &match); 223 if (ret) 224 return ret; 225 226 return component_master_add_with_match(&pdev->dev, &apq8096_ops, match); 227 } 228 229 static int apq8096_platform_remove(struct platform_device *pdev) 230 { 231 component_master_del(&pdev->dev, &apq8096_ops); 232 233 return 0; 234 } 235 236 static const struct of_device_id msm_snd_apq8096_dt_match[] = { 237 {.compatible = "qcom,apq8096-sndcard"}, 238 {} 239 }; 240 241 MODULE_DEVICE_TABLE(of, msm_snd_apq8096_dt_match); 242 243 static struct platform_driver msm_snd_apq8096_driver = { 244 .probe = apq8096_platform_probe, 245 .remove = apq8096_platform_remove, 246 .driver = { 247 .name = "msm-snd-apq8096", 248 .owner = THIS_MODULE, 249 .of_match_table = msm_snd_apq8096_dt_match, 250 }, 251 }; 252 module_platform_driver(msm_snd_apq8096_driver); 253 MODULE_AUTHOR("Srinivas Kandagatla <srinivas.kandagatla@linaro.org"); 254 MODULE_DESCRIPTION("APQ8096 ASoC Machine Driver"); 255 MODULE_LICENSE("GPL v2"); 256