WIP FPC-III support
[linux/fpc-iii.git] / sound / pci / ice1712 / wm8776.c
blobd96008df880d62f97aea4a96e47c0ba6ac402bf4
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3 * ALSA driver for ICEnsemble VT17xx
5 * Lowlevel functions for WM8776 codec
7 * Copyright (c) 2012 Ondrej Zary <linux@rainbow-software.org>
8 */
10 #include <linux/delay.h>
11 #include <sound/core.h>
12 #include <sound/control.h>
13 #include <sound/tlv.h>
14 #include "wm8776.h"
16 /* low-level access */
18 static void snd_wm8776_write(struct snd_wm8776 *wm, u16 addr, u16 data)
20 u8 bus_addr = addr << 1 | data >> 8; /* addr + 9th data bit */
21 u8 bus_data = data & 0xff; /* remaining 8 data bits */
23 if (addr < WM8776_REG_RESET)
24 wm->regs[addr] = data;
25 wm->ops.write(wm, bus_addr, bus_data);
28 /* register-level functions */
30 static void snd_wm8776_activate_ctl(struct snd_wm8776 *wm,
31 const char *ctl_name,
32 bool active)
34 struct snd_card *card = wm->card;
35 struct snd_kcontrol *kctl;
36 struct snd_kcontrol_volatile *vd;
37 struct snd_ctl_elem_id elem_id;
38 unsigned int index_offset;
40 memset(&elem_id, 0, sizeof(elem_id));
41 strlcpy(elem_id.name, ctl_name, sizeof(elem_id.name));
42 elem_id.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
43 kctl = snd_ctl_find_id(card, &elem_id);
44 if (!kctl)
45 return;
46 index_offset = snd_ctl_get_ioff(kctl, &kctl->id);
47 vd = &kctl->vd[index_offset];
48 if (active)
49 vd->access &= ~SNDRV_CTL_ELEM_ACCESS_INACTIVE;
50 else
51 vd->access |= SNDRV_CTL_ELEM_ACCESS_INACTIVE;
52 snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_INFO, &kctl->id);
55 static void snd_wm8776_update_agc_ctl(struct snd_wm8776 *wm)
57 int i, flags_on = 0, flags_off = 0;
59 switch (wm->agc_mode) {
60 case WM8776_AGC_OFF:
61 flags_off = WM8776_FLAG_LIM | WM8776_FLAG_ALC;
62 break;
63 case WM8776_AGC_LIM:
64 flags_off = WM8776_FLAG_ALC;
65 flags_on = WM8776_FLAG_LIM;
66 break;
67 case WM8776_AGC_ALC_R:
68 case WM8776_AGC_ALC_L:
69 case WM8776_AGC_ALC_STEREO:
70 flags_off = WM8776_FLAG_LIM;
71 flags_on = WM8776_FLAG_ALC;
72 break;
75 for (i = 0; i < WM8776_CTL_COUNT; i++)
76 if (wm->ctl[i].flags & flags_off)
77 snd_wm8776_activate_ctl(wm, wm->ctl[i].name, false);
78 else if (wm->ctl[i].flags & flags_on)
79 snd_wm8776_activate_ctl(wm, wm->ctl[i].name, true);
82 static void snd_wm8776_set_agc(struct snd_wm8776 *wm, u16 agc, u16 nothing)
84 u16 alc1 = wm->regs[WM8776_REG_ALCCTRL1] & ~WM8776_ALC1_LCT_MASK;
85 u16 alc2 = wm->regs[WM8776_REG_ALCCTRL2] & ~WM8776_ALC2_LCEN;
87 switch (agc) {
88 case 0: /* Off */
89 wm->agc_mode = WM8776_AGC_OFF;
90 break;
91 case 1: /* Limiter */
92 alc2 |= WM8776_ALC2_LCEN;
93 wm->agc_mode = WM8776_AGC_LIM;
94 break;
95 case 2: /* ALC Right */
96 alc1 |= WM8776_ALC1_LCSEL_ALCR;
97 alc2 |= WM8776_ALC2_LCEN;
98 wm->agc_mode = WM8776_AGC_ALC_R;
99 break;
100 case 3: /* ALC Left */
101 alc1 |= WM8776_ALC1_LCSEL_ALCL;
102 alc2 |= WM8776_ALC2_LCEN;
103 wm->agc_mode = WM8776_AGC_ALC_L;
104 break;
105 case 4: /* ALC Stereo */
106 alc1 |= WM8776_ALC1_LCSEL_ALCSTEREO;
107 alc2 |= WM8776_ALC2_LCEN;
108 wm->agc_mode = WM8776_AGC_ALC_STEREO;
109 break;
111 snd_wm8776_write(wm, WM8776_REG_ALCCTRL1, alc1);
112 snd_wm8776_write(wm, WM8776_REG_ALCCTRL2, alc2);
113 snd_wm8776_update_agc_ctl(wm);
116 static void snd_wm8776_get_agc(struct snd_wm8776 *wm, u16 *mode, u16 *nothing)
118 *mode = wm->agc_mode;
121 /* mixer controls */
123 static const DECLARE_TLV_DB_SCALE(wm8776_hp_tlv, -7400, 100, 1);
124 static const DECLARE_TLV_DB_SCALE(wm8776_dac_tlv, -12750, 50, 1);
125 static const DECLARE_TLV_DB_SCALE(wm8776_adc_tlv, -10350, 50, 1);
126 static const DECLARE_TLV_DB_SCALE(wm8776_lct_tlv, -1600, 100, 0);
127 static const DECLARE_TLV_DB_SCALE(wm8776_maxgain_tlv, 0, 400, 0);
128 static const DECLARE_TLV_DB_SCALE(wm8776_ngth_tlv, -7800, 600, 0);
129 static const DECLARE_TLV_DB_SCALE(wm8776_maxatten_lim_tlv, -1200, 100, 0);
130 static const DECLARE_TLV_DB_SCALE(wm8776_maxatten_alc_tlv, -2100, 400, 0);
132 static const struct snd_wm8776_ctl snd_wm8776_default_ctl[WM8776_CTL_COUNT] = {
133 [WM8776_CTL_DAC_VOL] = {
134 .name = "Master Playback Volume",
135 .type = SNDRV_CTL_ELEM_TYPE_INTEGER,
136 .tlv = wm8776_dac_tlv,
137 .reg1 = WM8776_REG_DACLVOL,
138 .reg2 = WM8776_REG_DACRVOL,
139 .mask1 = WM8776_DACVOL_MASK,
140 .mask2 = WM8776_DACVOL_MASK,
141 .max = 0xff,
142 .flags = WM8776_FLAG_STEREO | WM8776_FLAG_VOL_UPDATE,
144 [WM8776_CTL_DAC_SW] = {
145 .name = "Master Playback Switch",
146 .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
147 .reg1 = WM8776_REG_DACCTRL1,
148 .reg2 = WM8776_REG_DACCTRL1,
149 .mask1 = WM8776_DAC_PL_LL,
150 .mask2 = WM8776_DAC_PL_RR,
151 .flags = WM8776_FLAG_STEREO,
153 [WM8776_CTL_DAC_ZC_SW] = {
154 .name = "Master Zero Cross Detect Playback Switch",
155 .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
156 .reg1 = WM8776_REG_DACCTRL1,
157 .mask1 = WM8776_DAC_DZCEN,
159 [WM8776_CTL_HP_VOL] = {
160 .name = "Headphone Playback Volume",
161 .type = SNDRV_CTL_ELEM_TYPE_INTEGER,
162 .tlv = wm8776_hp_tlv,
163 .reg1 = WM8776_REG_HPLVOL,
164 .reg2 = WM8776_REG_HPRVOL,
165 .mask1 = WM8776_HPVOL_MASK,
166 .mask2 = WM8776_HPVOL_MASK,
167 .min = 0x2f,
168 .max = 0x7f,
169 .flags = WM8776_FLAG_STEREO | WM8776_FLAG_VOL_UPDATE,
171 [WM8776_CTL_HP_SW] = {
172 .name = "Headphone Playback Switch",
173 .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
174 .reg1 = WM8776_REG_PWRDOWN,
175 .mask1 = WM8776_PWR_HPPD,
176 .flags = WM8776_FLAG_INVERT,
178 [WM8776_CTL_HP_ZC_SW] = {
179 .name = "Headphone Zero Cross Detect Playback Switch",
180 .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
181 .reg1 = WM8776_REG_HPLVOL,
182 .reg2 = WM8776_REG_HPRVOL,
183 .mask1 = WM8776_VOL_HPZCEN,
184 .mask2 = WM8776_VOL_HPZCEN,
185 .flags = WM8776_FLAG_STEREO,
187 [WM8776_CTL_AUX_SW] = {
188 .name = "AUX Playback Switch",
189 .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
190 .reg1 = WM8776_REG_OUTMUX,
191 .mask1 = WM8776_OUTMUX_AUX,
193 [WM8776_CTL_BYPASS_SW] = {
194 .name = "Bypass Playback Switch",
195 .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
196 .reg1 = WM8776_REG_OUTMUX,
197 .mask1 = WM8776_OUTMUX_BYPASS,
199 [WM8776_CTL_DAC_IZD_SW] = {
200 .name = "Infinite Zero Detect Playback Switch",
201 .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
202 .reg1 = WM8776_REG_DACCTRL1,
203 .mask1 = WM8776_DAC_IZD,
205 [WM8776_CTL_PHASE_SW] = {
206 .name = "Phase Invert Playback Switch",
207 .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
208 .reg1 = WM8776_REG_PHASESWAP,
209 .reg2 = WM8776_REG_PHASESWAP,
210 .mask1 = WM8776_PHASE_INVERTL,
211 .mask2 = WM8776_PHASE_INVERTR,
212 .flags = WM8776_FLAG_STEREO,
214 [WM8776_CTL_DEEMPH_SW] = {
215 .name = "Deemphasis Playback Switch",
216 .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
217 .reg1 = WM8776_REG_DACCTRL2,
218 .mask1 = WM8776_DAC2_DEEMPH,
220 [WM8776_CTL_ADC_VOL] = {
221 .name = "Input Capture Volume",
222 .type = SNDRV_CTL_ELEM_TYPE_INTEGER,
223 .tlv = wm8776_adc_tlv,
224 .reg1 = WM8776_REG_ADCLVOL,
225 .reg2 = WM8776_REG_ADCRVOL,
226 .mask1 = WM8776_ADC_GAIN_MASK,
227 .mask2 = WM8776_ADC_GAIN_MASK,
228 .max = 0xff,
229 .flags = WM8776_FLAG_STEREO | WM8776_FLAG_VOL_UPDATE,
231 [WM8776_CTL_ADC_SW] = {
232 .name = "Input Capture Switch",
233 .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
234 .reg1 = WM8776_REG_ADCMUX,
235 .reg2 = WM8776_REG_ADCMUX,
236 .mask1 = WM8776_ADC_MUTEL,
237 .mask2 = WM8776_ADC_MUTER,
238 .flags = WM8776_FLAG_STEREO | WM8776_FLAG_INVERT,
240 [WM8776_CTL_INPUT1_SW] = {
241 .name = "AIN1 Capture Switch",
242 .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
243 .reg1 = WM8776_REG_ADCMUX,
244 .mask1 = WM8776_ADC_MUX_AIN1,
246 [WM8776_CTL_INPUT2_SW] = {
247 .name = "AIN2 Capture Switch",
248 .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
249 .reg1 = WM8776_REG_ADCMUX,
250 .mask1 = WM8776_ADC_MUX_AIN2,
252 [WM8776_CTL_INPUT3_SW] = {
253 .name = "AIN3 Capture Switch",
254 .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
255 .reg1 = WM8776_REG_ADCMUX,
256 .mask1 = WM8776_ADC_MUX_AIN3,
258 [WM8776_CTL_INPUT4_SW] = {
259 .name = "AIN4 Capture Switch",
260 .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
261 .reg1 = WM8776_REG_ADCMUX,
262 .mask1 = WM8776_ADC_MUX_AIN4,
264 [WM8776_CTL_INPUT5_SW] = {
265 .name = "AIN5 Capture Switch",
266 .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
267 .reg1 = WM8776_REG_ADCMUX,
268 .mask1 = WM8776_ADC_MUX_AIN5,
270 [WM8776_CTL_AGC_SEL] = {
271 .name = "AGC Select Capture Enum",
272 .type = SNDRV_CTL_ELEM_TYPE_ENUMERATED,
273 .enum_names = { "Off", "Limiter", "ALC Right", "ALC Left",
274 "ALC Stereo" },
275 .max = 5, /* .enum_names item count */
276 .set = snd_wm8776_set_agc,
277 .get = snd_wm8776_get_agc,
279 [WM8776_CTL_LIM_THR] = {
280 .name = "Limiter Threshold Capture Volume",
281 .type = SNDRV_CTL_ELEM_TYPE_INTEGER,
282 .tlv = wm8776_lct_tlv,
283 .reg1 = WM8776_REG_ALCCTRL1,
284 .mask1 = WM8776_ALC1_LCT_MASK,
285 .max = 15,
286 .flags = WM8776_FLAG_LIM,
288 [WM8776_CTL_LIM_ATK] = {
289 .name = "Limiter Attack Time Capture Enum",
290 .type = SNDRV_CTL_ELEM_TYPE_ENUMERATED,
291 .enum_names = { "0.25 ms", "0.5 ms", "1 ms", "2 ms", "4 ms",
292 "8 ms", "16 ms", "32 ms", "64 ms", "128 ms", "256 ms" },
293 .max = 11, /* .enum_names item count */
294 .reg1 = WM8776_REG_ALCCTRL3,
295 .mask1 = WM8776_ALC3_ATK_MASK,
296 .flags = WM8776_FLAG_LIM,
298 [WM8776_CTL_LIM_DCY] = {
299 .name = "Limiter Decay Time Capture Enum",
300 .type = SNDRV_CTL_ELEM_TYPE_ENUMERATED,
301 .enum_names = { "1.2 ms", "2.4 ms", "4.8 ms", "9.6 ms",
302 "19.2 ms", "38.4 ms", "76.8 ms", "154 ms", "307 ms",
303 "614 ms", "1.23 s" },
304 .max = 11, /* .enum_names item count */
305 .reg1 = WM8776_REG_ALCCTRL3,
306 .mask1 = WM8776_ALC3_DCY_MASK,
307 .flags = WM8776_FLAG_LIM,
309 [WM8776_CTL_LIM_TRANWIN] = {
310 .name = "Limiter Transient Window Capture Enum",
311 .type = SNDRV_CTL_ELEM_TYPE_ENUMERATED,
312 .enum_names = { "0 us", "62.5 us", "125 us", "250 us", "500 us",
313 "1 ms", "2 ms", "4 ms" },
314 .max = 8, /* .enum_names item count */
315 .reg1 = WM8776_REG_LIMITER,
316 .mask1 = WM8776_LIM_TRANWIN_MASK,
317 .flags = WM8776_FLAG_LIM,
319 [WM8776_CTL_LIM_MAXATTN] = {
320 .name = "Limiter Maximum Attenuation Capture Volume",
321 .type = SNDRV_CTL_ELEM_TYPE_INTEGER,
322 .tlv = wm8776_maxatten_lim_tlv,
323 .reg1 = WM8776_REG_LIMITER,
324 .mask1 = WM8776_LIM_MAXATTEN_MASK,
325 .min = 3,
326 .max = 12,
327 .flags = WM8776_FLAG_LIM | WM8776_FLAG_INVERT,
329 [WM8776_CTL_ALC_TGT] = {
330 .name = "ALC Target Level Capture Volume",
331 .type = SNDRV_CTL_ELEM_TYPE_INTEGER,
332 .tlv = wm8776_lct_tlv,
333 .reg1 = WM8776_REG_ALCCTRL1,
334 .mask1 = WM8776_ALC1_LCT_MASK,
335 .max = 15,
336 .flags = WM8776_FLAG_ALC,
338 [WM8776_CTL_ALC_ATK] = {
339 .name = "ALC Attack Time Capture Enum",
340 .type = SNDRV_CTL_ELEM_TYPE_ENUMERATED,
341 .enum_names = { "8.40 ms", "16.8 ms", "33.6 ms", "67.2 ms",
342 "134 ms", "269 ms", "538 ms", "1.08 s", "2.15 s",
343 "4.3 s", "8.6 s" },
344 .max = 11, /* .enum_names item count */
345 .reg1 = WM8776_REG_ALCCTRL3,
346 .mask1 = WM8776_ALC3_ATK_MASK,
347 .flags = WM8776_FLAG_ALC,
349 [WM8776_CTL_ALC_DCY] = {
350 .name = "ALC Decay Time Capture Enum",
351 .type = SNDRV_CTL_ELEM_TYPE_ENUMERATED,
352 .enum_names = { "33.5 ms", "67.0 ms", "134 ms", "268 ms",
353 "536 ms", "1.07 s", "2.14 s", "4.29 s", "8.58 s",
354 "17.2 s", "34.3 s" },
355 .max = 11, /* .enum_names item count */
356 .reg1 = WM8776_REG_ALCCTRL3,
357 .mask1 = WM8776_ALC3_DCY_MASK,
358 .flags = WM8776_FLAG_ALC,
360 [WM8776_CTL_ALC_MAXGAIN] = {
361 .name = "ALC Maximum Gain Capture Volume",
362 .type = SNDRV_CTL_ELEM_TYPE_INTEGER,
363 .tlv = wm8776_maxgain_tlv,
364 .reg1 = WM8776_REG_ALCCTRL1,
365 .mask1 = WM8776_ALC1_MAXGAIN_MASK,
366 .min = 1,
367 .max = 7,
368 .flags = WM8776_FLAG_ALC,
370 [WM8776_CTL_ALC_MAXATTN] = {
371 .name = "ALC Maximum Attenuation Capture Volume",
372 .type = SNDRV_CTL_ELEM_TYPE_INTEGER,
373 .tlv = wm8776_maxatten_alc_tlv,
374 .reg1 = WM8776_REG_LIMITER,
375 .mask1 = WM8776_LIM_MAXATTEN_MASK,
376 .min = 10,
377 .max = 15,
378 .flags = WM8776_FLAG_ALC | WM8776_FLAG_INVERT,
380 [WM8776_CTL_ALC_HLD] = {
381 .name = "ALC Hold Time Capture Enum",
382 .type = SNDRV_CTL_ELEM_TYPE_ENUMERATED,
383 .enum_names = { "0 ms", "2.67 ms", "5.33 ms", "10.6 ms",
384 "21.3 ms", "42.7 ms", "85.3 ms", "171 ms", "341 ms",
385 "683 ms", "1.37 s", "2.73 s", "5.46 s", "10.9 s",
386 "21.8 s", "43.7 s" },
387 .max = 16, /* .enum_names item count */
388 .reg1 = WM8776_REG_ALCCTRL2,
389 .mask1 = WM8776_ALC2_HOLD_MASK,
390 .flags = WM8776_FLAG_ALC,
392 [WM8776_CTL_NGT_SW] = {
393 .name = "Noise Gate Capture Switch",
394 .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
395 .reg1 = WM8776_REG_NOISEGATE,
396 .mask1 = WM8776_NGAT_ENABLE,
397 .flags = WM8776_FLAG_ALC,
399 [WM8776_CTL_NGT_THR] = {
400 .name = "Noise Gate Threshold Capture Volume",
401 .type = SNDRV_CTL_ELEM_TYPE_INTEGER,
402 .tlv = wm8776_ngth_tlv,
403 .reg1 = WM8776_REG_NOISEGATE,
404 .mask1 = WM8776_NGAT_THR_MASK,
405 .max = 7,
406 .flags = WM8776_FLAG_ALC,
410 /* exported functions */
412 void snd_wm8776_init(struct snd_wm8776 *wm)
414 int i;
415 static const u16 default_values[] = {
416 0x000, 0x100, 0x000,
417 0x000, 0x100, 0x000,
418 0x000, 0x090, 0x000, 0x000,
419 0x022, 0x022, 0x022,
420 0x008, 0x0cf, 0x0cf, 0x07b, 0x000,
421 0x032, 0x000, 0x0a6, 0x001, 0x001
424 memcpy(wm->ctl, snd_wm8776_default_ctl, sizeof(wm->ctl));
426 snd_wm8776_write(wm, WM8776_REG_RESET, 0x00); /* reset */
427 udelay(10);
428 /* load defaults */
429 for (i = 0; i < ARRAY_SIZE(default_values); i++)
430 snd_wm8776_write(wm, i, default_values[i]);
433 void snd_wm8776_resume(struct snd_wm8776 *wm)
435 int i;
437 for (i = 0; i < WM8776_REG_COUNT; i++)
438 snd_wm8776_write(wm, i, wm->regs[i]);
441 void snd_wm8776_set_power(struct snd_wm8776 *wm, u16 power)
443 snd_wm8776_write(wm, WM8776_REG_PWRDOWN, power);
446 void snd_wm8776_volume_restore(struct snd_wm8776 *wm)
448 u16 val = wm->regs[WM8776_REG_DACRVOL];
449 /* restore volume after MCLK stopped */
450 snd_wm8776_write(wm, WM8776_REG_DACRVOL, val | WM8776_VOL_UPDATE);
453 /* mixer callbacks */
455 static int snd_wm8776_volume_info(struct snd_kcontrol *kcontrol,
456 struct snd_ctl_elem_info *uinfo)
458 struct snd_wm8776 *wm = snd_kcontrol_chip(kcontrol);
459 int n = kcontrol->private_value;
461 uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
462 uinfo->count = (wm->ctl[n].flags & WM8776_FLAG_STEREO) ? 2 : 1;
463 uinfo->value.integer.min = wm->ctl[n].min;
464 uinfo->value.integer.max = wm->ctl[n].max;
466 return 0;
469 static int snd_wm8776_enum_info(struct snd_kcontrol *kcontrol,
470 struct snd_ctl_elem_info *uinfo)
472 struct snd_wm8776 *wm = snd_kcontrol_chip(kcontrol);
473 int n = kcontrol->private_value;
475 return snd_ctl_enum_info(uinfo, 1, wm->ctl[n].max,
476 wm->ctl[n].enum_names);
479 static int snd_wm8776_ctl_get(struct snd_kcontrol *kcontrol,
480 struct snd_ctl_elem_value *ucontrol)
482 struct snd_wm8776 *wm = snd_kcontrol_chip(kcontrol);
483 int n = kcontrol->private_value;
484 u16 val1, val2;
486 if (wm->ctl[n].get)
487 wm->ctl[n].get(wm, &val1, &val2);
488 else {
489 val1 = wm->regs[wm->ctl[n].reg1] & wm->ctl[n].mask1;
490 val1 >>= __ffs(wm->ctl[n].mask1);
491 if (wm->ctl[n].flags & WM8776_FLAG_STEREO) {
492 val2 = wm->regs[wm->ctl[n].reg2] & wm->ctl[n].mask2;
493 val2 >>= __ffs(wm->ctl[n].mask2);
494 if (wm->ctl[n].flags & WM8776_FLAG_VOL_UPDATE)
495 val2 &= ~WM8776_VOL_UPDATE;
498 if (wm->ctl[n].flags & WM8776_FLAG_INVERT) {
499 val1 = wm->ctl[n].max - (val1 - wm->ctl[n].min);
500 if (wm->ctl[n].flags & WM8776_FLAG_STEREO)
501 val2 = wm->ctl[n].max - (val2 - wm->ctl[n].min);
503 ucontrol->value.integer.value[0] = val1;
504 if (wm->ctl[n].flags & WM8776_FLAG_STEREO)
505 ucontrol->value.integer.value[1] = val2;
507 return 0;
510 static int snd_wm8776_ctl_put(struct snd_kcontrol *kcontrol,
511 struct snd_ctl_elem_value *ucontrol)
513 struct snd_wm8776 *wm = snd_kcontrol_chip(kcontrol);
514 int n = kcontrol->private_value;
515 u16 val, regval1, regval2;
517 /* this also works for enum because value is a union */
518 regval1 = ucontrol->value.integer.value[0];
519 regval2 = ucontrol->value.integer.value[1];
520 if (wm->ctl[n].flags & WM8776_FLAG_INVERT) {
521 regval1 = wm->ctl[n].max - (regval1 - wm->ctl[n].min);
522 regval2 = wm->ctl[n].max - (regval2 - wm->ctl[n].min);
524 if (wm->ctl[n].set)
525 wm->ctl[n].set(wm, regval1, regval2);
526 else {
527 val = wm->regs[wm->ctl[n].reg1] & ~wm->ctl[n].mask1;
528 val |= regval1 << __ffs(wm->ctl[n].mask1);
529 /* both stereo controls in one register */
530 if (wm->ctl[n].flags & WM8776_FLAG_STEREO &&
531 wm->ctl[n].reg1 == wm->ctl[n].reg2) {
532 val &= ~wm->ctl[n].mask2;
533 val |= regval2 << __ffs(wm->ctl[n].mask2);
535 snd_wm8776_write(wm, wm->ctl[n].reg1, val);
536 /* stereo controls in different registers */
537 if (wm->ctl[n].flags & WM8776_FLAG_STEREO &&
538 wm->ctl[n].reg1 != wm->ctl[n].reg2) {
539 val = wm->regs[wm->ctl[n].reg2] & ~wm->ctl[n].mask2;
540 val |= regval2 << __ffs(wm->ctl[n].mask2);
541 if (wm->ctl[n].flags & WM8776_FLAG_VOL_UPDATE)
542 val |= WM8776_VOL_UPDATE;
543 snd_wm8776_write(wm, wm->ctl[n].reg2, val);
547 return 0;
550 static int snd_wm8776_add_control(struct snd_wm8776 *wm, int num)
552 struct snd_kcontrol_new cont;
553 struct snd_kcontrol *ctl;
555 memset(&cont, 0, sizeof(cont));
556 cont.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
557 cont.private_value = num;
558 cont.name = wm->ctl[num].name;
559 cont.access = SNDRV_CTL_ELEM_ACCESS_READWRITE;
560 if (wm->ctl[num].flags & WM8776_FLAG_LIM ||
561 wm->ctl[num].flags & WM8776_FLAG_ALC)
562 cont.access |= SNDRV_CTL_ELEM_ACCESS_INACTIVE;
563 cont.tlv.p = NULL;
564 cont.get = snd_wm8776_ctl_get;
565 cont.put = snd_wm8776_ctl_put;
567 switch (wm->ctl[num].type) {
568 case SNDRV_CTL_ELEM_TYPE_INTEGER:
569 cont.info = snd_wm8776_volume_info;
570 cont.access |= SNDRV_CTL_ELEM_ACCESS_TLV_READ;
571 cont.tlv.p = wm->ctl[num].tlv;
572 break;
573 case SNDRV_CTL_ELEM_TYPE_BOOLEAN:
574 wm->ctl[num].max = 1;
575 if (wm->ctl[num].flags & WM8776_FLAG_STEREO)
576 cont.info = snd_ctl_boolean_stereo_info;
577 else
578 cont.info = snd_ctl_boolean_mono_info;
579 break;
580 case SNDRV_CTL_ELEM_TYPE_ENUMERATED:
581 cont.info = snd_wm8776_enum_info;
582 break;
583 default:
584 return -EINVAL;
586 ctl = snd_ctl_new1(&cont, wm);
587 if (!ctl)
588 return -ENOMEM;
590 return snd_ctl_add(wm->card, ctl);
593 int snd_wm8776_build_controls(struct snd_wm8776 *wm)
595 int err, i;
597 for (i = 0; i < WM8776_CTL_COUNT; i++)
598 if (wm->ctl[i].name) {
599 err = snd_wm8776_add_control(wm, i);
600 if (err < 0)
601 return err;
604 return 0;