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