xref: /linux/sound/usb/usx2y/us144mkii_controls.c (revision 69e4b75a5b90ef74300c283c0aafe8d41daf13a8)
1 // SPDX-License-Identifier: GPL-2.0-only
2 // Copyright (c) 2025 Šerif Rami <ramiserifpersia@gmail.com>
3 
4 #include "us144mkii.h"
5 
6 /**
7  * @brief Text descriptions for playback output source options.
8  *
9  * Used by ALSA kcontrol elements to provide user-friendly names for
10  * the playback routing options (e.g., "Playback 1-2", "Playback 3-4").
11  */
12 static const char *const playback_source_texts[] = { "Playback 1-2",
13 						     "Playback 3-4" };
14 
15 /**
16  * @brief Text descriptions for capture input source options.
17  *
18  * Used by ALSA kcontrol elements to provide user-friendly names for
19  * the capture routing options (e.g., "Analog In", "Digital In").
20  */
21 static const char *const capture_source_texts[] = { "Analog In", "Digital In" };
22 
23 /**
24  * tascam_playback_source_info() - ALSA control info callback for playback
25  * source.
26  * @kcontrol: The ALSA kcontrol instance.
27  * @uinfo: The ALSA control element info structure to fill.
28  *
29  * This function provides information about the enumerated playback source
30  * control, including its type, count, and available items (Playback 1-2,
31  * Playback 3-4).
32  *
33  * Return: 0 on success.
34  */
35 static int tascam_playback_source_info(struct snd_kcontrol *kcontrol,
36 				       struct snd_ctl_elem_info *uinfo)
37 {
38 	return snd_ctl_enum_info(uinfo, 1, 2, playback_source_texts);
39 }
40 
41 /**
42  * tascam_line_out_get() - ALSA control get callback for Line Outputs Source.
43  * @kcontrol: The ALSA kcontrol instance.
44  * @ucontrol: The ALSA control element value structure to fill.
45  *
46  * This function retrieves the current selection for the Line Outputs source
47  * (Playback 1-2 or Playback 3-4) from the driver's private data and populates
48  * the ALSA control element value.
49  *
50  * Return: 0 on success.
51  */
52 static int tascam_line_out_get(struct snd_kcontrol *kcontrol,
53 			       struct snd_ctl_elem_value *ucontrol)
54 {
55 	struct tascam_card *tascam = snd_kcontrol_chip(kcontrol);
56 
57 	scoped_guard(spinlock_irqsave, &tascam->lock) {
58 		ucontrol->value.enumerated.item[0] = tascam->line_out_source;
59 	}
60 	return 0;
61 }
62 
63 /**
64  * tascam_line_out_put() - ALSA control put callback for Line Outputs Source.
65  * @kcontrol: The ALSA kcontrol instance.
66  * @ucontrol: The ALSA control element value structure containing the new value.
67  *
68  * This function sets the Line Outputs source (Playback 1-2 or Playback 3-4)
69  * based on the user's selection from the ALSA control element. It validates
70  * the input and updates the driver's private data.
71  *
72  * Return: 1 if the value was changed, 0 if unchanged, or a negative error code.
73  */
74 static int tascam_line_out_put(struct snd_kcontrol *kcontrol,
75 			       struct snd_ctl_elem_value *ucontrol)
76 {
77 	struct tascam_card *tascam = snd_kcontrol_chip(kcontrol);
78 	int changed = 0;
79 
80 	if (ucontrol->value.enumerated.item[0] > 1)
81 		return -EINVAL;
82 
83 	scoped_guard(spinlock_irqsave, &tascam->lock) {
84 		if (tascam->line_out_source != ucontrol->value.enumerated.item[0]) {
85 			tascam->line_out_source = ucontrol->value.enumerated.item[0];
86 			changed = 1;
87 		}
88 	}
89 	return changed;
90 }
91 
92 /**
93  * tascam_line_out_control - ALSA kcontrol definition for Line Outputs Source.
94  *
95  * This defines a new ALSA mixer control named "Line OUTPUTS Source" that allows
96  * the user to select between "Playback 1-2" and "Playback 3-4" for the analog
97  * line outputs of the device. It uses the `tascam_playback_source_info` for
98  * information and `tascam_line_out_get`/`tascam_line_out_put` for value
99  * handling.
100  */
101 static const struct snd_kcontrol_new tascam_line_out_control = {
102 	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
103 	.name = "Line Playback Source",
104 	.info = tascam_playback_source_info,
105 	.get = tascam_line_out_get,
106 	.put = tascam_line_out_put,
107 };
108 
109 /**
110  * tascam_digital_out_get() - ALSA control get callback for Digital Outputs
111  * Source.
112  * @kcontrol: The ALSA kcontrol instance.
113  * @ucontrol: The ALSA control element value structure to fill.
114  *
115  * This function retrieves the current selection for the Digital Outputs source
116  * (Playback 1-2 or Playback 3-4) from the driver's private data and populates
117  * the ALSA control element value.
118  *
119  * Return: 0 on success.
120  */
121 static int tascam_digital_out_get(struct snd_kcontrol *kcontrol,
122 				  struct snd_ctl_elem_value *ucontrol)
123 {
124 	struct tascam_card *tascam = snd_kcontrol_chip(kcontrol);
125 
126 	scoped_guard(spinlock_irqsave, &tascam->lock) {
127 		ucontrol->value.enumerated.item[0] = tascam->digital_out_source;
128 	}
129 	return 0;
130 }
131 
132 /**
133  * tascam_digital_out_put() - ALSA control put callback for Digital Outputs
134  * Source.
135  * @kcontrol: The ALSA kcontrol instance.
136  * @ucontrol: The ALSA control element value structure containing the new value.
137  *
138  * This function sets the Digital Outputs source (Playback 1-2 or Playback 3-4)
139  * based on the user's selection from the ALSA control element. It validates
140  * the input and updates the driver's private data.
141  *
142  * Return: 1 if the value was changed, 0 if unchanged, or a negative error code.
143  */
144 static int tascam_digital_out_put(struct snd_kcontrol *kcontrol,
145 				  struct snd_ctl_elem_value *ucontrol)
146 {
147 	struct tascam_card *tascam = snd_kcontrol_chip(kcontrol);
148 	int changed = 0;
149 
150 	if (ucontrol->value.enumerated.item[0] > 1)
151 		return -EINVAL;
152 
153 	scoped_guard(spinlock_irqsave, &tascam->lock) {
154 		if (tascam->digital_out_source != ucontrol->value.enumerated.item[0]) {
155 			tascam->digital_out_source = ucontrol->value.enumerated.item[0];
156 			changed = 1;
157 		}
158 	}
159 	return changed;
160 }
161 
162 /**
163  * tascam_digital_out_control - ALSA kcontrol definition for Digital Outputs
164  * Source.
165  *
166  * This defines a new ALSA mixer control named "Digital OUTPUTS Source" that
167  * allows the user to select between "Playback 1-2" and "Playback 3-4" for the
168  * digital outputs of the device. It uses the `tascam_playback_source_info` for
169  * information and `tascam_digital_out_get`/`tascam_digital_out_put` for value
170  * handling.
171  */
172 static const struct snd_kcontrol_new tascam_digital_out_control = {
173 	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
174 	.name = "Digital Playback Source",
175 	.info = tascam_playback_source_info,
176 	.get = tascam_digital_out_get,
177 	.put = tascam_digital_out_put,
178 };
179 
180 /**
181  * tascam_capture_source_info() - ALSA control info callback for capture source.
182  * @kcontrol: The ALSA kcontrol instance.
183  * @uinfo: The ALSA control element info structure to fill.
184  *
185  * This function provides information about the enumerated capture source
186  * control, including its type, count, and available items (Analog In, Digital
187  * In).
188  *
189  * Return: 0 on success.
190  */
191 static int tascam_capture_source_info(struct snd_kcontrol *kcontrol,
192 				      struct snd_ctl_elem_info *uinfo)
193 {
194 	return snd_ctl_enum_info(uinfo, 1, 2, capture_source_texts);
195 }
196 
197 /**
198  * tascam_capture_12_get() - ALSA control get callback for Capture channels 1
199  * and 2 Source.
200  * @kcontrol: The ALSA kcontrol instance.
201  * @ucontrol: The ALSA control element value structure to fill.
202  *
203  * This function retrieves the current selection for the Capture channels 1 and
204  * 2 source (Analog In or Digital In) from the driver's private data and
205  * populates the ALSA control element value.
206  *
207  * Return: 0 on success.
208  */
209 static int tascam_capture_12_get(struct snd_kcontrol *kcontrol,
210 				 struct snd_ctl_elem_value *ucontrol)
211 {
212 	struct tascam_card *tascam = snd_kcontrol_chip(kcontrol);
213 
214 	scoped_guard(spinlock_irqsave, &tascam->lock) {
215 		ucontrol->value.enumerated.item[0] = tascam->capture_12_source;
216 	}
217 	return 0;
218 }
219 
220 /**
221  * tascam_capture_12_put() - ALSA control put callback for Capture channels 1
222  * and 2 Source.
223  * @kcontrol: The ALSA kcontrol instance.
224  * @ucontrol: The ALSA control element value structure containing the new value.
225  *
226  * This function sets the Capture channels 1 and 2 source (Analog In or Digital
227  * In) based on the user's selection from the ALSA control element. It validates
228  * the input and updates the driver's private data.
229  *
230  * Return: 1 if the value was changed, 0 if unchanged, or a negative error code.
231  */
232 static int tascam_capture_12_put(struct snd_kcontrol *kcontrol,
233 				 struct snd_ctl_elem_value *ucontrol)
234 {
235 	struct tascam_card *tascam = snd_kcontrol_chip(kcontrol);
236 	int changed = 0;
237 
238 	if (ucontrol->value.enumerated.item[0] > 1)
239 		return -EINVAL;
240 
241 	scoped_guard(spinlock_irqsave, &tascam->lock) {
242 		if (tascam->capture_12_source != ucontrol->value.enumerated.item[0]) {
243 			tascam->capture_12_source = ucontrol->value.enumerated.item[0];
244 			changed = 1;
245 		}
246 	}
247 	return changed;
248 }
249 
250 /**
251  * tascam_capture_12_control - ALSA kcontrol definition for Capture channels 1
252  * and 2 Source.
253  *
254  * This defines a new ALSA mixer control named "ch1 and ch2 Source" that allows
255  * the user to select between "Analog In" and "Digital In" for the first two
256  * capture channels of the device. It uses the `tascam_capture_source_info` for
257  * information and `tascam_capture_12_get`/`tascam_capture_12_put` for value
258  * handling.
259  */
260 static const struct snd_kcontrol_new tascam_capture_12_control = {
261 	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
262 	.name = "Ch1/2 Capture Source",
263 	.info = tascam_capture_source_info,
264 	.get = tascam_capture_12_get,
265 	.put = tascam_capture_12_put,
266 };
267 
268 /**
269  * tascam_capture_34_get() - ALSA control get callback for Capture channels 3
270  * and 4 Source.
271  * @kcontrol: The ALSA kcontrol instance.
272  * @ucontrol: The ALSA control element value structure to fill.
273  *
274  * This function retrieves the current selection for the Capture channels 3 and
275  * 4 source (Analog In or Digital In) from the driver's private data and
276  * populates the ALSA control element value.
277  *
278  * Return: 0 on success.
279  */
280 static int tascam_capture_34_get(struct snd_kcontrol *kcontrol,
281 				 struct snd_ctl_elem_value *ucontrol)
282 {
283 	struct tascam_card *tascam = snd_kcontrol_chip(kcontrol);
284 
285 	scoped_guard(spinlock_irqsave, &tascam->lock) {
286 		ucontrol->value.enumerated.item[0] = tascam->capture_34_source;
287 	}
288 	return 0;
289 }
290 
291 /**
292  * tascam_capture_34_put() - ALSA control put callback for Capture channels 3
293  * and 4 Source.
294  * @kcontrol: The ALSA kcontrol instance.
295  * @ucontrol: The ALSA control element value structure containing the new value.
296  *
297  * This function sets the Capture channels 3 and 4 source (Analog In or Digital
298  * In) based on the user's selection from the ALSA control element. It validates
299  * the input and updates the driver's private data.
300  *
301  * Return: 1 if the value was changed, 0 if unchanged, or a negative error code.
302  */
303 static int tascam_capture_34_put(struct snd_kcontrol *kcontrol,
304 				 struct snd_ctl_elem_value *ucontrol)
305 {
306 	struct tascam_card *tascam = snd_kcontrol_chip(kcontrol);
307 	int changed = 0;
308 
309 	if (ucontrol->value.enumerated.item[0] > 1)
310 		return -EINVAL;
311 
312 	scoped_guard(spinlock_irqsave, &tascam->lock) {
313 		if (tascam->capture_34_source != ucontrol->value.enumerated.item[0]) {
314 			tascam->capture_34_source = ucontrol->value.enumerated.item[0];
315 			changed = 1;
316 		}
317 	}
318 	return changed;
319 }
320 
321 /**
322  * tascam_capture_34_control - ALSA kcontrol definition for Capture channels 3
323  * and 4 Source.
324  *
325  * This defines a new ALSA mixer control named "ch3 and ch4 Source" that allows
326  * the user to select between "Analog In" and "Digital In" for the third and
327  * fourth capture channels of the device. It uses the
328  * `tascam_capture_source_info` for information and
329  * `tascam_capture_34_get`/`tascam_capture_34_put` for value handling.
330  */
331 static const struct snd_kcontrol_new tascam_capture_34_control = {
332 	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
333 	.name = "Ch3/4 Capture Source",
334 	.info = tascam_capture_source_info,
335 	.get = tascam_capture_34_get,
336 	.put = tascam_capture_34_put,
337 };
338 
339 /**
340  * tascam_samplerate_info() - ALSA control info callback for Sample Rate.
341  * @kcontrol: The ALSA kcontrol instance.
342  * @uinfo: The ALSA control element info structure to fill.
343  *
344  * This function provides information about the Sample Rate control, defining
345  * it as an integer type with a minimum value of 0 and a maximum of 96000.
346  *
347  * Return: 0 on success.
348  */
349 static int tascam_samplerate_info(struct snd_kcontrol *kcontrol,
350 				  struct snd_ctl_elem_info *uinfo)
351 {
352 	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
353 	uinfo->count = 1;
354 	uinfo->value.integer.min = 0;
355 	uinfo->value.integer.max = 96000;
356 	return 0;
357 }
358 
359 /**
360  * tascam_samplerate_get() - ALSA control get callback for Sample Rate.
361  * @kcontrol: The ALSA kcontrol instance.
362  * @ucontrol: The ALSA control element value structure to fill.
363  *
364  * This function retrieves the current sample rate from the device via a USB
365  * control message and populates the ALSA control element value. If the rate
366  * is already known (i.e., `current_rate` is set), it returns that value
367  * directly.
368  *
369  * Return: 0 on success, or a negative error code on failure.
370  */
371 static int tascam_samplerate_get(struct snd_kcontrol *kcontrol,
372 				 struct snd_ctl_elem_value *ucontrol)
373 {
374 	struct tascam_card *tascam =
375 		(struct tascam_card *)snd_kcontrol_chip(kcontrol);
376 	u8 *buf __free(kfree) = NULL;
377 	int err;
378 	u32 rate = 0;
379 
380 	scoped_guard(spinlock_irqsave, &tascam->lock) {
381 		if (tascam->current_rate > 0) {
382 			ucontrol->value.integer.value[0] = tascam->current_rate;
383 			return 0;
384 		}
385 	}
386 
387 	buf = kmalloc(3, GFP_KERNEL);
388 	if (!buf)
389 		return -ENOMEM;
390 
391 	err = usb_control_msg(tascam->dev, usb_rcvctrlpipe(tascam->dev, 0),
392 			      UAC_GET_CUR, RT_D2H_CLASS_EP,
393 			      UAC_SAMPLING_FREQ_CONTROL, EP_AUDIO_IN, buf, 3,
394 			      USB_CTRL_TIMEOUT_MS);
395 
396 	if (err >= 3)
397 		rate = buf[0] | (buf[1] << 8) | (buf[2] << 16);
398 
399 	ucontrol->value.integer.value[0] = rate;
400 	return 0;
401 }
402 
403 /**
404  * tascam_samplerate_control - ALSA kcontrol definition for Sample Rate.
405  *
406  * This defines a new ALSA mixer control named "Sample Rate" that displays
407  * the current sample rate of the device. It is a read-only control.
408  */
409 static const struct snd_kcontrol_new tascam_samplerate_control = {
410 	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
411 	.name = "Sample Rate",
412 	.info = tascam_samplerate_info,
413 	.get = tascam_samplerate_get,
414 	.access = SNDRV_CTL_ELEM_ACCESS_READ,
415 };
416 
417 int tascam_create_controls(struct tascam_card *tascam)
418 {
419 	int err;
420 
421 	err = snd_ctl_add(tascam->card,
422 			  snd_ctl_new1(&tascam_line_out_control, tascam));
423 	if (err < 0)
424 		return err;
425 	err = snd_ctl_add(tascam->card,
426 			  snd_ctl_new1(&tascam_digital_out_control, tascam));
427 	if (err < 0)
428 		return err;
429 	err = snd_ctl_add(tascam->card,
430 			  snd_ctl_new1(&tascam_capture_12_control, tascam));
431 	if (err < 0)
432 		return err;
433 	err = snd_ctl_add(tascam->card,
434 			  snd_ctl_new1(&tascam_capture_34_control, tascam));
435 	if (err < 0)
436 		return err;
437 
438 	err = snd_ctl_add(tascam->card,
439 			  snd_ctl_new1(&tascam_samplerate_control, tascam));
440 	if (err < 0)
441 		return err;
442 
443 	return 0;
444 }
445