Linux Audio

Check our new training course

Loading...
Note: File does not exist in v6.2.
  1/*
  2 * ALSA SoC Synopsys PIO PCM for I2S driver
  3 *
  4 * sound/soc/dwc/designware_pcm.c
  5 *
  6 * Copyright (C) 2016 Synopsys
  7 * Jose Abreu <joabreu@synopsys.com>
  8 *
  9 * This file is licensed under the terms of the GNU General Public
 10 * License version 2. This program is licensed "as is" without any
 11 * warranty of any kind, whether express or implied.
 12 */
 13
 14#include <linux/io.h>
 15#include <linux/rcupdate.h>
 16#include <sound/pcm.h>
 17#include <sound/pcm_params.h>
 18#include "local.h"
 19
 20#define BUFFER_BYTES_MAX	(3 * 2 * 8 * PERIOD_BYTES_MIN)
 21#define PERIOD_BYTES_MIN	4096
 22#define PERIODS_MIN		2
 23
 24#define dw_pcm_tx_fn(sample_bits) \
 25static unsigned int dw_pcm_tx_##sample_bits(struct dw_i2s_dev *dev, \
 26		struct snd_pcm_runtime *runtime, unsigned int tx_ptr, \
 27		bool *period_elapsed) \
 28{ \
 29	const u##sample_bits (*p)[2] = (void *)runtime->dma_area; \
 30	unsigned int period_pos = tx_ptr % runtime->period_size; \
 31	int i; \
 32\
 33	for (i = 0; i < dev->fifo_th; i++) { \
 34		iowrite32(p[tx_ptr][0], dev->i2s_base + LRBR_LTHR(0)); \
 35		iowrite32(p[tx_ptr][1], dev->i2s_base + RRBR_RTHR(0)); \
 36		period_pos++; \
 37		if (++tx_ptr >= runtime->buffer_size) \
 38			tx_ptr = 0; \
 39	} \
 40	*period_elapsed = period_pos >= runtime->period_size; \
 41	return tx_ptr; \
 42}
 43
 44dw_pcm_tx_fn(16);
 45dw_pcm_tx_fn(32);
 46
 47#undef dw_pcm_tx_fn
 48
 49static const struct snd_pcm_hardware dw_pcm_hardware = {
 50	.info = SNDRV_PCM_INFO_INTERLEAVED |
 51		SNDRV_PCM_INFO_MMAP |
 52		SNDRV_PCM_INFO_MMAP_VALID |
 53		SNDRV_PCM_INFO_BLOCK_TRANSFER,
 54	.rates = SNDRV_PCM_RATE_32000 |
 55		SNDRV_PCM_RATE_44100 |
 56		SNDRV_PCM_RATE_48000,
 57	.rate_min = 32000,
 58	.rate_max = 48000,
 59	.formats = SNDRV_PCM_FMTBIT_S16_LE |
 60		SNDRV_PCM_FMTBIT_S32_LE,
 61	.channels_min = 2,
 62	.channels_max = 2,
 63	.buffer_bytes_max = BUFFER_BYTES_MAX,
 64	.period_bytes_min = PERIOD_BYTES_MIN,
 65	.period_bytes_max = BUFFER_BYTES_MAX / PERIODS_MIN,
 66	.periods_min = PERIODS_MIN,
 67	.periods_max = BUFFER_BYTES_MAX / PERIOD_BYTES_MIN,
 68	.fifo_size = 16,
 69};
 70
 71void dw_pcm_push_tx(struct dw_i2s_dev *dev)
 72{
 73	struct snd_pcm_substream *tx_substream;
 74	bool tx_active, period_elapsed;
 75
 76	rcu_read_lock();
 77	tx_substream = rcu_dereference(dev->tx_substream);
 78	tx_active = tx_substream && snd_pcm_running(tx_substream);
 79	if (tx_active) {
 80		unsigned int tx_ptr = READ_ONCE(dev->tx_ptr);
 81		unsigned int new_tx_ptr = dev->tx_fn(dev, tx_substream->runtime,
 82				tx_ptr, &period_elapsed);
 83		cmpxchg(&dev->tx_ptr, tx_ptr, new_tx_ptr);
 84
 85		if (period_elapsed)
 86			snd_pcm_period_elapsed(tx_substream);
 87	}
 88	rcu_read_unlock();
 89}
 90EXPORT_SYMBOL_GPL(dw_pcm_push_tx);
 91
 92static int dw_pcm_open(struct snd_pcm_substream *substream)
 93{
 94	struct snd_pcm_runtime *runtime = substream->runtime;
 95	struct snd_soc_pcm_runtime *rtd = substream->private_data;
 96	struct dw_i2s_dev *dev = snd_soc_dai_get_drvdata(rtd->cpu_dai);
 97
 98	snd_soc_set_runtime_hwparams(substream, &dw_pcm_hardware);
 99	snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
100	runtime->private_data = dev;
101
102	return 0;
103}
104
105static int dw_pcm_close(struct snd_pcm_substream *substream)
106{
107	synchronize_rcu();
108	return 0;
109}
110
111static int dw_pcm_hw_params(struct snd_pcm_substream *substream,
112		struct snd_pcm_hw_params *hw_params)
113{
114	struct snd_pcm_runtime *runtime = substream->runtime;
115	struct dw_i2s_dev *dev = runtime->private_data;
116	int ret;
117
118	switch (params_channels(hw_params)) {
119	case 2:
120		break;
121	default:
122		dev_err(dev->dev, "invalid channels number\n");
123		return -EINVAL;
124	}
125
126	switch (params_format(hw_params)) {
127	case SNDRV_PCM_FORMAT_S16_LE:
128		dev->tx_fn = dw_pcm_tx_16;
129		break;
130	case SNDRV_PCM_FORMAT_S32_LE:
131		dev->tx_fn = dw_pcm_tx_32;
132		break;
133	default:
134		dev_err(dev->dev, "invalid format\n");
135		return -EINVAL;
136	}
137
138	if (substream->stream != SNDRV_PCM_STREAM_PLAYBACK) {
139		dev_err(dev->dev, "only playback is available\n");
140		return -EINVAL;
141	}
142
143	ret = snd_pcm_lib_malloc_pages(substream,
144			params_buffer_bytes(hw_params));
145	if (ret < 0)
146		return ret;
147	else
148		return 0;
149}
150
151static int dw_pcm_hw_free(struct snd_pcm_substream *substream)
152{
153	return snd_pcm_lib_free_pages(substream);
154}
155
156static int dw_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
157{
158	struct snd_pcm_runtime *runtime = substream->runtime;
159	struct dw_i2s_dev *dev = runtime->private_data;
160	int ret = 0;
161
162	switch (cmd) {
163	case SNDRV_PCM_TRIGGER_START:
164	case SNDRV_PCM_TRIGGER_RESUME:
165	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
166		WRITE_ONCE(dev->tx_ptr, 0);
167		rcu_assign_pointer(dev->tx_substream, substream);
168		break;
169	case SNDRV_PCM_TRIGGER_STOP:
170	case SNDRV_PCM_TRIGGER_SUSPEND:
171	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
172		rcu_assign_pointer(dev->tx_substream, NULL);
173		break;
174	default:
175		ret = -EINVAL;
176		break;
177	}
178
179	return ret;
180}
181
182static snd_pcm_uframes_t dw_pcm_pointer(struct snd_pcm_substream *substream)
183{
184	struct snd_pcm_runtime *runtime = substream->runtime;
185	struct dw_i2s_dev *dev = runtime->private_data;
186	snd_pcm_uframes_t pos = READ_ONCE(dev->tx_ptr);
187
188	return pos < runtime->buffer_size ? pos : 0;
189}
190
191static int dw_pcm_new(struct snd_soc_pcm_runtime *rtd)
192{
193	size_t size = dw_pcm_hardware.buffer_bytes_max;
194
195	return snd_pcm_lib_preallocate_pages_for_all(rtd->pcm,
196			SNDRV_DMA_TYPE_CONTINUOUS,
197			snd_dma_continuous_data(GFP_KERNEL), size, size);
198}
199
200static void dw_pcm_free(struct snd_pcm *pcm)
201{
202	snd_pcm_lib_preallocate_free_for_all(pcm);
203}
204
205static const struct snd_pcm_ops dw_pcm_ops = {
206	.open = dw_pcm_open,
207	.close = dw_pcm_close,
208	.ioctl = snd_pcm_lib_ioctl,
209	.hw_params = dw_pcm_hw_params,
210	.hw_free = dw_pcm_hw_free,
211	.trigger = dw_pcm_trigger,
212	.pointer = dw_pcm_pointer,
213};
214
215static const struct snd_soc_platform_driver dw_pcm_platform = {
216	.pcm_new = dw_pcm_new,
217	.pcm_free = dw_pcm_free,
218	.ops = &dw_pcm_ops,
219};
220
221int dw_pcm_register(struct platform_device *pdev)
222{
223	return devm_snd_soc_register_platform(&pdev->dev, &dw_pcm_platform);
224}
225EXPORT_SYMBOL_GPL(dw_pcm_register);