simple-scu-card.c 8.4 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 26
	struct simple_dai_props {
		struct asoc_simple_dai dai;
27
		struct snd_soc_dai_link_component codecs;
28
		struct snd_soc_dai_link_component platform;
29
	} *dai_props;
30
	struct snd_soc_dai_link *dai_link;
31
	struct asoc_simple_card_data adata;
32 33
};

34
#define simple_priv_to_card(priv) (&(priv)->snd_card)
35
#define simple_priv_to_props(priv, i) ((priv)->dai_props + (i))
36 37
#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))
38

39 40
#define DAI	"sound-dai"
#define CELL	"#sound-dai-cells"
41
#define PREFIX	"simple-audio-card,"
42

43
static int asoc_simple_card_startup(struct snd_pcm_substream *substream)
44 45
{
	struct snd_soc_pcm_runtime *rtd = substream->private_data;
46
	struct simple_card_data *priv =	snd_soc_card_get_drvdata(rtd->card);
47
	struct simple_dai_props *dai_props =
48
		simple_priv_to_props(priv, rtd->num);
49

50
	return asoc_simple_card_clk_enable(&dai_props->dai);
51 52
}

53
static void asoc_simple_card_shutdown(struct snd_pcm_substream *substream)
54 55
{
	struct snd_soc_pcm_runtime *rtd = substream->private_data;
56
	struct simple_card_data *priv =	snd_soc_card_get_drvdata(rtd->card);
57
	struct simple_dai_props *dai_props =
58
		simple_priv_to_props(priv, rtd->num);
59

60
	asoc_simple_card_clk_disable(&dai_props->dai);
61 62
}

63
static const struct snd_soc_ops asoc_simple_card_ops = {
64 65
	.startup = asoc_simple_card_startup,
	.shutdown = asoc_simple_card_shutdown,
66 67
};

68
static int asoc_simple_card_dai_init(struct snd_soc_pcm_runtime *rtd)
69
{
70
	struct simple_card_data *priv = snd_soc_card_get_drvdata(rtd->card);
71 72
	struct snd_soc_dai *dai;
	struct snd_soc_dai_link *dai_link;
73
	struct simple_dai_props *dai_props;
74
	int num = rtd->num;
75

76 77
	dai_link	= simple_priv_to_link(priv, num);
	dai_props	= simple_priv_to_props(priv, num);
78 79 80 81
	dai		= dai_link->dynamic ?
				rtd->cpu_dai :
				rtd->codec_dai;

82
	return asoc_simple_card_init_dai(dai, &dai_props->dai);
83 84
}

85
static int asoc_simple_card_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd,
86 87
					struct snd_pcm_hw_params *params)
{
88
	struct simple_card_data *priv = snd_soc_card_get_drvdata(rtd->card);
89

90
	asoc_simple_card_convert_fixup(&priv->adata, params);
91 92 93 94

	return 0;
}

95
static int asoc_simple_card_dai_link_of(struct device_node *np,
96
					struct simple_card_data *priv,
97
					unsigned int daifmt,
98
					int idx, bool is_fe)
99
{
100 101
	struct device *dev = simple_priv_to_dev(priv);
	struct snd_soc_dai_link *dai_link = simple_priv_to_link(priv, idx);
102
	struct simple_dai_props *dai_props = simple_priv_to_props(priv, idx);
103
	struct snd_soc_card *card = simple_priv_to_card(priv);
104 105
	int ret;

106
	if (is_fe) {
107
		int is_single_links = 0;
108
		struct snd_soc_dai_link_component *codecs;
109

110
		/* BE is dummy */
111 112 113 114
		codecs			= dai_link->codecs;
		codecs->of_node		= NULL;
		codecs->dai_name	= "snd-soc-dummy-dai";
		codecs->name		= "snd-soc-dummy";
115 116 117 118

		/* FE settings */
		dai_link->dynamic		= 1;
		dai_link->dpcm_merged_format	= 1;
119 120 121 122

		ret = asoc_simple_card_parse_cpu(np, dai_link, DAI, CELL,
						 &is_single_links);
		if (ret)
123
			return ret;
124

125
		ret = asoc_simple_card_parse_clk_cpu(dev, np, dai_link, &dai_props->dai);
126 127 128
		if (ret < 0)
			return ret;

129 130 131 132 133
		ret = asoc_simple_card_set_dailink_name(dev, dai_link,
							"fe.%s",
							dai_link->cpu_dai_name);
		if (ret < 0)
			return ret;
134

135
		asoc_simple_card_canonicalize_cpu(dai_link, is_single_links);
136 137 138 139 140
	} 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";
141

142 143
		/* BE settings */
		dai_link->no_pcm		= 1;
144
		dai_link->be_hw_params_fixup	= asoc_simple_card_be_hw_params_fixup;
145 146

		ret = asoc_simple_card_parse_codec(np, dai_link, DAI, CELL);
147 148
		if (ret < 0)
			return ret;
149

150
		ret = asoc_simple_card_parse_clk_codec(dev, np, dai_link, &dai_props->dai);
151 152 153
		if (ret < 0)
			return ret;

154 155
		ret = asoc_simple_card_set_dailink_name(dev, dai_link,
							"be.%s",
156
							dai_link->codecs->dai_name);
157 158 159
		if (ret < 0)
			return ret;

160
		snd_soc_of_parse_audio_prefix(card,
161
					      &priv->codec_conf,
162
					      dai_link->codecs->of_node,
163
					      PREFIX "prefix");
164 165
	}

166
	ret = asoc_simple_card_of_parse_tdm(np, &dai_props->dai);
167 168 169
	if (ret)
		return ret;

170 171 172 173
	ret = asoc_simple_card_canonicalize_dailink(dai_link);
	if (ret < 0)
		return ret;

174
	dai_link->dai_fmt		= daifmt;
175 176
	dai_link->dpcm_playback		= 1;
	dai_link->dpcm_capture		= 1;
177 178
	dai_link->ops			= &asoc_simple_card_ops;
	dai_link->init			= asoc_simple_card_dai_init;
179

180
	return 0;
181 182
}

183
static int asoc_simple_card_parse_of(struct simple_card_data *priv)
184

185
{
186
	struct device *dev = simple_priv_to_dev(priv);
187
	struct device_node *np;
188
	struct snd_soc_card *card = simple_priv_to_card(priv);
189
	struct device_node *node = dev->of_node;
190 191
	unsigned int daifmt = 0;
	bool is_fe;
192 193 194 195 196
	int ret, i;

	if (!node)
		return -EINVAL;

197 198 199 200
	ret = asoc_simple_card_of_parse_widgets(card, PREFIX);
	if (ret < 0)
		return ret;

201
	ret = asoc_simple_card_of_parse_routing(card, PREFIX, 0);
202 203 204
	if (ret < 0)
		return ret;

205
	asoc_simple_card_parse_convert(dev, node, PREFIX, &priv->adata);
206 207

	/* find 1st codec */
208 209 210 211
	np = of_get_child_by_name(node, PREFIX "codec");
	if (!np)
		return -ENODEV;

212
	ret = asoc_simple_card_parse_daifmt(dev, node, np, PREFIX, &daifmt);
213 214
	if (ret < 0)
		return ret;
215 216 217 218

	i = 0;
	for_each_child_of_node(node, np) {
		is_fe = false;
219
		if (strcmp(np->name, PREFIX "cpu") == 0)
220 221
			is_fe = true;

222
		ret = asoc_simple_card_dai_link_of(np, priv, daifmt, i, is_fe);
223 224 225 226 227
		if (ret < 0)
			return ret;
		i++;
	}

228
	ret = asoc_simple_card_parse_card_name(card, PREFIX);
229 230
	if (ret < 0)
		return ret;
231 232 233 234

	return 0;
}

235
static int asoc_simple_card_probe(struct platform_device *pdev)
236
{
237
	struct simple_card_data *priv;
238
	struct snd_soc_dai_link *dai_link;
239
	struct simple_dai_props *dai_props;
240
	struct snd_soc_card *card;
241
	struct device *dev = &pdev->dev;
242
	struct device_node *np = dev->of_node;
243
	int num, ret, i;
244 245 246 247 248 249

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

250 251
	num = of_get_child_count(np);

252 253
	dai_props = devm_kcalloc(dev, num, sizeof(*dai_props), GFP_KERNEL);
	dai_link  = devm_kcalloc(dev, num, sizeof(*dai_link), GFP_KERNEL);
254
	if (!dai_props || !dai_link)
255 256
		return -ENOMEM;

257 258 259 260 261 262 263 264 265
	/*
	 * 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()
	 */
	for (i = 0; i < num; i++) {
		dai_link[i].codecs	= &dai_props[i].codecs;
		dai_link[i].num_codecs	= 1;
266
		dai_link[i].platform	= &dai_props[i].platform;
267 268
	}

269 270
	priv->dai_props				= dai_props;
	priv->dai_link				= dai_link;
271 272

	/* Init snd_soc_card */
273 274 275 276 277 278 279
	card = simple_priv_to_card(priv);
	card->owner		= THIS_MODULE;
	card->dev		= dev;
	card->dai_link		= priv->dai_link;
	card->num_links		= num;
	card->codec_conf	= &priv->codec_conf;
	card->num_configs	= 1;
280

281
	ret = asoc_simple_card_parse_of(priv);
282 283 284 285 286 287
	if (ret < 0) {
		if (ret != -EPROBE_DEFER)
			dev_err(dev, "parse error %d\n", ret);
		goto err;
	}

288
	snd_soc_card_set_drvdata(card, priv);
289

290
	ret = devm_snd_soc_register_card(dev, card);
291 292 293 294
	if (ret < 0)
		goto err;

	return 0;
295
err:
296
	asoc_simple_card_clean_reference(card);
297 298 299 300

	return ret;
}

301
static int asoc_simple_card_remove(struct platform_device *pdev)
302 303 304
{
	struct snd_soc_card *card = platform_get_drvdata(pdev);

305
	return asoc_simple_card_clean_reference(card);
306 307
}

308 309 310 311 312 313 314
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);

315
static struct platform_driver asoc_simple_card = {
316
	.driver = {
317
		.name = "simple-scu-audio-card",
318
		.pm = &snd_soc_pm_ops,
319
		.of_match_table = asoc_simple_of_match,
320
	},
321 322
	.probe = asoc_simple_card_probe,
	.remove = asoc_simple_card_remove,
323 324
};

325
module_platform_driver(asoc_simple_card);
326

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