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>
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
,
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
);
60 index_offset
= snd_ctl_get_ioff(kctl
, &kctl
->id
);
61 vd
= &kctl
->vd
[index_offset
];
63 vd
->access
&= ~SNDRV_CTL_ELEM_ACCESS_INACTIVE
;
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
) {
75 flags_off
= WM8776_FLAG_LIM
| WM8776_FLAG_ALC
;
78 flags_off
= WM8776_FLAG_ALC
;
79 flags_on
= WM8776_FLAG_LIM
;
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
;
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
;
103 wm
->agc_mode
= WM8776_AGC_OFF
;
105 case 1: /* Limiter */
106 alc2
|= WM8776_ALC2_LCEN
;
107 wm
->agc_mode
= WM8776_AGC_LIM
;
109 case 2: /* ALC Right */
110 alc1
|= WM8776_ALC1_LCSEL_ALCR
;
111 alc2
|= WM8776_ALC2_LCEN
;
112 wm
->agc_mode
= WM8776_AGC_ALC_R
;
114 case 3: /* ALC Left */
115 alc1
|= WM8776_ALC1_LCSEL_ALCL
;
116 alc2
|= WM8776_ALC2_LCEN
;
117 wm
->agc_mode
= WM8776_AGC_ALC_L
;
119 case 4: /* ALC Stereo */
120 alc1
|= WM8776_ALC1_LCSEL_ALCSTEREO
;
121 alc2
|= WM8776_ALC2_LCEN
;
122 wm
->agc_mode
= WM8776_AGC_ALC_STEREO
;
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
;
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
,
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
,
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
,
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",
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
,
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
,
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
,
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",
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
,
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
,
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
,
420 .flags
= WM8776_FLAG_ALC
,
424 /* exported functions */
426 void snd_wm8776_init(struct snd_wm8776
*wm
)
429 static const u16 default_values
[] = {
432 0x000, 0x090, 0x000, 0x000,
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 */
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
)
451 for (i
= 0; i
< WM8776_REG_COUNT
; i
++)
452 snd_wm8776_write(wm
, i
, wm
->regs
[i
]);
455 void snd_wm8776_set_power(struct snd_wm8776
*wm
, u16 power
)
457 snd_wm8776_write(wm
, WM8776_REG_PWRDOWN
, power
);
460 void snd_wm8776_volume_restore(struct snd_wm8776
*wm
)
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
);
467 /* mixer callbacks */
469 static int snd_wm8776_volume_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 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
;
483 static int snd_wm8776_enum_info(struct snd_kcontrol
*kcontrol
,
484 struct snd_ctl_elem_info
*uinfo
)
486 struct snd_wm8776
*wm
= snd_kcontrol_chip(kcontrol
);
487 int n
= kcontrol
->private_value
;
489 return snd_ctl_enum_info(uinfo
, 1, wm
->ctl
[n
].max
,
490 wm
->ctl
[n
].enum_names
);
493 static int snd_wm8776_ctl_get(struct snd_kcontrol
*kcontrol
,
494 struct snd_ctl_elem_value
*ucontrol
)
496 struct snd_wm8776
*wm
= snd_kcontrol_chip(kcontrol
);
497 int n
= kcontrol
->private_value
;
501 wm
->ctl
[n
].get(wm
, &val1
, &val2
);
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
;
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
);
517 ucontrol
->value
.integer
.value
[0] = val1
;
518 if (wm
->ctl
[n
].flags
& WM8776_FLAG_STEREO
)
519 ucontrol
->value
.integer
.value
[1] = val2
;
524 static int snd_wm8776_ctl_put(struct snd_kcontrol
*kcontrol
,
525 struct snd_ctl_elem_value
*ucontrol
)
527 struct snd_wm8776
*wm
= snd_kcontrol_chip(kcontrol
);
528 int n
= kcontrol
->private_value
;
529 u16 val
, regval1
, regval2
;
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
);
539 wm
->ctl
[n
].set(wm
, regval1
, regval2
);
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
);
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
);
564 static int snd_wm8776_add_control(struct snd_wm8776
*wm
, int num
)
566 struct snd_kcontrol_new cont
;
567 struct snd_kcontrol
*ctl
;
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
;
578 cont
.get
= snd_wm8776_ctl_get
;
579 cont
.put
= snd_wm8776_ctl_put
;
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
;
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
;
592 cont
.info
= snd_ctl_boolean_mono_info
;
594 case SNDRV_CTL_ELEM_TYPE_ENUMERATED
:
595 cont
.info
= snd_wm8776_enum_info
;
600 ctl
= snd_ctl_new1(&cont
, wm
);
604 return snd_ctl_add(wm
->card
, ctl
);
607 int snd_wm8776_build_controls(struct snd_wm8776
*wm
)
611 for (i
= 0; i
< WM8776_CTL_COUNT
; i
++)
612 if (wm
->ctl
[i
].name
) {
613 err
= snd_wm8776_add_control(wm
, i
);