375 lines · c
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 links30 * Those will be over-written by the CPU side of the link31 */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 = 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 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(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 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(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 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;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->capture_only = 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 balanced147 */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 156static 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->capture_only = 1;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->playback_only = 1;191 192 /* ... but the interface should at least have one direction */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 it202 * accommodates the largest mask203 */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 or208 * if it is just too big209 */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 219static 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 246static 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->capture_only) {280 ret = axg_card_add_tdm_loopback(card, index);281 if (ret)282 return ret;283 }284 285 return 0;286}287 288static 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 293static 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 298static 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 303static 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 309static 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 if (axg_card_cpu_is_tdm_iface(dai_link->cpus->of_node))344 ret = axg_card_parse_tdm(card, np, index);345 }346 347 return ret;348}349 350static const struct meson_card_match_data axg_card_match_data = {351 .add_link = axg_card_add_link,352};353 354static const struct of_device_id axg_card_of_match[] = {355 {356 .compatible = "amlogic,axg-sound-card",357 .data = &axg_card_match_data,358 }, {}359};360MODULE_DEVICE_TABLE(of, axg_card_of_match);361 362static struct platform_driver axg_card_pdrv = {363 .probe = meson_card_probe,364 .remove = meson_card_remove,365 .driver = {366 .name = "axg-sound-card",367 .of_match_table = axg_card_of_match,368 },369};370module_platform_driver(axg_card_pdrv);371 372MODULE_DESCRIPTION("Amlogic AXG ALSA machine driver");373MODULE_AUTHOR("Jerome Brunet <jbrunet@baylibre.com>");374MODULE_LICENSE("GPL v2");375