Commit | Line | Data |
---|---|---|
e453df44 TS |
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 | ||
11 | static 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 | ||
21 | static 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 | ||
68 | static 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 | ||
e5e0c3dd TS |
75 | err = snd_tscm_stream_lock_try(tscm); |
76 | if (err < 0) | |
77 | goto end; | |
78 | ||
e453df44 TS |
79 | err = pcm_init_hw_params(tscm, substream); |
80 | if (err < 0) | |
e5e0c3dd | 81 | goto err_locked; |
e453df44 TS |
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) | |
e5e0c3dd | 89 | goto err_locked; |
e453df44 TS |
90 | substream->runtime->hw.rate_min = rate; |
91 | substream->runtime->hw.rate_max = rate; | |
92 | } | |
93 | ||
94 | snd_pcm_set_sync(substream); | |
e5e0c3dd TS |
95 | end: |
96 | return err; | |
97 | err_locked: | |
98 | snd_tscm_stream_lock_release(tscm); | |
e453df44 TS |
99 | return err; |
100 | } | |
101 | ||
102 | static int pcm_close(struct snd_pcm_substream *substream) | |
103 | { | |
e5e0c3dd TS |
104 | struct snd_tscm *tscm = substream->private_data; |
105 | ||
106 | snd_tscm_stream_lock_release(tscm); | |
107 | ||
e453df44 TS |
108 | return 0; |
109 | } | |
110 | ||
111 | static 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 | ||
133 | static 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 | ||
155 | static 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 | ||
171 | static 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 | ||
187 | static 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 | ||
204 | static 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 | ||
221 | static 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 | ||
239 | static 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 | ||
257 | static 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 | ||
264 | static 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 | ||
5116ffc3 | 271 | static const struct snd_pcm_ops pcm_capture_ops = { |
e453df44 TS |
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 | ||
5116ffc3 | 283 | static const struct snd_pcm_ops pcm_playback_ops = { |
e453df44 TS |
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 | ||
296 | int 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 | } |