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
308