Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0-or-later
0002 /*
0003  *   ALSA driver for ICEnsemble VT17xx
0004  *
0005  *   Lowlevel functions for WM8776 codec
0006  *
0007  *  Copyright (c) 2012 Ondrej Zary <linux@rainbow-software.org>
0008  */
0009 
0010 #include <linux/delay.h>
0011 #include <sound/core.h>
0012 #include <sound/control.h>
0013 #include <sound/tlv.h>
0014 #include "wm8776.h"
0015 
0016 /* low-level access */
0017 
0018 static void snd_wm8776_write(struct snd_wm8776 *wm, u16 addr, u16 data)
0019 {
0020     u8 bus_addr = addr << 1 | data >> 8;    /* addr + 9th data bit */
0021     u8 bus_data = data & 0xff;      /* remaining 8 data bits */
0022 
0023     if (addr < WM8776_REG_RESET)
0024         wm->regs[addr] = data;
0025     wm->ops.write(wm, bus_addr, bus_data);
0026 }
0027 
0028 /* register-level functions */
0029 
0030 static void snd_wm8776_activate_ctl(struct snd_wm8776 *wm,
0031                     const char *ctl_name,
0032                     bool active)
0033 {
0034     struct snd_card *card = wm->card;
0035     struct snd_kcontrol *kctl;
0036     struct snd_kcontrol_volatile *vd;
0037     struct snd_ctl_elem_id elem_id;
0038     unsigned int index_offset;
0039 
0040     memset(&elem_id, 0, sizeof(elem_id));
0041     strscpy(elem_id.name, ctl_name, sizeof(elem_id.name));
0042     elem_id.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
0043     kctl = snd_ctl_find_id(card, &elem_id);
0044     if (!kctl)
0045         return;
0046     index_offset = snd_ctl_get_ioff(kctl, &kctl->id);
0047     vd = &kctl->vd[index_offset];
0048     if (active)
0049         vd->access &= ~SNDRV_CTL_ELEM_ACCESS_INACTIVE;
0050     else
0051         vd->access |= SNDRV_CTL_ELEM_ACCESS_INACTIVE;
0052     snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_INFO, &kctl->id);
0053 }
0054 
0055 static void snd_wm8776_update_agc_ctl(struct snd_wm8776 *wm)
0056 {
0057     int i, flags_on = 0, flags_off = 0;
0058 
0059     switch (wm->agc_mode) {
0060     case WM8776_AGC_OFF:
0061         flags_off = WM8776_FLAG_LIM | WM8776_FLAG_ALC;
0062         break;
0063     case WM8776_AGC_LIM:
0064         flags_off = WM8776_FLAG_ALC;
0065         flags_on = WM8776_FLAG_LIM;
0066         break;
0067     case WM8776_AGC_ALC_R:
0068     case WM8776_AGC_ALC_L:
0069     case WM8776_AGC_ALC_STEREO:
0070         flags_off = WM8776_FLAG_LIM;
0071         flags_on = WM8776_FLAG_ALC;
0072         break;
0073     }
0074 
0075     for (i = 0; i < WM8776_CTL_COUNT; i++)
0076         if (wm->ctl[i].flags & flags_off)
0077             snd_wm8776_activate_ctl(wm, wm->ctl[i].name, false);
0078         else if (wm->ctl[i].flags & flags_on)
0079             snd_wm8776_activate_ctl(wm, wm->ctl[i].name, true);
0080 }
0081 
0082 static void snd_wm8776_set_agc(struct snd_wm8776 *wm, u16 agc, u16 nothing)
0083 {
0084     u16 alc1 = wm->regs[WM8776_REG_ALCCTRL1] & ~WM8776_ALC1_LCT_MASK;
0085     u16 alc2 = wm->regs[WM8776_REG_ALCCTRL2] & ~WM8776_ALC2_LCEN;
0086 
0087     switch (agc) {
0088     case 0: /* Off */
0089         wm->agc_mode = WM8776_AGC_OFF;
0090         break;
0091     case 1: /* Limiter */
0092         alc2 |= WM8776_ALC2_LCEN;
0093         wm->agc_mode = WM8776_AGC_LIM;
0094         break;
0095     case 2: /* ALC Right */
0096         alc1 |= WM8776_ALC1_LCSEL_ALCR;
0097         alc2 |= WM8776_ALC2_LCEN;
0098         wm->agc_mode = WM8776_AGC_ALC_R;
0099         break;
0100     case 3: /* ALC Left */
0101         alc1 |= WM8776_ALC1_LCSEL_ALCL;
0102         alc2 |= WM8776_ALC2_LCEN;
0103         wm->agc_mode = WM8776_AGC_ALC_L;
0104         break;
0105     case 4: /* ALC Stereo */
0106         alc1 |= WM8776_ALC1_LCSEL_ALCSTEREO;
0107         alc2 |= WM8776_ALC2_LCEN;
0108         wm->agc_mode = WM8776_AGC_ALC_STEREO;
0109         break;
0110     }
0111     snd_wm8776_write(wm, WM8776_REG_ALCCTRL1, alc1);
0112     snd_wm8776_write(wm, WM8776_REG_ALCCTRL2, alc2);
0113     snd_wm8776_update_agc_ctl(wm);
0114 }
0115 
0116 static void snd_wm8776_get_agc(struct snd_wm8776 *wm, u16 *mode, u16 *nothing)
0117 {
0118     *mode = wm->agc_mode;
0119 }
0120 
0121 /* mixer controls */
0122 
0123 static const DECLARE_TLV_DB_SCALE(wm8776_hp_tlv, -7400, 100, 1);
0124 static const DECLARE_TLV_DB_SCALE(wm8776_dac_tlv, -12750, 50, 1);
0125 static const DECLARE_TLV_DB_SCALE(wm8776_adc_tlv, -10350, 50, 1);
0126 static const DECLARE_TLV_DB_SCALE(wm8776_lct_tlv, -1600, 100, 0);
0127 static const DECLARE_TLV_DB_SCALE(wm8776_maxgain_tlv, 0, 400, 0);
0128 static const DECLARE_TLV_DB_SCALE(wm8776_ngth_tlv, -7800, 600, 0);
0129 static const DECLARE_TLV_DB_SCALE(wm8776_maxatten_lim_tlv, -1200, 100, 0);
0130 static const DECLARE_TLV_DB_SCALE(wm8776_maxatten_alc_tlv, -2100, 400, 0);
0131 
0132 static const struct snd_wm8776_ctl snd_wm8776_default_ctl[WM8776_CTL_COUNT] = {
0133     [WM8776_CTL_DAC_VOL] = {
0134         .name = "Master Playback Volume",
0135         .type = SNDRV_CTL_ELEM_TYPE_INTEGER,
0136         .tlv = wm8776_dac_tlv,
0137         .reg1 = WM8776_REG_DACLVOL,
0138         .reg2 = WM8776_REG_DACRVOL,
0139         .mask1 = WM8776_DACVOL_MASK,
0140         .mask2 = WM8776_DACVOL_MASK,
0141         .max = 0xff,
0142         .flags = WM8776_FLAG_STEREO | WM8776_FLAG_VOL_UPDATE,
0143     },
0144     [WM8776_CTL_DAC_SW] = {
0145         .name = "Master Playback Switch",
0146         .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
0147         .reg1 = WM8776_REG_DACCTRL1,
0148         .reg2 = WM8776_REG_DACCTRL1,
0149         .mask1 = WM8776_DAC_PL_LL,
0150         .mask2 = WM8776_DAC_PL_RR,
0151         .flags = WM8776_FLAG_STEREO,
0152     },
0153     [WM8776_CTL_DAC_ZC_SW] = {
0154         .name = "Master Zero Cross Detect Playback Switch",
0155         .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
0156         .reg1 = WM8776_REG_DACCTRL1,
0157         .mask1 = WM8776_DAC_DZCEN,
0158     },
0159     [WM8776_CTL_HP_VOL] = {
0160         .name = "Headphone Playback Volume",
0161         .type = SNDRV_CTL_ELEM_TYPE_INTEGER,
0162         .tlv = wm8776_hp_tlv,
0163         .reg1 = WM8776_REG_HPLVOL,
0164         .reg2 = WM8776_REG_HPRVOL,
0165         .mask1 = WM8776_HPVOL_MASK,
0166         .mask2 = WM8776_HPVOL_MASK,
0167         .min = 0x2f,
0168         .max = 0x7f,
0169         .flags = WM8776_FLAG_STEREO | WM8776_FLAG_VOL_UPDATE,
0170     },
0171     [WM8776_CTL_HP_SW] = {
0172         .name = "Headphone Playback Switch",
0173         .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
0174         .reg1 = WM8776_REG_PWRDOWN,
0175         .mask1 = WM8776_PWR_HPPD,
0176         .flags = WM8776_FLAG_INVERT,
0177     },
0178     [WM8776_CTL_HP_ZC_SW] = {
0179         .name = "Headphone Zero Cross Detect Playback Switch",
0180         .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
0181         .reg1 = WM8776_REG_HPLVOL,
0182         .reg2 = WM8776_REG_HPRVOL,
0183         .mask1 = WM8776_VOL_HPZCEN,
0184         .mask2 = WM8776_VOL_HPZCEN,
0185         .flags = WM8776_FLAG_STEREO,
0186     },
0187     [WM8776_CTL_AUX_SW] = {
0188         .name = "AUX Playback Switch",
0189         .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
0190         .reg1 = WM8776_REG_OUTMUX,
0191         .mask1 = WM8776_OUTMUX_AUX,
0192     },
0193     [WM8776_CTL_BYPASS_SW] = {
0194         .name = "Bypass Playback Switch",
0195         .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
0196         .reg1 = WM8776_REG_OUTMUX,
0197         .mask1 = WM8776_OUTMUX_BYPASS,
0198     },
0199     [WM8776_CTL_DAC_IZD_SW] = {
0200         .name = "Infinite Zero Detect Playback Switch",
0201         .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
0202         .reg1 = WM8776_REG_DACCTRL1,
0203         .mask1 = WM8776_DAC_IZD,
0204     },
0205     [WM8776_CTL_PHASE_SW] = {
0206         .name = "Phase Invert Playback Switch",
0207         .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
0208         .reg1 = WM8776_REG_PHASESWAP,
0209         .reg2 = WM8776_REG_PHASESWAP,
0210         .mask1 = WM8776_PHASE_INVERTL,
0211         .mask2 = WM8776_PHASE_INVERTR,
0212         .flags = WM8776_FLAG_STEREO,
0213     },
0214     [WM8776_CTL_DEEMPH_SW] = {
0215         .name = "Deemphasis Playback Switch",
0216         .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
0217         .reg1 = WM8776_REG_DACCTRL2,
0218         .mask1 = WM8776_DAC2_DEEMPH,
0219     },
0220     [WM8776_CTL_ADC_VOL] = {
0221         .name = "Input Capture Volume",
0222         .type = SNDRV_CTL_ELEM_TYPE_INTEGER,
0223         .tlv = wm8776_adc_tlv,
0224         .reg1 = WM8776_REG_ADCLVOL,
0225         .reg2 = WM8776_REG_ADCRVOL,
0226         .mask1 = WM8776_ADC_GAIN_MASK,
0227         .mask2 = WM8776_ADC_GAIN_MASK,
0228         .max = 0xff,
0229         .flags = WM8776_FLAG_STEREO | WM8776_FLAG_VOL_UPDATE,
0230     },
0231     [WM8776_CTL_ADC_SW] = {
0232         .name = "Input Capture Switch",
0233         .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
0234         .reg1 = WM8776_REG_ADCMUX,
0235         .reg2 = WM8776_REG_ADCMUX,
0236         .mask1 = WM8776_ADC_MUTEL,
0237         .mask2 = WM8776_ADC_MUTER,
0238         .flags = WM8776_FLAG_STEREO | WM8776_FLAG_INVERT,
0239     },
0240     [WM8776_CTL_INPUT1_SW] = {
0241         .name = "AIN1 Capture Switch",
0242         .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
0243         .reg1 = WM8776_REG_ADCMUX,
0244         .mask1 = WM8776_ADC_MUX_AIN1,
0245     },
0246     [WM8776_CTL_INPUT2_SW] = {
0247         .name = "AIN2 Capture Switch",
0248         .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
0249         .reg1 = WM8776_REG_ADCMUX,
0250         .mask1 = WM8776_ADC_MUX_AIN2,
0251     },
0252     [WM8776_CTL_INPUT3_SW] = {
0253         .name = "AIN3 Capture Switch",
0254         .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
0255         .reg1 = WM8776_REG_ADCMUX,
0256         .mask1 = WM8776_ADC_MUX_AIN3,
0257     },
0258     [WM8776_CTL_INPUT4_SW] = {
0259         .name = "AIN4 Capture Switch",
0260         .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
0261         .reg1 = WM8776_REG_ADCMUX,
0262         .mask1 = WM8776_ADC_MUX_AIN4,
0263     },
0264     [WM8776_CTL_INPUT5_SW] = {
0265         .name = "AIN5 Capture Switch",
0266         .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
0267         .reg1 = WM8776_REG_ADCMUX,
0268         .mask1 = WM8776_ADC_MUX_AIN5,
0269     },
0270     [WM8776_CTL_AGC_SEL] = {
0271         .name = "AGC Select Capture Enum",
0272         .type = SNDRV_CTL_ELEM_TYPE_ENUMERATED,
0273         .enum_names = { "Off", "Limiter", "ALC Right", "ALC Left",
0274                 "ALC Stereo" },
0275         .max = 5,   /* .enum_names item count */
0276         .set = snd_wm8776_set_agc,
0277         .get = snd_wm8776_get_agc,
0278     },
0279     [WM8776_CTL_LIM_THR] = {
0280         .name = "Limiter Threshold Capture Volume",
0281         .type = SNDRV_CTL_ELEM_TYPE_INTEGER,
0282         .tlv = wm8776_lct_tlv,
0283         .reg1 = WM8776_REG_ALCCTRL1,
0284         .mask1 = WM8776_ALC1_LCT_MASK,
0285         .max = 15,
0286         .flags = WM8776_FLAG_LIM,
0287     },
0288     [WM8776_CTL_LIM_ATK] = {
0289         .name = "Limiter Attack Time Capture Enum",
0290         .type = SNDRV_CTL_ELEM_TYPE_ENUMERATED,
0291         .enum_names = { "0.25 ms", "0.5 ms", "1 ms", "2 ms", "4 ms",
0292             "8 ms", "16 ms", "32 ms", "64 ms", "128 ms", "256 ms" },
0293         .max = 11,  /* .enum_names item count */
0294         .reg1 = WM8776_REG_ALCCTRL3,
0295         .mask1 = WM8776_ALC3_ATK_MASK,
0296         .flags = WM8776_FLAG_LIM,
0297     },
0298     [WM8776_CTL_LIM_DCY] = {
0299         .name = "Limiter Decay Time Capture Enum",
0300         .type = SNDRV_CTL_ELEM_TYPE_ENUMERATED,
0301         .enum_names = { "1.2 ms", "2.4 ms", "4.8 ms", "9.6 ms",
0302             "19.2 ms", "38.4 ms", "76.8 ms", "154 ms", "307 ms",
0303             "614 ms", "1.23 s" },
0304         .max = 11,  /* .enum_names item count */
0305         .reg1 = WM8776_REG_ALCCTRL3,
0306         .mask1 = WM8776_ALC3_DCY_MASK,
0307         .flags = WM8776_FLAG_LIM,
0308     },
0309     [WM8776_CTL_LIM_TRANWIN] = {
0310         .name = "Limiter Transient Window Capture Enum",
0311         .type = SNDRV_CTL_ELEM_TYPE_ENUMERATED,
0312         .enum_names = { "0 us", "62.5 us", "125 us", "250 us", "500 us",
0313             "1 ms", "2 ms", "4 ms" },
0314         .max = 8,   /* .enum_names item count */
0315         .reg1 = WM8776_REG_LIMITER,
0316         .mask1 = WM8776_LIM_TRANWIN_MASK,
0317         .flags = WM8776_FLAG_LIM,
0318     },
0319     [WM8776_CTL_LIM_MAXATTN] = {
0320         .name = "Limiter Maximum Attenuation Capture Volume",
0321         .type = SNDRV_CTL_ELEM_TYPE_INTEGER,
0322         .tlv = wm8776_maxatten_lim_tlv,
0323         .reg1 = WM8776_REG_LIMITER,
0324         .mask1 = WM8776_LIM_MAXATTEN_MASK,
0325         .min = 3,
0326         .max = 12,
0327         .flags = WM8776_FLAG_LIM | WM8776_FLAG_INVERT,
0328     },
0329     [WM8776_CTL_ALC_TGT] = {
0330         .name = "ALC Target Level Capture Volume",
0331         .type = SNDRV_CTL_ELEM_TYPE_INTEGER,
0332         .tlv = wm8776_lct_tlv,
0333         .reg1 = WM8776_REG_ALCCTRL1,
0334         .mask1 = WM8776_ALC1_LCT_MASK,
0335         .max = 15,
0336         .flags = WM8776_FLAG_ALC,
0337     },
0338     [WM8776_CTL_ALC_ATK] = {
0339         .name = "ALC Attack Time Capture Enum",
0340         .type = SNDRV_CTL_ELEM_TYPE_ENUMERATED,
0341         .enum_names = { "8.40 ms", "16.8 ms", "33.6 ms", "67.2 ms",
0342             "134 ms", "269 ms", "538 ms", "1.08 s", "2.15 s",
0343             "4.3 s", "8.6 s" },
0344         .max = 11,  /* .enum_names item count */
0345         .reg1 = WM8776_REG_ALCCTRL3,
0346         .mask1 = WM8776_ALC3_ATK_MASK,
0347         .flags = WM8776_FLAG_ALC,
0348     },
0349     [WM8776_CTL_ALC_DCY] = {
0350         .name = "ALC Decay Time Capture Enum",
0351         .type = SNDRV_CTL_ELEM_TYPE_ENUMERATED,
0352         .enum_names = { "33.5 ms", "67.0 ms", "134 ms", "268 ms",
0353             "536 ms", "1.07 s", "2.14 s", "4.29 s", "8.58 s",
0354             "17.2 s", "34.3 s" },
0355         .max = 11,  /* .enum_names item count */
0356         .reg1 = WM8776_REG_ALCCTRL3,
0357         .mask1 = WM8776_ALC3_DCY_MASK,
0358         .flags = WM8776_FLAG_ALC,
0359     },
0360     [WM8776_CTL_ALC_MAXGAIN] = {
0361         .name = "ALC Maximum Gain Capture Volume",
0362         .type = SNDRV_CTL_ELEM_TYPE_INTEGER,
0363         .tlv = wm8776_maxgain_tlv,
0364         .reg1 = WM8776_REG_ALCCTRL1,
0365         .mask1 = WM8776_ALC1_MAXGAIN_MASK,
0366         .min = 1,
0367         .max = 7,
0368         .flags = WM8776_FLAG_ALC,
0369     },
0370     [WM8776_CTL_ALC_MAXATTN] = {
0371         .name = "ALC Maximum Attenuation Capture Volume",
0372         .type = SNDRV_CTL_ELEM_TYPE_INTEGER,
0373         .tlv = wm8776_maxatten_alc_tlv,
0374         .reg1 = WM8776_REG_LIMITER,
0375         .mask1 = WM8776_LIM_MAXATTEN_MASK,
0376         .min = 10,
0377         .max = 15,
0378         .flags = WM8776_FLAG_ALC | WM8776_FLAG_INVERT,
0379     },
0380     [WM8776_CTL_ALC_HLD] = {
0381         .name = "ALC Hold Time Capture Enum",
0382         .type = SNDRV_CTL_ELEM_TYPE_ENUMERATED,
0383         .enum_names = { "0 ms", "2.67 ms", "5.33 ms", "10.6 ms",
0384             "21.3 ms", "42.7 ms", "85.3 ms", "171 ms", "341 ms",
0385             "683 ms", "1.37 s", "2.73 s", "5.46 s", "10.9 s",
0386             "21.8 s", "43.7 s" },
0387         .max = 16,  /* .enum_names item count */
0388         .reg1 = WM8776_REG_ALCCTRL2,
0389         .mask1 = WM8776_ALC2_HOLD_MASK,
0390         .flags = WM8776_FLAG_ALC,
0391     },
0392     [WM8776_CTL_NGT_SW] = {
0393         .name = "Noise Gate Capture Switch",
0394         .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
0395         .reg1 = WM8776_REG_NOISEGATE,
0396         .mask1 = WM8776_NGAT_ENABLE,
0397         .flags = WM8776_FLAG_ALC,
0398     },
0399     [WM8776_CTL_NGT_THR] = {
0400         .name = "Noise Gate Threshold Capture Volume",
0401         .type = SNDRV_CTL_ELEM_TYPE_INTEGER,
0402         .tlv = wm8776_ngth_tlv,
0403         .reg1 = WM8776_REG_NOISEGATE,
0404         .mask1 = WM8776_NGAT_THR_MASK,
0405         .max = 7,
0406         .flags = WM8776_FLAG_ALC,
0407     },
0408 };
0409 
0410 /* exported functions */
0411 
0412 void snd_wm8776_init(struct snd_wm8776 *wm)
0413 {
0414     int i;
0415     static const u16 default_values[] = {
0416         0x000, 0x100, 0x000,
0417         0x000, 0x100, 0x000,
0418         0x000, 0x090, 0x000, 0x000,
0419         0x022, 0x022, 0x022,
0420         0x008, 0x0cf, 0x0cf, 0x07b, 0x000,
0421         0x032, 0x000, 0x0a6, 0x001, 0x001
0422     };
0423 
0424     memcpy(wm->ctl, snd_wm8776_default_ctl, sizeof(wm->ctl));
0425 
0426     snd_wm8776_write(wm, WM8776_REG_RESET, 0x00); /* reset */
0427     udelay(10);
0428     /* load defaults */
0429     for (i = 0; i < ARRAY_SIZE(default_values); i++)
0430         snd_wm8776_write(wm, i, default_values[i]);
0431 }
0432 
0433 void snd_wm8776_resume(struct snd_wm8776 *wm)
0434 {
0435     int i;
0436 
0437     for (i = 0; i < WM8776_REG_COUNT; i++)
0438         snd_wm8776_write(wm, i, wm->regs[i]);
0439 }
0440 
0441 void snd_wm8776_set_power(struct snd_wm8776 *wm, u16 power)
0442 {
0443     snd_wm8776_write(wm, WM8776_REG_PWRDOWN, power);
0444 }
0445 
0446 void snd_wm8776_volume_restore(struct snd_wm8776 *wm)
0447 {
0448     u16 val = wm->regs[WM8776_REG_DACRVOL];
0449     /* restore volume after MCLK stopped */
0450     snd_wm8776_write(wm, WM8776_REG_DACRVOL, val | WM8776_VOL_UPDATE);
0451 }
0452 
0453 /* mixer callbacks */
0454 
0455 static int snd_wm8776_volume_info(struct snd_kcontrol *kcontrol,
0456                    struct snd_ctl_elem_info *uinfo)
0457 {
0458     struct snd_wm8776 *wm = snd_kcontrol_chip(kcontrol);
0459     int n = kcontrol->private_value;
0460 
0461     uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
0462     uinfo->count = (wm->ctl[n].flags & WM8776_FLAG_STEREO) ? 2 : 1;
0463     uinfo->value.integer.min = wm->ctl[n].min;
0464     uinfo->value.integer.max = wm->ctl[n].max;
0465 
0466     return 0;
0467 }
0468 
0469 static int snd_wm8776_enum_info(struct snd_kcontrol *kcontrol,
0470                       struct snd_ctl_elem_info *uinfo)
0471 {
0472     struct snd_wm8776 *wm = snd_kcontrol_chip(kcontrol);
0473     int n = kcontrol->private_value;
0474 
0475     return snd_ctl_enum_info(uinfo, 1, wm->ctl[n].max,
0476                         wm->ctl[n].enum_names);
0477 }
0478 
0479 static int snd_wm8776_ctl_get(struct snd_kcontrol *kcontrol,
0480                   struct snd_ctl_elem_value *ucontrol)
0481 {
0482     struct snd_wm8776 *wm = snd_kcontrol_chip(kcontrol);
0483     int n = kcontrol->private_value;
0484     u16 val1, val2;
0485 
0486     if (wm->ctl[n].get)
0487         wm->ctl[n].get(wm, &val1, &val2);
0488     else {
0489         val1 = wm->regs[wm->ctl[n].reg1] & wm->ctl[n].mask1;
0490         val1 >>= __ffs(wm->ctl[n].mask1);
0491         if (wm->ctl[n].flags & WM8776_FLAG_STEREO) {
0492             val2 = wm->regs[wm->ctl[n].reg2] & wm->ctl[n].mask2;
0493             val2 >>= __ffs(wm->ctl[n].mask2);
0494             if (wm->ctl[n].flags & WM8776_FLAG_VOL_UPDATE)
0495                 val2 &= ~WM8776_VOL_UPDATE;
0496         }
0497     }
0498     if (wm->ctl[n].flags & WM8776_FLAG_INVERT) {
0499         val1 = wm->ctl[n].max - (val1 - wm->ctl[n].min);
0500         if (wm->ctl[n].flags & WM8776_FLAG_STEREO)
0501             val2 = wm->ctl[n].max - (val2 - wm->ctl[n].min);
0502     }
0503     ucontrol->value.integer.value[0] = val1;
0504     if (wm->ctl[n].flags & WM8776_FLAG_STEREO)
0505         ucontrol->value.integer.value[1] = val2;
0506 
0507     return 0;
0508 }
0509 
0510 static int snd_wm8776_ctl_put(struct snd_kcontrol *kcontrol,
0511                   struct snd_ctl_elem_value *ucontrol)
0512 {
0513     struct snd_wm8776 *wm = snd_kcontrol_chip(kcontrol);
0514     int n = kcontrol->private_value;
0515     u16 val, regval1, regval2;
0516 
0517     /* this also works for enum because value is a union */
0518     regval1 = ucontrol->value.integer.value[0];
0519     regval2 = ucontrol->value.integer.value[1];
0520     if (wm->ctl[n].flags & WM8776_FLAG_INVERT) {
0521         regval1 = wm->ctl[n].max - (regval1 - wm->ctl[n].min);
0522         regval2 = wm->ctl[n].max - (regval2 - wm->ctl[n].min);
0523     }
0524     if (wm->ctl[n].set)
0525         wm->ctl[n].set(wm, regval1, regval2);
0526     else {
0527         val = wm->regs[wm->ctl[n].reg1] & ~wm->ctl[n].mask1;
0528         val |= regval1 << __ffs(wm->ctl[n].mask1);
0529         /* both stereo controls in one register */
0530         if (wm->ctl[n].flags & WM8776_FLAG_STEREO &&
0531                 wm->ctl[n].reg1 == wm->ctl[n].reg2) {
0532             val &= ~wm->ctl[n].mask2;
0533             val |= regval2 << __ffs(wm->ctl[n].mask2);
0534         }
0535         snd_wm8776_write(wm, wm->ctl[n].reg1, val);
0536         /* stereo controls in different registers */
0537         if (wm->ctl[n].flags & WM8776_FLAG_STEREO &&
0538                 wm->ctl[n].reg1 != wm->ctl[n].reg2) {
0539             val = wm->regs[wm->ctl[n].reg2] & ~wm->ctl[n].mask2;
0540             val |= regval2 << __ffs(wm->ctl[n].mask2);
0541             if (wm->ctl[n].flags & WM8776_FLAG_VOL_UPDATE)
0542                 val |= WM8776_VOL_UPDATE;
0543             snd_wm8776_write(wm, wm->ctl[n].reg2, val);
0544         }
0545     }
0546 
0547     return 0;
0548 }
0549 
0550 static int snd_wm8776_add_control(struct snd_wm8776 *wm, int num)
0551 {
0552     struct snd_kcontrol_new cont;
0553     struct snd_kcontrol *ctl;
0554 
0555     memset(&cont, 0, sizeof(cont));
0556     cont.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
0557     cont.private_value = num;
0558     cont.name = wm->ctl[num].name;
0559     cont.access = SNDRV_CTL_ELEM_ACCESS_READWRITE;
0560     if (wm->ctl[num].flags & WM8776_FLAG_LIM ||
0561         wm->ctl[num].flags & WM8776_FLAG_ALC)
0562         cont.access |= SNDRV_CTL_ELEM_ACCESS_INACTIVE;
0563     cont.tlv.p = NULL;
0564     cont.get = snd_wm8776_ctl_get;
0565     cont.put = snd_wm8776_ctl_put;
0566 
0567     switch (wm->ctl[num].type) {
0568     case SNDRV_CTL_ELEM_TYPE_INTEGER:
0569         cont.info = snd_wm8776_volume_info;
0570         cont.access |= SNDRV_CTL_ELEM_ACCESS_TLV_READ;
0571         cont.tlv.p = wm->ctl[num].tlv;
0572         break;
0573     case SNDRV_CTL_ELEM_TYPE_BOOLEAN:
0574         wm->ctl[num].max = 1;
0575         if (wm->ctl[num].flags & WM8776_FLAG_STEREO)
0576             cont.info = snd_ctl_boolean_stereo_info;
0577         else
0578             cont.info = snd_ctl_boolean_mono_info;
0579         break;
0580     case SNDRV_CTL_ELEM_TYPE_ENUMERATED:
0581         cont.info = snd_wm8776_enum_info;
0582         break;
0583     default:
0584         return -EINVAL;
0585     }
0586     ctl = snd_ctl_new1(&cont, wm);
0587     if (!ctl)
0588         return -ENOMEM;
0589 
0590     return snd_ctl_add(wm->card, ctl);
0591 }
0592 
0593 int snd_wm8776_build_controls(struct snd_wm8776 *wm)
0594 {
0595     int err, i;
0596 
0597     for (i = 0; i < WM8776_CTL_COUNT; i++)
0598         if (wm->ctl[i].name) {
0599             err = snd_wm8776_add_control(wm, i);
0600             if (err < 0)
0601                 return err;
0602         }
0603 
0604     return 0;
0605 }