diff options
Diffstat (limited to 'sound/pci/ice1712/wm8766.c')
-rw-r--r-- | sound/pci/ice1712/wm8766.c | 361 |
1 files changed, 361 insertions, 0 deletions
diff --git a/sound/pci/ice1712/wm8766.c b/sound/pci/ice1712/wm8766.c new file mode 100644 index 000000000000..8072adeecf68 --- /dev/null +++ b/sound/pci/ice1712/wm8766.c | |||
@@ -0,0 +1,361 @@ | |||
1 | /* | ||
2 | * ALSA driver for ICEnsemble VT17xx | ||
3 | * | ||
4 | * Lowlevel functions for WM8766 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 "wm8766.h" | ||
29 | |||
30 | /* low-level access */ | ||
31 | |||
32 | static void snd_wm8766_write(struct snd_wm8766 *wm, u16 addr, u16 data) | ||
33 | { | ||
34 | if (addr < WM8766_REG_RESET) | ||
35 | wm->regs[addr] = data; | ||
36 | wm->ops.write(wm, addr, data); | ||
37 | } | ||
38 | |||
39 | /* mixer controls */ | ||
40 | |||
41 | static const DECLARE_TLV_DB_SCALE(wm8766_tlv, -12750, 50, 1); | ||
42 | |||
43 | static struct snd_wm8766_ctl snd_wm8766_default_ctl[WM8766_CTL_COUNT] = { | ||
44 | [WM8766_CTL_CH1_VOL] = { | ||
45 | .name = "Channel 1 Playback Volume", | ||
46 | .type = SNDRV_CTL_ELEM_TYPE_INTEGER, | ||
47 | .tlv = wm8766_tlv, | ||
48 | .reg1 = WM8766_REG_DACL1, | ||
49 | .reg2 = WM8766_REG_DACR1, | ||
50 | .mask1 = WM8766_VOL_MASK, | ||
51 | .mask2 = WM8766_VOL_MASK, | ||
52 | .max = 0xff, | ||
53 | .flags = WM8766_FLAG_STEREO | WM8766_FLAG_VOL_UPDATE, | ||
54 | }, | ||
55 | [WM8766_CTL_CH2_VOL] = { | ||
56 | .name = "Channel 2 Playback Volume", | ||
57 | .type = SNDRV_CTL_ELEM_TYPE_INTEGER, | ||
58 | .tlv = wm8766_tlv, | ||
59 | .reg1 = WM8766_REG_DACL2, | ||
60 | .reg2 = WM8766_REG_DACR2, | ||
61 | .mask1 = WM8766_VOL_MASK, | ||
62 | .mask2 = WM8766_VOL_MASK, | ||
63 | .max = 0xff, | ||
64 | .flags = WM8766_FLAG_STEREO | WM8766_FLAG_VOL_UPDATE, | ||
65 | }, | ||
66 | [WM8766_CTL_CH3_VOL] = { | ||
67 | .name = "Channel 3 Playback Volume", | ||
68 | .type = SNDRV_CTL_ELEM_TYPE_INTEGER, | ||
69 | .tlv = wm8766_tlv, | ||
70 | .reg1 = WM8766_REG_DACL3, | ||
71 | .reg2 = WM8766_REG_DACR3, | ||
72 | .mask1 = WM8766_VOL_MASK, | ||
73 | .mask2 = WM8766_VOL_MASK, | ||
74 | .max = 0xff, | ||
75 | .flags = WM8766_FLAG_STEREO | WM8766_FLAG_VOL_UPDATE, | ||
76 | }, | ||
77 | [WM8766_CTL_CH1_SW] = { | ||
78 | .name = "Channel 1 Playback Switch", | ||
79 | .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN, | ||
80 | .reg1 = WM8766_REG_DACCTRL2, | ||
81 | .mask1 = WM8766_DAC2_MUTE1, | ||
82 | .flags = WM8766_FLAG_INVERT, | ||
83 | }, | ||
84 | [WM8766_CTL_CH2_SW] = { | ||
85 | .name = "Channel 2 Playback Switch", | ||
86 | .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN, | ||
87 | .reg1 = WM8766_REG_DACCTRL2, | ||
88 | .mask1 = WM8766_DAC2_MUTE2, | ||
89 | .flags = WM8766_FLAG_INVERT, | ||
90 | }, | ||
91 | [WM8766_CTL_CH3_SW] = { | ||
92 | .name = "Channel 3 Playback Switch", | ||
93 | .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN, | ||
94 | .reg1 = WM8766_REG_DACCTRL2, | ||
95 | .mask1 = WM8766_DAC2_MUTE3, | ||
96 | .flags = WM8766_FLAG_INVERT, | ||
97 | }, | ||
98 | [WM8766_CTL_PHASE1_SW] = { | ||
99 | .name = "Channel 1 Phase Invert Playback Switch", | ||
100 | .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN, | ||
101 | .reg1 = WM8766_REG_IFCTRL, | ||
102 | .mask1 = WM8766_PHASE_INVERT1, | ||
103 | }, | ||
104 | [WM8766_CTL_PHASE2_SW] = { | ||
105 | .name = "Channel 2 Phase Invert Playback Switch", | ||
106 | .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN, | ||
107 | .reg1 = WM8766_REG_IFCTRL, | ||
108 | .mask1 = WM8766_PHASE_INVERT2, | ||
109 | }, | ||
110 | [WM8766_CTL_PHASE3_SW] = { | ||
111 | .name = "Channel 3 Phase Invert Playback Switch", | ||
112 | .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN, | ||
113 | .reg1 = WM8766_REG_IFCTRL, | ||
114 | .mask1 = WM8766_PHASE_INVERT3, | ||
115 | }, | ||
116 | [WM8766_CTL_DEEMPH1_SW] = { | ||
117 | .name = "Channel 1 Deemphasis Playback Switch", | ||
118 | .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN, | ||
119 | .reg1 = WM8766_REG_DACCTRL2, | ||
120 | .mask1 = WM8766_DAC2_DEEMP1, | ||
121 | }, | ||
122 | [WM8766_CTL_DEEMPH2_SW] = { | ||
123 | .name = "Channel 2 Deemphasis Playback Switch", | ||
124 | .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN, | ||
125 | .reg1 = WM8766_REG_DACCTRL2, | ||
126 | .mask1 = WM8766_DAC2_DEEMP2, | ||
127 | }, | ||
128 | [WM8766_CTL_DEEMPH3_SW] = { | ||
129 | .name = "Channel 3 Deemphasis Playback Switch", | ||
130 | .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN, | ||
131 | .reg1 = WM8766_REG_DACCTRL2, | ||
132 | .mask1 = WM8766_DAC2_DEEMP3, | ||
133 | }, | ||
134 | [WM8766_CTL_IZD_SW] = { | ||
135 | .name = "Infinite Zero Detect Playback Switch", | ||
136 | .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN, | ||
137 | .reg1 = WM8766_REG_DACCTRL1, | ||
138 | .mask1 = WM8766_DAC_IZD, | ||
139 | }, | ||
140 | [WM8766_CTL_ZC_SW] = { | ||
141 | .name = "Zero Cross Detect Playback Switch", | ||
142 | .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN, | ||
143 | .reg1 = WM8766_REG_DACCTRL2, | ||
144 | .mask1 = WM8766_DAC2_ZCD, | ||
145 | .flags = WM8766_FLAG_INVERT, | ||
146 | }, | ||
147 | }; | ||
148 | |||
149 | /* exported functions */ | ||
150 | |||
151 | void snd_wm8766_init(struct snd_wm8766 *wm) | ||
152 | { | ||
153 | int i; | ||
154 | static const u16 default_values[] = { | ||
155 | 0x000, 0x100, | ||
156 | 0x120, 0x000, | ||
157 | 0x000, 0x100, 0x000, 0x100, 0x000, | ||
158 | 0x000, 0x080, | ||
159 | }; | ||
160 | |||
161 | memcpy(wm->ctl, snd_wm8766_default_ctl, sizeof(wm->ctl)); | ||
162 | |||
163 | snd_wm8766_write(wm, WM8766_REG_RESET, 0x00); /* reset */ | ||
164 | udelay(10); | ||
165 | /* load defaults */ | ||
166 | for (i = 0; i < ARRAY_SIZE(default_values); i++) | ||
167 | snd_wm8766_write(wm, i, default_values[i]); | ||
168 | } | ||
169 | |||
170 | void snd_wm8766_resume(struct snd_wm8766 *wm) | ||
171 | { | ||
172 | int i; | ||
173 | |||
174 | for (i = 0; i < WM8766_REG_COUNT; i++) | ||
175 | snd_wm8766_write(wm, i, wm->regs[i]); | ||
176 | } | ||
177 | |||
178 | void snd_wm8766_set_if(struct snd_wm8766 *wm, u16 dac) | ||
179 | { | ||
180 | u16 val = wm->regs[WM8766_REG_IFCTRL] & ~WM8766_IF_MASK; | ||
181 | |||
182 | dac &= WM8766_IF_MASK; | ||
183 | snd_wm8766_write(wm, WM8766_REG_IFCTRL, val | dac); | ||
184 | } | ||
185 | |||
186 | void snd_wm8766_set_master_mode(struct snd_wm8766 *wm, u16 mode) | ||
187 | { | ||
188 | u16 val = wm->regs[WM8766_REG_DACCTRL3] & ~WM8766_DAC3_MSTR_MASK; | ||
189 | |||
190 | mode &= WM8766_DAC3_MSTR_MASK; | ||
191 | snd_wm8766_write(wm, WM8766_REG_DACCTRL3, val | mode); | ||
192 | } | ||
193 | |||
194 | void snd_wm8766_set_power(struct snd_wm8766 *wm, u16 power) | ||
195 | { | ||
196 | u16 val = wm->regs[WM8766_REG_DACCTRL3] & ~WM8766_DAC3_POWER_MASK; | ||
197 | |||
198 | power &= WM8766_DAC3_POWER_MASK; | ||
199 | snd_wm8766_write(wm, WM8766_REG_DACCTRL3, val | power); | ||
200 | } | ||
201 | |||
202 | void snd_wm8766_volume_restore(struct snd_wm8766 *wm) | ||
203 | { | ||
204 | u16 val = wm->regs[WM8766_REG_DACR1]; | ||
205 | /* restore volume after MCLK stopped */ | ||
206 | snd_wm8766_write(wm, WM8766_REG_DACR1, val | WM8766_VOL_UPDATE); | ||
207 | } | ||
208 | |||
209 | /* mixer callbacks */ | ||
210 | |||
211 | static int snd_wm8766_volume_info(struct snd_kcontrol *kcontrol, | ||
212 | struct snd_ctl_elem_info *uinfo) | ||
213 | { | ||
214 | struct snd_wm8766 *wm = snd_kcontrol_chip(kcontrol); | ||
215 | int n = kcontrol->private_value; | ||
216 | |||
217 | uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; | ||
218 | uinfo->count = (wm->ctl[n].flags & WM8766_FLAG_STEREO) ? 2 : 1; | ||
219 | uinfo->value.integer.min = wm->ctl[n].min; | ||
220 | uinfo->value.integer.max = wm->ctl[n].max; | ||
221 | |||
222 | return 0; | ||
223 | } | ||
224 | |||
225 | static int snd_wm8766_enum_info(struct snd_kcontrol *kcontrol, | ||
226 | struct snd_ctl_elem_info *uinfo) | ||
227 | { | ||
228 | struct snd_wm8766 *wm = snd_kcontrol_chip(kcontrol); | ||
229 | int n = kcontrol->private_value; | ||
230 | |||
231 | return snd_ctl_enum_info(uinfo, 1, wm->ctl[n].max, | ||
232 | wm->ctl[n].enum_names); | ||
233 | } | ||
234 | |||
235 | static int snd_wm8766_ctl_get(struct snd_kcontrol *kcontrol, | ||
236 | struct snd_ctl_elem_value *ucontrol) | ||
237 | { | ||
238 | struct snd_wm8766 *wm = snd_kcontrol_chip(kcontrol); | ||
239 | int n = kcontrol->private_value; | ||
240 | u16 val1, val2; | ||
241 | |||
242 | if (wm->ctl[n].get) | ||
243 | wm->ctl[n].get(wm, &val1, &val2); | ||
244 | else { | ||
245 | val1 = wm->regs[wm->ctl[n].reg1] & wm->ctl[n].mask1; | ||
246 | val1 >>= __ffs(wm->ctl[n].mask1); | ||
247 | if (wm->ctl[n].flags & WM8766_FLAG_STEREO) { | ||
248 | val2 = wm->regs[wm->ctl[n].reg2] & wm->ctl[n].mask2; | ||
249 | val2 >>= __ffs(wm->ctl[n].mask2); | ||
250 | if (wm->ctl[n].flags & WM8766_FLAG_VOL_UPDATE) | ||
251 | val2 &= ~WM8766_VOL_UPDATE; | ||
252 | } | ||
253 | } | ||
254 | if (wm->ctl[n].flags & WM8766_FLAG_INVERT) { | ||
255 | val1 = wm->ctl[n].max - (val1 - wm->ctl[n].min); | ||
256 | val2 = wm->ctl[n].max - (val2 - wm->ctl[n].min); | ||
257 | } | ||
258 | ucontrol->value.integer.value[0] = val1; | ||
259 | if (wm->ctl[n].flags & WM8766_FLAG_STEREO) | ||
260 | ucontrol->value.integer.value[1] = val2; | ||
261 | |||
262 | return 0; | ||
263 | } | ||
264 | |||
265 | static int snd_wm8766_ctl_put(struct snd_kcontrol *kcontrol, | ||
266 | struct snd_ctl_elem_value *ucontrol) | ||
267 | { | ||
268 | struct snd_wm8766 *wm = snd_kcontrol_chip(kcontrol); | ||
269 | int n = kcontrol->private_value; | ||
270 | u16 val, regval1, regval2; | ||
271 | |||
272 | /* this also works for enum because value is an union */ | ||
273 | regval1 = ucontrol->value.integer.value[0]; | ||
274 | regval2 = ucontrol->value.integer.value[1]; | ||
275 | if (wm->ctl[n].flags & WM8766_FLAG_INVERT) { | ||
276 | regval1 = wm->ctl[n].max - (regval1 - wm->ctl[n].min); | ||
277 | regval2 = wm->ctl[n].max - (regval2 - wm->ctl[n].min); | ||
278 | } | ||
279 | if (wm->ctl[n].set) | ||
280 | wm->ctl[n].set(wm, regval1, regval2); | ||
281 | else { | ||
282 | val = wm->regs[wm->ctl[n].reg1] & ~wm->ctl[n].mask1; | ||
283 | val |= regval1 << __ffs(wm->ctl[n].mask1); | ||
284 | /* both stereo controls in one register */ | ||
285 | if (wm->ctl[n].flags & WM8766_FLAG_STEREO && | ||
286 | wm->ctl[n].reg1 == wm->ctl[n].reg2) { | ||
287 | val &= ~wm->ctl[n].mask2; | ||
288 | val |= regval2 << __ffs(wm->ctl[n].mask2); | ||
289 | } | ||
290 | snd_wm8766_write(wm, wm->ctl[n].reg1, val); | ||
291 | /* stereo controls in different registers */ | ||
292 | if (wm->ctl[n].flags & WM8766_FLAG_STEREO && | ||
293 | wm->ctl[n].reg1 != wm->ctl[n].reg2) { | ||
294 | val = wm->regs[wm->ctl[n].reg2] & ~wm->ctl[n].mask2; | ||
295 | val |= regval2 << __ffs(wm->ctl[n].mask2); | ||
296 | if (wm->ctl[n].flags & WM8766_FLAG_VOL_UPDATE) | ||
297 | val |= WM8766_VOL_UPDATE; | ||
298 | snd_wm8766_write(wm, wm->ctl[n].reg2, val); | ||
299 | } | ||
300 | } | ||
301 | |||
302 | return 0; | ||
303 | } | ||
304 | |||
305 | static int snd_wm8766_add_control(struct snd_wm8766 *wm, int num) | ||
306 | { | ||
307 | struct snd_kcontrol_new cont; | ||
308 | struct snd_kcontrol *ctl; | ||
309 | |||
310 | memset(&cont, 0, sizeof(cont)); | ||
311 | cont.iface = SNDRV_CTL_ELEM_IFACE_MIXER; | ||
312 | cont.private_value = num; | ||
313 | cont.name = wm->ctl[num].name; | ||
314 | cont.access = SNDRV_CTL_ELEM_ACCESS_READWRITE; | ||
315 | if (wm->ctl[num].flags & WM8766_FLAG_LIM || | ||
316 | wm->ctl[num].flags & WM8766_FLAG_ALC) | ||
317 | cont.access |= SNDRV_CTL_ELEM_ACCESS_INACTIVE; | ||
318 | cont.tlv.p = NULL; | ||
319 | cont.get = snd_wm8766_ctl_get; | ||
320 | cont.put = snd_wm8766_ctl_put; | ||
321 | |||
322 | switch (wm->ctl[num].type) { | ||
323 | case SNDRV_CTL_ELEM_TYPE_INTEGER: | ||
324 | cont.info = snd_wm8766_volume_info; | ||
325 | cont.access |= SNDRV_CTL_ELEM_ACCESS_TLV_READ; | ||
326 | cont.tlv.p = wm->ctl[num].tlv; | ||
327 | break; | ||
328 | case SNDRV_CTL_ELEM_TYPE_BOOLEAN: | ||
329 | wm->ctl[num].max = 1; | ||
330 | if (wm->ctl[num].flags & WM8766_FLAG_STEREO) | ||
331 | cont.info = snd_ctl_boolean_stereo_info; | ||
332 | else | ||
333 | cont.info = snd_ctl_boolean_mono_info; | ||
334 | break; | ||
335 | case SNDRV_CTL_ELEM_TYPE_ENUMERATED: | ||
336 | cont.info = snd_wm8766_enum_info; | ||
337 | break; | ||
338 | default: | ||
339 | return -EINVAL; | ||
340 | } | ||
341 | ctl = snd_ctl_new1(&cont, wm); | ||
342 | if (!ctl) | ||
343 | return -ENOMEM; | ||
344 | wm->ctl[num].kctl = ctl; | ||
345 | |||
346 | return snd_ctl_add(wm->card, ctl); | ||
347 | } | ||
348 | |||
349 | int snd_wm8766_build_controls(struct snd_wm8766 *wm) | ||
350 | { | ||
351 | int err, i; | ||
352 | |||
353 | for (i = 0; i < WM8766_CTL_COUNT; i++) | ||
354 | if (wm->ctl[i].name) { | ||
355 | err = snd_wm8766_add_control(wm, i); | ||
356 | if (err < 0) | ||
357 | return err; | ||
358 | } | ||
359 | |||
360 | return 0; | ||
361 | } | ||