1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * ALSA PCM device for the |
4 | * ALSA interface to ivtv PCM capture streams |
5 | * |
6 | * Copyright (C) 2009,2012 Andy Walls <awalls@md.metrocast.net> |
7 | * Copyright (C) 2009 Devin Heitmueller <dheitmueller@kernellabs.com> |
8 | * |
9 | * Portions of this work were sponsored by ONELAN Limited for the cx18 driver |
10 | */ |
11 | |
12 | #include "ivtv-driver.h" |
13 | #include "ivtv-queue.h" |
14 | #include "ivtv-streams.h" |
15 | #include "ivtv-fileops.h" |
16 | #include "ivtv-alsa.h" |
17 | #include "ivtv-alsa-pcm.h" |
18 | |
19 | #include <sound/core.h> |
20 | #include <sound/pcm.h> |
21 | |
22 | |
23 | static unsigned int pcm_debug; |
24 | module_param(pcm_debug, int, 0644); |
25 | MODULE_PARM_DESC(pcm_debug, "enable debug messages for pcm" ); |
26 | |
27 | #define dprintk(fmt, arg...) \ |
28 | do { \ |
29 | if (pcm_debug) \ |
30 | pr_info("ivtv-alsa-pcm %s: " fmt, __func__, ##arg); \ |
31 | } while (0) |
32 | |
33 | static const struct snd_pcm_hardware snd_ivtv_hw_capture = { |
34 | .info = SNDRV_PCM_INFO_BLOCK_TRANSFER | |
35 | SNDRV_PCM_INFO_MMAP | |
36 | SNDRV_PCM_INFO_INTERLEAVED | |
37 | SNDRV_PCM_INFO_MMAP_VALID, |
38 | |
39 | .formats = SNDRV_PCM_FMTBIT_S16_LE, |
40 | |
41 | .rates = SNDRV_PCM_RATE_48000, |
42 | |
43 | .rate_min = 48000, |
44 | .rate_max = 48000, |
45 | .channels_min = 2, |
46 | .channels_max = 2, |
47 | .buffer_bytes_max = 62720 * 8, /* just about the value in usbaudio.c */ |
48 | .period_bytes_min = 64, /* 12544/2, */ |
49 | .period_bytes_max = 12544, |
50 | .periods_min = 2, |
51 | .periods_max = 98, /* 12544, */ |
52 | }; |
53 | |
54 | static void ivtv_alsa_announce_pcm_data(struct snd_ivtv_card *itvsc, |
55 | u8 *pcm_data, |
56 | size_t num_bytes) |
57 | { |
58 | struct snd_pcm_substream *substream; |
59 | struct snd_pcm_runtime *runtime; |
60 | unsigned int oldptr; |
61 | unsigned int stride; |
62 | int period_elapsed = 0; |
63 | int length; |
64 | |
65 | dprintk("ivtv alsa announce ptr=%p data=%p num_bytes=%zu\n" , itvsc, |
66 | pcm_data, num_bytes); |
67 | |
68 | substream = itvsc->capture_pcm_substream; |
69 | if (substream == NULL) { |
70 | dprintk("substream was NULL\n" ); |
71 | return; |
72 | } |
73 | |
74 | runtime = substream->runtime; |
75 | if (runtime == NULL) { |
76 | dprintk("runtime was NULL\n" ); |
77 | return; |
78 | } |
79 | |
80 | stride = runtime->frame_bits >> 3; |
81 | if (stride == 0) { |
82 | dprintk("stride is zero\n" ); |
83 | return; |
84 | } |
85 | |
86 | length = num_bytes / stride; |
87 | if (length == 0) { |
88 | dprintk("%s: length was zero\n" , __func__); |
89 | return; |
90 | } |
91 | |
92 | if (runtime->dma_area == NULL) { |
93 | dprintk("dma area was NULL - ignoring\n" ); |
94 | return; |
95 | } |
96 | |
97 | oldptr = itvsc->hwptr_done_capture; |
98 | if (oldptr + length >= runtime->buffer_size) { |
99 | unsigned int cnt = |
100 | runtime->buffer_size - oldptr; |
101 | memcpy(runtime->dma_area + oldptr * stride, pcm_data, |
102 | cnt * stride); |
103 | memcpy(runtime->dma_area, pcm_data + cnt * stride, |
104 | length * stride - cnt * stride); |
105 | } else { |
106 | memcpy(runtime->dma_area + oldptr * stride, pcm_data, |
107 | length * stride); |
108 | } |
109 | snd_pcm_stream_lock(substream); |
110 | |
111 | itvsc->hwptr_done_capture += length; |
112 | if (itvsc->hwptr_done_capture >= |
113 | runtime->buffer_size) |
114 | itvsc->hwptr_done_capture -= |
115 | runtime->buffer_size; |
116 | |
117 | itvsc->capture_transfer_done += length; |
118 | if (itvsc->capture_transfer_done >= |
119 | runtime->period_size) { |
120 | itvsc->capture_transfer_done -= |
121 | runtime->period_size; |
122 | period_elapsed = 1; |
123 | } |
124 | |
125 | snd_pcm_stream_unlock(substream); |
126 | |
127 | if (period_elapsed) |
128 | snd_pcm_period_elapsed(substream); |
129 | } |
130 | |
131 | static int snd_ivtv_pcm_capture_open(struct snd_pcm_substream *substream) |
132 | { |
133 | struct snd_ivtv_card *itvsc = snd_pcm_substream_chip(substream); |
134 | struct snd_pcm_runtime *runtime = substream->runtime; |
135 | struct v4l2_device *v4l2_dev = itvsc->v4l2_dev; |
136 | struct ivtv *itv = to_ivtv(v4l2_dev); |
137 | struct ivtv_stream *s; |
138 | struct ivtv_open_id item; |
139 | int ret; |
140 | |
141 | /* Instruct the CX2341[56] to start sending packets */ |
142 | snd_ivtv_lock(itvsc); |
143 | |
144 | if (ivtv_init_on_first_open(itv)) { |
145 | snd_ivtv_unlock(itvsc); |
146 | return -ENXIO; |
147 | } |
148 | |
149 | s = &itv->streams[IVTV_ENC_STREAM_TYPE_PCM]; |
150 | |
151 | v4l2_fh_init(fh: &item.fh, vdev: &s->vdev); |
152 | item.itv = itv; |
153 | item.type = s->type; |
154 | |
155 | /* See if the stream is available */ |
156 | if (ivtv_claim_stream(id: &item, type: item.type)) { |
157 | /* No, it's already in use */ |
158 | v4l2_fh_exit(fh: &item.fh); |
159 | snd_ivtv_unlock(itvsc); |
160 | return -EBUSY; |
161 | } |
162 | |
163 | if (test_bit(IVTV_F_S_STREAMOFF, &s->s_flags) || |
164 | test_and_set_bit(IVTV_F_S_STREAMING, addr: &s->s_flags)) { |
165 | /* We're already streaming. No additional action required */ |
166 | snd_ivtv_unlock(itvsc); |
167 | return 0; |
168 | } |
169 | |
170 | |
171 | runtime->hw = snd_ivtv_hw_capture; |
172 | snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); |
173 | itvsc->capture_pcm_substream = substream; |
174 | runtime->private_data = itv; |
175 | |
176 | itv->pcm_announce_callback = ivtv_alsa_announce_pcm_data; |
177 | |
178 | /* Not currently streaming, so start it up */ |
179 | set_bit(IVTV_F_S_STREAMING, addr: &s->s_flags); |
180 | ret = ivtv_start_v4l2_encode_stream(s); |
181 | snd_ivtv_unlock(itvsc); |
182 | |
183 | return ret; |
184 | } |
185 | |
186 | static int snd_ivtv_pcm_capture_close(struct snd_pcm_substream *substream) |
187 | { |
188 | struct snd_ivtv_card *itvsc = snd_pcm_substream_chip(substream); |
189 | struct v4l2_device *v4l2_dev = itvsc->v4l2_dev; |
190 | struct ivtv *itv = to_ivtv(v4l2_dev); |
191 | struct ivtv_stream *s; |
192 | |
193 | /* Instruct the ivtv to stop sending packets */ |
194 | snd_ivtv_lock(itvsc); |
195 | s = &itv->streams[IVTV_ENC_STREAM_TYPE_PCM]; |
196 | ivtv_stop_v4l2_encode_stream(s, gop_end: 0); |
197 | clear_bit(IVTV_F_S_STREAMING, addr: &s->s_flags); |
198 | |
199 | ivtv_release_stream(s); |
200 | |
201 | itv->pcm_announce_callback = NULL; |
202 | snd_ivtv_unlock(itvsc); |
203 | |
204 | return 0; |
205 | } |
206 | |
207 | static int snd_ivtv_pcm_prepare(struct snd_pcm_substream *substream) |
208 | { |
209 | struct snd_ivtv_card *itvsc = snd_pcm_substream_chip(substream); |
210 | |
211 | itvsc->hwptr_done_capture = 0; |
212 | itvsc->capture_transfer_done = 0; |
213 | |
214 | return 0; |
215 | } |
216 | |
217 | static int snd_ivtv_pcm_trigger(struct snd_pcm_substream *substream, int cmd) |
218 | { |
219 | return 0; |
220 | } |
221 | |
222 | static |
223 | snd_pcm_uframes_t snd_ivtv_pcm_pointer(struct snd_pcm_substream *substream) |
224 | { |
225 | unsigned long flags; |
226 | snd_pcm_uframes_t hwptr_done; |
227 | struct snd_ivtv_card *itvsc = snd_pcm_substream_chip(substream); |
228 | |
229 | spin_lock_irqsave(&itvsc->slock, flags); |
230 | hwptr_done = itvsc->hwptr_done_capture; |
231 | spin_unlock_irqrestore(lock: &itvsc->slock, flags); |
232 | |
233 | return hwptr_done; |
234 | } |
235 | |
236 | static const struct snd_pcm_ops snd_ivtv_pcm_capture_ops = { |
237 | .open = snd_ivtv_pcm_capture_open, |
238 | .close = snd_ivtv_pcm_capture_close, |
239 | .prepare = snd_ivtv_pcm_prepare, |
240 | .trigger = snd_ivtv_pcm_trigger, |
241 | .pointer = snd_ivtv_pcm_pointer, |
242 | }; |
243 | |
244 | int snd_ivtv_pcm_create(struct snd_ivtv_card *itvsc) |
245 | { |
246 | struct snd_pcm *sp; |
247 | struct snd_card *sc = itvsc->sc; |
248 | struct v4l2_device *v4l2_dev = itvsc->v4l2_dev; |
249 | struct ivtv *itv = to_ivtv(v4l2_dev); |
250 | int ret; |
251 | |
252 | ret = snd_pcm_new(card: sc, id: "CX2341[56] PCM" , |
253 | device: 0, /* PCM device 0, the only one for this card */ |
254 | playback_count: 0, /* 0 playback substreams */ |
255 | capture_count: 1, /* 1 capture substream */ |
256 | rpcm: &sp); |
257 | if (ret) { |
258 | IVTV_ALSA_ERR("%s: snd_ivtv_pcm_create() failed with err %d\n" , |
259 | __func__, ret); |
260 | goto err_exit; |
261 | } |
262 | |
263 | spin_lock_init(&itvsc->slock); |
264 | |
265 | snd_pcm_set_ops(pcm: sp, direction: SNDRV_PCM_STREAM_CAPTURE, |
266 | ops: &snd_ivtv_pcm_capture_ops); |
267 | snd_pcm_set_managed_buffer_all(pcm: sp, SNDRV_DMA_TYPE_VMALLOC, NULL, size: 0, max: 0); |
268 | sp->info_flags = 0; |
269 | sp->private_data = itvsc; |
270 | strscpy(sp->name, itv->card_name, sizeof(sp->name)); |
271 | |
272 | return 0; |
273 | |
274 | err_exit: |
275 | return ret; |
276 | } |
277 | |