Merge branch 'for-3.18-fixes' of git://git.kernel.org/pub/scm/linux/kernel/git/tj...
[linux/fpc-iii.git] / sound / pci / ice1712 / wm8776.c
blobe66c0da62014bfd75453770a2fdc8ddec106e827
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 strlcpy(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 if (wm->ctl[n].flags & WM8776_FLAG_STEREO)
530 val2 = wm->ctl[n].max - (val2 - wm->ctl[n].min);
532 ucontrol->value.integer.value[0] = val1;
533 if (wm->ctl[n].flags & WM8776_FLAG_STEREO)
534 ucontrol->value.integer.value[1] = val2;
536 return 0;
539 static int snd_wm8776_ctl_put(struct snd_kcontrol *kcontrol,
540 struct snd_ctl_elem_value *ucontrol)
542 struct snd_wm8776 *wm = snd_kcontrol_chip(kcontrol);
543 int n = kcontrol->private_value;
544 u16 val, regval1, regval2;
546 /* this also works for enum because value is an union */
547 regval1 = ucontrol->value.integer.value[0];
548 regval2 = ucontrol->value.integer.value[1];
549 if (wm->ctl[n].flags & WM8776_FLAG_INVERT) {
550 regval1 = wm->ctl[n].max - (regval1 - wm->ctl[n].min);
551 regval2 = wm->ctl[n].max - (regval2 - wm->ctl[n].min);
553 if (wm->ctl[n].set)
554 wm->ctl[n].set(wm, regval1, regval2);
555 else {
556 val = wm->regs[wm->ctl[n].reg1] & ~wm->ctl[n].mask1;
557 val |= regval1 << __ffs(wm->ctl[n].mask1);
558 /* both stereo controls in one register */
559 if (wm->ctl[n].flags & WM8776_FLAG_STEREO &&
560 wm->ctl[n].reg1 == wm->ctl[n].reg2) {
561 val &= ~wm->ctl[n].mask2;
562 val |= regval2 << __ffs(wm->ctl[n].mask2);
564 snd_wm8776_write(wm, wm->ctl[n].reg1, val);
565 /* stereo controls in different registers */
566 if (wm->ctl[n].flags & WM8776_FLAG_STEREO &&
567 wm->ctl[n].reg1 != wm->ctl[n].reg2) {
568 val = wm->regs[wm->ctl[n].reg2] & ~wm->ctl[n].mask2;
569 val |= regval2 << __ffs(wm->ctl[n].mask2);
570 if (wm->ctl[n].flags & WM8776_FLAG_VOL_UPDATE)
571 val |= WM8776_VOL_UPDATE;
572 snd_wm8776_write(wm, wm->ctl[n].reg2, val);
576 return 0;
579 static int snd_wm8776_add_control(struct snd_wm8776 *wm, int num)
581 struct snd_kcontrol_new cont;
582 struct snd_kcontrol *ctl;
584 memset(&cont, 0, sizeof(cont));
585 cont.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
586 cont.private_value = num;
587 cont.name = wm->ctl[num].name;
588 cont.access = SNDRV_CTL_ELEM_ACCESS_READWRITE;
589 if (wm->ctl[num].flags & WM8776_FLAG_LIM ||
590 wm->ctl[num].flags & WM8776_FLAG_ALC)
591 cont.access |= SNDRV_CTL_ELEM_ACCESS_INACTIVE;
592 cont.tlv.p = NULL;
593 cont.get = snd_wm8776_ctl_get;
594 cont.put = snd_wm8776_ctl_put;
596 switch (wm->ctl[num].type) {
597 case SNDRV_CTL_ELEM_TYPE_INTEGER:
598 cont.info = snd_wm8776_volume_info;
599 cont.access |= SNDRV_CTL_ELEM_ACCESS_TLV_READ;
600 cont.tlv.p = wm->ctl[num].tlv;
601 break;
602 case SNDRV_CTL_ELEM_TYPE_BOOLEAN:
603 wm->ctl[num].max = 1;
604 if (wm->ctl[num].flags & WM8776_FLAG_STEREO)
605 cont.info = snd_ctl_boolean_stereo_info;
606 else
607 cont.info = snd_ctl_boolean_mono_info;
608 break;
609 case SNDRV_CTL_ELEM_TYPE_ENUMERATED:
610 cont.info = snd_wm8776_enum_info;
611 break;
612 default:
613 return -EINVAL;
615 ctl = snd_ctl_new1(&cont, wm);
616 if (!ctl)
617 return -ENOMEM;
619 return snd_ctl_add(wm->card, ctl);
622 int snd_wm8776_build_controls(struct snd_wm8776 *wm)
624 int err, i;
626 for (i = 0; i < WM8776_CTL_COUNT; i++)
627 if (wm->ctl[i].name) {
628 err = snd_wm8776_add_control(wm, i);
629 if (err < 0)
630 return err;
633 return 0;