1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Conexant Cx231xx audio extension |
4 | * |
5 | * Copyright (C) 2008 <srinivasa.deevi at conexant dot com> |
6 | * Based on em28xx driver |
7 | */ |
8 | |
9 | #include "cx231xx.h" |
10 | #include <linux/kernel.h> |
11 | #include <linux/init.h> |
12 | #include <linux/sound.h> |
13 | #include <linux/spinlock.h> |
14 | #include <linux/soundcard.h> |
15 | #include <linux/slab.h> |
16 | #include <linux/module.h> |
17 | #include <sound/core.h> |
18 | #include <sound/pcm.h> |
19 | #include <sound/pcm_params.h> |
20 | #include <sound/info.h> |
21 | #include <sound/initval.h> |
22 | #include <sound/control.h> |
23 | #include <media/v4l2-common.h> |
24 | |
25 | static int debug; |
26 | module_param(debug, int, 0644); |
27 | MODULE_PARM_DESC(debug, "activates debug info" ); |
28 | |
29 | static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; |
30 | |
31 | static int cx231xx_isoc_audio_deinit(struct cx231xx *dev) |
32 | { |
33 | int i; |
34 | |
35 | dev_dbg(dev->dev, "Stopping isoc\n" ); |
36 | |
37 | for (i = 0; i < CX231XX_AUDIO_BUFS; i++) { |
38 | if (dev->adev.urb[i]) { |
39 | if (!irqs_disabled()) |
40 | usb_kill_urb(urb: dev->adev.urb[i]); |
41 | else |
42 | usb_unlink_urb(urb: dev->adev.urb[i]); |
43 | |
44 | usb_free_urb(urb: dev->adev.urb[i]); |
45 | dev->adev.urb[i] = NULL; |
46 | |
47 | kfree(objp: dev->adev.transfer_buffer[i]); |
48 | dev->adev.transfer_buffer[i] = NULL; |
49 | } |
50 | } |
51 | |
52 | return 0; |
53 | } |
54 | |
55 | static int cx231xx_bulk_audio_deinit(struct cx231xx *dev) |
56 | { |
57 | int i; |
58 | |
59 | dev_dbg(dev->dev, "Stopping bulk\n" ); |
60 | |
61 | for (i = 0; i < CX231XX_AUDIO_BUFS; i++) { |
62 | if (dev->adev.urb[i]) { |
63 | if (!irqs_disabled()) |
64 | usb_kill_urb(urb: dev->adev.urb[i]); |
65 | else |
66 | usb_unlink_urb(urb: dev->adev.urb[i]); |
67 | |
68 | usb_free_urb(urb: dev->adev.urb[i]); |
69 | dev->adev.urb[i] = NULL; |
70 | |
71 | kfree(objp: dev->adev.transfer_buffer[i]); |
72 | dev->adev.transfer_buffer[i] = NULL; |
73 | } |
74 | } |
75 | |
76 | return 0; |
77 | } |
78 | |
79 | static void cx231xx_audio_isocirq(struct urb *urb) |
80 | { |
81 | struct cx231xx *dev = urb->context; |
82 | int i; |
83 | unsigned int oldptr; |
84 | int period_elapsed = 0; |
85 | int status; |
86 | unsigned char *cp; |
87 | unsigned int stride; |
88 | struct snd_pcm_substream *substream; |
89 | struct snd_pcm_runtime *runtime; |
90 | |
91 | if (dev->state & DEV_DISCONNECTED) |
92 | return; |
93 | |
94 | switch (urb->status) { |
95 | case 0: /* success */ |
96 | case -ETIMEDOUT: /* NAK */ |
97 | break; |
98 | case -ECONNRESET: /* kill */ |
99 | case -ENOENT: |
100 | case -ESHUTDOWN: |
101 | return; |
102 | default: /* error */ |
103 | dev_dbg(dev->dev, "urb completion error %d.\n" , |
104 | urb->status); |
105 | break; |
106 | } |
107 | |
108 | if (atomic_read(v: &dev->stream_started) == 0) |
109 | return; |
110 | |
111 | if (dev->adev.capture_pcm_substream) { |
112 | substream = dev->adev.capture_pcm_substream; |
113 | runtime = substream->runtime; |
114 | stride = runtime->frame_bits >> 3; |
115 | |
116 | for (i = 0; i < urb->number_of_packets; i++) { |
117 | unsigned long flags; |
118 | int length = urb->iso_frame_desc[i].actual_length / |
119 | stride; |
120 | cp = (unsigned char *)urb->transfer_buffer + |
121 | urb->iso_frame_desc[i].offset; |
122 | |
123 | if (!length) |
124 | continue; |
125 | |
126 | oldptr = dev->adev.hwptr_done_capture; |
127 | if (oldptr + length >= runtime->buffer_size) { |
128 | unsigned int cnt; |
129 | |
130 | cnt = runtime->buffer_size - oldptr; |
131 | memcpy(runtime->dma_area + oldptr * stride, cp, |
132 | cnt * stride); |
133 | memcpy(runtime->dma_area, cp + cnt * stride, |
134 | length * stride - cnt * stride); |
135 | } else { |
136 | memcpy(runtime->dma_area + oldptr * stride, cp, |
137 | length * stride); |
138 | } |
139 | |
140 | snd_pcm_stream_lock_irqsave(substream, flags); |
141 | |
142 | dev->adev.hwptr_done_capture += length; |
143 | if (dev->adev.hwptr_done_capture >= |
144 | runtime->buffer_size) |
145 | dev->adev.hwptr_done_capture -= |
146 | runtime->buffer_size; |
147 | |
148 | dev->adev.capture_transfer_done += length; |
149 | if (dev->adev.capture_transfer_done >= |
150 | runtime->period_size) { |
151 | dev->adev.capture_transfer_done -= |
152 | runtime->period_size; |
153 | period_elapsed = 1; |
154 | } |
155 | snd_pcm_stream_unlock_irqrestore(substream, flags); |
156 | } |
157 | if (period_elapsed) |
158 | snd_pcm_period_elapsed(substream); |
159 | } |
160 | urb->status = 0; |
161 | |
162 | status = usb_submit_urb(urb, GFP_ATOMIC); |
163 | if (status < 0) { |
164 | dev_err(dev->dev, |
165 | "resubmit of audio urb failed (error=%i)\n" , |
166 | status); |
167 | } |
168 | return; |
169 | } |
170 | |
171 | static void cx231xx_audio_bulkirq(struct urb *urb) |
172 | { |
173 | struct cx231xx *dev = urb->context; |
174 | unsigned int oldptr; |
175 | int period_elapsed = 0; |
176 | int status; |
177 | unsigned char *cp; |
178 | unsigned int stride; |
179 | struct snd_pcm_substream *substream; |
180 | struct snd_pcm_runtime *runtime; |
181 | |
182 | if (dev->state & DEV_DISCONNECTED) |
183 | return; |
184 | |
185 | switch (urb->status) { |
186 | case 0: /* success */ |
187 | case -ETIMEDOUT: /* NAK */ |
188 | break; |
189 | case -ECONNRESET: /* kill */ |
190 | case -ENOENT: |
191 | case -ESHUTDOWN: |
192 | return; |
193 | default: /* error */ |
194 | dev_dbg(dev->dev, "urb completion error %d.\n" , |
195 | urb->status); |
196 | break; |
197 | } |
198 | |
199 | if (atomic_read(v: &dev->stream_started) == 0) |
200 | return; |
201 | |
202 | if (dev->adev.capture_pcm_substream) { |
203 | substream = dev->adev.capture_pcm_substream; |
204 | runtime = substream->runtime; |
205 | stride = runtime->frame_bits >> 3; |
206 | |
207 | if (1) { |
208 | unsigned long flags; |
209 | int length = urb->actual_length / |
210 | stride; |
211 | cp = (unsigned char *)urb->transfer_buffer; |
212 | |
213 | oldptr = dev->adev.hwptr_done_capture; |
214 | if (oldptr + length >= runtime->buffer_size) { |
215 | unsigned int cnt; |
216 | |
217 | cnt = runtime->buffer_size - oldptr; |
218 | memcpy(runtime->dma_area + oldptr * stride, cp, |
219 | cnt * stride); |
220 | memcpy(runtime->dma_area, cp + cnt * stride, |
221 | length * stride - cnt * stride); |
222 | } else { |
223 | memcpy(runtime->dma_area + oldptr * stride, cp, |
224 | length * stride); |
225 | } |
226 | |
227 | snd_pcm_stream_lock_irqsave(substream, flags); |
228 | |
229 | dev->adev.hwptr_done_capture += length; |
230 | if (dev->adev.hwptr_done_capture >= |
231 | runtime->buffer_size) |
232 | dev->adev.hwptr_done_capture -= |
233 | runtime->buffer_size; |
234 | |
235 | dev->adev.capture_transfer_done += length; |
236 | if (dev->adev.capture_transfer_done >= |
237 | runtime->period_size) { |
238 | dev->adev.capture_transfer_done -= |
239 | runtime->period_size; |
240 | period_elapsed = 1; |
241 | } |
242 | snd_pcm_stream_unlock_irqrestore(substream, flags); |
243 | } |
244 | if (period_elapsed) |
245 | snd_pcm_period_elapsed(substream); |
246 | } |
247 | urb->status = 0; |
248 | |
249 | status = usb_submit_urb(urb, GFP_ATOMIC); |
250 | if (status < 0) { |
251 | dev_err(dev->dev, |
252 | "resubmit of audio urb failed (error=%i)\n" , |
253 | status); |
254 | } |
255 | return; |
256 | } |
257 | |
258 | static int cx231xx_init_audio_isoc(struct cx231xx *dev) |
259 | { |
260 | int i, errCode; |
261 | int sb_size; |
262 | |
263 | dev_dbg(dev->dev, |
264 | "%s: Starting ISO AUDIO transfers\n" , __func__); |
265 | |
266 | if (dev->state & DEV_DISCONNECTED) |
267 | return -ENODEV; |
268 | |
269 | sb_size = CX231XX_ISO_NUM_AUDIO_PACKETS * dev->adev.max_pkt_size; |
270 | |
271 | for (i = 0; i < CX231XX_AUDIO_BUFS; i++) { |
272 | struct urb *urb; |
273 | int j, k; |
274 | |
275 | dev->adev.transfer_buffer[i] = kmalloc(size: sb_size, GFP_ATOMIC); |
276 | if (!dev->adev.transfer_buffer[i]) |
277 | return -ENOMEM; |
278 | |
279 | memset(dev->adev.transfer_buffer[i], 0x80, sb_size); |
280 | urb = usb_alloc_urb(CX231XX_ISO_NUM_AUDIO_PACKETS, GFP_ATOMIC); |
281 | if (!urb) { |
282 | for (j = 0; j < i; j++) { |
283 | usb_free_urb(urb: dev->adev.urb[j]); |
284 | kfree(objp: dev->adev.transfer_buffer[j]); |
285 | } |
286 | return -ENOMEM; |
287 | } |
288 | |
289 | urb->dev = dev->udev; |
290 | urb->context = dev; |
291 | urb->pipe = usb_rcvisocpipe(dev->udev, |
292 | dev->adev.end_point_addr); |
293 | urb->transfer_flags = URB_ISO_ASAP; |
294 | urb->transfer_buffer = dev->adev.transfer_buffer[i]; |
295 | urb->interval = 1; |
296 | urb->complete = cx231xx_audio_isocirq; |
297 | urb->number_of_packets = CX231XX_ISO_NUM_AUDIO_PACKETS; |
298 | urb->transfer_buffer_length = sb_size; |
299 | |
300 | for (j = k = 0; j < CX231XX_ISO_NUM_AUDIO_PACKETS; |
301 | j++, k += dev->adev.max_pkt_size) { |
302 | urb->iso_frame_desc[j].offset = k; |
303 | urb->iso_frame_desc[j].length = dev->adev.max_pkt_size; |
304 | } |
305 | dev->adev.urb[i] = urb; |
306 | } |
307 | |
308 | for (i = 0; i < CX231XX_AUDIO_BUFS; i++) { |
309 | errCode = usb_submit_urb(urb: dev->adev.urb[i], GFP_ATOMIC); |
310 | if (errCode < 0) { |
311 | cx231xx_isoc_audio_deinit(dev); |
312 | return errCode; |
313 | } |
314 | } |
315 | |
316 | return errCode; |
317 | } |
318 | |
319 | static int cx231xx_init_audio_bulk(struct cx231xx *dev) |
320 | { |
321 | int i, errCode; |
322 | int sb_size; |
323 | |
324 | dev_dbg(dev->dev, |
325 | "%s: Starting BULK AUDIO transfers\n" , __func__); |
326 | |
327 | if (dev->state & DEV_DISCONNECTED) |
328 | return -ENODEV; |
329 | |
330 | sb_size = CX231XX_NUM_AUDIO_PACKETS * dev->adev.max_pkt_size; |
331 | |
332 | for (i = 0; i < CX231XX_AUDIO_BUFS; i++) { |
333 | struct urb *urb; |
334 | int j; |
335 | |
336 | dev->adev.transfer_buffer[i] = kmalloc(size: sb_size, GFP_ATOMIC); |
337 | if (!dev->adev.transfer_buffer[i]) |
338 | return -ENOMEM; |
339 | |
340 | memset(dev->adev.transfer_buffer[i], 0x80, sb_size); |
341 | urb = usb_alloc_urb(CX231XX_NUM_AUDIO_PACKETS, GFP_ATOMIC); |
342 | if (!urb) { |
343 | for (j = 0; j < i; j++) { |
344 | usb_free_urb(urb: dev->adev.urb[j]); |
345 | kfree(objp: dev->adev.transfer_buffer[j]); |
346 | } |
347 | return -ENOMEM; |
348 | } |
349 | |
350 | urb->dev = dev->udev; |
351 | urb->context = dev; |
352 | urb->pipe = usb_rcvbulkpipe(dev->udev, |
353 | dev->adev.end_point_addr); |
354 | urb->transfer_flags = 0; |
355 | urb->transfer_buffer = dev->adev.transfer_buffer[i]; |
356 | urb->complete = cx231xx_audio_bulkirq; |
357 | urb->transfer_buffer_length = sb_size; |
358 | |
359 | dev->adev.urb[i] = urb; |
360 | |
361 | } |
362 | |
363 | for (i = 0; i < CX231XX_AUDIO_BUFS; i++) { |
364 | errCode = usb_submit_urb(urb: dev->adev.urb[i], GFP_ATOMIC); |
365 | if (errCode < 0) { |
366 | cx231xx_bulk_audio_deinit(dev); |
367 | return errCode; |
368 | } |
369 | } |
370 | |
371 | return errCode; |
372 | } |
373 | |
374 | static const struct snd_pcm_hardware snd_cx231xx_hw_capture = { |
375 | .info = SNDRV_PCM_INFO_BLOCK_TRANSFER | |
376 | SNDRV_PCM_INFO_MMAP | |
377 | SNDRV_PCM_INFO_INTERLEAVED | |
378 | SNDRV_PCM_INFO_MMAP_VALID, |
379 | |
380 | .formats = SNDRV_PCM_FMTBIT_S16_LE, |
381 | |
382 | .rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_KNOT, |
383 | |
384 | .rate_min = 48000, |
385 | .rate_max = 48000, |
386 | .channels_min = 2, |
387 | .channels_max = 2, |
388 | .buffer_bytes_max = 62720 * 8, /* just about the value in usbaudio.c */ |
389 | .period_bytes_min = 64, /* 12544/2, */ |
390 | .period_bytes_max = 12544, |
391 | .periods_min = 2, |
392 | .periods_max = 98, /* 12544, */ |
393 | }; |
394 | |
395 | static int snd_cx231xx_capture_open(struct snd_pcm_substream *substream) |
396 | { |
397 | struct cx231xx *dev = snd_pcm_substream_chip(substream); |
398 | struct snd_pcm_runtime *runtime = substream->runtime; |
399 | int ret = 0; |
400 | |
401 | dev_dbg(dev->dev, |
402 | "opening device and trying to acquire exclusive lock\n" ); |
403 | |
404 | if (dev->state & DEV_DISCONNECTED) { |
405 | dev_err(dev->dev, |
406 | "Can't open. the device was removed.\n" ); |
407 | return -ENODEV; |
408 | } |
409 | |
410 | /* set alternate setting for audio interface */ |
411 | /* 1 - 48000 samples per sec */ |
412 | mutex_lock(&dev->lock); |
413 | if (dev->USE_ISO) |
414 | ret = cx231xx_set_alt_setting(dev, index: INDEX_AUDIO, alt: 1); |
415 | else |
416 | ret = cx231xx_set_alt_setting(dev, index: INDEX_AUDIO, alt: 0); |
417 | mutex_unlock(lock: &dev->lock); |
418 | if (ret < 0) { |
419 | dev_err(dev->dev, |
420 | "failed to set alternate setting !\n" ); |
421 | |
422 | return ret; |
423 | } |
424 | |
425 | runtime->hw = snd_cx231xx_hw_capture; |
426 | |
427 | mutex_lock(&dev->lock); |
428 | /* inform hardware to start streaming */ |
429 | ret = cx231xx_capture_start(dev, start: 1, media_type: Audio); |
430 | |
431 | dev->adev.users++; |
432 | mutex_unlock(lock: &dev->lock); |
433 | |
434 | snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); |
435 | dev->adev.capture_pcm_substream = substream; |
436 | runtime->private_data = dev; |
437 | |
438 | return 0; |
439 | } |
440 | |
441 | static int snd_cx231xx_pcm_close(struct snd_pcm_substream *substream) |
442 | { |
443 | int ret; |
444 | struct cx231xx *dev = snd_pcm_substream_chip(substream); |
445 | |
446 | dev_dbg(dev->dev, "closing device\n" ); |
447 | |
448 | /* inform hardware to stop streaming */ |
449 | mutex_lock(&dev->lock); |
450 | ret = cx231xx_capture_start(dev, start: 0, media_type: Audio); |
451 | |
452 | /* set alternate setting for audio interface */ |
453 | /* 1 - 48000 samples per sec */ |
454 | ret = cx231xx_set_alt_setting(dev, index: INDEX_AUDIO, alt: 0); |
455 | if (ret < 0) { |
456 | dev_err(dev->dev, |
457 | "failed to set alternate setting !\n" ); |
458 | |
459 | mutex_unlock(lock: &dev->lock); |
460 | return ret; |
461 | } |
462 | |
463 | dev->adev.users--; |
464 | mutex_unlock(lock: &dev->lock); |
465 | |
466 | if (dev->adev.users == 0 && dev->adev.shutdown == 1) { |
467 | dev_dbg(dev->dev, "audio users: %d\n" , dev->adev.users); |
468 | dev_dbg(dev->dev, "disabling audio stream!\n" ); |
469 | dev->adev.shutdown = 0; |
470 | dev_dbg(dev->dev, "released lock\n" ); |
471 | if (atomic_read(v: &dev->stream_started) > 0) { |
472 | atomic_set(v: &dev->stream_started, i: 0); |
473 | schedule_work(work: &dev->wq_trigger); |
474 | } |
475 | } |
476 | return 0; |
477 | } |
478 | |
479 | static int snd_cx231xx_prepare(struct snd_pcm_substream *substream) |
480 | { |
481 | struct cx231xx *dev = snd_pcm_substream_chip(substream); |
482 | |
483 | dev->adev.hwptr_done_capture = 0; |
484 | dev->adev.capture_transfer_done = 0; |
485 | |
486 | return 0; |
487 | } |
488 | |
489 | static void audio_trigger(struct work_struct *work) |
490 | { |
491 | struct cx231xx *dev = container_of(work, struct cx231xx, wq_trigger); |
492 | |
493 | if (atomic_read(v: &dev->stream_started)) { |
494 | dev_dbg(dev->dev, "starting capture" ); |
495 | if (is_fw_load(dev) == 0) |
496 | cx25840_call(dev, core, load_fw); |
497 | if (dev->USE_ISO) |
498 | cx231xx_init_audio_isoc(dev); |
499 | else |
500 | cx231xx_init_audio_bulk(dev); |
501 | } else { |
502 | dev_dbg(dev->dev, "stopping capture" ); |
503 | cx231xx_isoc_audio_deinit(dev); |
504 | } |
505 | } |
506 | |
507 | static int snd_cx231xx_capture_trigger(struct snd_pcm_substream *substream, |
508 | int cmd) |
509 | { |
510 | struct cx231xx *dev = snd_pcm_substream_chip(substream); |
511 | int retval = 0; |
512 | |
513 | if (dev->state & DEV_DISCONNECTED) |
514 | return -ENODEV; |
515 | |
516 | spin_lock(lock: &dev->adev.slock); |
517 | switch (cmd) { |
518 | case SNDRV_PCM_TRIGGER_START: |
519 | atomic_set(v: &dev->stream_started, i: 1); |
520 | break; |
521 | case SNDRV_PCM_TRIGGER_STOP: |
522 | atomic_set(v: &dev->stream_started, i: 0); |
523 | break; |
524 | default: |
525 | retval = -EINVAL; |
526 | break; |
527 | } |
528 | spin_unlock(lock: &dev->adev.slock); |
529 | |
530 | schedule_work(work: &dev->wq_trigger); |
531 | |
532 | return retval; |
533 | } |
534 | |
535 | static snd_pcm_uframes_t snd_cx231xx_capture_pointer(struct snd_pcm_substream |
536 | *substream) |
537 | { |
538 | struct cx231xx *dev; |
539 | unsigned long flags; |
540 | snd_pcm_uframes_t hwptr_done; |
541 | |
542 | dev = snd_pcm_substream_chip(substream); |
543 | |
544 | spin_lock_irqsave(&dev->adev.slock, flags); |
545 | hwptr_done = dev->adev.hwptr_done_capture; |
546 | spin_unlock_irqrestore(lock: &dev->adev.slock, flags); |
547 | |
548 | return hwptr_done; |
549 | } |
550 | |
551 | static const struct snd_pcm_ops snd_cx231xx_pcm_capture = { |
552 | .open = snd_cx231xx_capture_open, |
553 | .close = snd_cx231xx_pcm_close, |
554 | .prepare = snd_cx231xx_prepare, |
555 | .trigger = snd_cx231xx_capture_trigger, |
556 | .pointer = snd_cx231xx_capture_pointer, |
557 | }; |
558 | |
559 | static int cx231xx_audio_init(struct cx231xx *dev) |
560 | { |
561 | struct cx231xx_audio *adev = &dev->adev; |
562 | struct snd_pcm *pcm; |
563 | struct snd_card *card; |
564 | static int devnr; |
565 | int err; |
566 | struct usb_interface *uif; |
567 | int i, isoc_pipe = 0; |
568 | |
569 | if (dev->has_alsa_audio != 1) { |
570 | /* This device does not support the extension (in this case |
571 | the device is expecting the snd-usb-audio module or |
572 | doesn't have analog audio support at all) */ |
573 | return 0; |
574 | } |
575 | |
576 | dev_dbg(dev->dev, |
577 | "probing for cx231xx non standard usbaudio\n" ); |
578 | |
579 | err = snd_card_new(parent: dev->dev, idx: index[devnr], xid: "Cx231xx Audio" , |
580 | THIS_MODULE, extra_size: 0, card_ret: &card); |
581 | if (err < 0) |
582 | return err; |
583 | |
584 | spin_lock_init(&adev->slock); |
585 | err = snd_pcm_new(card, id: "Cx231xx Audio" , device: 0, playback_count: 0, capture_count: 1, rpcm: &pcm); |
586 | if (err < 0) |
587 | goto err_free_card; |
588 | |
589 | snd_pcm_set_ops(pcm, direction: SNDRV_PCM_STREAM_CAPTURE, |
590 | ops: &snd_cx231xx_pcm_capture); |
591 | snd_pcm_set_managed_buffer_all(pcm, SNDRV_DMA_TYPE_VMALLOC, NULL, size: 0, max: 0); |
592 | pcm->info_flags = 0; |
593 | pcm->private_data = dev; |
594 | strscpy(p: pcm->name, q: "Conexant cx231xx Capture" , size: sizeof(pcm->name)); |
595 | strscpy(p: card->driver, q: "Cx231xx-Audio" , size: sizeof(card->driver)); |
596 | strscpy(p: card->shortname, q: "Cx231xx Audio" , size: sizeof(card->shortname)); |
597 | strscpy(p: card->longname, q: "Conexant cx231xx Audio" , size: sizeof(card->longname)); |
598 | |
599 | INIT_WORK(&dev->wq_trigger, audio_trigger); |
600 | |
601 | err = snd_card_register(card); |
602 | if (err < 0) |
603 | goto err_free_card; |
604 | |
605 | adev->sndcard = card; |
606 | adev->udev = dev->udev; |
607 | |
608 | /* compute alternate max packet sizes for Audio */ |
609 | uif = |
610 | dev->udev->actconfig->interface[dev->current_pcb_config. |
611 | hs_config_info[0].interface_info. |
612 | audio_index + 1]; |
613 | |
614 | if (uif->altsetting[0].desc.bNumEndpoints < isoc_pipe + 1) { |
615 | err = -ENODEV; |
616 | goto err_free_card; |
617 | } |
618 | |
619 | adev->end_point_addr = |
620 | uif->altsetting[0].endpoint[isoc_pipe].desc. |
621 | bEndpointAddress; |
622 | |
623 | adev->num_alt = uif->num_altsetting; |
624 | dev_info(dev->dev, |
625 | "audio EndPoint Addr 0x%x, Alternate settings: %i\n" , |
626 | adev->end_point_addr, adev->num_alt); |
627 | adev->alt_max_pkt_size = kmalloc_array(n: 32, size: adev->num_alt, GFP_KERNEL); |
628 | if (!adev->alt_max_pkt_size) { |
629 | err = -ENOMEM; |
630 | goto err_free_card; |
631 | } |
632 | |
633 | for (i = 0; i < adev->num_alt; i++) { |
634 | u16 tmp; |
635 | |
636 | if (uif->altsetting[i].desc.bNumEndpoints < isoc_pipe + 1) { |
637 | err = -ENODEV; |
638 | goto err_free_pkt_size; |
639 | } |
640 | |
641 | tmp = le16_to_cpu(uif->altsetting[i].endpoint[isoc_pipe].desc. |
642 | wMaxPacketSize); |
643 | adev->alt_max_pkt_size[i] = |
644 | (tmp & 0x07ff) * (((tmp & 0x1800) >> 11) + 1); |
645 | dev_dbg(dev->dev, |
646 | "audio alternate setting %i, max size= %i\n" , i, |
647 | adev->alt_max_pkt_size[i]); |
648 | } |
649 | |
650 | return 0; |
651 | |
652 | err_free_pkt_size: |
653 | kfree(objp: adev->alt_max_pkt_size); |
654 | err_free_card: |
655 | snd_card_free(card); |
656 | |
657 | return err; |
658 | } |
659 | |
660 | static int cx231xx_audio_fini(struct cx231xx *dev) |
661 | { |
662 | if (dev == NULL) |
663 | return 0; |
664 | |
665 | if (dev->has_alsa_audio != 1) { |
666 | /* This device does not support the extension (in this case |
667 | the device is expecting the snd-usb-audio module or |
668 | doesn't have analog audio support at all) */ |
669 | return 0; |
670 | } |
671 | |
672 | if (dev->adev.sndcard) { |
673 | snd_card_free_when_closed(card: dev->adev.sndcard); |
674 | kfree(objp: dev->adev.alt_max_pkt_size); |
675 | dev->adev.sndcard = NULL; |
676 | } |
677 | |
678 | return 0; |
679 | } |
680 | |
681 | static struct cx231xx_ops audio_ops = { |
682 | .id = CX231XX_AUDIO, |
683 | .name = "Cx231xx Audio Extension" , |
684 | .init = cx231xx_audio_init, |
685 | .fini = cx231xx_audio_fini, |
686 | }; |
687 | |
688 | static int __init cx231xx_alsa_register(void) |
689 | { |
690 | return cx231xx_register_extension(dev: &audio_ops); |
691 | } |
692 | |
693 | static void __exit cx231xx_alsa_unregister(void) |
694 | { |
695 | cx231xx_unregister_extension(dev: &audio_ops); |
696 | } |
697 | |
698 | MODULE_LICENSE("GPL" ); |
699 | MODULE_AUTHOR("Srinivasa Deevi <srinivasa.deevi@conexant.com>" ); |
700 | MODULE_DESCRIPTION("Cx231xx Audio driver" ); |
701 | |
702 | module_init(cx231xx_alsa_register); |
703 | module_exit(cx231xx_alsa_unregister); |
704 | |