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