summaryrefslogtreecommitdiff
path: root/drivers/codec/wm8960.py
diff options
context:
space:
mode:
authorrobert-hh <robert@hammelrath.com>2022-02-10 13:51:48 +0100
committerDamien George <damien@micropython.org>2022-04-04 16:31:17 +1000
commit56b331ace6b7f01bacdb8b5c66f0f517670abfdc (patch)
treed1a1d1bd2d80ed1a7608fc2db0b73e15ab4aae7c /drivers/codec/wm8960.py
parent7a447e08b23f47fac6ebcbe5d60c4e1d0fe0d157 (diff)
drivers/codec: Add driver for the WM8960 codec.
This codec is assembled for the MIMXRT1xxx_DEV boards and available for WM8960 breakout boards as well. The driver itself has been tested as working with the MIMXRT boards and a Sparkfun WM6890 breakout board. It implements the initialization, basic methods and some enhanced methods like 3D, ALC, soft-mute and deemphasis.
Diffstat (limited to 'drivers/codec/wm8960.py')
-rw-r--r--drivers/codec/wm8960.py753
1 files changed, 753 insertions, 0 deletions
diff --git a/drivers/codec/wm8960.py b/drivers/codec/wm8960.py
new file mode 100644
index 000000000..ad1cb1cbf
--- /dev/null
+++ b/drivers/codec/wm8960.py
@@ -0,0 +1,753 @@
+#
+# Driver class for the WM8960 Codec to be used e.g. with MIMXRT_1xxx Boards.
+# Derived from the NXP SDK drivers.
+#
+# Copyright (c) 2015, Freescale Semiconductor, Inc., (C-Code)
+# Copyright 2016-2021 NXP, (C-Code)
+# All rights reserved.
+#
+# Translated to MicroPython by Robert Hammelrath, 2022
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
+
+import array
+from micropython import const
+
+# Define the register addresses of WM8960.
+_LINVOL = const(0x0)
+_RINVOL = const(0x1)
+_LOUT1 = const(0x2)
+_ROUT1 = const(0x3)
+_CLOCK1 = const(0x4)
+_DACCTL1 = const(0x5)
+_DACCTL2 = const(0x6)
+_IFACE1 = const(0x7)
+_CLOCK2 = const(0x8)
+_IFACE2 = const(0x9)
+_LDAC = const(0xA)
+_RDAC = const(0xB)
+_RESET = const(0xF)
+_3D = const(0x10)
+_ALC1 = const(0x11)
+_ALC2 = const(0x12)
+_ALC3 = const(0x13)
+_NOISEG = const(0x14)
+_LADC = const(0x15)
+_RADC = const(0x16)
+_ADDCTL1 = const(0x17)
+# Register _ADDCTL2 = const(0x18)
+_POWER1 = const(0x19)
+_POWER2 = const(0x1A)
+_ADDCTL3 = const(0x1B)
+# Register _APOP1 = const(0x1C)
+# Register _APOP2 = const(0x1D)
+_LINPATH = const(0x20)
+_RINPATH = const(0x21)
+_LOUTMIX = const(0x22)
+_ROUTMIX = const(0x25)
+_MONOMIX1 = const(0x26)
+_MONOMIX2 = const(0x27)
+_LOUT2 = const(0x28)
+_ROUT2 = const(0x29)
+_MONO = const(0x2A)
+_INBMIX1 = const(0x2B)
+_INBMIX2 = const(0x2C)
+_BYPASS1 = const(0x2D)
+_BYPASS2 = const(0x2E)
+_POWER3 = const(0x2F)
+_ADDCTL4 = const(0x30)
+_CLASSD1 = const(0x31)
+# Register _CLASSD3 = const(0x33)
+_PLL1 = const(0x34)
+_PLL2 = const(0x35)
+_PLL3 = const(0x36)
+_PLL4 = const(0x37)
+
+# WM8960 PLLN range */
+_PLL_N_MIN_VALUE = const(6)
+_PLL_N_MAX_VALUE = const(12)
+
+# WM8960 CLOCK2 bits
+_CLOCK2_BCLK_DIV_MASK = const(0x0F)
+_CLOCK2_DCLK_DIV_MASK = const(0x1C0)
+_CLOCK2_DCLK_DIV_SHIFT = const(0x06)
+
+# Register _IFACE1
+_IFACE1_FORMAT_MASK = const(0x03)
+_IFACE1_WL_MASK = const(0x0C)
+_IFACE1_WL_SHIFT = const(0x02)
+_IFACE1_LRP_MASK = const(0x10)
+_IFACE1_MS_MASK = const(0x40)
+_IFACE1_DLRSWAP_MASK = const(0x20)
+_IFACE1_ALRSWAP_MASK = const(0x100)
+
+# Register _POWER1
+_POWER1_VREF_MASK = const(0x40)
+_POWER1_VREF_SHIFT = const(0x06)
+_POWER1_AINL_MASK = const(0x20)
+_POWER1_AINR_MASK = const(0x10)
+_POWER1_ADCL_MASK = const(0x08)
+_POWER1_ADCR_MASK = const(0x0)
+_POWER1_MICB_MASK = const(0x02)
+_POWER1_MICB_SHIFT = const(0x01)
+
+# Register _POWER2
+_POWER2_DACL_MASK = const(0x100)
+_POWER2_DACR_MASK = const(0x80)
+_POWER2_LOUT1_MASK = const(0x40)
+_POWER2_ROUT1_MASK = const(0x20)
+_POWER2_SPKL_MASK = const(0x10)
+_POWER2_SPKR_MASK = const(0x08)
+_POWER3_LMIC_MASK = const(0x20)
+_POWER3_RMIC_MASK = const(0x10)
+_POWER3_LOMIX_MASK = const(0x08)
+_POWER3_ROMIX_MASK = const(0x04)
+
+# Register _DACCTL1 .. 3
+_DACCTL1_MONOMIX_MASK = const(0x10)
+_DACCTL1_MONOMIX_SHIFT = const(0x4)
+_DACCTL1_DACMU_MASK = const(0x08)
+_DACCTL1_DEEM_MASK = const(0x06)
+_DACCTL1_DEEM_SHIFT = const(0x01)
+_DACCTL2_DACSMM_MASK = const(0x08)
+_DACCTL2_DACMR_MASK = const(0x04)
+_DACCTL3_ALCSR_MASK = const(0x07)
+
+# _WM8060_ALC1 .. 3
+_ALC_CHANNEL_MASK = const(0x180)
+_ALC_CHANNEL_SHIFT = const(0x7)
+_ALC_MODE_MASK = const(0x100)
+_ALC_MODE_SHIFT = const(0x8)
+_ALC_GAIN_MASK = const(0x70)
+_ALC_GAIN_SHIFT = const(0x4)
+_ALC_TARGET_MASK = const(0x0F)
+_ALC_ATTACK_MASK = const(0x0F)
+_ALC_DECAY_MASK = const(0xF0)
+_ALC_DECAY_SHIFT = const(4)
+_ALC_HOLD_MASK = const(0xF)
+
+# Register _NOISEG
+_NOISEG_LEVEL_SHIFT = const(3)
+
+_I2C_ADDR = const(0x1A)
+
+# WM8960 maximum volume values
+_MAX_VOLUME_ADC = const(0xFF)
+_MAX_VOLUME_DAC = const(0xFF)
+_MAX_VOLUME_HEADPHONE = const(0x7F)
+_MAX_VOLUME_LINEIN = const(0x3F)
+_MAX_VOLUME_SPEAKER = const(0x7F)
+
+# Config symbol names
+# Modules
+MODULE_ADC = const(0) # ADC module in WM8960
+MODULE_DAC = const(1) # DAC module in WM8960
+MODULE_VREF = const(2) # VREF module
+MODULE_HEADPHONE = const(3) # Headphone
+MODULE_MIC_BIAS = const(4) # Mic bias
+MODULE_MIC = const(5) # Input Mic
+MODULE_LINE_IN = const(6) # Analog in PGA
+MODULE_LINE_OUT = const(7) # Line out module
+MODULE_SPEAKER = const(8) # Speaker module
+MODULE_OMIX = const(9) # Output mixer
+MODULE_MONO_OUT = const(10) # Mono mix
+
+# Route
+ROUTE_BYPASS = const(0) # LINEIN->Headphone.
+ROUTE_PLAYBACK = const(1) # I2SIN->DAC->Headphone.
+ROUTE_PLAYBACK_RECORD = const(2) # I2SIN->DAC->Headphone, LINEIN->ADC->I2SOUT.
+ROUTE_RECORD = const(5) # LINEIN->ADC->I2SOUT.
+
+# Input
+INPUT_CLOSED = const(0) # Input device is closed
+INPUT_MIC1 = const(1) # Input as single ended mic, only use L/RINPUT1
+INPUT_MIC2 = const(2) # Input as diff. mic, use L/RINPUT1 and L/RINPUT2
+INPUT_MIC3 = const(3) # Input as diff. mic, use L/RINPUT1 and L/RINPUT3
+INPUT_LINE2 = const(4) # Input as line input, only use L/RINPUT2
+INPUT_LINE3 = const(5) # Input as line input, only use L/RINPUT3
+
+# ADC sync input
+SYNC_ADC = const(0) # Use ADCLRC pin for ADC sync
+SYNC_DAC = const(1) # used DACLRC pin for ADC sync
+
+# Protocol type
+BUS_I2S = const(2) # I2S type
+BUS_LEFT_JUSTIFIED = const(1) # Left justified mode
+BUS_RIGHT_JUSTIFIED = const(0) # Right justified mode
+BUS_PCMA = const(3) # PCM A mode
+BUS_PCMB = const(3 | (1 << 4)) # PCM B mode
+
+# Channel swap
+SWAP_NONE = const(0)
+SWAP_INPUT = const(1)
+SWAP_OUTPUT = const(2)
+
+# Mute settings
+MUTE_FAST = const(0)
+MUTE_SLOW = const(1)
+
+# ALC settings
+ALC_OFF = const(0)
+ALC_RIGHT = const(1)
+ALC_LEFT = const(2)
+ALC_STEREO = const(3)
+ALC_MODE = const(0) # ALC mode
+ALC_LIMITER = const(1) # Limiter mode
+
+# Clock Source
+SYSCLK_MCLK = const(0) # sysclk source from external MCLK
+SYSCLK_PLL = const(1) # sysclk source from internal PLL
+
+
+class Regs:
+ # register cache of 56 register. Since registers cannot be read back, they are
+ # kept in the table for modification
+ # fmt: off
+ cache = array.array("H", (
+ 0x0097, 0x0097, 0x0000, 0x0000, 0x0000, 0x0008, 0x0000,
+ 0x000a, 0x01c0, 0x0000, 0x00ff, 0x00ff, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x007b, 0x0100, 0x0032, 0x0000,
+ 0x00c3, 0x00c3, 0x01c0, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0000, 0x0000, 0x0000, 0x0000, 0x0100, 0x0100, 0x0050,
+ 0x0050, 0x0050, 0x0050, 0x0000, 0x0000, 0x0000, 0x0000,
+ 0x0040, 0x0000, 0x0000, 0x0050, 0x0050, 0x0000, 0x0002,
+ 0x0037, 0x004d, 0x0080, 0x0008, 0x0031, 0x0026, 0x00e9
+ ))
+ # fmt: on
+
+ def __init__(self, i2c, i2c_address=_I2C_ADDR):
+ self.value_buffer = bytearray(2)
+ self.i2c = i2c
+ self.i2c_address = i2c_address
+
+ def __getitem__(self, reg):
+ return self.cache[reg]
+
+ def __setitem__(self, reg, value):
+ if type(reg) is tuple:
+ if type(value) is tuple:
+ self[reg[0]] = value[0]
+ self[reg[1]] = value[1]
+ else:
+ self[reg[0]] = value
+ self[reg[1]] = value
+ else:
+ if type(value) is tuple:
+ val = (self.cache[reg] & (~value[0] & 0xFFFF)) | value[1]
+ else:
+ val = value
+ self.cache[reg] = val
+ self.value_buffer[0] = (reg << 1) | ((val >> 8) & 0x01)
+ self.value_buffer[1] = val & 0xFF
+ self.i2c.writeto(self.i2c_address, self.value_buffer)
+
+
+class WM8960:
+
+ _bit_clock_divider_table = {
+ 2: 0,
+ 3: 1,
+ 4: 2,
+ 6: 3,
+ 8: 4,
+ 11: 5,
+ 12: 6,
+ 16: 7,
+ 22: 8,
+ 24: 9,
+ 32: 10,
+ 44: 11,
+ 48: 12,
+ }
+
+ _dac_divider_table = {
+ 1.0 * 256: 0b000,
+ 1.5 * 256: 0b001,
+ 2 * 256: 0b010,
+ 3 * 256: 0b011,
+ 4 * 256: 0b100,
+ 5.5 * 256: 0b101,
+ 6 * 256: 0b110,
+ }
+
+ _audio_word_length_table = {
+ 16: 0b00,
+ 20: 0b01,
+ 24: 0b10,
+ 32: 0b11,
+ }
+
+ _alc_sample_rate_table = {
+ 48000: 0,
+ 44100: 0,
+ 32000: 1,
+ 24000: 2,
+ 22050: 2,
+ 16000: 3,
+ 12000: 4,
+ 11025: 4,
+ 8000: 5,
+ }
+
+ _volume_config_table = {
+ MODULE_ADC: (_MAX_VOLUME_ADC, _LADC, 0x100),
+ MODULE_DAC: (_MAX_VOLUME_DAC, _LDAC, 0x100),
+ MODULE_HEADPHONE: (_MAX_VOLUME_HEADPHONE, _LOUT1, 0x180),
+ MODULE_LINE_IN: (_MAX_VOLUME_LINEIN, _LINVOL, 0x140),
+ MODULE_SPEAKER: (_MAX_VOLUME_SPEAKER, _LOUT2, 0x180),
+ }
+
+ _input_config_table = {
+ INPUT_CLOSED: None,
+ INPUT_MIC1: (0x138, 0x117),
+ INPUT_MIC2: (0x178, 0x117),
+ INPUT_MIC3: (0x1B8, 0x117),
+ INPUT_LINE2: (0, 0xE),
+ INPUT_LINE3: (0, 0x70),
+ }
+
+ def __init__(
+ self,
+ i2c,
+ sample_rate=16000,
+ bits=16,
+ swap=SWAP_NONE,
+ route=ROUTE_PLAYBACK_RECORD,
+ left_input=INPUT_MIC3,
+ right_input=INPUT_MIC2,
+ sysclk_source=SYSCLK_MCLK,
+ mclk_freq=None,
+ primary=False,
+ adc_sync=SYNC_DAC,
+ protocol=BUS_I2S,
+ i2c_address=_I2C_ADDR,
+ ):
+ self.regs = regs = Regs(i2c, i2c_address)
+ self.sample_rate = sample_rate
+
+ # check parameter consistency and set the sysclk value
+ if sysclk_source == SYSCLK_PLL:
+ if sample_rate in (11025, 22050, 44100):
+ sysclk = 11289600
+ else:
+ sysclk = 12288000
+ if sysclk < sample_rate * 256:
+ sysclk = sample_rate * 256
+ if mclk_freq is None:
+ mclk_freq = sysclk
+ else: # sysclk_source == SYSCLK_MCLK
+ if mclk_freq is None:
+ mclk_freq = sample_rate * 256
+ sysclk = mclk_freq
+
+ regs[_RESET] = 0x00
+ # VMID=50K, Enable VREF, AINL, AINR, ADCL and ADCR
+ # I2S_IN (bit 0), I2S_OUT (bit 1), DAP (bit 4), DAC (bit 5), ADC (bit 6) are powered on
+ regs[_POWER1] = 0xFE
+ # Enable DACL, DACR, LOUT1, ROUT1, PLL down, SPKL, SPKR
+ regs[_POWER2] = 0x1F8
+ # Enable left and right channel input PGA, left and right output mixer
+ regs[_POWER3] = 0x3C
+
+ if adc_sync == SYNC_ADC:
+ # ADC and DAC use different Frame Clock Pins
+ regs[_IFACE2] = 0x00 # ADCLRC 0x00:Input 0x40:output.
+ else:
+ # ADC and DAC use the same Frame Clock Pin
+ regs[_IFACE2] = 0x40 # ADCLRC 0x00:Input 0x40:output.
+ self.set_data_route(route)
+ self.set_protocol(protocol)
+
+ if sysclk_source == SYSCLK_PLL:
+ self.set_internal_pll_config(mclk_freq, sysclk)
+ if primary:
+ self.set_master_clock(sysclk, sample_rate, bits)
+ # set master bit.
+ self.regs[_IFACE1] = (0, _IFACE1_MS_MASK)
+
+ self.set_speaker_clock(sysclk)
+
+ # swap channels
+ if swap & SWAP_INPUT:
+ regs[_IFACE1] = (0, _IFACE1_ALRSWAP_MASK)
+ if swap & SWAP_OUTPUT:
+ regs[_IFACE1] = (0, _IFACE1_DLRSWAP_MASK)
+
+ self.set_left_input(left_input)
+ self.set_right_input(right_input)
+
+ regs[_ADDCTL1] = 0x0C0
+ regs[_ADDCTL4] = 0x60 # Set GPIO1 to 0.
+
+ regs[_BYPASS1] = regs[_BYPASS2] = 0x0
+ # ADC volume, 0dB
+ regs[_LADC, _RADC] = 0x1C3
+ # Digital DAC volume, 0dB
+ regs[_LDAC, _RDAC] = 0x1FF
+ # Headphone volume, LOUT1 and ROUT1, 0dB
+ regs[_LOUT1, _ROUT1] = 0x16F
+ # speaker volume 6dB
+ regs[_LOUT2, _ROUT2] = 0x1FF
+ # enable class D output
+ regs[_CLASSD1] = 0xF7
+ # Unmute DAC.
+ regs[_DACCTL1] = 0x0000
+ # Input PGA volume 0 dB
+ regs[_LINVOL, _RINVOL] = 0x117
+
+ self.config_data_format(sysclk, sample_rate, bits)
+
+ def deinit(self):
+
+ self.set_module(MODULE_ADC, False)
+ self.set_module(MODULE_DAC, False)
+ self.set_module(MODULE_VREF, False)
+ self.set_module(MODULE_LINE_IN, False)
+ self.set_module(MODULE_LINE_OUT, False)
+ self.set_module(MODULE_SPEAKER, False)
+
+ def set_internal_pll_config(self, input_mclk, output_clk):
+ regs = self.regs
+ pllF2 = output_clk * 4
+ pll_prescale = 0
+ sysclk_div = 1
+ frac_mode = 0
+
+ # disable PLL power
+ regs[_POWER2] = (1, 0)
+ regs[_CLOCK1] = (7, 0)
+
+ pllN = pllF2 // input_mclk
+ if pllN < _PLL_N_MIN_VALUE:
+ input_mclk //= 2
+ pll_prescale = 1
+ pllN = pllF2 // input_mclk
+ if pllN < _PLL_N_MIN_VALUE:
+ sysclk_div = 2
+ pllF2 *= 2
+ pllN = pllF2 // input_mclk
+
+ if (pllN < _PLL_N_MIN_VALUE) or (pllN > _PLL_N_MAX_VALUE):
+ raise ValueError("Invalid MCLK vs. sysclk ratio")
+
+ pllK = ((pllF2 % input_mclk) * (1 << 24)) // input_mclk
+ if pllK != 0:
+ frac_mode = 1
+
+ regs[_PLL1] = (frac_mode << 5) | (pll_prescale << 4) | (pllN & 0x0F)
+ regs[_PLL2] = (pllK >> 16) & 0xFF
+ regs[_PLL3] = (pllK >> 8) & 0xFF
+ regs[_PLL4] = pllK & 0xFF
+ # enable PLL power
+ regs[_POWER2] = (1, 1)
+ regs[_CLOCK1] = (7, ((0 if sysclk_div == 1 else sysclk_div) << 1) | 1)
+
+ def set_master_clock(self, sysclk, sample_rate, bit_width):
+ bit_clock_divider = (sysclk * 2) // (sample_rate * bit_width * 2)
+ try:
+ reg_divider = self._bit_clock_divider_table[bit_clock_divider]
+ except:
+ raise ValueError("Invalid ratio of sysclk sample rate and bits")
+ # configure the master bit clock divider
+ self.regs[_CLOCK2] = (_CLOCK2_BCLK_DIV_MASK, reg_divider)
+
+ def set_speaker_clock(self, sysclk):
+ speaker_divider_table = (1.5, 2, 3, 4, 6, 8, 12, 16)
+ for val in range(8):
+ divider = speaker_divider_table[val]
+ f = sysclk / divider
+ if 500_000 < f < 1_000_000:
+ break
+ else:
+ val = 7
+ self.regs[_CLOCK2] = (
+ _CLOCK2_DCLK_DIV_MASK,
+ val << _CLOCK2_DCLK_DIV_SHIFT,
+ )
+
+ def set_module(self, module, is_enabled):
+
+ is_enabled = 1 if is_enabled else 0
+ regs = self.regs
+
+ if module == MODULE_ADC:
+
+ regs[_POWER1] = (
+ _POWER1_ADCL_MASK | _POWER1_ADCR_MASK,
+ (_POWER1_ADCL_MASK | _POWER1_ADCR_MASK) * is_enabled,
+ )
+
+ elif module == MODULE_DAC:
+
+ regs[_POWER2] = (
+ _POWER2_DACL_MASK | _POWER2_DACR_MASK,
+ (_POWER2_DACL_MASK | _POWER2_DACR_MASK) * is_enabled,
+ )
+
+ elif module == MODULE_VREF:
+
+ regs[_POWER1] = (
+ _POWER1_VREF_MASK,
+ (is_enabled << _POWER1_VREF_SHIFT),
+ )
+
+ elif module == MODULE_LINE_IN:
+
+ regs[_POWER1] = (
+ _POWER1_AINL_MASK | _POWER1_AINR_MASK,
+ (_POWER1_AINL_MASK | _POWER1_AINR_MASK) * is_enabled,
+ )
+ regs[_POWER3] = (
+ _POWER3_LMIC_MASK | _POWER3_RMIC_MASK,
+ (_POWER3_LMIC_MASK | _POWER3_RMIC_MASK) * is_enabled,
+ )
+
+ elif module == MODULE_LINE_OUT:
+
+ regs[_POWER2] = (
+ _POWER2_LOUT1_MASK | _POWER2_ROUT1_MASK,
+ (_POWER2_LOUT1_MASK | _POWER2_ROUT1_MASK) * is_enabled,
+ )
+
+ elif module == MODULE_MIC_BIAS:
+
+ regs[_POWER1] = (
+ _POWER1_MICB_MASK,
+ (is_enabled << _POWER1_MICB_SHIFT),
+ )
+
+ elif module == MODULE_SPEAKER:
+
+ regs[_POWER2] = (
+ _POWER2_SPKL_MASK | _POWER2_SPKR_MASK,
+ (_POWER2_SPKL_MASK | _POWER2_SPKR_MASK) * is_enabled,
+ )
+ regs[_CLASSD1] = 0xF7
+
+ elif module == MODULE_OMIX:
+
+ regs[_POWER3] = (
+ _POWER3_LOMIX_MASK | _POWER3_ROMIX_MASK,
+ (_POWER3_LOMIX_MASK | _POWER3_ROMIX_MASK) * is_enabled,
+ )
+
+ elif module == MODULE_MONO_OUT:
+
+ regs[_MONOMIX1] = regs[_MONOMIX2] = is_enabled << 7
+ regs[_MONO] = is_enabled << 6
+
+ else:
+ raise ValueError("Invalid module")
+
+ def enable_module(self, module):
+ self.set_module(module, True)
+
+ def disable_module(self, module):
+ self.set_module(module, False)
+
+ def set_data_route(self, route):
+ regs = self.regs
+ if route == ROUTE_BYPASS:
+ # Bypass means from line-in to HP
+ # Left LINPUT3 to left output mixer, LINPUT3 left output mixer volume = 0dB
+ # Right RINPUT3 to right output mixer, RINPUT3 right output mixer volume = 0dB
+ regs[_LOUTMIX, _ROUTMIX] = 0x80
+
+ elif route == ROUTE_PLAYBACK:
+ # Data route I2S_IN-> DAC-> HP
+ #
+ # Left DAC to left output mixer, LINPUT3 left output mixer volume = 0dB
+ # Right DAC to right output mixer, RINPUT3 right output mixer volume = 0dB
+ regs[_LOUTMIX, _ROUTMIX] = 0x100
+ regs[_POWER3] = 0x0C
+ # Set power for DAC
+ self.set_module(MODULE_DAC, True)
+ self.set_module(MODULE_OMIX, True)
+ self.set_module(MODULE_LINE_OUT, True)
+
+ elif route == ROUTE_PLAYBACK_RECORD:
+ #
+ # Left DAC to left output mixer, LINPUT3 left output mixer volume = 0dB
+ # Right DAC to right output mixer, RINPUT3 right output mixer volume = 0dB
+ regs[_LOUTMIX, _ROUTMIX] = 0x100
+ regs[_POWER3] = 0x3C
+ self.set_module(MODULE_DAC, True)
+ self.set_module(MODULE_ADC, True)
+ self.set_module(MODULE_LINE_IN, True)
+ self.set_module(MODULE_OMIX, True)
+ self.set_module(MODULE_LINE_OUT, True)
+
+ elif route == ROUTE_RECORD:
+ # LINE_IN->ADC->I2S_OUT
+ # Left and right input boost, LIN3BOOST and RIN3BOOST = 0dB
+ regs[_POWER3] = 0x30
+ # Power up ADC and AIN
+ self.set_module(MODULE_LINE_IN, True)
+ self.set_module(MODULE_ADC, True)
+
+ else:
+ raise ValueError("Invalid route")
+
+ def set_left_input(self, input):
+ if not input in self._input_config_table.keys():
+ raise ValueError("Invalid input")
+
+ input = self._input_config_table[input]
+
+ regs = self.regs
+ if input is None:
+ regs[_POWER1] = (_POWER1_AINL_MASK | _POWER1_ADCL_MASK, 0)
+ elif input[0] == 0:
+ regs[_POWER1] = (0, _POWER1_AINL_MASK | _POWER1_ADCL_MASK)
+ regs[_INBMIX1] = input
+ else:
+ regs[_POWER1] = (0, _POWER1_AINL_MASK | _POWER1_ADCL_MASK | _POWER1_MICB_MASK)
+ regs[_LINPATH] = input[0]
+ regs[_LINVOL] = input[1]
+
+ def set_right_input(self, input):
+ if not input in self._input_config_table.keys():
+ raise ValueError("Invalid input name")
+
+ input = self._input_config_table[input]
+
+ regs = self.regs
+ if input is None:
+ regs[_POWER1] = (_POWER1_AINR_MASK | _POWER1_ADCR_MASK, 0)
+ elif input[0] == 0:
+ regs[_POWER1] = (0, _POWER1_AINL_MASK | _POWER1_ADCR_MASK)
+ regs[_INBMIX2] = input
+ else:
+ regs[_POWER1] = (0, _POWER1_AINR_MASK | _POWER1_ADCR_MASK | _POWER1_MICB_MASK)
+ regs[_RINPATH] = input[0]
+ regs[_RINVOL] = input[1]
+
+ def set_protocol(self, protocol):
+ self.regs[_IFACE1] = (
+ _IFACE1_FORMAT_MASK | _IFACE1_LRP_MASK,
+ protocol,
+ )
+
+ def config_data_format(self, sysclk, sample_rate, bits):
+ # Compute sample rate divider, dac and adc are the same sample rate
+ try:
+ divider = self._dac_divider_table[sysclk // sample_rate]
+ wl = self._audio_word_length_table[bits]
+ except:
+ raise ValueError("Invalid ratio sysclk/sample_rate or invalid bit length")
+
+ self.regs[_CLOCK1] = (0x1F8, divider << 6 | divider << 3)
+ self.regs[_IFACE1] = (_IFACE1_WL_MASK, wl << _IFACE1_WL_SHIFT)
+
+ def volume(self, module, volume_l=None, volume_r=None):
+ if not module in self._volume_config_table.keys():
+ raise ValueError("Invalid module")
+
+ if volume_l is None: # get volume
+ vol_max, regnum, _ = self._volume_config_table[module]
+ return (
+ int((self.regs[regnum] & vol_max) * 100 / vol_max + 0.5),
+ int((self.regs[regnum + 1] & vol_max) * 100 / vol_max + 0.5),
+ )
+ else: # set volume
+ if volume_r is None:
+ volume_r = volume_l
+
+ if not ((0 <= volume_l <= 100) and (0 <= volume_r <= 100)):
+ raise ValueError("Invalid value for volume")
+ elif not module in self._volume_config_table.keys():
+ raise ValueError("Invalid module")
+
+ vol_max, regnum, flags = self._volume_config_table[module]
+ self.regs[regnum] = int(volume_l * vol_max / 100 + 0.5) | flags
+ self.regs[regnum + 1] = int(volume_r * vol_max / 100 + 0.5) | flags
+
+ def mute(self, enable, soft=True, ramp=MUTE_FAST):
+ enable = _DACCTL1_DACMU_MASK if enable else 0
+ soft = _DACCTL2_DACSMM_MASK if soft else 0
+ ramp = _DACCTL2_DACMR_MASK if ramp == MUTE_SLOW else 0
+ self.regs[_DACCTL1] = (_DACCTL1_DACMU_MASK, enable)
+ self.regs[_DACCTL2] = (
+ _DACCTL2_DACSMM_MASK | _DACCTL2_DACMR_MASK,
+ soft | ramp,
+ )
+
+ def expand_3d(self, depth=0):
+ depth &= 0x0F
+ cutoff = 0 if self.sample_rate >= 32000 else 0b1100000
+ self.regs[_3D] = cutoff | depth << 1 | (1 if depth > 0 else 0)
+
+ def mono(self, enable):
+ enable = 1 if enable else 0
+ self.regs[_DACCTL1] = (
+ _DACCTL1_MONOMIX_MASK,
+ enable << _DACCTL1_MONOMIX_SHIFT,
+ )
+
+ def alc_mode(self, channel, mode=ALC_MODE):
+ if mode != ALC_MODE:
+ mode = ALC_LIMITER
+ channel &= 3
+ self.regs[_ALC1] = (
+ _ALC_CHANNEL_MASK,
+ channel << _ALC_CHANNEL_SHIFT,
+ )
+ self.regs[_ALC3] = (_ALC_MODE_MASK, mode << _ALC_MODE_SHIFT)
+ try:
+ rate = _alc_sample_rate_table[self.sample_rate]
+ except:
+ rate = 0
+ self.regs[_ADDCTL3] = (_DACCTL3_ALCSR_MASK, rate)
+
+ def alc_gain(self, target=-12, max_gain=30, min_gain=-17.25, noise_gate=-78):
+ def limit(value, minval, maxval):
+ value = int(value)
+ if value < minval:
+ value = minval
+ if value > maxval:
+ value = maxval
+ return value
+
+ target = limit((16 + (target * 2) // 3), 0, 15)
+ max_gain = limit((max_gain + 12) // 6, 0, 7)
+ min_gain = limit((min_gain * 4 + 69) // 24, 0, 7)
+ noise_gate = limit((noise_gate * 2 + 153) // 3, -1, 31)
+ self.regs[_ALC1] = (
+ _ALC_GAIN_MASK | _ALC_TARGET_MASK,
+ (max_gain << _ALC_GAIN_SHIFT) | target,
+ )
+ self.regs[_ALC2] = (_ALC_GAIN_MASK, (min_gain << _ALC_GAIN_SHIFT))
+ if noise_gate >= 0:
+ self.regs[_NOISEG] = noise_gate << _NOISEG_LEVEL_SHIFT | 1
+ else:
+ self.regs[_NOISEG] = 0
+
+ def alc_time(self, attack=24, decay=192, hold=0):
+ def logb(value, limit):
+ value = int(value)
+ lb = 0
+ while value > 1:
+ value >>= 1
+ lb += 1
+ if lb > limit:
+ lb = limit
+ return lb
+
+ attack = logb(attack / 6, 7)
+ decay = logb(decay / 24, 7)
+ hold = logb((hold * 3) / 8, 15)
+ self.regs[_ALC2] = (_ALC_HOLD_MASK, hold)
+ self.regs[_ALC3] = (
+ _ALC_DECAY_MASK | _ALC_ATTACK_MASK,
+ (decay << _ALC_DECAY_SHIFT) | attack,
+ )
+
+ def deemphasis(self, enable):
+ deem_table = (32000, 44100, 48000)
+ enable = not not enable
+ if enable and self.sample_rate in deem_table:
+ val = deem_table.index(self.sample_rate) + 1
+ else:
+ val = 0
+ self.regs[_DACCTL1] = (_DACCTL1_DEEM_MASK, val << _DACCTL1_DEEM_SHIFT)