Add the rt linux 4.1.3-rt3 as base
[kvmfornfv.git] / kernel / sound / pci / ice1712 / wm8776.c
1 /*
2  *   ALSA driver for ICEnsemble VT17xx
3  *
4  *   Lowlevel functions for WM8776 codec
5  *
6  *      Copyright (c) 2012 Ondrej Zary <linux@rainbow-software.org>
7  *
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.
12  *
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.
17  *
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
21  *
22  */
23
24 #include <linux/delay.h>
25 #include <sound/core.h>
26 #include <sound/control.h>
27 #include <sound/tlv.h>
28 #include "wm8776.h"
29
30 /* low-level access */
31
32 static void snd_wm8776_write(struct snd_wm8776 *wm, u16 addr, u16 data)
33 {
34         u8 bus_addr = addr << 1 | data >> 8;    /* addr + 9th data bit */
35         u8 bus_data = data & 0xff;              /* remaining 8 data bits */
36
37         if (addr < WM8776_REG_RESET)
38                 wm->regs[addr] = data;
39         wm->ops.write(wm, bus_addr, bus_data);
40 }
41
42 /* register-level functions */
43
44 static void snd_wm8776_activate_ctl(struct snd_wm8776 *wm,
45                                     const char *ctl_name,
46                                     bool active)
47 {
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;
53
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);
67 }
68
69 static void snd_wm8776_update_agc_ctl(struct snd_wm8776 *wm)
70 {
71         int i, flags_on = 0, flags_off = 0;
72
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;
87         }
88
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);
94 }
95
96 static void snd_wm8776_set_agc(struct snd_wm8776 *wm, u16 agc, u16 nothing)
97 {
98         u16 alc1 = wm->regs[WM8776_REG_ALCCTRL1] & ~WM8776_ALC1_LCT_MASK;
99         u16 alc2 = wm->regs[WM8776_REG_ALCCTRL2] & ~WM8776_ALC2_LCEN;
100
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;
124         }
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);
128 }
129
130 static void snd_wm8776_get_agc(struct snd_wm8776 *wm, u16 *mode, u16 *nothing)
131 {
132         *mode = wm->agc_mode;
133 }
134
135 /* mixer controls */
136
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);
145
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,
157         },
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,
166         },
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,
172         },
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,
184         },
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,
191         },
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,
200         },
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,
206         },
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,
212         },
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,
218         },
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,
227         },
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,
233         },
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,
244         },
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,
253         },
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,
259         },
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,
265         },
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,
271         },
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,
277         },
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,
283         },
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,
292         },
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,
301         },
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,
311         },
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,
322         },
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,
332         },
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,
342         },
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,
351         },
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,
362         },
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,
373         },
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,
383         },
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,
393         },
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,
405         },
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,
412         },
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,
421         },
422 };
423
424 /* exported functions */
425
426 void snd_wm8776_init(struct snd_wm8776 *wm)
427 {
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
436         };
437
438         memcpy(wm->ctl, snd_wm8776_default_ctl, sizeof(wm->ctl));
439
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]);
445 }
446
447 void snd_wm8776_resume(struct snd_wm8776 *wm)
448 {
449         int i;
450
451         for (i = 0; i < WM8776_REG_COUNT; i++)
452                 snd_wm8776_write(wm, i, wm->regs[i]);
453 }
454
455 void snd_wm8776_set_power(struct snd_wm8776 *wm, u16 power)
456 {
457         snd_wm8776_write(wm, WM8776_REG_PWRDOWN, power);
458 }
459
460 void snd_wm8776_volume_restore(struct snd_wm8776 *wm)
461 {
462         u16 val = wm->regs[WM8776_REG_DACRVOL];
463         /* restore volume after MCLK stopped */
464         snd_wm8776_write(wm, WM8776_REG_DACRVOL, val | WM8776_VOL_UPDATE);
465 }
466
467 /* mixer callbacks */
468
469 static int snd_wm8776_volume_info(struct snd_kcontrol *kcontrol,
470                                    struct snd_ctl_elem_info *uinfo)
471 {
472         struct snd_wm8776 *wm = snd_kcontrol_chip(kcontrol);
473         int n = kcontrol->private_value;
474
475         uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
476         uinfo->count = (wm->ctl[n].flags & WM8776_FLAG_STEREO) ? 2 : 1;
477         uinfo->value.integer.min = wm->ctl[n].min;
478         uinfo->value.integer.max = wm->ctl[n].max;
479
480         return 0;
481 }
482
483 static int snd_wm8776_enum_info(struct snd_kcontrol *kcontrol,
484                                       struct snd_ctl_elem_info *uinfo)
485 {
486         struct snd_wm8776 *wm = snd_kcontrol_chip(kcontrol);
487         int n = kcontrol->private_value;
488
489         return snd_ctl_enum_info(uinfo, 1, wm->ctl[n].max,
490                                                 wm->ctl[n].enum_names);
491 }
492
493 static int snd_wm8776_ctl_get(struct snd_kcontrol *kcontrol,
494                                   struct snd_ctl_elem_value *ucontrol)
495 {
496         struct snd_wm8776 *wm = snd_kcontrol_chip(kcontrol);
497         int n = kcontrol->private_value;
498         u16 val1, val2;
499
500         if (wm->ctl[n].get)
501                 wm->ctl[n].get(wm, &val1, &val2);
502         else {
503                 val1 = wm->regs[wm->ctl[n].reg1] & wm->ctl[n].mask1;
504                 val1 >>= __ffs(wm->ctl[n].mask1);
505                 if (wm->ctl[n].flags & WM8776_FLAG_STEREO) {
506                         val2 = wm->regs[wm->ctl[n].reg2] & wm->ctl[n].mask2;
507                         val2 >>= __ffs(wm->ctl[n].mask2);
508                         if (wm->ctl[n].flags & WM8776_FLAG_VOL_UPDATE)
509                                 val2 &= ~WM8776_VOL_UPDATE;
510                 }
511         }
512         if (wm->ctl[n].flags & WM8776_FLAG_INVERT) {
513                 val1 = wm->ctl[n].max - (val1 - wm->ctl[n].min);
514                 if (wm->ctl[n].flags & WM8776_FLAG_STEREO)
515                         val2 = wm->ctl[n].max - (val2 - wm->ctl[n].min);
516         }
517         ucontrol->value.integer.value[0] = val1;
518         if (wm->ctl[n].flags & WM8776_FLAG_STEREO)
519                 ucontrol->value.integer.value[1] = val2;
520
521         return 0;
522 }
523
524 static int snd_wm8776_ctl_put(struct snd_kcontrol *kcontrol,
525                                   struct snd_ctl_elem_value *ucontrol)
526 {
527         struct snd_wm8776 *wm = snd_kcontrol_chip(kcontrol);
528         int n = kcontrol->private_value;
529         u16 val, regval1, regval2;
530
531         /* this also works for enum because value is an union */
532         regval1 = ucontrol->value.integer.value[0];
533         regval2 = ucontrol->value.integer.value[1];
534         if (wm->ctl[n].flags & WM8776_FLAG_INVERT) {
535                 regval1 = wm->ctl[n].max - (regval1 - wm->ctl[n].min);
536                 regval2 = wm->ctl[n].max - (regval2 - wm->ctl[n].min);
537         }
538         if (wm->ctl[n].set)
539                 wm->ctl[n].set(wm, regval1, regval2);
540         else {
541                 val = wm->regs[wm->ctl[n].reg1] & ~wm->ctl[n].mask1;
542                 val |= regval1 << __ffs(wm->ctl[n].mask1);
543                 /* both stereo controls in one register */
544                 if (wm->ctl[n].flags & WM8776_FLAG_STEREO &&
545                                 wm->ctl[n].reg1 == wm->ctl[n].reg2) {
546                         val &= ~wm->ctl[n].mask2;
547                         val |= regval2 << __ffs(wm->ctl[n].mask2);
548                 }
549                 snd_wm8776_write(wm, wm->ctl[n].reg1, val);
550                 /* stereo controls in different registers */
551                 if (wm->ctl[n].flags & WM8776_FLAG_STEREO &&
552                                 wm->ctl[n].reg1 != wm->ctl[n].reg2) {
553                         val = wm->regs[wm->ctl[n].reg2] & ~wm->ctl[n].mask2;
554                         val |= regval2 << __ffs(wm->ctl[n].mask2);
555                         if (wm->ctl[n].flags & WM8776_FLAG_VOL_UPDATE)
556                                 val |= WM8776_VOL_UPDATE;
557                         snd_wm8776_write(wm, wm->ctl[n].reg2, val);
558                 }
559         }
560
561         return 0;
562 }
563
564 static int snd_wm8776_add_control(struct snd_wm8776 *wm, int num)
565 {
566         struct snd_kcontrol_new cont;
567         struct snd_kcontrol *ctl;
568
569         memset(&cont, 0, sizeof(cont));
570         cont.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
571         cont.private_value = num;
572         cont.name = wm->ctl[num].name;
573         cont.access = SNDRV_CTL_ELEM_ACCESS_READWRITE;
574         if (wm->ctl[num].flags & WM8776_FLAG_LIM ||
575             wm->ctl[num].flags & WM8776_FLAG_ALC)
576                 cont.access |= SNDRV_CTL_ELEM_ACCESS_INACTIVE;
577         cont.tlv.p = NULL;
578         cont.get = snd_wm8776_ctl_get;
579         cont.put = snd_wm8776_ctl_put;
580
581         switch (wm->ctl[num].type) {
582         case SNDRV_CTL_ELEM_TYPE_INTEGER:
583                 cont.info = snd_wm8776_volume_info;
584                 cont.access |= SNDRV_CTL_ELEM_ACCESS_TLV_READ;
585                 cont.tlv.p = wm->ctl[num].tlv;
586                 break;
587         case SNDRV_CTL_ELEM_TYPE_BOOLEAN:
588                 wm->ctl[num].max = 1;
589                 if (wm->ctl[num].flags & WM8776_FLAG_STEREO)
590                         cont.info = snd_ctl_boolean_stereo_info;
591                 else
592                         cont.info = snd_ctl_boolean_mono_info;
593                 break;
594         case SNDRV_CTL_ELEM_TYPE_ENUMERATED:
595                 cont.info = snd_wm8776_enum_info;
596                 break;
597         default:
598                 return -EINVAL;
599         }
600         ctl = snd_ctl_new1(&cont, wm);
601         if (!ctl)
602                 return -ENOMEM;
603
604         return snd_ctl_add(wm->card, ctl);
605 }
606
607 int snd_wm8776_build_controls(struct snd_wm8776 *wm)
608 {
609         int err, i;
610
611         for (i = 0; i < WM8776_CTL_COUNT; i++)
612                 if (wm->ctl[i].name) {
613                         err = snd_wm8776_add_control(wm, i);
614                         if (err < 0)
615                                 return err;
616                 }
617
618         return 0;
619 }