diff options
Diffstat (limited to 'sound/usb/6fire/control.c')
-rw-r--r-- | sound/usb/6fire/control.c | 380 |
1 files changed, 380 insertions, 0 deletions
diff --git a/sound/usb/6fire/control.c b/sound/usb/6fire/control.c new file mode 100644 index 000000000000..ac828eff1a63 --- /dev/null +++ b/sound/usb/6fire/control.c | |||
@@ -0,0 +1,380 @@ | |||
1 | /* | ||
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 | ||
8 | * Version: 0.3.0 | ||
9 | * Copyright: (C) Torsten Schenk | ||
10 | * | ||
11 | * This program is free software; you can redistribute it and/or modify | ||
12 | * it under the terms of the GNU General Public License as published by | ||
13 | * the Free Software Foundation; either version 2 of the License, or | ||
14 | * (at your option) any later version. | ||
15 | */ | ||
16 | |||
17 | #include <linux/interrupt.h> | ||
18 | #include <sound/control.h> | ||
19 | |||
20 | #include "control.h" | ||
21 | #include "comm.h" | ||
22 | #include "chip.h" | ||
23 | |||
24 | static char *opt_coax_texts[2] = { "Optical", "Coax" }; | ||
25 | static char *line_phono_texts[2] = { "Line", "Phono" }; | ||
26 | |||
27 | /* | ||
28 | * calculated with $value\[i\] = 128 \cdot sqrt[3]{\frac{i}{128}}$ | ||
29 | * this is done because the linear values cause rapid degredation | ||
30 | * of volume in the uppermost region. | ||
31 | */ | ||
32 | static const u8 log_volume_table[128] = { | ||
33 | 0x00, 0x19, 0x20, 0x24, 0x28, 0x2b, 0x2e, 0x30, 0x32, 0x34, | ||
34 | 0x36, 0x38, 0x3a, 0x3b, 0x3d, 0x3e, 0x40, 0x41, 0x42, 0x43, | ||
35 | 0x44, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, | ||
36 | 0x4e, 0x4f, 0x50, 0x51, 0x52, 0x53, 0x53, 0x54, 0x55, 0x56, | ||
37 | 0x56, 0x57, 0x58, 0x58, 0x59, 0x5a, 0x5b, 0x5b, 0x5c, 0x5c, | ||
38 | 0x5d, 0x5e, 0x5e, 0x5f, 0x60, 0x60, 0x61, 0x61, 0x62, 0x62, | ||
39 | 0x63, 0x63, 0x64, 0x65, 0x65, 0x66, 0x66, 0x67, 0x67, 0x68, | ||
40 | 0x68, 0x69, 0x69, 0x6a, 0x6a, 0x6b, 0x6b, 0x6c, 0x6c, 0x6c, | ||
41 | 0x6d, 0x6d, 0x6e, 0x6e, 0x6f, 0x6f, 0x70, 0x70, 0x70, 0x71, | ||
42 | 0x71, 0x72, 0x72, 0x73, 0x73, 0x73, 0x74, 0x74, 0x75, 0x75, | ||
43 | 0x75, 0x76, 0x76, 0x77, 0x77, 0x77, 0x78, 0x78, 0x78, 0x79, | ||
44 | 0x79, 0x7a, 0x7a, 0x7a, 0x7b, 0x7b, 0x7b, 0x7c, 0x7c, 0x7c, | ||
45 | 0x7d, 0x7d, 0x7d, 0x7e, 0x7e, 0x7e, 0x7f, 0x7f }; | ||
46 | |||
47 | /* | ||
48 | * data that needs to be sent to device. sets up card internal stuff. | ||
49 | * values dumped from windows driver and filtered by trial'n'error. | ||
50 | */ | ||
51 | static const struct { | ||
52 | u8 type; | ||
53 | u8 reg; | ||
54 | u8 value; | ||
55 | } | ||
56 | init_data[] = { | ||
57 | { 0x22, 0x00, 0x00 }, { 0x20, 0x00, 0x08 }, { 0x22, 0x01, 0x01 }, | ||
58 | { 0x20, 0x01, 0x08 }, { 0x22, 0x02, 0x00 }, { 0x20, 0x02, 0x08 }, | ||
59 | { 0x22, 0x03, 0x00 }, { 0x20, 0x03, 0x08 }, { 0x22, 0x04, 0x00 }, | ||
60 | { 0x20, 0x04, 0x08 }, { 0x22, 0x05, 0x01 }, { 0x20, 0x05, 0x08 }, | ||
61 | { 0x22, 0x04, 0x01 }, { 0x12, 0x04, 0x00 }, { 0x12, 0x05, 0x00 }, | ||
62 | { 0x12, 0x0d, 0x78 }, { 0x12, 0x21, 0x82 }, { 0x12, 0x22, 0x80 }, | ||
63 | { 0x12, 0x23, 0x00 }, { 0x12, 0x06, 0x02 }, { 0x12, 0x03, 0x00 }, | ||
64 | { 0x12, 0x02, 0x00 }, { 0x22, 0x03, 0x01 }, | ||
65 | { 0 } /* TERMINATING ENTRY */ | ||
66 | }; | ||
67 | |||
68 | static const int rates_altsetting[] = { 1, 1, 2, 2, 3, 3 }; | ||
69 | /* values to write to soundcard register for all samplerates */ | ||
70 | static const u16 rates_6fire_vl[] = {0x00, 0x01, 0x00, 0x01, 0x00, 0x01}; | ||
71 | static const u16 rates_6fire_vh[] = {0x11, 0x11, 0x10, 0x10, 0x00, 0x00}; | ||
72 | |||
73 | enum { | ||
74 | DIGITAL_THRU_ONLY_SAMPLERATE = 3 | ||
75 | }; | ||
76 | |||
77 | static void usb6fire_control_master_vol_update(struct control_runtime *rt) | ||
78 | { | ||
79 | struct comm_runtime *comm_rt = rt->chip->comm; | ||
80 | if (comm_rt) { | ||
81 | /* set volume */ | ||
82 | comm_rt->write8(comm_rt, 0x12, 0x0f, 0x7f - | ||
83 | log_volume_table[rt->master_vol]); | ||
84 | /* unmute */ | ||
85 | comm_rt->write8(comm_rt, 0x12, 0x0e, 0x00); | ||
86 | } | ||
87 | } | ||
88 | |||
89 | static void usb6fire_control_line_phono_update(struct control_runtime *rt) | ||
90 | { | ||
91 | struct comm_runtime *comm_rt = rt->chip->comm; | ||
92 | if (comm_rt) { | ||
93 | comm_rt->write8(comm_rt, 0x22, 0x02, rt->line_phono_switch); | ||
94 | comm_rt->write8(comm_rt, 0x21, 0x02, rt->line_phono_switch); | ||
95 | } | ||
96 | } | ||
97 | |||
98 | static void usb6fire_control_opt_coax_update(struct control_runtime *rt) | ||
99 | { | ||
100 | struct comm_runtime *comm_rt = rt->chip->comm; | ||
101 | if (comm_rt) { | ||
102 | comm_rt->write8(comm_rt, 0x22, 0x00, rt->opt_coax_switch); | ||
103 | comm_rt->write8(comm_rt, 0x21, 0x00, rt->opt_coax_switch); | ||
104 | } | ||
105 | } | ||
106 | |||
107 | static int usb6fire_control_set_rate(struct control_runtime *rt, int rate) | ||
108 | { | ||
109 | int ret; | ||
110 | struct usb_device *device = rt->chip->dev; | ||
111 | struct comm_runtime *comm_rt = rt->chip->comm; | ||
112 | |||
113 | if (rate < 0 || rate >= CONTROL_N_RATES) | ||
114 | return -EINVAL; | ||
115 | |||
116 | ret = usb_set_interface(device, 1, rates_altsetting[rate]); | ||
117 | if (ret < 0) | ||
118 | return ret; | ||
119 | |||
120 | /* set soundcard clock */ | ||
121 | ret = comm_rt->write16(comm_rt, 0x02, 0x01, rates_6fire_vl[rate], | ||
122 | rates_6fire_vh[rate]); | ||
123 | if (ret < 0) | ||
124 | return ret; | ||
125 | |||
126 | return 0; | ||
127 | } | ||
128 | |||
129 | static int usb6fire_control_set_channels( | ||
130 | struct control_runtime *rt, int n_analog_out, | ||
131 | int n_analog_in, bool spdif_out, bool spdif_in) | ||
132 | { | ||
133 | int ret; | ||
134 | struct comm_runtime *comm_rt = rt->chip->comm; | ||
135 | |||
136 | /* enable analog inputs and outputs | ||
137 | * (one bit per stereo-channel) */ | ||
138 | ret = comm_rt->write16(comm_rt, 0x02, 0x02, | ||
139 | (1 << (n_analog_out / 2)) - 1, | ||
140 | (1 << (n_analog_in / 2)) - 1); | ||
141 | if (ret < 0) | ||
142 | return ret; | ||
143 | |||
144 | /* disable digital inputs and outputs */ | ||
145 | /* TODO: use spdif_x to enable/disable digital channels */ | ||
146 | ret = comm_rt->write16(comm_rt, 0x02, 0x03, 0x00, 0x00); | ||
147 | if (ret < 0) | ||
148 | return ret; | ||
149 | |||
150 | return 0; | ||
151 | } | ||
152 | |||
153 | static int usb6fire_control_streaming_update(struct control_runtime *rt) | ||
154 | { | ||
155 | struct comm_runtime *comm_rt = rt->chip->comm; | ||
156 | |||
157 | if (comm_rt) { | ||
158 | if (!rt->usb_streaming && rt->digital_thru_switch) | ||
159 | usb6fire_control_set_rate(rt, | ||
160 | DIGITAL_THRU_ONLY_SAMPLERATE); | ||
161 | return comm_rt->write16(comm_rt, 0x02, 0x00, 0x00, | ||
162 | (rt->usb_streaming ? 0x01 : 0x00) | | ||
163 | (rt->digital_thru_switch ? 0x08 : 0x00)); | ||
164 | } | ||
165 | return -EINVAL; | ||
166 | } | ||
167 | |||
168 | static int usb6fire_control_master_vol_info(struct snd_kcontrol *kcontrol, | ||
169 | struct snd_ctl_elem_info *uinfo) | ||
170 | { | ||
171 | uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; | ||
172 | uinfo->count = 1; | ||
173 | uinfo->value.integer.min = 0; | ||
174 | uinfo->value.integer.max = 127; | ||
175 | return 0; | ||
176 | } | ||
177 | |||
178 | static int usb6fire_control_master_vol_put(struct snd_kcontrol *kcontrol, | ||
179 | struct snd_ctl_elem_value *ucontrol) | ||
180 | { | ||
181 | struct control_runtime *rt = snd_kcontrol_chip(kcontrol); | ||
182 | int changed = 0; | ||
183 | if (rt->master_vol != ucontrol->value.integer.value[0]) { | ||
184 | rt->master_vol = ucontrol->value.integer.value[0]; | ||
185 | usb6fire_control_master_vol_update(rt); | ||
186 | changed = 1; | ||
187 | } | ||
188 | return changed; | ||
189 | } | ||
190 | |||
191 | static int usb6fire_control_master_vol_get(struct snd_kcontrol *kcontrol, | ||
192 | struct snd_ctl_elem_value *ucontrol) | ||
193 | { | ||
194 | struct control_runtime *rt = snd_kcontrol_chip(kcontrol); | ||
195 | ucontrol->value.integer.value[0] = rt->master_vol; | ||
196 | return 0; | ||
197 | } | ||
198 | |||
199 | static int usb6fire_control_line_phono_info(struct snd_kcontrol *kcontrol, | ||
200 | struct snd_ctl_elem_info *uinfo) | ||
201 | { | ||
202 | uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; | ||
203 | uinfo->count = 1; | ||
204 | uinfo->value.enumerated.items = 2; | ||
205 | if (uinfo->value.enumerated.item > 1) | ||
206 | uinfo->value.enumerated.item = 1; | ||
207 | strcpy(uinfo->value.enumerated.name, | ||
208 | line_phono_texts[uinfo->value.enumerated.item]); | ||
209 | return 0; | ||
210 | } | ||
211 | |||
212 | static int usb6fire_control_line_phono_put(struct snd_kcontrol *kcontrol, | ||
213 | struct snd_ctl_elem_value *ucontrol) | ||
214 | { | ||
215 | struct control_runtime *rt = snd_kcontrol_chip(kcontrol); | ||
216 | int changed = 0; | ||
217 | if (rt->line_phono_switch != ucontrol->value.integer.value[0]) { | ||
218 | rt->line_phono_switch = ucontrol->value.integer.value[0]; | ||
219 | usb6fire_control_line_phono_update(rt); | ||
220 | changed = 1; | ||
221 | } | ||
222 | return changed; | ||
223 | } | ||
224 | |||
225 | static int usb6fire_control_line_phono_get(struct snd_kcontrol *kcontrol, | ||
226 | struct snd_ctl_elem_value *ucontrol) | ||
227 | { | ||
228 | struct control_runtime *rt = snd_kcontrol_chip(kcontrol); | ||
229 | ucontrol->value.integer.value[0] = rt->line_phono_switch; | ||
230 | return 0; | ||
231 | } | ||
232 | |||
233 | static int usb6fire_control_opt_coax_info(struct snd_kcontrol *kcontrol, | ||
234 | struct snd_ctl_elem_info *uinfo) | ||
235 | { | ||
236 | uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; | ||
237 | uinfo->count = 1; | ||
238 | uinfo->value.enumerated.items = 2; | ||
239 | if (uinfo->value.enumerated.item > 1) | ||
240 | uinfo->value.enumerated.item = 1; | ||
241 | strcpy(uinfo->value.enumerated.name, | ||
242 | opt_coax_texts[uinfo->value.enumerated.item]); | ||
243 | return 0; | ||
244 | } | ||
245 | |||
246 | static int usb6fire_control_opt_coax_put(struct snd_kcontrol *kcontrol, | ||
247 | struct snd_ctl_elem_value *ucontrol) | ||
248 | { | ||
249 | struct control_runtime *rt = snd_kcontrol_chip(kcontrol); | ||
250 | int changed = 0; | ||
251 | |||
252 | if (rt->opt_coax_switch != ucontrol->value.enumerated.item[0]) { | ||
253 | rt->opt_coax_switch = ucontrol->value.enumerated.item[0]; | ||
254 | usb6fire_control_opt_coax_update(rt); | ||
255 | changed = 1; | ||
256 | } | ||
257 | return changed; | ||
258 | } | ||
259 | |||
260 | static int usb6fire_control_opt_coax_get(struct snd_kcontrol *kcontrol, | ||
261 | struct snd_ctl_elem_value *ucontrol) | ||
262 | { | ||
263 | struct control_runtime *rt = snd_kcontrol_chip(kcontrol); | ||
264 | ucontrol->value.enumerated.item[0] = rt->opt_coax_switch; | ||
265 | return 0; | ||
266 | } | ||
267 | |||
268 | static int usb6fire_control_digital_thru_put(struct snd_kcontrol *kcontrol, | ||
269 | struct snd_ctl_elem_value *ucontrol) | ||
270 | { | ||
271 | struct control_runtime *rt = snd_kcontrol_chip(kcontrol); | ||
272 | int changed = 0; | ||
273 | |||
274 | if (rt->digital_thru_switch != ucontrol->value.integer.value[0]) { | ||
275 | rt->digital_thru_switch = ucontrol->value.integer.value[0]; | ||
276 | usb6fire_control_streaming_update(rt); | ||
277 | changed = 1; | ||
278 | } | ||
279 | return changed; | ||
280 | } | ||
281 | |||
282 | static int usb6fire_control_digital_thru_get(struct snd_kcontrol *kcontrol, | ||
283 | struct snd_ctl_elem_value *ucontrol) | ||
284 | { | ||
285 | struct control_runtime *rt = snd_kcontrol_chip(kcontrol); | ||
286 | ucontrol->value.integer.value[0] = rt->digital_thru_switch; | ||
287 | return 0; | ||
288 | } | ||
289 | |||
290 | static struct __devinitdata snd_kcontrol_new elements[] = { | ||
291 | { | ||
292 | .iface = SNDRV_CTL_ELEM_IFACE_MIXER, | ||
293 | .name = "Master Playback Volume", | ||
294 | .index = 0, | ||
295 | .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, | ||
296 | .info = usb6fire_control_master_vol_info, | ||
297 | .get = usb6fire_control_master_vol_get, | ||
298 | .put = usb6fire_control_master_vol_put | ||
299 | }, | ||
300 | { | ||
301 | .iface = SNDRV_CTL_ELEM_IFACE_MIXER, | ||
302 | .name = "Line/Phono Capture Route", | ||
303 | .index = 0, | ||
304 | .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, | ||
305 | .info = usb6fire_control_line_phono_info, | ||
306 | .get = usb6fire_control_line_phono_get, | ||
307 | .put = usb6fire_control_line_phono_put | ||
308 | }, | ||
309 | { | ||
310 | .iface = SNDRV_CTL_ELEM_IFACE_MIXER, | ||
311 | .name = "Opt/Coax Capture Route", | ||
312 | .index = 0, | ||
313 | .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, | ||
314 | .info = usb6fire_control_opt_coax_info, | ||
315 | .get = usb6fire_control_opt_coax_get, | ||
316 | .put = usb6fire_control_opt_coax_put | ||
317 | }, | ||
318 | { | ||
319 | .iface = SNDRV_CTL_ELEM_IFACE_MIXER, | ||
320 | .name = "Digital Thru Playback Route", | ||
321 | .index = 0, | ||
322 | .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, | ||
323 | .info = snd_ctl_boolean_mono_info, | ||
324 | .get = usb6fire_control_digital_thru_get, | ||
325 | .put = usb6fire_control_digital_thru_put | ||
326 | }, | ||
327 | {} | ||
328 | }; | ||
329 | |||
330 | int __devinit usb6fire_control_init(struct sfire_chip *chip) | ||
331 | { | ||
332 | int i; | ||
333 | int ret; | ||
334 | struct control_runtime *rt = kzalloc(sizeof(struct control_runtime), | ||
335 | GFP_KERNEL); | ||
336 | struct comm_runtime *comm_rt = chip->comm; | ||
337 | |||
338 | if (!rt) | ||
339 | return -ENOMEM; | ||
340 | |||
341 | rt->chip = chip; | ||
342 | rt->update_streaming = usb6fire_control_streaming_update; | ||
343 | rt->set_rate = usb6fire_control_set_rate; | ||
344 | rt->set_channels = usb6fire_control_set_channels; | ||
345 | |||
346 | i = 0; | ||
347 | while (init_data[i].type) { | ||
348 | comm_rt->write8(comm_rt, init_data[i].type, init_data[i].reg, | ||
349 | init_data[i].value); | ||
350 | i++; | ||
351 | } | ||
352 | |||
353 | usb6fire_control_opt_coax_update(rt); | ||
354 | usb6fire_control_line_phono_update(rt); | ||
355 | usb6fire_control_master_vol_update(rt); | ||
356 | usb6fire_control_streaming_update(rt); | ||
357 | |||
358 | i = 0; | ||
359 | while (elements[i].name) { | ||
360 | ret = snd_ctl_add(chip->card, snd_ctl_new1(&elements[i], rt)); | ||
361 | if (ret < 0) { | ||
362 | kfree(rt); | ||
363 | snd_printk(KERN_ERR PREFIX "cannot add control.\n"); | ||
364 | return ret; | ||
365 | } | ||
366 | i++; | ||
367 | } | ||
368 | |||
369 | chip->control = rt; | ||
370 | return 0; | ||
371 | } | ||
372 | |||
373 | void usb6fire_control_abort(struct sfire_chip *chip) | ||
374 | {} | ||
375 | |||
376 | void usb6fire_control_destroy(struct sfire_chip *chip) | ||
377 | { | ||
378 | kfree(chip->control); | ||
379 | chip->control = NULL; | ||
380 | } | ||