Commit | Line | Data |
---|---|---|
c6994e6f | 1 | /* |
18b5b3b5 | 2 | * u_uac1.c -- ALSA audio utilities for Gadget stack |
c6994e6f BW |
3 | * |
4 | * Copyright (C) 2008 Bryan Wu <cooloney@kernel.org> | |
5 | * Copyright (C) 2008 Analog Devices, Inc | |
6 | * | |
7 | * Enter bugs at http://blackfin.uclinux.org/ | |
8 | * | |
9 | * Licensed under the GPL-2 or later. | |
10 | */ | |
11 | ||
12 | #include <linux/kernel.h> | |
f3a3406b | 13 | #include <linux/module.h> |
5a0e3ad6 | 14 | #include <linux/slab.h> |
c6994e6f BW |
15 | #include <linux/device.h> |
16 | #include <linux/delay.h> | |
17 | #include <linux/ctype.h> | |
18 | #include <linux/random.h> | |
19 | #include <linux/syscalls.h> | |
20 | ||
18b5b3b5 | 21 | #include "u_uac1.h" |
c6994e6f BW |
22 | |
23 | /* | |
24 | * This component encapsulates the ALSA devices for USB audio gadget | |
25 | */ | |
26 | ||
c6994e6f BW |
27 | /*-------------------------------------------------------------------------*/ |
28 | ||
29 | /** | |
30 | * Some ALSA internal helper functions | |
31 | */ | |
32 | static int snd_interval_refine_set(struct snd_interval *i, unsigned int val) | |
33 | { | |
34 | struct snd_interval t; | |
35 | t.empty = 0; | |
36 | t.min = t.max = val; | |
37 | t.openmin = t.openmax = 0; | |
38 | t.integer = 1; | |
39 | return snd_interval_refine(i, &t); | |
40 | } | |
41 | ||
42 | static int _snd_pcm_hw_param_set(struct snd_pcm_hw_params *params, | |
43 | snd_pcm_hw_param_t var, unsigned int val, | |
44 | int dir) | |
45 | { | |
46 | int changed; | |
47 | if (hw_is_mask(var)) { | |
48 | struct snd_mask *m = hw_param_mask(params, var); | |
49 | if (val == 0 && dir < 0) { | |
50 | changed = -EINVAL; | |
51 | snd_mask_none(m); | |
52 | } else { | |
53 | if (dir > 0) | |
54 | val++; | |
55 | else if (dir < 0) | |
56 | val--; | |
57 | changed = snd_mask_refine_set( | |
58 | hw_param_mask(params, var), val); | |
59 | } | |
60 | } else if (hw_is_interval(var)) { | |
61 | struct snd_interval *i = hw_param_interval(params, var); | |
62 | if (val == 0 && dir < 0) { | |
63 | changed = -EINVAL; | |
64 | snd_interval_none(i); | |
65 | } else if (dir == 0) | |
66 | changed = snd_interval_refine_set(i, val); | |
67 | else { | |
68 | struct snd_interval t; | |
69 | t.openmin = 1; | |
70 | t.openmax = 1; | |
71 | t.empty = 0; | |
72 | t.integer = 0; | |
73 | if (dir < 0) { | |
74 | t.min = val - 1; | |
75 | t.max = val; | |
76 | } else { | |
77 | t.min = val; | |
78 | t.max = val+1; | |
79 | } | |
80 | changed = snd_interval_refine(i, &t); | |
81 | } | |
82 | } else | |
83 | return -EINVAL; | |
84 | if (changed) { | |
85 | params->cmask |= 1 << var; | |
86 | params->rmask |= 1 << var; | |
87 | } | |
88 | return changed; | |
89 | } | |
90 | /*-------------------------------------------------------------------------*/ | |
91 | ||
92 | /** | |
93 | * Set default hardware params | |
94 | */ | |
95 | static int playback_default_hw_params(struct gaudio_snd_dev *snd) | |
96 | { | |
97 | struct snd_pcm_substream *substream = snd->substream; | |
98 | struct snd_pcm_hw_params *params; | |
99 | snd_pcm_sframes_t result; | |
100 | ||
101 | /* | |
102 | * SNDRV_PCM_ACCESS_RW_INTERLEAVED, | |
103 | * SNDRV_PCM_FORMAT_S16_LE | |
104 | * CHANNELS: 2 | |
105 | * RATE: 48000 | |
106 | */ | |
107 | snd->access = SNDRV_PCM_ACCESS_RW_INTERLEAVED; | |
108 | snd->format = SNDRV_PCM_FORMAT_S16_LE; | |
109 | snd->channels = 2; | |
110 | snd->rate = 48000; | |
111 | ||
112 | params = kzalloc(sizeof(*params), GFP_KERNEL); | |
113 | if (!params) | |
114 | return -ENOMEM; | |
115 | ||
116 | _snd_pcm_hw_params_any(params); | |
117 | _snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_ACCESS, | |
118 | snd->access, 0); | |
119 | _snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_FORMAT, | |
120 | snd->format, 0); | |
121 | _snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_CHANNELS, | |
122 | snd->channels, 0); | |
123 | _snd_pcm_hw_param_set(params, SNDRV_PCM_HW_PARAM_RATE, | |
124 | snd->rate, 0); | |
125 | ||
126 | snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_DROP, NULL); | |
127 | snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_HW_PARAMS, params); | |
128 | ||
129 | result = snd_pcm_kernel_ioctl(substream, SNDRV_PCM_IOCTL_PREPARE, NULL); | |
130 | if (result < 0) { | |
131 | ERROR(snd->card, | |
132 | "Preparing sound card failed: %d\n", (int)result); | |
133 | kfree(params); | |
134 | return result; | |
135 | } | |
136 | ||
137 | /* Store the hardware parameters */ | |
138 | snd->access = params_access(params); | |
139 | snd->format = params_format(params); | |
140 | snd->channels = params_channels(params); | |
141 | snd->rate = params_rate(params); | |
142 | ||
143 | kfree(params); | |
144 | ||
145 | INFO(snd->card, | |
146 | "Hardware params: access %x, format %x, channels %d, rate %d\n", | |
147 | snd->access, snd->format, snd->channels, snd->rate); | |
148 | ||
149 | return 0; | |
150 | } | |
151 | ||
152 | /** | |
153 | * Playback audio buffer data by ALSA PCM device | |
154 | */ | |
f3a3406b | 155 | size_t u_audio_playback(struct gaudio *card, void *buf, size_t count) |
c6994e6f BW |
156 | { |
157 | struct gaudio_snd_dev *snd = &card->playback; | |
158 | struct snd_pcm_substream *substream = snd->substream; | |
159 | struct snd_pcm_runtime *runtime = substream->runtime; | |
160 | mm_segment_t old_fs; | |
161 | ssize_t result; | |
162 | snd_pcm_sframes_t frames; | |
163 | ||
164 | try_again: | |
165 | if (runtime->status->state == SNDRV_PCM_STATE_XRUN || | |
166 | runtime->status->state == SNDRV_PCM_STATE_SUSPENDED) { | |
167 | result = snd_pcm_kernel_ioctl(substream, | |
168 | SNDRV_PCM_IOCTL_PREPARE, NULL); | |
169 | if (result < 0) { | |
170 | ERROR(card, "Preparing sound card failed: %d\n", | |
171 | (int)result); | |
172 | return result; | |
173 | } | |
174 | } | |
175 | ||
176 | frames = bytes_to_frames(runtime, count); | |
177 | old_fs = get_fs(); | |
178 | set_fs(KERNEL_DS); | |
2419831c | 179 | result = snd_pcm_lib_write(snd->substream, (void __user *)buf, frames); |
c6994e6f BW |
180 | if (result != frames) { |
181 | ERROR(card, "Playback error: %d\n", (int)result); | |
182 | set_fs(old_fs); | |
183 | goto try_again; | |
184 | } | |
185 | set_fs(old_fs); | |
186 | ||
187 | return 0; | |
188 | } | |
189 | ||
f3a3406b | 190 | int u_audio_get_playback_channels(struct gaudio *card) |
c6994e6f BW |
191 | { |
192 | return card->playback.channels; | |
193 | } | |
194 | ||
f3a3406b | 195 | int u_audio_get_playback_rate(struct gaudio *card) |
c6994e6f BW |
196 | { |
197 | return card->playback.rate; | |
198 | } | |
199 | ||
200 | /** | |
201 | * Open ALSA PCM and control device files | |
202 | * Initial the PCM or control device | |
203 | */ | |
204 | static int gaudio_open_snd_dev(struct gaudio *card) | |
205 | { | |
206 | struct snd_pcm_file *pcm_file; | |
207 | struct gaudio_snd_dev *snd; | |
f3a3406b AP |
208 | struct f_uac1_opts *opts; |
209 | char *fn_play, *fn_cap, *fn_cntl; | |
210 | ||
211 | opts = container_of(card->func.fi, struct f_uac1_opts, func_inst); | |
212 | fn_play = opts->fn_play; | |
213 | fn_cap = opts->fn_cap; | |
214 | fn_cntl = opts->fn_cntl; | |
c6994e6f | 215 | |
c6994e6f BW |
216 | /* Open control device */ |
217 | snd = &card->control; | |
218 | snd->filp = filp_open(fn_cntl, O_RDWR, 0); | |
219 | if (IS_ERR(snd->filp)) { | |
220 | int ret = PTR_ERR(snd->filp); | |
221 | ERROR(card, "unable to open sound control device file: %s\n", | |
222 | fn_cntl); | |
223 | snd->filp = NULL; | |
224 | return ret; | |
225 | } | |
226 | snd->card = card; | |
227 | ||
228 | /* Open PCM playback device and setup substream */ | |
229 | snd = &card->playback; | |
230 | snd->filp = filp_open(fn_play, O_WRONLY, 0); | |
231 | if (IS_ERR(snd->filp)) { | |
29240e23 DC |
232 | int ret = PTR_ERR(snd->filp); |
233 | ||
c6994e6f BW |
234 | ERROR(card, "No such PCM playback device: %s\n", fn_play); |
235 | snd->filp = NULL; | |
29240e23 | 236 | return ret; |
c6994e6f BW |
237 | } |
238 | pcm_file = snd->filp->private_data; | |
239 | snd->substream = pcm_file->substream; | |
240 | snd->card = card; | |
241 | playback_default_hw_params(snd); | |
242 | ||
243 | /* Open PCM capture device and setup substream */ | |
244 | snd = &card->capture; | |
245 | snd->filp = filp_open(fn_cap, O_RDONLY, 0); | |
246 | if (IS_ERR(snd->filp)) { | |
247 | ERROR(card, "No such PCM capture device: %s\n", fn_cap); | |
e792b1b0 RC |
248 | snd->substream = NULL; |
249 | snd->card = NULL; | |
df4fedea | 250 | snd->filp = NULL; |
e792b1b0 RC |
251 | } else { |
252 | pcm_file = snd->filp->private_data; | |
253 | snd->substream = pcm_file->substream; | |
254 | snd->card = card; | |
c6994e6f | 255 | } |
c6994e6f BW |
256 | |
257 | return 0; | |
258 | } | |
259 | ||
260 | /** | |
261 | * Close ALSA PCM and control device files | |
262 | */ | |
263 | static int gaudio_close_snd_dev(struct gaudio *gau) | |
264 | { | |
265 | struct gaudio_snd_dev *snd; | |
266 | ||
267 | /* Close control device */ | |
268 | snd = &gau->control; | |
df4fedea | 269 | if (snd->filp) |
20818a0c | 270 | filp_close(snd->filp, NULL); |
c6994e6f BW |
271 | |
272 | /* Close PCM playback device and setup substream */ | |
273 | snd = &gau->playback; | |
df4fedea | 274 | if (snd->filp) |
20818a0c | 275 | filp_close(snd->filp, NULL); |
c6994e6f BW |
276 | |
277 | /* Close PCM capture device and setup substream */ | |
278 | snd = &gau->capture; | |
df4fedea | 279 | if (snd->filp) |
20818a0c | 280 | filp_close(snd->filp, NULL); |
c6994e6f BW |
281 | |
282 | return 0; | |
283 | } | |
284 | ||
285 | /** | |
286 | * gaudio_setup - setup ALSA interface and preparing for USB transfer | |
287 | * | |
288 | * This sets up PCM, mixer or MIDI ALSA devices fore USB gadget using. | |
289 | * | |
290 | * Returns negative errno, or zero on success | |
291 | */ | |
f3a3406b | 292 | int gaudio_setup(struct gaudio *card) |
c6994e6f BW |
293 | { |
294 | int ret; | |
295 | ||
296 | ret = gaudio_open_snd_dev(card); | |
297 | if (ret) | |
298 | ERROR(card, "we need at least one control device\n"); | |
feef1d95 | 299 | |
c6994e6f BW |
300 | return ret; |
301 | ||
302 | } | |
303 | ||
304 | /** | |
305 | * gaudio_cleanup - remove ALSA device interface | |
306 | * | |
307 | * This is called to free all resources allocated by @gaudio_setup(). | |
308 | */ | |
f3a3406b | 309 | void gaudio_cleanup(struct gaudio *the_card) |
c6994e6f | 310 | { |
c76abecc | 311 | if (the_card) |
feef1d95 | 312 | gaudio_close_snd_dev(the_card); |
c6994e6f BW |
313 | } |
314 |