Adafruit_CircuitPython_TLV320/adafruit_tlv320.py
Liz 08a9a85b1c fix init order for speaker
tested with rev b fruit jam
2025-03-21 17:04:07 -04:00

1910 lines
64 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:**
* `Link Text <url>`_
**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_DISABLED = const(0b0000) # GPIO1 disabled (input and output buffers powered down)
GPIO1_INPUT_MODE = const(0b0001) # Input mode (secondary BCLK/WCLK/DIN input or ClockGen)
GPIO1_GPI = const(0b0010) # General-purpose input
GPIO1_GPO = const(0b0011) # General-purpose output
GPIO1_CLKOUT = const(0b0100) # CLKOUT output
GPIO1_INT1 = const(0b0101) # INT1 output
GPIO1_INT2 = const(0b0110) # INT2 output
GPIO1_BCLK_OUT = const(0b1000) # Secondary BCLK output for codec interface
GPIO1_WCLK_OUT = const(0b1001) # Secondary WCLK output for codec interface
# DAC channel data path options
DAC_PATH_OFF = const(0b00) # DAC data path off
DAC_PATH_NORMAL = const(0b01) # Normal path (L->L or R->R)
DAC_PATH_SWAPPED = const(0b10) # Swapped path (R->L or L->R)
DAC_PATH_MIXED = const(0b11) # Mixed L+R path
# DAC volume control soft stepping options
VOLUME_STEP_1SAMPLE = const(0b00) # One step per sample
VOLUME_STEP_2SAMPLE = const(0b01) # One step per two samples
VOLUME_STEP_DISABLED = const(0b10) # Soft stepping disabled
# DAC volume control configuration options
VOL_INDEPENDENT = const(0b00) # Left and right channels independent
VOL_LEFT_TO_RIGHT = const(0b01) # Left follows right volume
VOL_RIGHT_TO_LEFT = const(0b10) # Right follows left volume
# DAC output routing options
DAC_ROUTE_NONE = const(0b00) # DAC not routed
DAC_ROUTE_MIXER = const(0b01) # DAC routed to mixer amplifier
DAC_ROUTE_HP = const(0b10) # DAC routed directly to HP driver
# Speaker amplifier gain options
SPK_GAIN_6DB = const(0b00) # 6 dB gain
SPK_GAIN_12DB = const(0b01) # 12 dB gain
SPK_GAIN_18DB = const(0b10) # 18 dB gain
SPK_GAIN_24DB = const(0b11) # 24 dB gain
# Headphone common mode voltage settings
HP_COMMON_1_35V = const(0b00) # Common-mode voltage 1.35V
HP_COMMON_1_50V = const(0b01) # Common-mode voltage 1.50V
HP_COMMON_1_65V = const(0b10) # Common-mode voltage 1.65V
HP_COMMON_1_80V = const(0b11) # Common-mode voltage 1.80V
# Headset detection debounce time options
DEBOUNCE_16MS = const(0b000) # 16ms debounce (2ms clock)
DEBOUNCE_32MS = const(0b001) # 32ms debounce (4ms clock)
DEBOUNCE_64MS = const(0b010) # 64ms debounce (8ms clock)
DEBOUNCE_128MS = const(0b011) # 128ms debounce (16ms clock)
DEBOUNCE_256MS = const(0b100) # 256ms debounce (32ms clock)
DEBOUNCE_512MS = const(0b101) # 512ms debounce (64ms clock)
# Button press debounce time options
BTN_DEBOUNCE_0MS = const(0b00) # No debounce
BTN_DEBOUNCE_8MS = const(0b01) # 8ms debounce (1ms clock)
BTN_DEBOUNCE_16MS = const(0b10) # 16ms debounce (2ms clock)
BTN_DEBOUNCE_32MS = const(0b11) # 32ms debounce (4ms clock)
# 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."""
if gain_db > 9:
raise ValueError("Gain cannot be greater than 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."""
if gain_db > 9:
raise ValueError("Gain cannot be greater than 9")
value = (gain_db & 0x0F) << 3
if unmute:
value |= 1 << 2
self._write_register(_HPR_DRIVER, value)
def _configure_spk_pga(self, gain=SPK_GAIN_6DB, unmute=True):
"""Speaker driver settings."""
value = (gain & 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)
# 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)
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:
"""The left headphone gain in dB.
:return: Gain value in dB
"""
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:
"""The left headphone gain in dB.
:param gain_db: Gain value in dB
"""
unmute = not self.headphone_left_mute
self._page1._configure_hpl_pga(gain_db, unmute)
@property
def headphone_left_mute(self) -> bool:
"""The left headphone mute status.
:return: True if left headphone is muted, False otherwise
"""
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:
"""The left headphone mute status.
:param mute: True to mute left headphone, False to unmute
"""
gain = self.headphone_left_gain
self._page1._configure_hpl_pga(gain, not mute)
@property
def headphone_right_gain(self) -> int:
"""The right headphone gain in dB.
:return: Gain value in dB
"""
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:
"""The right headphone gain in dB.
:param gain_db: Gain value in dB
"""
unmute = not self.headphone_right_mute
self._page1._configure_hpr_pga(gain_db, unmute)
@property
def headphone_right_mute(self) -> bool:
"""The right headphone mute status.
:return: True if right headphone is muted, False otherwise
"""
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:
"""The right headphone mute status.
:param mute: True to mute right headphone, False to unmute
"""
gain = self.headphone_right_gain
self._page1._configure_hpr_pga(gain, not mute)
@property
def speaker_gain(self) -> int:
"""The speaker gain setting in dB.
:return: The gain value in dB
"""
reg_value = self._page1._read_register(_SPK_DRIVER)
return (reg_value >> 3) & 0x03
@speaker_gain.setter
def speaker_gain(self, gain_db: int) -> None:
"""The speaker gain in dB.
:param gain_db: Speaker gain in dB (6, 12, 18, or 24)
:raises ValueError: If gain_db is not a valid value
"""
# Convert dB to register value
gain_mapping: List[int] = [SPK_GAIN_6DB, SPK_GAIN_12DB, SPK_GAIN_18DB, SPK_GAIN_24DB]
if gain_db not in gain_mapping:
raise ValueError(
f"Invalid preset value: {gain_db}. Must be one of the SPK_GAIN_* constants."
)
unmute = not self.speaker_mute
self._page1._configure_spk_pga(gain_db, unmute)
@property
def speaker_mute(self) -> bool:
"""The speaker mute status.
:return: True if speaker is muted, False otherwise
"""
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:
"""The speaker mute status.
:param mute: True to mute speaker, False to unmute
"""
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 = SPK_GAIN_6DB
self._page1._set_speaker_enabled(True)
self._page1._configure_analog_inputs(
left_dac=DAC_ROUTE_MIXER, right_dac=DAC_ROUTE_MIXER
)
self.speaker_volume = -10
self.speaker_mute = False
else:
self._page1._set_speaker_enabled(False)
@property
def headphone_volume(self) -> float:
"""The current headphone volume in dB.
:return: The volume in dB (0 = max, -63.5 = min)
"""
left_gain = self._page1._read_register(_HPL_VOL) & 0x7F
right_gain = self._page1._read_register(_HPR_VOL) & 0x7F
avg_gain = (left_gain + right_gain) / 2
# Convert from register value to dB
# 55 ≈ 0dB, 0 ≈ -63.5dB
db = (avg_gain - 55) / 1.14
return db
@headphone_volume.setter
def headphone_volume(self, db: float) -> None:
"""
:param db: Volume in dB (0 = max, -63.5 = min)
"""
# Convert from dB to register gain value (0-127)
# 0dB = ~55, -63.5dB = 0
if db > 0:
db = 0 # Limit to 0dB to prevent distortion
gain = int(55 + (db * 1.14))
gain = max(0, min(gain, 127))
self._page1._set_hpl_volume(route_enabled=True, gain=gain)
self._page1._set_hpr_volume(route_enabled=True, gain=gain)
@property
def speaker_volume(self) -> float:
"""The current speaker volume in dB.
:return: The volume in dB (0 = max, -63.5 = min)
"""
gain = self._page1._read_register(_SPK_VOL) & 0x7F
# Convert from register value to dB
# 55 ≈ 0dB, 0 ≈ -63.5dB
db = (gain - 55) / 1.14
return db
@speaker_volume.setter
def speaker_volume(self, db: float) -> None:
"""
:param db: Volume in dB (0 = max, -63.5 = min)
"""
if db > 0:
db = 0
gain = int(55 + (db * 1.14))
gain = max(0, min(gain, 127))
self._page1._set_spk_volume(route_enabled=True, gain=gain)
@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