blob: a3c05fe5daf93829a0eed48204fa128d9de914fd [file] [log] [blame]
Ondrej Zarybfe514272012-10-14 21:09:21 +02001/*
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
32static 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
Takashi Iwaia2af0502012-10-17 09:21:48 +020044static void snd_wm8776_activate_ctl(struct snd_wm8776 *wm,
45 const char *ctl_name,
Ondrej Zarybfe514272012-10-14 21:09:21 +020046 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 strncpy(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
69static 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
96static 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
130static 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
137static const DECLARE_TLV_DB_SCALE(wm8776_hp_tlv, -7400, 100, 1);
138static const DECLARE_TLV_DB_SCALE(wm8776_dac_tlv, -12750, 50, 1);
139static const DECLARE_TLV_DB_SCALE(wm8776_adc_tlv, -10350, 50, 1);
140static const DECLARE_TLV_DB_SCALE(wm8776_lct_tlv, -1600, 100, 0);
141static const DECLARE_TLV_DB_SCALE(wm8776_maxgain_tlv, 0, 400, 0);
142static const DECLARE_TLV_DB_SCALE(wm8776_ngth_tlv, -7800, 600, 0);
143static const DECLARE_TLV_DB_SCALE(wm8776_maxatten_lim_tlv, -1200, 100, 0);
144static const DECLARE_TLV_DB_SCALE(wm8776_maxatten_alc_tlv, -2100, 400, 0);
145
146static 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
426void 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
447void 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
455void snd_wm8776_set_dac_if(struct snd_wm8776 *wm, u16 dac)
456{
457 snd_wm8776_write(wm, WM8776_REG_DACIFCTRL, dac);
458}
459
460void snd_wm8776_set_adc_if(struct snd_wm8776 *wm, u16 adc)
461{
462 snd_wm8776_write(wm, WM8776_REG_ADCIFCTRL, adc);
463}
464
465void snd_wm8776_set_master_mode(struct snd_wm8776 *wm, u16 mode)
466{
467 snd_wm8776_write(wm, WM8776_REG_MSTRCTRL, mode);
468}
469
470void snd_wm8776_set_power(struct snd_wm8776 *wm, u16 power)
471{
472 snd_wm8776_write(wm, WM8776_REG_PWRDOWN, power);
473}
474
475void snd_wm8776_volume_restore(struct snd_wm8776 *wm)
476{
477 u16 val = wm->regs[WM8776_REG_DACRVOL];
478 /* restore volume after MCLK stopped */
479 snd_wm8776_write(wm, WM8776_REG_DACRVOL, val | WM8776_VOL_UPDATE);
480}
481
482/* mixer callbacks */
483
484static int snd_wm8776_volume_info(struct snd_kcontrol *kcontrol,
485 struct snd_ctl_elem_info *uinfo)
486{
487 struct snd_wm8776 *wm = snd_kcontrol_chip(kcontrol);
488 int n = kcontrol->private_value;
489
490 uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
491 uinfo->count = (wm->ctl[n].flags & WM8776_FLAG_STEREO) ? 2 : 1;
492 uinfo->value.integer.min = wm->ctl[n].min;
493 uinfo->value.integer.max = wm->ctl[n].max;
494
495 return 0;
496}
497
498static int snd_wm8776_enum_info(struct snd_kcontrol *kcontrol,
499 struct snd_ctl_elem_info *uinfo)
500{
501 struct snd_wm8776 *wm = snd_kcontrol_chip(kcontrol);
502 int n = kcontrol->private_value;
503
504 return snd_ctl_enum_info(uinfo, 1, wm->ctl[n].max,
505 wm->ctl[n].enum_names);
506}
507
508static int snd_wm8776_ctl_get(struct snd_kcontrol *kcontrol,
509 struct snd_ctl_elem_value *ucontrol)
510{
511 struct snd_wm8776 *wm = snd_kcontrol_chip(kcontrol);
512 int n = kcontrol->private_value;
513 u16 val1, val2;
514
515 if (wm->ctl[n].get)
516 wm->ctl[n].get(wm, &val1, &val2);
517 else {
518 val1 = wm->regs[wm->ctl[n].reg1] & wm->ctl[n].mask1;
519 val1 >>= __ffs(wm->ctl[n].mask1);
520 if (wm->ctl[n].flags & WM8776_FLAG_STEREO) {
521 val2 = wm->regs[wm->ctl[n].reg2] & wm->ctl[n].mask2;
522 val2 >>= __ffs(wm->ctl[n].mask2);
523 if (wm->ctl[n].flags & WM8776_FLAG_VOL_UPDATE)
524 val2 &= ~WM8776_VOL_UPDATE;
525 }
526 }
527 if (wm->ctl[n].flags & WM8776_FLAG_INVERT) {
528 val1 = wm->ctl[n].max - (val1 - wm->ctl[n].min);
529 val2 = wm->ctl[n].max - (val2 - wm->ctl[n].min);
530 }
531 ucontrol->value.integer.value[0] = val1;
532 if (wm->ctl[n].flags & WM8776_FLAG_STEREO)
533 ucontrol->value.integer.value[1] = val2;
534
535 return 0;
536}
537
538static int snd_wm8776_ctl_put(struct snd_kcontrol *kcontrol,
539 struct snd_ctl_elem_value *ucontrol)
540{
541 struct snd_wm8776 *wm = snd_kcontrol_chip(kcontrol);
542 int n = kcontrol->private_value;
543 u16 val, regval1, regval2;
544
545 /* this also works for enum because value is an union */
546 regval1 = ucontrol->value.integer.value[0];
547 regval2 = ucontrol->value.integer.value[1];
548 if (wm->ctl[n].flags & WM8776_FLAG_INVERT) {
549 regval1 = wm->ctl[n].max - (regval1 - wm->ctl[n].min);
550 regval2 = wm->ctl[n].max - (regval2 - wm->ctl[n].min);
551 }
552 if (wm->ctl[n].set)
553 wm->ctl[n].set(wm, regval1, regval2);
554 else {
555 val = wm->regs[wm->ctl[n].reg1] & ~wm->ctl[n].mask1;
556 val |= regval1 << __ffs(wm->ctl[n].mask1);
557 /* both stereo controls in one register */
558 if (wm->ctl[n].flags & WM8776_FLAG_STEREO &&
559 wm->ctl[n].reg1 == wm->ctl[n].reg2) {
560 val &= ~wm->ctl[n].mask2;
561 val |= regval2 << __ffs(wm->ctl[n].mask2);
562 }
563 snd_wm8776_write(wm, wm->ctl[n].reg1, val);
564 /* stereo controls in different registers */
565 if (wm->ctl[n].flags & WM8776_FLAG_STEREO &&
566 wm->ctl[n].reg1 != wm->ctl[n].reg2) {
567 val = wm->regs[wm->ctl[n].reg2] & ~wm->ctl[n].mask2;
568 val |= regval2 << __ffs(wm->ctl[n].mask2);
569 if (wm->ctl[n].flags & WM8776_FLAG_VOL_UPDATE)
570 val |= WM8776_VOL_UPDATE;
571 snd_wm8776_write(wm, wm->ctl[n].reg2, val);
572 }
573 }
574
575 return 0;
576}
577
578static int snd_wm8776_add_control(struct snd_wm8776 *wm, int num)
579{
580 struct snd_kcontrol_new cont;
581 struct snd_kcontrol *ctl;
582
583 memset(&cont, 0, sizeof(cont));
584 cont.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
585 cont.private_value = num;
586 cont.name = wm->ctl[num].name;
587 cont.access = SNDRV_CTL_ELEM_ACCESS_READWRITE;
588 if (wm->ctl[num].flags & WM8776_FLAG_LIM ||
589 wm->ctl[num].flags & WM8776_FLAG_ALC)
590 cont.access |= SNDRV_CTL_ELEM_ACCESS_INACTIVE;
591 cont.tlv.p = NULL;
592 cont.get = snd_wm8776_ctl_get;
593 cont.put = snd_wm8776_ctl_put;
594
595 switch (wm->ctl[num].type) {
596 case SNDRV_CTL_ELEM_TYPE_INTEGER:
597 cont.info = snd_wm8776_volume_info;
598 cont.access |= SNDRV_CTL_ELEM_ACCESS_TLV_READ;
599 cont.tlv.p = wm->ctl[num].tlv;
600 break;
601 case SNDRV_CTL_ELEM_TYPE_BOOLEAN:
602 wm->ctl[num].max = 1;
603 if (wm->ctl[num].flags & WM8776_FLAG_STEREO)
604 cont.info = snd_ctl_boolean_stereo_info;
605 else
606 cont.info = snd_ctl_boolean_mono_info;
607 break;
608 case SNDRV_CTL_ELEM_TYPE_ENUMERATED:
609 cont.info = snd_wm8776_enum_info;
610 break;
611 default:
612 return -EINVAL;
613 }
614 ctl = snd_ctl_new1(&cont, wm);
615 if (!ctl)
616 return -ENOMEM;
617
618 return snd_ctl_add(wm->card, ctl);
619}
620
621int snd_wm8776_build_controls(struct snd_wm8776 *wm)
622{
623 int err, i;
624
625 for (i = 0; i < WM8776_CTL_COUNT; i++)
626 if (wm->ctl[i].name) {
627 err = snd_wm8776_add_control(wm, i);
628 if (err < 0)
629 return err;
630 }
631
632 return 0;
633}