1/*
2 * tascam-pcm.c - a part of driver for TASCAM FireWire series
3 *
4 * Copyright (c) 2015 Takashi Sakamoto
5 *
6 * Licensed under the terms of the GNU General Public License, version 2.
7 */
8
9#include "tascam.h"
10
11static void set_buffer_params(struct snd_pcm_hardware *hw)
12{
13	hw->period_bytes_min = 4 * hw->channels_min;
14	hw->period_bytes_max = hw->period_bytes_min * 2048;
15	hw->buffer_bytes_max = hw->period_bytes_max * 2;
16
17	hw->periods_min = 2;
18	hw->periods_max = UINT_MAX;
19}
20
21static int pcm_init_hw_params(struct snd_tscm *tscm,
22			      struct snd_pcm_substream *substream)
23{
24	static const struct snd_pcm_hardware hardware = {
25		.info = SNDRV_PCM_INFO_BATCH |
26			SNDRV_PCM_INFO_BLOCK_TRANSFER |
27			SNDRV_PCM_INFO_INTERLEAVED |
28			SNDRV_PCM_INFO_JOINT_DUPLEX |
29			SNDRV_PCM_INFO_MMAP |
30			SNDRV_PCM_INFO_MMAP_VALID,
31		.rates = SNDRV_PCM_RATE_44100 |
32			 SNDRV_PCM_RATE_48000 |
33			 SNDRV_PCM_RATE_88200 |
34			 SNDRV_PCM_RATE_96000,
35		.rate_min = 44100,
36		.rate_max = 96000,
37		.channels_min = 10,
38		.channels_max = 18,
39	};
40	struct snd_pcm_runtime *runtime = substream->runtime;
41	struct amdtp_stream *stream;
42	unsigned int pcm_channels;
43
44	runtime->hw = hardware;
45
46	if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
47		runtime->hw.formats = SNDRV_PCM_FMTBIT_S32;
48		stream = &tscm->tx_stream;
49		pcm_channels = tscm->spec->pcm_capture_analog_channels;
50	} else {
51		runtime->hw.formats =
52				SNDRV_PCM_FMTBIT_S16 | SNDRV_PCM_FMTBIT_S32;
53		stream = &tscm->rx_stream;
54		pcm_channels = tscm->spec->pcm_playback_analog_channels;
55	}
56
57	if (tscm->spec->has_adat)
58		pcm_channels += 8;
59	if (tscm->spec->has_spdif)
60		pcm_channels += 2;
61	runtime->hw.channels_min = runtime->hw.channels_max = pcm_channels;
62
63	set_buffer_params(&runtime->hw);
64
65	return amdtp_tscm_add_pcm_hw_constraints(stream, runtime);
66}
67
68static int pcm_open(struct snd_pcm_substream *substream)
69{
70	struct snd_tscm *tscm = substream->private_data;
71	enum snd_tscm_clock clock;
72	unsigned int rate;
73	int err;
74
75	err = snd_tscm_stream_lock_try(tscm);
76	if (err < 0)
77		goto end;
78
79	err = pcm_init_hw_params(tscm, substream);
80	if (err < 0)
81		goto err_locked;
82
83	err = snd_tscm_stream_get_clock(tscm, &clock);
84	if (clock != SND_TSCM_CLOCK_INTERNAL ||
85	    amdtp_stream_pcm_running(&tscm->rx_stream) ||
86	    amdtp_stream_pcm_running(&tscm->tx_stream)) {
87		err = snd_tscm_stream_get_rate(tscm, &rate);
88		if (err < 0)
89			goto err_locked;
90		substream->runtime->hw.rate_min = rate;
91		substream->runtime->hw.rate_max = rate;
92	}
93
94	snd_pcm_set_sync(substream);
95end:
96	return err;
97err_locked:
98	snd_tscm_stream_lock_release(tscm);
99	return err;
100}
101
102static int pcm_close(struct snd_pcm_substream *substream)
103{
104	struct snd_tscm *tscm = substream->private_data;
105
106	snd_tscm_stream_lock_release(tscm);
107
108	return 0;
109}
110
111static int pcm_capture_hw_params(struct snd_pcm_substream *substream,
112				 struct snd_pcm_hw_params *hw_params)
113{
114	struct snd_tscm *tscm = substream->private_data;
115	int err;
116
117	err = snd_pcm_lib_alloc_vmalloc_buffer(substream,
118					       params_buffer_bytes(hw_params));
119	if (err < 0)
120		return err;
121
122	if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) {
123		mutex_lock(&tscm->mutex);
124		tscm->substreams_counter++;
125		mutex_unlock(&tscm->mutex);
126	}
127
128	amdtp_tscm_set_pcm_format(&tscm->tx_stream, params_format(hw_params));
129
130	return 0;
131}
132
133static int pcm_playback_hw_params(struct snd_pcm_substream *substream,
134				  struct snd_pcm_hw_params *hw_params)
135{
136	struct snd_tscm *tscm = substream->private_data;
137	int err;
138
139	err = snd_pcm_lib_alloc_vmalloc_buffer(substream,
140					       params_buffer_bytes(hw_params));
141	if (err < 0)
142		return err;
143
144	if (substream->runtime->status->state == SNDRV_PCM_STATE_OPEN) {
145		mutex_lock(&tscm->mutex);
146		tscm->substreams_counter++;
147		mutex_unlock(&tscm->mutex);
148	}
149
150	amdtp_tscm_set_pcm_format(&tscm->rx_stream, params_format(hw_params));
151
152	return 0;
153}
154
155static int pcm_capture_hw_free(struct snd_pcm_substream *substream)
156{
157	struct snd_tscm *tscm = substream->private_data;
158
159	mutex_lock(&tscm->mutex);
160
161	if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN)
162		tscm->substreams_counter--;
163
164	snd_tscm_stream_stop_duplex(tscm);
165
166	mutex_unlock(&tscm->mutex);
167
168	return snd_pcm_lib_free_vmalloc_buffer(substream);
169}
170
171static int pcm_playback_hw_free(struct snd_pcm_substream *substream)
172{
173	struct snd_tscm *tscm = substream->private_data;
174
175	mutex_lock(&tscm->mutex);
176
177	if (substream->runtime->status->state != SNDRV_PCM_STATE_OPEN)
178		tscm->substreams_counter--;
179
180	snd_tscm_stream_stop_duplex(tscm);
181
182	mutex_unlock(&tscm->mutex);
183
184	return snd_pcm_lib_free_vmalloc_buffer(substream);
185}
186
187static int pcm_capture_prepare(struct snd_pcm_substream *substream)
188{
189	struct snd_tscm *tscm = substream->private_data;
190	struct snd_pcm_runtime *runtime = substream->runtime;
191	int err;
192
193	mutex_lock(&tscm->mutex);
194
195	err = snd_tscm_stream_start_duplex(tscm, runtime->rate);
196	if (err >= 0)
197		amdtp_stream_pcm_prepare(&tscm->tx_stream);
198
199	mutex_unlock(&tscm->mutex);
200
201	return err;
202}
203
204static int pcm_playback_prepare(struct snd_pcm_substream *substream)
205{
206	struct snd_tscm *tscm = substream->private_data;
207	struct snd_pcm_runtime *runtime = substream->runtime;
208	int err;
209
210	mutex_lock(&tscm->mutex);
211
212	err = snd_tscm_stream_start_duplex(tscm, runtime->rate);
213	if (err >= 0)
214		amdtp_stream_pcm_prepare(&tscm->rx_stream);
215
216	mutex_unlock(&tscm->mutex);
217
218	return err;
219}
220
221static int pcm_capture_trigger(struct snd_pcm_substream *substream, int cmd)
222{
223	struct snd_tscm *tscm = substream->private_data;
224
225	switch (cmd) {
226	case SNDRV_PCM_TRIGGER_START:
227		amdtp_stream_pcm_trigger(&tscm->tx_stream, substream);
228		break;
229	case SNDRV_PCM_TRIGGER_STOP:
230		amdtp_stream_pcm_trigger(&tscm->tx_stream, NULL);
231		break;
232	default:
233		return -EINVAL;
234	}
235
236	return 0;
237}
238
239static int pcm_playback_trigger(struct snd_pcm_substream *substream, int cmd)
240{
241	struct snd_tscm *tscm = substream->private_data;
242
243	switch (cmd) {
244	case SNDRV_PCM_TRIGGER_START:
245		amdtp_stream_pcm_trigger(&tscm->rx_stream, substream);
246		break;
247	case SNDRV_PCM_TRIGGER_STOP:
248		amdtp_stream_pcm_trigger(&tscm->rx_stream, NULL);
249		break;
250	default:
251		return -EINVAL;
252	}
253
254	return 0;
255}
256
257static snd_pcm_uframes_t pcm_capture_pointer(struct snd_pcm_substream *sbstrm)
258{
259	struct snd_tscm *tscm = sbstrm->private_data;
260
261	return amdtp_stream_pcm_pointer(&tscm->tx_stream);
262}
263
264static snd_pcm_uframes_t pcm_playback_pointer(struct snd_pcm_substream *sbstrm)
265{
266	struct snd_tscm *tscm = sbstrm->private_data;
267
268	return amdtp_stream_pcm_pointer(&tscm->rx_stream);
269}
270
271static struct snd_pcm_ops pcm_capture_ops = {
272	.open		= pcm_open,
273	.close		= pcm_close,
274	.ioctl		= snd_pcm_lib_ioctl,
275	.hw_params	= pcm_capture_hw_params,
276	.hw_free	= pcm_capture_hw_free,
277	.prepare	= pcm_capture_prepare,
278	.trigger	= pcm_capture_trigger,
279	.pointer	= pcm_capture_pointer,
280	.page		= snd_pcm_lib_get_vmalloc_page,
281};
282
283static struct snd_pcm_ops pcm_playback_ops = {
284	.open		= pcm_open,
285	.close		= pcm_close,
286	.ioctl		= snd_pcm_lib_ioctl,
287	.hw_params	= pcm_playback_hw_params,
288	.hw_free	= pcm_playback_hw_free,
289	.prepare	= pcm_playback_prepare,
290	.trigger	= pcm_playback_trigger,
291	.pointer	= pcm_playback_pointer,
292	.page		= snd_pcm_lib_get_vmalloc_page,
293	.mmap		= snd_pcm_lib_mmap_vmalloc,
294};
295
296int snd_tscm_create_pcm_devices(struct snd_tscm *tscm)
297{
298	struct snd_pcm *pcm;
299	int err;
300
301	err = snd_pcm_new(tscm->card, tscm->card->driver, 0, 1, 1, &pcm);
302	if (err < 0)
303		return err;
304
305	pcm->private_data = tscm;
306	snprintf(pcm->name, sizeof(pcm->name),
307		 "%s PCM", tscm->card->shortname);
308	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &pcm_playback_ops);
309	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &pcm_capture_ops);
310
311	return 0;
312}
313