| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442 |
- /**
- * Implementation of control interface to TI TAS58xx (TAS5825M) DAC/Amp
- * TAS5825M datasheet:
- * https://www.ti.com/lit/ds/symlink/tas5825m.pdf
- */
- #include "dac_tas58xx.h"
- #include "dac_tas58xx_eq.h"
- #include "board_utils.h"
- #include <math.h>
- #include <string.h>
- #include <sys/param.h>
- #include "driver/i2c_master.h"
- #include "esp_log.h"
- #include "freertos/FreeRTOS.h"
- #include "freertos/semphr.h"
- #include "freertos/task.h"
- /* ---------- TAS5825M I2C addresses (7-bit) ---------- */
- #define TAS5825M_ADDR_GND 0x4C // ADR pin = 0 Ω to GND
- #define TAS5825M_ADDR_1K 0x4D // ADR pin = 1 kΩ to GND
- #define TAS5825M_ADDR_4K7 0x4E // ADR pin = 4.7 kΩ to GND
- #define TAS5825M_ADDR_15K 0x4F // ADR pin = 15 kΩ to GND
- /* ---------- TAS5805M I2C addresses (7-bit) ---------- */
- #define TAS5805M_ADDR_4K7 0x2C // ADR pin = 4.7 kΩ to DVDD
- #define TAS5805M_ADDR_15K 0x2D // ADR pin = 15 kΩ to DVDD
- #define TAS5805M_ADDR_47K 0x2E // ADR pin = 47 kΩ to DVDD
- #define TAS5805M_ADDR_120K 0x2F // ADR pin = 120 kΩ to DVDD
- /* ---------- Register addresses (Book 0, Page 0) ---------- */
- #define REG_PAGE_SEL 0x00
- #define REG_BOOK_SEL 0x7F
- #define REG_RESET_CTRL 0x01
- #define REG_DEVICE_CTRL1 0x02
- #define REG_DEVICE_CTRL2 0x03
- #define REG_SIG_CH_CTRL 0x28
- #define REG_CLOCK_DET_CTRL 0x29
- #define REG_SDOUT_SEL 0x30
- #define REG_SAP_CTRL1 0x33 // I2S format + word length
- #define REG_SAP_CTRL2 0x34 // Data offset
- #define REG_SAP_CTRL3 0x35 // L/R channel routing
- #define REG_DSP_PGM_MODE 0x40
- #define REG_DSP_CTRL 0x46
- #define REG_DIG_VOL 0x4C // Digital volume (both channels)
- #define REG_DIG_VOL_CTRL1 0x4E // Volume ramp control
- #define REG_AUTO_MUTE_CTRL 0x50
- #define REG_AUTO_MUTE_TIME 0x51
- #define REG_ANA_CTRL 0x53
- #define REG_AGAIN 0x54 // Analog gain
- #define REG_GPIO_CTL 0x60
- #define REG_GPIO0 0x61
- #define REG_GPIO1 0x62
- #define REG_GPIO2 0x63
- #define REG_DSP_MISC 0x66
- #define REG_GPIO_OFF 0x00
- #define REG_GPIO_WARN 0b1000
- #define REG_GPIO_FAULT 0b1011
- #define REG_GPIO_SDOUT 0b1001
- #define REG_GPIO_CTL_OUT 0b0111
- #define REG_DIE_ID 0x67 // Expected: 0x95
- #define REG_POWER_STATE 0x68
- #define REG_CHAN_FAULT 0x70
- #define REG_GLOBAL_FAULT1 0x71
- #define REG_GLOBAL_FAULT2 0x72
- #define REG_WARNING 0x73
- #define REG_FAULT_CLEAR 0x78
- /* ---------- DEVICE_CTRL2 (0x03) bit fields ---------- */
- #define CTRL2_MUTE (1 << 3)
- #define CTRL2_DIS_DSP (1 << 4)
- #define CTRL2_STATE_MASK 0x03
- #define CTRL2_DEEP_SLEEP 0x00
- #define CTRL2_SLEEP 0x01
- #define CTRL2_HIZ 0x02
- #define CTRL2_PLAY 0x03
- /* ---------- DIG_VOL (0x4C) ---------- */
- // 0x00 = +24.0 dB, 0x30 = 0.0 dB, 0xFE = -103.0 dB, 0xFF = mute
- // step = -0.5 dB per increment
- #define DIG_VOL_0DB 0x30
- #define DIG_VOL_MUTE 0xFF
- /* ---------- AGAIN (0x54) ---------- */
- // bits[4:0]: 0x00 = 0 dB, each step = -0.5 dB, max 0x1F = -15.5 dB
- /* ---------- RESET_CTRL (0x01) ---------- */
- #define RESET_DIG_CORE (1 << 4)
- #define RESET_REG (1 << 0)
- /* ---------- Constants ---------- */
- #define I2C_TIMEOUT 100 // ms
- #define I2C_LINE_SPEED 400000 // TAS5825M supports fast-mode 400 kHz
- #define TAS5805M_DIE_ID 0x0
- #define TAS5825M_DIE_ID 0x95
- static const char TAG[] = "TAS58xx DAC";
- typedef enum {
- TAS58XX_MODEL_UNKNOWN = 0,
- TAS58XX_MODEL_TAS5805M = 1,
- TAS58XX_MODEL_TAS5825M = 2,
- } tas58xx_model_t;
- /* ---------- Init sequence ---------- */
- struct tas58xx_cmd_s {
- uint8_t reg;
- uint8_t value;
- };
- /*
- * Startup procedure from datasheet §9.5.3.1:
- * 1. Go to Book 0 / Page 0
- * 2. Reset device registers
- * 3. Configure device into HiZ with DSP enabled
- * 4. Wait ≥5 ms for clocks to settle
- * 5. Configure I2S format + word length
- * 6. Set DSP to ROM mode (simple passthrough, no custom coefficients)
- * 7. Set default analog gain
- * 8. Set volume ramp rates
- * 9. Configure auto-mute
- * 10. Clear faults
- *
- * NOTE: We do NOT transition to Play here — I2S clocks are not yet
- * running when dac_init() is called, so the PLL cannot lock and the
- * device will stay stuck in HiZ. The transition to Play happens
- * later via dac_set_power_mode(DAC_POWER_ON) once I2S is active.
- */
- static const struct tas58xx_cmd_s tas5825m_init_seq[] = {
- {REG_PAGE_SEL, 0x00}, // Select Book 0 Page 0
- {REG_BOOK_SEL, 0x00}, // Select Book 0
- {REG_PAGE_SEL, 0x00}, // Confirm Page 0
- {REG_RESET_CTRL, RESET_REG}, // Reset control port registers
- {REG_DEVICE_CTRL2, CTRL2_HIZ},
- // I2S format: standard I2S, 16-bit word length
- {REG_SAP_CTRL1, 0x00}, // DATA_FORMAT=I2S(00), WORD_LENGTH=16bit(00)
- {REG_CLOCK_DET_CTRL, 0x00},
- // DSP: Process Flow 1 (Base/Pro, 96kHz, 2.0)
- {REG_DSP_PGM_MODE, 0x01},
- {REG_DSP_CTRL, 0x01}, // Use default coefficients
- // Volume ramp: smooth transitions
- {REG_DIG_VOL_CTRL1, 0x33}, // Default ramp rates
- // Auto-mute: enable for both channels
- {REG_AUTO_MUTE_CTRL, 0x07},
- {REG_AUTO_MUTE_TIME, 0x00},
- // Clear any pending faults
- {REG_FAULT_CLEAR, 0x80},
- // Set SDOUT source to Pre-DSP
- {REG_SDOUT_SEL, 0x01},
- // GPIO config - WARN/FLT LEDs and SDOUT pin
- {REG_GPIO0, REG_GPIO_WARN},
- {REG_GPIO1, REG_GPIO_FAULT},
- {REG_GPIO2, REG_GPIO_SDOUT},
- {REG_GPIO_CTL, REG_GPIO_CTL_OUT},
- // Set digital volume to 0 dB initially
- {REG_DIG_VOL, DIG_VOL_0DB},
- // Analog gain: 0 dB
- {REG_AGAIN, 0x00},
- {0xFF, 0xFF} // End of table sentinel
- };
- /* TAS5805M is slightly simpler configuration, namely
- - lack of GPIO configuration
- - no process flow select register
- - DSP_MISC register to configure BQ coefficients per channel */
- static const struct tas58xx_cmd_s tas5805m_init_seq[] = {
- {REG_PAGE_SEL, 0x00}, // Select Book 0 Page 0
- {REG_BOOK_SEL, 0x00}, // Select Book 0
- {REG_PAGE_SEL, 0x00}, // Confirm Page 0
- {REG_RESET_CTRL, RESET_REG}, // Reset control port registers
- {REG_DEVICE_CTRL2, CTRL2_HIZ},
- // I2S format: standard I2S, 16-bit word length
- {REG_SAP_CTRL1, 0x00}, // DATA_FORMAT=I2S(00), WORD_LENGTH=16bit(00)
- {REG_CLOCK_DET_CTRL, 0x00},
- // Volume ramp: smooth transitions
- {REG_DIG_VOL_CTRL1, 0x33}, // Default ramp rates
- // Auto-mute: enable for both channels
- {REG_AUTO_MUTE_CTRL, 0x03},
- {REG_AUTO_MUTE_TIME, 0x00},
- // Clear any pending faults
- {REG_FAULT_CLEAR, 0x80},
- // Set SDOUT source to Pre-DSP
- {REG_SDOUT_SEL, 0x01},
- // Set BQ coefficients to be unique per channel
- {REG_DSP_MISC, 0x08},
- // Set digital volume to 0 dB initially
- {REG_DIG_VOL, DIG_VOL_0DB},
- // Analog gain: 0 dB
- {REG_AGAIN, 0x00},
- {0xFF, 0xFF} // End of table sentinel
- };
- /* ---------- State ---------- */
- static uint8_t tas58xx_addr;
- static tas58xx_model_t tas58xx_model = TAS58XX_MODEL_UNKNOWN;
- static i2c_master_bus_handle_t s_bus_handle = NULL;
- static i2c_master_dev_handle_t tas58xx_device_handle;
- static bool s_dsp_defaults_written = false;
- /**
- * Mutex protecting all TAS5825M register access.
- *
- * The TAS5825M uses a page/book register model: writing to any register
- * beyond Page 0 requires first selecting the target book and page via
- * REG_PAGE_SEL (0x00) and REG_BOOK_SEL (0x7F). This makes register
- * access non-atomic: a context switch between selecting a page and
- * writing the target register will corrupt the operation.
- *
- * All functions that touch the I2C bus MUST hold this mutex. Public API
- * functions acquire it; internal helpers assume it's already held.
- */
- static SemaphoreHandle_t s_reg_mutex = NULL;
- #define REG_LOCK() xSemaphoreTake(s_reg_mutex, portMAX_DELAY)
- #define REG_UNLOCK() xSemaphoreGive(s_reg_mutex)
- /* ---------- Forward declarations ---------- */
- static esp_err_t tas58xx_write_reg(uint8_t reg, uint8_t value);
- static esp_err_t tas58xx_read_reg(uint8_t reg, uint8_t *value);
- /* ---------- Detect ---------- */
- static uint8_t tas58xx_detect(i2c_master_bus_handle_t bus) {
- static const struct {
- uint8_t addr;
- tas58xx_model_t model;
- const char *name;
- } candidates[] = {
- {TAS5825M_ADDR_GND, TAS58XX_MODEL_TAS5825M, "TAS5825M"},
- {TAS5825M_ADDR_1K, TAS58XX_MODEL_TAS5825M, "TAS5825M"},
- {TAS5825M_ADDR_4K7, TAS58XX_MODEL_TAS5825M, "TAS5825M"},
- {TAS5825M_ADDR_15K, TAS58XX_MODEL_TAS5825M, "TAS5825M"},
- {TAS5805M_ADDR_4K7, TAS58XX_MODEL_TAS5805M, "TAS5805M"},
- {TAS5805M_ADDR_15K, TAS58XX_MODEL_TAS5805M, "TAS5805M"},
- {TAS5805M_ADDR_47K, TAS58XX_MODEL_TAS5805M, "TAS5805M"},
- {TAS5805M_ADDR_120K, TAS58XX_MODEL_TAS5805M, "TAS5805M"},
- };
- if (!bus) {
- ESP_LOGE(TAG, "Invalid I2C handle");
- return 0;
- }
- for (int i = 0; i < sizeof(candidates) / sizeof(candidates[0]); i++) {
- if (ESP_OK == i2c_master_probe(bus, candidates[i].addr, I2C_TIMEOUT)) {
- ESP_LOGI(TAG, "Detected %s at @0x%02X", candidates[i].name,
- candidates[i].addr);
- tas58xx_model = candidates[i].model;
- return candidates[i].addr;
- }
- }
- return 0;
- }
- /* ---------- DAC ops implementation ---------- */
- static void tas58xx_dump_status(const char *context) {
- uint8_t val = 0;
- ESP_LOGD(TAG, "--- %s: TAS5825M status dump ---", context);
- if (tas58xx_read_reg(REG_DEVICE_CTRL2, &val) == ESP_OK) {
- const char *state_str;
- switch (val & CTRL2_STATE_MASK) {
- case CTRL2_DEEP_SLEEP:
- state_str = "DEEP_SLEEP";
- break;
- case CTRL2_SLEEP:
- state_str = "SLEEP";
- break;
- case CTRL2_HIZ:
- state_str = "HIZ";
- break;
- case CTRL2_PLAY:
- state_str = "PLAY";
- break;
- default:
- state_str = "UNKNOWN";
- break;
- }
- ESP_LOGD(TAG, " DEVICE_CTRL2=0x%02X state=%s mute=%s dsp=%s", val,
- state_str, (val & CTRL2_MUTE) ? "YES" : "no",
- (val & CTRL2_DIS_DSP) ? "DISABLED" : "enabled");
- }
- if (tas58xx_read_reg(REG_POWER_STATE, &val) == ESP_OK) {
- const char *ps_str;
- switch (val) {
- case 0x00:
- ps_str = "DEEP_SLEEP";
- break;
- case 0x01:
- ps_str = "SLEEP";
- break;
- case 0x02:
- ps_str = "HIZ";
- break;
- case 0x03:
- ps_str = "PLAY";
- break;
- default:
- ps_str = "UNKNOWN";
- break;
- }
- ESP_LOGD(TAG, " POWER_STATE=0x%02X (%s)", val, ps_str);
- }
- if (tas58xx_read_reg(REG_SAP_CTRL1, &val) == ESP_OK) {
- const char *fmt_str;
- switch ((val >> 4) & 0x03) {
- case 0:
- fmt_str = "I2S";
- break;
- case 1:
- fmt_str = "TDM/DSP";
- break;
- case 2:
- fmt_str = "RJ";
- break;
- case 3:
- fmt_str = "LJ";
- break;
- default:
- fmt_str = "?";
- break;
- }
- int wlen = 16 + ((val >> 0) & 0x03) * 8; // 00=16, 01=20, 10=24, 11=32
- ESP_LOGD(TAG, " SAP_CTRL1=0x%02X format=%s word_len=%d-bit", val,
- fmt_str, wlen);
- }
- if (tas58xx_read_reg(REG_DIG_VOL, &val) == ESP_OK) {
- float db = (float)(0x30 - (int)val) * 0.5f;
- ESP_LOGD(TAG, " DIG_VOL=0x%02X (%.1f dB%s)", val, db,
- val == DIG_VOL_MUTE ? " MUTED" : "");
- }
- if (tas58xx_read_reg(REG_AGAIN, &val) == ESP_OK) {
- float again_db = -(float)(val & 0x1F) * 0.5f;
- ESP_LOGD(TAG, " AGAIN=0x%02X (%.1f dB)", val, again_db);
- }
- if (tas58xx_read_reg(REG_AUTO_MUTE_CTRL, &val) == ESP_OK) {
- ESP_LOGD(TAG, " AUTO_MUTE_CTRL=0x%02X", val);
- }
- uint8_t chan_fault = 0, global1 = 0, global2 = 0, ot_warning = 0;
- tas58xx_read_reg(REG_CHAN_FAULT, &chan_fault);
- tas58xx_read_reg(REG_GLOBAL_FAULT1, &global1);
- tas58xx_read_reg(REG_GLOBAL_FAULT2, &global2);
- tas58xx_read_reg(REG_WARNING, &ot_warning);
- if (chan_fault || global1 || global2 || ot_warning) {
- if (chan_fault) {
- if (chan_fault & BIT(0)) {
- ESP_LOGW(TAG, "Right channel over current fault");
- }
- if (chan_fault & BIT(1)) {
- ESP_LOGW(TAG, "Left channel over current fault");
- }
- if (chan_fault & BIT(2)) {
- ESP_LOGW(TAG, "Right channel DC fault");
- }
- if (chan_fault & BIT(3)) {
- ESP_LOGW(TAG, "Left channel DC fault");
- }
- }
- if (global1) {
- if (global1 & BIT(0)) {
- ESP_LOGW(TAG, "PVDD UV fault");
- }
- if (global1 & BIT(1)) {
- ESP_LOGW(TAG, "PVDD OV fault");
- }
- // This fault is often triggered by lack of I2S clock, which is expected
- // during longer pauses (when mute state is triggeered).
- if (global1 & BIT(2)) {
- ESP_LOGW(TAG, "Clock fault");
- }
- // Bits 3-4 are reserved
- // Bit 5 applies only to tas5825m
- if (global1 & BIT(5)) {
- ESP_LOGW(TAG, "EEPROM boot load error");
- }
- if (global1 & BIT(6)) {
- ESP_LOGW(TAG, "The recent BQ write failed");
- }
- if (global1 & BIT(7)) {
- ESP_LOGW(TAG, "OTP CRC check error");
- }
- }
- if (global2) {
- if (global2 & BIT(0)) {
- ESP_LOGW(TAG, "Over temperature shut down fault");
- }
- // Bits 1-2 only apply to tas5825m
- if (global2 & BIT(1)) {
- ESP_LOGW(TAG, "Left channel cycle by cycle over current fault");
- }
- if (global2 & BIT(2)) {
- ESP_LOGW(TAG, "Right channel cycle by cycle over current fault");
- }
- }
- if (ot_warning) {
- if (ot_warning & BIT(0)) {
- ESP_LOGW(TAG, "Over temperature warning level 1, 112C");
- }
- if (ot_warning & BIT(1)) {
- ESP_LOGW(TAG, "Over temperature warning level 2, 122C");
- }
- if (ot_warning & BIT(2)) {
- ESP_LOGW(TAG, "Over temperature warning level 3, 134C");
- }
- if (ot_warning & BIT(3)) {
- ESP_LOGW(TAG, "Over temperature warning level 4, 146C");
- }
- // Bits 4-5 apply to tas5825m only
- if (ot_warning & BIT(4)) {
- ESP_LOGW(TAG, "Right channel cycle by cycle over current warning");
- }
- if (ot_warning & BIT(5)) {
- ESP_LOGW(TAG, "Left channel cycle by cycle over current warning");
- }
- }
- } else {
- ESP_LOGD(TAG, " FAULTS: none");
- }
- if (tas58xx_read_reg(REG_DSP_PGM_MODE, &val) == ESP_OK) {
- ESP_LOGD(TAG, " DSP_PGM_MODE=0x%02X", val);
- }
- if (tas58xx_read_reg(REG_DSP_CTRL, &val) == ESP_OK) {
- ESP_LOGD(TAG, " DSP_CTRL=0x%02X", val);
- }
- ESP_LOGD(TAG, "--- end status dump ---");
- }
- static esp_err_t tas58xx_init(void *i2c_bus) {
- esp_err_t err;
- ESP_LOGI(TAG, "Initializing TAS58XX");
- /* Create the register-access mutex (once) */
- if (s_reg_mutex == NULL) {
- s_reg_mutex = xSemaphoreCreateMutex();
- if (s_reg_mutex == NULL) {
- ESP_LOGE(TAG, "Failed to create register mutex");
- return ESP_ERR_NO_MEM;
- }
- }
- s_bus_handle = (i2c_master_bus_handle_t)i2c_bus;
- if (s_bus_handle == NULL) {
- ESP_LOGE(TAG, "No I2C bus handle provided");
- return ESP_ERR_INVALID_ARG;
- }
- // Detect device
- tas58xx_model = TAS58XX_MODEL_UNKNOWN;
- tas58xx_addr = tas58xx_detect(s_bus_handle);
- if (!tas58xx_addr) {
- ESP_LOGE(TAG, "No TAS5825M/TAS5805M detected on I2C bus!");
- return ESP_ERR_NOT_FOUND;
- }
- err = board_i2c_add_device(s_bus_handle, tas58xx_addr, I2C_LINE_SPEED,
- &tas58xx_device_handle);
- if (err != ESP_OK) {
- ESP_LOGE(TAG, "Could not add device to I2C bus: %s", esp_err_to_name(err));
- return err;
- }
- // Verify die ID
- uint8_t die_id = 0;
- err = tas58xx_read_reg(REG_DIE_ID, &die_id);
- if (err == ESP_OK) {
- ESP_LOGI(TAG, "Die ID: 0x%02X %s", die_id,
- (die_id == TAS5825M_DIE_ID) ? "(TAS5825M)"
- : (die_id == TAS5805M_DIE_ID) ? "(TAS5805M)"
- : "(UNEXPECTED!)");
- } else {
- ESP_LOGE(TAG, "Failed to read die ID: %s", esp_err_to_name(err));
- }
- if (tas58xx_model == TAS58XX_MODEL_UNKNOWN) {
- ESP_LOGE(TAG, "Unknown TAS58XX model detected — aborting init");
- return ESP_ERR_NOT_FOUND;
- }
- // Run init sequence
- const struct tas58xx_cmd_s *tas58xx_init_seq =
- (tas58xx_model == TAS58XX_MODEL_TAS5825M) ? tas5825m_init_seq
- : tas5805m_init_seq;
- ESP_LOGI(TAG, "Running init sequence...");
- for (int i = 0; tas58xx_init_seq[i].reg != 0xFF; i++) {
- err = tas58xx_write_reg(tas58xx_init_seq[i].reg, tas58xx_init_seq[i].value);
- if (err != ESP_OK) {
- ESP_LOGE(TAG, "Init failed at step %d: reg 0x%02X val 0x%02X: %s", i,
- tas58xx_init_seq[i].reg, tas58xx_init_seq[i].value,
- esp_err_to_name(err));
- return err;
- }
- ESP_LOGD(TAG, " [%02d] reg 0x%02X <- 0x%02X", i, tas58xx_init_seq[i].reg,
- tas58xx_init_seq[i].value);
- // Pause after HiZ transition to let clocks settle
- if (tas58xx_init_seq[i].reg == REG_DEVICE_CTRL2 &&
- (tas58xx_init_seq[i].value & CTRL2_STATE_MASK) == CTRL2_HIZ) {
- ESP_LOGD(TAG, " Waiting 10 ms for HiZ clock settle");
- vTaskDelay(pdMS_TO_TICKS(10));
- }
- // Pause after DSP configuration before going to PLAY
- if (tas58xx_init_seq[i].reg == REG_DSP_CTRL) {
- ESP_LOGD(TAG, " Waiting 5 ms for DSP settle");
- vTaskDelay(pdMS_TO_TICKS(5));
- }
- }
- // Give the device time to reach PLAY state
- vTaskDelay(pdMS_TO_TICKS(10));
- // Dump full status after init
- tas58xx_dump_status("post-init");
- ESP_LOGI(TAG, "%s initialized at I2C addr 0x%02X",
- tas58xx_model == TAS58XX_MODEL_TAS5805M ? "TAS5805M" : "TAS5825M",
- tas58xx_addr);
- return ESP_OK;
- }
- static esp_err_t tas58xx_deinit(void) {
- esp_err_t err = ESP_OK;
- // Put device into deep sleep
- tas58xx_write_reg(REG_DEVICE_CTRL2, CTRL2_DEEP_SLEEP);
- if (tas58xx_device_handle) {
- err = board_i2c_remove_device(tas58xx_device_handle);
- if (err != ESP_OK) {
- ESP_LOGE(TAG, "Failed to remove from I2C bus: %s", esp_err_to_name(err));
- }
- tas58xx_device_handle = NULL;
- }
- s_bus_handle = NULL;
- return err;
- }
- static void tas58xx_set_power_mode(dac_power_mode_t mode) {
- REG_LOCK();
- uint8_t cur_ctrl2 = 0;
- tas58xx_read_reg(REG_DEVICE_CTRL2, &cur_ctrl2);
- uint8_t cur_state = cur_ctrl2 & CTRL2_STATE_MASK;
- if (mode == DAC_POWER_ON) {
- // Always go through HIZ first (per datasheet §9.5.3.1)
- // The PLL needs valid I2S clocks to lock — they must be present
- // by the time this function is called.
- if (cur_state != CTRL2_HIZ) {
- ESP_LOGW(TAG, "Transitioning to HIZ first (from state %d)", cur_state);
- tas58xx_write_reg(REG_DEVICE_CTRL2, CTRL2_HIZ);
- vTaskDelay(pdMS_TO_TICKS(10));
- }
- /*
- * Per TAS5825M datasheet §7.6.2.2, exiting DEEP_SLEEP is similar
- * to a power-on-reset — all registers may revert to defaults.
- * Re-program the critical DSP registers so the correct process
- * flow, I2S format, and coefficient mode are active.
- */
- if (cur_state == CTRL2_DEEP_SLEEP) {
- ESP_LOGI(TAG, "Woke from DEEP_SLEEP — re-programming DSP registers");
- tas58xx_write_reg(REG_SAP_CTRL1, 0x00); /* I2S, 16-bit */
- tas58xx_write_reg(REG_CLOCK_DET_CTRL, 0x00);
- tas58xx_write_reg(REG_DSP_PGM_MODE, 0x01); /* PF1 (Base/Pro, 96kHz) */
- tas58xx_write_reg(REG_DSP_CTRL, 0x01); /* USE_DEFAULT_COEFFS */
- vTaskDelay(pdMS_TO_TICKS(5));
- tas58xx_write_reg(REG_DIG_VOL_CTRL1, 0x33);
- tas58xx_write_reg(REG_AUTO_MUTE_CTRL, 0x07);
- tas58xx_write_reg(REG_AUTO_MUTE_TIME, 0x00);
- tas58xx_write_reg(REG_DIG_VOL, DIG_VOL_0DB);
- tas58xx_write_reg(REG_AGAIN, 0x00);
- /* Coefficient RAM may be invalid after DEEP_SLEEP — force
- * full re-write of signal-path defaults on next EQ update. */
- s_dsp_defaults_written = false;
- }
- // Clear any faults accumulated while clocks were absent
- tas58xx_write_reg(REG_FAULT_CLEAR, 0x80);
- vTaskDelay(pdMS_TO_TICKS(5));
- // Request transition to PLAY (unmuted)
- tas58xx_write_reg(REG_DEVICE_CTRL2, CTRL2_PLAY);
- // Poll POWER_STATE until the device actually reaches PLAY.
- // The TAS5825M won't transition until its PLL locks on SCLK.
- uint8_t ps = 0;
- bool reached_play = false;
- for (int attempt = 0; attempt < 50; attempt++) { // up to ~500 ms
- vTaskDelay(pdMS_TO_TICKS(10));
- if (tas58xx_read_reg(REG_POWER_STATE, &ps) == ESP_OK && ps == 0x03) {
- ESP_LOGI(TAG, "Reached PLAY state after %d ms", (attempt + 1) * 10);
- reached_play = true;
- break;
- }
- }
- if (!reached_play) {
- ESP_LOGE(TAG,
- "FAILED to reach PLAY — POWER_STATE=0x%02X "
- "(is I2S providing BCLK/WS on GPIO %d/%d?)",
- ps, CONFIG_I2S_BCK_IO, CONFIG_I2S_WS_IO);
- }
- // Clear any faults from PLAY transition
- tas58xx_write_reg(REG_FAULT_CLEAR, 0x80);
- tas58xx_dump_status("power-on");
- } else if (mode == DAC_POWER_STANDBY) {
- tas58xx_write_reg(REG_DEVICE_CTRL2, CTRL2_HIZ);
- } else {
- tas58xx_write_reg(REG_DEVICE_CTRL2, CTRL2_DEEP_SLEEP);
- /* DEEP_SLEEP may reset registers and coefficient RAM — ensure
- * full re-initialization happens on the next wake-up. */
- s_dsp_defaults_written = false;
- }
- REG_UNLOCK();
- }
- static void tas58xx_enable_speaker(bool enable) {
- REG_LOCK();
- // Use mute bit in DEVICE_CTRL2 to enable/disable output.
- // Read current register, modify mute bit, write back.
- uint8_t val;
- esp_err_t err = tas58xx_read_reg(REG_DEVICE_CTRL2, &val);
- if (err != ESP_OK) {
- ESP_LOGE(TAG, "Failed to read DEVICE_CTRL2");
- REG_UNLOCK();
- return;
- }
- ESP_LOGI(TAG, "Speaker %s (DEVICE_CTRL2 was 0x%02X)",
- enable ? "ENABLE" : "DISABLE", val);
- if (enable) {
- val &= ~CTRL2_MUTE; // Clear mute bit
- } else {
- val |= CTRL2_MUTE; // Set mute bit
- }
- tas58xx_write_reg(REG_DEVICE_CTRL2, val);
- REG_UNLOCK();
- }
- static void tas58xx_enable_line_out(bool enable) {
- (void)enable;
- ESP_LOGW(TAG, "Line out not supported on TAS58XX");
- }
- static void tas58xx_set_volume(float volume_airplay_db) {
- REG_LOCK();
- // Clamp AirPlay input range (-30 to 0)
- if (volume_airplay_db > 0.0f) {
- volume_airplay_db = 0.0f;
- }
- if (volume_airplay_db < -30.0f) {
- volume_airplay_db = -30.0f;
- }
- // TAS5825M DIG_VOL register:
- // 0x00 = +24.0 dB
- // 0x30 = 0.0 dB
- // 0xFE = -103.0 dB
- // 0xFF = mute
- // Step = -0.5 dB per count
- //
- // Volume mapping (2:1 scaling):
- // AirPlay 0 dB -> DAC CONFIG_TAS58XX_MAX_VOLUME
- // AirPlay -25 dB -> DAC (MAX - 50)
- // AirPlay -30..-25 dB -> steep roll-off to mute
- float max_db = (float)CONFIG_TAS58XX_MAX_VOLUME;
- float db_level;
- if (volume_airplay_db >= -25.0f) {
- // 2:1 linear scaling: 25 dB AirPlay range -> 50 dB DAC range
- db_level = max_db + (volume_airplay_db * 2.0f);
- } else {
- // Roll-off: map -30..-25 to -103..(MAX-50)
- float normalized = (volume_airplay_db + 30.0f) / 5.0f;
- float rolloff_top = max_db - 50.0f;
- db_level = -103.0f + normalized * (103.0f + rolloff_top);
- }
- // Clamp to TAS5825M valid range: +24 dB to -103 dB
- if (db_level > 24.0f) {
- db_level = 24.0f;
- }
- if (db_level < -103.0f) {
- db_level = -103.0f;
- }
- // Convert dB to register value:
- // reg = 0x30 - (db_level * 2) (since 0x30 = 0 dB and step = -0.5 dB)
- uint8_t reg_val;
- if (db_level <= -103.0f) {
- reg_val = DIG_VOL_MUTE;
- } else {
- int raw = DIG_VOL_0DB - (int)(db_level * 2.0f);
- if (raw < 0x00) {
- raw = 0x00;
- }
- if (raw > 0xFE) {
- raw = 0xFE;
- }
- reg_val = (uint8_t)raw;
- }
- ESP_LOGD(TAG, "Volume: AirPlay %.1f dB -> DAC %.1f dB -> reg 0x%02X",
- volume_airplay_db, db_level, reg_val);
- tas58xx_write_reg(REG_DIG_VOL, reg_val);
- REG_UNLOCK();
- }
- /* ---------- Public ops struct ---------- */
- const dac_ops_t dac_tas58xx_ops = {
- .init = tas58xx_init,
- .deinit = tas58xx_deinit,
- .set_volume = tas58xx_set_volume,
- .set_power_mode = tas58xx_set_power_mode,
- .enable_speaker = tas58xx_enable_speaker,
- .enable_line_out = tas58xx_enable_line_out,
- };
- /* ---------- Register read/write helpers ---------- */
- static esp_err_t tas58xx_write_reg(uint8_t reg, uint8_t value) {
- return board_i2c_write(tas58xx_device_handle, reg, &value, sizeof(uint8_t));
- }
- static esp_err_t tas58xx_read_reg(uint8_t reg, uint8_t *value) {
- return board_i2c_read(tas58xx_device_handle, reg, value, sizeof(uint8_t));
- }
- /* ================== 15-Band Parametric EQ ================== */
- #include "dac_tas58xx_eq_data.h"
- #define BQ_COEFF_BOOK 0xAA /* TAS5825M coefficient book */
- #define BQ_COEFF_SIZE 20 /* bytes per biquad (5 × 4) */
- /* Book / Page / Register for EQ mode control */
- #define EQ_MODE_BOOK 0x8C
- #define EQ_MODE_PAGE 0x0B
- #define EQ_MODE_REG 0x28
- #define EQ_MODE_SIZE 8 /* 4 bytes gang_eq + 4 bytes bypass_eq */
- /* 15 center frequencies matching mrtoy-me/esphome-tas58xx reference */
- static const float eq_center_freq[TAS58XX_EQ_BANDS] = {
- 20.0f, 31.5f, 50.0f, 80.0f, 125.0f, 200.0f, 315.0f, 500.0f,
- 800.0f, 1250.0f, 2000.0f, 3150.0f, 5000.0f, 8000.0f, 16000.0f,
- };
- /* 1.0 in 5.27 fixed-point (1 sign + 4 int + 27 frac = 32-bit) */
- #define FP_ONE 0x08000000
- /* ---------- helpers ---------- */
- /** Select a book/page for coefficient access. */
- static inline esp_err_t select_book_page(uint8_t book, uint8_t page) {
- esp_err_t err;
- err = tas58xx_write_reg(REG_PAGE_SEL, 0x00);
- if (err != ESP_OK) {
- return err;
- }
- err = tas58xx_write_reg(REG_BOOK_SEL, book);
- if (err != ESP_OK) {
- return err;
- }
- return tas58xx_write_reg(REG_PAGE_SEL, page);
- }
- /** Return to Book 0, Page 0. */
- static inline esp_err_t select_default_page(void) {
- esp_err_t err;
- err = tas58xx_write_reg(REG_PAGE_SEL, 0x00);
- if (err != ESP_OK) {
- return err;
- }
- err = tas58xx_write_reg(REG_BOOK_SEL, 0x00);
- if (err != ESP_OK) {
- return err;
- }
- return tas58xx_write_reg(REG_PAGE_SEL, 0x00);
- }
- /**
- * Write a single biquad's 5 coefficients (20 bytes, big-endian) to the
- * TAS5825M coefficient RAM.
- * Caller must already have selected the coefficient Book.
- */
- static esp_err_t write_biquad_coeff(uint8_t page, uint8_t reg_start,
- const int32_t coeff[5]) {
- esp_err_t err;
- /* Select coefficient page */
- err = tas58xx_write_reg(REG_PAGE_SEL, page);
- if (err != ESP_OK) {
- return err;
- }
- uint8_t buf[BQ_COEFF_SIZE];
- for (int i = 0; i < 5; i++) {
- buf[i * 4 + 0] = (uint8_t)((coeff[i] >> 24) & 0xFF);
- buf[i * 4 + 1] = (uint8_t)((coeff[i] >> 16) & 0xFF);
- buf[i * 4 + 2] = (uint8_t)((coeff[i] >> 8) & 0xFF);
- buf[i * 4 + 3] = (uint8_t)((coeff[i]) & 0xFF);
- }
- return board_i2c_write(tas58xx_device_handle, reg_start, buf, BQ_COEFF_SIZE);
- }
- /**
- * Write a single biquad's pre-computed 20-byte coefficient block to the
- * TAS5825M coefficient RAM. The caller must already have selected the
- * correct book (0xAA); this function selects the page and writes the data.
- */
- static esp_err_t write_biquad_raw(uint8_t page, uint8_t sub_addr,
- const uint8_t data[EQ_COEFF_BYTES]) {
- esp_err_t err;
- err = tas58xx_write_reg(REG_PAGE_SEL, page);
- if (err != ESP_OK) {
- return err;
- }
- return board_i2c_write(tas58xx_device_handle, sub_addr, data, EQ_COEFF_BYTES);
- }
- static esp_err_t write_dsp_coeff32(uint8_t page, uint8_t reg, int32_t val) {
- esp_err_t err = tas58xx_write_reg(REG_PAGE_SEL, page);
- if (err != ESP_OK) {
- return err;
- }
- uint8_t buf[4] = {(uint8_t)(val >> 24), (uint8_t)(val >> 16),
- (uint8_t)(val >> 8), (uint8_t)(val)};
- return board_i2c_write(tas58xx_device_handle, reg, buf, 4);
- }
- /**
- * Write default coefficient values for all DSP signal-path blocks in
- * Book 0x8C
- */
- static esp_err_t write_dsp_signal_path_defaults(void) {
- esp_err_t err = ESP_OK;
- switch (tas58xx_model) {
- case TAS58XX_MODEL_TAS5805M: {
- ESP_LOGD(TAG, "DSP: writing signal-path defaults (Books 0x8C + 0xAA)");
- /*
- * ── Book 0xAA: ALL biquad coefficient RAM ──
- *
- * We must initialize EVERY BQ slot in Book 0xAA:
- * - 30 EQ BQs (15 L + 15 R) — from tas58xx_eq_left_addr /
- * tas58xx_eq_right_addr
- */
- err = select_book_page(0xAA, 0x00);
- if (err != ESP_OK) {
- select_default_page();
- return err;
- }
- /* Unity BQ: B0=1.0 (5.27), B1=B2=A1=A2=0 */
- static const int32_t unity_bq[5] = {FP_ONE, 0, 0, 0, 0};
- /*
- * ── EQ BQs (30 total, Pages 0x01-0x06) ──
- */
- for (int bq = 0; bq < TAS58XX_EQ_BANDS; bq++) {
- write_biquad_coeff(tas5805m_eq_left_addr[bq].page,
- tas5805m_eq_left_addr[bq].sub_addr, unity_bq);
- write_biquad_coeff(tas5805m_eq_right_addr[bq].page,
- tas5805m_eq_right_addr[bq].sub_addr, unity_bq);
- }
- err = select_default_page();
- s_dsp_defaults_written = true;
- ESP_LOGD(TAG, "DSP: signal-path defaults written (Book 0x8C + 0xAA)");
- } break;
- case TAS58XX_MODEL_TAS5825M: {
- ESP_LOGD(TAG, "DSP: writing signal-path defaults (Books 0x8C + 0xAA)");
- /*
- * ── Book 0x8C: control coefficients ──
- * All values from SLAA786A Table 9 (Process Flow 1).
- */
- err = select_book_page(0x8C, 0x00);
- if (err != ESP_OK) {
- return err;
- }
- /* Volume softening filter alpha (Page 0x01 Reg 0x2C) */
- write_dsp_coeff32(0x01, 0x2C, 0x00E2C46B);
- /*
- * DRC — 3-band Dynamic Range Compression (Pages 0x06–0x07)
- */
- write_dsp_coeff32(0x06, 0x58, 0x00800000); /* DRC1 mixer gain (unity) */
- write_dsp_coeff32(0x06, 0x5C, 0x00800000); /* DRC2 mixer gain (unity) */
- write_dsp_coeff32(0x06, 0x60, 0x00800000); /* DRC3 mixer gain (unity) */
- /* DRC1 time constants */
- write_dsp_coeff32(0x06, 0x64, 0x7FFFFFFF); /* DRC1 Energy */
- write_dsp_coeff32(0x06, 0x68, 0x7FFFFFFF); /* DRC1 Attack */
- write_dsp_coeff32(0x06, 0x6C, 0x7FFFFFFF); /* DRC1 Decay */
- /* DRC1 slopes and thresholds */
- write_dsp_coeff32(0x06, 0x70, 0x00000000); /* K0_1 (no compression) */
- write_dsp_coeff32(0x06, 0x74, 0x00000000); /* K1_1 */
- write_dsp_coeff32(0x06, 0x78, 0x00000000); /* K2_1 */
- write_dsp_coeff32(0x06, 0x7C, (int32_t)0xE7000000); /* T1_1 threshold */
- write_dsp_coeff32(0x07, 0x08, (int32_t)0xFE800000); /* T2_1 threshold */
- write_dsp_coeff32(0x07, 0x0C, 0x00000000); /* off1_1 */
- write_dsp_coeff32(0x07, 0x10, 0x00000000); /* off2_1 */
- /* DRC2 time constants */
- write_dsp_coeff32(0x07, 0x14, 0x7FFFFFFF); /* DRC2 Energy */
- write_dsp_coeff32(0x07, 0x18, 0x7FFFFFFF); /* DRC2 Attack */
- write_dsp_coeff32(0x07, 0x1C, 0x7FFFFFFF); /* DRC2 Decay */
- /* DRC2 slopes and thresholds */
- write_dsp_coeff32(0x07, 0x20, 0x00000000); /* k0_2 */
- write_dsp_coeff32(0x07, 0x24, 0x00000000); /* k1_2 */
- write_dsp_coeff32(0x07, 0x28, 0x00000000); /* k2_2 */
- write_dsp_coeff32(0x07, 0x2C, (int32_t)0xE7000000); /* t1_2 */
- write_dsp_coeff32(0x07, 0x30, (int32_t)0xFE800000); /* t2_2 */
- write_dsp_coeff32(0x07, 0x34, 0x00000000); /* off1_2 */
- write_dsp_coeff32(0x07, 0x38, 0x00000000); /* off2_2 */
- /* DRC3 time constants */
- write_dsp_coeff32(0x07, 0x3C, 0x7FFFFFFF); /* DRC3 Energy */
- write_dsp_coeff32(0x07, 0x40, 0x7FFFFFFF); /* DRC3 Attack */
- write_dsp_coeff32(0x07, 0x44, 0x7FFFFFFF); /* DRC3 Decay */
- /* DRC3 slopes and thresholds */
- write_dsp_coeff32(0x07, 0x48, 0x00000000); /* k0_3 */
- write_dsp_coeff32(0x07, 0x4C, 0x00000000); /* k1_3 */
- write_dsp_coeff32(0x07, 0x50, 0x00000000); /* k2_3 */
- write_dsp_coeff32(0x07, 0x54, (int32_t)0xE7000000); /* t1_3 */
- write_dsp_coeff32(0x07, 0x58, (int32_t)0xFE800000); /* t2_3 */
- write_dsp_coeff32(0x07, 0x5C, 0x00000000); /* off1_3 */
- write_dsp_coeff32(0x07, 0x60, 0x00000000); /* off2_3 */
- /* FS Clipper (Page 0x07) */
- write_dsp_coeff32(0x07, 0x64, 0x00800000); /* THD Boost (unity) */
- write_dsp_coeff32(0x07, 0x6C, 0x3FFFFFFF); /* CH-L Fine Volume */
- write_dsp_coeff32(0x07, 0x70, 0x3FFFFFFF); /* CH-R Fine Volume */
- /* DPEQ Control (Page 0x09) */
- write_dsp_coeff32(0x09, 0x28, 0x02DEAD00); /* DPEQ sense energy alpha */
- write_dsp_coeff32(0x09, 0x2C, 0x74013901); /* DPEQ threshold gain */
- write_dsp_coeff32(0x09, 0x30, 0x0020C49B); /* DPEQ threshold offset */
- /* Spatializer (Page 0x0A) */
- write_dsp_coeff32(0x0A, 0x38, 0x00000000); /* Spatializer level (off) */
- /* Output Crossbar (Page 0x0A) — default: straight stereo */
- write_dsp_coeff32(0x0A, 0x64, 0x00800000); /* Dig L ← L (unity) */
- write_dsp_coeff32(0x0A, 0x68, 0x00000000); /* Dig L ← R (zero) */
- write_dsp_coeff32(0x0A, 0x6C, 0x00000000); /* Dig R ← L (zero) */
- write_dsp_coeff32(0x0A, 0x70, 0x00800000); /* Dig R ← R (unity) */
- write_dsp_coeff32(0x0A, 0x74, 0x00800000); /* Ana L ← L (unity) */
- write_dsp_coeff32(0x0A, 0x78, 0x00000000); /* Ana L ← R (zero) */
- write_dsp_coeff32(0x0A, 0x7C, 0x00000000); /* Ana R ← L (zero) */
- write_dsp_coeff32(0x0B, 0x08, 0x00800000); /* Ana R ← R (unity) */
- /* Volume Control (Page 0x0B) */
- write_dsp_coeff32(0x0B, 0x0C, 0x00800000); /* CH-L Volume (unity) */
- write_dsp_coeff32(0x0B, 0x10, 0x00800000); /* CH-R Volume (unity) */
- /* Input Mixer (Page 0x0B) */
- write_dsp_coeff32(0x0B, 0x14, 0x00800000); /* L → L (unity) */
- write_dsp_coeff32(0x0B, 0x18, 0x00000000); /* R → L (zero) */
- write_dsp_coeff32(0x0B, 0x1C, 0x00000000); /* L → R (zero) */
- write_dsp_coeff32(0x0B, 0x20, 0x00800000); /* R → R (unity) */
- /* Bypass DC Block (Page 0x0B) */
- write_dsp_coeff32(0x0B, 0x24, 0x00000000);
- /* EQ Control (Page 0x0B) */
- write_dsp_coeff32(0x0B, 0x28, 0x00000000); /* GangEQ = 0 */
- write_dsp_coeff32(0x0B, 0x2C, 0x00000000); /* BypassEQ = 0 */
- /* Level Meter (Page 0x0B) */
- write_dsp_coeff32(0x0B, 0x30, 0x00A7264A); /* Softening filter alpha */
- write_dsp_coeff32(0x0B, 0x34, 0x00000000); /* Level meter input mux */
- /* Bank Switch (Page 0x0C) */
- write_dsp_coeff32(0x0C, 0x20, 0x00000000);
- /*
- * ── Book 0xAA: ALL biquad coefficient RAM ──
- *
- * We must initialize EVERY BQ slot in Book 0xAA:
- * - 30 EQ BQs (15 L + 15 R) — from tas58xx_eq_left_addr /
- * tas58xx_eq_right_addr
- * - 8 DRC crossover BQs — linear layout from Page 0x07:0x78
- * - 3 DPEQ BQs — Pages 0x09-0x0A
- * - 2 Spatializer BQs — Page 0x0A
- */
- err = select_book_page(0xAA, 0x00);
- if (err != ESP_OK) {
- select_default_page();
- return err;
- }
- /* Unity BQ: B0=1.0 (5.27), B1=B2=A1=A2=0 */
- static const int32_t unity_bq[5] = {FP_ONE, 0, 0, 0, 0};
- /*
- * ── DRC crossover BQs (8 total, Pages 0x07-0x09) ──
- * Linear from Page 0x07 Reg 0x78.
- */
- /* DRC low BQ1: 0x07:0x78 → crosses to 0x08 (use individual writes) */
- write_dsp_coeff32(0x07, 0x78, FP_ONE);
- write_dsp_coeff32(0x07, 0x7C, 0x00000000);
- write_dsp_coeff32(0x08, 0x08, 0x00000000);
- write_dsp_coeff32(0x08, 0x0C, 0x00000000);
- write_dsp_coeff32(0x08, 0x10, 0x00000000);
- /* DRC low BQ2: 0x08:0x14 (fits on page) */
- write_biquad_coeff(0x08, 0x14, unity_bq);
- /* DRC high BQ1: 0x08:0x28 (fits on page) */
- write_biquad_coeff(0x08, 0x28, unity_bq);
- /* DRC high BQ2: 0x08:0x3C (fits on page) */
- write_biquad_coeff(0x08, 0x3C, unity_bq);
- /* DRC mid BQ1: 0x08:0x50 (fits on page) */
- write_biquad_coeff(0x08, 0x50, unity_bq);
- /* DRC mid BQ2: 0x08:0x64 (fits: 0x64+19=0x77) */
- write_biquad_coeff(0x08, 0x64, unity_bq);
- /* DRC mid BQ3: 0x08:0x78 → crosses to 0x09 (use individual writes) */
- write_dsp_coeff32(0x08, 0x78, FP_ONE);
- write_dsp_coeff32(0x08, 0x7C, 0x00000000);
- write_dsp_coeff32(0x09, 0x08, 0x00000000);
- write_dsp_coeff32(0x09, 0x0C, 0x00000000);
- write_dsp_coeff32(0x09, 0x10, 0x00000000);
- /* DRC mid BQ4: 0x09:0x14 (fits on page) */
- write_biquad_coeff(0x09, 0x14, unity_bq);
- /*
- * ── DPEQ BQs (3 total, Pages 0x09-0x0A) ──
- */
- write_biquad_coeff(0x09, 0x34, unity_bq); /* DPEQ sense BQ */
- write_biquad_coeff(0x09, 0x5C, unity_bq); /* DPEQ low-level path BQ */
- write_biquad_coeff(0x0A, 0x0C, unity_bq); /* DPEQ high-level path BQ */
- /*
- * ── Spatializer BQs (2 total, Page 0x0A) ──
- */
- write_biquad_coeff(0x0A, 0x3C, unity_bq); /* Spatializer BQ1 */
- write_biquad_coeff(0x0A, 0x50, unity_bq); /* Spatializer BQ2 */
- /*
- * ── EQ BQs (30 total, Pages 0x01-0x06) ──
- */
- for (int bq = 0; bq < TAS58XX_EQ_BANDS; bq++) {
- write_biquad_coeff(tas5825m_eq_left_addr[bq].page,
- tas5825m_eq_left_addr[bq].sub_addr, unity_bq);
- write_biquad_coeff(tas5825m_eq_right_addr[bq].page,
- tas5825m_eq_right_addr[bq].sub_addr, unity_bq);
- }
- err = select_default_page();
- s_dsp_defaults_written = true;
- ESP_LOGD(TAG, "DSP: signal-path defaults written (Book 0x8C + 0xAA)");
- } break;
- default:
- ESP_LOGE(TAG, "Unknown TAS58XX model %d in write_dsp_signal_path_defaults",
- tas58xx_model);
- return ESP_ERR_INVALID_STATE;
- }
- return err;
- }
- static esp_err_t ensure_custom_coeffs_mode(void) {
- // Only applicable to TAS5825M
- uint8_t dsp_ctrl;
- esp_err_t err = tas58xx_read_reg(REG_DSP_CTRL, &dsp_ctrl);
- if (err != ESP_OK) {
- return err;
- }
- if (dsp_ctrl & 0x01) {
- /* Mute while we reconfigure the entire coefficient RAM */
- uint8_t saved_ctrl2 = 0;
- tas58xx_read_reg(REG_DEVICE_CTRL2, &saved_ctrl2);
- bool was_unmuted = !(saved_ctrl2 & CTRL2_MUTE);
- if (was_unmuted) {
- tas58xx_write_reg(REG_DEVICE_CTRL2, saved_ctrl2 | CTRL2_MUTE);
- vTaskDelay(pdMS_TO_TICKS(5)); /* let mute take effect */
- }
- /* Write all signal-path coefficients first */
- if (!s_dsp_defaults_written) {
- err = write_dsp_signal_path_defaults();
- if (err != ESP_OK) {
- if (was_unmuted) {
- tas58xx_write_reg(REG_DEVICE_CTRL2, saved_ctrl2);
- }
- return err;
- }
- }
- /* Now safe to clear USE_DEFAULT_COEFFS */
- ESP_LOGD(TAG, "DSP: clearing USE_DEFAULT_COEFFS");
- err = tas58xx_write_reg(REG_DSP_CTRL, dsp_ctrl & ~0x01);
- /* Verify the bit was actually cleared */
- {
- uint8_t verify = 0xFF;
- tas58xx_read_reg(REG_DSP_CTRL, &verify);
- uint8_t pgm = 0xFF;
- tas58xx_read_reg(REG_DSP_PGM_MODE, &pgm);
- ESP_LOGD(TAG,
- "DSP: post-clear DSP_CTRL=0x%02X (expect 0x00) "
- "DSP_PGM_MODE=0x%02X (expect 0x01)",
- verify, pgm);
- if (verify & 0x01) {
- ESP_LOGE(TAG, "DSP: USE_DEFAULT_COEFFS still set!");
- }
- if (pgm != 0x01) {
- ESP_LOGW(TAG,
- "DSP: unexpected DSP_PGM_MODE — process flow may be wrong! "
- "EQ addresses assume PF1 (0x01)");
- }
- }
- /* Unmute */
- if (was_unmuted) {
- vTaskDelay(pdMS_TO_TICKS(5));
- tas58xx_write_reg(REG_DEVICE_CTRL2, saved_ctrl2);
- }
- }
- return err;
- }
- /**
- * Program one biquad on both channels using pre-computed 20-byte coefficient
- * blocks from dac_tas58xx_eq_data.h.
- *
- * Enters Book 0xAA, writes CH-L then CH-R, returns to Book 0 / Page 0.
- * Assumes the caller holds REG_LOCK.
- */
- static esp_err_t program_biquad_raw(int bq,
- const uint8_t data[EQ_COEFF_BYTES]) {
- esp_err_t err;
- if (tas58xx_model == TAS58XX_MODEL_TAS5825M) {
- /* Ensure DSP has all signal-path defaults before using custom coefficients
- */
- err = ensure_custom_coeffs_mode();
- if (err != ESP_OK) {
- return err;
- }
- }
- /* Enter coefficient book */
- err = select_book_page(BQ_COEFF_BOOK, 0x00);
- if (err != ESP_OK) {
- goto out;
- }
- const eq_bq_addr_t *eq_left_addr = (tas58xx_model == TAS58XX_MODEL_TAS5805M)
- ? tas5805m_eq_left_addr
- : tas5825m_eq_left_addr;
- const eq_bq_addr_t *eq_right_addr = (tas58xx_model == TAS58XX_MODEL_TAS5805M)
- ? tas5805m_eq_right_addr
- : tas5825m_eq_right_addr;
- /* Channel 1 (Left) */
- err =
- write_biquad_raw(eq_left_addr[bq].page, eq_left_addr[bq].sub_addr, data);
- if (err != ESP_OK) {
- ESP_LOGE(TAG, "EQ: CH1 BQ%d raw write failed: %s", bq,
- esp_err_to_name(err));
- goto out;
- }
- /* Channel 2 (Right) */
- err = write_biquad_raw(eq_right_addr[bq].page, eq_right_addr[bq].sub_addr,
- data);
- if (err != ESP_OK) {
- ESP_LOGE(TAG, "EQ: CH2 BQ%d raw write failed: %s", bq,
- esp_err_to_name(err));
- goto out;
- }
- ESP_LOGD(TAG,
- "EQ: BQ%d raw write OK (L page=0x%02X:0x%02X, R page=0x%02X:0x%02X)",
- bq, eq_left_addr[bq].page, eq_left_addr[bq].sub_addr,
- eq_right_addr[bq].page, eq_right_addr[bq].sub_addr);
- out:
- select_default_page();
- return err;
- }
- static esp_err_t write_eq_mode(bool enable) {
- esp_err_t err;
- switch (tas58xx_model) {
- case TAS58XX_MODEL_TAS5805M: {
- select_default_page();
- uint8_t value = enable ? 0x08 : 0x09; /* bit0 = BYPASS_EQ */
- err = tas58xx_write_reg(REG_DSP_MISC, value);
- if (err != ESP_OK) {
- ESP_LOGE(TAG, "EQ: mode write failed: %s", esp_err_to_name(err));
- } else {
- ESP_LOGD(TAG, "EQ: %s", enable ? "ENABLED" : "BYPASSED");
- }
- } break;
- case TAS58XX_MODEL_TAS5825M: {
- err = select_book_page(EQ_MODE_BOOK, EQ_MODE_PAGE);
- if (err != ESP_OK) {
- return err;
- }
- uint8_t mode_data[EQ_MODE_SIZE] = {
- 0x00, 0x80, 0x00, 0x00, /* gang_eq = 0x00800000 */
- 0x00, 0x00, 0x00, enable ? 0x00 : 0x01, /* bypass_eq */
- };
- err = board_i2c_write(tas58xx_device_handle, EQ_MODE_REG, mode_data,
- EQ_MODE_SIZE);
- if (err != ESP_OK) {
- ESP_LOGE(TAG, "EQ: mode write failed: %s", esp_err_to_name(err));
- } else {
- ESP_LOGD(TAG, "EQ: %s", enable ? "ENABLED" : "BYPASSED");
- }
- select_default_page();
- } break;
- default:
- ESP_LOGE(TAG, "Unknown TAS58XX model %d in write_eq_mode", tas58xx_model);
- return ESP_ERR_INVALID_STATE;
- }
- return err;
- }
- /* ---------- Public API ---------- */
- esp_err_t tas58xx_eq_enable(bool enable) {
- REG_LOCK();
- esp_err_t err;
- if (tas58xx_model == TAS58XX_MODEL_TAS5825M) {
- /* Ensure DSP defaults are written before touching EQ mode */
- err = ensure_custom_coeffs_mode();
- if (err != ESP_OK) {
- REG_UNLOCK();
- return err;
- }
- }
- err = write_eq_mode(enable);
- REG_UNLOCK();
- return err;
- }
- esp_err_t tas58xx_eq_set_band(int band, float gain_db) {
- if (band < 0 || band >= TAS58XX_EQ_BANDS) {
- ESP_LOGE(TAG, "EQ: invalid band %d", band);
- return ESP_ERR_INVALID_ARG;
- }
- /* Clamp gain to integer dB range of pre-computed table */
- int gain_int = (int)roundf(gain_db);
- if (gain_int > (int)TAS58XX_EQ_MAX_GAIN_DB) {
- gain_int = (int)TAS58XX_EQ_MAX_GAIN_DB;
- }
- if (gain_int < (int)TAS58XX_EQ_MIN_GAIN_DB) {
- gain_int = (int)TAS58XX_EQ_MIN_GAIN_DB;
- }
- int idx = gain_int + EQ_GAIN_OFFSET;
- ESP_LOGD(TAG, "EQ: band %d (%.0f Hz) -> %+d dB (table idx %d)", band,
- eq_center_freq[band], gain_int, idx);
- REG_LOCK();
- esp_err_t err = program_biquad_raw(band, eq_coeff_table[idx][band].bytes);
- REG_UNLOCK();
- return err;
- }
- esp_err_t tas58xx_eq_set_all(const float gains_db[TAS58XX_EQ_BANDS]) {
- if (!gains_db) {
- return ESP_ERR_INVALID_ARG;
- }
- REG_LOCK();
- /* Mute to prevent DSP glitches while bulk-updating coefficients */
- uint8_t saved_ctrl2 = 0;
- tas58xx_read_reg(REG_DEVICE_CTRL2, &saved_ctrl2);
- if (!(saved_ctrl2 & CTRL2_MUTE)) {
- tas58xx_write_reg(REG_DEVICE_CTRL2, saved_ctrl2 | CTRL2_MUTE);
- }
- esp_err_t first_err = ESP_OK;
- for (int i = 0; i < TAS58XX_EQ_BANDS; i++) {
- int gain_int = (int)roundf(gains_db[i]);
- if (gain_int > (int)TAS58XX_EQ_MAX_GAIN_DB) {
- gain_int = (int)TAS58XX_EQ_MAX_GAIN_DB;
- }
- if (gain_int < (int)TAS58XX_EQ_MIN_GAIN_DB) {
- gain_int = (int)TAS58XX_EQ_MIN_GAIN_DB;
- }
- int idx = gain_int + EQ_GAIN_OFFSET;
- esp_err_t err = program_biquad_raw(i, eq_coeff_table[idx][i].bytes);
- if (err != ESP_OK && first_err == ESP_OK) {
- first_err = err;
- }
- }
- /* Restore original mute state */
- tas58xx_write_reg(REG_DEVICE_CTRL2, saved_ctrl2);
- REG_UNLOCK();
- return first_err;
- }
- esp_err_t tas58xx_eq_flat(void) {
- ESP_LOGD(TAG, "EQ: resetting all bands to flat");
- /* Index for 0 dB gain = unity passthrough */
- const int flat_idx = EQ_GAIN_OFFSET;
- REG_LOCK();
- /* Mute during bulk update */
- uint8_t saved_ctrl2 = 0;
- tas58xx_read_reg(REG_DEVICE_CTRL2, &saved_ctrl2);
- if (!(saved_ctrl2 & CTRL2_MUTE)) {
- tas58xx_write_reg(REG_DEVICE_CTRL2, saved_ctrl2 | CTRL2_MUTE);
- }
- esp_err_t first_err = ESP_OK;
- for (int i = 0; i < TAS58XX_EQ_BANDS; i++) {
- esp_err_t err = program_biquad_raw(i, eq_coeff_table[flat_idx][i].bytes);
- if (err != ESP_OK && first_err == ESP_OK) {
- first_err = err;
- }
- }
- /* Enable EQ after programming flat coefficients */
- if (first_err == ESP_OK) {
- first_err = write_eq_mode(true);
- }
- /* Restore original mute state */
- tas58xx_write_reg(REG_DEVICE_CTRL2, saved_ctrl2);
- REG_UNLOCK();
- return first_err;
- }
- float tas58xx_eq_get_center_freq(int band) {
- if (band < 0 || band >= TAS58XX_EQ_BANDS) {
- return 0.0f;
- }
- return eq_center_freq[band];
- }
|