summaryrefslogtreecommitdiff
path: root/sound/soc/sti/sti_uniperif.c
blob: 549fac349fa03809153c2b47e82bad2240266d9b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
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
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
/*
 * Copyright (C) STMicroelectronics SA 2015
 * Authors: Arnaud Pouliquen <arnaud.pouliquen@st.com>
 *          for STMicroelectronics.
 * License terms:  GNU General Public License (GPL), version 2
 */

#include <linux/module.h>
#include <linux/pinctrl/consumer.h>

#include "uniperif.h"

/*
 * User frame size shall be 2, 4, 6 or 8 32-bits words length
 * (i.e. 8, 16, 24 or 32 bytes)
 * This constraint comes from allowed values for
 * UNIPERIF_I2S_FMT_NUM_CH register
 */
#define UNIPERIF_MAX_FRAME_SZ 0x20
#define UNIPERIF_ALLOWED_FRAME_SZ (0x08 | 0x10 | 0x18 | UNIPERIF_MAX_FRAME_SZ)

struct sti_uniperiph_dev_data {
	unsigned int id; /* Nb available player instances */
	unsigned int version; /* player IP version */
	unsigned int stream;
	const char *dai_names;
	enum uniperif_type type;
};

static const struct sti_uniperiph_dev_data sti_uniplayer_hdmi = {
	.id = 0,
	.version = SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0,
	.stream = SNDRV_PCM_STREAM_PLAYBACK,
	.dai_names = "Uni Player #0 (HDMI)",
	.type = SND_ST_UNIPERIF_TYPE_HDMI
};

static const struct sti_uniperiph_dev_data sti_uniplayer_pcm_out = {
	.id = 1,
	.version = SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0,
	.stream = SNDRV_PCM_STREAM_PLAYBACK,
	.dai_names = "Uni Player #1 (PCM OUT)",
	.type = SND_ST_UNIPERIF_TYPE_PCM | SND_ST_UNIPERIF_TYPE_TDM,
};

static const struct sti_uniperiph_dev_data sti_uniplayer_dac = {
	.id = 2,
	.version = SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0,
	.stream = SNDRV_PCM_STREAM_PLAYBACK,
	.dai_names = "Uni Player #2 (DAC)",
	.type = SND_ST_UNIPERIF_TYPE_PCM,
};

static const struct sti_uniperiph_dev_data sti_uniplayer_spdif = {
	.id = 3,
	.version = SND_ST_UNIPERIF_VERSION_UNI_PLR_TOP_1_0,
	.stream = SNDRV_PCM_STREAM_PLAYBACK,
	.dai_names = "Uni Player #3 (SPDIF)",
	.type = SND_ST_UNIPERIF_TYPE_SPDIF
};

static const struct sti_uniperiph_dev_data sti_unireader_pcm_in = {
	.id = 0,
	.version = SND_ST_UNIPERIF_VERSION_UNI_RDR_1_0,
	.stream = SNDRV_PCM_STREAM_CAPTURE,
	.dai_names = "Uni Reader #0 (PCM IN)",
	.type = SND_ST_UNIPERIF_TYPE_PCM | SND_ST_UNIPERIF_TYPE_TDM,
};

static const struct sti_uniperiph_dev_data sti_unireader_hdmi_in = {
	.id = 1,
	.version = SND_ST_UNIPERIF_VERSION_UNI_RDR_1_0,
	.stream = SNDRV_PCM_STREAM_CAPTURE,
	.dai_names = "Uni Reader #1 (HDMI IN)",
	.type = SND_ST_UNIPERIF_TYPE_PCM,
};

static const struct of_device_id snd_soc_sti_match[] = {
	{ .compatible = "st,stih407-uni-player-hdmi",
	  .data = &sti_uniplayer_hdmi
	},
	{ .compatible = "st,stih407-uni-player-pcm-out",
	  .data = &sti_uniplayer_pcm_out
	},
	{ .compatible = "st,stih407-uni-player-dac",
	  .data = &sti_uniplayer_dac
	},
	{ .compatible = "st,stih407-uni-player-spdif",
	  .data = &sti_uniplayer_spdif
	},
	{ .compatible = "st,stih407-uni-reader-pcm_in",
	  .data = &sti_unireader_pcm_in
	},
	{ .compatible = "st,stih407-uni-reader-hdmi",
	  .data = &sti_unireader_hdmi_in
	},
	{},
};

int sti_uniperiph_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask,
			       unsigned int rx_mask, int slots,
			       int slot_width)
{
	struct sti_uniperiph_data *priv = snd_soc_dai_get_drvdata(dai);
	struct uniperif *uni = priv->dai_data.uni;
	int i, frame_size, avail_slots;

	if (!UNIPERIF_TYPE_IS_TDM(uni)) {
		dev_err(uni->dev, "cpu dai not in tdm mode\n");
		return -EINVAL;
	}

	/* store info in unip context */
	uni->tdm_slot.slots = slots;
	uni->tdm_slot.slot_width = slot_width;
	/* unip is unidirectionnal */
	uni->tdm_slot.mask = (tx_mask != 0) ? tx_mask : rx_mask;

	/* number of available timeslots */
	for (i = 0, avail_slots = 0; i < uni->tdm_slot.slots; i++) {
		if ((uni->tdm_slot.mask >> i) & 0x01)
			avail_slots++;
	}
	uni->tdm_slot.avail_slots = avail_slots;

	/* frame size in bytes */
	frame_size = uni->tdm_slot.avail_slots * uni->tdm_slot.slot_width / 8;

	/* check frame size is allowed */
	if ((frame_size > UNIPERIF_MAX_FRAME_SZ) ||
	    (frame_size & ~(int)UNIPERIF_ALLOWED_FRAME_SZ)) {
		dev_err(uni->dev, "frame size not allowed: %d bytes\n",
			frame_size);
		return -EINVAL;
	}

	return 0;
}

int sti_uniperiph_fix_tdm_chan(struct snd_pcm_hw_params *params,
			       struct snd_pcm_hw_rule *rule)
{
	struct uniperif *uni = rule->private;
	struct snd_interval t;

	t.min = uni->tdm_slot.avail_slots;
	t.max = uni->tdm_slot.avail_slots;
	t.openmin = 0;
	t.openmax = 0;
	t.integer = 0;

	return snd_interval_refine(hw_param_interval(params, rule->var), &t);
}

int sti_uniperiph_fix_tdm_format(struct snd_pcm_hw_params *params,
				 struct snd_pcm_hw_rule *rule)
{
	struct uniperif *uni = rule->private;
	struct snd_mask *maskp = hw_param_mask(params, rule->var);
	u64 format;

	switch (uni->tdm_slot.slot_width) {
	case 16:
		format = SNDRV_PCM_FMTBIT_S16_LE;
		break;
	case 32:
		format = SNDRV_PCM_FMTBIT_S32_LE;
		break;
	default:
		dev_err(uni->dev, "format not supported: %d bits\n",
			uni->tdm_slot.slot_width);
		return -EINVAL;
	}

	maskp->bits[0] &= (u_int32_t)format;
	maskp->bits[1] &= (u_int32_t)(format >> 32);
	/* clear remaining indexes */
	memset(maskp->bits + 2, 0, (SNDRV_MASK_MAX - 64) / 8);

	if (!maskp->bits[0] && !maskp->bits[1])
		return -EINVAL;

	return 0;
}

int sti_uniperiph_get_tdm_word_pos(struct uniperif *uni,
				   unsigned int *word_pos)
{
	int slot_width = uni->tdm_slot.slot_width / 8;
	int slots_num = uni->tdm_slot.slots;
	unsigned int slots_mask = uni->tdm_slot.mask;
	int i, j, k;
	unsigned int word16_pos[4];

	/* word16_pos:
	 * word16_pos[0] = WORDX_LSB
	 * word16_pos[1] = WORDX_MSB,
	 * word16_pos[2] = WORDX+1_LSB
	 * word16_pos[3] = WORDX+1_MSB
	 */

	/* set unip word position */
	for (i = 0, j = 0, k = 0; (i < slots_num) && (k < WORD_MAX); i++) {
		if ((slots_mask >> i) & 0x01) {
			word16_pos[j] = i * slot_width;

			if (slot_width == 4) {
				word16_pos[j + 1] = word16_pos[j] + 2;
				j++;
			}
			j++;

			if (j > 3) {
				word_pos[k] = word16_pos[1] |
					      (word16_pos[0] << 8) |
					      (word16_pos[3] << 16) |
					      (word16_pos[2] << 24);
				j = 0;
				k++;
			}
		}
	}

	return 0;
}

/*
 * sti_uniperiph_dai_create_ctrl
 * This function is used to create Ctrl associated to DAI but also pcm device.
 * Request is done by front end to associate ctrl with pcm device id
 */
static int sti_uniperiph_dai_create_ctrl(struct snd_soc_dai *dai)
{
	struct sti_uniperiph_data *priv = snd_soc_dai_get_drvdata(dai);
	struct uniperif *uni = priv->dai_data.uni;
	struct snd_kcontrol_new *ctrl;
	int i;

	if (!uni->num_ctrls)
		return 0;

	for (i = 0; i < uni->num_ctrls; i++) {
		/*
		 * Several Control can have same name. Controls are indexed on
		 * Uniperipheral instance ID
		 */
		ctrl = &uni->snd_ctrls[i];
		ctrl->index = uni->id;
		ctrl->device = uni->id;
	}

	return snd_soc_add_dai_controls(dai, uni->snd_ctrls, uni->num_ctrls);
}

/*
 * DAI
 */
int sti_uniperiph_dai_hw_params(struct snd_pcm_substream *substream,
				struct snd_pcm_hw_params *params,
				struct snd_soc_dai *dai)
{
	struct sti_uniperiph_data *priv = snd_soc_dai_get_drvdata(dai);
	struct uniperif *uni = priv->dai_data.uni;
	struct snd_dmaengine_dai_dma_data *dma_data;
	int transfer_size;

	if (uni->type == SND_ST_UNIPERIF_TYPE_TDM)
		/* transfer size = user frame size (in 32-bits FIFO cell) */
		transfer_size = snd_soc_params_to_frame_size(params) / 32;
	else
		transfer_size = params_channels(params) * UNIPERIF_FIFO_FRAMES;

	dma_data = snd_soc_dai_get_dma_data(dai, substream);
	dma_data->maxburst = transfer_size;

	return 0;
}

int sti_uniperiph_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
{
	struct sti_uniperiph_data *priv = snd_soc_dai_get_drvdata(dai);

	priv->dai_data.uni->daifmt = fmt;

	return 0;
}

static int sti_uniperiph_dai_suspend(struct snd_soc_dai *dai)
{
	struct sti_uniperiph_data *priv = snd_soc_dai_get_drvdata(dai);
	struct uniperif *uni = priv->dai_data.uni;
	int ret;

	/* The uniperipheral should be in stopped state */
	if (uni->state != UNIPERIF_STATE_STOPPED) {
		dev_err(uni->dev, "%s: invalid uni state( %d)",
			__func__, (int)uni->state);
		return -EBUSY;
	}

	/* Pinctrl: switch pinstate to sleep */
	ret = pinctrl_pm_select_sleep_state(uni->dev);
	if (ret)
		dev_err(uni->dev, "%s: failed to select pinctrl state",
			__func__);

	return ret;
}

static int sti_uniperiph_dai_resume(struct snd_soc_dai *dai)
{
	struct sti_uniperiph_data *priv = snd_soc_dai_get_drvdata(dai);
	struct uniperif *uni = priv->dai_data.uni;
	int ret;

	if (priv->dai_data.stream == SNDRV_PCM_STREAM_PLAYBACK) {
		ret = uni_player_resume(uni);
		if (ret)
			return ret;
	}

	/* pinctrl: switch pinstate to default */
	ret = pinctrl_pm_select_default_state(uni->dev);
	if (ret)
		dev_err(uni->dev, "%s: failed to select pinctrl state",
			__func__);

	return ret;
}

static int sti_uniperiph_dai_probe(struct snd_soc_dai *dai)
{
	struct sti_uniperiph_data *priv = snd_soc_dai_get_drvdata(dai);
	struct sti_uniperiph_dai *dai_data = &priv->dai_data;

	/* DMA settings*/
	if (priv->dai_data.stream == SNDRV_PCM_STREAM_PLAYBACK)
		snd_soc_dai_init_dma_data(dai, &dai_data->dma_data, NULL);
	else
		snd_soc_dai_init_dma_data(dai, NULL, &dai_data->dma_data);

	dai_data->dma_data.addr = dai_data->uni->fifo_phys_address;
	dai_data->dma_data.addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;

	return sti_uniperiph_dai_create_ctrl(dai);
}

static const struct snd_soc_dai_driver sti_uniperiph_dai_template = {
	.probe = sti_uniperiph_dai_probe,
	.suspend = sti_uniperiph_dai_suspend,
	.resume = sti_uniperiph_dai_resume
};

static const struct snd_soc_component_driver sti_uniperiph_dai_component = {
	.name = "sti_cpu_dai",
};

static int sti_uniperiph_cpu_dai_of(struct device_node *node,
				    struct sti_uniperiph_data *priv)
{
	struct device *dev = &priv->pdev->dev;
	struct sti_uniperiph_dai *dai_data = &priv->dai_data;
	struct snd_soc_dai_driver *dai = priv->dai;
	struct snd_soc_pcm_stream *stream;
	struct uniperif *uni;
	const struct of_device_id *of_id;
	const struct sti_uniperiph_dev_data *dev_data;
	const char *mode;

	/* Populate data structure depending on compatibility */
	of_id = of_match_node(snd_soc_sti_match, node);
	if (!of_id->data) {
		dev_err(dev, "data associated to device is missing");
		return -EINVAL;
	}
	dev_data = (struct sti_uniperiph_dev_data *)of_id->data;

	uni = devm_kzalloc(dev, sizeof(*uni), GFP_KERNEL);
	if (!uni)
		return -ENOMEM;

	uni->id = dev_data->id;
	uni->ver = dev_data->version;

	*dai = sti_uniperiph_dai_template;
	dai->name = dev_data->dai_names;

	/* Get resources */
	uni->mem_region = platform_get_resource(priv->pdev, IORESOURCE_MEM, 0);

	if (!uni->mem_region) {
		dev_err(dev, "Failed to get memory resource");
		return -ENODEV;
	}

	uni->base = devm_ioremap_resource(dev, uni->mem_region);

	if (IS_ERR(uni->base))
		return PTR_ERR(uni->base);

	uni->fifo_phys_address = uni->mem_region->start +
				     UNIPERIF_FIFO_DATA_OFFSET(uni);

	uni->irq = platform_get_irq(priv->pdev, 0);
	if (uni->irq < 0) {
		dev_err(dev, "Failed to get IRQ resource");
		return -ENXIO;
	}

	uni->type = dev_data->type;

	/* check if player should be configured for tdm */
	if (dev_data->type & SND_ST_UNIPERIF_TYPE_TDM) {
		if (!of_property_read_string(node, "st,tdm-mode", &mode))
			uni->type = SND_ST_UNIPERIF_TYPE_TDM;
		else
			uni->type = SND_ST_UNIPERIF_TYPE_PCM;
	}

	dai_data->uni = uni;
	dai_data->stream = dev_data->stream;

	if (priv->dai_data.stream == SNDRV_PCM_STREAM_PLAYBACK) {
		uni_player_init(priv->pdev, uni);
		stream = &dai->playback;
	} else {
		uni_reader_init(priv->pdev, uni);
		stream = &dai->capture;
	}
	dai->ops = uni->dai_ops;

	stream->stream_name = dai->name;
	stream->channels_min = uni->hw->channels_min;
	stream->channels_max = uni->hw->channels_max;
	stream->rates = uni->hw->rates;
	stream->formats = uni->hw->formats;

	return 0;
}

static const struct snd_dmaengine_pcm_config dmaengine_pcm_config = {
	.prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config,
};

static int sti_uniperiph_probe(struct platform_device *pdev)
{
	struct sti_uniperiph_data *priv;
	struct device_node *node = pdev->dev.of_node;
	int ret;

	/* Allocate the private data and the CPU_DAI array */
	priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
	if (!priv)
		return -ENOMEM;
	priv->dai = devm_kzalloc(&pdev->dev, sizeof(*priv->dai), GFP_KERNEL);
	if (!priv->dai)
		return -ENOMEM;

	priv->pdev = pdev;

	ret = sti_uniperiph_cpu_dai_of(node, priv);

	dev_set_drvdata(&pdev->dev, priv);

	ret = devm_snd_soc_register_component(&pdev->dev,
					      &sti_uniperiph_dai_component,
					      priv->dai, 1);
	if (ret < 0)
		return ret;

	return devm_snd_dmaengine_pcm_register(&pdev->dev,
					       &dmaengine_pcm_config, 0);
}

static struct platform_driver sti_uniperiph_driver = {
	.driver = {
		.name = "sti-uniperiph-dai",
		.of_match_table = snd_soc_sti_match,
	},
	.probe = sti_uniperiph_probe,
};
module_platform_driver(sti_uniperiph_driver);

MODULE_DESCRIPTION("uniperipheral DAI driver");
MODULE_AUTHOR("Arnaud Pouliquen <arnaud.pouliquen@st.com>");
MODULE_LICENSE("GPL v2");