simple-scu-card.c 8.6 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 simple_card_data {
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
#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 asoc_simple_dai *dai_props =
48
		simple_priv_to_props(priv, rtd->num);
49

50
	return clk_prepare_enable(dai_props->clk);
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 asoc_simple_dai *dai_props =
58
		simple_priv_to_props(priv, rtd->num);
59

60
	clk_disable_unprepare(dai_props->clk);
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 asoc_simple_dai *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);
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
	struct snd_interval *rate = hw_param_interval(params,
						      SNDRV_PCM_HW_PARAM_RATE);
91 92
	struct snd_interval *channels = hw_param_interval(params,
						SNDRV_PCM_HW_PARAM_CHANNELS);
93

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

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

	return 0;
}

105
static int asoc_simple_card_dai_link_of(struct device_node *np,
106
					struct simple_card_data *priv,
107
					unsigned int daifmt,
108
					int idx, bool is_fe)
109
{
110 111 112
	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);
113
	struct snd_soc_card *card = simple_priv_to_card(priv);
114 115
	int ret;

116
	if (is_fe) {
117 118
		int is_single_links = 0;

119 120 121 122 123 124 125 126
		/* 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;
127 128 129 130

		ret = asoc_simple_card_parse_cpu(np, dai_link, DAI, CELL,
						 &is_single_links);
		if (ret)
131
			return ret;
132

133
		ret = asoc_simple_card_parse_clk_cpu(dev, np, dai_link, dai_props);
134 135 136
		if (ret < 0)
			return ret;

137 138 139 140 141
		ret = asoc_simple_card_set_dailink_name(dev, dai_link,
							"fe.%s",
							dai_link->cpu_dai_name);
		if (ret < 0)
			return ret;
142

143
		asoc_simple_card_canonicalize_cpu(dai_link, is_single_links);
144 145 146 147 148
	} 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";
149

150 151
		/* BE settings */
		dai_link->no_pcm		= 1;
152
		dai_link->be_hw_params_fixup	= asoc_simple_card_be_hw_params_fixup;
153 154

		ret = asoc_simple_card_parse_codec(np, dai_link, DAI, CELL);
155 156
		if (ret < 0)
			return ret;
157

158
		ret = asoc_simple_card_parse_clk_codec(dev, np, dai_link, dai_props);
159 160 161
		if (ret < 0)
			return ret;

162 163 164 165 166 167
		ret = asoc_simple_card_set_dailink_name(dev, dai_link,
							"be.%s",
							dai_link->codec_dai_name);
		if (ret < 0)
			return ret;

168
		snd_soc_of_parse_audio_prefix(card,
169 170 171
					      &priv->codec_conf,
					      dai_link->codec_of_node,
					      PREFIX "prefix");
172 173
	}

174 175 176 177 178 179 180 181
	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;

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

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

192
	return 0;
193 194
}

195
static int asoc_simple_card_parse_of(struct device_node *node,
196
				     struct simple_card_data *priv)
197

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

	if (!node)
		return -EINVAL;

209
	ret = snd_soc_of_parse_audio_routing(card, PREFIX "routing");
210 211 212 213 214 215 216 217
	if (ret < 0)
		return ret;

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

	/* channels transfer */
	of_property_read_u32(node, PREFIX "convert-channels", &priv->convert_channels);
218 219

	/* find 1st codec */
220 221 222 223
	np = of_get_child_by_name(node, PREFIX "codec");
	if (!np)
		return -ENODEV;

224
	ret = asoc_simple_card_parse_daifmt(dev, node, np, PREFIX, &daifmt);
225 226
	if (ret < 0)
		return ret;
227 228 229 230

	i = 0;
	for_each_child_of_node(node, np) {
		is_fe = false;
231
		if (strcmp(np->name, PREFIX "cpu") == 0)
232 233
			is_fe = true;

234
		ret = asoc_simple_card_dai_link_of(np, priv, daifmt, i, is_fe);
235 236 237 238 239
		if (ret < 0)
			return ret;
		i++;
	}

240
	ret = asoc_simple_card_parse_card_name(card, PREFIX);
241 242
	if (ret < 0)
		return ret;
243

244 245 246
	dev_dbg(dev, "convert_rate     %d\n", priv->convert_rate);
	dev_dbg(dev, "convert_channels %d\n", priv->convert_channels);

247 248 249
	return 0;
}

250
static int asoc_simple_card_probe(struct platform_device *pdev)
251
{
252
	struct simple_card_data *priv;
253 254
	struct snd_soc_dai_link *dai_link;
	struct asoc_simple_dai *dai_props;
255
	struct snd_soc_card *card;
256
	struct device *dev = &pdev->dev;
257
	struct device_node *np = dev->of_node;
258
	int num, ret;
259 260 261 262 263 264

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

265 266
	num = of_get_child_count(np);

267 268 269
	dai_props = devm_kzalloc(dev, sizeof(*dai_props) * num, GFP_KERNEL);
	dai_link  = devm_kzalloc(dev, sizeof(*dai_link)  * num, GFP_KERNEL);
	if (!dai_props || !dai_link)
270 271
		return -ENOMEM;

272 273
	priv->dai_props				= dai_props;
	priv->dai_link				= dai_link;
274 275

	/* Init snd_soc_card */
276 277 278 279 280 281 282
	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;
283 284

	ret = asoc_simple_card_parse_of(np, priv);
285 286 287 288 289 290
	if (ret < 0) {
		if (ret != -EPROBE_DEFER)
			dev_err(dev, "parse error %d\n", ret);
		goto err;
	}

291
	snd_soc_card_set_drvdata(card, priv);
292

293
	ret = devm_snd_soc_register_card(dev, card);
294 295 296 297
	if (ret < 0)
		goto err;

	return 0;
298
err:
299
	asoc_simple_card_clean_reference(card);
300 301 302 303

	return ret;
}

304
static int asoc_simple_card_remove(struct platform_device *pdev)
305 306 307
{
	struct snd_soc_card *card = platform_get_drvdata(pdev);

308
	return asoc_simple_card_clean_reference(card);
309 310
}

311 312 313 314 315 316 317
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);

318
static struct platform_driver asoc_simple_card = {
319
	.driver = {
320
		.name = "simple-scu-audio-card",
321
		.of_match_table = asoc_simple_of_match,
322
	},
323 324
	.probe = asoc_simple_card_probe,
	.remove = asoc_simple_card_remove,
325 326
};

327
module_platform_driver(asoc_simple_card);
328

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