drm/panthor: Don't add write fences to the shared BOs
[drm/drm-misc.git] / sound / pci / ice1712 / wm8776.c
blob493425697bb44040dab159d3a0aaa884de2c8f08
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 unsigned int index_offset;
39 kctl = snd_ctl_find_id_mixer(card, ctl_name);
40 if (!kctl)
41 return;
42 index_offset = snd_ctl_get_ioff(kctl, &kctl->id);
43 vd = &kctl->vd[index_offset];
44 if (active)
45 vd->access &= ~SNDRV_CTL_ELEM_ACCESS_INACTIVE;
46 else
47 vd->access |= SNDRV_CTL_ELEM_ACCESS_INACTIVE;
48 snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_INFO, &kctl->id);
51 static void snd_wm8776_update_agc_ctl(struct snd_wm8776 *wm)
53 int i, flags_on = 0, flags_off = 0;
55 switch (wm->agc_mode) {
56 case WM8776_AGC_OFF:
57 flags_off = WM8776_FLAG_LIM | WM8776_FLAG_ALC;
58 break;
59 case WM8776_AGC_LIM:
60 flags_off = WM8776_FLAG_ALC;
61 flags_on = WM8776_FLAG_LIM;
62 break;
63 case WM8776_AGC_ALC_R:
64 case WM8776_AGC_ALC_L:
65 case WM8776_AGC_ALC_STEREO:
66 flags_off = WM8776_FLAG_LIM;
67 flags_on = WM8776_FLAG_ALC;
68 break;
71 for (i = 0; i < WM8776_CTL_COUNT; i++)
72 if (wm->ctl[i].flags & flags_off)
73 snd_wm8776_activate_ctl(wm, wm->ctl[i].name, false);
74 else if (wm->ctl[i].flags & flags_on)
75 snd_wm8776_activate_ctl(wm, wm->ctl[i].name, true);
78 static void snd_wm8776_set_agc(struct snd_wm8776 *wm, u16 agc, u16 nothing)
80 u16 alc1 = wm->regs[WM8776_REG_ALCCTRL1] & ~WM8776_ALC1_LCT_MASK;
81 u16 alc2 = wm->regs[WM8776_REG_ALCCTRL2] & ~WM8776_ALC2_LCEN;
83 switch (agc) {
84 case 0: /* Off */
85 wm->agc_mode = WM8776_AGC_OFF;
86 break;
87 case 1: /* Limiter */
88 alc2 |= WM8776_ALC2_LCEN;
89 wm->agc_mode = WM8776_AGC_LIM;
90 break;
91 case 2: /* ALC Right */
92 alc1 |= WM8776_ALC1_LCSEL_ALCR;
93 alc2 |= WM8776_ALC2_LCEN;
94 wm->agc_mode = WM8776_AGC_ALC_R;
95 break;
96 case 3: /* ALC Left */
97 alc1 |= WM8776_ALC1_LCSEL_ALCL;
98 alc2 |= WM8776_ALC2_LCEN;
99 wm->agc_mode = WM8776_AGC_ALC_L;
100 break;
101 case 4: /* ALC Stereo */
102 alc1 |= WM8776_ALC1_LCSEL_ALCSTEREO;
103 alc2 |= WM8776_ALC2_LCEN;
104 wm->agc_mode = WM8776_AGC_ALC_STEREO;
105 break;
107 snd_wm8776_write(wm, WM8776_REG_ALCCTRL1, alc1);
108 snd_wm8776_write(wm, WM8776_REG_ALCCTRL2, alc2);
109 snd_wm8776_update_agc_ctl(wm);
112 static void snd_wm8776_get_agc(struct snd_wm8776 *wm, u16 *mode, u16 *nothing)
114 *mode = wm->agc_mode;
117 /* mixer controls */
119 static const DECLARE_TLV_DB_SCALE(wm8776_hp_tlv, -7400, 100, 1);
120 static const DECLARE_TLV_DB_SCALE(wm8776_dac_tlv, -12750, 50, 1);
121 static const DECLARE_TLV_DB_SCALE(wm8776_adc_tlv, -10350, 50, 1);
122 static const DECLARE_TLV_DB_SCALE(wm8776_lct_tlv, -1600, 100, 0);
123 static const DECLARE_TLV_DB_SCALE(wm8776_maxgain_tlv, 0, 400, 0);
124 static const DECLARE_TLV_DB_SCALE(wm8776_ngth_tlv, -7800, 600, 0);
125 static const DECLARE_TLV_DB_SCALE(wm8776_maxatten_lim_tlv, -1200, 100, 0);
126 static const DECLARE_TLV_DB_SCALE(wm8776_maxatten_alc_tlv, -2100, 400, 0);
128 static const struct snd_wm8776_ctl snd_wm8776_default_ctl[WM8776_CTL_COUNT] = {
129 [WM8776_CTL_DAC_VOL] = {
130 .name = "Master Playback Volume",
131 .type = SNDRV_CTL_ELEM_TYPE_INTEGER,
132 .tlv = wm8776_dac_tlv,
133 .reg1 = WM8776_REG_DACLVOL,
134 .reg2 = WM8776_REG_DACRVOL,
135 .mask1 = WM8776_DACVOL_MASK,
136 .mask2 = WM8776_DACVOL_MASK,
137 .max = 0xff,
138 .flags = WM8776_FLAG_STEREO | WM8776_FLAG_VOL_UPDATE,
140 [WM8776_CTL_DAC_SW] = {
141 .name = "Master Playback Switch",
142 .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
143 .reg1 = WM8776_REG_DACCTRL1,
144 .reg2 = WM8776_REG_DACCTRL1,
145 .mask1 = WM8776_DAC_PL_LL,
146 .mask2 = WM8776_DAC_PL_RR,
147 .flags = WM8776_FLAG_STEREO,
149 [WM8776_CTL_DAC_ZC_SW] = {
150 .name = "Master Zero Cross Detect Playback Switch",
151 .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
152 .reg1 = WM8776_REG_DACCTRL1,
153 .mask1 = WM8776_DAC_DZCEN,
155 [WM8776_CTL_HP_VOL] = {
156 .name = "Headphone Playback Volume",
157 .type = SNDRV_CTL_ELEM_TYPE_INTEGER,
158 .tlv = wm8776_hp_tlv,
159 .reg1 = WM8776_REG_HPLVOL,
160 .reg2 = WM8776_REG_HPRVOL,
161 .mask1 = WM8776_HPVOL_MASK,
162 .mask2 = WM8776_HPVOL_MASK,
163 .min = 0x2f,
164 .max = 0x7f,
165 .flags = WM8776_FLAG_STEREO | WM8776_FLAG_VOL_UPDATE,
167 [WM8776_CTL_HP_SW] = {
168 .name = "Headphone Playback Switch",
169 .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
170 .reg1 = WM8776_REG_PWRDOWN,
171 .mask1 = WM8776_PWR_HPPD,
172 .flags = WM8776_FLAG_INVERT,
174 [WM8776_CTL_HP_ZC_SW] = {
175 .name = "Headphone Zero Cross Detect Playback Switch",
176 .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
177 .reg1 = WM8776_REG_HPLVOL,
178 .reg2 = WM8776_REG_HPRVOL,
179 .mask1 = WM8776_VOL_HPZCEN,
180 .mask2 = WM8776_VOL_HPZCEN,
181 .flags = WM8776_FLAG_STEREO,
183 [WM8776_CTL_AUX_SW] = {
184 .name = "AUX Playback Switch",
185 .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
186 .reg1 = WM8776_REG_OUTMUX,
187 .mask1 = WM8776_OUTMUX_AUX,
189 [WM8776_CTL_BYPASS_SW] = {
190 .name = "Bypass Playback Switch",
191 .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
192 .reg1 = WM8776_REG_OUTMUX,
193 .mask1 = WM8776_OUTMUX_BYPASS,
195 [WM8776_CTL_DAC_IZD_SW] = {
196 .name = "Infinite Zero Detect Playback Switch",
197 .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
198 .reg1 = WM8776_REG_DACCTRL1,
199 .mask1 = WM8776_DAC_IZD,
201 [WM8776_CTL_PHASE_SW] = {
202 .name = "Phase Invert Playback Switch",
203 .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
204 .reg1 = WM8776_REG_PHASESWAP,
205 .reg2 = WM8776_REG_PHASESWAP,
206 .mask1 = WM8776_PHASE_INVERTL,
207 .mask2 = WM8776_PHASE_INVERTR,
208 .flags = WM8776_FLAG_STEREO,
210 [WM8776_CTL_DEEMPH_SW] = {
211 .name = "Deemphasis Playback Switch",
212 .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
213 .reg1 = WM8776_REG_DACCTRL2,
214 .mask1 = WM8776_DAC2_DEEMPH,
216 [WM8776_CTL_ADC_VOL] = {
217 .name = "Input Capture Volume",
218 .type = SNDRV_CTL_ELEM_TYPE_INTEGER,
219 .tlv = wm8776_adc_tlv,
220 .reg1 = WM8776_REG_ADCLVOL,
221 .reg2 = WM8776_REG_ADCRVOL,
222 .mask1 = WM8776_ADC_GAIN_MASK,
223 .mask2 = WM8776_ADC_GAIN_MASK,
224 .max = 0xff,
225 .flags = WM8776_FLAG_STEREO | WM8776_FLAG_VOL_UPDATE,
227 [WM8776_CTL_ADC_SW] = {
228 .name = "Input Capture Switch",
229 .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
230 .reg1 = WM8776_REG_ADCMUX,
231 .reg2 = WM8776_REG_ADCMUX,
232 .mask1 = WM8776_ADC_MUTEL,
233 .mask2 = WM8776_ADC_MUTER,
234 .flags = WM8776_FLAG_STEREO | WM8776_FLAG_INVERT,
236 [WM8776_CTL_INPUT1_SW] = {
237 .name = "AIN1 Capture Switch",
238 .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
239 .reg1 = WM8776_REG_ADCMUX,
240 .mask1 = WM8776_ADC_MUX_AIN1,
242 [WM8776_CTL_INPUT2_SW] = {
243 .name = "AIN2 Capture Switch",
244 .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
245 .reg1 = WM8776_REG_ADCMUX,
246 .mask1 = WM8776_ADC_MUX_AIN2,
248 [WM8776_CTL_INPUT3_SW] = {
249 .name = "AIN3 Capture Switch",
250 .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
251 .reg1 = WM8776_REG_ADCMUX,
252 .mask1 = WM8776_ADC_MUX_AIN3,
254 [WM8776_CTL_INPUT4_SW] = {
255 .name = "AIN4 Capture Switch",
256 .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
257 .reg1 = WM8776_REG_ADCMUX,
258 .mask1 = WM8776_ADC_MUX_AIN4,
260 [WM8776_CTL_INPUT5_SW] = {
261 .name = "AIN5 Capture Switch",
262 .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
263 .reg1 = WM8776_REG_ADCMUX,
264 .mask1 = WM8776_ADC_MUX_AIN5,
266 [WM8776_CTL_AGC_SEL] = {
267 .name = "AGC Select Capture Enum",
268 .type = SNDRV_CTL_ELEM_TYPE_ENUMERATED,
269 .enum_names = { "Off", "Limiter", "ALC Right", "ALC Left",
270 "ALC Stereo" },
271 .max = 5, /* .enum_names item count */
272 .set = snd_wm8776_set_agc,
273 .get = snd_wm8776_get_agc,
275 [WM8776_CTL_LIM_THR] = {
276 .name = "Limiter Threshold Capture Volume",
277 .type = SNDRV_CTL_ELEM_TYPE_INTEGER,
278 .tlv = wm8776_lct_tlv,
279 .reg1 = WM8776_REG_ALCCTRL1,
280 .mask1 = WM8776_ALC1_LCT_MASK,
281 .max = 15,
282 .flags = WM8776_FLAG_LIM,
284 [WM8776_CTL_LIM_ATK] = {
285 .name = "Limiter Attack Time Capture Enum",
286 .type = SNDRV_CTL_ELEM_TYPE_ENUMERATED,
287 .enum_names = { "0.25 ms", "0.5 ms", "1 ms", "2 ms", "4 ms",
288 "8 ms", "16 ms", "32 ms", "64 ms", "128 ms", "256 ms" },
289 .max = 11, /* .enum_names item count */
290 .reg1 = WM8776_REG_ALCCTRL3,
291 .mask1 = WM8776_ALC3_ATK_MASK,
292 .flags = WM8776_FLAG_LIM,
294 [WM8776_CTL_LIM_DCY] = {
295 .name = "Limiter Decay Time Capture Enum",
296 .type = SNDRV_CTL_ELEM_TYPE_ENUMERATED,
297 .enum_names = { "1.2 ms", "2.4 ms", "4.8 ms", "9.6 ms",
298 "19.2 ms", "38.4 ms", "76.8 ms", "154 ms", "307 ms",
299 "614 ms", "1.23 s" },
300 .max = 11, /* .enum_names item count */
301 .reg1 = WM8776_REG_ALCCTRL3,
302 .mask1 = WM8776_ALC3_DCY_MASK,
303 .flags = WM8776_FLAG_LIM,
305 [WM8776_CTL_LIM_TRANWIN] = {
306 .name = "Limiter Transient Window Capture Enum",
307 .type = SNDRV_CTL_ELEM_TYPE_ENUMERATED,
308 .enum_names = { "0 us", "62.5 us", "125 us", "250 us", "500 us",
309 "1 ms", "2 ms", "4 ms" },
310 .max = 8, /* .enum_names item count */
311 .reg1 = WM8776_REG_LIMITER,
312 .mask1 = WM8776_LIM_TRANWIN_MASK,
313 .flags = WM8776_FLAG_LIM,
315 [WM8776_CTL_LIM_MAXATTN] = {
316 .name = "Limiter Maximum Attenuation Capture Volume",
317 .type = SNDRV_CTL_ELEM_TYPE_INTEGER,
318 .tlv = wm8776_maxatten_lim_tlv,
319 .reg1 = WM8776_REG_LIMITER,
320 .mask1 = WM8776_LIM_MAXATTEN_MASK,
321 .min = 3,
322 .max = 12,
323 .flags = WM8776_FLAG_LIM | WM8776_FLAG_INVERT,
325 [WM8776_CTL_ALC_TGT] = {
326 .name = "ALC Target Level Capture Volume",
327 .type = SNDRV_CTL_ELEM_TYPE_INTEGER,
328 .tlv = wm8776_lct_tlv,
329 .reg1 = WM8776_REG_ALCCTRL1,
330 .mask1 = WM8776_ALC1_LCT_MASK,
331 .max = 15,
332 .flags = WM8776_FLAG_ALC,
334 [WM8776_CTL_ALC_ATK] = {
335 .name = "ALC Attack Time Capture Enum",
336 .type = SNDRV_CTL_ELEM_TYPE_ENUMERATED,
337 .enum_names = { "8.40 ms", "16.8 ms", "33.6 ms", "67.2 ms",
338 "134 ms", "269 ms", "538 ms", "1.08 s", "2.15 s",
339 "4.3 s", "8.6 s" },
340 .max = 11, /* .enum_names item count */
341 .reg1 = WM8776_REG_ALCCTRL3,
342 .mask1 = WM8776_ALC3_ATK_MASK,
343 .flags = WM8776_FLAG_ALC,
345 [WM8776_CTL_ALC_DCY] = {
346 .name = "ALC Decay Time Capture Enum",
347 .type = SNDRV_CTL_ELEM_TYPE_ENUMERATED,
348 .enum_names = { "33.5 ms", "67.0 ms", "134 ms", "268 ms",
349 "536 ms", "1.07 s", "2.14 s", "4.29 s", "8.58 s",
350 "17.2 s", "34.3 s" },
351 .max = 11, /* .enum_names item count */
352 .reg1 = WM8776_REG_ALCCTRL3,
353 .mask1 = WM8776_ALC3_DCY_MASK,
354 .flags = WM8776_FLAG_ALC,
356 [WM8776_CTL_ALC_MAXGAIN] = {
357 .name = "ALC Maximum Gain Capture Volume",
358 .type = SNDRV_CTL_ELEM_TYPE_INTEGER,
359 .tlv = wm8776_maxgain_tlv,
360 .reg1 = WM8776_REG_ALCCTRL1,
361 .mask1 = WM8776_ALC1_MAXGAIN_MASK,
362 .min = 1,
363 .max = 7,
364 .flags = WM8776_FLAG_ALC,
366 [WM8776_CTL_ALC_MAXATTN] = {
367 .name = "ALC Maximum Attenuation Capture Volume",
368 .type = SNDRV_CTL_ELEM_TYPE_INTEGER,
369 .tlv = wm8776_maxatten_alc_tlv,
370 .reg1 = WM8776_REG_LIMITER,
371 .mask1 = WM8776_LIM_MAXATTEN_MASK,
372 .min = 10,
373 .max = 15,
374 .flags = WM8776_FLAG_ALC | WM8776_FLAG_INVERT,
376 [WM8776_CTL_ALC_HLD] = {
377 .name = "ALC Hold Time Capture Enum",
378 .type = SNDRV_CTL_ELEM_TYPE_ENUMERATED,
379 .enum_names = { "0 ms", "2.67 ms", "5.33 ms", "10.6 ms",
380 "21.3 ms", "42.7 ms", "85.3 ms", "171 ms", "341 ms",
381 "683 ms", "1.37 s", "2.73 s", "5.46 s", "10.9 s",
382 "21.8 s", "43.7 s" },
383 .max = 16, /* .enum_names item count */
384 .reg1 = WM8776_REG_ALCCTRL2,
385 .mask1 = WM8776_ALC2_HOLD_MASK,
386 .flags = WM8776_FLAG_ALC,
388 [WM8776_CTL_NGT_SW] = {
389 .name = "Noise Gate Capture Switch",
390 .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
391 .reg1 = WM8776_REG_NOISEGATE,
392 .mask1 = WM8776_NGAT_ENABLE,
393 .flags = WM8776_FLAG_ALC,
395 [WM8776_CTL_NGT_THR] = {
396 .name = "Noise Gate Threshold Capture Volume",
397 .type = SNDRV_CTL_ELEM_TYPE_INTEGER,
398 .tlv = wm8776_ngth_tlv,
399 .reg1 = WM8776_REG_NOISEGATE,
400 .mask1 = WM8776_NGAT_THR_MASK,
401 .max = 7,
402 .flags = WM8776_FLAG_ALC,
406 /* exported functions */
408 void snd_wm8776_init(struct snd_wm8776 *wm)
410 int i;
411 static const u16 default_values[] = {
412 0x000, 0x100, 0x000,
413 0x000, 0x100, 0x000,
414 0x000, 0x090, 0x000, 0x000,
415 0x022, 0x022, 0x022,
416 0x008, 0x0cf, 0x0cf, 0x07b, 0x000,
417 0x032, 0x000, 0x0a6, 0x001, 0x001
420 memcpy(wm->ctl, snd_wm8776_default_ctl, sizeof(wm->ctl));
422 snd_wm8776_write(wm, WM8776_REG_RESET, 0x00); /* reset */
423 udelay(10);
424 /* load defaults */
425 for (i = 0; i < ARRAY_SIZE(default_values); i++)
426 snd_wm8776_write(wm, i, default_values[i]);
429 void snd_wm8776_resume(struct snd_wm8776 *wm)
431 int i;
433 for (i = 0; i < WM8776_REG_COUNT; i++)
434 snd_wm8776_write(wm, i, wm->regs[i]);
437 void snd_wm8776_set_power(struct snd_wm8776 *wm, u16 power)
439 snd_wm8776_write(wm, WM8776_REG_PWRDOWN, power);
442 void snd_wm8776_volume_restore(struct snd_wm8776 *wm)
444 u16 val = wm->regs[WM8776_REG_DACRVOL];
445 /* restore volume after MCLK stopped */
446 snd_wm8776_write(wm, WM8776_REG_DACRVOL, val | WM8776_VOL_UPDATE);
449 /* mixer callbacks */
451 static int snd_wm8776_volume_info(struct snd_kcontrol *kcontrol,
452 struct snd_ctl_elem_info *uinfo)
454 struct snd_wm8776 *wm = snd_kcontrol_chip(kcontrol);
455 int n = kcontrol->private_value;
457 uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
458 uinfo->count = (wm->ctl[n].flags & WM8776_FLAG_STEREO) ? 2 : 1;
459 uinfo->value.integer.min = wm->ctl[n].min;
460 uinfo->value.integer.max = wm->ctl[n].max;
462 return 0;
465 static int snd_wm8776_enum_info(struct snd_kcontrol *kcontrol,
466 struct snd_ctl_elem_info *uinfo)
468 struct snd_wm8776 *wm = snd_kcontrol_chip(kcontrol);
469 int n = kcontrol->private_value;
471 return snd_ctl_enum_info(uinfo, 1, wm->ctl[n].max,
472 wm->ctl[n].enum_names);
475 static int snd_wm8776_ctl_get(struct snd_kcontrol *kcontrol,
476 struct snd_ctl_elem_value *ucontrol)
478 struct snd_wm8776 *wm = snd_kcontrol_chip(kcontrol);
479 int n = kcontrol->private_value;
480 u16 val1, val2;
482 if (wm->ctl[n].get)
483 wm->ctl[n].get(wm, &val1, &val2);
484 else {
485 val1 = wm->regs[wm->ctl[n].reg1] & wm->ctl[n].mask1;
486 val1 >>= __ffs(wm->ctl[n].mask1);
487 if (wm->ctl[n].flags & WM8776_FLAG_STEREO) {
488 val2 = wm->regs[wm->ctl[n].reg2] & wm->ctl[n].mask2;
489 val2 >>= __ffs(wm->ctl[n].mask2);
490 if (wm->ctl[n].flags & WM8776_FLAG_VOL_UPDATE)
491 val2 &= ~WM8776_VOL_UPDATE;
494 if (wm->ctl[n].flags & WM8776_FLAG_INVERT) {
495 val1 = wm->ctl[n].max - (val1 - wm->ctl[n].min);
496 if (wm->ctl[n].flags & WM8776_FLAG_STEREO)
497 val2 = wm->ctl[n].max - (val2 - wm->ctl[n].min);
499 ucontrol->value.integer.value[0] = val1;
500 if (wm->ctl[n].flags & WM8776_FLAG_STEREO)
501 ucontrol->value.integer.value[1] = val2;
503 return 0;
506 static int snd_wm8776_ctl_put(struct snd_kcontrol *kcontrol,
507 struct snd_ctl_elem_value *ucontrol)
509 struct snd_wm8776 *wm = snd_kcontrol_chip(kcontrol);
510 int n = kcontrol->private_value;
511 u16 val, regval1, regval2;
513 /* this also works for enum because value is a union */
514 regval1 = ucontrol->value.integer.value[0];
515 regval2 = ucontrol->value.integer.value[1];
516 if (wm->ctl[n].flags & WM8776_FLAG_INVERT) {
517 regval1 = wm->ctl[n].max - (regval1 - wm->ctl[n].min);
518 regval2 = wm->ctl[n].max - (regval2 - wm->ctl[n].min);
520 if (wm->ctl[n].set)
521 wm->ctl[n].set(wm, regval1, regval2);
522 else {
523 val = wm->regs[wm->ctl[n].reg1] & ~wm->ctl[n].mask1;
524 val |= regval1 << __ffs(wm->ctl[n].mask1);
525 /* both stereo controls in one register */
526 if (wm->ctl[n].flags & WM8776_FLAG_STEREO &&
527 wm->ctl[n].reg1 == wm->ctl[n].reg2) {
528 val &= ~wm->ctl[n].mask2;
529 val |= regval2 << __ffs(wm->ctl[n].mask2);
531 snd_wm8776_write(wm, wm->ctl[n].reg1, val);
532 /* stereo controls in different registers */
533 if (wm->ctl[n].flags & WM8776_FLAG_STEREO &&
534 wm->ctl[n].reg1 != wm->ctl[n].reg2) {
535 val = wm->regs[wm->ctl[n].reg2] & ~wm->ctl[n].mask2;
536 val |= regval2 << __ffs(wm->ctl[n].mask2);
537 if (wm->ctl[n].flags & WM8776_FLAG_VOL_UPDATE)
538 val |= WM8776_VOL_UPDATE;
539 snd_wm8776_write(wm, wm->ctl[n].reg2, val);
543 return 0;
546 static int snd_wm8776_add_control(struct snd_wm8776 *wm, int num)
548 struct snd_kcontrol_new cont;
549 struct snd_kcontrol *ctl;
551 memset(&cont, 0, sizeof(cont));
552 cont.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
553 cont.private_value = num;
554 cont.name = wm->ctl[num].name;
555 cont.access = SNDRV_CTL_ELEM_ACCESS_READWRITE;
556 if (wm->ctl[num].flags & WM8776_FLAG_LIM ||
557 wm->ctl[num].flags & WM8776_FLAG_ALC)
558 cont.access |= SNDRV_CTL_ELEM_ACCESS_INACTIVE;
559 cont.tlv.p = NULL;
560 cont.get = snd_wm8776_ctl_get;
561 cont.put = snd_wm8776_ctl_put;
563 switch (wm->ctl[num].type) {
564 case SNDRV_CTL_ELEM_TYPE_INTEGER:
565 cont.info = snd_wm8776_volume_info;
566 cont.access |= SNDRV_CTL_ELEM_ACCESS_TLV_READ;
567 cont.tlv.p = wm->ctl[num].tlv;
568 break;
569 case SNDRV_CTL_ELEM_TYPE_BOOLEAN:
570 wm->ctl[num].max = 1;
571 if (wm->ctl[num].flags & WM8776_FLAG_STEREO)
572 cont.info = snd_ctl_boolean_stereo_info;
573 else
574 cont.info = snd_ctl_boolean_mono_info;
575 break;
576 case SNDRV_CTL_ELEM_TYPE_ENUMERATED:
577 cont.info = snd_wm8776_enum_info;
578 break;
579 default:
580 return -EINVAL;
582 ctl = snd_ctl_new1(&cont, wm);
583 if (!ctl)
584 return -ENOMEM;
586 return snd_ctl_add(wm->card, ctl);
589 int snd_wm8776_build_controls(struct snd_wm8776 *wm)
591 int err, i;
593 for (i = 0; i < WM8776_CTL_COUNT; i++)
594 if (wm->ctl[i].name) {
595 err = snd_wm8776_add_control(wm, i);
596 if (err < 0)
597 return err;
600 return 0;