simple-scu-card.c 8.9 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
struct asoc_simple_card_priv {
26 27
	struct snd_soc_card snd_card;
	struct snd_soc_codec_conf codec_conf;
28
	struct asoc_simple_dai *dai_props;
29
	struct snd_soc_dai_link *dai_link;
30
	u32 convert_rate;
31
	u32 convert_channels;
32 33
};

34 35 36
#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))
37

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

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

49
	return clk_prepare_enable(dai_props->clk);
50 51
}

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

59
	clk_disable_unprepare(dai_props->clk);
60 61
}

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

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

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

81
	return asoc_simple_card_init_dai(dai, dai_props);
82 83
}

84
static int asoc_simple_card_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd,
85 86
					struct snd_pcm_hw_params *params)
{
87
	struct asoc_simple_card_priv *priv = snd_soc_card_get_drvdata(rtd->card);
88 89
	struct snd_interval *rate = hw_param_interval(params,
						      SNDRV_PCM_HW_PARAM_RATE);
90 91
	struct snd_interval *channels = hw_param_interval(params,
						SNDRV_PCM_HW_PARAM_CHANNELS);
92

93 94 95
	if (priv->convert_rate)
		rate->min =
		rate->max = priv->convert_rate;
96

97 98 99
	if (priv->convert_channels)
		channels->min =
		channels->max = priv->convert_channels;
100 101 102 103

	return 0;
}

104 105
static int asoc_simple_card_parse_links(struct device_node *np,
					struct asoc_simple_card_priv *priv,
106
					unsigned int daifmt,
107
					int idx, bool is_fe)
108
{
109 110 111
	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);
112 113
	int ret;

114 115 116 117 118 119 120 121 122
	/* 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;

123
	if (is_fe) {
124 125
		int is_single_links = 0;

126 127 128 129 130 131 132 133
		/* 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;
134 135 136 137

		ret = asoc_simple_card_parse_cpu(np, dai_link, DAI, CELL,
						 &is_single_links);
		if (ret)
138
			return ret;
139

140 141 142 143
		ret = asoc_simple_card_parse_clk_cpu(np, dai_link, dai_props);
		if (ret < 0)
			return ret;

144 145 146 147 148
		ret = asoc_simple_card_set_dailink_name(dev, dai_link,
							"fe.%s",
							dai_link->cpu_dai_name);
		if (ret < 0)
			return ret;
149

150
		asoc_simple_card_canonicalize_cpu(dai_link, is_single_links);
151 152 153 154 155
	} 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";
156

157 158
		/* BE settings */
		dai_link->no_pcm		= 1;
159
		dai_link->be_hw_params_fixup	= asoc_simple_card_be_hw_params_fixup;
160 161

		ret = asoc_simple_card_parse_codec(np, dai_link, DAI, CELL);
162 163
		if (ret < 0)
			return ret;
164

165 166 167 168
		ret = asoc_simple_card_parse_clk_codec(np, dai_link, dai_props);
		if (ret < 0)
			return ret;

169 170 171 172 173 174
		ret = asoc_simple_card_set_dailink_name(dev, dai_link,
							"be.%s",
							dai_link->codec_dai_name);
		if (ret < 0)
			return ret;

175 176 177 178
		snd_soc_of_parse_audio_prefix(&priv->snd_card,
					      &priv->codec_conf,
					      dai_link->codec_of_node,
					      PREFIX "prefix");
179 180
	}

181 182 183 184
	ret = asoc_simple_card_canonicalize_dailink(dai_link);
	if (ret < 0)
		return ret;

185
	dai_link->dai_fmt		= daifmt;
186 187
	dai_link->dpcm_playback		= 1;
	dai_link->dpcm_capture		= 1;
188 189
	dai_link->ops			= &asoc_simple_card_ops;
	dai_link->init			= asoc_simple_card_dai_init;
190 191

	dev_dbg(dev, "\t%s / %04x / %d\n",
192
		dai_link->name,
193
		dai_link->dai_fmt,
194
		dai_props->sysclk);
195

196
	return 0;
197 198
}

199 200
static int asoc_simple_card_dai_link_of(struct device_node *node,
				 struct asoc_simple_card_priv *priv)
201
{
202
	struct device *dev = simple_priv_to_dev(priv);
203 204 205 206 207 208
	struct device_node *np;
	unsigned int daifmt = 0;
	int ret, i;
	bool is_fe;

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

	ret = asoc_simple_card_parse_daifmt(dev, node, np,
					    PREFIX, &daifmt);
	if (ret < 0)
		return ret;
217 218 219 220

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

224
		ret = asoc_simple_card_parse_links(np, priv, daifmt, i, is_fe);
225 226 227 228 229 230 231 232
		if (ret < 0)
			return ret;
		i++;
	}

	return 0;
}

233 234
static int asoc_simple_card_parse_of(struct device_node *node,
			      struct asoc_simple_card_priv *priv,
235
			      struct device *dev)
236
{
237
	struct asoc_simple_dai *props;
238
	struct snd_soc_dai_link *links;
239
	int ret;
240
	int num;
241 242 243 244

	if (!node)
		return -EINVAL;

245 246 247 248 249 250 251 252 253
	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;

254 255 256 257
	/* Init snd_soc_card */
	priv->snd_card.owner			= THIS_MODULE;
	priv->snd_card.dev			= dev;
	priv->snd_card.dai_link			= priv->dai_link;
258
	priv->snd_card.num_links		= num;
259 260
	priv->snd_card.codec_conf		= &priv->codec_conf;
	priv->snd_card.num_configs		= 1;
261

262 263 264
	ret = snd_soc_of_parse_audio_routing(&priv->snd_card, PREFIX "routing");
	if (ret < 0)
		return ret;
265

266
	/* sampling rate convert */
267
	of_property_read_u32(node, PREFIX "convert-rate", &priv->convert_rate);
268

269
	/* channels transfer */
270
	of_property_read_u32(node, PREFIX "convert-channels", &priv->convert_channels);
271

272
	ret = asoc_simple_card_dai_link_of(node, priv);
273 274
	if (ret < 0)
		return ret;
275

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

280 281 282 283 284
	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);

285 286 287
	return 0;
}

288
static int asoc_simple_card_probe(struct platform_device *pdev)
289
{
290
	struct asoc_simple_card_priv *priv;
291 292 293 294 295 296 297 298 299
	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;

300
	ret = asoc_simple_card_parse_of(np, priv, dev);
301 302 303 304 305 306 307 308 309 310 311 312
	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:
313
	asoc_simple_card_clean_reference(&priv->snd_card);
314 315 316 317

	return ret;
}

318
static int asoc_simple_card_remove(struct platform_device *pdev)
319 320 321
{
	struct snd_soc_card *card = platform_get_drvdata(pdev);

322
	return asoc_simple_card_clean_reference(card);
323 324
}

325 326 327 328 329 330 331
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);

332
static struct platform_driver asoc_simple_card = {
333
	.driver = {
334
		.name = "simple-scu-audio-card",
335
		.of_match_table = asoc_simple_of_match,
336
	},
337 338
	.probe = asoc_simple_card_probe,
	.remove = asoc_simple_card_remove,
339 340
};

341
module_platform_driver(asoc_simple_card);
342

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