xref: /openbmc/linux/sound/pci/ice1712/wm8776.c (revision 4da722ca)
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 a 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 }
620