18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci *   ALSA driver for ICEnsemble VT17xx
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci *   Lowlevel functions for WM8766 codec
68c2ecf20Sopenharmony_ci *
78c2ecf20Sopenharmony_ci *	Copyright (c) 2012 Ondrej Zary <linux@rainbow-software.org>
88c2ecf20Sopenharmony_ci */
98c2ecf20Sopenharmony_ci
108c2ecf20Sopenharmony_ci#include <linux/delay.h>
118c2ecf20Sopenharmony_ci#include <sound/core.h>
128c2ecf20Sopenharmony_ci#include <sound/control.h>
138c2ecf20Sopenharmony_ci#include <sound/tlv.h>
148c2ecf20Sopenharmony_ci#include "wm8766.h"
158c2ecf20Sopenharmony_ci
168c2ecf20Sopenharmony_ci/* low-level access */
178c2ecf20Sopenharmony_ci
188c2ecf20Sopenharmony_cistatic void snd_wm8766_write(struct snd_wm8766 *wm, u16 addr, u16 data)
198c2ecf20Sopenharmony_ci{
208c2ecf20Sopenharmony_ci	if (addr < WM8766_REG_COUNT)
218c2ecf20Sopenharmony_ci		wm->regs[addr] = data;
228c2ecf20Sopenharmony_ci	wm->ops.write(wm, addr, data);
238c2ecf20Sopenharmony_ci}
248c2ecf20Sopenharmony_ci
258c2ecf20Sopenharmony_ci/* mixer controls */
268c2ecf20Sopenharmony_ci
278c2ecf20Sopenharmony_cistatic const DECLARE_TLV_DB_SCALE(wm8766_tlv, -12750, 50, 1);
288c2ecf20Sopenharmony_ci
298c2ecf20Sopenharmony_cistatic const struct snd_wm8766_ctl snd_wm8766_default_ctl[WM8766_CTL_COUNT] = {
308c2ecf20Sopenharmony_ci	[WM8766_CTL_CH1_VOL] = {
318c2ecf20Sopenharmony_ci		.name = "Channel 1 Playback Volume",
328c2ecf20Sopenharmony_ci		.type = SNDRV_CTL_ELEM_TYPE_INTEGER,
338c2ecf20Sopenharmony_ci		.tlv = wm8766_tlv,
348c2ecf20Sopenharmony_ci		.reg1 = WM8766_REG_DACL1,
358c2ecf20Sopenharmony_ci		.reg2 = WM8766_REG_DACR1,
368c2ecf20Sopenharmony_ci		.mask1 = WM8766_VOL_MASK,
378c2ecf20Sopenharmony_ci		.mask2 = WM8766_VOL_MASK,
388c2ecf20Sopenharmony_ci		.max = 0xff,
398c2ecf20Sopenharmony_ci		.flags = WM8766_FLAG_STEREO | WM8766_FLAG_VOL_UPDATE,
408c2ecf20Sopenharmony_ci	},
418c2ecf20Sopenharmony_ci	[WM8766_CTL_CH2_VOL] = {
428c2ecf20Sopenharmony_ci		.name = "Channel 2 Playback Volume",
438c2ecf20Sopenharmony_ci		.type = SNDRV_CTL_ELEM_TYPE_INTEGER,
448c2ecf20Sopenharmony_ci		.tlv = wm8766_tlv,
458c2ecf20Sopenharmony_ci		.reg1 = WM8766_REG_DACL2,
468c2ecf20Sopenharmony_ci		.reg2 = WM8766_REG_DACR2,
478c2ecf20Sopenharmony_ci		.mask1 = WM8766_VOL_MASK,
488c2ecf20Sopenharmony_ci		.mask2 = WM8766_VOL_MASK,
498c2ecf20Sopenharmony_ci		.max = 0xff,
508c2ecf20Sopenharmony_ci		.flags = WM8766_FLAG_STEREO | WM8766_FLAG_VOL_UPDATE,
518c2ecf20Sopenharmony_ci	},
528c2ecf20Sopenharmony_ci	[WM8766_CTL_CH3_VOL] = {
538c2ecf20Sopenharmony_ci		.name = "Channel 3 Playback Volume",
548c2ecf20Sopenharmony_ci		.type = SNDRV_CTL_ELEM_TYPE_INTEGER,
558c2ecf20Sopenharmony_ci		.tlv = wm8766_tlv,
568c2ecf20Sopenharmony_ci		.reg1 = WM8766_REG_DACL3,
578c2ecf20Sopenharmony_ci		.reg2 = WM8766_REG_DACR3,
588c2ecf20Sopenharmony_ci		.mask1 = WM8766_VOL_MASK,
598c2ecf20Sopenharmony_ci		.mask2 = WM8766_VOL_MASK,
608c2ecf20Sopenharmony_ci		.max = 0xff,
618c2ecf20Sopenharmony_ci		.flags = WM8766_FLAG_STEREO | WM8766_FLAG_VOL_UPDATE,
628c2ecf20Sopenharmony_ci	},
638c2ecf20Sopenharmony_ci	[WM8766_CTL_CH1_SW] = {
648c2ecf20Sopenharmony_ci		.name = "Channel 1 Playback Switch",
658c2ecf20Sopenharmony_ci		.type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
668c2ecf20Sopenharmony_ci		.reg1 = WM8766_REG_DACCTRL2,
678c2ecf20Sopenharmony_ci		.mask1 = WM8766_DAC2_MUTE1,
688c2ecf20Sopenharmony_ci		.flags = WM8766_FLAG_INVERT,
698c2ecf20Sopenharmony_ci	},
708c2ecf20Sopenharmony_ci	[WM8766_CTL_CH2_SW] = {
718c2ecf20Sopenharmony_ci		.name = "Channel 2 Playback Switch",
728c2ecf20Sopenharmony_ci		.type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
738c2ecf20Sopenharmony_ci		.reg1 = WM8766_REG_DACCTRL2,
748c2ecf20Sopenharmony_ci		.mask1 = WM8766_DAC2_MUTE2,
758c2ecf20Sopenharmony_ci		.flags = WM8766_FLAG_INVERT,
768c2ecf20Sopenharmony_ci	},
778c2ecf20Sopenharmony_ci	[WM8766_CTL_CH3_SW] = {
788c2ecf20Sopenharmony_ci		.name = "Channel 3 Playback Switch",
798c2ecf20Sopenharmony_ci		.type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
808c2ecf20Sopenharmony_ci		.reg1 = WM8766_REG_DACCTRL2,
818c2ecf20Sopenharmony_ci		.mask1 = WM8766_DAC2_MUTE3,
828c2ecf20Sopenharmony_ci		.flags = WM8766_FLAG_INVERT,
838c2ecf20Sopenharmony_ci	},
848c2ecf20Sopenharmony_ci	[WM8766_CTL_PHASE1_SW] = {
858c2ecf20Sopenharmony_ci		.name = "Channel 1 Phase Invert Playback Switch",
868c2ecf20Sopenharmony_ci		.type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
878c2ecf20Sopenharmony_ci		.reg1 = WM8766_REG_IFCTRL,
888c2ecf20Sopenharmony_ci		.mask1 = WM8766_PHASE_INVERT1,
898c2ecf20Sopenharmony_ci	},
908c2ecf20Sopenharmony_ci	[WM8766_CTL_PHASE2_SW] = {
918c2ecf20Sopenharmony_ci		.name = "Channel 2 Phase Invert Playback Switch",
928c2ecf20Sopenharmony_ci		.type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
938c2ecf20Sopenharmony_ci		.reg1 = WM8766_REG_IFCTRL,
948c2ecf20Sopenharmony_ci		.mask1 = WM8766_PHASE_INVERT2,
958c2ecf20Sopenharmony_ci	},
968c2ecf20Sopenharmony_ci	[WM8766_CTL_PHASE3_SW] = {
978c2ecf20Sopenharmony_ci		.name = "Channel 3 Phase Invert Playback Switch",
988c2ecf20Sopenharmony_ci		.type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
998c2ecf20Sopenharmony_ci		.reg1 = WM8766_REG_IFCTRL,
1008c2ecf20Sopenharmony_ci		.mask1 = WM8766_PHASE_INVERT3,
1018c2ecf20Sopenharmony_ci	},
1028c2ecf20Sopenharmony_ci	[WM8766_CTL_DEEMPH1_SW] = {
1038c2ecf20Sopenharmony_ci		.name = "Channel 1 Deemphasis Playback Switch",
1048c2ecf20Sopenharmony_ci		.type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
1058c2ecf20Sopenharmony_ci		.reg1 = WM8766_REG_DACCTRL2,
1068c2ecf20Sopenharmony_ci		.mask1 = WM8766_DAC2_DEEMP1,
1078c2ecf20Sopenharmony_ci	},
1088c2ecf20Sopenharmony_ci	[WM8766_CTL_DEEMPH2_SW] = {
1098c2ecf20Sopenharmony_ci		.name = "Channel 2 Deemphasis Playback Switch",
1108c2ecf20Sopenharmony_ci		.type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
1118c2ecf20Sopenharmony_ci		.reg1 = WM8766_REG_DACCTRL2,
1128c2ecf20Sopenharmony_ci		.mask1 = WM8766_DAC2_DEEMP2,
1138c2ecf20Sopenharmony_ci	},
1148c2ecf20Sopenharmony_ci	[WM8766_CTL_DEEMPH3_SW] = {
1158c2ecf20Sopenharmony_ci		.name = "Channel 3 Deemphasis Playback Switch",
1168c2ecf20Sopenharmony_ci		.type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
1178c2ecf20Sopenharmony_ci		.reg1 = WM8766_REG_DACCTRL2,
1188c2ecf20Sopenharmony_ci		.mask1 = WM8766_DAC2_DEEMP3,
1198c2ecf20Sopenharmony_ci	},
1208c2ecf20Sopenharmony_ci	[WM8766_CTL_IZD_SW] = {
1218c2ecf20Sopenharmony_ci		.name = "Infinite Zero Detect Playback Switch",
1228c2ecf20Sopenharmony_ci		.type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
1238c2ecf20Sopenharmony_ci		.reg1 = WM8766_REG_DACCTRL1,
1248c2ecf20Sopenharmony_ci		.mask1 = WM8766_DAC_IZD,
1258c2ecf20Sopenharmony_ci	},
1268c2ecf20Sopenharmony_ci	[WM8766_CTL_ZC_SW] = {
1278c2ecf20Sopenharmony_ci		.name = "Zero Cross Detect Playback Switch",
1288c2ecf20Sopenharmony_ci		.type = SNDRV_CTL_ELEM_TYPE_BOOLEAN,
1298c2ecf20Sopenharmony_ci		.reg1 = WM8766_REG_DACCTRL2,
1308c2ecf20Sopenharmony_ci		.mask1 = WM8766_DAC2_ZCD,
1318c2ecf20Sopenharmony_ci		.flags = WM8766_FLAG_INVERT,
1328c2ecf20Sopenharmony_ci	},
1338c2ecf20Sopenharmony_ci};
1348c2ecf20Sopenharmony_ci
1358c2ecf20Sopenharmony_ci/* exported functions */
1368c2ecf20Sopenharmony_ci
1378c2ecf20Sopenharmony_civoid snd_wm8766_init(struct snd_wm8766 *wm)
1388c2ecf20Sopenharmony_ci{
1398c2ecf20Sopenharmony_ci	int i;
1408c2ecf20Sopenharmony_ci	static const u16 default_values[] = {
1418c2ecf20Sopenharmony_ci		0x000, 0x100,
1428c2ecf20Sopenharmony_ci		0x120, 0x000,
1438c2ecf20Sopenharmony_ci		0x000, 0x100, 0x000, 0x100, 0x000,
1448c2ecf20Sopenharmony_ci		0x000, 0x080,
1458c2ecf20Sopenharmony_ci	};
1468c2ecf20Sopenharmony_ci
1478c2ecf20Sopenharmony_ci	memcpy(wm->ctl, snd_wm8766_default_ctl, sizeof(wm->ctl));
1488c2ecf20Sopenharmony_ci
1498c2ecf20Sopenharmony_ci	snd_wm8766_write(wm, WM8766_REG_RESET, 0x00); /* reset */
1508c2ecf20Sopenharmony_ci	udelay(10);
1518c2ecf20Sopenharmony_ci	/* load defaults */
1528c2ecf20Sopenharmony_ci	for (i = 0; i < ARRAY_SIZE(default_values); i++)
1538c2ecf20Sopenharmony_ci		snd_wm8766_write(wm, i, default_values[i]);
1548c2ecf20Sopenharmony_ci}
1558c2ecf20Sopenharmony_ci
1568c2ecf20Sopenharmony_civoid snd_wm8766_resume(struct snd_wm8766 *wm)
1578c2ecf20Sopenharmony_ci{
1588c2ecf20Sopenharmony_ci	int i;
1598c2ecf20Sopenharmony_ci
1608c2ecf20Sopenharmony_ci	for (i = 0; i < WM8766_REG_COUNT; i++)
1618c2ecf20Sopenharmony_ci		snd_wm8766_write(wm, i, wm->regs[i]);
1628c2ecf20Sopenharmony_ci}
1638c2ecf20Sopenharmony_ci
1648c2ecf20Sopenharmony_civoid snd_wm8766_set_if(struct snd_wm8766 *wm, u16 dac)
1658c2ecf20Sopenharmony_ci{
1668c2ecf20Sopenharmony_ci	u16 val = wm->regs[WM8766_REG_IFCTRL] & ~WM8766_IF_MASK;
1678c2ecf20Sopenharmony_ci
1688c2ecf20Sopenharmony_ci	dac &= WM8766_IF_MASK;
1698c2ecf20Sopenharmony_ci	snd_wm8766_write(wm, WM8766_REG_IFCTRL, val | dac);
1708c2ecf20Sopenharmony_ci}
1718c2ecf20Sopenharmony_ci
1728c2ecf20Sopenharmony_civoid snd_wm8766_volume_restore(struct snd_wm8766 *wm)
1738c2ecf20Sopenharmony_ci{
1748c2ecf20Sopenharmony_ci	u16 val = wm->regs[WM8766_REG_DACR1];
1758c2ecf20Sopenharmony_ci	/* restore volume after MCLK stopped */
1768c2ecf20Sopenharmony_ci	snd_wm8766_write(wm, WM8766_REG_DACR1, val | WM8766_VOL_UPDATE);
1778c2ecf20Sopenharmony_ci}
1788c2ecf20Sopenharmony_ci
1798c2ecf20Sopenharmony_ci/* mixer callbacks */
1808c2ecf20Sopenharmony_ci
1818c2ecf20Sopenharmony_cistatic int snd_wm8766_volume_info(struct snd_kcontrol *kcontrol,
1828c2ecf20Sopenharmony_ci				   struct snd_ctl_elem_info *uinfo)
1838c2ecf20Sopenharmony_ci{
1848c2ecf20Sopenharmony_ci	struct snd_wm8766 *wm = snd_kcontrol_chip(kcontrol);
1858c2ecf20Sopenharmony_ci	int n = kcontrol->private_value;
1868c2ecf20Sopenharmony_ci
1878c2ecf20Sopenharmony_ci	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
1888c2ecf20Sopenharmony_ci	uinfo->count = (wm->ctl[n].flags & WM8766_FLAG_STEREO) ? 2 : 1;
1898c2ecf20Sopenharmony_ci	uinfo->value.integer.min = wm->ctl[n].min;
1908c2ecf20Sopenharmony_ci	uinfo->value.integer.max = wm->ctl[n].max;
1918c2ecf20Sopenharmony_ci
1928c2ecf20Sopenharmony_ci	return 0;
1938c2ecf20Sopenharmony_ci}
1948c2ecf20Sopenharmony_ci
1958c2ecf20Sopenharmony_cistatic int snd_wm8766_enum_info(struct snd_kcontrol *kcontrol,
1968c2ecf20Sopenharmony_ci				      struct snd_ctl_elem_info *uinfo)
1978c2ecf20Sopenharmony_ci{
1988c2ecf20Sopenharmony_ci	struct snd_wm8766 *wm = snd_kcontrol_chip(kcontrol);
1998c2ecf20Sopenharmony_ci	int n = kcontrol->private_value;
2008c2ecf20Sopenharmony_ci
2018c2ecf20Sopenharmony_ci	return snd_ctl_enum_info(uinfo, 1, wm->ctl[n].max,
2028c2ecf20Sopenharmony_ci						wm->ctl[n].enum_names);
2038c2ecf20Sopenharmony_ci}
2048c2ecf20Sopenharmony_ci
2058c2ecf20Sopenharmony_cistatic int snd_wm8766_ctl_get(struct snd_kcontrol *kcontrol,
2068c2ecf20Sopenharmony_ci				  struct snd_ctl_elem_value *ucontrol)
2078c2ecf20Sopenharmony_ci{
2088c2ecf20Sopenharmony_ci	struct snd_wm8766 *wm = snd_kcontrol_chip(kcontrol);
2098c2ecf20Sopenharmony_ci	int n = kcontrol->private_value;
2108c2ecf20Sopenharmony_ci	u16 val1, val2;
2118c2ecf20Sopenharmony_ci
2128c2ecf20Sopenharmony_ci	if (wm->ctl[n].get)
2138c2ecf20Sopenharmony_ci		wm->ctl[n].get(wm, &val1, &val2);
2148c2ecf20Sopenharmony_ci	else {
2158c2ecf20Sopenharmony_ci		val1 = wm->regs[wm->ctl[n].reg1] & wm->ctl[n].mask1;
2168c2ecf20Sopenharmony_ci		val1 >>= __ffs(wm->ctl[n].mask1);
2178c2ecf20Sopenharmony_ci		if (wm->ctl[n].flags & WM8766_FLAG_STEREO) {
2188c2ecf20Sopenharmony_ci			val2 = wm->regs[wm->ctl[n].reg2] & wm->ctl[n].mask2;
2198c2ecf20Sopenharmony_ci			val2 >>= __ffs(wm->ctl[n].mask2);
2208c2ecf20Sopenharmony_ci			if (wm->ctl[n].flags & WM8766_FLAG_VOL_UPDATE)
2218c2ecf20Sopenharmony_ci				val2 &= ~WM8766_VOL_UPDATE;
2228c2ecf20Sopenharmony_ci		}
2238c2ecf20Sopenharmony_ci	}
2248c2ecf20Sopenharmony_ci	if (wm->ctl[n].flags & WM8766_FLAG_INVERT) {
2258c2ecf20Sopenharmony_ci		val1 = wm->ctl[n].max - (val1 - wm->ctl[n].min);
2268c2ecf20Sopenharmony_ci		if (wm->ctl[n].flags & WM8766_FLAG_STEREO)
2278c2ecf20Sopenharmony_ci			val2 = wm->ctl[n].max - (val2 - wm->ctl[n].min);
2288c2ecf20Sopenharmony_ci	}
2298c2ecf20Sopenharmony_ci	ucontrol->value.integer.value[0] = val1;
2308c2ecf20Sopenharmony_ci	if (wm->ctl[n].flags & WM8766_FLAG_STEREO)
2318c2ecf20Sopenharmony_ci		ucontrol->value.integer.value[1] = val2;
2328c2ecf20Sopenharmony_ci
2338c2ecf20Sopenharmony_ci	return 0;
2348c2ecf20Sopenharmony_ci}
2358c2ecf20Sopenharmony_ci
2368c2ecf20Sopenharmony_cistatic int snd_wm8766_ctl_put(struct snd_kcontrol *kcontrol,
2378c2ecf20Sopenharmony_ci				  struct snd_ctl_elem_value *ucontrol)
2388c2ecf20Sopenharmony_ci{
2398c2ecf20Sopenharmony_ci	struct snd_wm8766 *wm = snd_kcontrol_chip(kcontrol);
2408c2ecf20Sopenharmony_ci	int n = kcontrol->private_value;
2418c2ecf20Sopenharmony_ci	u16 val, regval1, regval2;
2428c2ecf20Sopenharmony_ci
2438c2ecf20Sopenharmony_ci	/* this also works for enum because value is a union */
2448c2ecf20Sopenharmony_ci	regval1 = ucontrol->value.integer.value[0];
2458c2ecf20Sopenharmony_ci	regval2 = ucontrol->value.integer.value[1];
2468c2ecf20Sopenharmony_ci	if (wm->ctl[n].flags & WM8766_FLAG_INVERT) {
2478c2ecf20Sopenharmony_ci		regval1 = wm->ctl[n].max - (regval1 - wm->ctl[n].min);
2488c2ecf20Sopenharmony_ci		regval2 = wm->ctl[n].max - (regval2 - wm->ctl[n].min);
2498c2ecf20Sopenharmony_ci	}
2508c2ecf20Sopenharmony_ci	if (wm->ctl[n].set)
2518c2ecf20Sopenharmony_ci		wm->ctl[n].set(wm, regval1, regval2);
2528c2ecf20Sopenharmony_ci	else {
2538c2ecf20Sopenharmony_ci		val = wm->regs[wm->ctl[n].reg1] & ~wm->ctl[n].mask1;
2548c2ecf20Sopenharmony_ci		val |= regval1 << __ffs(wm->ctl[n].mask1);
2558c2ecf20Sopenharmony_ci		/* both stereo controls in one register */
2568c2ecf20Sopenharmony_ci		if (wm->ctl[n].flags & WM8766_FLAG_STEREO &&
2578c2ecf20Sopenharmony_ci				wm->ctl[n].reg1 == wm->ctl[n].reg2) {
2588c2ecf20Sopenharmony_ci			val &= ~wm->ctl[n].mask2;
2598c2ecf20Sopenharmony_ci			val |= regval2 << __ffs(wm->ctl[n].mask2);
2608c2ecf20Sopenharmony_ci		}
2618c2ecf20Sopenharmony_ci		snd_wm8766_write(wm, wm->ctl[n].reg1, val);
2628c2ecf20Sopenharmony_ci		/* stereo controls in different registers */
2638c2ecf20Sopenharmony_ci		if (wm->ctl[n].flags & WM8766_FLAG_STEREO &&
2648c2ecf20Sopenharmony_ci				wm->ctl[n].reg1 != wm->ctl[n].reg2) {
2658c2ecf20Sopenharmony_ci			val = wm->regs[wm->ctl[n].reg2] & ~wm->ctl[n].mask2;
2668c2ecf20Sopenharmony_ci			val |= regval2 << __ffs(wm->ctl[n].mask2);
2678c2ecf20Sopenharmony_ci			if (wm->ctl[n].flags & WM8766_FLAG_VOL_UPDATE)
2688c2ecf20Sopenharmony_ci				val |= WM8766_VOL_UPDATE;
2698c2ecf20Sopenharmony_ci			snd_wm8766_write(wm, wm->ctl[n].reg2, val);
2708c2ecf20Sopenharmony_ci		}
2718c2ecf20Sopenharmony_ci	}
2728c2ecf20Sopenharmony_ci
2738c2ecf20Sopenharmony_ci	return 0;
2748c2ecf20Sopenharmony_ci}
2758c2ecf20Sopenharmony_ci
2768c2ecf20Sopenharmony_cistatic int snd_wm8766_add_control(struct snd_wm8766 *wm, int num)
2778c2ecf20Sopenharmony_ci{
2788c2ecf20Sopenharmony_ci	struct snd_kcontrol_new cont;
2798c2ecf20Sopenharmony_ci	struct snd_kcontrol *ctl;
2808c2ecf20Sopenharmony_ci
2818c2ecf20Sopenharmony_ci	memset(&cont, 0, sizeof(cont));
2828c2ecf20Sopenharmony_ci	cont.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
2838c2ecf20Sopenharmony_ci	cont.private_value = num;
2848c2ecf20Sopenharmony_ci	cont.name = wm->ctl[num].name;
2858c2ecf20Sopenharmony_ci	cont.access = SNDRV_CTL_ELEM_ACCESS_READWRITE;
2868c2ecf20Sopenharmony_ci	if (wm->ctl[num].flags & WM8766_FLAG_LIM ||
2878c2ecf20Sopenharmony_ci	    wm->ctl[num].flags & WM8766_FLAG_ALC)
2888c2ecf20Sopenharmony_ci		cont.access |= SNDRV_CTL_ELEM_ACCESS_INACTIVE;
2898c2ecf20Sopenharmony_ci	cont.tlv.p = NULL;
2908c2ecf20Sopenharmony_ci	cont.get = snd_wm8766_ctl_get;
2918c2ecf20Sopenharmony_ci	cont.put = snd_wm8766_ctl_put;
2928c2ecf20Sopenharmony_ci
2938c2ecf20Sopenharmony_ci	switch (wm->ctl[num].type) {
2948c2ecf20Sopenharmony_ci	case SNDRV_CTL_ELEM_TYPE_INTEGER:
2958c2ecf20Sopenharmony_ci		cont.info = snd_wm8766_volume_info;
2968c2ecf20Sopenharmony_ci		cont.access |= SNDRV_CTL_ELEM_ACCESS_TLV_READ;
2978c2ecf20Sopenharmony_ci		cont.tlv.p = wm->ctl[num].tlv;
2988c2ecf20Sopenharmony_ci		break;
2998c2ecf20Sopenharmony_ci	case SNDRV_CTL_ELEM_TYPE_BOOLEAN:
3008c2ecf20Sopenharmony_ci		wm->ctl[num].max = 1;
3018c2ecf20Sopenharmony_ci		if (wm->ctl[num].flags & WM8766_FLAG_STEREO)
3028c2ecf20Sopenharmony_ci			cont.info = snd_ctl_boolean_stereo_info;
3038c2ecf20Sopenharmony_ci		else
3048c2ecf20Sopenharmony_ci			cont.info = snd_ctl_boolean_mono_info;
3058c2ecf20Sopenharmony_ci		break;
3068c2ecf20Sopenharmony_ci	case SNDRV_CTL_ELEM_TYPE_ENUMERATED:
3078c2ecf20Sopenharmony_ci		cont.info = snd_wm8766_enum_info;
3088c2ecf20Sopenharmony_ci		break;
3098c2ecf20Sopenharmony_ci	default:
3108c2ecf20Sopenharmony_ci		return -EINVAL;
3118c2ecf20Sopenharmony_ci	}
3128c2ecf20Sopenharmony_ci	ctl = snd_ctl_new1(&cont, wm);
3138c2ecf20Sopenharmony_ci	if (!ctl)
3148c2ecf20Sopenharmony_ci		return -ENOMEM;
3158c2ecf20Sopenharmony_ci	wm->ctl[num].kctl = ctl;
3168c2ecf20Sopenharmony_ci
3178c2ecf20Sopenharmony_ci	return snd_ctl_add(wm->card, ctl);
3188c2ecf20Sopenharmony_ci}
3198c2ecf20Sopenharmony_ci
3208c2ecf20Sopenharmony_ciint snd_wm8766_build_controls(struct snd_wm8766 *wm)
3218c2ecf20Sopenharmony_ci{
3228c2ecf20Sopenharmony_ci	int err, i;
3238c2ecf20Sopenharmony_ci
3248c2ecf20Sopenharmony_ci	for (i = 0; i < WM8766_CTL_COUNT; i++)
3258c2ecf20Sopenharmony_ci		if (wm->ctl[i].name) {
3268c2ecf20Sopenharmony_ci			err = snd_wm8766_add_control(wm, i);
3278c2ecf20Sopenharmony_ci			if (err < 0)
3288c2ecf20Sopenharmony_ci				return err;
3298c2ecf20Sopenharmony_ci		}
3308c2ecf20Sopenharmony_ci
3318c2ecf20Sopenharmony_ci	return 0;
3328c2ecf20Sopenharmony_ci}
333