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