tuntap: switch to use rtnl_dereference()
[linux-2.6/linux-acpi-2.6/ibm-acpi-2.6.git] / sound / pci / ice1712 / wm8776.c
bloba3c05fe5daf93829a0eed48204fa128d9de914fd
1 /*
2 * ALSA driver for ICEnsemble VT17xx
4 * Lowlevel functions for WM8776 codec
6 * Copyright (c) 2012 Ondrej Zary <linux@rainbow-software.org>
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 #include <linux/delay.h>
25 #include <sound/core.h>
26 #include <sound/control.h>
27 #include <sound/tlv.h>
28 #include "wm8776.h"
30 /* low-level access */
32 static void snd_wm8776_write(struct snd_wm8776 *wm, u16 addr, u16 data)
34 u8 bus_addr = addr << 1 | data >> 8; /* addr + 9th data bit */
35 u8 bus_data = data & 0xff; /* remaining 8 data bits */
37 if (addr < WM8776_REG_RESET)
38 wm->regs[addr] = data;
39 wm->ops.write(wm, bus_addr, bus_data);
42 /* register-level functions */
44 static void snd_wm8776_activate_ctl(struct snd_wm8776 *wm,
45 const char *ctl_name,
46 bool active)
48 struct snd_card *card = wm->card;
49 struct snd_kcontrol *kctl;
50 struct snd_kcontrol_volatile *vd;
51 struct snd_ctl_elem_id elem_id;
52 unsigned int index_offset;
54 memset(&elem_id, 0, sizeof(elem_id));
55 strncpy(elem_id.name, ctl_name, sizeof(elem_id.name));
56 elem_id.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
57 kctl = snd_ctl_find_id(card, &elem_id);
58 if (!kctl)
59 return;
60 index_offset = snd_ctl_get_ioff(kctl, &kctl->id);
61 vd = &kctl->vd[index_offset];
62 if (active)
63 vd->access &= ~SNDRV_CTL_ELEM_ACCESS_INACTIVE;
64 else
65 vd->access |= SNDRV_CTL_ELEM_ACCESS_INACTIVE;
66 snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_INFO, &kctl->id);
69 static void snd_wm8776_update_agc_ctl(struct snd_wm8776 *wm)
71 int i, flags_on = 0, flags_off = 0;
73 switch (wm->agc_mode) {
74 case WM8776_AGC_OFF:
75 flags_off = WM8776_FLAG_LIM | WM8776_FLAG_ALC;
76 break;
77 case WM8776_AGC_LIM:
78 flags_off = WM8776_FLAG_ALC;
79 flags_on = WM8776_FLAG_LIM;
80 break;
81 case WM8776_AGC_ALC_R:
82 case WM8776_AGC_ALC_L:
83 case WM8776_AGC_ALC_STEREO:
84 flags_off = WM8776_FLAG_LIM;
85 flags_on = WM8776_FLAG_ALC;
86 break;
89 for (i = 0; i < WM8776_CTL_COUNT; i++)
90 if (wm->ctl[i].flags & flags_off)
91 snd_wm8776_activate_ctl(wm, wm->ctl[i].name, false);
92 else if (wm->ctl[i].flags & flags_on)
93 snd_wm8776_activate_ctl(wm, wm->ctl[i].name, true);
96 static void snd_wm8776_set_agc(struct snd_wm8776 *wm, u16 agc, u16 nothing)
98 u16 alc1 = wm->regs[WM8776_REG_ALCCTRL1] & ~WM8776_ALC1_LCT_MASK;
99 u16 alc2 = wm->regs[WM8776_REG_ALCCTRL2] & ~WM8776_ALC2_LCEN;
101 switch (agc) {
102 case 0: /* Off */
103 wm->agc_mode = WM8776_AGC_OFF;
104 break;
105 case 1: /* Limiter */
106 alc2 |= WM8776_ALC2_LCEN;
107 wm->agc_mode = WM8776_AGC_LIM;
108 break;
109 case 2: /* ALC Right */
110 alc1 |= WM8776_ALC1_LCSEL_ALCR;
111 alc2 |= WM8776_ALC2_LCEN;
112 wm->agc_mode = WM8776_AGC_ALC_R;
113 break;
114 case 3: /* ALC Left */
115 alc1 |= WM8776_ALC1_LCSEL_ALCL;
116 alc2 |= WM8776_ALC2_LCEN;
117 wm->agc_mode = WM8776_AGC_ALC_L;
118 break;
119 case 4: /* ALC Stereo */
120 alc1 |= WM8776_ALC1_LCSEL_ALCSTEREO;
121 alc2 |= WM8776_ALC2_LCEN;
122 wm->agc_mode = WM8776_AGC_ALC_STEREO;
123 break;
125 snd_wm8776_write(wm, WM8776_REG_ALCCTRL1, alc1);
126 snd_wm8776_write(wm, WM8776_REG_ALCCTRL2, alc2);
127 snd_wm8776_update_agc_ctl(wm);
130 static void snd_wm8776_get_agc(struct snd_wm8776 *wm, u16 *mode, u16 *nothing)
132 *mode = wm->agc_mode;
135 /* mixer controls */
137 static const DECLARE_TLV_DB_SCALE(wm8776_hp_tlv, -7400, 100, 1);
138 static const DECLARE_TLV_DB_SCALE(wm8776_dac_tlv, -12750, 50, 1);
139 static const DECLARE_TLV_DB_SCALE(wm8776_adc_tlv, -10350, 50, 1);
140 static const DECLARE_TLV_DB_SCALE(wm8776_lct_tlv, -1600, 100, 0);
141 static const DECLARE_TLV_DB_SCALE(wm8776_maxgain_tlv, 0, 400, 0);
142 static const DECLARE_TLV_DB_SCALE(wm8776_ngth_tlv, -7800, 600, 0);
143 static const DECLARE_TLV_DB_SCALE(wm8776_maxatten_lim_tlv, -1200, 100, 0);
144 static const DECLARE_TLV_DB_SCALE(wm8776_maxatten_alc_tlv, -2100, 400, 0);
146 static struct snd_wm8776_ctl snd_wm8776_default_ctl[WM8776_CTL_COUNT] = {
147 [WM8776_CTL_DAC_VOL] = {
148 .name = "Master Playback Volume",
149 .type = SNDRV_CTL_ELEM_TYPE_INTEGER,
150 .tlv = wm8776_dac_tlv,
151 .reg1 = WM8776_REG_DACLVOL,
152 .reg2 = WM8776_REG_DACRVOL,
153 .mask1 = WM8776_DACVOL_MASK,
154 .mask2 = WM8776_DACVOL_MASK,
155 .max = 0xff,
156 .flags = WM8776_FLAG_STEREO | WM8776_FLAG_VOL_UPDATE,
158 [WM8776_CTL_DAC_SW] = {
159 .name = "Master Playback Switch",
160 .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
161 .reg1 = WM8776_REG_DACCTRL1,
162 .reg2 = WM8776_REG_DACCTRL1,
163 .mask1 = WM8776_DAC_PL_LL,
164 .mask2 = WM8776_DAC_PL_RR,
165 .flags = WM8776_FLAG_STEREO,
167 [WM8776_CTL_DAC_ZC_SW] = {
168 .name = "Master Zero Cross Detect Playback Switch",
169 .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
170 .reg1 = WM8776_REG_DACCTRL1,
171 .mask1 = WM8776_DAC_DZCEN,
173 [WM8776_CTL_HP_VOL] = {
174 .name = "Headphone Playback Volume",
175 .type = SNDRV_CTL_ELEM_TYPE_INTEGER,
176 .tlv = wm8776_hp_tlv,
177 .reg1 = WM8776_REG_HPLVOL,
178 .reg2 = WM8776_REG_HPRVOL,
179 .mask1 = WM8776_HPVOL_MASK,
180 .mask2 = WM8776_HPVOL_MASK,
181 .min = 0x2f,
182 .max = 0x7f,
183 .flags = WM8776_FLAG_STEREO | WM8776_FLAG_VOL_UPDATE,
185 [WM8776_CTL_HP_SW] = {
186 .name = "Headphone Playback Switch",
187 .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
188 .reg1 = WM8776_REG_PWRDOWN,
189 .mask1 = WM8776_PWR_HPPD,
190 .flags = WM8776_FLAG_INVERT,
192 [WM8776_CTL_HP_ZC_SW] = {
193 .name = "Headphone Zero Cross Detect Playback Switch",
194 .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
195 .reg1 = WM8776_REG_HPLVOL,
196 .reg2 = WM8776_REG_HPRVOL,
197 .mask1 = WM8776_VOL_HPZCEN,
198 .mask2 = WM8776_VOL_HPZCEN,
199 .flags = WM8776_FLAG_STEREO,
201 [WM8776_CTL_AUX_SW] = {
202 .name = "AUX Playback Switch",
203 .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
204 .reg1 = WM8776_REG_OUTMUX,
205 .mask1 = WM8776_OUTMUX_AUX,
207 [WM8776_CTL_BYPASS_SW] = {
208 .name = "Bypass Playback Switch",
209 .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
210 .reg1 = WM8776_REG_OUTMUX,
211 .mask1 = WM8776_OUTMUX_BYPASS,
213 [WM8776_CTL_DAC_IZD_SW] = {
214 .name = "Infinite Zero Detect Playback Switch",
215 .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
216 .reg1 = WM8776_REG_DACCTRL1,
217 .mask1 = WM8776_DAC_IZD,
219 [WM8776_CTL_PHASE_SW] = {
220 .name = "Phase Invert Playback Switch",
221 .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
222 .reg1 = WM8776_REG_PHASESWAP,
223 .reg2 = WM8776_REG_PHASESWAP,
224 .mask1 = WM8776_PHASE_INVERTL,
225 .mask2 = WM8776_PHASE_INVERTR,
226 .flags = WM8776_FLAG_STEREO,
228 [WM8776_CTL_DEEMPH_SW] = {
229 .name = "Deemphasis Playback Switch",
230 .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
231 .reg1 = WM8776_REG_DACCTRL2,
232 .mask1 = WM8776_DAC2_DEEMPH,
234 [WM8776_CTL_ADC_VOL] = {
235 .name = "Input Capture Volume",
236 .type = SNDRV_CTL_ELEM_TYPE_INTEGER,
237 .tlv = wm8776_adc_tlv,
238 .reg1 = WM8776_REG_ADCLVOL,
239 .reg2 = WM8776_REG_ADCRVOL,
240 .mask1 = WM8776_ADC_GAIN_MASK,
241 .mask2 = WM8776_ADC_GAIN_MASK,
242 .max = 0xff,
243 .flags = WM8776_FLAG_STEREO | WM8776_FLAG_VOL_UPDATE,
245 [WM8776_CTL_ADC_SW] = {
246 .name = "Input Capture Switch",
247 .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
248 .reg1 = WM8776_REG_ADCMUX,
249 .reg2 = WM8776_REG_ADCMUX,
250 .mask1 = WM8776_ADC_MUTEL,
251 .mask2 = WM8776_ADC_MUTER,
252 .flags = WM8776_FLAG_STEREO | WM8776_FLAG_INVERT,
254 [WM8776_CTL_INPUT1_SW] = {
255 .name = "AIN1 Capture Switch",
256 .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
257 .reg1 = WM8776_REG_ADCMUX,
258 .mask1 = WM8776_ADC_MUX_AIN1,
260 [WM8776_CTL_INPUT2_SW] = {
261 .name = "AIN2 Capture Switch",
262 .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
263 .reg1 = WM8776_REG_ADCMUX,
264 .mask1 = WM8776_ADC_MUX_AIN2,
266 [WM8776_CTL_INPUT3_SW] = {
267 .name = "AIN3 Capture Switch",
268 .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
269 .reg1 = WM8776_REG_ADCMUX,
270 .mask1 = WM8776_ADC_MUX_AIN3,
272 [WM8776_CTL_INPUT4_SW] = {
273 .name = "AIN4 Capture Switch",
274 .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
275 .reg1 = WM8776_REG_ADCMUX,
276 .mask1 = WM8776_ADC_MUX_AIN4,
278 [WM8776_CTL_INPUT5_SW] = {
279 .name = "AIN5 Capture Switch",
280 .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
281 .reg1 = WM8776_REG_ADCMUX,
282 .mask1 = WM8776_ADC_MUX_AIN5,
284 [WM8776_CTL_AGC_SEL] = {
285 .name = "AGC Select Capture Enum",
286 .type = SNDRV_CTL_ELEM_TYPE_ENUMERATED,
287 .enum_names = { "Off", "Limiter", "ALC Right", "ALC Left",
288 "ALC Stereo" },
289 .max = 5, /* .enum_names item count */
290 .set = snd_wm8776_set_agc,
291 .get = snd_wm8776_get_agc,
293 [WM8776_CTL_LIM_THR] = {
294 .name = "Limiter Threshold Capture Volume",
295 .type = SNDRV_CTL_ELEM_TYPE_INTEGER,
296 .tlv = wm8776_lct_tlv,
297 .reg1 = WM8776_REG_ALCCTRL1,
298 .mask1 = WM8776_ALC1_LCT_MASK,
299 .max = 15,
300 .flags = WM8776_FLAG_LIM,
302 [WM8776_CTL_LIM_ATK] = {
303 .name = "Limiter Attack Time Capture Enum",
304 .type = SNDRV_CTL_ELEM_TYPE_ENUMERATED,
305 .enum_names = { "0.25 ms", "0.5 ms", "1 ms", "2 ms", "4 ms",
306 "8 ms", "16 ms", "32 ms", "64 ms", "128 ms", "256 ms" },
307 .max = 11, /* .enum_names item count */
308 .reg1 = WM8776_REG_ALCCTRL3,
309 .mask1 = WM8776_ALC3_ATK_MASK,
310 .flags = WM8776_FLAG_LIM,
312 [WM8776_CTL_LIM_DCY] = {
313 .name = "Limiter Decay Time Capture Enum",
314 .type = SNDRV_CTL_ELEM_TYPE_ENUMERATED,
315 .enum_names = { "1.2 ms", "2.4 ms", "4.8 ms", "9.6 ms",
316 "19.2 ms", "38.4 ms", "76.8 ms", "154 ms", "307 ms",
317 "614 ms", "1.23 s" },
318 .max = 11, /* .enum_names item count */
319 .reg1 = WM8776_REG_ALCCTRL3,
320 .mask1 = WM8776_ALC3_DCY_MASK,
321 .flags = WM8776_FLAG_LIM,
323 [WM8776_CTL_LIM_TRANWIN] = {
324 .name = "Limiter Transient Window Capture Enum",
325 .type = SNDRV_CTL_ELEM_TYPE_ENUMERATED,
326 .enum_names = { "0 us", "62.5 us", "125 us", "250 us", "500 us",
327 "1 ms", "2 ms", "4 ms" },
328 .max = 8, /* .enum_names item count */
329 .reg1 = WM8776_REG_LIMITER,
330 .mask1 = WM8776_LIM_TRANWIN_MASK,
331 .flags = WM8776_FLAG_LIM,
333 [WM8776_CTL_LIM_MAXATTN] = {
334 .name = "Limiter Maximum Attenuation Capture Volume",
335 .type = SNDRV_CTL_ELEM_TYPE_INTEGER,
336 .tlv = wm8776_maxatten_lim_tlv,
337 .reg1 = WM8776_REG_LIMITER,
338 .mask1 = WM8776_LIM_MAXATTEN_MASK,
339 .min = 3,
340 .max = 12,
341 .flags = WM8776_FLAG_LIM | WM8776_FLAG_INVERT,
343 [WM8776_CTL_ALC_TGT] = {
344 .name = "ALC Target Level Capture Volume",
345 .type = SNDRV_CTL_ELEM_TYPE_INTEGER,
346 .tlv = wm8776_lct_tlv,
347 .reg1 = WM8776_REG_ALCCTRL1,
348 .mask1 = WM8776_ALC1_LCT_MASK,
349 .max = 15,
350 .flags = WM8776_FLAG_ALC,
352 [WM8776_CTL_ALC_ATK] = {
353 .name = "ALC Attack Time Capture Enum",
354 .type = SNDRV_CTL_ELEM_TYPE_ENUMERATED,
355 .enum_names = { "8.40 ms", "16.8 ms", "33.6 ms", "67.2 ms",
356 "134 ms", "269 ms", "538 ms", "1.08 s", "2.15 s",
357 "4.3 s", "8.6 s" },
358 .max = 11, /* .enum_names item count */
359 .reg1 = WM8776_REG_ALCCTRL3,
360 .mask1 = WM8776_ALC3_ATK_MASK,
361 .flags = WM8776_FLAG_ALC,
363 [WM8776_CTL_ALC_DCY] = {
364 .name = "ALC Decay Time Capture Enum",
365 .type = SNDRV_CTL_ELEM_TYPE_ENUMERATED,
366 .enum_names = { "33.5 ms", "67.0 ms", "134 ms", "268 ms",
367 "536 ms", "1.07 s", "2.14 s", "4.29 s", "8.58 s",
368 "17.2 s", "34.3 s" },
369 .max = 11, /* .enum_names item count */
370 .reg1 = WM8776_REG_ALCCTRL3,
371 .mask1 = WM8776_ALC3_DCY_MASK,
372 .flags = WM8776_FLAG_ALC,
374 [WM8776_CTL_ALC_MAXGAIN] = {
375 .name = "ALC Maximum Gain Capture Volume",
376 .type = SNDRV_CTL_ELEM_TYPE_INTEGER,
377 .tlv = wm8776_maxgain_tlv,
378 .reg1 = WM8776_REG_ALCCTRL1,
379 .mask1 = WM8776_ALC1_MAXGAIN_MASK,
380 .min = 1,
381 .max = 7,
382 .flags = WM8776_FLAG_ALC,
384 [WM8776_CTL_ALC_MAXATTN] = {
385 .name = "ALC Maximum Attenuation Capture Volume",
386 .type = SNDRV_CTL_ELEM_TYPE_INTEGER,
387 .tlv = wm8776_maxatten_alc_tlv,
388 .reg1 = WM8776_REG_LIMITER,
389 .mask1 = WM8776_LIM_MAXATTEN_MASK,
390 .min = 10,
391 .max = 15,
392 .flags = WM8776_FLAG_ALC | WM8776_FLAG_INVERT,
394 [WM8776_CTL_ALC_HLD] = {
395 .name = "ALC Hold Time Capture Enum",
396 .type = SNDRV_CTL_ELEM_TYPE_ENUMERATED,
397 .enum_names = { "0 ms", "2.67 ms", "5.33 ms", "10.6 ms",
398 "21.3 ms", "42.7 ms", "85.3 ms", "171 ms", "341 ms",
399 "683 ms", "1.37 s", "2.73 s", "5.46 s", "10.9 s",
400 "21.8 s", "43.7 s" },
401 .max = 16, /* .enum_names item count */
402 .reg1 = WM8776_REG_ALCCTRL2,
403 .mask1 = WM8776_ALC2_HOLD_MASK,
404 .flags = WM8776_FLAG_ALC,
406 [WM8776_CTL_NGT_SW] = {
407 .name = "Noise Gate Capture Switch",
408 .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
409 .reg1 = WM8776_REG_NOISEGATE,
410 .mask1 = WM8776_NGAT_ENABLE,
411 .flags = WM8776_FLAG_ALC,
413 [WM8776_CTL_NGT_THR] = {
414 .name = "Noise Gate Threshold Capture Volume",
415 .type = SNDRV_CTL_ELEM_TYPE_INTEGER,
416 .tlv = wm8776_ngth_tlv,
417 .reg1 = WM8776_REG_NOISEGATE,
418 .mask1 = WM8776_NGAT_THR_MASK,
419 .max = 7,
420 .flags = WM8776_FLAG_ALC,
424 /* exported functions */
426 void snd_wm8776_init(struct snd_wm8776 *wm)
428 int i;
429 static const u16 default_values[] = {
430 0x000, 0x100, 0x000,
431 0x000, 0x100, 0x000,
432 0x000, 0x090, 0x000, 0x000,
433 0x022, 0x022, 0x022,
434 0x008, 0x0cf, 0x0cf, 0x07b, 0x000,
435 0x032, 0x000, 0x0a6, 0x001, 0x001
438 memcpy(wm->ctl, snd_wm8776_default_ctl, sizeof(wm->ctl));
440 snd_wm8776_write(wm, WM8776_REG_RESET, 0x00); /* reset */
441 udelay(10);
442 /* load defaults */
443 for (i = 0; i < ARRAY_SIZE(default_values); i++)
444 snd_wm8776_write(wm, i, default_values[i]);
447 void snd_wm8776_resume(struct snd_wm8776 *wm)
449 int i;
451 for (i = 0; i < WM8776_REG_COUNT; i++)
452 snd_wm8776_write(wm, i, wm->regs[i]);
455 void snd_wm8776_set_dac_if(struct snd_wm8776 *wm, u16 dac)
457 snd_wm8776_write(wm, WM8776_REG_DACIFCTRL, dac);
460 void snd_wm8776_set_adc_if(struct snd_wm8776 *wm, u16 adc)
462 snd_wm8776_write(wm, WM8776_REG_ADCIFCTRL, adc);
465 void snd_wm8776_set_master_mode(struct snd_wm8776 *wm, u16 mode)
467 snd_wm8776_write(wm, WM8776_REG_MSTRCTRL, mode);
470 void snd_wm8776_set_power(struct snd_wm8776 *wm, u16 power)
472 snd_wm8776_write(wm, WM8776_REG_PWRDOWN, power);
475 void snd_wm8776_volume_restore(struct snd_wm8776 *wm)
477 u16 val = wm->regs[WM8776_REG_DACRVOL];
478 /* restore volume after MCLK stopped */
479 snd_wm8776_write(wm, WM8776_REG_DACRVOL, val | WM8776_VOL_UPDATE);
482 /* mixer callbacks */
484 static int snd_wm8776_volume_info(struct snd_kcontrol *kcontrol,
485 struct snd_ctl_elem_info *uinfo)
487 struct snd_wm8776 *wm = snd_kcontrol_chip(kcontrol);
488 int n = kcontrol->private_value;
490 uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
491 uinfo->count = (wm->ctl[n].flags & WM8776_FLAG_STEREO) ? 2 : 1;
492 uinfo->value.integer.min = wm->ctl[n].min;
493 uinfo->value.integer.max = wm->ctl[n].max;
495 return 0;
498 static int snd_wm8776_enum_info(struct snd_kcontrol *kcontrol,
499 struct snd_ctl_elem_info *uinfo)
501 struct snd_wm8776 *wm = snd_kcontrol_chip(kcontrol);
502 int n = kcontrol->private_value;
504 return snd_ctl_enum_info(uinfo, 1, wm->ctl[n].max,
505 wm->ctl[n].enum_names);
508 static int snd_wm8776_ctl_get(struct snd_kcontrol *kcontrol,
509 struct snd_ctl_elem_value *ucontrol)
511 struct snd_wm8776 *wm = snd_kcontrol_chip(kcontrol);
512 int n = kcontrol->private_value;
513 u16 val1, val2;
515 if (wm->ctl[n].get)
516 wm->ctl[n].get(wm, &val1, &val2);
517 else {
518 val1 = wm->regs[wm->ctl[n].reg1] & wm->ctl[n].mask1;
519 val1 >>= __ffs(wm->ctl[n].mask1);
520 if (wm->ctl[n].flags & WM8776_FLAG_STEREO) {
521 val2 = wm->regs[wm->ctl[n].reg2] & wm->ctl[n].mask2;
522 val2 >>= __ffs(wm->ctl[n].mask2);
523 if (wm->ctl[n].flags & WM8776_FLAG_VOL_UPDATE)
524 val2 &= ~WM8776_VOL_UPDATE;
527 if (wm->ctl[n].flags & WM8776_FLAG_INVERT) {
528 val1 = wm->ctl[n].max - (val1 - wm->ctl[n].min);
529 val2 = wm->ctl[n].max - (val2 - wm->ctl[n].min);
531 ucontrol->value.integer.value[0] = val1;
532 if (wm->ctl[n].flags & WM8776_FLAG_STEREO)
533 ucontrol->value.integer.value[1] = val2;
535 return 0;
538 static int snd_wm8776_ctl_put(struct snd_kcontrol *kcontrol,
539 struct snd_ctl_elem_value *ucontrol)
541 struct snd_wm8776 *wm = snd_kcontrol_chip(kcontrol);
542 int n = kcontrol->private_value;
543 u16 val, regval1, regval2;
545 /* this also works for enum because value is an union */
546 regval1 = ucontrol->value.integer.value[0];
547 regval2 = ucontrol->value.integer.value[1];
548 if (wm->ctl[n].flags & WM8776_FLAG_INVERT) {
549 regval1 = wm->ctl[n].max - (regval1 - wm->ctl[n].min);
550 regval2 = wm->ctl[n].max - (regval2 - wm->ctl[n].min);
552 if (wm->ctl[n].set)
553 wm->ctl[n].set(wm, regval1, regval2);
554 else {
555 val = wm->regs[wm->ctl[n].reg1] & ~wm->ctl[n].mask1;
556 val |= regval1 << __ffs(wm->ctl[n].mask1);
557 /* both stereo controls in one register */
558 if (wm->ctl[n].flags & WM8776_FLAG_STEREO &&
559 wm->ctl[n].reg1 == wm->ctl[n].reg2) {
560 val &= ~wm->ctl[n].mask2;
561 val |= regval2 << __ffs(wm->ctl[n].mask2);
563 snd_wm8776_write(wm, wm->ctl[n].reg1, val);
564 /* stereo controls in different registers */
565 if (wm->ctl[n].flags & WM8776_FLAG_STEREO &&
566 wm->ctl[n].reg1 != wm->ctl[n].reg2) {
567 val = wm->regs[wm->ctl[n].reg2] & ~wm->ctl[n].mask2;
568 val |= regval2 << __ffs(wm->ctl[n].mask2);
569 if (wm->ctl[n].flags & WM8776_FLAG_VOL_UPDATE)
570 val |= WM8776_VOL_UPDATE;
571 snd_wm8776_write(wm, wm->ctl[n].reg2, val);
575 return 0;
578 static int snd_wm8776_add_control(struct snd_wm8776 *wm, int num)
580 struct snd_kcontrol_new cont;
581 struct snd_kcontrol *ctl;
583 memset(&cont, 0, sizeof(cont));
584 cont.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
585 cont.private_value = num;
586 cont.name = wm->ctl[num].name;
587 cont.access = SNDRV_CTL_ELEM_ACCESS_READWRITE;
588 if (wm->ctl[num].flags & WM8776_FLAG_LIM ||
589 wm->ctl[num].flags & WM8776_FLAG_ALC)
590 cont.access |= SNDRV_CTL_ELEM_ACCESS_INACTIVE;
591 cont.tlv.p = NULL;
592 cont.get = snd_wm8776_ctl_get;
593 cont.put = snd_wm8776_ctl_put;
595 switch (wm->ctl[num].type) {
596 case SNDRV_CTL_ELEM_TYPE_INTEGER:
597 cont.info = snd_wm8776_volume_info;
598 cont.access |= SNDRV_CTL_ELEM_ACCESS_TLV_READ;
599 cont.tlv.p = wm->ctl[num].tlv;
600 break;
601 case SNDRV_CTL_ELEM_TYPE_BOOLEAN:
602 wm->ctl[num].max = 1;
603 if (wm->ctl[num].flags & WM8776_FLAG_STEREO)
604 cont.info = snd_ctl_boolean_stereo_info;
605 else
606 cont.info = snd_ctl_boolean_mono_info;
607 break;
608 case SNDRV_CTL_ELEM_TYPE_ENUMERATED:
609 cont.info = snd_wm8776_enum_info;
610 break;
611 default:
612 return -EINVAL;
614 ctl = snd_ctl_new1(&cont, wm);
615 if (!ctl)
616 return -ENOMEM;
618 return snd_ctl_add(wm->card, ctl);
621 int snd_wm8776_build_controls(struct snd_wm8776 *wm)
623 int err, i;
625 for (i = 0; i < WM8776_CTL_COUNT; i++)
626 if (wm->ctl[i].name) {
627 err = snd_wm8776_add_control(wm, i);
628 if (err < 0)
629 return err;
632 return 0;