axg-card.c (9794B)
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 14struct axg_dai_link_tdm_mask { 15 u32 tx; 16 u32 rx; 17}; 18 19struct 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 */ 32static 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 40static 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 = asoc_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 51static const struct snd_soc_ops axg_card_tdm_be_ops = { 52 .hw_params = axg_card_tdm_be_hw_params, 53}; 54 55static 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(asoc_rtd_to_cpu(rtd, 0), be->tx_mask, be->rx_mask, 76 be->slots, be->slot_width); 77 if (ret) { 78 dev_err(asoc_rtd_to_cpu(rtd, 0)->dev, "setting tdm link slots failed\n"); 79 return ret; 80 } 81 82 return 0; 83} 84 85static 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(asoc_rtd_to_cpu(rtd, 0), NULL, be->tx_mask, 94 be->slots, be->slot_width); 95 if (ret) { 96 dev_err(asoc_rtd_to_cpu(rtd, 0)->dev, "setting tdm link slots failed\n"); 97 return ret; 98 } 99 100 return 0; 101} 102 103static 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 = &card->dai_link[*index]; 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 lb = &card->dai_link[*index + 1]; 118 119 lb->name = devm_kasprintf(card->dev, GFP_KERNEL, "%s-lb", pad->name); 120 if (!lb->name) 121 return -ENOMEM; 122 123 dlc = devm_kzalloc(card->dev, 2 * sizeof(*dlc), GFP_KERNEL); 124 if (!dlc) 125 return -ENOMEM; 126 127 lb->cpus = &dlc[0]; 128 lb->codecs = &dlc[1]; 129 lb->num_cpus = 1; 130 lb->num_codecs = 1; 131 132 lb->stream_name = lb->name; 133 lb->cpus->of_node = pad->cpus->of_node; 134 lb->cpus->dai_name = "TDM Loopback"; 135 lb->codecs->name = "snd-soc-dummy"; 136 lb->codecs->dai_name = "snd-soc-dummy-dai"; 137 lb->dpcm_capture = 1; 138 lb->no_pcm = 1; 139 lb->ops = &axg_card_tdm_be_ops; 140 lb->init = axg_card_tdm_dai_lb_init; 141 142 /* Provide the same link data to the loopback */ 143 priv->link_data[*index + 1] = priv->link_data[*index]; 144 145 /* 146 * axg_card_clean_references() will iterate over this link, 147 * make sure the node count is balanced 148 */ 149 of_node_get(lb->cpus->of_node); 150 151 /* Let add_links continue where it should */ 152 *index += 1; 153 154 return 0; 155} 156 157static int axg_card_parse_cpu_tdm_slots(struct snd_soc_card *card, 158 struct snd_soc_dai_link *link, 159 struct device_node *node, 160 struct axg_dai_link_tdm_data *be) 161{ 162 char propname[32]; 163 u32 tx, rx; 164 int i; 165 166 be->tx_mask = devm_kcalloc(card->dev, AXG_TDM_NUM_LANES, 167 sizeof(*be->tx_mask), GFP_KERNEL); 168 be->rx_mask = devm_kcalloc(card->dev, AXG_TDM_NUM_LANES, 169 sizeof(*be->rx_mask), GFP_KERNEL); 170 if (!be->tx_mask || !be->rx_mask) 171 return -ENOMEM; 172 173 for (i = 0, tx = 0; i < AXG_TDM_NUM_LANES; i++) { 174 snprintf(propname, 32, "dai-tdm-slot-tx-mask-%d", i); 175 snd_soc_of_get_slot_mask(node, propname, &be->tx_mask[i]); 176 tx = max(tx, be->tx_mask[i]); 177 } 178 179 /* Disable playback is the interface has no tx slots */ 180 if (!tx) 181 link->dpcm_playback = 0; 182 183 for (i = 0, rx = 0; i < AXG_TDM_NUM_LANES; i++) { 184 snprintf(propname, 32, "dai-tdm-slot-rx-mask-%d", i); 185 snd_soc_of_get_slot_mask(node, propname, &be->rx_mask[i]); 186 rx = max(rx, be->rx_mask[i]); 187 } 188 189 /* Disable capture is the interface has no rx slots */ 190 if (!rx) 191 link->dpcm_capture = 0; 192 193 /* ... but the interface should at least have one of them */ 194 if (!tx && !rx) { 195 dev_err(card->dev, "tdm link has no cpu slots\n"); 196 return -EINVAL; 197 } 198 199 of_property_read_u32(node, "dai-tdm-slot-num", &be->slots); 200 if (!be->slots) { 201 /* 202 * If the slot number is not provided, set it such as it 203 * accommodates the largest mask 204 */ 205 be->slots = fls(max(tx, rx)); 206 } else if (be->slots < fls(max(tx, rx)) || be->slots > 32) { 207 /* 208 * Error if the slots can't accommodate the largest mask or 209 * if it is just too big 210 */ 211 dev_err(card->dev, "bad slot number\n"); 212 return -EINVAL; 213 } 214 215 of_property_read_u32(node, "dai-tdm-slot-width", &be->slot_width); 216 217 return 0; 218} 219 220static int axg_card_parse_codecs_masks(struct snd_soc_card *card, 221 struct snd_soc_dai_link *link, 222 struct device_node *node, 223 struct axg_dai_link_tdm_data *be) 224{ 225 struct axg_dai_link_tdm_mask *codec_mask; 226 struct device_node *np; 227 228 codec_mask = devm_kcalloc(card->dev, link->num_codecs, 229 sizeof(*codec_mask), GFP_KERNEL); 230 if (!codec_mask) 231 return -ENOMEM; 232 233 be->codec_masks = codec_mask; 234 235 for_each_child_of_node(node, np) { 236 snd_soc_of_get_slot_mask(np, "dai-tdm-slot-rx-mask", 237 &codec_mask->rx); 238 snd_soc_of_get_slot_mask(np, "dai-tdm-slot-tx-mask", 239 &codec_mask->tx); 240 241 codec_mask++; 242 } 243 244 return 0; 245} 246 247static int axg_card_parse_tdm(struct snd_soc_card *card, 248 struct device_node *node, 249 int *index) 250{ 251 struct meson_card *priv = snd_soc_card_get_drvdata(card); 252 struct snd_soc_dai_link *link = &card->dai_link[*index]; 253 struct axg_dai_link_tdm_data *be; 254 int ret; 255 256 /* Allocate tdm link parameters */ 257 be = devm_kzalloc(card->dev, sizeof(*be), GFP_KERNEL); 258 if (!be) 259 return -ENOMEM; 260 priv->link_data[*index] = be; 261 262 /* Setup tdm link */ 263 link->ops = &axg_card_tdm_be_ops; 264 link->init = axg_card_tdm_dai_init; 265 link->dai_fmt = meson_card_parse_daifmt(node, link->cpus->of_node); 266 267 of_property_read_u32(node, "mclk-fs", &be->mclk_fs); 268 269 ret = axg_card_parse_cpu_tdm_slots(card, link, node, be); 270 if (ret) { 271 dev_err(card->dev, "error parsing tdm link slots\n"); 272 return ret; 273 } 274 275 ret = axg_card_parse_codecs_masks(card, link, node, be); 276 if (ret) 277 return ret; 278 279 /* Add loopback if the pad dai has playback */ 280 if (link->dpcm_playback) { 281 ret = axg_card_add_tdm_loopback(card, index); 282 if (ret) 283 return ret; 284 } 285 286 return 0; 287} 288 289static int axg_card_cpu_is_capture_fe(struct device_node *np) 290{ 291 return of_device_is_compatible(np, DT_PREFIX "axg-toddr"); 292} 293 294static int axg_card_cpu_is_playback_fe(struct device_node *np) 295{ 296 return of_device_is_compatible(np, DT_PREFIX "axg-frddr"); 297} 298 299static int axg_card_cpu_is_tdm_iface(struct device_node *np) 300{ 301 return of_device_is_compatible(np, DT_PREFIX "axg-tdm-iface"); 302} 303 304static int axg_card_cpu_is_codec(struct device_node *np) 305{ 306 return of_device_is_compatible(np, DT_PREFIX "g12a-tohdmitx") || 307 of_device_is_compatible(np, DT_PREFIX "g12a-toacodec"); 308} 309 310static int axg_card_add_link(struct snd_soc_card *card, struct device_node *np, 311 int *index) 312{ 313 struct snd_soc_dai_link *dai_link = &card->dai_link[*index]; 314 struct snd_soc_dai_link_component *cpu; 315 int ret; 316 317 cpu = devm_kzalloc(card->dev, sizeof(*cpu), GFP_KERNEL); 318 if (!cpu) 319 return -ENOMEM; 320 321 dai_link->cpus = cpu; 322 dai_link->num_cpus = 1; 323 324 ret = meson_card_parse_dai(card, np, &dai_link->cpus->of_node, 325 &dai_link->cpus->dai_name); 326 if (ret) 327 return ret; 328 329 if (axg_card_cpu_is_playback_fe(dai_link->cpus->of_node)) 330 return meson_card_set_fe_link(card, dai_link, np, true); 331 else if (axg_card_cpu_is_capture_fe(dai_link->cpus->of_node)) 332 return meson_card_set_fe_link(card, dai_link, np, false); 333 334 335 ret = meson_card_set_be_link(card, dai_link, np); 336 if (ret) 337 return ret; 338 339 if (axg_card_cpu_is_codec(dai_link->cpus->of_node)) { 340 dai_link->params = &codec_params; 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 351static const struct meson_card_match_data axg_card_match_data = { 352 .add_link = axg_card_add_link, 353}; 354 355static const struct of_device_id axg_card_of_match[] = { 356 { 357 .compatible = "amlogic,axg-sound-card", 358 .data = &axg_card_match_data, 359 }, {} 360}; 361MODULE_DEVICE_TABLE(of, axg_card_of_match); 362 363static struct platform_driver axg_card_pdrv = { 364 .probe = meson_card_probe, 365 .remove = meson_card_remove, 366 .driver = { 367 .name = "axg-sound-card", 368 .of_match_table = axg_card_of_match, 369 }, 370}; 371module_platform_driver(axg_card_pdrv); 372 373MODULE_DESCRIPTION("Amlogic AXG ALSA machine driver"); 374MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>"); 375MODULE_LICENSE("GPL v2");