1 // SPDX-License-Identifier: (GPL-2.0 OR MIT) 2 // 3 // Copyright (c) 2018 BayLibre, SAS. 4 // Author: Jerome Brunet <jbrunet@baylibre.com> 5 6 #include <linux/module.h> 7 #include <linux/of_platform.h> 8 #include <sound/soc.h> 9 #include <sound/soc-dai.h> 10 11 #include "axg-tdm.h" 12 #include "meson-card.h" 13 14 struct axg_dai_link_tdm_mask { 15 u32 tx; 16 u32 rx; 17 }; 18 19 struct axg_dai_link_tdm_data { 20 unsigned int mclk_fs; 21 unsigned int slots; 22 unsigned int slot_width; 23 u32 *tx_mask; 24 u32 *rx_mask; 25 struct axg_dai_link_tdm_mask *codec_masks; 26 }; 27 28 /* 29 * Base params for the codec to codec links 30 * Those will be over-written by the CPU side of the link 31 */ 32 static const struct snd_soc_pcm_stream codec_params = { 33 .formats = SNDRV_PCM_FMTBIT_S24_LE, 34 .rate_min = 5525, 35 .rate_max = 192000, 36 .channels_min = 1, 37 .channels_max = 8, 38 }; 39 40 static int axg_card_tdm_be_hw_params(struct snd_pcm_substream *substream, 41 struct snd_pcm_hw_params *params) 42 { 43 struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); 44 struct meson_card *priv = snd_soc_card_get_drvdata(rtd->card); 45 struct axg_dai_link_tdm_data *be = 46 (struct axg_dai_link_tdm_data *)priv->link_data[rtd->num]; 47 48 return meson_card_i2s_set_sysclk(substream, params, be->mclk_fs); 49 } 50 51 static const struct snd_soc_ops axg_card_tdm_be_ops = { 52 .hw_params = axg_card_tdm_be_hw_params, 53 }; 54 55 static int axg_card_tdm_dai_init(struct snd_soc_pcm_runtime *rtd) 56 { 57 struct meson_card *priv = snd_soc_card_get_drvdata(rtd->card); 58 struct axg_dai_link_tdm_data *be = 59 (struct axg_dai_link_tdm_data *)priv->link_data[rtd->num]; 60 struct snd_soc_dai *codec_dai; 61 int ret, i; 62 63 for_each_rtd_codec_dais(rtd, i, codec_dai) { 64 ret = snd_soc_dai_set_tdm_slot(codec_dai, 65 be->codec_masks[i].tx, 66 be->codec_masks[i].rx, 67 be->slots, be->slot_width); 68 if (ret && ret != -ENOTSUPP) { 69 dev_err(codec_dai->dev, 70 "setting tdm link slots failed\n"); 71 return ret; 72 } 73 } 74 75 ret = axg_tdm_set_tdm_slots(snd_soc_rtd_to_cpu(rtd, 0), be->tx_mask, be->rx_mask, 76 be->slots, be->slot_width); 77 if (ret) { 78 dev_err(snd_soc_rtd_to_cpu(rtd, 0)->dev, "setting tdm link slots failed\n"); 79 return ret; 80 } 81 82 return 0; 83 } 84 85 static int axg_card_tdm_dai_lb_init(struct snd_soc_pcm_runtime *rtd) 86 { 87 struct meson_card *priv = snd_soc_card_get_drvdata(rtd->card); 88 struct axg_dai_link_tdm_data *be = 89 (struct axg_dai_link_tdm_data *)priv->link_data[rtd->num]; 90 int ret; 91 92 /* The loopback rx_mask is the pad tx_mask */ 93 ret = axg_tdm_set_tdm_slots(snd_soc_rtd_to_cpu(rtd, 0), NULL, be->tx_mask, 94 be->slots, be->slot_width); 95 if (ret) { 96 dev_err(snd_soc_rtd_to_cpu(rtd, 0)->dev, "setting tdm link slots failed\n"); 97 return ret; 98 } 99 100 return 0; 101 } 102 103 static int axg_card_add_tdm_loopback(struct snd_soc_card *card, 104 int *index) 105 { 106 struct meson_card *priv = snd_soc_card_get_drvdata(card); 107 struct snd_soc_dai_link *pad; 108 struct snd_soc_dai_link *lb; 109 struct snd_soc_dai_link_component *dlc; 110 int ret; 111 112 /* extend links */ 113 ret = meson_card_reallocate_links(card, card->num_links + 1); 114 if (ret) 115 return ret; 116 117 pad = &card->dai_link[*index]; 118 lb = &card->dai_link[*index + 1]; 119 120 lb->name = devm_kasprintf(card->dev, GFP_KERNEL, "%s-lb", pad->name); 121 if (!lb->name) 122 return -ENOMEM; 123 124 dlc = devm_kzalloc(card->dev, sizeof(*dlc), GFP_KERNEL); 125 if (!dlc) 126 return -ENOMEM; 127 128 lb->cpus = dlc; 129 lb->codecs = &snd_soc_dummy_dlc; 130 lb->num_cpus = 1; 131 lb->num_codecs = 1; 132 133 lb->stream_name = lb->name; 134 lb->cpus->of_node = pad->cpus->of_node; 135 lb->cpus->dai_name = "TDM Loopback"; 136 lb->dpcm_capture = 1; 137 lb->no_pcm = 1; 138 lb->ops = &axg_card_tdm_be_ops; 139 lb->init = axg_card_tdm_dai_lb_init; 140 141 /* Provide the same link data to the loopback */ 142 priv->link_data[*index + 1] = priv->link_data[*index]; 143 144 /* 145 * axg_card_clean_references() will iterate over this link, 146 * make sure the node count is balanced 147 */ 148 of_node_get(lb->cpus->of_node); 149 150 /* Let add_links continue where it should */ 151 *index += 1; 152 153 return 0; 154 } 155 156 static int axg_card_parse_cpu_tdm_slots(struct snd_soc_card *card, 157 struct snd_soc_dai_link *link, 158 struct device_node *node, 159 struct axg_dai_link_tdm_data *be) 160 { 161 char propname[32]; 162 u32 tx, rx; 163 int i; 164 165 be->tx_mask = devm_kcalloc(card->dev, AXG_TDM_NUM_LANES, 166 sizeof(*be->tx_mask), GFP_KERNEL); 167 be->rx_mask = devm_kcalloc(card->dev, AXG_TDM_NUM_LANES, 168 sizeof(*be->rx_mask), GFP_KERNEL); 169 if (!be->tx_mask || !be->rx_mask) 170 return -ENOMEM; 171 172 for (i = 0, tx = 0; i < AXG_TDM_NUM_LANES; i++) { 173 snprintf(propname, 32, "dai-tdm-slot-tx-mask-%d", i); 174 snd_soc_of_get_slot_mask(node, propname, &be->tx_mask[i]); 175 tx = max(tx, be->tx_mask[i]); 176 } 177 178 /* Disable playback is the interface has no tx slots */ 179 if (!tx) 180 link->dpcm_playback = 0; 181 182 for (i = 0, rx = 0; i < AXG_TDM_NUM_LANES; i++) { 183 snprintf(propname, 32, "dai-tdm-slot-rx-mask-%d", i); 184 snd_soc_of_get_slot_mask(node, propname, &be->rx_mask[i]); 185 rx = max(rx, be->rx_mask[i]); 186 } 187 188 /* Disable capture is the interface has no rx slots */ 189 if (!rx) 190 link->dpcm_capture = 0; 191 192 /* ... but the interface should at least have one of them */ 193 if (!tx && !rx) { 194 dev_err(card->dev, "tdm link has no cpu slots\n"); 195 return -EINVAL; 196 } 197 198 of_property_read_u32(node, "dai-tdm-slot-num", &be->slots); 199 if (!be->slots) { 200 /* 201 * If the slot number is not provided, set it such as it 202 * accommodates the largest mask 203 */ 204 be->slots = fls(max(tx, rx)); 205 } else if (be->slots < fls(max(tx, rx)) || be->slots > 32) { 206 /* 207 * Error if the slots can't accommodate the largest mask or 208 * if it is just too big 209 */ 210 dev_err(card->dev, "bad slot number\n"); 211 return -EINVAL; 212 } 213 214 of_property_read_u32(node, "dai-tdm-slot-width", &be->slot_width); 215 216 return 0; 217 } 218 219 static int axg_card_parse_codecs_masks(struct snd_soc_card *card, 220 struct snd_soc_dai_link *link, 221 struct device_node *node, 222 struct axg_dai_link_tdm_data *be) 223 { 224 struct axg_dai_link_tdm_mask *codec_mask; 225 struct device_node *np; 226 227 codec_mask = devm_kcalloc(card->dev, link->num_codecs, 228 sizeof(*codec_mask), GFP_KERNEL); 229 if (!codec_mask) 230 return -ENOMEM; 231 232 be->codec_masks = codec_mask; 233 234 for_each_child_of_node(node, np) { 235 snd_soc_of_get_slot_mask(np, "dai-tdm-slot-rx-mask", 236 &codec_mask->rx); 237 snd_soc_of_get_slot_mask(np, "dai-tdm-slot-tx-mask", 238 &codec_mask->tx); 239 240 codec_mask++; 241 } 242 243 return 0; 244 } 245 246 static int axg_card_parse_tdm(struct snd_soc_card *card, 247 struct device_node *node, 248 int *index) 249 { 250 struct meson_card *priv = snd_soc_card_get_drvdata(card); 251 struct snd_soc_dai_link *link = &card->dai_link[*index]; 252 struct axg_dai_link_tdm_data *be; 253 int ret; 254 255 /* Allocate tdm link parameters */ 256 be = devm_kzalloc(card->dev, sizeof(*be), GFP_KERNEL); 257 if (!be) 258 return -ENOMEM; 259 priv->link_data[*index] = be; 260 261 /* Setup tdm link */ 262 link->ops = &axg_card_tdm_be_ops; 263 link->init = axg_card_tdm_dai_init; 264 link->dai_fmt = meson_card_parse_daifmt(node, link->cpus->of_node); 265 266 of_property_read_u32(node, "mclk-fs", &be->mclk_fs); 267 268 ret = axg_card_parse_cpu_tdm_slots(card, link, node, be); 269 if (ret) { 270 dev_err(card->dev, "error parsing tdm link slots\n"); 271 return ret; 272 } 273 274 ret = axg_card_parse_codecs_masks(card, link, node, be); 275 if (ret) 276 return ret; 277 278 /* Add loopback if the pad dai has playback */ 279 if (link->dpcm_playback) { 280 ret = axg_card_add_tdm_loopback(card, index); 281 if (ret) 282 return ret; 283 } 284 285 return 0; 286 } 287 288 static int axg_card_cpu_is_capture_fe(struct device_node *np) 289 { 290 return of_device_is_compatible(np, DT_PREFIX "axg-toddr"); 291 } 292 293 static int axg_card_cpu_is_playback_fe(struct device_node *np) 294 { 295 return of_device_is_compatible(np, DT_PREFIX "axg-frddr"); 296 } 297 298 static int axg_card_cpu_is_tdm_iface(struct device_node *np) 299 { 300 return of_device_is_compatible(np, DT_PREFIX "axg-tdm-iface"); 301 } 302 303 static int axg_card_cpu_is_codec(struct device_node *np) 304 { 305 return of_device_is_compatible(np, DT_PREFIX "g12a-tohdmitx") || 306 of_device_is_compatible(np, DT_PREFIX "g12a-toacodec"); 307 } 308 309 static int axg_card_add_link(struct snd_soc_card *card, struct device_node *np, 310 int *index) 311 { 312 struct snd_soc_dai_link *dai_link = &card->dai_link[*index]; 313 struct snd_soc_dai_link_component *cpu; 314 int ret; 315 316 cpu = devm_kzalloc(card->dev, sizeof(*cpu), GFP_KERNEL); 317 if (!cpu) 318 return -ENOMEM; 319 320 dai_link->cpus = cpu; 321 dai_link->num_cpus = 1; 322 dai_link->nonatomic = true; 323 324 ret = meson_card_parse_dai(card, np, dai_link->cpus); 325 if (ret) 326 return ret; 327 328 if (axg_card_cpu_is_playback_fe(dai_link->cpus->of_node)) 329 return meson_card_set_fe_link(card, dai_link, np, true); 330 else if (axg_card_cpu_is_capture_fe(dai_link->cpus->of_node)) 331 return meson_card_set_fe_link(card, dai_link, np, false); 332 333 334 ret = meson_card_set_be_link(card, dai_link, np); 335 if (ret) 336 return ret; 337 338 if (axg_card_cpu_is_codec(dai_link->cpus->of_node)) { 339 dai_link->c2c_params = &codec_params; 340 dai_link->num_c2c_params = 1; 341 } else { 342 dai_link->no_pcm = 1; 343 snd_soc_dai_link_set_capabilities(dai_link); 344 if (axg_card_cpu_is_tdm_iface(dai_link->cpus->of_node)) 345 ret = axg_card_parse_tdm(card, np, index); 346 } 347 348 return ret; 349 } 350 351 static const struct meson_card_match_data axg_card_match_data = { 352 .add_link = axg_card_add_link, 353 }; 354 355 static const struct of_device_id axg_card_of_match[] = { 356 { 357 .compatible = "amlogic,axg-sound-card", 358 .data = &axg_card_match_data, 359 }, {} 360 }; 361 MODULE_DEVICE_TABLE(of, axg_card_of_match); 362 363 static struct platform_driver axg_card_pdrv = { 364 .probe = meson_card_probe, 365 .remove_new = meson_card_remove, 366 .driver = { 367 .name = "axg-sound-card", 368 .of_match_table = axg_card_of_match, 369 }, 370 }; 371 module_platform_driver(axg_card_pdrv); 372 373 MODULE_DESCRIPTION("Amlogic AXG ALSA machine driver"); 374 MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>"); 375 MODULE_LICENSE("GPL v2"); 376
Linux® is a registered trademark of Linus Torvalds in the United States and other countries.
TOMOYO® is a registered trademark of NTT DATA CORPORATION.