brintos

brintos / linux-shallow public Read only

0
0
Text · 9.5 KiB · 5ebf287 Raw
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