Linux Audio

Check our new training course

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