simple-scu-card.c 11.5 KB
Newer Older
1 2 3 4 5 6 7 8 9
// SPDX-License-Identifier: GPL-2.0
//
// ASoC simple SCU sound card support
//
// Copyright (C) 2015 Renesas Solutions Corp.
// Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
//
// based on ${LINUX}/sound/soc/generic/simple-card.c

10 11 12 13 14 15 16 17 18 19
#include <linux/clk.h>
#include <linux/device.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/string.h>
#include <sound/jack.h>
#include <sound/soc.h>
#include <sound/soc-dai.h>
20
#include <sound/simple_card_utils.h>
21

22
struct simple_card_data {
23 24
	struct snd_soc_card snd_card;
	struct snd_soc_codec_conf codec_conf;
25
	struct simple_dai_props {
26 27
		struct asoc_simple_dai *cpu_dai;
		struct asoc_simple_dai *codec_dai;
28
		struct snd_soc_dai_link_component codecs;
29
		struct snd_soc_dai_link_component platform;
30
		struct asoc_simple_card_data adata;
31
	} *dai_props;
32
	struct snd_soc_dai_link *dai_link;
33
	struct asoc_simple_dai *dais;
34
	struct asoc_simple_card_data adata;
35 36
};

37
#define simple_priv_to_card(priv) (&(priv)->snd_card)
38
#define simple_priv_to_props(priv, i) ((priv)->dai_props + (i))
39 40
#define simple_priv_to_dev(priv) (simple_priv_to_card(priv)->dev)
#define simple_priv_to_link(priv, i) (simple_priv_to_card(priv)->dai_link + (i))
41

42 43
#define DAI	"sound-dai"
#define CELL	"#sound-dai-cells"
44
#define PREFIX	"simple-audio-card,"
45

46
static int asoc_simple_card_startup(struct snd_pcm_substream *substream)
47 48
{
	struct snd_soc_pcm_runtime *rtd = substream->private_data;
49
	struct simple_card_data *priv =	snd_soc_card_get_drvdata(rtd->card);
50
	struct simple_dai_props *dai_props =
51
		simple_priv_to_props(priv, rtd->num);
52 53 54 55 56 57 58 59 60
	int ret;

	ret = asoc_simple_card_clk_enable(dai_props->cpu_dai);
	if (ret)
		return ret;

	ret = asoc_simple_card_clk_enable(dai_props->codec_dai);
	if (ret)
		asoc_simple_card_clk_disable(dai_props->cpu_dai);
61

62
	return ret;
63 64
}

65
static void asoc_simple_card_shutdown(struct snd_pcm_substream *substream)
66 67
{
	struct snd_soc_pcm_runtime *rtd = substream->private_data;
68
	struct simple_card_data *priv =	snd_soc_card_get_drvdata(rtd->card);
69
	struct simple_dai_props *dai_props =
70
		simple_priv_to_props(priv, rtd->num);
71

72 73 74
	asoc_simple_card_clk_disable(dai_props->cpu_dai);

	asoc_simple_card_clk_disable(dai_props->codec_dai);
75 76
}

77
static const struct snd_soc_ops asoc_simple_card_ops = {
78 79
	.startup = asoc_simple_card_startup,
	.shutdown = asoc_simple_card_shutdown,
80 81
};

82
static int asoc_simple_card_dai_init(struct snd_soc_pcm_runtime *rtd)
83
{
84 85 86 87 88 89 90 91
	struct simple_card_data *priv =	snd_soc_card_get_drvdata(rtd->card);
	struct simple_dai_props *dai_props = simple_priv_to_props(priv, rtd->num);
	int ret;

	ret = asoc_simple_card_init_dai(rtd->codec_dai,
					dai_props->codec_dai);
	if (ret < 0)
		return ret;
92

93 94 95 96
	ret = asoc_simple_card_init_dai(rtd->cpu_dai,
					dai_props->cpu_dai);
	if (ret < 0)
		return ret;
97

98
	return 0;
99 100
}

101
static int asoc_simple_card_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd,
102 103
					struct snd_pcm_hw_params *params)
{
104
	struct simple_card_data *priv = snd_soc_card_get_drvdata(rtd->card);
105
	struct simple_dai_props *dai_props = simple_priv_to_props(priv, rtd->num);
106

107 108 109
	asoc_simple_card_convert_fixup(&dai_props->adata, params);

	/* overwrite by top level adata if exist */
110
	asoc_simple_card_convert_fixup(&priv->adata, params);
111 112 113 114

	return 0;
}

115 116 117
static int asoc_simple_card_dai_link_of(struct device_node *link,
					struct device_node *np,
					struct device_node *codec,
118
					struct simple_card_data *priv,
119
					int *dai_idx, int link_idx, int is_fe,
120
					bool is_top_level_node)
121
{
122
	struct device *dev = simple_priv_to_dev(priv);
123 124
	struct snd_soc_dai_link *dai_link = simple_priv_to_link(priv, link_idx);
	struct simple_dai_props *dai_props = simple_priv_to_props(priv, link_idx);
125
	struct snd_soc_card *card = simple_priv_to_card(priv);
126
	struct asoc_simple_dai *dai;
127
	char *prefix = "";
128 129
	int ret;

130 131 132 133
	/* For single DAI link & old style of DT node */
	if (is_top_level_node)
		prefix = PREFIX;

134
	if (is_fe) {
135
		int is_single_links = 0;
136
		struct snd_soc_dai_link_component *codecs;
137

138
		/* BE is dummy */
139 140 141 142
		codecs			= dai_link->codecs;
		codecs->of_node		= NULL;
		codecs->dai_name	= "snd-soc-dummy-dai";
		codecs->name		= "snd-soc-dummy";
143 144 145 146

		/* FE settings */
		dai_link->dynamic		= 1;
		dai_link->dpcm_merged_format	= 1;
147

148 149 150
		dai =
		dai_props->cpu_dai	= &priv->dais[(*dai_idx)++];

151 152 153
		ret = asoc_simple_card_parse_cpu(np, dai_link, DAI, CELL,
						 &is_single_links);
		if (ret)
154
			return ret;
155

156
		ret = asoc_simple_card_parse_clk_cpu(dev, np, dai_link, dai);
157 158 159
		if (ret < 0)
			return ret;

160 161 162 163 164
		ret = asoc_simple_card_set_dailink_name(dev, dai_link,
							"fe.%s",
							dai_link->cpu_dai_name);
		if (ret < 0)
			return ret;
165

166
		asoc_simple_card_canonicalize_cpu(dai_link, is_single_links);
167 168 169 170 171
	} else {
		/* FE is dummy */
		dai_link->cpu_of_node		= NULL;
		dai_link->cpu_dai_name		= "snd-soc-dummy-dai";
		dai_link->cpu_name		= "snd-soc-dummy";
172

173 174
		/* BE settings */
		dai_link->no_pcm		= 1;
175
		dai_link->be_hw_params_fixup	= asoc_simple_card_be_hw_params_fixup;
176

177 178 179
		dai =
		dai_props->codec_dai	= &priv->dais[(*dai_idx)++];

180
		ret = asoc_simple_card_parse_codec(np, dai_link, DAI, CELL);
181 182
		if (ret < 0)
			return ret;
183

184
		ret = asoc_simple_card_parse_clk_codec(dev, np, dai_link, dai);
185 186 187
		if (ret < 0)
			return ret;

188 189
		ret = asoc_simple_card_set_dailink_name(dev, dai_link,
							"be.%s",
190
							dai_link->codecs->dai_name);
191 192 193
		if (ret < 0)
			return ret;

194
		/* check "prefix" from top node */
195
		snd_soc_of_parse_audio_prefix(card,
196
					      &priv->codec_conf,
197
					      dai_link->codecs->of_node,
198
					      PREFIX "prefix");
199 200 201 202 203 204
		/* check "prefix" from each node if top doesn't have */
		if (!priv->codec_conf.of_node)
			snd_soc_of_parse_node_prefix(np,
						     &priv->codec_conf,
						     dai_link->codecs->of_node,
						     "prefix");
205 206
	}

207 208
	asoc_simple_card_parse_convert(dev, link, prefix, &dai_props->adata);

209
	ret = asoc_simple_card_of_parse_tdm(np, dai);
210 211 212
	if (ret)
		return ret;

213 214 215 216
	ret = asoc_simple_card_canonicalize_dailink(dai_link);
	if (ret < 0)
		return ret;

217 218 219 220 221
	ret = asoc_simple_card_parse_daifmt(dev, link, codec,
					    prefix, &dai_link->dai_fmt);
	if (ret < 0)
		return ret;

222 223
	dai_link->dpcm_playback		= 1;
	dai_link->dpcm_capture		= 1;
224 225
	dai_link->ops			= &asoc_simple_card_ops;
	dai_link->init			= asoc_simple_card_dai_init;
226

227
	return 0;
228 229
}

230
static int asoc_simple_card_parse_of(struct simple_card_data *priv)
231

232
{
233
	struct device *dev = simple_priv_to_dev(priv);
234 235
	struct device_node *top = dev->of_node;
	struct device_node *node;
236
	struct device_node *np;
237
	struct device_node *codec;
238
	struct snd_soc_card *card = simple_priv_to_card(priv);
239
	bool is_fe;
240 241
	int ret, loop;
	int dai_idx, link_idx;
242

243
	if (!top)
244 245
		return -EINVAL;

246 247 248 249
	ret = asoc_simple_card_of_parse_widgets(card, PREFIX);
	if (ret < 0)
		return ret;

250
	ret = asoc_simple_card_of_parse_routing(card, PREFIX);
251 252 253
	if (ret < 0)
		return ret;

254
	asoc_simple_card_parse_convert(dev, top, PREFIX, &priv->adata);
255

256
	loop = 1;
257 258
	link_idx = 0;
	dai_idx = 0;
259 260 261 262
	node = of_get_child_by_name(top, PREFIX "dai-link");
	if (!node) {
		node = dev->of_node;
		loop = 0;
263 264
	}

265 266 267 268 269 270 271 272 273 274
	do  {
		codec = of_get_child_by_name(node,
					     loop ? "codec" : PREFIX "codec");
		if (!codec)
			return -ENODEV;

		for_each_child_of_node(node, np) {
			is_fe = (np != codec);

			ret = asoc_simple_card_dai_link_of(node, np, codec, priv,
275 276
							   &dai_idx, link_idx++,
							   is_fe, !loop);
277 278 279 280 281 282
			if (ret < 0)
				return ret;
		}
		node = of_get_next_child(top, node);
	} while (loop && node);

283
	ret = asoc_simple_card_parse_card_name(card, PREFIX);
284 285
	if (ret < 0)
		return ret;
286 287 288 289

	return 0;
}

290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363
static void asoc_simple_card_get_dais_count(struct device *dev,
					    int *link_num,
					    int *dais_num,
					    int *ccnf_num)
{
	struct device_node *top = dev->of_node;
	struct device_node *node;
	int loop;
	int num;

	/*
	 * link_num :	number of links.
	 *		CPU-Codec / CPU-dummy / dummy-Codec
	 * dais_num :	number of DAIs
	 * ccnf_num :	number of codec_conf
	 *		same number for "dummy-Codec"
	 *
	 * ex1)
	 * CPU0 --- Codec0	link : 5
	 * CPU1 --- Codec1	dais : 7
	 * CPU2 -/		ccnf : 1
	 * CPU3 --- Codec2
	 *
	 *	=> 5 links = 2xCPU-Codec + 2xCPU-dummy + 1xdummy-Codec
	 *	=> 7 DAIs  = 4xCPU + 3xCodec
	 *	=> 1 ccnf  = 1xdummy-Codec
	 *
	 * ex2)
	 * CPU0 --- Codec0	link : 5
	 * CPU1 --- Codec1	dais : 6
	 * CPU2 -/		ccnf : 1
	 * CPU3 -/
	 *
	 *	=> 5 links = 1xCPU-Codec + 3xCPU-dummy + 1xdummy-Codec
	 *	=> 6 DAIs  = 4xCPU + 2xCodec
	 *	=> 1 ccnf  = 1xdummy-Codec
	 *
	 * ex3)
	 * CPU0 --- Codec0	link : 6
	 * CPU1 -/		dais : 6
	 * CPU2 --- Codec1	ccnf : 2
	 * CPU3 -/
	 *
	 *	=> 6 links = 0xCPU-Codec + 4xCPU-dummy + 2xdummy-Codec
	 *	=> 6 DAIs  = 4xCPU + 2xCodec
	 *	=> 2 ccnf  = 2xdummy-Codec
	 */
	if (!top) {
		(*link_num) = 1;
		(*dais_num) = 2;
		(*ccnf_num) = 0;
		return;
	}

	loop = 1;
	node = of_get_child_by_name(top, PREFIX "dai-link");
	if (!node) {
		node = top;
		loop = 0;
	}

	do {
		num = of_get_child_count(node);
		(*dais_num) += num;
		if (num > 2) {
			(*link_num) += num;
			(*ccnf_num)++;
		} else {
			(*link_num)++;
		}
		node = of_get_next_child(top, node);
	} while (loop && node);
}

364
static int asoc_simple_card_probe(struct platform_device *pdev)
365
{
366
	struct simple_card_data *priv;
367
	struct snd_soc_dai_link *dai_link;
368
	struct simple_dai_props *dai_props;
369
	struct asoc_simple_dai *dais;
370
	struct snd_soc_card *card;
371
	struct device *dev = &pdev->dev;
372 373
	int ret, i;
	int lnum = 0, dnum = 0, cnum = 0;
374 375 376 377 378 379

	/* Allocate the private data */
	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
	if (!priv)
		return -ENOMEM;

380 381 382
	asoc_simple_card_get_dais_count(dev, &lnum, &dnum, &cnum);
	if (!lnum || !dnum)
		return -EINVAL;
383

384 385
	dai_props = devm_kcalloc(dev, lnum, sizeof(*dai_props), GFP_KERNEL);
	dai_link  = devm_kcalloc(dev, lnum, sizeof(*dai_link), GFP_KERNEL);
386 387
	dais      = devm_kcalloc(dev, dnum, sizeof(*dais),      GFP_KERNEL);
	if (!dai_props || !dai_link || !dais)
388 389
		return -ENOMEM;

390 391 392 393 394 395
	/*
	 * Use snd_soc_dai_link_component instead of legacy style
	 * It is codec only. but cpu/platform will be supported in the future.
	 * see
	 *	soc-core.c :: snd_soc_init_multicodec()
	 */
396
	for (i = 0; i < lnum; i++) {
397 398
		dai_link[i].codecs	= &dai_props[i].codecs;
		dai_link[i].num_codecs	= 1;
399
		dai_link[i].platform	= &dai_props[i].platform;
400 401
	}

402 403
	priv->dai_props				= dai_props;
	priv->dai_link				= dai_link;
404
	priv->dais				= dais;
405 406

	/* Init snd_soc_card */
407 408 409 410
	card = simple_priv_to_card(priv);
	card->owner		= THIS_MODULE;
	card->dev		= dev;
	card->dai_link		= priv->dai_link;
411
	card->num_links		= lnum;
412 413
	card->codec_conf	= &priv->codec_conf;
	card->num_configs	= 1;
414

415
	ret = asoc_simple_card_parse_of(priv);
416 417 418 419 420 421
	if (ret < 0) {
		if (ret != -EPROBE_DEFER)
			dev_err(dev, "parse error %d\n", ret);
		goto err;
	}

422
	snd_soc_card_set_drvdata(card, priv);
423

424
	ret = devm_snd_soc_register_card(dev, card);
425 426 427 428
	if (ret < 0)
		goto err;

	return 0;
429
err:
430
	asoc_simple_card_clean_reference(card);
431 432 433 434

	return ret;
}

435
static int asoc_simple_card_remove(struct platform_device *pdev)
436 437 438
{
	struct snd_soc_card *card = platform_get_drvdata(pdev);

439
	return asoc_simple_card_clean_reference(card);
440 441
}

442 443 444 445 446 447 448
static const struct of_device_id asoc_simple_of_match[] = {
	{ .compatible = "renesas,rsrc-card", },
	{ .compatible = "simple-scu-audio-card", },
	{},
};
MODULE_DEVICE_TABLE(of, asoc_simple_of_match);

449
static struct platform_driver asoc_simple_card = {
450
	.driver = {
451
		.name = "simple-scu-audio-card",
452
		.pm = &snd_soc_pm_ops,
453
		.of_match_table = asoc_simple_of_match,
454
	},
455 456
	.probe = asoc_simple_card_probe,
	.remove = asoc_simple_card_remove,
457 458
};

459
module_platform_driver(asoc_simple_card);
460

461
MODULE_ALIAS("platform:asoc-simple-scu-card");
462
MODULE_LICENSE("GPL v2");
463
MODULE_DESCRIPTION("ASoC Simple SCU Sound Card");
464
MODULE_AUTHOR("Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>");