diff options
author | 2023-02-21 18:24:12 -0800 | |
---|---|---|
committer | 2023-02-21 18:24:12 -0800 | |
commit | 5b7c4cabbb65f5c469464da6c5f614cbd7f730f2 (patch) | |
tree | cc5c2d0a898769fd59549594fedb3ee6f84e59a0 /sound/pci/ice1712/wm8776.c | |
download | linux-5b7c4cabbb65f5c469464da6c5f614cbd7f730f2.tar.gz linux-5b7c4cabbb65f5c469464da6c5f614cbd7f730f2.zip |
Merge tag 'net-next-6.3' of git://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-nextgrafted
Pull networking updates from Jakub Kicinski:
"Core:
- Add dedicated kmem_cache for typical/small skb->head, avoid having
to access struct page at kfree time, and improve memory use.
- Introduce sysctl to set default RPS configuration for new netdevs.
- Define Netlink protocol specification format which can be used to
describe messages used by each family and auto-generate parsers.
Add tools for generating kernel data structures and uAPI headers.
- Expose all net/core sysctls inside netns.
- Remove 4s sleep in netpoll if carrier is instantly detected on
boot.
- Add configurable limit of MDB entries per port, and port-vlan.
- Continue populating drop reasons throughout the stack.
- Retire a handful of legacy Qdiscs and classifiers.
Protocols:
- Support IPv4 big TCP (TSO frames larger than 64kB).
- Add IP_LOCAL_PORT_RANGE socket option, to control local port range
on socket by socket basis.
- Track and report in procfs number of MPTCP sockets used.
- Support mixing IPv4 and IPv6 flows in the in-kernel MPTCP path
manager.
- IPv6: don't check net.ipv6.route.max_size and rely on garbage
collection to free memory (similarly to IPv4).
- Support Penultimate Segment Pop (PSP) flavor in SRv6 (RFC8986).
- ICMP: add per-rate limit counters.
- Add support for user scanning requests in ieee802154.
- Remove static WEP support.
- Support minimal Wi-Fi 7 Extremely High Throughput (EHT) rate
reporting.
- WiFi 7 EHT channel puncturing support (client & AP).
BPF:
- Add a rbtree data structure following the "next-gen data structure"
precedent set by recently added linked list, that is, by using
kfunc + kptr instead of adding a new BPF map type.
- Expose XDP hints via kfuncs with initial support for RX hash and
timestamp metadata.
- Add BPF_F_NO_TUNNEL_KEY extension to bpf_skb_set_tunnel_key to
better support decap on GRE tunnel devices not operating in collect
metadata.
- Improve x86 JIT's codegen for PROBE_MEM runtime error checks.
- Remove the need for trace_printk_lock for bpf_trace_printk and
bpf_trace_vprintk helpers.
- Extend libbpf's bpf_tracing.h support for tracing arguments of
kprobes/uprobes and syscall as a special case.
- Significantly reduce the search time for module symbols by
livepatch and BPF.
- Enable cpumasks to be used as kptrs, which is useful for tracing
programs tracking which tasks end up running on which CPUs in
different time intervals.
- Add support for BPF trampoline on s390x and riscv64.
- Add capability to export the XDP features supported by the NIC.
- Add __bpf_kfunc tag for marking kernel functions as kfuncs.
- Add cgroup.memory=nobpf kernel parameter option to disable BPF
memory accounting for container environments.
Netfilter:
- Remove the CLUSTERIP target. It has been marked as obsolete for
years, and we still have WARN splats wrt races of the out-of-band
/proc interface installed by this target.
- Add 'destroy' commands to nf_tables. They are identical to the
existing 'delete' commands, but do not return an error if the
referenced object (set, chain, rule...) did not exist.
Driver API:
- Improve cpumask_local_spread() locality to help NICs set the right
IRQ affinity on AMD platforms.
- Separate C22 and C45 MDIO bus transactions more clearly.
- Introduce new DCB table to control DSCP rewrite on egress.
- Support configuration of Physical Layer Collision Avoidance (PLCA)
Reconciliation Sublayer (RS) (802.3cg-2019). Modern version of
shared medium Ethernet.
- Support for MAC Merge layer (IEEE 802.3-2018 clause 99). Allowing
preemption of low priority frames by high priority frames.
- Add support for controlling MACSec offload using netlink SET.
- Rework devlink instance refcounts to allow registration and
de-registration under the instance lock. Split the code into
multiple files, drop some of the unnecessarily granular locks and
factor out common parts of netlink operation handling.
- Add TX frame aggregation parameters (for USB drivers).
- Add a new attr TCA_EXT_WARN_MSG to report TC (offload) warning
messages with notifications for debug.
- Allow offloading of UDP NEW connections via act_ct.
- Add support for per action HW stats in TC.
- Support hardware miss to TC action (continue processing in SW from
a specific point in the action chain).
- Warn if old Wireless Extension user space interface is used with
modern cfg80211/mac80211 drivers. Do not support Wireless
Extensions for Wi-Fi 7 devices at all. Everyone should switch to
using nl80211 interface instead.
- Improve the CAN bit timing configuration. Use extack to return
error messages directly to user space, update the SJW handling,
including the definition of a new default value that will benefit
CAN-FD controllers, by increasing their oscillator tolerance.
New hardware / drivers:
- Ethernet:
- nVidia BlueField-3 support (control traffic driver)
- Ethernet support for imx93 SoCs
- Motorcomm yt8531 gigabit Ethernet PHY
- onsemi NCN26000 10BASE-T1S PHY (with support for PLCA)
- Microchip LAN8841 PHY (incl. cable diagnostics and PTP)
- Amlogic gxl MDIO mux
- WiFi:
- RealTek RTL8188EU (rtl8xxxu)
- Qualcomm Wi-Fi 7 devices (ath12k)
- CAN:
- Renesas R-Car V4H
Drivers:
- Bluetooth:
- Set Per Platform Antenna Gain (PPAG) for Intel controllers.
- Ethernet NICs:
- Intel (1G, igc):
- support TSN / Qbv / packet scheduling features of i226 model
- Intel (100G, ice):
- use GNSS subsystem instead of TTY
- multi-buffer XDP support
- extend support for GPIO pins to E823 devices
- nVidia/Mellanox:
- update the shared buffer configuration on PFC commands
- implement PTP adjphase function for HW offset control
- TC support for Geneve and GRE with VF tunnel offload
- more efficient crypto key management method
- multi-port eswitch support
- Netronome/Corigine:
- add DCB IEEE support
- support IPsec offloading for NFP3800
- Freescale/NXP (enetc):
- support XDP_REDIRECT for XDP non-linear buffers
- improve reconfig, avoid link flap and waiting for idle
- support MAC Merge layer
- Other NICs:
- sfc/ef100: add basic devlink support for ef100
- ionic: rx_push mode operation (writing descriptors via MMIO)
- bnxt: use the auxiliary bus abstraction for RDMA
- r8169: disable ASPM and reset bus in case of tx timeout
- cpsw: support QSGMII mode for J721e CPSW9G
- cpts: support pulse-per-second output
- ngbe: add an mdio bus driver
- usbnet: optimize usbnet_bh() by avoiding unnecessary queuing
- r8152: handle devices with FW with NCM support
- amd-xgbe: support 10Mbps, 2.5GbE speeds and rx-adaptation
- virtio-net: support multi buffer XDP
- virtio/vsock: replace virtio_vsock_pkt with sk_buff
- tsnep: XDP support
- Ethernet high-speed switches:
- nVidia/Mellanox (mlxsw):
- add support for latency TLV (in FW control messages)
- Microchip (sparx5):
- separate explicit and implicit traffic forwarding rules, make
the implicit rules always active
- add support for egress DSCP rewrite
- IS0 VCAP support (Ingress Classification)
- IS2 VCAP filters (protos, L3 addrs, L4 ports, flags, ToS
etc.)
- ES2 VCAP support (Egress Access Control)
- support for Per-Stream Filtering and Policing (802.1Q,
8.6.5.1)
- Ethernet embedded switches:
- Marvell (mv88e6xxx):
- add MAB (port auth) offload support
- enable PTP receive for mv88e6390
- NXP (ocelot):
- support MAC Merge layer
- support for the the vsc7512 internal copper phys
- Microchip:
- lan9303: convert to PHYLINK
- lan966x: support TC flower filter statistics
- lan937x: PTP support for KSZ9563/KSZ8563 and LAN937x
- lan937x: support Credit Based Shaper configuration
- ksz9477: support Energy Efficient Ethernet
- other:
- qca8k: convert to regmap read/write API, use bulk operations
- rswitch: Improve TX timestamp accuracy
- Intel WiFi (iwlwifi):
- EHT (Wi-Fi 7) rate reporting
- STEP equalizer support: transfer some STEP (connection to radio
on platforms with integrated wifi) related parameters from the
BIOS to the firmware.
- Qualcomm 802.11ax WiFi (ath11k):
- IPQ5018 support
- Fine Timing Measurement (FTM) responder role support
- channel 177 support
- MediaTek WiFi (mt76):
- per-PHY LED support
- mt7996: EHT (Wi-Fi 7) support
- Wireless Ethernet Dispatch (WED) reset support
- switch to using page pool allocator
- RealTek WiFi (rtw89):
- support new version of Bluetooth co-existance
- Mobile:
- rmnet: support TX aggregation"
* tag 'net-next-6.3' of git://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-next: (1872 commits)
page_pool: add a comment explaining the fragment counter usage
net: ethtool: fix __ethtool_dev_mm_supported() implementation
ethtool: pse-pd: Fix double word in comments
xsk: add linux/vmalloc.h to xsk.c
sefltests: netdevsim: wait for devlink instance after netns removal
selftest: fib_tests: Always cleanup before exit
net/mlx5e: Align IPsec ASO result memory to be as required by hardware
net/mlx5e: TC, Set CT miss to the specific ct action instance
net/mlx5e: Rename CHAIN_TO_REG to MAPPED_OBJ_TO_REG
net/mlx5: Refactor tc miss handling to a single function
net/mlx5: Kconfig: Make tc offload depend on tc skb extension
net/sched: flower: Support hardware miss to tc action
net/sched: flower: Move filter handle initialization earlier
net/sched: cls_api: Support hardware miss to tc action
net/sched: Rename user cookie and act cookie
sfc: fix builds without CONFIG_RTC_LIB
sfc: clean up some inconsistent indentings
net/mlx4_en: Introduce flexible array to silence overflow warning
net: lan966x: Fix possible deadlock inside PTP
net/ulp: Remove redundant ->clone() test in inet_clone_ulp().
...
Diffstat (limited to 'sound/pci/ice1712/wm8776.c')
-rw-r--r-- | sound/pci/ice1712/wm8776.c | 605 |
1 files changed, 605 insertions, 0 deletions
diff --git a/sound/pci/ice1712/wm8776.c b/sound/pci/ice1712/wm8776.c new file mode 100644 index 000000000..6eda86119 --- /dev/null +++ b/sound/pci/ice1712/wm8776.c @@ -0,0 +1,605 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ALSA driver for ICEnsemble VT17xx + * + * Lowlevel functions for WM8776 codec + * + * Copyright (c) 2012 Ondrej Zary <linux@rainbow-software.org> + */ + +#include <linux/delay.h> +#include <sound/core.h> +#include <sound/control.h> +#include <sound/tlv.h> +#include "wm8776.h" + +/* low-level access */ + +static void snd_wm8776_write(struct snd_wm8776 *wm, u16 addr, u16 data) +{ + u8 bus_addr = addr << 1 | data >> 8; /* addr + 9th data bit */ + u8 bus_data = data & 0xff; /* remaining 8 data bits */ + + if (addr < WM8776_REG_RESET) + wm->regs[addr] = data; + wm->ops.write(wm, bus_addr, bus_data); +} + +/* register-level functions */ + +static void snd_wm8776_activate_ctl(struct snd_wm8776 *wm, + const char *ctl_name, + bool active) +{ + struct snd_card *card = wm->card; + struct snd_kcontrol *kctl; + struct snd_kcontrol_volatile *vd; + struct snd_ctl_elem_id elem_id; + unsigned int index_offset; + + memset(&elem_id, 0, sizeof(elem_id)); + strscpy(elem_id.name, ctl_name, sizeof(elem_id.name)); + elem_id.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + kctl = snd_ctl_find_id(card, &elem_id); + if (!kctl) + return; + index_offset = snd_ctl_get_ioff(kctl, &kctl->id); + vd = &kctl->vd[index_offset]; + if (active) + vd->access &= ~SNDRV_CTL_ELEM_ACCESS_INACTIVE; + else + vd->access |= SNDRV_CTL_ELEM_ACCESS_INACTIVE; + snd_ctl_notify(card, SNDRV_CTL_EVENT_MASK_INFO, &kctl->id); +} + +static void snd_wm8776_update_agc_ctl(struct snd_wm8776 *wm) +{ + int i, flags_on = 0, flags_off = 0; + + switch (wm->agc_mode) { + case WM8776_AGC_OFF: + flags_off = WM8776_FLAG_LIM | WM8776_FLAG_ALC; + break; + case WM8776_AGC_LIM: + flags_off = WM8776_FLAG_ALC; + flags_on = WM8776_FLAG_LIM; + break; + case WM8776_AGC_ALC_R: + case WM8776_AGC_ALC_L: + case WM8776_AGC_ALC_STEREO: + flags_off = WM8776_FLAG_LIM; + flags_on = WM8776_FLAG_ALC; + break; + } + + for (i = 0; i < WM8776_CTL_COUNT; i++) + if (wm->ctl[i].flags & flags_off) + snd_wm8776_activate_ctl(wm, wm->ctl[i].name, false); + else if (wm->ctl[i].flags & flags_on) + snd_wm8776_activate_ctl(wm, wm->ctl[i].name, true); +} + +static void snd_wm8776_set_agc(struct snd_wm8776 *wm, u16 agc, u16 nothing) +{ + u16 alc1 = wm->regs[WM8776_REG_ALCCTRL1] & ~WM8776_ALC1_LCT_MASK; + u16 alc2 = wm->regs[WM8776_REG_ALCCTRL2] & ~WM8776_ALC2_LCEN; + + switch (agc) { + case 0: /* Off */ + wm->agc_mode = WM8776_AGC_OFF; + break; + case 1: /* Limiter */ + alc2 |= WM8776_ALC2_LCEN; + wm->agc_mode = WM8776_AGC_LIM; + break; + case 2: /* ALC Right */ + alc1 |= WM8776_ALC1_LCSEL_ALCR; + alc2 |= WM8776_ALC2_LCEN; + wm->agc_mode = WM8776_AGC_ALC_R; + break; + case 3: /* ALC Left */ + alc1 |= WM8776_ALC1_LCSEL_ALCL; + alc2 |= WM8776_ALC2_LCEN; + wm->agc_mode = WM8776_AGC_ALC_L; + break; + case 4: /* ALC Stereo */ + alc1 |= WM8776_ALC1_LCSEL_ALCSTEREO; + alc2 |= WM8776_ALC2_LCEN; + wm->agc_mode = WM8776_AGC_ALC_STEREO; + break; + } + snd_wm8776_write(wm, WM8776_REG_ALCCTRL1, alc1); + snd_wm8776_write(wm, WM8776_REG_ALCCTRL2, alc2); + snd_wm8776_update_agc_ctl(wm); +} + +static void snd_wm8776_get_agc(struct snd_wm8776 *wm, u16 *mode, u16 *nothing) +{ + *mode = wm->agc_mode; +} + +/* mixer controls */ + +static const DECLARE_TLV_DB_SCALE(wm8776_hp_tlv, -7400, 100, 1); +static const DECLARE_TLV_DB_SCALE(wm8776_dac_tlv, -12750, 50, 1); +static const DECLARE_TLV_DB_SCALE(wm8776_adc_tlv, -10350, 50, 1); +static const DECLARE_TLV_DB_SCALE(wm8776_lct_tlv, -1600, 100, 0); +static const DECLARE_TLV_DB_SCALE(wm8776_maxgain_tlv, 0, 400, 0); +static const DECLARE_TLV_DB_SCALE(wm8776_ngth_tlv, -7800, 600, 0); +static const DECLARE_TLV_DB_SCALE(wm8776_maxatten_lim_tlv, -1200, 100, 0); +static const DECLARE_TLV_DB_SCALE(wm8776_maxatten_alc_tlv, -2100, 400, 0); + +static const struct snd_wm8776_ctl snd_wm8776_default_ctl[WM8776_CTL_COUNT] = { + [WM8776_CTL_DAC_VOL] = { + .name = "Master Playback Volume", + .type = SNDRV_CTL_ELEM_TYPE_INTEGER, + .tlv = wm8776_dac_tlv, + .reg1 = WM8776_REG_DACLVOL, + .reg2 = WM8776_REG_DACRVOL, + .mask1 = WM8776_DACVOL_MASK, + .mask2 = WM8776_DACVOL_MASK, + .max = 0xff, + .flags = WM8776_FLAG_STEREO | WM8776_FLAG_VOL_UPDATE, + }, + [WM8776_CTL_DAC_SW] = { + .name = "Master Playback Switch", + .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN, + .reg1 = WM8776_REG_DACCTRL1, + .reg2 = WM8776_REG_DACCTRL1, + .mask1 = WM8776_DAC_PL_LL, + .mask2 = WM8776_DAC_PL_RR, + .flags = WM8776_FLAG_STEREO, + }, + [WM8776_CTL_DAC_ZC_SW] = { + .name = "Master Zero Cross Detect Playback Switch", + .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN, + .reg1 = WM8776_REG_DACCTRL1, + .mask1 = WM8776_DAC_DZCEN, + }, + [WM8776_CTL_HP_VOL] = { + .name = "Headphone Playback Volume", + .type = SNDRV_CTL_ELEM_TYPE_INTEGER, + .tlv = wm8776_hp_tlv, + .reg1 = WM8776_REG_HPLVOL, + .reg2 = WM8776_REG_HPRVOL, + .mask1 = WM8776_HPVOL_MASK, + .mask2 = WM8776_HPVOL_MASK, + .min = 0x2f, + .max = 0x7f, + .flags = WM8776_FLAG_STEREO | WM8776_FLAG_VOL_UPDATE, + }, + [WM8776_CTL_HP_SW] = { + .name = "Headphone Playback Switch", + .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN, + .reg1 = WM8776_REG_PWRDOWN, + .mask1 = WM8776_PWR_HPPD, + .flags = WM8776_FLAG_INVERT, + }, + [WM8776_CTL_HP_ZC_SW] = { + .name = "Headphone Zero Cross Detect Playback Switch", + .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN, + .reg1 = WM8776_REG_HPLVOL, + .reg2 = WM8776_REG_HPRVOL, + .mask1 = WM8776_VOL_HPZCEN, + .mask2 = WM8776_VOL_HPZCEN, + .flags = WM8776_FLAG_STEREO, + }, + [WM8776_CTL_AUX_SW] = { + .name = "AUX Playback Switch", + .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN, + .reg1 = WM8776_REG_OUTMUX, + .mask1 = WM8776_OUTMUX_AUX, + }, + [WM8776_CTL_BYPASS_SW] = { + .name = "Bypass Playback Switch", + .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN, + .reg1 = WM8776_REG_OUTMUX, + .mask1 = WM8776_OUTMUX_BYPASS, + }, + [WM8776_CTL_DAC_IZD_SW] = { + .name = "Infinite Zero Detect Playback Switch", + .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN, + .reg1 = WM8776_REG_DACCTRL1, + .mask1 = WM8776_DAC_IZD, + }, + [WM8776_CTL_PHASE_SW] = { + .name = "Phase Invert Playback Switch", + .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN, + .reg1 = WM8776_REG_PHASESWAP, + .reg2 = WM8776_REG_PHASESWAP, + .mask1 = WM8776_PHASE_INVERTL, + .mask2 = WM8776_PHASE_INVERTR, + .flags = WM8776_FLAG_STEREO, + }, + [WM8776_CTL_DEEMPH_SW] = { + .name = "Deemphasis Playback Switch", + .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN, + .reg1 = WM8776_REG_DACCTRL2, + .mask1 = WM8776_DAC2_DEEMPH, + }, + [WM8776_CTL_ADC_VOL] = { + .name = "Input Capture Volume", + .type = SNDRV_CTL_ELEM_TYPE_INTEGER, + .tlv = wm8776_adc_tlv, + .reg1 = WM8776_REG_ADCLVOL, + .reg2 = WM8776_REG_ADCRVOL, + .mask1 = WM8776_ADC_GAIN_MASK, + .mask2 = WM8776_ADC_GAIN_MASK, + .max = 0xff, + .flags = WM8776_FLAG_STEREO | WM8776_FLAG_VOL_UPDATE, + }, + [WM8776_CTL_ADC_SW] = { + .name = "Input Capture Switch", + .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN, + .reg1 = WM8776_REG_ADCMUX, + .reg2 = WM8776_REG_ADCMUX, + .mask1 = WM8776_ADC_MUTEL, + .mask2 = WM8776_ADC_MUTER, + .flags = WM8776_FLAG_STEREO | WM8776_FLAG_INVERT, + }, + [WM8776_CTL_INPUT1_SW] = { + .name = "AIN1 Capture Switch", + .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN, + .reg1 = WM8776_REG_ADCMUX, + .mask1 = WM8776_ADC_MUX_AIN1, + }, + [WM8776_CTL_INPUT2_SW] = { + .name = "AIN2 Capture Switch", + .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN, + .reg1 = WM8776_REG_ADCMUX, + .mask1 = WM8776_ADC_MUX_AIN2, + }, + [WM8776_CTL_INPUT3_SW] = { + .name = "AIN3 Capture Switch", + .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN, + .reg1 = WM8776_REG_ADCMUX, + .mask1 = WM8776_ADC_MUX_AIN3, + }, + [WM8776_CTL_INPUT4_SW] = { + .name = "AIN4 Capture Switch", + .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN, + .reg1 = WM8776_REG_ADCMUX, + .mask1 = WM8776_ADC_MUX_AIN4, + }, + [WM8776_CTL_INPUT5_SW] = { + .name = "AIN5 Capture Switch", + .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN, + .reg1 = WM8776_REG_ADCMUX, + .mask1 = WM8776_ADC_MUX_AIN5, + }, + [WM8776_CTL_AGC_SEL] = { + .name = "AGC Select Capture Enum", + .type = SNDRV_CTL_ELEM_TYPE_ENUMERATED, + .enum_names = { "Off", "Limiter", "ALC Right", "ALC Left", + "ALC Stereo" }, + .max = 5, /* .enum_names item count */ + .set = snd_wm8776_set_agc, + .get = snd_wm8776_get_agc, + }, + [WM8776_CTL_LIM_THR] = { + .name = "Limiter Threshold Capture Volume", + .type = SNDRV_CTL_ELEM_TYPE_INTEGER, + .tlv = wm8776_lct_tlv, + .reg1 = WM8776_REG_ALCCTRL1, + .mask1 = WM8776_ALC1_LCT_MASK, + .max = 15, + .flags = WM8776_FLAG_LIM, + }, + [WM8776_CTL_LIM_ATK] = { + .name = "Limiter Attack Time Capture Enum", + .type = SNDRV_CTL_ELEM_TYPE_ENUMERATED, + .enum_names = { "0.25 ms", "0.5 ms", "1 ms", "2 ms", "4 ms", + "8 ms", "16 ms", "32 ms", "64 ms", "128 ms", "256 ms" }, + .max = 11, /* .enum_names item count */ + .reg1 = WM8776_REG_ALCCTRL3, + .mask1 = WM8776_ALC3_ATK_MASK, + .flags = WM8776_FLAG_LIM, + }, + [WM8776_CTL_LIM_DCY] = { + .name = "Limiter Decay Time Capture Enum", + .type = SNDRV_CTL_ELEM_TYPE_ENUMERATED, + .enum_names = { "1.2 ms", "2.4 ms", "4.8 ms", "9.6 ms", + "19.2 ms", "38.4 ms", "76.8 ms", "154 ms", "307 ms", + "614 ms", "1.23 s" }, + .max = 11, /* .enum_names item count */ + .reg1 = WM8776_REG_ALCCTRL3, + .mask1 = WM8776_ALC3_DCY_MASK, + .flags = WM8776_FLAG_LIM, + }, + [WM8776_CTL_LIM_TRANWIN] = { + .name = "Limiter Transient Window Capture Enum", + .type = SNDRV_CTL_ELEM_TYPE_ENUMERATED, + .enum_names = { "0 us", "62.5 us", "125 us", "250 us", "500 us", + "1 ms", "2 ms", "4 ms" }, + .max = 8, /* .enum_names item count */ + .reg1 = WM8776_REG_LIMITER, + .mask1 = WM8776_LIM_TRANWIN_MASK, + .flags = WM8776_FLAG_LIM, + }, + [WM8776_CTL_LIM_MAXATTN] = { + .name = "Limiter Maximum Attenuation Capture Volume", + .type = SNDRV_CTL_ELEM_TYPE_INTEGER, + .tlv = wm8776_maxatten_lim_tlv, + .reg1 = WM8776_REG_LIMITER, + .mask1 = WM8776_LIM_MAXATTEN_MASK, + .min = 3, + .max = 12, + .flags = WM8776_FLAG_LIM | WM8776_FLAG_INVERT, + }, + [WM8776_CTL_ALC_TGT] = { + .name = "ALC Target Level Capture Volume", + .type = SNDRV_CTL_ELEM_TYPE_INTEGER, + .tlv = wm8776_lct_tlv, + .reg1 = WM8776_REG_ALCCTRL1, + .mask1 = WM8776_ALC1_LCT_MASK, + .max = 15, + .flags = WM8776_FLAG_ALC, + }, + [WM8776_CTL_ALC_ATK] = { + .name = "ALC Attack Time Capture Enum", + .type = SNDRV_CTL_ELEM_TYPE_ENUMERATED, + .enum_names = { "8.40 ms", "16.8 ms", "33.6 ms", "67.2 ms", + "134 ms", "269 ms", "538 ms", "1.08 s", "2.15 s", + "4.3 s", "8.6 s" }, + .max = 11, /* .enum_names item count */ + .reg1 = WM8776_REG_ALCCTRL3, + .mask1 = WM8776_ALC3_ATK_MASK, + .flags = WM8776_FLAG_ALC, + }, + [WM8776_CTL_ALC_DCY] = { + .name = "ALC Decay Time Capture Enum", + .type = SNDRV_CTL_ELEM_TYPE_ENUMERATED, + .enum_names = { "33.5 ms", "67.0 ms", "134 ms", "268 ms", + "536 ms", "1.07 s", "2.14 s", "4.29 s", "8.58 s", + "17.2 s", "34.3 s" }, + .max = 11, /* .enum_names item count */ + .reg1 = WM8776_REG_ALCCTRL3, + .mask1 = WM8776_ALC3_DCY_MASK, + .flags = WM8776_FLAG_ALC, + }, + [WM8776_CTL_ALC_MAXGAIN] = { + .name = "ALC Maximum Gain Capture Volume", + .type = SNDRV_CTL_ELEM_TYPE_INTEGER, + .tlv = wm8776_maxgain_tlv, + .reg1 = WM8776_REG_ALCCTRL1, + .mask1 = WM8776_ALC1_MAXGAIN_MASK, + .min = 1, + .max = 7, + .flags = WM8776_FLAG_ALC, + }, + [WM8776_CTL_ALC_MAXATTN] = { + .name = "ALC Maximum Attenuation Capture Volume", + .type = SNDRV_CTL_ELEM_TYPE_INTEGER, + .tlv = wm8776_maxatten_alc_tlv, + .reg1 = WM8776_REG_LIMITER, + .mask1 = WM8776_LIM_MAXATTEN_MASK, + .min = 10, + .max = 15, + .flags = WM8776_FLAG_ALC | WM8776_FLAG_INVERT, + }, + [WM8776_CTL_ALC_HLD] = { + .name = "ALC Hold Time Capture Enum", + .type = SNDRV_CTL_ELEM_TYPE_ENUMERATED, + .enum_names = { "0 ms", "2.67 ms", "5.33 ms", "10.6 ms", + "21.3 ms", "42.7 ms", "85.3 ms", "171 ms", "341 ms", + "683 ms", "1.37 s", "2.73 s", "5.46 s", "10.9 s", + "21.8 s", "43.7 s" }, + .max = 16, /* .enum_names item count */ + .reg1 = WM8776_REG_ALCCTRL2, + .mask1 = WM8776_ALC2_HOLD_MASK, + .flags = WM8776_FLAG_ALC, + }, + [WM8776_CTL_NGT_SW] = { + .name = "Noise Gate Capture Switch", + .type = SNDRV_CTL_ELEM_TYPE_BOOLEAN, + .reg1 = WM8776_REG_NOISEGATE, + .mask1 = WM8776_NGAT_ENABLE, + .flags = WM8776_FLAG_ALC, + }, + [WM8776_CTL_NGT_THR] = { + .name = "Noise Gate Threshold Capture Volume", + .type = SNDRV_CTL_ELEM_TYPE_INTEGER, + .tlv = wm8776_ngth_tlv, + .reg1 = WM8776_REG_NOISEGATE, + .mask1 = WM8776_NGAT_THR_MASK, + .max = 7, + .flags = WM8776_FLAG_ALC, + }, +}; + +/* exported functions */ + +void snd_wm8776_init(struct snd_wm8776 *wm) +{ + int i; + static const u16 default_values[] = { + 0x000, 0x100, 0x000, + 0x000, 0x100, 0x000, + 0x000, 0x090, 0x000, 0x000, + 0x022, 0x022, 0x022, + 0x008, 0x0cf, 0x0cf, 0x07b, 0x000, + 0x032, 0x000, 0x0a6, 0x001, 0x001 + }; + + memcpy(wm->ctl, snd_wm8776_default_ctl, sizeof(wm->ctl)); + + snd_wm8776_write(wm, WM8776_REG_RESET, 0x00); /* reset */ + udelay(10); + /* load defaults */ + for (i = 0; i < ARRAY_SIZE(default_values); i++) + snd_wm8776_write(wm, i, default_values[i]); +} + +void snd_wm8776_resume(struct snd_wm8776 *wm) +{ + int i; + + for (i = 0; i < WM8776_REG_COUNT; i++) + snd_wm8776_write(wm, i, wm->regs[i]); +} + +void snd_wm8776_set_power(struct snd_wm8776 *wm, u16 power) +{ + snd_wm8776_write(wm, WM8776_REG_PWRDOWN, power); +} + +void snd_wm8776_volume_restore(struct snd_wm8776 *wm) +{ + u16 val = wm->regs[WM8776_REG_DACRVOL]; + /* restore volume after MCLK stopped */ + snd_wm8776_write(wm, WM8776_REG_DACRVOL, val | WM8776_VOL_UPDATE); +} + +/* mixer callbacks */ + +static int snd_wm8776_volume_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct snd_wm8776 *wm = snd_kcontrol_chip(kcontrol); + int n = kcontrol->private_value; + + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + uinfo->count = (wm->ctl[n].flags & WM8776_FLAG_STEREO) ? 2 : 1; + uinfo->value.integer.min = wm->ctl[n].min; + uinfo->value.integer.max = wm->ctl[n].max; + + return 0; +} + +static int snd_wm8776_enum_info(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_info *uinfo) +{ + struct snd_wm8776 *wm = snd_kcontrol_chip(kcontrol); + int n = kcontrol->private_value; + + return snd_ctl_enum_info(uinfo, 1, wm->ctl[n].max, + wm->ctl[n].enum_names); +} + +static int snd_wm8776_ctl_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_wm8776 *wm = snd_kcontrol_chip(kcontrol); + int n = kcontrol->private_value; + u16 val1, val2; + + if (wm->ctl[n].get) + wm->ctl[n].get(wm, &val1, &val2); + else { + val1 = wm->regs[wm->ctl[n].reg1] & wm->ctl[n].mask1; + val1 >>= __ffs(wm->ctl[n].mask1); + if (wm->ctl[n].flags & WM8776_FLAG_STEREO) { + val2 = wm->regs[wm->ctl[n].reg2] & wm->ctl[n].mask2; + val2 >>= __ffs(wm->ctl[n].mask2); + if (wm->ctl[n].flags & WM8776_FLAG_VOL_UPDATE) + val2 &= ~WM8776_VOL_UPDATE; + } + } + if (wm->ctl[n].flags & WM8776_FLAG_INVERT) { + val1 = wm->ctl[n].max - (val1 - wm->ctl[n].min); + if (wm->ctl[n].flags & WM8776_FLAG_STEREO) + val2 = wm->ctl[n].max - (val2 - wm->ctl[n].min); + } + ucontrol->value.integer.value[0] = val1; + if (wm->ctl[n].flags & WM8776_FLAG_STEREO) + ucontrol->value.integer.value[1] = val2; + + return 0; +} + +static int snd_wm8776_ctl_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_wm8776 *wm = snd_kcontrol_chip(kcontrol); + int n = kcontrol->private_value; + u16 val, regval1, regval2; + + /* this also works for enum because value is a union */ + regval1 = ucontrol->value.integer.value[0]; + regval2 = ucontrol->value.integer.value[1]; + if (wm->ctl[n].flags & WM8776_FLAG_INVERT) { + regval1 = wm->ctl[n].max - (regval1 - wm->ctl[n].min); + regval2 = wm->ctl[n].max - (regval2 - wm->ctl[n].min); + } + if (wm->ctl[n].set) + wm->ctl[n].set(wm, regval1, regval2); + else { + val = wm->regs[wm->ctl[n].reg1] & ~wm->ctl[n].mask1; + val |= regval1 << __ffs(wm->ctl[n].mask1); + /* both stereo controls in one register */ + if (wm->ctl[n].flags & WM8776_FLAG_STEREO && + wm->ctl[n].reg1 == wm->ctl[n].reg2) { + val &= ~wm->ctl[n].mask2; + val |= regval2 << __ffs(wm->ctl[n].mask2); + } + snd_wm8776_write(wm, wm->ctl[n].reg1, val); + /* stereo controls in different registers */ + if (wm->ctl[n].flags & WM8776_FLAG_STEREO && + wm->ctl[n].reg1 != wm->ctl[n].reg2) { + val = wm->regs[wm->ctl[n].reg2] & ~wm->ctl[n].mask2; + val |= regval2 << __ffs(wm->ctl[n].mask2); + if (wm->ctl[n].flags & WM8776_FLAG_VOL_UPDATE) + val |= WM8776_VOL_UPDATE; + snd_wm8776_write(wm, wm->ctl[n].reg2, val); + } + } + + return 0; +} + +static int snd_wm8776_add_control(struct snd_wm8776 *wm, int num) +{ + struct snd_kcontrol_new cont; + struct snd_kcontrol *ctl; + + memset(&cont, 0, sizeof(cont)); + cont.iface = SNDRV_CTL_ELEM_IFACE_MIXER; + cont.private_value = num; + cont.name = wm->ctl[num].name; + cont.access = SNDRV_CTL_ELEM_ACCESS_READWRITE; + if (wm->ctl[num].flags & WM8776_FLAG_LIM || + wm->ctl[num].flags & WM8776_FLAG_ALC) + cont.access |= SNDRV_CTL_ELEM_ACCESS_INACTIVE; + cont.tlv.p = NULL; + cont.get = snd_wm8776_ctl_get; + cont.put = snd_wm8776_ctl_put; + + switch (wm->ctl[num].type) { + case SNDRV_CTL_ELEM_TYPE_INTEGER: + cont.info = snd_wm8776_volume_info; + cont.access |= SNDRV_CTL_ELEM_ACCESS_TLV_READ; + cont.tlv.p = wm->ctl[num].tlv; + break; + case SNDRV_CTL_ELEM_TYPE_BOOLEAN: + wm->ctl[num].max = 1; + if (wm->ctl[num].flags & WM8776_FLAG_STEREO) + cont.info = snd_ctl_boolean_stereo_info; + else + cont.info = snd_ctl_boolean_mono_info; + break; + case SNDRV_CTL_ELEM_TYPE_ENUMERATED: + cont.info = snd_wm8776_enum_info; + break; + default: + return -EINVAL; + } + ctl = snd_ctl_new1(&cont, wm); + if (!ctl) + return -ENOMEM; + + return snd_ctl_add(wm->card, ctl); +} + +int snd_wm8776_build_controls(struct snd_wm8776 *wm) +{ + int err, i; + + for (i = 0; i < WM8776_CTL_COUNT; i++) + if (wm->ctl[i].name) { + err = snd_wm8776_add_control(wm, i); + if (err < 0) + return err; + } + + return 0; +} |