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