blob: a2bbf48c6413f9fa54fa462412f6ec08b6d1cf69 [file] [log] [blame]
Torsten Schenkc6d43ba2011-01-24 18:45:30 +01001/*
2 * Linux driver for TerraTec DMX 6Fire USB
3 *
4 * Mixer control
5 *
6 * Author: Torsten Schenk <torsten.schenk@zoho.com>
7 * Created: Jan 01, 2011
Torsten Schenkc6d43ba2011-01-24 18:45:30 +01008 * Copyright: (C) Torsten Schenk
9 *
Torsten Schenkf90ffbf2012-02-22 15:21:12 +010010 * Thanks to:
11 * - Holger Ruckdeschel: he found out how to control individual channel
12 * volumes and introduced mute switch
13 *
Torsten Schenkc6d43ba2011-01-24 18:45:30 +010014 * This program is free software; you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License as published by
16 * the Free Software Foundation; either version 2 of the License, or
17 * (at your option) any later version.
18 */
19
20#include <linux/interrupt.h>
21#include <sound/control.h>
Torsten Schenk8e247a92012-02-22 15:20:54 +010022#include <sound/tlv.h>
Torsten Schenkc6d43ba2011-01-24 18:45:30 +010023
24#include "control.h"
25#include "comm.h"
26#include "chip.h"
27
28static char *opt_coax_texts[2] = { "Optical", "Coax" };
29static char *line_phono_texts[2] = { "Line", "Phono" };
30
31/*
Torsten Schenkc6d43ba2011-01-24 18:45:30 +010032 * data that needs to be sent to device. sets up card internal stuff.
33 * values dumped from windows driver and filtered by trial'n'error.
34 */
35static const struct {
36 u8 type;
37 u8 reg;
38 u8 value;
39}
40init_data[] = {
41 { 0x22, 0x00, 0x00 }, { 0x20, 0x00, 0x08 }, { 0x22, 0x01, 0x01 },
42 { 0x20, 0x01, 0x08 }, { 0x22, 0x02, 0x00 }, { 0x20, 0x02, 0x08 },
43 { 0x22, 0x03, 0x00 }, { 0x20, 0x03, 0x08 }, { 0x22, 0x04, 0x00 },
44 { 0x20, 0x04, 0x08 }, { 0x22, 0x05, 0x01 }, { 0x20, 0x05, 0x08 },
45 { 0x22, 0x04, 0x01 }, { 0x12, 0x04, 0x00 }, { 0x12, 0x05, 0x00 },
Torsten Schenkf90ffbf2012-02-22 15:21:12 +010046 { 0x12, 0x0d, 0x38 }, { 0x12, 0x21, 0x82 }, { 0x12, 0x22, 0x80 },
Torsten Schenkc6d43ba2011-01-24 18:45:30 +010047 { 0x12, 0x23, 0x00 }, { 0x12, 0x06, 0x02 }, { 0x12, 0x03, 0x00 },
48 { 0x12, 0x02, 0x00 }, { 0x22, 0x03, 0x01 },
49 { 0 } /* TERMINATING ENTRY */
50};
51
Torsten Schenk2475b0d2011-04-04 11:50:53 +020052static const int rates_altsetting[] = { 1, 1, 2, 2, 3, 3 };
53/* values to write to soundcard register for all samplerates */
54static const u16 rates_6fire_vl[] = {0x00, 0x01, 0x00, 0x01, 0x00, 0x01};
55static const u16 rates_6fire_vh[] = {0x11, 0x11, 0x10, 0x10, 0x00, 0x00};
56
Torsten Schenk8e247a92012-02-22 15:20:54 +010057static DECLARE_TLV_DB_MINMAX(tlv_output, -9000, 0);
58
Torsten Schenk2475b0d2011-04-04 11:50:53 +020059enum {
60 DIGITAL_THRU_ONLY_SAMPLERATE = 3
61};
62
Torsten Schenkf90ffbf2012-02-22 15:21:12 +010063static void usb6fire_control_output_vol_update(struct control_runtime *rt)
Torsten Schenkc6d43ba2011-01-24 18:45:30 +010064{
65 struct comm_runtime *comm_rt = rt->chip->comm;
Torsten Schenkf90ffbf2012-02-22 15:21:12 +010066 int i;
67
68 if (comm_rt)
69 for (i = 0; i < 6; i++)
70 if (!(rt->ovol_updated & (1 << i))) {
71 comm_rt->write8(comm_rt, 0x12, 0x0f + i,
72 180 - rt->output_vol[i]);
73 rt->ovol_updated |= 1 << i;
74 }
Torsten Schenkc6d43ba2011-01-24 18:45:30 +010075}
76
Torsten Schenkd97c7352012-02-22 15:21:23 +010077static void usb6fire_control_output_mute_update(struct control_runtime *rt)
78{
79 struct comm_runtime *comm_rt = rt->chip->comm;
80
81 if (comm_rt)
82 comm_rt->write8(comm_rt, 0x12, 0x0e, ~rt->output_mute);
83}
84
Torsten Schenkc6d43ba2011-01-24 18:45:30 +010085static void usb6fire_control_line_phono_update(struct control_runtime *rt)
86{
87 struct comm_runtime *comm_rt = rt->chip->comm;
88 if (comm_rt) {
89 comm_rt->write8(comm_rt, 0x22, 0x02, rt->line_phono_switch);
90 comm_rt->write8(comm_rt, 0x21, 0x02, rt->line_phono_switch);
91 }
92}
93
94static void usb6fire_control_opt_coax_update(struct control_runtime *rt)
95{
96 struct comm_runtime *comm_rt = rt->chip->comm;
97 if (comm_rt) {
98 comm_rt->write8(comm_rt, 0x22, 0x00, rt->opt_coax_switch);
99 comm_rt->write8(comm_rt, 0x21, 0x00, rt->opt_coax_switch);
100 }
101}
102
Torsten Schenk2475b0d2011-04-04 11:50:53 +0200103static int usb6fire_control_set_rate(struct control_runtime *rt, int rate)
104{
105 int ret;
106 struct usb_device *device = rt->chip->dev;
107 struct comm_runtime *comm_rt = rt->chip->comm;
108
109 if (rate < 0 || rate >= CONTROL_N_RATES)
110 return -EINVAL;
111
112 ret = usb_set_interface(device, 1, rates_altsetting[rate]);
113 if (ret < 0)
114 return ret;
115
116 /* set soundcard clock */
117 ret = comm_rt->write16(comm_rt, 0x02, 0x01, rates_6fire_vl[rate],
118 rates_6fire_vh[rate]);
119 if (ret < 0)
120 return ret;
121
122 return 0;
123}
124
125static int usb6fire_control_set_channels(
126 struct control_runtime *rt, int n_analog_out,
127 int n_analog_in, bool spdif_out, bool spdif_in)
128{
129 int ret;
130 struct comm_runtime *comm_rt = rt->chip->comm;
131
132 /* enable analog inputs and outputs
133 * (one bit per stereo-channel) */
134 ret = comm_rt->write16(comm_rt, 0x02, 0x02,
135 (1 << (n_analog_out / 2)) - 1,
136 (1 << (n_analog_in / 2)) - 1);
137 if (ret < 0)
138 return ret;
139
140 /* disable digital inputs and outputs */
141 /* TODO: use spdif_x to enable/disable digital channels */
142 ret = comm_rt->write16(comm_rt, 0x02, 0x03, 0x00, 0x00);
143 if (ret < 0)
144 return ret;
145
146 return 0;
147}
148
149static int usb6fire_control_streaming_update(struct control_runtime *rt)
150{
151 struct comm_runtime *comm_rt = rt->chip->comm;
152
153 if (comm_rt) {
154 if (!rt->usb_streaming && rt->digital_thru_switch)
155 usb6fire_control_set_rate(rt,
156 DIGITAL_THRU_ONLY_SAMPLERATE);
157 return comm_rt->write16(comm_rt, 0x02, 0x00, 0x00,
158 (rt->usb_streaming ? 0x01 : 0x00) |
159 (rt->digital_thru_switch ? 0x08 : 0x00));
160 }
161 return -EINVAL;
162}
163
Torsten Schenkf90ffbf2012-02-22 15:21:12 +0100164static int usb6fire_control_output_vol_info(struct snd_kcontrol *kcontrol,
Torsten Schenkc6d43ba2011-01-24 18:45:30 +0100165 struct snd_ctl_elem_info *uinfo)
166{
167 uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
Torsten Schenkf90ffbf2012-02-22 15:21:12 +0100168 uinfo->count = 2;
Torsten Schenkc6d43ba2011-01-24 18:45:30 +0100169 uinfo->value.integer.min = 0;
Torsten Schenk8e247a92012-02-22 15:20:54 +0100170 uinfo->value.integer.max = 180;
Torsten Schenkc6d43ba2011-01-24 18:45:30 +0100171 return 0;
172}
173
Torsten Schenkf90ffbf2012-02-22 15:21:12 +0100174static int usb6fire_control_output_vol_put(struct snd_kcontrol *kcontrol,
Torsten Schenkc6d43ba2011-01-24 18:45:30 +0100175 struct snd_ctl_elem_value *ucontrol)
176{
177 struct control_runtime *rt = snd_kcontrol_chip(kcontrol);
Torsten Schenkf90ffbf2012-02-22 15:21:12 +0100178 unsigned int ch = kcontrol->private_value;
Torsten Schenkc6d43ba2011-01-24 18:45:30 +0100179 int changed = 0;
Torsten Schenkf90ffbf2012-02-22 15:21:12 +0100180
181 if (ch > 4) {
182 snd_printk(KERN_ERR PREFIX "Invalid channel in volume control.");
183 return -EINVAL;
184 }
185
186 if (rt->output_vol[ch] != ucontrol->value.integer.value[0]) {
187 rt->output_vol[ch] = ucontrol->value.integer.value[0];
188 rt->ovol_updated &= ~(1 << ch);
Torsten Schenkc6d43ba2011-01-24 18:45:30 +0100189 changed = 1;
190 }
Torsten Schenkf90ffbf2012-02-22 15:21:12 +0100191 if (rt->output_vol[ch + 1] != ucontrol->value.integer.value[1]) {
192 rt->output_vol[ch + 1] = ucontrol->value.integer.value[1];
193 rt->ovol_updated &= ~(2 << ch);
194 changed = 1;
195 }
196
197 if (changed)
198 usb6fire_control_output_vol_update(rt);
199
Torsten Schenkc6d43ba2011-01-24 18:45:30 +0100200 return changed;
201}
202
Torsten Schenkf90ffbf2012-02-22 15:21:12 +0100203static int usb6fire_control_output_vol_get(struct snd_kcontrol *kcontrol,
Torsten Schenkc6d43ba2011-01-24 18:45:30 +0100204 struct snd_ctl_elem_value *ucontrol)
205{
206 struct control_runtime *rt = snd_kcontrol_chip(kcontrol);
Torsten Schenkf90ffbf2012-02-22 15:21:12 +0100207 unsigned int ch = kcontrol->private_value;
208
209 if (ch > 4) {
210 snd_printk(KERN_ERR PREFIX "Invalid channel in volume control.");
211 return -EINVAL;
212 }
213
214 ucontrol->value.integer.value[0] = rt->output_vol[ch];
215 ucontrol->value.integer.value[1] = rt->output_vol[ch + 1];
Torsten Schenkc6d43ba2011-01-24 18:45:30 +0100216 return 0;
217}
218
Torsten Schenkd97c7352012-02-22 15:21:23 +0100219static int usb6fire_control_output_mute_put(struct snd_kcontrol *kcontrol,
220 struct snd_ctl_elem_value *ucontrol)
221{
222 struct control_runtime *rt = snd_kcontrol_chip(kcontrol);
223 unsigned int ch = kcontrol->private_value;
224 u8 old = rt->output_mute;
225 u8 value = 0;
226
227 if (ch > 4) {
228 snd_printk(KERN_ERR PREFIX "Invalid channel in volume control.");
229 return -EINVAL;
230 }
231
232 rt->output_mute &= ~(3 << ch);
233 if (ucontrol->value.integer.value[0])
234 value |= 1;
235 if (ucontrol->value.integer.value[1])
236 value |= 2;
237 rt->output_mute |= value << ch;
238
239 if (rt->output_mute != old)
240 usb6fire_control_output_mute_update(rt);
241
242 return rt->output_mute != old;
243}
244
245static int usb6fire_control_output_mute_get(struct snd_kcontrol *kcontrol,
246 struct snd_ctl_elem_value *ucontrol)
247{
248 struct control_runtime *rt = snd_kcontrol_chip(kcontrol);
249 unsigned int ch = kcontrol->private_value;
250 u8 value = rt->output_mute >> ch;
251
252 if (ch > 4) {
253 snd_printk(KERN_ERR PREFIX "Invalid channel in volume control.");
254 return -EINVAL;
255 }
256
257 ucontrol->value.integer.value[0] = 1 & value;
258 value >>= 1;
259 ucontrol->value.integer.value[1] = 1 & value;
260
261 return 0;
262}
263
Torsten Schenkc6d43ba2011-01-24 18:45:30 +0100264static int usb6fire_control_line_phono_info(struct snd_kcontrol *kcontrol,
265 struct snd_ctl_elem_info *uinfo)
266{
267 uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
268 uinfo->count = 1;
269 uinfo->value.enumerated.items = 2;
270 if (uinfo->value.enumerated.item > 1)
271 uinfo->value.enumerated.item = 1;
272 strcpy(uinfo->value.enumerated.name,
273 line_phono_texts[uinfo->value.enumerated.item]);
274 return 0;
275}
276
277static int usb6fire_control_line_phono_put(struct snd_kcontrol *kcontrol,
278 struct snd_ctl_elem_value *ucontrol)
279{
280 struct control_runtime *rt = snd_kcontrol_chip(kcontrol);
281 int changed = 0;
282 if (rt->line_phono_switch != ucontrol->value.integer.value[0]) {
283 rt->line_phono_switch = ucontrol->value.integer.value[0];
284 usb6fire_control_line_phono_update(rt);
285 changed = 1;
286 }
287 return changed;
288}
289
290static int usb6fire_control_line_phono_get(struct snd_kcontrol *kcontrol,
291 struct snd_ctl_elem_value *ucontrol)
292{
293 struct control_runtime *rt = snd_kcontrol_chip(kcontrol);
294 ucontrol->value.integer.value[0] = rt->line_phono_switch;
295 return 0;
296}
297
298static int usb6fire_control_opt_coax_info(struct snd_kcontrol *kcontrol,
299 struct snd_ctl_elem_info *uinfo)
300{
301 uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
302 uinfo->count = 1;
303 uinfo->value.enumerated.items = 2;
304 if (uinfo->value.enumerated.item > 1)
305 uinfo->value.enumerated.item = 1;
306 strcpy(uinfo->value.enumerated.name,
307 opt_coax_texts[uinfo->value.enumerated.item]);
308 return 0;
309}
310
311static int usb6fire_control_opt_coax_put(struct snd_kcontrol *kcontrol,
312 struct snd_ctl_elem_value *ucontrol)
313{
314 struct control_runtime *rt = snd_kcontrol_chip(kcontrol);
315 int changed = 0;
316
317 if (rt->opt_coax_switch != ucontrol->value.enumerated.item[0]) {
318 rt->opt_coax_switch = ucontrol->value.enumerated.item[0];
319 usb6fire_control_opt_coax_update(rt);
320 changed = 1;
321 }
322 return changed;
323}
324
325static int usb6fire_control_opt_coax_get(struct snd_kcontrol *kcontrol,
326 struct snd_ctl_elem_value *ucontrol)
327{
328 struct control_runtime *rt = snd_kcontrol_chip(kcontrol);
329 ucontrol->value.enumerated.item[0] = rt->opt_coax_switch;
330 return 0;
331}
332
Torsten Schenk2475b0d2011-04-04 11:50:53 +0200333static int usb6fire_control_digital_thru_put(struct snd_kcontrol *kcontrol,
334 struct snd_ctl_elem_value *ucontrol)
335{
336 struct control_runtime *rt = snd_kcontrol_chip(kcontrol);
337 int changed = 0;
338
339 if (rt->digital_thru_switch != ucontrol->value.integer.value[0]) {
340 rt->digital_thru_switch = ucontrol->value.integer.value[0];
341 usb6fire_control_streaming_update(rt);
342 changed = 1;
343 }
344 return changed;
345}
346
347static int usb6fire_control_digital_thru_get(struct snd_kcontrol *kcontrol,
348 struct snd_ctl_elem_value *ucontrol)
349{
350 struct control_runtime *rt = snd_kcontrol_chip(kcontrol);
351 ucontrol->value.integer.value[0] = rt->digital_thru_switch;
352 return 0;
353}
354
Torsten Schenkf90ffbf2012-02-22 15:21:12 +0100355static struct __devinitdata snd_kcontrol_new vol_elements[] = {
Torsten Schenkc6d43ba2011-01-24 18:45:30 +0100356 {
357 .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
Torsten Schenkf90ffbf2012-02-22 15:21:12 +0100358 .name = "Analog Playback Volume",
Torsten Schenkc6d43ba2011-01-24 18:45:30 +0100359 .index = 0,
Torsten Schenkf90ffbf2012-02-22 15:21:12 +0100360 .private_value = 0,
Torsten Schenk8e247a92012-02-22 15:20:54 +0100361 .access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
362 SNDRV_CTL_ELEM_ACCESS_TLV_READ,
Torsten Schenkf90ffbf2012-02-22 15:21:12 +0100363 .info = usb6fire_control_output_vol_info,
364 .get = usb6fire_control_output_vol_get,
365 .put = usb6fire_control_output_vol_put,
Torsten Schenk8e247a92012-02-22 15:20:54 +0100366 .tlv = { .p = tlv_output }
Torsten Schenkc6d43ba2011-01-24 18:45:30 +0100367 },
368 {
369 .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
Torsten Schenkf90ffbf2012-02-22 15:21:12 +0100370 .name = "Analog Playback Volume",
371 .index = 1,
372 .private_value = 2,
373 .access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
374 SNDRV_CTL_ELEM_ACCESS_TLV_READ,
375 .info = usb6fire_control_output_vol_info,
376 .get = usb6fire_control_output_vol_get,
377 .put = usb6fire_control_output_vol_put,
378 .tlv = { .p = tlv_output }
379 },
380 {
381 .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
382 .name = "Analog Playback Volume",
383 .index = 2,
384 .private_value = 4,
385 .access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
386 SNDRV_CTL_ELEM_ACCESS_TLV_READ,
387 .info = usb6fire_control_output_vol_info,
388 .get = usb6fire_control_output_vol_get,
389 .put = usb6fire_control_output_vol_put,
390 .tlv = { .p = tlv_output }
391 },
392 {}
393};
394
Torsten Schenkd97c7352012-02-22 15:21:23 +0100395static struct __devinitdata snd_kcontrol_new mute_elements[] = {
396 {
397 .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
398 .name = "Analog Playback Switch",
399 .index = 0,
400 .private_value = 0,
401 .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
402 .info = snd_ctl_boolean_stereo_info,
403 .get = usb6fire_control_output_mute_get,
404 .put = usb6fire_control_output_mute_put,
405 },
406 {
407 .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
408 .name = "Analog Playback Switch",
409 .index = 1,
410 .private_value = 2,
411 .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
412 .info = snd_ctl_boolean_stereo_info,
413 .get = usb6fire_control_output_mute_get,
414 .put = usb6fire_control_output_mute_put,
415 },
416 {
417 .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
418 .name = "Analog Playback Switch",
419 .index = 2,
420 .private_value = 4,
421 .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
422 .info = snd_ctl_boolean_stereo_info,
423 .get = usb6fire_control_output_mute_get,
424 .put = usb6fire_control_output_mute_put,
425 },
426 {}
427};
428
Torsten Schenkf90ffbf2012-02-22 15:21:12 +0100429static struct __devinitdata snd_kcontrol_new elements[] = {
430 {
431 .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
Torsten Schenkc6d43ba2011-01-24 18:45:30 +0100432 .name = "Line/Phono Capture Route",
433 .index = 0,
434 .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
435 .info = usb6fire_control_line_phono_info,
436 .get = usb6fire_control_line_phono_get,
437 .put = usb6fire_control_line_phono_put
438 },
439 {
440 .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
441 .name = "Opt/Coax Capture Route",
442 .index = 0,
443 .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
444 .info = usb6fire_control_opt_coax_info,
445 .get = usb6fire_control_opt_coax_get,
446 .put = usb6fire_control_opt_coax_put
447 },
Torsten Schenk2475b0d2011-04-04 11:50:53 +0200448 {
449 .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
450 .name = "Digital Thru Playback Route",
451 .index = 0,
452 .access = SNDRV_CTL_ELEM_ACCESS_READWRITE,
453 .info = snd_ctl_boolean_mono_info,
454 .get = usb6fire_control_digital_thru_get,
455 .put = usb6fire_control_digital_thru_put
456 },
Torsten Schenkc6d43ba2011-01-24 18:45:30 +0100457 {}
458};
459
Torsten Schenkf90ffbf2012-02-22 15:21:12 +0100460static int usb6fire_control_add_virtual(
461 struct control_runtime *rt,
462 struct snd_card *card,
463 char *name,
464 struct snd_kcontrol_new *elems)
465{
466 int ret;
467 int i;
468 struct snd_kcontrol *vmaster =
469 snd_ctl_make_virtual_master(name, tlv_output);
470 struct snd_kcontrol *control;
471
472 if (!vmaster)
473 return -ENOMEM;
474 ret = snd_ctl_add(card, vmaster);
475 if (ret < 0)
476 return ret;
477
478 i = 0;
479 while (elems[i].name) {
480 control = snd_ctl_new1(&elems[i], rt);
481 if (!control)
482 return -ENOMEM;
483 ret = snd_ctl_add(card, control);
484 if (ret < 0)
485 return ret;
486 ret = snd_ctl_add_slave(vmaster, control);
487 if (ret < 0)
488 return ret;
489 i++;
490 }
491 return 0;
492}
493
Torsten Schenkc6d43ba2011-01-24 18:45:30 +0100494int __devinit usb6fire_control_init(struct sfire_chip *chip)
495{
496 int i;
497 int ret;
498 struct control_runtime *rt = kzalloc(sizeof(struct control_runtime),
499 GFP_KERNEL);
500 struct comm_runtime *comm_rt = chip->comm;
501
502 if (!rt)
503 return -ENOMEM;
504
505 rt->chip = chip;
Torsten Schenk2475b0d2011-04-04 11:50:53 +0200506 rt->update_streaming = usb6fire_control_streaming_update;
507 rt->set_rate = usb6fire_control_set_rate;
508 rt->set_channels = usb6fire_control_set_channels;
Torsten Schenkc6d43ba2011-01-24 18:45:30 +0100509
510 i = 0;
511 while (init_data[i].type) {
512 comm_rt->write8(comm_rt, init_data[i].type, init_data[i].reg,
513 init_data[i].value);
514 i++;
515 }
516
517 usb6fire_control_opt_coax_update(rt);
518 usb6fire_control_line_phono_update(rt);
Torsten Schenkf90ffbf2012-02-22 15:21:12 +0100519 usb6fire_control_output_vol_update(rt);
Torsten Schenkd97c7352012-02-22 15:21:23 +0100520 usb6fire_control_output_mute_update(rt);
Torsten Schenk2475b0d2011-04-04 11:50:53 +0200521 usb6fire_control_streaming_update(rt);
Torsten Schenkc6d43ba2011-01-24 18:45:30 +0100522
Torsten Schenkf90ffbf2012-02-22 15:21:12 +0100523 ret = usb6fire_control_add_virtual(rt, chip->card,
524 "Master Playback Volume", vol_elements);
525 if (ret) {
Torsten Schenkf90ffbf2012-02-22 15:21:12 +0100526 snd_printk(KERN_ERR PREFIX "cannot add control.\n");
Torsten Schenkd97c7352012-02-22 15:21:23 +0100527 kfree(rt);
528 return ret;
529 }
530 ret = usb6fire_control_add_virtual(rt, chip->card,
531 "Master Playback Switch", mute_elements);
532 if (ret) {
533 snd_printk(KERN_ERR PREFIX "cannot add control.\n");
534 kfree(rt);
Torsten Schenkf90ffbf2012-02-22 15:21:12 +0100535 return ret;
536 }
537
Torsten Schenkc6d43ba2011-01-24 18:45:30 +0100538 i = 0;
539 while (elements[i].name) {
540 ret = snd_ctl_add(chip->card, snd_ctl_new1(&elements[i], rt));
541 if (ret < 0) {
542 kfree(rt);
543 snd_printk(KERN_ERR PREFIX "cannot add control.\n");
544 return ret;
545 }
546 i++;
547 }
548
549 chip->control = rt;
550 return 0;
551}
552
553void usb6fire_control_abort(struct sfire_chip *chip)
554{}
555
556void usb6fire_control_destroy(struct sfire_chip *chip)
557{
558 kfree(chip->control);
559 chip->control = NULL;
560}