This fixes assorted Sphinx documentation stuff: 1. Add CSS workaround for horizontal stacking glitch in rtd theme 2. Convert plain comments for public constants to doc-comment style so they will get included in the html docs 3. Change Sphinx autodoc's default sort order from alphabetical to groupwise. Now the class methods come first, the properties come next, and the constants go at the end. It's much easier to read this way.
2206 lines
73 KiB
Python
2206 lines
73 KiB
Python
# SPDX-FileCopyrightText: 2025 Liz Clark for Adafruit Industries
|
|
#
|
|
# SPDX-License-Identifier: MIT
|
|
"""
|
|
`adafruit_tlv320`
|
|
================================================================================
|
|
|
|
CircuitPython driver for the TLV320DAC3100 I2S DAC
|
|
|
|
|
|
* Author(s): Liz Clark
|
|
|
|
Implementation Notes
|
|
--------------------
|
|
|
|
**Hardware:**
|
|
|
|
* `Adafruit TLV320DAC3100 - I2S DAC <https://www.adafruit.com/product/6309>`_
|
|
|
|
* `Adafruit Fruit Jam <https://www.adafruit.com/product/6200>`_
|
|
|
|
|
|
* The TLV320DAC chip has moderately complex onboard audio filtering, routing,
|
|
and amplification capability. Each of the signal chains for speaker, headphone
|
|
left, and headphone right start with the DAC, then they go through a mixer
|
|
stage, an analog volume (attenuation) stage, and finally an analog amplifier
|
|
stage. Parameters for each stage of each signal chain can be separately set
|
|
with different properties.
|
|
|
|
* To understand how the different audio stages (DAC, volume, amplifier gain)
|
|
relate to each other, it can help to look at the Functional Block Diagram in
|
|
the TLV320DAC3100 datasheet:
|
|
https://learn.adafruit.com/adafruit-tlv320dac3100-i2s-dac/downloads
|
|
|
|
* **CAUTION**: The TLV320 speaker amplifier has enough power to easily burn out
|
|
small 1W speakers if you max out the volume and gain settings. To be safe,
|
|
start with lower levels for ``speaker_volume`` and ``speaker_gain``, then work
|
|
your way up to find a comfortable listening level. Similarly, for the
|
|
headphone output, start low with ``headphone_volume``,
|
|
``headphone_left_gain``\, and ``headphone_right_gain``\, then increase as
|
|
needed.
|
|
|
|
**Software and Dependencies:**
|
|
|
|
* Adafruit CircuitPython firmware for the supported boards:
|
|
https://circuitpython.org/downloads
|
|
|
|
* Adafruit's Bus Device library: https://github.com/adafruit/Adafruit_CircuitPython_BusDevice
|
|
"""
|
|
|
|
import time
|
|
|
|
from adafruit_bus_device.i2c_device import I2CDevice
|
|
from micropython import const
|
|
|
|
try:
|
|
from typing import Any, Dict, List, Literal, Optional, Tuple, TypedDict, Union, cast
|
|
|
|
from busio import I2C
|
|
except ImportError:
|
|
pass
|
|
|
|
__version__ = "0.0.0+auto.0"
|
|
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_TLV320.git"
|
|
|
|
# Register addresses
|
|
_PAGE_SELECT = const(0x00)
|
|
_RESET = const(0x01)
|
|
_OT_FLAG = const(0x03)
|
|
_CLOCK_MUX1 = const(0x04)
|
|
_PLL_PROG_PR = const(0x05)
|
|
_PLL_PROG_J = const(0x06)
|
|
_PLL_PROG_D_MSB = const(0x07)
|
|
_PLL_PROG_D_LSB = const(0x08)
|
|
_NDAC = const(0x0B)
|
|
_MDAC = const(0x0C)
|
|
_DOSR_MSB = const(0x0D)
|
|
_DOSR_LSB = const(0x0E)
|
|
_CODEC_IF_CTRL1 = const(0x1B)
|
|
_DAC_FLAG = const(0x25)
|
|
_DAC_FLAG2 = const(0x26)
|
|
_INT1_CTRL = const(0x30)
|
|
_INT2_CTRL = const(0x31)
|
|
_GPIO1_CTRL = const(0x33)
|
|
_DIN_CTRL = const(0x36)
|
|
_DAC_DATAPATH = const(0x3F)
|
|
_DAC_VOL_CTRL = const(0x40)
|
|
_DAC_LVOL = const(0x41)
|
|
_DAC_RVOL = const(0x42)
|
|
_HEADSET_DETECT = const(0x43)
|
|
_VOL_ADC_CTRL = const(0x74) # VOL/MICDET-Pin SAR ADC Control ister
|
|
_VOL_ADC_READ = const(0x75) # VOL/MICDET-Pin Gain Register
|
|
|
|
# Page 1 registers
|
|
_HP_SPK_ERR_CTL = const(0x1E)
|
|
_HP_DRIVERS = const(0x1F)
|
|
_SPK_AMP = const(0x20)
|
|
_HP_POP = const(0x21)
|
|
_PGA_RAMP = const(0x22)
|
|
_OUT_ROUTING = const(0x23)
|
|
_HPL_VOL = const(0x24)
|
|
_HPR_VOL = const(0x25)
|
|
_SPK_VOL = const(0x26)
|
|
_HPL_DRIVER = const(0x28)
|
|
_HPR_DRIVER = const(0x29)
|
|
_SPK_DRIVER = const(0x2A)
|
|
_HP_DRIVER_CTRL = const(0x2C)
|
|
_MICBIAS = const(0x2E) # MICBIAS Configuration ister
|
|
_INPUT_CM = const(0x32) # Input Common Mode Settings Register
|
|
|
|
# Page 3 registers
|
|
_TIMER_MCLK_DIV = const(0x10) # Timer Clock MCLK Divider Register
|
|
|
|
# Default I2C address
|
|
I2C_ADDR_DEFAULT = const(0x18)
|
|
|
|
# Data format for I2S interface
|
|
FORMAT_I2S = const(0b00) # I2S format
|
|
FORMAT_DSP = const(0b01) # DSP format
|
|
FORMAT_RJF = const(0b10) # Right justified format
|
|
FORMAT_LJF = const(0b11) # Left justified format
|
|
|
|
# Data length for I2S interface
|
|
DATA_LEN_16 = const(0b00) # 16 bits
|
|
DATA_LEN_20 = const(0b01) # 20 bits
|
|
DATA_LEN_24 = const(0b10) # 24 bits
|
|
DATA_LEN_32 = const(0b11) # 32 bits
|
|
|
|
# GPIO1 pin mode options
|
|
#: GPIO1 pin mode options: GPIO1 disabled (input and output buffers powered down)
|
|
GPIO1_DISABLED = const(0b0000)
|
|
#: GPIO1 pin mode options: Input mode (secondary BCLK/WCLK/DIN input or ClockGen)
|
|
GPIO1_INPUT_MODE = const(0b0001)
|
|
#: GPIO1 pin mode options: General-purpose input
|
|
GPIO1_GPI = const(0b0010)
|
|
#: GPIO1 pin mode options: General-purpose output
|
|
GPIO1_GPO = const(0b0011)
|
|
#: GPIO1 pin mode options: CLKOUT output
|
|
GPIO1_CLKOUT = const(0b0100)
|
|
#: GPIO1 pin mode options: INT1 output
|
|
GPIO1_INT1 = const(0b0101)
|
|
#: GPIO1 pin mode options: INT2 output
|
|
GPIO1_INT2 = const(0b0110)
|
|
#: GPIO1 pin mode options: Secondary BCLK output for codec interface
|
|
GPIO1_BCLK_OUT = const(0b1000)
|
|
#: GPIO1 pin mode options: Secondary WCLK output for codec interface
|
|
GPIO1_WCLK_OUT = const(0b1001)
|
|
|
|
# DAC channel data path options
|
|
#: DAC channel data path option: DAC data path off
|
|
DAC_PATH_OFF = const(0b00)
|
|
#: DAC channel data path option: Normal path (L->L or R->R)
|
|
DAC_PATH_NORMAL = const(0b01)
|
|
#: DAC channel data path option: Swapped path (R->L or L->R)
|
|
DAC_PATH_SWAPPED = const(0b10)
|
|
#: DAC channel data path option: Mixed L+R path
|
|
DAC_PATH_MIXED = const(0b11)
|
|
|
|
# DAC volume control soft stepping options
|
|
#: DAC volume control soft stepping option: One step per sample
|
|
VOLUME_STEP_1SAMPLE = const(0b00)
|
|
#: DAC volume control soft stepping option: One step per two samples
|
|
VOLUME_STEP_2SAMPLE = const(0b01)
|
|
#: DAC volume control soft stepping option: Soft stepping disabled
|
|
VOLUME_STEP_DISABLED = const(0b10)
|
|
|
|
# DAC volume control configuration options
|
|
#: DAC volume control configuration option: Left and right channels independent
|
|
VOL_INDEPENDENT = const(0b00)
|
|
#: DAC volume control configuration option: Left follows right volume
|
|
VOL_LEFT_TO_RIGHT = const(0b01)
|
|
#: DAC volume control configuration option: Right follows left volume
|
|
VOL_RIGHT_TO_LEFT = const(0b10)
|
|
|
|
# DAC output routing options
|
|
#: DAC output routing option: DAC not routed
|
|
DAC_ROUTE_NONE = const(0b00)
|
|
#: DAC output routing option: DAC routed to mixer amplifier
|
|
DAC_ROUTE_MIXER = const(0b01)
|
|
#: DAC output routing option: DAC routed directly to HP driver
|
|
DAC_ROUTE_HP = const(0b10)
|
|
|
|
# Headphone common mode voltage options
|
|
#: Headphone common mode voltage option: Common-mode voltage 1.35V
|
|
HP_COMMON_1_35V = const(0b00)
|
|
#: Headphone common mode voltage option: Common-mode voltage 1.50V
|
|
HP_COMMON_1_50V = const(0b01)
|
|
#: Headphone common mode voltage option: Common-mode voltage 1.65V
|
|
HP_COMMON_1_65V = const(0b10)
|
|
#: Headphone common mode voltage option: Common-mode voltage 1.80V
|
|
HP_COMMON_1_80V = const(0b11)
|
|
|
|
# Headset detection debounce time options
|
|
#: Headset detection debounce time option: 16ms debounce (2ms clock)
|
|
DEBOUNCE_16MS = const(0b000)
|
|
#: Headset detection debounce time option: 32ms debounce (4ms clock)
|
|
DEBOUNCE_32MS = const(0b001)
|
|
#: Headset detection debounce time option: 64ms debounce (8ms clock)
|
|
DEBOUNCE_64MS = const(0b010)
|
|
#: Headset detection debounce time option: 128ms debounce (16ms clock)
|
|
DEBOUNCE_128MS = const(0b011)
|
|
#: Headset detection debounce time option: 256ms debounce (32ms clock)
|
|
DEBOUNCE_256MS = const(0b100)
|
|
#: Headset detection debounce time option: 512ms debounce (64ms clock)
|
|
DEBOUNCE_512MS = const(0b101)
|
|
|
|
# Button press debounce time options
|
|
#: Button press debounce time option: No debounce
|
|
BTN_DEBOUNCE_0MS = const(0b00)
|
|
#: Button press debounce time option: 8ms debounce (1ms clock)
|
|
BTN_DEBOUNCE_8MS = const(0b01)
|
|
#: Button press debounce time option: 16ms debounce (2ms clock)
|
|
BTN_DEBOUNCE_16MS = const(0b10)
|
|
#: Button press debounce time option: 32ms debounce (4ms clock)
|
|
BTN_DEBOUNCE_32MS = const(0b11)
|
|
|
|
# Lookup table for speaker_volume and headphone_volume.
|
|
# These are from TLV320DAC3100 datasheet Table 6-24.
|
|
TABLE_6_24 = (
|
|
0, # 0 Begin linear segment: round((-1.99 * dB) - 0.2)
|
|
-0.5, # 1
|
|
-1, # 2
|
|
-1.5, # 3
|
|
-2, # 4
|
|
-2.5, # 5
|
|
-3, # 6
|
|
-3.5, # 7
|
|
-4, # 8
|
|
-4.5, # 9
|
|
-5, # 10
|
|
-5.5, # 11
|
|
-6, # 12
|
|
-6.5, # 13
|
|
-7, # 14
|
|
-7.5, # 15
|
|
-8, # 16
|
|
-8.5, # 17
|
|
-9, # 18
|
|
-9.5, # 19
|
|
-10, # 20
|
|
-10.5, # 21
|
|
-11, # 22
|
|
-11.5, # 23
|
|
-12, # 24
|
|
-12.5, # 25
|
|
-13, # 26
|
|
-13.5, # 27
|
|
-14, # 28
|
|
-14.5, # 29
|
|
-15, # 30
|
|
-15.5, # 31
|
|
-16, # 32
|
|
-16.5, # 33
|
|
-17, # 34
|
|
-17.5, # 35
|
|
-18.1, # 36
|
|
-18.6, # 37
|
|
-19.1, # 38
|
|
-19.6, # 39
|
|
-20.1, # 40
|
|
-20.6, # 41
|
|
-21.1, # 42
|
|
-21.6, # 43
|
|
-22.1, # 44
|
|
-22.6, # 45
|
|
-23.1, # 46
|
|
-23.6, # 47
|
|
-24.1, # 48
|
|
-24.6, # 49
|
|
-25.1, # 50
|
|
-25.6, # 51
|
|
-26.1, # 52
|
|
-26.6, # 53
|
|
-27.1, # 54
|
|
-27.6, # 55
|
|
-28.1, # 56
|
|
-28.6, # 57
|
|
-29.1, # 58
|
|
-29.6, # 59
|
|
-30.1, # 60
|
|
-30.6, # 61
|
|
-31.1, # 62
|
|
-31.6, # 63
|
|
-32.1, # 64
|
|
-32.6, # 65
|
|
-33.1, # 66
|
|
-33.6, # 67
|
|
-34.1, # 68
|
|
-34.6, # 69
|
|
-35.2, # 70
|
|
-35.7, # 71
|
|
-36.2, # 72
|
|
-36.7, # 73
|
|
-37.2, # 74
|
|
-37.7, # 75
|
|
-38.2, # 76
|
|
-38.7, # 77
|
|
-39.2, # 78
|
|
-39.7, # 79
|
|
-40.2, # 80
|
|
-40.7, # 81
|
|
-41.2, # 82
|
|
-41.7, # 83
|
|
-42.1, # 84
|
|
-42.7, # 85
|
|
-43.2, # 86
|
|
-43.8, # 87
|
|
-44.3, # 88
|
|
-44.8, # 89
|
|
-45.2, # 90
|
|
-45.8, # 91
|
|
-46.2, # 92
|
|
-46.7, # 93
|
|
-47.4, # 94
|
|
-47.9, # 95
|
|
-48.2, # 96
|
|
-48.7, # 97
|
|
-49.3, # 98
|
|
-50, # 99
|
|
-50.3, # 100
|
|
-51, # 101
|
|
-51.4, # 102
|
|
-51.8, # 103
|
|
-52.2, # 104
|
|
-52.7, # 105 End linear segment: round((-1.99 * dB) - 0.2)
|
|
-53.7, # 106 Begin curved segment
|
|
-54.2, # 107
|
|
-55.3, # 108
|
|
-56.7, # 109
|
|
-58.3, # 110
|
|
-60.2, # 111
|
|
-62.7, # 112
|
|
-64.3, # 113
|
|
-66.2, # 114
|
|
-68.7, # 115
|
|
-72.2, # 116 End curved segment
|
|
-78.3, # 117 Begin constant segment -78.3 dB
|
|
-78.3, # 118
|
|
-78.3, # 119
|
|
-78.3, # 120
|
|
-78.3, # 121
|
|
-78.3, # 122
|
|
-78.3, # 123
|
|
-78.3, # 124
|
|
-78.3, # 125
|
|
-78.3, # 126
|
|
-78.3, # 127
|
|
)
|
|
|
|
# ruff: noqa: PLR0904, PLR0912, PLR0913, PLR0915, PLR0917
|
|
|
|
|
|
class _PagedRegisterBase:
|
|
"""Base class for paged register access."""
|
|
|
|
def __init__(self, i2c_device, page):
|
|
"""Initialize the paged register base.
|
|
|
|
:param i2c_device: The I2C device
|
|
:param page: The register page number
|
|
"""
|
|
self._device = i2c_device
|
|
self._page = page
|
|
self._buffer = bytearray(2)
|
|
|
|
def _write_register(self, register, value):
|
|
"""Write a value to a register.
|
|
|
|
:param register: The register address
|
|
:param value: The value to write
|
|
"""
|
|
self._set_page()
|
|
self._buffer[0] = register
|
|
self._buffer[1] = value
|
|
with self._device as i2c:
|
|
i2c.write(self._buffer)
|
|
|
|
def _read_register(self, register):
|
|
"""Value from a register.
|
|
|
|
:param register: The register address
|
|
:return: The register value
|
|
"""
|
|
self._set_page()
|
|
self._buffer[0] = register
|
|
with self._device as i2c:
|
|
i2c.write(self._buffer, end=1)
|
|
i2c.readinto(self._buffer, start=0, end=1)
|
|
return self._buffer[0]
|
|
|
|
def _set_page(self):
|
|
"""The current register page."""
|
|
self._buffer[0] = _PAGE_SELECT
|
|
self._buffer[1] = self._page
|
|
with self._device as i2c:
|
|
i2c.write(self._buffer)
|
|
|
|
def _get_bits(self, register, mask, shift):
|
|
"""Specific bits from a register.
|
|
|
|
:param register: The register address
|
|
:param mask: The bit mask (after shifting)
|
|
:param shift: The bit position (0 = LSB)
|
|
:return: The extracted bits
|
|
"""
|
|
value = self._read_register(register)
|
|
return (value >> shift) & mask
|
|
|
|
def _set_bits(self, register, mask, shift, value):
|
|
"""Specific bits in a register.
|
|
|
|
:param register: The register address
|
|
:param mask: The bit mask (after shifting)
|
|
:param shift: The bit position (0 = LSB)
|
|
:param value: The value to set
|
|
"""
|
|
reg_value = self._read_register(register)
|
|
reg_value &= ~(mask << shift)
|
|
reg_value |= (value & mask) << shift
|
|
self._write_register(register, reg_value)
|
|
|
|
|
|
class _Page0Registers(_PagedRegisterBase):
|
|
"""Page 0 registers containing system configuration, clocking, etc."""
|
|
|
|
def __init__(self, i2c_device):
|
|
"""Initialize Page 0 registers.
|
|
|
|
:param i2c_device: The I2C device
|
|
"""
|
|
super().__init__(i2c_device, 0)
|
|
|
|
def _reset(self):
|
|
"""Perform a software _reset of the chip.
|
|
|
|
:return: True if successful, False otherwise
|
|
"""
|
|
self._write_register(_RESET, 1)
|
|
time.sleep(0.01)
|
|
return self._read_register(_RESET) == 0
|
|
|
|
def _is_overtemperature(self):
|
|
"""Check if the chip is in an over-temperature condition.
|
|
|
|
:return: True if overtemp condition exists, False if temperature is OK
|
|
"""
|
|
return not ((self._read_register(_OT_FLAG) >> 1) & 0x01)
|
|
|
|
def _set_int1_source(
|
|
self, headset_detect, button_press, dac_drc, agc_noise, over_current, multiple_pulse
|
|
):
|
|
"""Configure the INT1 interrupt sources."""
|
|
value = 0
|
|
if headset_detect:
|
|
value |= 1 << 7
|
|
if button_press:
|
|
value |= 1 << 6
|
|
if dac_drc:
|
|
value |= 1 << 5
|
|
if over_current:
|
|
value |= 1 << 3
|
|
if agc_noise:
|
|
value |= 1 << 2
|
|
if multiple_pulse:
|
|
value |= 1 << 0
|
|
self._write_register(_INT1_CTRL, value)
|
|
|
|
def _set_gpio1_mode(self, mode):
|
|
"""The GPIO1 pin mode."""
|
|
return self._set_bits(_GPIO1_CTRL, 0x0F, 2, mode)
|
|
|
|
def _set_headset_detect(self, enable, detect_debounce=0, button_debounce=0):
|
|
"""Configure headset detection settings."""
|
|
value = (1 if enable else 0) << 7
|
|
value |= (detect_debounce & 0x07) << 2
|
|
value |= button_debounce & 0x03
|
|
self._write_register(_HEADSET_DETECT, value)
|
|
|
|
def _set_dac_data_path(
|
|
self,
|
|
left_dac_on,
|
|
right_dac_on,
|
|
left_path=DAC_PATH_NORMAL,
|
|
right_path=DAC_PATH_NORMAL,
|
|
volume_step=VOLUME_STEP_1SAMPLE,
|
|
):
|
|
"""Configure the DAC data path settings."""
|
|
value = 0
|
|
if left_dac_on:
|
|
value |= 1 << 7
|
|
if right_dac_on:
|
|
value |= 1 << 6
|
|
value |= (left_path & 0x03) << 4
|
|
value |= (right_path & 0x03) << 2
|
|
value |= volume_step & 0x03
|
|
self._write_register(_DAC_DATAPATH, value)
|
|
|
|
def _set_dac_volume_control(self, left_mute, right_mute, control=VOL_INDEPENDENT):
|
|
"""Configure the DAC volume control settings."""
|
|
value = 0
|
|
if left_mute:
|
|
value |= 1 << 3
|
|
if right_mute:
|
|
value |= 1 << 2
|
|
value |= control & 0x03
|
|
self._write_register(_DAC_VOL_CTRL, value)
|
|
|
|
def _set_channel_volume(self, right_channel, db):
|
|
"""DAC channel volume in dB."""
|
|
if db > 24.0:
|
|
db = 24.0
|
|
if db < -63.5:
|
|
db = -63.5
|
|
reg_val = int(db * 2)
|
|
if reg_val == 0x80 or reg_val > 0x30:
|
|
raise ValueError
|
|
|
|
if right_channel:
|
|
self._write_register(_DAC_RVOL, reg_val & 0xFF)
|
|
else:
|
|
self._write_register(_DAC_LVOL, reg_val & 0xFF)
|
|
|
|
def _get_dac_flags(self):
|
|
"""The DAC and output driver status flags.
|
|
|
|
:return: Dictionary with status flags for various components
|
|
"""
|
|
flag_reg = self._read_register(_DAC_FLAG)
|
|
left_dac_powered = bool(flag_reg & (1 << 7))
|
|
hpl_powered = bool(flag_reg & (1 << 5))
|
|
left_classd_powered = bool(flag_reg & (1 << 4))
|
|
right_dac_powered = bool(flag_reg & (1 << 3))
|
|
hpr_powered = bool(flag_reg & (1 << 1))
|
|
right_classd_powered = bool(flag_reg & (1 << 0))
|
|
flag2_reg = self._read_register(_DAC_FLAG2)
|
|
left_pga_gain_ok = bool(flag2_reg & (1 << 4))
|
|
right_pga_gain_ok = bool(flag2_reg & (1 << 0))
|
|
|
|
return {
|
|
"left_dac_powered": left_dac_powered,
|
|
"hpl_powered": hpl_powered,
|
|
"left_classd_powered": left_classd_powered,
|
|
"right_dac_powered": right_dac_powered,
|
|
"hpr_powered": hpr_powered,
|
|
"right_classd_powered": right_classd_powered,
|
|
"left_pga_gain_ok": left_pga_gain_ok,
|
|
"right_pga_gain_ok": right_pga_gain_ok,
|
|
}
|
|
|
|
def get_gpio1_input(self):
|
|
"""The current GPIO1 input value.
|
|
|
|
:return: Current GPIO1 input state (True/False)
|
|
"""
|
|
return bool(self._get_bits(_GPIO1_CTRL, 0x01, 1))
|
|
|
|
def _get_din_input(self):
|
|
"""The current DIN input value.
|
|
|
|
:return: Current DIN input state (True/False)
|
|
"""
|
|
return bool(self._get_bits(_DIN_CTRL, 0x01, 0))
|
|
|
|
def _get_codec_interface(self):
|
|
"""The current codec interface settings.
|
|
|
|
:return: Dictionary with format, data_len, bclk_out, and wclk_out values
|
|
"""
|
|
reg_value = self._read_register(_CODEC_IF_CTRL1)
|
|
format_val = (reg_value >> 6) & 0x03
|
|
data_len = (reg_value >> 4) & 0x03
|
|
bclk_out = bool(reg_value & (1 << 3))
|
|
wclk_out = bool(reg_value & (1 << 2))
|
|
|
|
return {
|
|
"format": format_val,
|
|
"data_len": data_len,
|
|
"bclk_out": bclk_out,
|
|
"wclk_out": wclk_out,
|
|
}
|
|
|
|
def _get_dac_data_path(self):
|
|
"""The current DAC data path configuration.
|
|
|
|
:return: Dictionary with DAC data path settings
|
|
"""
|
|
reg_value = self._read_register(_DAC_DATAPATH)
|
|
left_dac_on = bool(reg_value & (1 << 7))
|
|
right_dac_on = bool(reg_value & (1 << 6))
|
|
left_path = (reg_value >> 4) & 0x03
|
|
right_path = (reg_value >> 2) & 0x03
|
|
volume_step = reg_value & 0x03
|
|
|
|
return {
|
|
"left_dac_on": left_dac_on,
|
|
"right_dac_on": right_dac_on,
|
|
"left_path": left_path,
|
|
"right_path": right_path,
|
|
"volume_step": volume_step,
|
|
}
|
|
|
|
def _get_dac_volume_control(self):
|
|
"""The current DAC volume control configuration.
|
|
|
|
:return: Dictionary with volume control settings
|
|
"""
|
|
reg_value = self._read_register(_DAC_VOL_CTRL)
|
|
left_mute = bool(reg_value & (1 << 3))
|
|
right_mute = bool(reg_value & (1 << 2))
|
|
control = reg_value & 0x03
|
|
return {"left_mute": left_mute, "right_mute": right_mute, "control": control}
|
|
|
|
def _get_channel_volume(self, right_channel):
|
|
"""DAC channel volume in dB.
|
|
|
|
:param right_channel: True for right channel, False for left channel
|
|
:return: Current volume in dB
|
|
"""
|
|
reg = _DAC_RVOL if right_channel else _DAC_LVOL
|
|
reg_val = self._read_register(reg)
|
|
if reg_val & 0x80:
|
|
steps = reg_val - 256
|
|
else:
|
|
steps = reg_val
|
|
return steps * 0.5
|
|
|
|
def _get_headset_status(self):
|
|
"""Current headset detection status.
|
|
|
|
:return: Integer value representing headset status (0=none, 1=without mic, 3=with mic)
|
|
"""
|
|
status_bits = self._get_bits(_HEADSET_DETECT, 0x03, 5)
|
|
return status_bits
|
|
|
|
def _config_vol_adc(self, pin_control=False, use_mclk=False, hysteresis=0, rate=0):
|
|
"""The Volume/MicDet pin ADC.
|
|
|
|
:param pin_control: Enable pin control of DAC volume
|
|
:param use_mclk: Use MCLK instead of internal RC oscillator
|
|
:param hysteresis: ADC hysteresis setting (0-2)
|
|
:param rate: ADC sampling rate (0-7)
|
|
"""
|
|
value = (1 if pin_control else 0) << 7
|
|
value |= (1 if use_mclk else 0) << 6
|
|
value |= (hysteresis & 0x03) << 4
|
|
value |= rate & 0x07
|
|
self._write_register(_VOL_ADC_CTRL, value)
|
|
|
|
def _read_vol_adc_db(self):
|
|
"""The current volume from the Volume ADC in dB.
|
|
|
|
:return: Current volume in dB (+18 to -63 dB)
|
|
"""
|
|
raw_val = self._read_register(_VOL_ADC_READ) & 0x7F
|
|
if raw_val == 0x7F:
|
|
return 0.0
|
|
if raw_val <= 0x24:
|
|
return 18.0 - (raw_val * 0.5)
|
|
else:
|
|
return -((raw_val - 0x24) * 0.5)
|
|
|
|
def _set_int2_source(
|
|
self,
|
|
headset_detect=False,
|
|
button_press=False,
|
|
dac_drc=False,
|
|
agc_noise=False,
|
|
over_current=False,
|
|
multiple_pulse=False,
|
|
):
|
|
"""Configure the INT2 interrupt sources.
|
|
|
|
:param headset_detect: Enable headset detection interrupt
|
|
:param button_press: Enable button press detection interrupt
|
|
:param dac_drc: Enable DAC DRC signal power interrupt
|
|
:param agc_noise: Enable DAC data overflow interrupt
|
|
:param over_current: Enable short circuit interrupt
|
|
:param multiple_pulse: If true, INT2 generates multiple pulses until flag read
|
|
"""
|
|
value = 0
|
|
if headset_detect:
|
|
value |= 1 << 7
|
|
if button_press:
|
|
value |= 1 << 6
|
|
if dac_drc:
|
|
value |= 1 << 5
|
|
if over_current:
|
|
value |= 1 << 3
|
|
if agc_noise:
|
|
value |= 1 << 2
|
|
if multiple_pulse:
|
|
value |= 1 << 0
|
|
|
|
self._write_register(_INT2_CTRL, value)
|
|
|
|
def _set_codec_interface(self, format, data_len, bclk_out=False, wclk_out=False):
|
|
"""The codec interface parameters."""
|
|
value = (format & 0x03) << 6
|
|
value |= (data_len & 0x03) << 4
|
|
value |= (1 if bclk_out else 0) << 3
|
|
value |= (1 if wclk_out else 0) << 2
|
|
|
|
self._write_register(_CODEC_IF_CTRL1, value)
|
|
|
|
def _configure_clocks_for_sample_rate(self, mclk_freq: int, sample_rate: int, bit_depth: int):
|
|
"""Clock settings for the specified sample rate.
|
|
|
|
:param mclk_freq: The main clock frequency in Hz, or 0 to use BCLK as PLL input
|
|
:param sample_rate: The desired sample rate in Hz
|
|
:param bit_depth: The bit depth (16, 20, 24, or 32)
|
|
:return: True if successful, False otherwise
|
|
"""
|
|
if bit_depth == 16:
|
|
data_len = DATA_LEN_16
|
|
elif bit_depth == 20:
|
|
data_len = DATA_LEN_20
|
|
elif bit_depth == 24:
|
|
data_len = DATA_LEN_24
|
|
else:
|
|
data_len = DATA_LEN_32
|
|
|
|
if mclk_freq == 0:
|
|
self._set_bits(_CLOCK_MUX1, 0x03, 2, 0b01)
|
|
self._set_bits(_CLOCK_MUX1, 0x03, 0, 0b11)
|
|
p, r, j, d = 1, 3, 20, 0
|
|
ndac = 5
|
|
mdac = 3
|
|
dosr = 128
|
|
# Set the data format
|
|
self._set_codec_interface(FORMAT_I2S, data_len)
|
|
# Configure PLL
|
|
pr_value = ((p & 0x07) << 4) | (r & 0x0F)
|
|
self._write_register(_PLL_PROG_PR, pr_value & 0x7F)
|
|
self._write_register(_PLL_PROG_J, j & 0x3F)
|
|
self._write_register(_PLL_PROG_D_MSB, (d >> 8) & 0xFF)
|
|
self._write_register(_PLL_PROG_D_LSB, d & 0xFF)
|
|
# Configure dividers
|
|
self._write_register(_NDAC, 0x80 | (ndac & 0x7F))
|
|
self._write_register(_MDAC, 0x80 | (mdac & 0x7F))
|
|
self._write_register(_DOSR_MSB, (dosr >> 8) & 0xFF)
|
|
self._write_register(_DOSR_LSB, dosr & 0xFF)
|
|
# Power up PLL
|
|
self._set_bits(_PLL_PROG_PR, 0x01, 7, 1)
|
|
time.sleep(0.01)
|
|
|
|
elif mclk_freq % (128 * sample_rate) == 0:
|
|
div_ratio = mclk_freq // (128 * sample_rate)
|
|
self._set_bits(_CLOCK_MUX1, 0x03, 0, 0b00)
|
|
self._set_bits(_PLL_PROG_PR, 0x01, 7, 0)
|
|
if div_ratio <= 128:
|
|
self._write_register(_NDAC, 0x80 | (div_ratio & 0x7F))
|
|
self._write_register(_MDAC, 0x81)
|
|
self._write_register(_DOSR_MSB, 0)
|
|
self._write_register(_DOSR_LSB, 128)
|
|
self._set_codec_interface(FORMAT_I2S, data_len)
|
|
|
|
elif mclk_freq == 12000000:
|
|
if sample_rate == 22050:
|
|
p, r, j, d = 1, 1, 7, 6144
|
|
ndac = 8
|
|
mdac = 1
|
|
dosr = 128
|
|
elif sample_rate == 44100:
|
|
p, r, j, d = 1, 1, 7, 6144
|
|
ndac = 4
|
|
mdac = 1
|
|
dosr = 128
|
|
elif sample_rate == 48000:
|
|
p, r, j, d = 1, 1, 8, 0
|
|
ndac = 4
|
|
mdac = 1
|
|
dosr = 128
|
|
elif sample_rate == 96000:
|
|
p, r, j, d = 1, 1, 8, 0
|
|
ndac = 2
|
|
mdac = 1
|
|
dosr = 128
|
|
else:
|
|
raise ValueError("Need a valid sample rate: 22050, 44100, 48000 or 96000")
|
|
|
|
elif mclk_freq == 24000000:
|
|
if sample_rate == 44100:
|
|
p, r, j, d = 1, 2, 7, 6144
|
|
ndac = 4
|
|
mdac = 1
|
|
dosr = 128
|
|
elif sample_rate == 48000:
|
|
p, r, j, d = 1, 2, 8, 0
|
|
ndac = 4
|
|
mdac = 1
|
|
dosr = 128
|
|
elif sample_rate == 96000:
|
|
p, r, j, d = 1, 2, 8, 0
|
|
ndac = 2
|
|
mdac = 1
|
|
dosr = 128
|
|
else:
|
|
raise ValueError("Need a valid sample rate: 44100, 48000 or 96000")
|
|
else:
|
|
raise ValueError("Need a valid MCLK frequency: 12MHz, 24MHz or 0 for BCLK")
|
|
|
|
if mclk_freq != 0:
|
|
self._set_bits(_CLOCK_MUX1, 0x03, 2, 0b00)
|
|
self._set_bits(_CLOCK_MUX1, 0x03, 0, 0b11)
|
|
pr_value = ((p & 0x07) << 4) | (r & 0x0F)
|
|
self._write_register(_PLL_PROG_PR, pr_value & 0x7F)
|
|
self._write_register(_PLL_PROG_J, j & 0x3F)
|
|
self._write_register(_PLL_PROG_D_MSB, (d >> 8) & 0xFF)
|
|
self._write_register(_PLL_PROG_D_LSB, d & 0xFF)
|
|
self._write_register(_NDAC, 0x80 | (ndac & 0x7F))
|
|
self._write_register(_MDAC, 0x80 | (mdac & 0x7F))
|
|
self._write_register(_DOSR_MSB, (dosr >> 8) & 0xFF)
|
|
self._write_register(_DOSR_LSB, dosr & 0xFF)
|
|
self._set_codec_interface(FORMAT_I2S, data_len)
|
|
self._set_bits(_PLL_PROG_PR, 0x01, 7, 1)
|
|
time.sleep(0.01)
|
|
|
|
|
|
class _Page1Registers(_PagedRegisterBase):
|
|
"""Page 1 registers containing analog output settings, HP/SPK controls, etc."""
|
|
|
|
def __init__(self, i2c_device):
|
|
"""Initialize Page 1 registers.
|
|
|
|
:param i2c_device: The I2C device
|
|
"""
|
|
super().__init__(i2c_device, 1)
|
|
|
|
def _get_speaker_enabled(self):
|
|
"""Check if speaker is enabled."""
|
|
return bool(self._get_bits(_SPK_AMP, 0x01, 7))
|
|
|
|
def _set_speaker_enabled(self, enable):
|
|
"""Enable or disable the Class-D speaker amplifier."""
|
|
return self._set_bits(_SPK_AMP, 0x01, 7, 1 if enable else 0)
|
|
|
|
def _configure_headphone_driver(
|
|
self, left_powered, right_powered, common=HP_COMMON_1_35V, power_down_on_scd=False
|
|
):
|
|
"""Headphone driver settings."""
|
|
value = 0x04
|
|
if left_powered:
|
|
value |= 1 << 7
|
|
if right_powered:
|
|
value |= 1 << 6
|
|
value |= (common & 0x03) << 3
|
|
if power_down_on_scd:
|
|
value |= 1 << 1
|
|
self._write_register(_HP_DRIVERS, value)
|
|
|
|
def _configure_analog_inputs(
|
|
self,
|
|
left_dac=DAC_ROUTE_NONE,
|
|
right_dac=DAC_ROUTE_NONE,
|
|
left_ain1=False,
|
|
left_ain2=False,
|
|
right_ain2=False,
|
|
hpl_routed_to_hpr=False,
|
|
):
|
|
"""DAC and analog input routing."""
|
|
value = 0
|
|
value |= (left_dac & 0x03) << 6
|
|
if left_ain1:
|
|
value |= 1 << 5
|
|
if left_ain2:
|
|
value |= 1 << 4
|
|
value |= (right_dac & 0x03) << 2
|
|
if right_ain2:
|
|
value |= 1 << 1
|
|
if hpl_routed_to_hpr:
|
|
value |= 1
|
|
|
|
self._write_register(_OUT_ROUTING, value)
|
|
|
|
def _set_hpl_volume(self, route_enabled, gain=0x7F):
|
|
"""HPL analog volume control."""
|
|
if gain > 0x7F:
|
|
gain = 0x7F
|
|
value = ((1 if route_enabled else 0) << 7) | (gain & 0x7F)
|
|
self._write_register(_HPL_VOL, value)
|
|
|
|
def _set_hpr_volume(self, route_enabled, gain=0x7F):
|
|
"""HPR analog volume control."""
|
|
if gain > 0x7F:
|
|
gain = 0x7F
|
|
value = ((1 if route_enabled else 0) << 7) | (gain & 0x7F)
|
|
self._write_register(_HPR_VOL, value)
|
|
|
|
def _set_spk_volume(self, route_enabled, gain=0x7F):
|
|
"""Speaker analog volume control."""
|
|
if gain > 0x7F:
|
|
gain = 0x7F
|
|
value = ((1 if route_enabled else 0) << 7) | (gain & 0x7F)
|
|
self._write_register(_SPK_VOL, value)
|
|
|
|
def _configure_hpl_pga(self, gain_db=0, unmute=True):
|
|
"""HPL driver PGA settings.
|
|
:raises ValueError: If set to anything outside of range 0 to 9
|
|
"""
|
|
if not (0 <= gain_db <= 9):
|
|
raise ValueError("HPL gain must be in range 0 to 9")
|
|
value = (gain_db & 0x0F) << 3
|
|
if unmute:
|
|
value |= 1 << 2
|
|
self._write_register(_HPL_DRIVER, value)
|
|
|
|
def _configure_hpr_pga(self, gain_db=0, unmute=True):
|
|
"""HPR driver PGA settings.
|
|
:raises ValueError: If set to anything outside of range 0 to 9
|
|
"""
|
|
if not (0 <= gain_db <= 9):
|
|
raise ValueError("HPR gain must be in range 0 to 9")
|
|
value = (gain_db & 0x0F) << 3
|
|
if unmute:
|
|
value |= 1 << 2
|
|
self._write_register(_HPR_DRIVER, value)
|
|
|
|
def _configure_spk_pga(self, gain_db=6, unmute=True):
|
|
"""Speaker driver settings.
|
|
:raises ValueError: If set to anything other than 6, 12, 18, or 24
|
|
"""
|
|
if gain_db not in (6, 12, 18, 24):
|
|
raise ValueError(
|
|
f"Invalid speaker gain: {gain_db}. Must be 6, 12, 18, or 24."
|
|
)
|
|
uint2_val = (gain_db / 6) - 1
|
|
value = (uint2_val & 0x03) << 3
|
|
if unmute:
|
|
value |= 1 << 2
|
|
self._write_register(_SPK_DRIVER, value)
|
|
|
|
def _is_speaker_shorted(self):
|
|
"""Check if speaker short circuit is detected.
|
|
|
|
:return: True if short circuit detected, False if not
|
|
"""
|
|
return bool(self._get_bits(_SPK_AMP, 0x01, 0))
|
|
|
|
def _is_hpl_gain_applied(self):
|
|
"""Check if all programmed gains have been applied to HPL.
|
|
|
|
:return: True if gains applied, False if still ramping
|
|
"""
|
|
return bool(self._get_bits(_HPL_DRIVER, 0x01, 0))
|
|
|
|
def _is_hpr_gain_applied(self):
|
|
"""Check if all programmed gains have been applied to HPR.
|
|
|
|
:return: True if gains applied, False if still ramping
|
|
"""
|
|
return bool(self._get_bits(_HPR_DRIVER, 0x01, 0))
|
|
|
|
def _is_spk_gain_applied(self):
|
|
"""Check if all programmed gains have been applied to Speaker.
|
|
|
|
:return: True if gains applied, False if still ramping
|
|
"""
|
|
return bool(self._get_bits(_SPK_DRIVER, 0x01, 0))
|
|
|
|
def _reset_speaker_on_scd(self, reset):
|
|
"""Configure speaker reset behavior on short circuit detection.
|
|
|
|
:param reset: True to reset speaker on short circuit, False to remain unchanged
|
|
:return: True if successful
|
|
"""
|
|
return self._set_bits(_HP_SPK_ERR_CTL, 0x01, 1, 0 if reset else 1)
|
|
|
|
def _reset_headphone_on_scd(self, reset):
|
|
"""Configure headphone reset behavior on short circuit detection.
|
|
|
|
:param reset: True to reset headphone on short circuit, False to remain unchanged
|
|
:return: True if successful
|
|
"""
|
|
# Register is inverse of parameter (0 = reset, 1 = no reset)
|
|
return self._set_bits(_HP_SPK_ERR_CTL, 0x01, 0, 0 if reset else 1)
|
|
|
|
def _configure_headphone_pop(self, wait_for_powerdown=True, powerup_time=0x07, ramp_time=0x03):
|
|
"""Configure headphone pop removal settings.
|
|
|
|
:param wait_for_powerdown: Wait for amp powerdown before DAC powerdown
|
|
:param powerup_time: Driver power-on time (0-11)
|
|
:param ramp_time: Driver ramp-up step time (0-3)
|
|
:return: True if successful
|
|
"""
|
|
value = (1 if wait_for_powerdown else 0) << 7
|
|
value |= (powerup_time & 0x0F) << 3
|
|
value |= (ramp_time & 0x03) << 1
|
|
self._write_register(_HP_POP, value)
|
|
|
|
def _set_speaker_wait_time(self, wait_time=0):
|
|
"""Speaker power-up wait time.
|
|
|
|
:param wait_time: Speaker power-up wait duration (0-7)
|
|
:return: True if successful
|
|
"""
|
|
return self._set_bits(_PGA_RAMP, 0x07, 4, wait_time)
|
|
|
|
def _headphone_lineout(self, left, right):
|
|
"""Configure headphone outputs as line-out.
|
|
|
|
:param left: Configure left channel as line-out
|
|
:param right: Configure right channel as line-out
|
|
:return: True if successful
|
|
"""
|
|
value = 0
|
|
if left:
|
|
value |= 1 << 2
|
|
if right:
|
|
value |= 1 << 1
|
|
self._write_register(_HP_DRIVER_CTRL, value)
|
|
|
|
def _config_mic_bias(self, power_down=False, always_on=False, voltage=0):
|
|
"""Configure MICBIAS settings."""
|
|
value = (1 if power_down else 0) << 7
|
|
value |= (1 if always_on else 0) << 3
|
|
value |= voltage & 0x03
|
|
self._write_register(_MICBIAS, value)
|
|
|
|
def _set_input_common_mode(self, ain1_cm, ain2_cm):
|
|
"""Analog input common mode connections."""
|
|
value = 0
|
|
if ain1_cm:
|
|
value |= 1 << 7
|
|
if ain2_cm:
|
|
value |= 1 << 6
|
|
self._write_register(_INPUT_CM, value)
|
|
|
|
|
|
class _Page3Registers(_PagedRegisterBase):
|
|
"""Page 3 registers containing timer settings."""
|
|
|
|
def __init__(self, i2c_device):
|
|
"""Page 3 registers.
|
|
|
|
:param i2c_device: The I2C device
|
|
"""
|
|
super().__init__(i2c_device, 3)
|
|
|
|
def _config_delay_divider(self, use_mclk=True, divider=1):
|
|
"""Configure programmable delay timer clock source and divider."""
|
|
value = (1 if use_mclk else 0) << 7
|
|
value |= divider & 0x7F
|
|
self._write_register(_TIMER_MCLK_DIV, value)
|
|
|
|
|
|
class TLV320DAC3100:
|
|
"""Driver for the TI TLV320DAC3100 Stereo DAC with Headphone Amplifier."""
|
|
|
|
def __init__(self, i2c: I2C, address: int = 0x18) -> None:
|
|
"""Initialize the TLV320DAC3100.
|
|
|
|
:param i2c: The I2C bus the device is connected to
|
|
:param address: The I2C device address (default is 0x18)
|
|
"""
|
|
self._device: I2CDevice = I2CDevice(i2c, address)
|
|
|
|
# Initialize register page classes
|
|
self._page0: "Page0Registers" = _Page0Registers(self._device)
|
|
self._page1: "Page1Registers" = _Page1Registers(self._device)
|
|
self._page3: "Page3Registers" = _Page3Registers(self._device)
|
|
self._sample_rate: int = 44100
|
|
self._bit_depth: int = 16
|
|
self._mclk_freq: int = 0 # Default blck
|
|
if not self.reset():
|
|
raise RuntimeError("Failed to reset TLV320DAC3100")
|
|
time.sleep(0.01)
|
|
self._page0._set_channel_volume(False, 0)
|
|
self._page0._set_channel_volume(True, 0)
|
|
|
|
# Both DACs on with normal path by default
|
|
self._page0._set_dac_data_path(
|
|
left_dac_on=True,
|
|
right_dac_on=True,
|
|
left_path=DAC_PATH_NORMAL,
|
|
right_path=DAC_PATH_NORMAL,
|
|
)
|
|
self._page0._set_dac_volume_control(False, False, VOL_INDEPENDENT)
|
|
|
|
def _table_6_24_db_to_uint7(self, db: float) -> int:
|
|
"""Convert gain dB to 7-bit unsigned int following datasheet Table 6-24.
|
|
|
|
:param db: Analog gain in dB; range is 0 dB (loud) to -78.3 dB (soft)
|
|
:return: 7-bit unsigned int value, range is 0 (loud) to 127 (soft)
|
|
"""
|
|
# Clip dB argument to fit in the valid range if it's too big or too small
|
|
db = max(-78.3, min(0, db))
|
|
# Loop through the table, looking for the lowest table index where the
|
|
# target dB value is not greater than the table dB value
|
|
result = 0
|
|
for (table_u7, table_db) in enumerate(TABLE_6_24):
|
|
if db < table_db:
|
|
result = table_u7
|
|
elif db == table_db:
|
|
result = table_u7
|
|
break
|
|
else:
|
|
break
|
|
return result
|
|
|
|
def _table_6_24_uint7_to_db(self, u7: int) -> float:
|
|
"""Convert 7-bit unsigned int to gain dB following datasheet Table 6-24.
|
|
|
|
:param u7: 7-bit unsigned int value, range is 0 (loud) to 127 (soft)
|
|
:return: Analog gain in dB, range is 0 dB (loud) to -78.3 dB (soft)
|
|
"""
|
|
return TABLE_6_24[max(0, min(127, int(u7)))]
|
|
|
|
# Basic properties and methods
|
|
|
|
def reset(self) -> bool:
|
|
"""Reset the device.
|
|
|
|
:return: True if reset successful, False otherwise
|
|
"""
|
|
return self._page0._reset()
|
|
|
|
@property
|
|
def overtemperature(self) -> bool:
|
|
"""Check if the chip is overheating.
|
|
|
|
:return: True if overtemperature condition exists, False otherwise
|
|
"""
|
|
return self._page0._is_overtemperature()
|
|
|
|
def set_headset_detect(
|
|
self, enable: bool, detect_debounce: int = 0, button_debounce: int = 0
|
|
) -> bool:
|
|
"""Headset detection settings.
|
|
|
|
:param enable: Boolean to enable/disable headset detection
|
|
:param detect_debounce: One of the DEBOUNCE_* constants for headset detect
|
|
:param button_debounce: One of the BTN_DEBOUNCE_* constants for button press
|
|
:raises ValueError: If debounce values are not valid constants
|
|
:return: True if successful, False otherwise
|
|
"""
|
|
valid_detect_debounce: List[int] = [
|
|
DEBOUNCE_16MS,
|
|
DEBOUNCE_32MS,
|
|
DEBOUNCE_64MS,
|
|
DEBOUNCE_128MS,
|
|
DEBOUNCE_256MS,
|
|
DEBOUNCE_512MS,
|
|
]
|
|
valid_button_debounce: List[int] = [
|
|
BTN_DEBOUNCE_0MS,
|
|
BTN_DEBOUNCE_8MS,
|
|
BTN_DEBOUNCE_16MS,
|
|
BTN_DEBOUNCE_32MS,
|
|
]
|
|
|
|
if detect_debounce not in valid_detect_debounce:
|
|
raise ValueError(
|
|
f"Invalid detect_debounce value: {detect_debounce}."
|
|
+ "Must be one of the DEBOUNCE_* constants."
|
|
)
|
|
|
|
if button_debounce not in valid_button_debounce:
|
|
raise ValueError(
|
|
f"Invalid button_debounce value: {button_debounce}."
|
|
+ "Must be one of the BTN_DEBOUNCE_* constants."
|
|
)
|
|
|
|
return self._page0._set_headset_detect(enable, detect_debounce, button_debounce)
|
|
|
|
def int1_source(
|
|
self,
|
|
headset_detect: bool,
|
|
button_press: bool,
|
|
dac_drc: bool,
|
|
agc_noise: bool,
|
|
over_current: bool,
|
|
multiple_pulse: bool,
|
|
) -> bool:
|
|
"""The INT1 interrupt sources.
|
|
|
|
:param headset_detect: Enable headset detection interrupt
|
|
:param button_press: Enable button press detection interrupt
|
|
:param dac_drc: Enable DAC DRC signal power interrupt
|
|
:param agc_noise: Enable DAC data overflow interrupt
|
|
:param over_current: Enable short circuit interrupt
|
|
:param multiple_pulse: If true, INT1 generates multiple pulses until flag read
|
|
:return: True if successful, False otherwise
|
|
"""
|
|
return self._page0._set_int1_source(
|
|
headset_detect, button_press, dac_drc, agc_noise, over_current, multiple_pulse
|
|
)
|
|
|
|
@property
|
|
def left_dac(self) -> bool:
|
|
"""The left DAC enabled status.
|
|
|
|
:return: True if left DAC is enabled, False otherwise
|
|
"""
|
|
return self._page0._get_dac_data_path()["left_dac_on"]
|
|
|
|
@left_dac.setter
|
|
def left_dac(self, enabled: bool) -> None:
|
|
"""The left DAC enabled status.
|
|
|
|
:param enabled: True to enable left DAC, False to disable
|
|
"""
|
|
current: DACDataPath = self._page0._get_dac_data_path()
|
|
self._page0._set_dac_data_path(
|
|
enabled,
|
|
current["right_dac_on"],
|
|
current["left_path"],
|
|
current["right_path"],
|
|
current["volume_step"],
|
|
)
|
|
|
|
@property
|
|
def right_dac(self) -> bool:
|
|
"""The right DAC enabled status.
|
|
|
|
:return: True if right DAC is enabled, False otherwise
|
|
"""
|
|
return self._page0._get_dac_data_path()["right_dac_on"]
|
|
|
|
@right_dac.setter
|
|
def right_dac(self, enabled: bool) -> None:
|
|
"""The right DAC enabled status.
|
|
|
|
:param enabled: True to enable right DAC, False to disable
|
|
"""
|
|
current: DACDataPath = self._page0._get_dac_data_path()
|
|
self._page0._set_dac_data_path(
|
|
current["left_dac_on"],
|
|
enabled,
|
|
current["left_path"],
|
|
current["right_path"],
|
|
current["volume_step"],
|
|
)
|
|
|
|
@property
|
|
def left_dac_path(self) -> int:
|
|
"""The left DAC path setting.
|
|
|
|
:return: One of the DAC_PATH_* constants
|
|
"""
|
|
return self._page0._get_dac_data_path()["left_path"]
|
|
|
|
@left_dac_path.setter
|
|
def left_dac_path(self, path: int) -> None:
|
|
"""The left DAC path.
|
|
|
|
:param path: One of the DAC_PATH_* constants
|
|
:raises ValueError: If path is not a valid DAC_PATH_* constant
|
|
"""
|
|
valid_paths: List[int] = [DAC_PATH_OFF, DAC_PATH_NORMAL, DAC_PATH_SWAPPED, DAC_PATH_MIXED]
|
|
|
|
if path not in valid_paths:
|
|
raise ValueError(
|
|
f"Invalid DAC path value: {path}. Must be one of the DAC_PATH_* constants."
|
|
)
|
|
|
|
current: DACDataPath = self._page0._get_dac_data_path()
|
|
self._page0._set_dac_data_path(
|
|
current["left_dac_on"],
|
|
current["right_dac_on"],
|
|
path,
|
|
current["right_path"],
|
|
current["volume_step"],
|
|
)
|
|
|
|
@property
|
|
def right_dac_path(self) -> int:
|
|
"""The right DAC path setting.
|
|
|
|
:return: One of the DAC_PATH_* constants
|
|
"""
|
|
return self._page0._get_dac_data_path()["right_path"]
|
|
|
|
@right_dac_path.setter
|
|
def right_dac_path(self, path: int) -> None:
|
|
"""The right DAC path.
|
|
|
|
:param path: One of the DAC_PATH_* constants
|
|
:raises ValueError: If path is not a valid DAC_PATH_* constant
|
|
"""
|
|
valid_paths: List[int] = [DAC_PATH_OFF, DAC_PATH_NORMAL, DAC_PATH_SWAPPED, DAC_PATH_MIXED]
|
|
|
|
if path not in valid_paths:
|
|
raise ValueError(
|
|
f"Invalid DAC path value: {path}. Must be one of the DAC_PATH_* constants."
|
|
)
|
|
|
|
current: DACDataPath = self._page0._get_dac_data_path()
|
|
self._page0._set_dac_data_path(
|
|
current["left_dac_on"],
|
|
current["right_dac_on"],
|
|
current["left_path"],
|
|
path,
|
|
current["volume_step"],
|
|
)
|
|
|
|
@property
|
|
def dac_volume_step(self) -> int:
|
|
"""The DAC volume step setting.
|
|
|
|
:return: One of the VOLUME_STEP_* constants
|
|
"""
|
|
return self._page0._get_dac_data_path()["volume_step"]
|
|
|
|
@dac_volume_step.setter
|
|
def dac_volume_step(self, step: int) -> None:
|
|
"""The DAC volume step setting.
|
|
|
|
:param step: One of the VOLUME_STEP_* constants
|
|
:raises ValueError: If step is not a valid VOLUME_STEP_* constant
|
|
"""
|
|
valid_steps: List[int] = [VOLUME_STEP_1SAMPLE, VOLUME_STEP_2SAMPLE, VOLUME_STEP_DISABLED]
|
|
|
|
if step not in valid_steps:
|
|
raise ValueError(
|
|
f"Invalid volume step value: {step}. Must be one of the VOLUME_STEP_* constants."
|
|
)
|
|
|
|
current: DACDataPath = self._page0._get_dac_data_path()
|
|
self._page0._set_dac_data_path(
|
|
current["left_dac_on"],
|
|
current["right_dac_on"],
|
|
current["left_path"],
|
|
current["right_path"],
|
|
step,
|
|
)
|
|
|
|
def configure_analog_inputs(
|
|
self,
|
|
left_dac: int = 0,
|
|
right_dac: int = 0,
|
|
left_ain1: bool = False,
|
|
left_ain2: bool = False,
|
|
right_ain2: bool = False,
|
|
hpl_routed_to_hpr: bool = False,
|
|
) -> bool:
|
|
"""DAC and analog input routing.
|
|
|
|
:param left_dac: One of the DAC_ROUTE_* constants for left DAC routing
|
|
:param right_dac: One of the DAC_ROUTE_* constants for right DAC routing
|
|
:param left_ain1: Boolean to route left AIN1 to output
|
|
:param left_ain2: Boolean to route left AIN2 to output
|
|
:param right_ain2: Boolean to route right AIN2 to output
|
|
:param hpl_routed_to_hpr: Boolean to route HPL to HPR
|
|
:raises ValueError: If DAC route values are not valid constants
|
|
:return: True if successful, False otherwise
|
|
"""
|
|
valid_dac_routes: List[int] = [DAC_ROUTE_NONE, DAC_ROUTE_MIXER, DAC_ROUTE_HP]
|
|
|
|
if left_dac not in valid_dac_routes:
|
|
raise ValueError(
|
|
f"Invalid left_dac value: {left_dac}. Must be one of the DAC_ROUTE_* constants."
|
|
)
|
|
|
|
if right_dac not in valid_dac_routes:
|
|
raise ValueError(
|
|
f"Invalid right_dac value: {right_dac}. Must be one of the DAC_ROUTE_* constants."
|
|
)
|
|
|
|
return self._page1._configure_analog_inputs(
|
|
left_dac, right_dac, left_ain1, left_ain2, right_ain2, hpl_routed_to_hpr
|
|
)
|
|
|
|
@property
|
|
def left_dac_mute(self) -> bool:
|
|
"""The left DAC mute status.
|
|
|
|
:return: True if left DAC is muted, False otherwise
|
|
"""
|
|
return self._page0._get_dac_volume_control()["left_mute"]
|
|
|
|
@left_dac_mute.setter
|
|
def left_dac_mute(self, mute: bool) -> None:
|
|
"""The left DAC mute status.
|
|
|
|
:param mute: True to mute left DAC, False to unmute
|
|
"""
|
|
current: DACVolumeControl = self._page0._get_dac_volume_control()
|
|
self._page0._set_dac_volume_control(mute, current["right_mute"], current["control"])
|
|
|
|
@property
|
|
def right_dac_mute(self) -> bool:
|
|
"""The right DAC mute status.
|
|
|
|
:return: True if right DAC is muted, False otherwise
|
|
"""
|
|
return self._page0._get_dac_volume_control()["right_mute"]
|
|
|
|
@right_dac_mute.setter
|
|
def right_dac_mute(self, mute: bool) -> None:
|
|
"""The right DAC mute status.
|
|
|
|
:param mute: True to mute right DAC, False to unmute
|
|
"""
|
|
current: DACVolumeControl = self._page0._get_dac_volume_control()
|
|
self._page0._set_dac_volume_control(current["left_mute"], mute, current["control"])
|
|
|
|
@property
|
|
def dac_volume_control_mode(self) -> int:
|
|
"""The DAC volume control mode.
|
|
|
|
:return: One of the VOL_* constants
|
|
"""
|
|
return self._page0._get_dac_volume_control()["control"]
|
|
|
|
@dac_volume_control_mode.setter
|
|
def dac_volume_control_mode(self, mode: int) -> None:
|
|
"""The volume control mode.
|
|
|
|
:param mode: One of the VOL_* constants for volume control mode
|
|
:raises ValueError: If mode is not a valid VOL_* constant
|
|
"""
|
|
valid_modes: List[int] = [VOL_INDEPENDENT, VOL_LEFT_TO_RIGHT, VOL_RIGHT_TO_LEFT]
|
|
if mode not in valid_modes:
|
|
raise ValueError(
|
|
f"Invalid volume control mode: {mode}. Must be one of the VOL_* constants."
|
|
)
|
|
current: DACVolumeControl = self._page0._get_dac_volume_control()
|
|
self._page0._set_dac_volume_control(current["left_mute"], current["right_mute"], mode)
|
|
|
|
@property
|
|
def left_dac_channel_volume(self) -> float:
|
|
"""Left DAC channel volume in dB.
|
|
|
|
:return: Volume in dB
|
|
"""
|
|
return self._page0._get_channel_volume(False)
|
|
|
|
@left_dac_channel_volume.setter
|
|
def left_dac_channel_volume(self, db: float) -> None:
|
|
"""Left DAC channel volume in dB.
|
|
|
|
:param db: Volume in dB
|
|
"""
|
|
self._page0._set_channel_volume(False, db)
|
|
|
|
@property
|
|
def right_dac_channel_volume(self) -> float:
|
|
"""Right DAC channel volume in dB.
|
|
|
|
:return: Volume in dB
|
|
"""
|
|
return self._page0._get_channel_volume(True)
|
|
|
|
@right_dac_channel_volume.setter
|
|
def right_dac_channel_volume(self, db: float) -> None:
|
|
"""Right DAC channel volume in dB.
|
|
|
|
:param db: Volume in dB
|
|
"""
|
|
self._page0._set_channel_volume(True, db)
|
|
|
|
@staticmethod
|
|
def _convert_reg_to_db(reg_val: int) -> float:
|
|
"""
|
|
Convert a register value to decibel volume.
|
|
|
|
:param reg_val: 8-bit register value
|
|
:return: Volume in dB
|
|
"""
|
|
if reg_val & 0x80:
|
|
reg_val = reg_val - 256
|
|
|
|
return reg_val * 0.5
|
|
|
|
@staticmethod
|
|
def _convert_db_to_reg(db: float) -> int:
|
|
"""
|
|
Convert decibel volume to register value.
|
|
|
|
:param db: Volume in dB (-63.5 to 24 dB)
|
|
:return: 8-bit register value
|
|
"""
|
|
reg_val = int(db * 2)
|
|
if reg_val > 0x30:
|
|
reg_val = 0x30
|
|
elif reg_val < -0x80:
|
|
reg_val = 0x80
|
|
|
|
if reg_val < 0:
|
|
reg_val += 256
|
|
|
|
return reg_val & 0xFF
|
|
|
|
@property
|
|
def dac_volume(self) -> float:
|
|
"""Current DAC digital volume in dB.
|
|
|
|
Range is -63.5 dB (soft) to 24 dB (loud).
|
|
|
|
This acts on two registers at once. In the datasheet, they are:
|
|
|
|
* Page 0 / Register 65 (0x41): DAC Left Volume Control
|
|
|
|
* Page 0 / Register 66 (0x42): DAC Right Volume Control
|
|
|
|
Changing the DAC volume will change the signal level feeding into the
|
|
analog signal chains of the speaker and both headphone channels. You
|
|
should also be aware of ``speaker_volume``\, ``speaker_gain``\,
|
|
``speaker_mute``\, ``headphone_volume``\, ``headphone_left_gain``\,
|
|
``headphone_right_gain``\, ``headphone_left_mute``\, and
|
|
``headphone_right_mute``\.
|
|
"""
|
|
left_vol = self._page0._read_register(_DAC_LVOL)
|
|
right_vol = self._page0._read_register(_DAC_RVOL)
|
|
|
|
left_db = self._convert_reg_to_db(left_vol)
|
|
right_db = self._convert_reg_to_db(right_vol)
|
|
|
|
return (left_db + right_db) / 2
|
|
|
|
@dac_volume.setter
|
|
def dac_volume(self, db: float) -> None:
|
|
db = max(-63.5, min(24, db))
|
|
reg_val = self._convert_db_to_reg(db)
|
|
self._page0._set_page()
|
|
self._page0._write_register(_DAC_LVOL, reg_val)
|
|
self._page0._write_register(_DAC_RVOL, reg_val)
|
|
|
|
self.left_dac_mute = False
|
|
self.right_dac_mute = False
|
|
|
|
def manual_headphone_driver(
|
|
self,
|
|
left_powered: bool,
|
|
right_powered: bool,
|
|
common: int = 0,
|
|
power_down_on_scd: bool = False,
|
|
) -> bool:
|
|
"""Headphone driver settings.
|
|
|
|
:param left_powered: Boolean to power left headphone driver
|
|
:param right_powered: Boolean to power right headphone driver
|
|
:param common: One of the HP_COMMON_* constants for common mode voltage
|
|
:param power_down_on_scd: Boolean to power down on short circuit detection
|
|
:raises ValueError: If common is not a valid HP_COMMON_* constant
|
|
:return: True if successful, False otherwise
|
|
"""
|
|
valid_common_modes: List[int] = [
|
|
HP_COMMON_1_35V,
|
|
HP_COMMON_1_50V,
|
|
HP_COMMON_1_65V,
|
|
HP_COMMON_1_80V,
|
|
]
|
|
|
|
if common not in valid_common_modes:
|
|
raise ValueError(
|
|
f"Invalid common mode value: {common}. Must be one of the HP_COMMON_* constants."
|
|
)
|
|
|
|
return self._page1._configure_headphone_driver(
|
|
left_powered, right_powered, common, power_down_on_scd
|
|
)
|
|
|
|
def manual_headphone_left_volume(self, route_enabled: bool, gain: int = 0x7F) -> bool:
|
|
"""HPL analog volume control.
|
|
|
|
:param route_enabled: Enable routing to HPL
|
|
:param gain: Analog volume control value (0-127)
|
|
:return: True if successful, False otherwise
|
|
"""
|
|
return self._page1._set_hpl_volume(route_enabled, gain)
|
|
|
|
def manual_headphone_right_volume(self, route_enabled: bool, gain: int = 0x7F) -> bool:
|
|
"""HPR analog volume control.
|
|
|
|
:param route_enabled: Enable routing to HPR
|
|
:param gain: Analog volume control value (0-127)
|
|
:return: True if successful, False otherwise
|
|
"""
|
|
return self._page1._set_hpr_volume(route_enabled, gain)
|
|
|
|
@property
|
|
def headphone_left_gain(self) -> int:
|
|
"""Left headphone amplifier gain in dB.
|
|
|
|
Range is 0 dB (soft) to 9 dB (loud) in steps of 1 dB.
|
|
|
|
In the datasheet, this is Page 1 / Register 40 (0x28): HPL Driver.
|
|
|
|
Note that the headphone left channel volume is also affected by
|
|
``dac_volume``\, ``headphone_volume``\, and ``headphone_left_mute``\.
|
|
|
|
:raises ValueError: If set to a value outside the range of 0 to 9
|
|
"""
|
|
reg_value = self._page1._read_register(_HPL_DRIVER)
|
|
return (reg_value >> 3) & 0x0F
|
|
|
|
@headphone_left_gain.setter
|
|
def headphone_left_gain(self, gain_db: int) -> None:
|
|
unmute = not self.headphone_left_mute
|
|
# This call can raise ValueError
|
|
self._page1._configure_hpl_pga(int(gain_db), unmute)
|
|
|
|
@property
|
|
def headphone_left_mute(self) -> bool:
|
|
"""Left headphone mute status.
|
|
|
|
True means left headphone is muted, False means not muted.
|
|
"""
|
|
reg_value = self._page1._read_register(_HPL_DRIVER)
|
|
return not bool(reg_value & (1 << 2))
|
|
|
|
@headphone_left_mute.setter
|
|
def headphone_left_mute(self, mute: bool) -> None:
|
|
gain = self.headphone_left_gain
|
|
# This could in theory raise ValueError, but that's very unlikely
|
|
self._page1._configure_hpl_pga(gain, not mute)
|
|
|
|
@property
|
|
def headphone_right_gain(self) -> int:
|
|
"""Right headphone amplifier gain in dB.
|
|
|
|
Range is 0 dB (soft) to 9 dB (loud) in steps of 1 dB.
|
|
|
|
In the datasheet, this is Page 1 / Register 41 (0x29): HPR Driver.
|
|
|
|
Note that the headphone right channel volume is also affected by
|
|
``dac_volume``\, ``headphone_volume``\, and ``headphone_right_mute``\.
|
|
|
|
:raises ValueError: If set to a value outside the range of 0 to 9
|
|
"""
|
|
reg_value = self._page1._read_register(_HPR_DRIVER)
|
|
return (reg_value >> 3) & 0x0F
|
|
|
|
@headphone_right_gain.setter
|
|
def headphone_right_gain(self, gain_db: int) -> None:
|
|
unmute = not self.headphone_right_mute
|
|
# This call can raise ValueError
|
|
self._page1._configure_hpr_pga(int(gain_db), unmute)
|
|
|
|
@property
|
|
def headphone_right_mute(self) -> bool:
|
|
"""Right headphone mute status.
|
|
|
|
True means right headphone is muted, False means not muted.
|
|
"""
|
|
reg_value = self._page1._read_register(_HPR_DRIVER)
|
|
return not bool(reg_value & (1 << 2))
|
|
|
|
@headphone_right_mute.setter
|
|
def headphone_right_mute(self, mute: bool) -> None:
|
|
gain = self.headphone_right_gain
|
|
self._page1._configure_hpr_pga(gain, not mute)
|
|
|
|
@property
|
|
def speaker_gain(self) -> int:
|
|
"""Speaker amplifier gain setting in dB.
|
|
|
|
Range is 6 dB (soft) to 24 dB (loud) in steps of 6 dB.
|
|
|
|
In the datasheet, this is Page 1 / Register 42 (0x2A): Class-D Speaker
|
|
(SPK) Driver.
|
|
|
|
Note that ``dac_volume``\, ``speaker_volume``\, and ``speaker_mute``
|
|
also affect the speaker output level.
|
|
|
|
:raises ValueError: If set to anything other than 6, 12, 18, or 24
|
|
"""
|
|
# This gives us a 2-bit unsigned integer where 0 means 6 dB, 1 is 12 dB,
|
|
# 2 is 18 dB, and 3 is 24 dB
|
|
reg_value = self._page1._read_register(_SPK_DRIVER)
|
|
return (((reg_value >> 3) & 0x03) + 1) * 6
|
|
|
|
@speaker_gain.setter
|
|
def speaker_gain(self, gain_db: int) -> None:
|
|
"""
|
|
:raises ValueError: If set to anything other than 6, 12, 18, or 24
|
|
"""
|
|
unmute = not self.speaker_mute
|
|
# This relies on _configure_spk_pga() to raise ValueError if the gain
|
|
# value is out of range
|
|
self._page1._configure_spk_pga(gain_db, unmute)
|
|
|
|
@property
|
|
def speaker_mute(self) -> bool:
|
|
"""The speaker mute status.
|
|
|
|
True means speaker is muted, False means unmuted.
|
|
"""
|
|
reg_value = self._page1._read_register(_SPK_DRIVER)
|
|
return not bool(reg_value & (1 << 2))
|
|
|
|
@speaker_mute.setter
|
|
def speaker_mute(self, mute: bool) -> None:
|
|
gain = self.speaker_gain
|
|
# Unmute is inverse of mute
|
|
self._page1._configure_spk_pga(gain, not mute)
|
|
|
|
@property
|
|
def dac_flags(self) -> Dict[str, Any]:
|
|
"""The DAC and output driver status flags.
|
|
|
|
:return: Dictionary with status flags
|
|
"""
|
|
return self._page0._get_dac_flags()
|
|
|
|
@property
|
|
def gpio1_mode(self) -> int:
|
|
"""The current GPIO1 pin mode.
|
|
|
|
:return: One of the GPIO1_* mode constants
|
|
"""
|
|
value = self._page0._read_register(_GPIO1_CTRL)
|
|
return (value >> 2) & 0x0F
|
|
|
|
@gpio1_mode.setter
|
|
def gpio1_mode(self, mode: int) -> None:
|
|
"""The GPIO1 pin mode.
|
|
|
|
:param mode: One of the GPIO1_* mode constants
|
|
:raises ValueError: If mode is not a valid GPIO1_* constant
|
|
"""
|
|
valid_modes: List[int] = [
|
|
GPIO1_DISABLED,
|
|
GPIO1_INPUT_MODE,
|
|
GPIO1_GPI,
|
|
GPIO1_GPO,
|
|
GPIO1_CLKOUT,
|
|
GPIO1_INT1,
|
|
GPIO1_INT2,
|
|
GPIO1_BCLK_OUT,
|
|
GPIO1_WCLK_OUT,
|
|
]
|
|
|
|
if mode not in valid_modes:
|
|
raise ValueError(f"Invalid GPIO1 mode: {mode}. Must be one of the GPIO1_* constants.")
|
|
|
|
self._page0._set_gpio1_mode(mode)
|
|
|
|
@property
|
|
def din_input(self) -> int:
|
|
"""The current DIN input value.
|
|
|
|
:return: The DIN input value
|
|
"""
|
|
return self._page0._get_din_input()
|
|
|
|
@property
|
|
def codec_interface(self) -> Dict[str, Any]:
|
|
"""The current codec interface settings.
|
|
|
|
:return: Dictionary with codec interface settings
|
|
"""
|
|
return self._page0._get_codec_interface()
|
|
|
|
@property
|
|
def headphone_shorted(self) -> bool:
|
|
"""Check if headphone short circuit is detected.
|
|
|
|
:return: True if headphone is shorted, False otherwise
|
|
"""
|
|
return self._page1._is_headphone_shorted()
|
|
|
|
@property
|
|
def speaker_shorted(self) -> bool:
|
|
"""Check if speaker short circuit is detected.
|
|
|
|
:return: True if speaker is shorted, False otherwise
|
|
"""
|
|
return self._page1._is_speaker_shorted()
|
|
|
|
@property
|
|
def hpl_gain_applied(self) -> bool:
|
|
"""Check if all programmed gains have been applied to HPL.
|
|
|
|
:return: True if gains are applied, False otherwise
|
|
"""
|
|
return self._page1._is_hpl_gain_applied()
|
|
|
|
@property
|
|
def hpr_gain_applied(self) -> bool:
|
|
"""Check if all programmed gains have been applied to HPR.
|
|
|
|
:return: True if gains are applied, False otherwise
|
|
"""
|
|
return self._page1._is_hpr_gain_applied()
|
|
|
|
@property
|
|
def speaker_gain_applied(self) -> bool:
|
|
"""Check if all programmed gains have been applied to Speaker.
|
|
|
|
:return: True if gains are applied, False otherwise
|
|
"""
|
|
return self._page1._is_spk_gain_applied()
|
|
|
|
@property
|
|
def headset_status(self) -> int:
|
|
"""Current headset detection status.
|
|
|
|
:return: Integer value representing headset status (0=none, 1=without mic, 3=with mic)
|
|
"""
|
|
return self._page0._get_headset_status()
|
|
|
|
@property
|
|
def reset_speaker_on_scd(self) -> bool:
|
|
"""The speaker reset behavior on short circuit detection.
|
|
|
|
:return: True if speaker resets on short circuit, False otherwise
|
|
"""
|
|
value = self._page1._read_register(_HP_SPK_ERR_CTL)
|
|
return not bool((value >> 1) & 0x01)
|
|
|
|
@reset_speaker_on_scd.setter
|
|
def reset_speaker_on_scd(self, reset: bool) -> None:
|
|
"""
|
|
:param reset: True to reset speaker on short circuit, False to remain unchanged
|
|
"""
|
|
self._page1._reset_speaker_on_scd(reset)
|
|
|
|
@property
|
|
def reset_headphone_on_scd(self) -> bool:
|
|
"""The headphone reset behavior on short circuit detection.
|
|
|
|
:return: True if headphone resets on short circuit, False otherwise
|
|
"""
|
|
value = self._page1._read_register(_HP_SPK_ERR_CTL)
|
|
return not bool(value & 0x01)
|
|
|
|
@reset_headphone_on_scd.setter
|
|
def reset_headphone_on_scd(self, reset: bool) -> None:
|
|
"""
|
|
:param reset: True to reset headphone on short circuit, False to remain unchanged
|
|
"""
|
|
self._page1._reset_headphone_on_scd(reset)
|
|
|
|
def configure_headphone_pop(
|
|
self, wait_for_powerdown: bool = True, powerup_time: int = 0x07, ramp_time: int = 0x03
|
|
) -> bool:
|
|
"""Headphone pop removal settings.
|
|
|
|
:param wait_for_powerdown: Wait for amp powerdown before DAC powerdown
|
|
:param powerup_time: Driver power-on time (0-11)
|
|
:param ramp_time: Driver ramp-up step time (0-3)
|
|
:return: True if successful, False otherwise
|
|
"""
|
|
return self._page1._configure_headphone_pop(wait_for_powerdown, powerup_time, ramp_time)
|
|
|
|
@property
|
|
def speaker_wait_time(self) -> int:
|
|
"""The current speaker power-up wait time.
|
|
|
|
:return: The wait time setting (0-7)
|
|
"""
|
|
value = self._page1._read_register(_PGA_RAMP)
|
|
return (value >> 4) & 0x07
|
|
|
|
@speaker_wait_time.setter
|
|
def speaker_wait_time(self, wait_time: int) -> None:
|
|
"""Speaker power-up wait time.
|
|
|
|
:param wait_time: Speaker power-up wait duration (0-7)
|
|
"""
|
|
self._page1._set_speaker_wait_time(wait_time)
|
|
|
|
@property
|
|
def headphone_lineout(self) -> bool:
|
|
"""The current headphone line-out configuration.
|
|
|
|
:return: True if both channels are configured as line-out, False otherwise
|
|
"""
|
|
value = self._page1._read_register(_HP_DRIVER_CTRL)
|
|
left = bool(value & (1 << 2))
|
|
right = bool(value & (1 << 1))
|
|
return left and right
|
|
|
|
@headphone_lineout.setter
|
|
def headphone_lineout(self, enabled: bool) -> None:
|
|
"""
|
|
:param enabled: True to configure both channels as line-out, False otherwise
|
|
"""
|
|
self._page1._headphone_lineout(enabled, enabled)
|
|
|
|
def config_mic_bias(
|
|
self, power_down: bool = False, always_on: bool = False, voltage: int = 0
|
|
) -> bool:
|
|
"""MICBIAS settings.
|
|
|
|
:param power_down: Enable software power down
|
|
:param always_on: Keep MICBIAS on even without headset
|
|
:param voltage: MICBIAS voltage setting (0-3)
|
|
:return: True if successful, False otherwise
|
|
"""
|
|
return self._page1._config_mic_bias(power_down, always_on, voltage)
|
|
|
|
def set_input_common_mode(self, ain1_cm: bool, ain2_cm: bool) -> bool:
|
|
"""Analog input common mode connections.
|
|
|
|
:param ain1_cm: Connect AIN1 to common mode when unused
|
|
:param ain2_cm: Connect AIN2 to common mode when unused
|
|
:return: True if successful, False otherwise
|
|
"""
|
|
return self._page1._set_input_common_mode(ain1_cm, ain2_cm)
|
|
|
|
def config_delay_divider(self, use_mclk: bool = True, divider: int = 1) -> bool:
|
|
"""Programmable delay timer clock source and divider.
|
|
|
|
:param use_mclk: True to use external MCLK, False for internal oscillator
|
|
:param divider: Clock divider (1-127, or 0 for 128)
|
|
:return: True if successful, False otherwise
|
|
"""
|
|
return self._page3._config_delay_divider(use_mclk, divider)
|
|
|
|
@property
|
|
def vol_adc_pin_control(self) -> bool:
|
|
"""The volume ADC pin control status.
|
|
|
|
:return: True if volume ADC pin control is enabled, False otherwise
|
|
"""
|
|
reg_value = self._page0._read_register(_VOL_ADC_CTRL)
|
|
return bool(reg_value & (1 << 7))
|
|
|
|
@vol_adc_pin_control.setter
|
|
def vol_adc_pin_control(self, enabled: bool) -> None:
|
|
"""
|
|
:param enabled: True to enable volume ADC pin control, False to disable
|
|
"""
|
|
current_config = self._get_vol_adc_config()
|
|
self._page0._config_vol_adc(
|
|
enabled,
|
|
current_config["use_mclk"],
|
|
current_config["hysteresis"],
|
|
current_config["rate"],
|
|
)
|
|
|
|
@property
|
|
def vol_adc_use_mclk(self) -> bool:
|
|
"""The volume ADC use MCLK status.
|
|
|
|
:return: True if volume ADC uses MCLK, False otherwise
|
|
"""
|
|
reg_value = self._page0._read_register(_VOL_ADC_CTRL)
|
|
return bool(reg_value & (1 << 6))
|
|
|
|
@vol_adc_use_mclk.setter
|
|
def vol_adc_use_mclk(self, use_mclk: bool) -> None:
|
|
"""
|
|
:param use_mclk: True to use MCLK, False to use internal oscillator
|
|
"""
|
|
current_config = self._get_vol_adc_config()
|
|
self._page0._config_vol_adc(
|
|
current_config["pin_control"],
|
|
use_mclk,
|
|
current_config["hysteresis"],
|
|
current_config["rate"],
|
|
)
|
|
|
|
@property
|
|
def vol_adc_hysteresis(self) -> int:
|
|
"""The volume ADC hysteresis setting.
|
|
|
|
:return: Hysteresis value (0-3)
|
|
"""
|
|
reg_value = self._page0._read_register(_VOL_ADC_CTRL)
|
|
return (reg_value >> 4) & 0x03
|
|
|
|
@vol_adc_hysteresis.setter
|
|
def vol_adc_hysteresis(self, hysteresis: int) -> None:
|
|
"""
|
|
:param hysteresis: Hysteresis value (0-3)
|
|
"""
|
|
current_config = self._get_vol_adc_config()
|
|
self._page0._config_vol_adc(
|
|
current_config["pin_control"],
|
|
current_config["use_mclk"],
|
|
hysteresis,
|
|
current_config["rate"],
|
|
)
|
|
|
|
@property
|
|
def vol_adc_rate(self) -> int:
|
|
"""The volume ADC sampling rate.
|
|
|
|
:return: Rate value (0-7)
|
|
"""
|
|
reg_value = self._page0._read_register(_VOL_ADC_CTRL)
|
|
return reg_value & 0x07
|
|
|
|
@vol_adc_rate.setter
|
|
def vol_adc_rate(self, rate: int) -> None:
|
|
"""
|
|
|
|
:param rate: Rate value (0-7)
|
|
"""
|
|
current_config = self._get_vol_adc_config()
|
|
self._page0._config_vol_adc(
|
|
current_config["pin_control"],
|
|
current_config["use_mclk"],
|
|
current_config["hysteresis"],
|
|
rate,
|
|
)
|
|
|
|
def _get_vol_adc_config(self) -> Dict[str, Any]:
|
|
"""Helper method for the current volume ADC configuration.
|
|
|
|
:return: Dictionary with current volume ADC configuration
|
|
"""
|
|
reg_value = self._page0._read_register(_VOL_ADC_CTRL)
|
|
return {
|
|
"pin_control": bool(reg_value & (1 << 7)),
|
|
"use_mclk": bool(reg_value & (1 << 6)),
|
|
"hysteresis": (reg_value >> 4) & 0x03,
|
|
"rate": reg_value & 0x07,
|
|
}
|
|
|
|
@property
|
|
def vol_adc_db(self) -> float:
|
|
"""The current volume from the Volume ADC in dB.
|
|
|
|
:return: Volume in dB
|
|
"""
|
|
return self._page0._read_vol_adc_db()
|
|
|
|
def int2_sources(
|
|
self,
|
|
headset_detect: bool = False,
|
|
button_press: bool = False,
|
|
dac_drc: bool = False,
|
|
agc_noise: bool = False,
|
|
over_current: bool = False,
|
|
multiple_pulse: bool = False,
|
|
) -> bool:
|
|
"""Configure the INT2 interrupt sources.
|
|
|
|
:param headset_detect: Enable headset detection interrupt
|
|
:param button_press: Enable button press detection interrupt
|
|
:param dac_drc: Enable DAC DRC signal power interrupt
|
|
:param agc_noise: Enable DAC data overflow interrupt
|
|
:param over_current: Enable short circuit interrupt
|
|
:param multiple_pulse: If true, INT2 generates multiple pulses until flag read
|
|
:return: True if successful, False otherwise
|
|
"""
|
|
return self._page0._set_int2_source(
|
|
headset_detect, button_press, dac_drc, agc_noise, over_current, multiple_pulse
|
|
)
|
|
|
|
def configure_clocks(
|
|
self, sample_rate: int, bit_depth: int = 16, mclk_freq: Optional[int] = None
|
|
):
|
|
"""Configure the TLV320DAC3100 clock settings.
|
|
|
|
This function configures all necessary clock settings including PLL, dividers,
|
|
and interface settings to achieve the requested sample rate.
|
|
|
|
:param sample_rate: The desired sample rate in Hz (e.g., 44100, 48000)
|
|
:param bit_depth: The bit depth (16, 20, 24, or 32), defaults to 16
|
|
:param mclk_freq: The main clock frequency in Hz (e.g., 12000000 for 12MHz)
|
|
If None (default), BCLK will be used as the PLL input source
|
|
:return: True if successful, False otherwise
|
|
"""
|
|
self._sample_rate = sample_rate
|
|
self._bit_depth = bit_depth
|
|
if mclk_freq is not None:
|
|
self._mclk_freq = mclk_freq
|
|
else:
|
|
self._mclk_freq = 0 # Internally use 0 to indicate BCLK mode
|
|
|
|
return self._page0._configure_clocks_for_sample_rate(
|
|
self._mclk_freq, sample_rate, bit_depth
|
|
)
|
|
|
|
@property
|
|
def headphone_output(self) -> bool:
|
|
"""Headphone output helper with quickstart settings for users.
|
|
Headphone output state (True if either left or right channel is powered).
|
|
|
|
:return: True if headphone output is enabled, False otherwise
|
|
"""
|
|
hp_drivers = self._page1._read_register(_HP_DRIVERS)
|
|
left_powered = bool(hp_drivers & (1 << 7))
|
|
right_powered = bool(hp_drivers & (1 << 6))
|
|
return left_powered or right_powered
|
|
|
|
@headphone_output.setter
|
|
def headphone_output(self, enabled: bool) -> None:
|
|
"""
|
|
:param enabled: True to enable headphone output, False to disable
|
|
"""
|
|
if enabled:
|
|
self.left_dac = True
|
|
self.right_dac = True
|
|
self.left_dac_channel_volume = 0
|
|
self.right_dac_channel_volume = 0
|
|
self.left_dac_mute = False
|
|
self.right_dac_mute = False
|
|
self.left_dac_path = DAC_PATH_NORMAL
|
|
self.right_dac_path = DAC_PATH_NORMAL
|
|
self.headphone_left_gain = 0
|
|
self.headphone_right_gain = 0
|
|
self._page1._configure_headphone_driver(
|
|
left_powered=True, right_powered=True, common=HP_COMMON_1_65V
|
|
)
|
|
self._page1._configure_analog_inputs(left_dac=DAC_ROUTE_HP, right_dac=DAC_ROUTE_HP)
|
|
self.headphone_left_mute = False
|
|
self.headphone_right_mute = False
|
|
else:
|
|
self._page1._configure_headphone_driver(left_powered=False, right_powered=False)
|
|
|
|
@property
|
|
def speaker_output(self) -> bool:
|
|
"""Speaker output helper with quickstart settings for users.
|
|
Speaker output state.
|
|
|
|
:return: True if speaker output is enabled, False otherwise
|
|
"""
|
|
return self._page1._get_speaker_enabled()
|
|
|
|
@speaker_output.setter
|
|
def speaker_output(self, enabled: bool) -> None:
|
|
"""
|
|
:param enabled: True to enable speaker, False to disable
|
|
"""
|
|
if enabled:
|
|
self.left_dac = True
|
|
self.right_dac = True
|
|
self.left_dac_channel_volume = 0
|
|
self.right_dac_channel_volume = 0
|
|
self.left_dac_mute = False
|
|
self.right_dac_mute = False
|
|
self.left_dac_path = DAC_PATH_NORMAL
|
|
self.right_dac_path = DAC_PATH_NORMAL
|
|
self.speaker_gain = 6 # safest speaker amp gain option: 6 dB
|
|
self._page1._set_speaker_enabled(True)
|
|
self._page1._configure_analog_inputs(
|
|
left_dac=DAC_ROUTE_MIXER, right_dac=DAC_ROUTE_MIXER
|
|
)
|
|
self.speaker_volume = -20
|
|
self.speaker_mute = False
|
|
else:
|
|
self._page1._set_speaker_enabled(False)
|
|
|
|
@property
|
|
def headphone_volume(self) -> float:
|
|
"""Current headphone analog volume in dB.
|
|
|
|
Range is 0 (loud) to -78.3 (very soft).
|
|
|
|
This acts on two registers at once. In the datasheet they are:
|
|
|
|
* Page 1 / Register 36 (0x24): Left Analog Volume to HPL
|
|
|
|
* Page 1 / Register 37 (0x25) Right Analog Volume to HPR
|
|
|
|
Note that headphone output is also affected by ``dac_volume``\,
|
|
``headphone_left_gain``\, ``headphone_right_gain``\,
|
|
``headphone_left_mute``\, and ``headphone_right_mute``\.
|
|
"""
|
|
left_gain_u7 = self._page1._read_register(_HPL_VOL) & 0x7F
|
|
right_gain_u7 = self._page1._read_register(_HPR_VOL) & 0x7F
|
|
left_db = self._table_6_24_uint7_to_db(left_gain_u7)
|
|
right_db = self._table_6_24_uint7_to_db(right_gain_u7)
|
|
if left_db == right_db:
|
|
return left_db
|
|
else:
|
|
return (left_db + right_db) / 2
|
|
|
|
@headphone_volume.setter
|
|
def headphone_volume(self, db: float) -> None:
|
|
# The table 6-24 lookup function includes min/max range clipping
|
|
gain_u7 = self._table_6_24_db_to_uint7(db)
|
|
self._page1._set_hpl_volume(route_enabled=True, gain=gain_u7)
|
|
self._page1._set_hpr_volume(route_enabled=True, gain=gain_u7)
|
|
|
|
@property
|
|
def speaker_volume(self) -> float:
|
|
"""Current speaker analog volume in dB.
|
|
|
|
Range is 0 (loud) to -78.3 (very soft).
|
|
|
|
In the datasheet, this is Page 1 / Register 38 (0x26): Left Analog
|
|
Volume to SPK.
|
|
|
|
Note that ``dac_volume``\, ``speaker_gain``\, and ``speaker_mute`` also
|
|
affect the speaker output level.
|
|
"""
|
|
gain_u7 = self._page1._read_register(_SPK_VOL) & 0x7F
|
|
return self._table_6_24_uint7_to_db(gain_u7)
|
|
|
|
@speaker_volume.setter
|
|
def speaker_volume(self, db: float) -> None:
|
|
# The table 6-24 lookup function includes min/max range clipping
|
|
gain_u7 = self._table_6_24_uint7_to_dB(db)
|
|
self._page1._set_spk_volume(route_enabled=True, gain=gain_u7)
|
|
|
|
@property
|
|
def sample_rate(self) -> int:
|
|
"""Configured sample rate in Hz.
|
|
|
|
:return: The sample rate in Hz
|
|
"""
|
|
return self._sample_rate
|
|
|
|
@property
|
|
def bit_depth(self) -> int:
|
|
"""Configured bit depth.
|
|
|
|
:return: The bit depth
|
|
"""
|
|
return self._bit_depth
|
|
|
|
@property
|
|
def mclk_freq(self) -> int:
|
|
"""Configured MCLK frequency in Hz.
|
|
|
|
:return: The MCLK frequency in Hz
|
|
"""
|
|
return self._mclk_freq
|