simple-scu-card.c 9.0 KB
Newer Older
1
/*
2
 * ASoC simple SCU sound card support
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
 *
 * Copyright (C) 2015 Renesas Solutions Corp.
 * Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
 *
 * based on ${LINUX}/sound/soc/generic/simple-card.c
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */
#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>
23
#include <sound/simple_card_utils.h>
24

25
static const struct of_device_id asoc_simple_card_of_match[] = {
26
	{ .compatible = "renesas,rsrc-card", },
27
	{ .compatible = "simple-scu-audio-card", },
28 29
	{},
};
30
MODULE_DEVICE_TABLE(of, asoc_simple_card_of_match);
31

32
struct asoc_simple_card_priv {
33 34
	struct snd_soc_card snd_card;
	struct snd_soc_codec_conf codec_conf;
35
	struct asoc_simple_dai *dai_props;
36
	struct snd_soc_dai_link *dai_link;
37
	u32 convert_rate;
38
	u32 convert_channels;
39 40
};

41 42 43
#define simple_priv_to_dev(priv) ((priv)->snd_card.dev)
#define simple_priv_to_link(priv, i) ((priv)->snd_card.dai_link + (i))
#define simple_priv_to_props(priv, i) ((priv)->dai_props + (i))
44

45 46
#define DAI	"sound-dai"
#define CELL	"#sound-dai-cells"
47
#define PREFIX	"simple-audio-card,"
48

49
static int asoc_simple_card_startup(struct snd_pcm_substream *substream)
50 51
{
	struct snd_soc_pcm_runtime *rtd = substream->private_data;
52
	struct asoc_simple_card_priv *priv =	snd_soc_card_get_drvdata(rtd->card);
53
	struct asoc_simple_dai *dai_props =
54
		simple_priv_to_props(priv, rtd->num);
55

56
	return clk_prepare_enable(dai_props->clk);
57 58
}

59
static void asoc_simple_card_shutdown(struct snd_pcm_substream *substream)
60 61
{
	struct snd_soc_pcm_runtime *rtd = substream->private_data;
62
	struct asoc_simple_card_priv *priv =	snd_soc_card_get_drvdata(rtd->card);
63
	struct asoc_simple_dai *dai_props =
64
		simple_priv_to_props(priv, rtd->num);
65

66
	clk_disable_unprepare(dai_props->clk);
67 68
}

69 70 71
static struct snd_soc_ops asoc_simple_card_ops = {
	.startup = asoc_simple_card_startup,
	.shutdown = asoc_simple_card_shutdown,
72 73
};

74
static int asoc_simple_card_dai_init(struct snd_soc_pcm_runtime *rtd)
75
{
76
	struct asoc_simple_card_priv *priv = snd_soc_card_get_drvdata(rtd->card);
77 78
	struct snd_soc_dai *dai;
	struct snd_soc_dai_link *dai_link;
79
	struct asoc_simple_dai *dai_props;
80
	int num = rtd->num;
81

82 83
	dai_link	= simple_priv_to_link(priv, num);
	dai_props	= simple_priv_to_props(priv, num);
84 85 86 87
	dai		= dai_link->dynamic ?
				rtd->cpu_dai :
				rtd->codec_dai;

88
	return asoc_simple_card_init_dai(dai, dai_props);
89 90
}

91
static int asoc_simple_card_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd,
92 93
					struct snd_pcm_hw_params *params)
{
94
	struct asoc_simple_card_priv *priv = snd_soc_card_get_drvdata(rtd->card);
95 96
	struct snd_interval *rate = hw_param_interval(params,
						      SNDRV_PCM_HW_PARAM_RATE);
97 98
	struct snd_interval *channels = hw_param_interval(params,
						SNDRV_PCM_HW_PARAM_CHANNELS);
99

100 101 102
	if (priv->convert_rate)
		rate->min =
		rate->max = priv->convert_rate;
103

104 105 106
	if (priv->convert_channels)
		channels->min =
		channels->max = priv->convert_channels;
107 108 109 110

	return 0;
}

111 112 113
static int asoc_simple_card_parse_links(struct device_node *np,
					struct asoc_simple_card_priv *priv,
					int idx, bool is_fe)
114
{
115 116 117
	struct device *dev = simple_priv_to_dev(priv);
	struct snd_soc_dai_link *dai_link = simple_priv_to_link(priv, idx);
	struct asoc_simple_dai *dai_props = simple_priv_to_props(priv, idx);
118 119
	int ret;

120 121 122 123 124 125 126 127 128
	/* Parse TDM slot */
	ret = snd_soc_of_parse_tdm_slot(np,
					&dai_props->tx_slot_mask,
					&dai_props->rx_slot_mask,
					&dai_props->slots,
					&dai_props->slot_width);
	if (ret)
		return ret;

129
	if (is_fe) {
130 131
		int is_single_links = 0;

132 133 134 135 136 137 138 139
		/* BE is dummy */
		dai_link->codec_of_node		= NULL;
		dai_link->codec_dai_name	= "snd-soc-dummy-dai";
		dai_link->codec_name		= "snd-soc-dummy";

		/* FE settings */
		dai_link->dynamic		= 1;
		dai_link->dpcm_merged_format	= 1;
140 141 142 143

		ret = asoc_simple_card_parse_cpu(np, dai_link, DAI, CELL,
						 &is_single_links);
		if (ret)
144
			return ret;
145

146 147 148 149
		ret = asoc_simple_card_parse_clk_cpu(np, dai_link, dai_props);
		if (ret < 0)
			return ret;

150 151 152 153 154
		ret = asoc_simple_card_set_dailink_name(dev, dai_link,
							"fe.%s",
							dai_link->cpu_dai_name);
		if (ret < 0)
			return ret;
155

156
		asoc_simple_card_canonicalize_cpu(dai_link, is_single_links);
157 158 159 160 161
	} 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";
162

163 164
		/* BE settings */
		dai_link->no_pcm		= 1;
165
		dai_link->be_hw_params_fixup	= asoc_simple_card_be_hw_params_fixup;
166 167

		ret = asoc_simple_card_parse_codec(np, dai_link, DAI, CELL);
168 169
		if (ret < 0)
			return ret;
170

171 172 173 174
		ret = asoc_simple_card_parse_clk_codec(np, dai_link, dai_props);
		if (ret < 0)
			return ret;

175 176 177 178 179 180
		ret = asoc_simple_card_set_dailink_name(dev, dai_link,
							"be.%s",
							dai_link->codec_dai_name);
		if (ret < 0)
			return ret;

181 182 183 184
		snd_soc_of_parse_audio_prefix(&priv->snd_card,
					      &priv->codec_conf,
					      dai_link->codec_of_node,
					      PREFIX "prefix");
185 186
	}

187 188 189 190
	ret = asoc_simple_card_canonicalize_dailink(dai_link);
	if (ret < 0)
		return ret;

191 192
	dai_link->dpcm_playback		= 1;
	dai_link->dpcm_capture		= 1;
193 194
	dai_link->ops			= &asoc_simple_card_ops;
	dai_link->init			= asoc_simple_card_dai_init;
195 196

	dev_dbg(dev, "\t%s / %04x / %d\n",
197
		dai_link->name,
198
		dai_link->dai_fmt,
199
		dai_props->sysclk);
200

201
	return 0;
202 203
}

204 205
static int asoc_simple_card_dai_link_of(struct device_node *node,
				 struct asoc_simple_card_priv *priv)
206
{
207
	struct device *dev = simple_priv_to_dev(priv);
208 209 210 211 212 213 214 215 216
	struct snd_soc_dai_link *dai_link;
	struct device_node *np;
	unsigned int daifmt = 0;
	int ret, i;
	bool is_fe;

	/* find 1st codec */
	i = 0;
	for_each_child_of_node(node, np) {
217
		dai_link = simple_priv_to_link(priv, i);
218

219
		if (strcmp(np->name, PREFIX "codec") == 0) {
220
			ret = asoc_simple_card_parse_daifmt(dev, node, np,
221
							    PREFIX, &daifmt);
222 223 224 225 226 227 228 229 230
			if (ret < 0)
				return ret;
			break;
		}
		i++;
	}

	i = 0;
	for_each_child_of_node(node, np) {
231
		dai_link = simple_priv_to_link(priv, i);
232 233 234
		dai_link->dai_fmt = daifmt;

		is_fe = false;
235
		if (strcmp(np->name, PREFIX "cpu") == 0)
236 237
			is_fe = true;

238
		ret = asoc_simple_card_parse_links(np, priv, i, is_fe);
239 240 241 242 243 244 245 246
		if (ret < 0)
			return ret;
		i++;
	}

	return 0;
}

247 248
static int asoc_simple_card_parse_of(struct device_node *node,
			      struct asoc_simple_card_priv *priv,
249
			      struct device *dev)
250
{
251
	struct asoc_simple_dai *props;
252
	struct snd_soc_dai_link *links;
253
	int ret;
254
	int num;
255 256 257 258

	if (!node)
		return -EINVAL;

259 260 261 262 263 264 265 266 267
	num = of_get_child_count(node);
	props = devm_kzalloc(dev, sizeof(*props) * num, GFP_KERNEL);
	links = devm_kzalloc(dev, sizeof(*links) * num, GFP_KERNEL);
	if (!props || !links)
		return -ENOMEM;

	priv->dai_props	= props;
	priv->dai_link	= links;

268 269 270 271
	/* Init snd_soc_card */
	priv->snd_card.owner			= THIS_MODULE;
	priv->snd_card.dev			= dev;
	priv->snd_card.dai_link			= priv->dai_link;
272
	priv->snd_card.num_links		= num;
273 274
	priv->snd_card.codec_conf		= &priv->codec_conf;
	priv->snd_card.num_configs		= 1;
275

276 277 278
	ret = snd_soc_of_parse_audio_routing(&priv->snd_card, PREFIX "routing");
	if (ret < 0)
		return ret;
279

280
	/* sampling rate convert */
281
	of_property_read_u32(node, PREFIX "convert-rate", &priv->convert_rate);
282

283
	/* channels transfer */
284
	of_property_read_u32(node, PREFIX "convert-channels", &priv->convert_channels);
285

286
	ret = asoc_simple_card_dai_link_of(node, priv);
287 288
	if (ret < 0)
		return ret;
289

290
	ret = asoc_simple_card_parse_card_name(&priv->snd_card, PREFIX);
291 292
	if (ret < 0)
		return ret;
293

294 295 296 297 298
	dev_dbg(dev, "New card: %s\n",
		priv->snd_card.name ? priv->snd_card.name : "");
	dev_dbg(dev, "convert_rate     %d\n", priv->convert_rate);
	dev_dbg(dev, "convert_channels %d\n", priv->convert_channels);

299 300 301
	return 0;
}

302
static int asoc_simple_card_probe(struct platform_device *pdev)
303
{
304
	struct asoc_simple_card_priv *priv;
305 306 307 308 309 310 311 312 313
	struct device_node *np = pdev->dev.of_node;
	struct device *dev = &pdev->dev;
	int ret;

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

314
	ret = asoc_simple_card_parse_of(np, priv, dev);
315 316 317 318 319 320 321 322 323 324 325 326
	if (ret < 0) {
		if (ret != -EPROBE_DEFER)
			dev_err(dev, "parse error %d\n", ret);
		goto err;
	}

	snd_soc_card_set_drvdata(&priv->snd_card, priv);

	ret = devm_snd_soc_register_card(&pdev->dev, &priv->snd_card);
	if (ret >= 0)
		return ret;
err:
327
	asoc_simple_card_clean_reference(&priv->snd_card);
328 329 330 331

	return ret;
}

332
static int asoc_simple_card_remove(struct platform_device *pdev)
333 334 335
{
	struct snd_soc_card *card = platform_get_drvdata(pdev);

336
	return asoc_simple_card_clean_reference(card);
337 338
}

339
static struct platform_driver asoc_simple_card = {
340
	.driver = {
341
		.name = "simple-scu-audio-card",
342
		.of_match_table = asoc_simple_card_of_match,
343
	},
344 345
	.probe = asoc_simple_card_probe,
	.remove = asoc_simple_card_remove,
346 347
};

348
module_platform_driver(asoc_simple_card);
349

350
MODULE_ALIAS("platform:asoc-simple-scu-card");
351
MODULE_LICENSE("GPL");
352
MODULE_DESCRIPTION("ASoC Simple SCU Sound Card");
353
MODULE_AUTHOR("Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>");