Commit | Line | Data |
---|---|---|
5b3f03f0 HS |
1 | #include <linux/kernel.h> |
2 | #include <linux/usb.h> | |
3 | #include <linux/init.h> | |
4 | #include <linux/sound.h> | |
5 | #include <linux/spinlock.h> | |
6 | #include <linux/soundcard.h> | |
5b3f03f0 HS |
7 | #include <linux/vmalloc.h> |
8 | #include <linux/proc_fs.h> | |
9 | #include <linux/module.h> | |
5a0e3ad6 | 10 | #include <linux/gfp.h> |
5b3f03f0 HS |
11 | #include <sound/core.h> |
12 | #include <sound/pcm.h> | |
13 | #include <sound/pcm_params.h> | |
14 | #include <sound/info.h> | |
15 | #include <sound/initval.h> | |
16 | #include <sound/control.h> | |
17 | #include <media/v4l2-common.h> | |
18 | #include "pd-common.h" | |
19 | #include "vendorcmds.h" | |
20 | ||
21 | static void complete_handler_audio(struct urb *urb); | |
22 | #define AUDIO_EP (0x83) | |
23 | #define AUDIO_BUF_SIZE (512) | |
24 | #define PERIOD_SIZE (1024 * 8) | |
25 | #define PERIOD_MIN (4) | |
26 | #define PERIOD_MAX PERIOD_MIN | |
27 | ||
28 | static struct snd_pcm_hardware snd_pd_hw_capture = { | |
29 | .info = SNDRV_PCM_INFO_BLOCK_TRANSFER | | |
30 | SNDRV_PCM_INFO_MMAP | | |
31 | SNDRV_PCM_INFO_INTERLEAVED | | |
32 | SNDRV_PCM_INFO_MMAP_VALID, | |
33 | ||
34 | .formats = SNDRV_PCM_FMTBIT_S16_LE, | |
35 | .rates = SNDRV_PCM_RATE_48000, | |
36 | ||
37 | .rate_min = 48000, | |
38 | .rate_max = 48000, | |
39 | .channels_min = 2, | |
40 | .channels_max = 2, | |
41 | .buffer_bytes_max = PERIOD_SIZE * PERIOD_MIN, | |
42 | .period_bytes_min = PERIOD_SIZE, | |
43 | .period_bytes_max = PERIOD_SIZE, | |
44 | .periods_min = PERIOD_MIN, | |
45 | .periods_max = PERIOD_MAX, | |
46 | /* | |
47 | .buffer_bytes_max = 62720 * 8, | |
48 | .period_bytes_min = 64, | |
49 | .period_bytes_max = 12544, | |
50 | .periods_min = 2, | |
51 | .periods_max = 98 | |
52 | */ | |
53 | }; | |
54 | ||
55 | static int snd_pd_capture_open(struct snd_pcm_substream *substream) | |
56 | { | |
57 | struct poseidon *p = snd_pcm_substream_chip(substream); | |
58 | struct poseidon_audio *pa = &p->audio; | |
59 | struct snd_pcm_runtime *runtime = substream->runtime; | |
60 | ||
61 | if (!p) | |
62 | return -ENODEV; | |
63 | pa->users++; | |
64 | pa->card_close = 0; | |
65 | pa->capture_pcm_substream = substream; | |
66 | runtime->private_data = p; | |
67 | ||
68 | runtime->hw = snd_pd_hw_capture; | |
69 | snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); | |
70 | usb_autopm_get_interface(p->interface); | |
71 | kref_get(&p->kref); | |
72 | return 0; | |
73 | } | |
74 | ||
75 | static int snd_pd_pcm_close(struct snd_pcm_substream *substream) | |
76 | { | |
77 | struct poseidon *p = snd_pcm_substream_chip(substream); | |
78 | struct poseidon_audio *pa = &p->audio; | |
79 | ||
80 | pa->users--; | |
81 | pa->card_close = 1; | |
82 | usb_autopm_put_interface(p->interface); | |
83 | kref_put(&p->kref, poseidon_delete); | |
84 | return 0; | |
85 | } | |
86 | ||
87 | static int snd_pd_hw_capture_params(struct snd_pcm_substream *substream, | |
88 | struct snd_pcm_hw_params *hw_params) | |
89 | { | |
90 | struct snd_pcm_runtime *runtime = substream->runtime; | |
91 | unsigned int size; | |
92 | ||
93 | size = params_buffer_bytes(hw_params); | |
94 | if (runtime->dma_area) { | |
95 | if (runtime->dma_bytes > size) | |
96 | return 0; | |
97 | vfree(runtime->dma_area); | |
98 | } | |
99 | runtime->dma_area = vmalloc(size); | |
100 | if (!runtime->dma_area) | |
101 | return -ENOMEM; | |
102 | else | |
103 | runtime->dma_bytes = size; | |
104 | return 0; | |
105 | } | |
106 | ||
107 | static int audio_buf_free(struct poseidon *p) | |
108 | { | |
109 | struct poseidon_audio *pa = &p->audio; | |
110 | int i; | |
111 | ||
112 | for (i = 0; i < AUDIO_BUFS; i++) | |
113 | if (pa->urb_array[i]) | |
114 | usb_kill_urb(pa->urb_array[i]); | |
115 | free_all_urb_generic(pa->urb_array, AUDIO_BUFS); | |
116 | logpm(); | |
117 | return 0; | |
118 | } | |
119 | ||
120 | static int snd_pd_hw_capture_free(struct snd_pcm_substream *substream) | |
121 | { | |
122 | struct poseidon *p = snd_pcm_substream_chip(substream); | |
123 | ||
124 | logpm(); | |
125 | audio_buf_free(p); | |
126 | return 0; | |
127 | } | |
128 | ||
129 | static int snd_pd_prepare(struct snd_pcm_substream *substream) | |
130 | { | |
131 | return 0; | |
132 | } | |
133 | ||
134 | #define AUDIO_TRAILER_SIZE (16) | |
135 | static inline void handle_audio_data(struct urb *urb, int *period_elapsed) | |
136 | { | |
137 | struct poseidon_audio *pa = urb->context; | |
138 | struct snd_pcm_runtime *runtime = pa->capture_pcm_substream->runtime; | |
139 | ||
140 | int stride = runtime->frame_bits >> 3; | |
141 | int len = urb->actual_length / stride; | |
142 | unsigned char *cp = urb->transfer_buffer; | |
143 | unsigned int oldptr = pa->rcv_position; | |
144 | ||
145 | if (urb->actual_length == AUDIO_BUF_SIZE - 4) | |
146 | len -= (AUDIO_TRAILER_SIZE / stride); | |
147 | ||
148 | /* do the copy */ | |
149 | if (oldptr + len >= runtime->buffer_size) { | |
150 | unsigned int cnt = runtime->buffer_size - oldptr; | |
151 | ||
152 | memcpy(runtime->dma_area + oldptr * stride, cp, cnt * stride); | |
153 | memcpy(runtime->dma_area, (cp + cnt * stride), | |
154 | (len * stride - cnt * stride)); | |
155 | } else | |
156 | memcpy(runtime->dma_area + oldptr * stride, cp, len * stride); | |
157 | ||
158 | /* update the statas */ | |
159 | snd_pcm_stream_lock(pa->capture_pcm_substream); | |
160 | pa->rcv_position += len; | |
161 | if (pa->rcv_position >= runtime->buffer_size) | |
162 | pa->rcv_position -= runtime->buffer_size; | |
163 | ||
164 | pa->copied_position += (len); | |
165 | if (pa->copied_position >= runtime->period_size) { | |
166 | pa->copied_position -= runtime->period_size; | |
167 | *period_elapsed = 1; | |
168 | } | |
169 | snd_pcm_stream_unlock(pa->capture_pcm_substream); | |
170 | } | |
171 | ||
172 | static void complete_handler_audio(struct urb *urb) | |
173 | { | |
174 | struct poseidon_audio *pa = urb->context; | |
175 | struct snd_pcm_substream *substream = pa->capture_pcm_substream; | |
176 | int period_elapsed = 0; | |
177 | int ret; | |
178 | ||
179 | if (1 == pa->card_close || pa->capture_stream != STREAM_ON) | |
180 | return; | |
181 | ||
182 | if (urb->status != 0) { | |
183 | /*if (urb->status == -ESHUTDOWN)*/ | |
184 | return; | |
185 | } | |
186 | ||
187 | if (substream) { | |
188 | if (urb->actual_length) { | |
189 | handle_audio_data(urb, &period_elapsed); | |
190 | if (period_elapsed) | |
191 | snd_pcm_period_elapsed(substream); | |
192 | } | |
193 | } | |
194 | ||
195 | ret = usb_submit_urb(urb, GFP_ATOMIC); | |
196 | if (ret < 0) | |
197 | log("audio urb failed (errcod = %i)", ret); | |
198 | return; | |
199 | } | |
200 | ||
201 | static int fire_audio_urb(struct poseidon *p) | |
202 | { | |
203 | int i, ret = 0; | |
204 | struct poseidon_audio *pa = &p->audio; | |
205 | ||
206 | alloc_bulk_urbs_generic(pa->urb_array, AUDIO_BUFS, | |
207 | p->udev, AUDIO_EP, | |
208 | AUDIO_BUF_SIZE, GFP_ATOMIC, | |
209 | complete_handler_audio, pa); | |
210 | ||
211 | for (i = 0; i < AUDIO_BUFS; i++) { | |
212 | ret = usb_submit_urb(pa->urb_array[i], GFP_KERNEL); | |
213 | if (ret) | |
214 | log("urb err : %d", ret); | |
215 | } | |
216 | log(); | |
217 | return ret; | |
218 | } | |
219 | ||
220 | static int snd_pd_capture_trigger(struct snd_pcm_substream *substream, int cmd) | |
221 | { | |
222 | struct poseidon *p = snd_pcm_substream_chip(substream); | |
223 | struct poseidon_audio *pa = &p->audio; | |
224 | ||
225 | if (debug_mode) | |
226 | log("cmd %d, audio stat : %d\n", cmd, pa->capture_stream); | |
227 | ||
228 | switch (cmd) { | |
229 | case SNDRV_PCM_TRIGGER_RESUME: | |
230 | case SNDRV_PCM_TRIGGER_START: | |
231 | if (pa->capture_stream == STREAM_ON) | |
232 | return 0; | |
233 | ||
234 | pa->rcv_position = pa->copied_position = 0; | |
235 | pa->capture_stream = STREAM_ON; | |
236 | ||
237 | if (in_hibernation(p)) | |
238 | return 0; | |
239 | fire_audio_urb(p); | |
240 | return 0; | |
241 | ||
242 | case SNDRV_PCM_TRIGGER_SUSPEND: | |
243 | pa->capture_stream = STREAM_SUSPEND; | |
244 | return 0; | |
245 | case SNDRV_PCM_TRIGGER_STOP: | |
246 | pa->capture_stream = STREAM_OFF; | |
247 | return 0; | |
248 | default: | |
249 | return -EINVAL; | |
250 | } | |
251 | } | |
252 | ||
253 | static snd_pcm_uframes_t | |
254 | snd_pd_capture_pointer(struct snd_pcm_substream *substream) | |
255 | { | |
256 | struct poseidon *p = snd_pcm_substream_chip(substream); | |
257 | struct poseidon_audio *pa = &p->audio; | |
258 | return pa->rcv_position; | |
259 | } | |
260 | ||
261 | static struct page *snd_pcm_pd_get_page(struct snd_pcm_substream *subs, | |
262 | unsigned long offset) | |
263 | { | |
264 | void *pageptr = subs->runtime->dma_area + offset; | |
265 | return vmalloc_to_page(pageptr); | |
266 | } | |
267 | ||
268 | static struct snd_pcm_ops pcm_capture_ops = { | |
269 | .open = snd_pd_capture_open, | |
270 | .close = snd_pd_pcm_close, | |
271 | .ioctl = snd_pcm_lib_ioctl, | |
272 | .hw_params = snd_pd_hw_capture_params, | |
273 | .hw_free = snd_pd_hw_capture_free, | |
274 | .prepare = snd_pd_prepare, | |
275 | .trigger = snd_pd_capture_trigger, | |
276 | .pointer = snd_pd_capture_pointer, | |
277 | .page = snd_pcm_pd_get_page, | |
278 | }; | |
279 | ||
280 | #ifdef CONFIG_PM | |
281 | int pm_alsa_suspend(struct poseidon *p) | |
282 | { | |
283 | logpm(p); | |
284 | audio_buf_free(p); | |
285 | return 0; | |
286 | } | |
287 | ||
288 | int pm_alsa_resume(struct poseidon *p) | |
289 | { | |
290 | logpm(p); | |
291 | fire_audio_urb(p); | |
292 | return 0; | |
293 | } | |
294 | #endif | |
295 | ||
296 | int poseidon_audio_init(struct poseidon *p) | |
297 | { | |
298 | struct poseidon_audio *pa = &p->audio; | |
299 | struct snd_card *card; | |
300 | struct snd_pcm *pcm; | |
301 | int ret; | |
302 | ||
303 | ret = snd_card_create(-1, "Telegent", THIS_MODULE, 0, &card); | |
304 | if (ret != 0) | |
305 | return ret; | |
306 | ||
307 | ret = snd_pcm_new(card, "poseidon audio", 0, 0, 1, &pcm); | |
308 | snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &pcm_capture_ops); | |
309 | pcm->info_flags = 0; | |
310 | pcm->private_data = p; | |
311 | strcpy(pcm->name, "poseidon audio capture"); | |
312 | ||
313 | strcpy(card->driver, "ALSA driver"); | |
314 | strcpy(card->shortname, "poseidon Audio"); | |
315 | strcpy(card->longname, "poseidon ALSA Audio"); | |
316 | ||
317 | if (snd_card_register(card)) { | |
318 | snd_card_free(card); | |
319 | return -ENOMEM; | |
320 | } | |
321 | pa->card = card; | |
322 | return 0; | |
323 | } | |
324 | ||
325 | int poseidon_audio_free(struct poseidon *p) | |
326 | { | |
327 | struct poseidon_audio *pa = &p->audio; | |
328 | ||
329 | if (pa->card) | |
330 | snd_card_free(pa->card); | |
331 | return 0; | |
332 | } |