1 // SPDX-License-Identifier: GPL-2.0-or-later
3 * ALSA driver for ICEnsemble VT17xx
5 * Lowlevel functions for WM8776 codec
7 * Copyright (c) 2012 Ondrej Zary <linux@rainbow-software.org>
10 #include <linux/delay.h>
11 #include <sound/core.h>
12 #include <sound/control.h>
13 #include <sound/tlv.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
,
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
);
46 index_offset
= snd_ctl_get_ioff(kctl
, &kctl
->id
);
47 vd
= &kctl
->vd
[index_offset
];
49 vd
->access
&= ~SNDRV_CTL_ELEM_ACCESS_INACTIVE
;
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
) {
61 flags_off
= WM8776_FLAG_LIM
| WM8776_FLAG_ALC
;
64 flags_off
= WM8776_FLAG_ALC
;
65 flags_on
= WM8776_FLAG_LIM
;
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
;
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
;
89 wm
->agc_mode
= WM8776_AGC_OFF
;
92 alc2
|= WM8776_ALC2_LCEN
;
93 wm
->agc_mode
= WM8776_AGC_LIM
;
95 case 2: /* ALC Right */
96 alc1
|= WM8776_ALC1_LCSEL_ALCR
;
97 alc2
|= WM8776_ALC2_LCEN
;
98 wm
->agc_mode
= WM8776_AGC_ALC_R
;
100 case 3: /* ALC Left */
101 alc1
|= WM8776_ALC1_LCSEL_ALCL
;
102 alc2
|= WM8776_ALC2_LCEN
;
103 wm
->agc_mode
= WM8776_AGC_ALC_L
;
105 case 4: /* ALC Stereo */
106 alc1
|= WM8776_ALC1_LCSEL_ALCSTEREO
;
107 alc2
|= WM8776_ALC2_LCEN
;
108 wm
->agc_mode
= WM8776_AGC_ALC_STEREO
;
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
;
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
,
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
,
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
,
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",
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
,
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
,
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
,
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",
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
,
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
,
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
,
406 .flags
= WM8776_FLAG_ALC
,
410 /* exported functions */
412 void snd_wm8776_init(struct snd_wm8776
*wm
)
415 static const u16 default_values
[] = {
418 0x000, 0x090, 0x000, 0x000,
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 */
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
)
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
;
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
;
487 wm
->ctl
[n
].get(wm
, &val1
, &val2
);
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
;
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
);
525 wm
->ctl
[n
].set(wm
, regval1
, regval2
);
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
);
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
;
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
;
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
;
578 cont
.info
= snd_ctl_boolean_mono_info
;
580 case SNDRV_CTL_ELEM_TYPE_ENUMERATED
:
581 cont
.info
= snd_wm8776_enum_info
;
586 ctl
= snd_ctl_new1(&cont
, wm
);
590 return snd_ctl_add(wm
->card
, ctl
);
593 int snd_wm8776_build_controls(struct snd_wm8776
*wm
)
597 for (i
= 0; i
< WM8776_CTL_COUNT
; i
++)
598 if (wm
->ctl
[i
].name
) {
599 err
= snd_wm8776_add_control(wm
, i
);