dac_tas58xx.c 45 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442
  1. /**
  2. * Implementation of control interface to TI TAS58xx (TAS5825M) DAC/Amp
  3. * TAS5825M datasheet:
  4. * https://www.ti.com/lit/ds/symlink/tas5825m.pdf
  5. */
  6. #include "dac_tas58xx.h"
  7. #include "dac_tas58xx_eq.h"
  8. #include "board_utils.h"
  9. #include <math.h>
  10. #include <string.h>
  11. #include <sys/param.h>
  12. #include "driver/i2c_master.h"
  13. #include "esp_log.h"
  14. #include "freertos/FreeRTOS.h"
  15. #include "freertos/semphr.h"
  16. #include "freertos/task.h"
  17. /* ---------- TAS5825M I2C addresses (7-bit) ---------- */
  18. #define TAS5825M_ADDR_GND 0x4C // ADR pin = 0 Ω to GND
  19. #define TAS5825M_ADDR_1K 0x4D // ADR pin = 1 kΩ to GND
  20. #define TAS5825M_ADDR_4K7 0x4E // ADR pin = 4.7 kΩ to GND
  21. #define TAS5825M_ADDR_15K 0x4F // ADR pin = 15 kΩ to GND
  22. /* ---------- TAS5805M I2C addresses (7-bit) ---------- */
  23. #define TAS5805M_ADDR_4K7 0x2C // ADR pin = 4.7 kΩ to DVDD
  24. #define TAS5805M_ADDR_15K 0x2D // ADR pin = 15 kΩ to DVDD
  25. #define TAS5805M_ADDR_47K 0x2E // ADR pin = 47 kΩ to DVDD
  26. #define TAS5805M_ADDR_120K 0x2F // ADR pin = 120 kΩ to DVDD
  27. /* ---------- Register addresses (Book 0, Page 0) ---------- */
  28. #define REG_PAGE_SEL 0x00
  29. #define REG_BOOK_SEL 0x7F
  30. #define REG_RESET_CTRL 0x01
  31. #define REG_DEVICE_CTRL1 0x02
  32. #define REG_DEVICE_CTRL2 0x03
  33. #define REG_SIG_CH_CTRL 0x28
  34. #define REG_CLOCK_DET_CTRL 0x29
  35. #define REG_SDOUT_SEL 0x30
  36. #define REG_SAP_CTRL1 0x33 // I2S format + word length
  37. #define REG_SAP_CTRL2 0x34 // Data offset
  38. #define REG_SAP_CTRL3 0x35 // L/R channel routing
  39. #define REG_DSP_PGM_MODE 0x40
  40. #define REG_DSP_CTRL 0x46
  41. #define REG_DIG_VOL 0x4C // Digital volume (both channels)
  42. #define REG_DIG_VOL_CTRL1 0x4E // Volume ramp control
  43. #define REG_AUTO_MUTE_CTRL 0x50
  44. #define REG_AUTO_MUTE_TIME 0x51
  45. #define REG_ANA_CTRL 0x53
  46. #define REG_AGAIN 0x54 // Analog gain
  47. #define REG_GPIO_CTL 0x60
  48. #define REG_GPIO0 0x61
  49. #define REG_GPIO1 0x62
  50. #define REG_GPIO2 0x63
  51. #define REG_DSP_MISC 0x66
  52. #define REG_GPIO_OFF 0x00
  53. #define REG_GPIO_WARN 0b1000
  54. #define REG_GPIO_FAULT 0b1011
  55. #define REG_GPIO_SDOUT 0b1001
  56. #define REG_GPIO_CTL_OUT 0b0111
  57. #define REG_DIE_ID 0x67 // Expected: 0x95
  58. #define REG_POWER_STATE 0x68
  59. #define REG_CHAN_FAULT 0x70
  60. #define REG_GLOBAL_FAULT1 0x71
  61. #define REG_GLOBAL_FAULT2 0x72
  62. #define REG_WARNING 0x73
  63. #define REG_FAULT_CLEAR 0x78
  64. /* ---------- DEVICE_CTRL2 (0x03) bit fields ---------- */
  65. #define CTRL2_MUTE (1 << 3)
  66. #define CTRL2_DIS_DSP (1 << 4)
  67. #define CTRL2_STATE_MASK 0x03
  68. #define CTRL2_DEEP_SLEEP 0x00
  69. #define CTRL2_SLEEP 0x01
  70. #define CTRL2_HIZ 0x02
  71. #define CTRL2_PLAY 0x03
  72. /* ---------- DIG_VOL (0x4C) ---------- */
  73. // 0x00 = +24.0 dB, 0x30 = 0.0 dB, 0xFE = -103.0 dB, 0xFF = mute
  74. // step = -0.5 dB per increment
  75. #define DIG_VOL_0DB 0x30
  76. #define DIG_VOL_MUTE 0xFF
  77. /* ---------- AGAIN (0x54) ---------- */
  78. // bits[4:0]: 0x00 = 0 dB, each step = -0.5 dB, max 0x1F = -15.5 dB
  79. /* ---------- RESET_CTRL (0x01) ---------- */
  80. #define RESET_DIG_CORE (1 << 4)
  81. #define RESET_REG (1 << 0)
  82. /* ---------- Constants ---------- */
  83. #define I2C_TIMEOUT 100 // ms
  84. #define I2C_LINE_SPEED 400000 // TAS5825M supports fast-mode 400 kHz
  85. #define TAS5805M_DIE_ID 0x0
  86. #define TAS5825M_DIE_ID 0x95
  87. static const char TAG[] = "TAS58xx DAC";
  88. typedef enum {
  89. TAS58XX_MODEL_UNKNOWN = 0,
  90. TAS58XX_MODEL_TAS5805M = 1,
  91. TAS58XX_MODEL_TAS5825M = 2,
  92. } tas58xx_model_t;
  93. /* ---------- Init sequence ---------- */
  94. struct tas58xx_cmd_s {
  95. uint8_t reg;
  96. uint8_t value;
  97. };
  98. /*
  99. * Startup procedure from datasheet §9.5.3.1:
  100. * 1. Go to Book 0 / Page 0
  101. * 2. Reset device registers
  102. * 3. Configure device into HiZ with DSP enabled
  103. * 4. Wait ≥5 ms for clocks to settle
  104. * 5. Configure I2S format + word length
  105. * 6. Set DSP to ROM mode (simple passthrough, no custom coefficients)
  106. * 7. Set default analog gain
  107. * 8. Set volume ramp rates
  108. * 9. Configure auto-mute
  109. * 10. Clear faults
  110. *
  111. * NOTE: We do NOT transition to Play here — I2S clocks are not yet
  112. * running when dac_init() is called, so the PLL cannot lock and the
  113. * device will stay stuck in HiZ. The transition to Play happens
  114. * later via dac_set_power_mode(DAC_POWER_ON) once I2S is active.
  115. */
  116. static const struct tas58xx_cmd_s tas5825m_init_seq[] = {
  117. {REG_PAGE_SEL, 0x00}, // Select Book 0 Page 0
  118. {REG_BOOK_SEL, 0x00}, // Select Book 0
  119. {REG_PAGE_SEL, 0x00}, // Confirm Page 0
  120. {REG_RESET_CTRL, RESET_REG}, // Reset control port registers
  121. {REG_DEVICE_CTRL2, CTRL2_HIZ},
  122. // I2S format: standard I2S, 16-bit word length
  123. {REG_SAP_CTRL1, 0x00}, // DATA_FORMAT=I2S(00), WORD_LENGTH=16bit(00)
  124. {REG_CLOCK_DET_CTRL, 0x00},
  125. // DSP: Process Flow 1 (Base/Pro, 96kHz, 2.0)
  126. {REG_DSP_PGM_MODE, 0x01},
  127. {REG_DSP_CTRL, 0x01}, // Use default coefficients
  128. // Volume ramp: smooth transitions
  129. {REG_DIG_VOL_CTRL1, 0x33}, // Default ramp rates
  130. // Auto-mute: enable for both channels
  131. {REG_AUTO_MUTE_CTRL, 0x07},
  132. {REG_AUTO_MUTE_TIME, 0x00},
  133. // Clear any pending faults
  134. {REG_FAULT_CLEAR, 0x80},
  135. // Set SDOUT source to Pre-DSP
  136. {REG_SDOUT_SEL, 0x01},
  137. // GPIO config - WARN/FLT LEDs and SDOUT pin
  138. {REG_GPIO0, REG_GPIO_WARN},
  139. {REG_GPIO1, REG_GPIO_FAULT},
  140. {REG_GPIO2, REG_GPIO_SDOUT},
  141. {REG_GPIO_CTL, REG_GPIO_CTL_OUT},
  142. // Set digital volume to 0 dB initially
  143. {REG_DIG_VOL, DIG_VOL_0DB},
  144. // Analog gain: 0 dB
  145. {REG_AGAIN, 0x00},
  146. {0xFF, 0xFF} // End of table sentinel
  147. };
  148. /* TAS5805M is slightly simpler configuration, namely
  149. - lack of GPIO configuration
  150. - no process flow select register
  151. - DSP_MISC register to configure BQ coefficients per channel */
  152. static const struct tas58xx_cmd_s tas5805m_init_seq[] = {
  153. {REG_PAGE_SEL, 0x00}, // Select Book 0 Page 0
  154. {REG_BOOK_SEL, 0x00}, // Select Book 0
  155. {REG_PAGE_SEL, 0x00}, // Confirm Page 0
  156. {REG_RESET_CTRL, RESET_REG}, // Reset control port registers
  157. {REG_DEVICE_CTRL2, CTRL2_HIZ},
  158. // I2S format: standard I2S, 16-bit word length
  159. {REG_SAP_CTRL1, 0x00}, // DATA_FORMAT=I2S(00), WORD_LENGTH=16bit(00)
  160. {REG_CLOCK_DET_CTRL, 0x00},
  161. // Volume ramp: smooth transitions
  162. {REG_DIG_VOL_CTRL1, 0x33}, // Default ramp rates
  163. // Auto-mute: enable for both channels
  164. {REG_AUTO_MUTE_CTRL, 0x03},
  165. {REG_AUTO_MUTE_TIME, 0x00},
  166. // Clear any pending faults
  167. {REG_FAULT_CLEAR, 0x80},
  168. // Set SDOUT source to Pre-DSP
  169. {REG_SDOUT_SEL, 0x01},
  170. // Set BQ coefficients to be unique per channel
  171. {REG_DSP_MISC, 0x08},
  172. // Set digital volume to 0 dB initially
  173. {REG_DIG_VOL, DIG_VOL_0DB},
  174. // Analog gain: 0 dB
  175. {REG_AGAIN, 0x00},
  176. {0xFF, 0xFF} // End of table sentinel
  177. };
  178. /* ---------- State ---------- */
  179. static uint8_t tas58xx_addr;
  180. static tas58xx_model_t tas58xx_model = TAS58XX_MODEL_UNKNOWN;
  181. static i2c_master_bus_handle_t s_bus_handle = NULL;
  182. static i2c_master_dev_handle_t tas58xx_device_handle;
  183. static bool s_dsp_defaults_written = false;
  184. /**
  185. * Mutex protecting all TAS5825M register access.
  186. *
  187. * The TAS5825M uses a page/book register model: writing to any register
  188. * beyond Page 0 requires first selecting the target book and page via
  189. * REG_PAGE_SEL (0x00) and REG_BOOK_SEL (0x7F). This makes register
  190. * access non-atomic: a context switch between selecting a page and
  191. * writing the target register will corrupt the operation.
  192. *
  193. * All functions that touch the I2C bus MUST hold this mutex. Public API
  194. * functions acquire it; internal helpers assume it's already held.
  195. */
  196. static SemaphoreHandle_t s_reg_mutex = NULL;
  197. #define REG_LOCK() xSemaphoreTake(s_reg_mutex, portMAX_DELAY)
  198. #define REG_UNLOCK() xSemaphoreGive(s_reg_mutex)
  199. /* ---------- Forward declarations ---------- */
  200. static esp_err_t tas58xx_write_reg(uint8_t reg, uint8_t value);
  201. static esp_err_t tas58xx_read_reg(uint8_t reg, uint8_t *value);
  202. /* ---------- Detect ---------- */
  203. static uint8_t tas58xx_detect(i2c_master_bus_handle_t bus) {
  204. static const struct {
  205. uint8_t addr;
  206. tas58xx_model_t model;
  207. const char *name;
  208. } candidates[] = {
  209. {TAS5825M_ADDR_GND, TAS58XX_MODEL_TAS5825M, "TAS5825M"},
  210. {TAS5825M_ADDR_1K, TAS58XX_MODEL_TAS5825M, "TAS5825M"},
  211. {TAS5825M_ADDR_4K7, TAS58XX_MODEL_TAS5825M, "TAS5825M"},
  212. {TAS5825M_ADDR_15K, TAS58XX_MODEL_TAS5825M, "TAS5825M"},
  213. {TAS5805M_ADDR_4K7, TAS58XX_MODEL_TAS5805M, "TAS5805M"},
  214. {TAS5805M_ADDR_15K, TAS58XX_MODEL_TAS5805M, "TAS5805M"},
  215. {TAS5805M_ADDR_47K, TAS58XX_MODEL_TAS5805M, "TAS5805M"},
  216. {TAS5805M_ADDR_120K, TAS58XX_MODEL_TAS5805M, "TAS5805M"},
  217. };
  218. if (!bus) {
  219. ESP_LOGE(TAG, "Invalid I2C handle");
  220. return 0;
  221. }
  222. for (int i = 0; i < sizeof(candidates) / sizeof(candidates[0]); i++) {
  223. if (ESP_OK == i2c_master_probe(bus, candidates[i].addr, I2C_TIMEOUT)) {
  224. ESP_LOGI(TAG, "Detected %s at @0x%02X", candidates[i].name,
  225. candidates[i].addr);
  226. tas58xx_model = candidates[i].model;
  227. return candidates[i].addr;
  228. }
  229. }
  230. return 0;
  231. }
  232. /* ---------- DAC ops implementation ---------- */
  233. static void tas58xx_dump_status(const char *context) {
  234. uint8_t val = 0;
  235. ESP_LOGD(TAG, "--- %s: TAS5825M status dump ---", context);
  236. if (tas58xx_read_reg(REG_DEVICE_CTRL2, &val) == ESP_OK) {
  237. const char *state_str;
  238. switch (val & CTRL2_STATE_MASK) {
  239. case CTRL2_DEEP_SLEEP:
  240. state_str = "DEEP_SLEEP";
  241. break;
  242. case CTRL2_SLEEP:
  243. state_str = "SLEEP";
  244. break;
  245. case CTRL2_HIZ:
  246. state_str = "HIZ";
  247. break;
  248. case CTRL2_PLAY:
  249. state_str = "PLAY";
  250. break;
  251. default:
  252. state_str = "UNKNOWN";
  253. break;
  254. }
  255. ESP_LOGD(TAG, " DEVICE_CTRL2=0x%02X state=%s mute=%s dsp=%s", val,
  256. state_str, (val & CTRL2_MUTE) ? "YES" : "no",
  257. (val & CTRL2_DIS_DSP) ? "DISABLED" : "enabled");
  258. }
  259. if (tas58xx_read_reg(REG_POWER_STATE, &val) == ESP_OK) {
  260. const char *ps_str;
  261. switch (val) {
  262. case 0x00:
  263. ps_str = "DEEP_SLEEP";
  264. break;
  265. case 0x01:
  266. ps_str = "SLEEP";
  267. break;
  268. case 0x02:
  269. ps_str = "HIZ";
  270. break;
  271. case 0x03:
  272. ps_str = "PLAY";
  273. break;
  274. default:
  275. ps_str = "UNKNOWN";
  276. break;
  277. }
  278. ESP_LOGD(TAG, " POWER_STATE=0x%02X (%s)", val, ps_str);
  279. }
  280. if (tas58xx_read_reg(REG_SAP_CTRL1, &val) == ESP_OK) {
  281. const char *fmt_str;
  282. switch ((val >> 4) & 0x03) {
  283. case 0:
  284. fmt_str = "I2S";
  285. break;
  286. case 1:
  287. fmt_str = "TDM/DSP";
  288. break;
  289. case 2:
  290. fmt_str = "RJ";
  291. break;
  292. case 3:
  293. fmt_str = "LJ";
  294. break;
  295. default:
  296. fmt_str = "?";
  297. break;
  298. }
  299. int wlen = 16 + ((val >> 0) & 0x03) * 8; // 00=16, 01=20, 10=24, 11=32
  300. ESP_LOGD(TAG, " SAP_CTRL1=0x%02X format=%s word_len=%d-bit", val,
  301. fmt_str, wlen);
  302. }
  303. if (tas58xx_read_reg(REG_DIG_VOL, &val) == ESP_OK) {
  304. float db = (float)(0x30 - (int)val) * 0.5f;
  305. ESP_LOGD(TAG, " DIG_VOL=0x%02X (%.1f dB%s)", val, db,
  306. val == DIG_VOL_MUTE ? " MUTED" : "");
  307. }
  308. if (tas58xx_read_reg(REG_AGAIN, &val) == ESP_OK) {
  309. float again_db = -(float)(val & 0x1F) * 0.5f;
  310. ESP_LOGD(TAG, " AGAIN=0x%02X (%.1f dB)", val, again_db);
  311. }
  312. if (tas58xx_read_reg(REG_AUTO_MUTE_CTRL, &val) == ESP_OK) {
  313. ESP_LOGD(TAG, " AUTO_MUTE_CTRL=0x%02X", val);
  314. }
  315. uint8_t chan_fault = 0, global1 = 0, global2 = 0, ot_warning = 0;
  316. tas58xx_read_reg(REG_CHAN_FAULT, &chan_fault);
  317. tas58xx_read_reg(REG_GLOBAL_FAULT1, &global1);
  318. tas58xx_read_reg(REG_GLOBAL_FAULT2, &global2);
  319. tas58xx_read_reg(REG_WARNING, &ot_warning);
  320. if (chan_fault || global1 || global2 || ot_warning) {
  321. if (chan_fault) {
  322. if (chan_fault & BIT(0)) {
  323. ESP_LOGW(TAG, "Right channel over current fault");
  324. }
  325. if (chan_fault & BIT(1)) {
  326. ESP_LOGW(TAG, "Left channel over current fault");
  327. }
  328. if (chan_fault & BIT(2)) {
  329. ESP_LOGW(TAG, "Right channel DC fault");
  330. }
  331. if (chan_fault & BIT(3)) {
  332. ESP_LOGW(TAG, "Left channel DC fault");
  333. }
  334. }
  335. if (global1) {
  336. if (global1 & BIT(0)) {
  337. ESP_LOGW(TAG, "PVDD UV fault");
  338. }
  339. if (global1 & BIT(1)) {
  340. ESP_LOGW(TAG, "PVDD OV fault");
  341. }
  342. // This fault is often triggered by lack of I2S clock, which is expected
  343. // during longer pauses (when mute state is triggeered).
  344. if (global1 & BIT(2)) {
  345. ESP_LOGW(TAG, "Clock fault");
  346. }
  347. // Bits 3-4 are reserved
  348. // Bit 5 applies only to tas5825m
  349. if (global1 & BIT(5)) {
  350. ESP_LOGW(TAG, "EEPROM boot load error");
  351. }
  352. if (global1 & BIT(6)) {
  353. ESP_LOGW(TAG, "The recent BQ write failed");
  354. }
  355. if (global1 & BIT(7)) {
  356. ESP_LOGW(TAG, "OTP CRC check error");
  357. }
  358. }
  359. if (global2) {
  360. if (global2 & BIT(0)) {
  361. ESP_LOGW(TAG, "Over temperature shut down fault");
  362. }
  363. // Bits 1-2 only apply to tas5825m
  364. if (global2 & BIT(1)) {
  365. ESP_LOGW(TAG, "Left channel cycle by cycle over current fault");
  366. }
  367. if (global2 & BIT(2)) {
  368. ESP_LOGW(TAG, "Right channel cycle by cycle over current fault");
  369. }
  370. }
  371. if (ot_warning) {
  372. if (ot_warning & BIT(0)) {
  373. ESP_LOGW(TAG, "Over temperature warning level 1, 112C");
  374. }
  375. if (ot_warning & BIT(1)) {
  376. ESP_LOGW(TAG, "Over temperature warning level 2, 122C");
  377. }
  378. if (ot_warning & BIT(2)) {
  379. ESP_LOGW(TAG, "Over temperature warning level 3, 134C");
  380. }
  381. if (ot_warning & BIT(3)) {
  382. ESP_LOGW(TAG, "Over temperature warning level 4, 146C");
  383. }
  384. // Bits 4-5 apply to tas5825m only
  385. if (ot_warning & BIT(4)) {
  386. ESP_LOGW(TAG, "Right channel cycle by cycle over current warning");
  387. }
  388. if (ot_warning & BIT(5)) {
  389. ESP_LOGW(TAG, "Left channel cycle by cycle over current warning");
  390. }
  391. }
  392. } else {
  393. ESP_LOGD(TAG, " FAULTS: none");
  394. }
  395. if (tas58xx_read_reg(REG_DSP_PGM_MODE, &val) == ESP_OK) {
  396. ESP_LOGD(TAG, " DSP_PGM_MODE=0x%02X", val);
  397. }
  398. if (tas58xx_read_reg(REG_DSP_CTRL, &val) == ESP_OK) {
  399. ESP_LOGD(TAG, " DSP_CTRL=0x%02X", val);
  400. }
  401. ESP_LOGD(TAG, "--- end status dump ---");
  402. }
  403. static esp_err_t tas58xx_init(void *i2c_bus) {
  404. esp_err_t err;
  405. ESP_LOGI(TAG, "Initializing TAS58XX");
  406. /* Create the register-access mutex (once) */
  407. if (s_reg_mutex == NULL) {
  408. s_reg_mutex = xSemaphoreCreateMutex();
  409. if (s_reg_mutex == NULL) {
  410. ESP_LOGE(TAG, "Failed to create register mutex");
  411. return ESP_ERR_NO_MEM;
  412. }
  413. }
  414. s_bus_handle = (i2c_master_bus_handle_t)i2c_bus;
  415. if (s_bus_handle == NULL) {
  416. ESP_LOGE(TAG, "No I2C bus handle provided");
  417. return ESP_ERR_INVALID_ARG;
  418. }
  419. // Detect device
  420. tas58xx_model = TAS58XX_MODEL_UNKNOWN;
  421. tas58xx_addr = tas58xx_detect(s_bus_handle);
  422. if (!tas58xx_addr) {
  423. ESP_LOGE(TAG, "No TAS5825M/TAS5805M detected on I2C bus!");
  424. return ESP_ERR_NOT_FOUND;
  425. }
  426. err = board_i2c_add_device(s_bus_handle, tas58xx_addr, I2C_LINE_SPEED,
  427. &tas58xx_device_handle);
  428. if (err != ESP_OK) {
  429. ESP_LOGE(TAG, "Could not add device to I2C bus: %s", esp_err_to_name(err));
  430. return err;
  431. }
  432. // Verify die ID
  433. uint8_t die_id = 0;
  434. err = tas58xx_read_reg(REG_DIE_ID, &die_id);
  435. if (err == ESP_OK) {
  436. ESP_LOGI(TAG, "Die ID: 0x%02X %s", die_id,
  437. (die_id == TAS5825M_DIE_ID) ? "(TAS5825M)"
  438. : (die_id == TAS5805M_DIE_ID) ? "(TAS5805M)"
  439. : "(UNEXPECTED!)");
  440. } else {
  441. ESP_LOGE(TAG, "Failed to read die ID: %s", esp_err_to_name(err));
  442. }
  443. if (tas58xx_model == TAS58XX_MODEL_UNKNOWN) {
  444. ESP_LOGE(TAG, "Unknown TAS58XX model detected — aborting init");
  445. return ESP_ERR_NOT_FOUND;
  446. }
  447. // Run init sequence
  448. const struct tas58xx_cmd_s *tas58xx_init_seq =
  449. (tas58xx_model == TAS58XX_MODEL_TAS5825M) ? tas5825m_init_seq
  450. : tas5805m_init_seq;
  451. ESP_LOGI(TAG, "Running init sequence...");
  452. for (int i = 0; tas58xx_init_seq[i].reg != 0xFF; i++) {
  453. err = tas58xx_write_reg(tas58xx_init_seq[i].reg, tas58xx_init_seq[i].value);
  454. if (err != ESP_OK) {
  455. ESP_LOGE(TAG, "Init failed at step %d: reg 0x%02X val 0x%02X: %s", i,
  456. tas58xx_init_seq[i].reg, tas58xx_init_seq[i].value,
  457. esp_err_to_name(err));
  458. return err;
  459. }
  460. ESP_LOGD(TAG, " [%02d] reg 0x%02X <- 0x%02X", i, tas58xx_init_seq[i].reg,
  461. tas58xx_init_seq[i].value);
  462. // Pause after HiZ transition to let clocks settle
  463. if (tas58xx_init_seq[i].reg == REG_DEVICE_CTRL2 &&
  464. (tas58xx_init_seq[i].value & CTRL2_STATE_MASK) == CTRL2_HIZ) {
  465. ESP_LOGD(TAG, " Waiting 10 ms for HiZ clock settle");
  466. vTaskDelay(pdMS_TO_TICKS(10));
  467. }
  468. // Pause after DSP configuration before going to PLAY
  469. if (tas58xx_init_seq[i].reg == REG_DSP_CTRL) {
  470. ESP_LOGD(TAG, " Waiting 5 ms for DSP settle");
  471. vTaskDelay(pdMS_TO_TICKS(5));
  472. }
  473. }
  474. // Give the device time to reach PLAY state
  475. vTaskDelay(pdMS_TO_TICKS(10));
  476. // Dump full status after init
  477. tas58xx_dump_status("post-init");
  478. ESP_LOGI(TAG, "%s initialized at I2C addr 0x%02X",
  479. tas58xx_model == TAS58XX_MODEL_TAS5805M ? "TAS5805M" : "TAS5825M",
  480. tas58xx_addr);
  481. return ESP_OK;
  482. }
  483. static esp_err_t tas58xx_deinit(void) {
  484. esp_err_t err = ESP_OK;
  485. // Put device into deep sleep
  486. tas58xx_write_reg(REG_DEVICE_CTRL2, CTRL2_DEEP_SLEEP);
  487. if (tas58xx_device_handle) {
  488. err = board_i2c_remove_device(tas58xx_device_handle);
  489. if (err != ESP_OK) {
  490. ESP_LOGE(TAG, "Failed to remove from I2C bus: %s", esp_err_to_name(err));
  491. }
  492. tas58xx_device_handle = NULL;
  493. }
  494. s_bus_handle = NULL;
  495. return err;
  496. }
  497. static void tas58xx_set_power_mode(dac_power_mode_t mode) {
  498. REG_LOCK();
  499. uint8_t cur_ctrl2 = 0;
  500. tas58xx_read_reg(REG_DEVICE_CTRL2, &cur_ctrl2);
  501. uint8_t cur_state = cur_ctrl2 & CTRL2_STATE_MASK;
  502. if (mode == DAC_POWER_ON) {
  503. // Always go through HIZ first (per datasheet §9.5.3.1)
  504. // The PLL needs valid I2S clocks to lock — they must be present
  505. // by the time this function is called.
  506. if (cur_state != CTRL2_HIZ) {
  507. ESP_LOGW(TAG, "Transitioning to HIZ first (from state %d)", cur_state);
  508. tas58xx_write_reg(REG_DEVICE_CTRL2, CTRL2_HIZ);
  509. vTaskDelay(pdMS_TO_TICKS(10));
  510. }
  511. /*
  512. * Per TAS5825M datasheet §7.6.2.2, exiting DEEP_SLEEP is similar
  513. * to a power-on-reset — all registers may revert to defaults.
  514. * Re-program the critical DSP registers so the correct process
  515. * flow, I2S format, and coefficient mode are active.
  516. */
  517. if (cur_state == CTRL2_DEEP_SLEEP) {
  518. ESP_LOGI(TAG, "Woke from DEEP_SLEEP — re-programming DSP registers");
  519. tas58xx_write_reg(REG_SAP_CTRL1, 0x00); /* I2S, 16-bit */
  520. tas58xx_write_reg(REG_CLOCK_DET_CTRL, 0x00);
  521. tas58xx_write_reg(REG_DSP_PGM_MODE, 0x01); /* PF1 (Base/Pro, 96kHz) */
  522. tas58xx_write_reg(REG_DSP_CTRL, 0x01); /* USE_DEFAULT_COEFFS */
  523. vTaskDelay(pdMS_TO_TICKS(5));
  524. tas58xx_write_reg(REG_DIG_VOL_CTRL1, 0x33);
  525. tas58xx_write_reg(REG_AUTO_MUTE_CTRL, 0x07);
  526. tas58xx_write_reg(REG_AUTO_MUTE_TIME, 0x00);
  527. tas58xx_write_reg(REG_DIG_VOL, DIG_VOL_0DB);
  528. tas58xx_write_reg(REG_AGAIN, 0x00);
  529. /* Coefficient RAM may be invalid after DEEP_SLEEP — force
  530. * full re-write of signal-path defaults on next EQ update. */
  531. s_dsp_defaults_written = false;
  532. }
  533. // Clear any faults accumulated while clocks were absent
  534. tas58xx_write_reg(REG_FAULT_CLEAR, 0x80);
  535. vTaskDelay(pdMS_TO_TICKS(5));
  536. // Request transition to PLAY (unmuted)
  537. tas58xx_write_reg(REG_DEVICE_CTRL2, CTRL2_PLAY);
  538. // Poll POWER_STATE until the device actually reaches PLAY.
  539. // The TAS5825M won't transition until its PLL locks on SCLK.
  540. uint8_t ps = 0;
  541. bool reached_play = false;
  542. for (int attempt = 0; attempt < 50; attempt++) { // up to ~500 ms
  543. vTaskDelay(pdMS_TO_TICKS(10));
  544. if (tas58xx_read_reg(REG_POWER_STATE, &ps) == ESP_OK && ps == 0x03) {
  545. ESP_LOGI(TAG, "Reached PLAY state after %d ms", (attempt + 1) * 10);
  546. reached_play = true;
  547. break;
  548. }
  549. }
  550. if (!reached_play) {
  551. ESP_LOGE(TAG,
  552. "FAILED to reach PLAY — POWER_STATE=0x%02X "
  553. "(is I2S providing BCLK/WS on GPIO %d/%d?)",
  554. ps, CONFIG_I2S_BCK_IO, CONFIG_I2S_WS_IO);
  555. }
  556. // Clear any faults from PLAY transition
  557. tas58xx_write_reg(REG_FAULT_CLEAR, 0x80);
  558. tas58xx_dump_status("power-on");
  559. } else if (mode == DAC_POWER_STANDBY) {
  560. tas58xx_write_reg(REG_DEVICE_CTRL2, CTRL2_HIZ);
  561. } else {
  562. tas58xx_write_reg(REG_DEVICE_CTRL2, CTRL2_DEEP_SLEEP);
  563. /* DEEP_SLEEP may reset registers and coefficient RAM — ensure
  564. * full re-initialization happens on the next wake-up. */
  565. s_dsp_defaults_written = false;
  566. }
  567. REG_UNLOCK();
  568. }
  569. static void tas58xx_enable_speaker(bool enable) {
  570. REG_LOCK();
  571. // Use mute bit in DEVICE_CTRL2 to enable/disable output.
  572. // Read current register, modify mute bit, write back.
  573. uint8_t val;
  574. esp_err_t err = tas58xx_read_reg(REG_DEVICE_CTRL2, &val);
  575. if (err != ESP_OK) {
  576. ESP_LOGE(TAG, "Failed to read DEVICE_CTRL2");
  577. REG_UNLOCK();
  578. return;
  579. }
  580. ESP_LOGI(TAG, "Speaker %s (DEVICE_CTRL2 was 0x%02X)",
  581. enable ? "ENABLE" : "DISABLE", val);
  582. if (enable) {
  583. val &= ~CTRL2_MUTE; // Clear mute bit
  584. } else {
  585. val |= CTRL2_MUTE; // Set mute bit
  586. }
  587. tas58xx_write_reg(REG_DEVICE_CTRL2, val);
  588. REG_UNLOCK();
  589. }
  590. static void tas58xx_enable_line_out(bool enable) {
  591. (void)enable;
  592. ESP_LOGW(TAG, "Line out not supported on TAS58XX");
  593. }
  594. static void tas58xx_set_volume(float volume_airplay_db) {
  595. REG_LOCK();
  596. // Clamp AirPlay input range (-30 to 0)
  597. if (volume_airplay_db > 0.0f) {
  598. volume_airplay_db = 0.0f;
  599. }
  600. if (volume_airplay_db < -30.0f) {
  601. volume_airplay_db = -30.0f;
  602. }
  603. // TAS5825M DIG_VOL register:
  604. // 0x00 = +24.0 dB
  605. // 0x30 = 0.0 dB
  606. // 0xFE = -103.0 dB
  607. // 0xFF = mute
  608. // Step = -0.5 dB per count
  609. //
  610. // Volume mapping (2:1 scaling):
  611. // AirPlay 0 dB -> DAC CONFIG_TAS58XX_MAX_VOLUME
  612. // AirPlay -25 dB -> DAC (MAX - 50)
  613. // AirPlay -30..-25 dB -> steep roll-off to mute
  614. float max_db = (float)CONFIG_TAS58XX_MAX_VOLUME;
  615. float db_level;
  616. if (volume_airplay_db >= -25.0f) {
  617. // 2:1 linear scaling: 25 dB AirPlay range -> 50 dB DAC range
  618. db_level = max_db + (volume_airplay_db * 2.0f);
  619. } else {
  620. // Roll-off: map -30..-25 to -103..(MAX-50)
  621. float normalized = (volume_airplay_db + 30.0f) / 5.0f;
  622. float rolloff_top = max_db - 50.0f;
  623. db_level = -103.0f + normalized * (103.0f + rolloff_top);
  624. }
  625. // Clamp to TAS5825M valid range: +24 dB to -103 dB
  626. if (db_level > 24.0f) {
  627. db_level = 24.0f;
  628. }
  629. if (db_level < -103.0f) {
  630. db_level = -103.0f;
  631. }
  632. // Convert dB to register value:
  633. // reg = 0x30 - (db_level * 2) (since 0x30 = 0 dB and step = -0.5 dB)
  634. uint8_t reg_val;
  635. if (db_level <= -103.0f) {
  636. reg_val = DIG_VOL_MUTE;
  637. } else {
  638. int raw = DIG_VOL_0DB - (int)(db_level * 2.0f);
  639. if (raw < 0x00) {
  640. raw = 0x00;
  641. }
  642. if (raw > 0xFE) {
  643. raw = 0xFE;
  644. }
  645. reg_val = (uint8_t)raw;
  646. }
  647. ESP_LOGD(TAG, "Volume: AirPlay %.1f dB -> DAC %.1f dB -> reg 0x%02X",
  648. volume_airplay_db, db_level, reg_val);
  649. tas58xx_write_reg(REG_DIG_VOL, reg_val);
  650. REG_UNLOCK();
  651. }
  652. /* ---------- Public ops struct ---------- */
  653. const dac_ops_t dac_tas58xx_ops = {
  654. .init = tas58xx_init,
  655. .deinit = tas58xx_deinit,
  656. .set_volume = tas58xx_set_volume,
  657. .set_power_mode = tas58xx_set_power_mode,
  658. .enable_speaker = tas58xx_enable_speaker,
  659. .enable_line_out = tas58xx_enable_line_out,
  660. };
  661. /* ---------- Register read/write helpers ---------- */
  662. static esp_err_t tas58xx_write_reg(uint8_t reg, uint8_t value) {
  663. return board_i2c_write(tas58xx_device_handle, reg, &value, sizeof(uint8_t));
  664. }
  665. static esp_err_t tas58xx_read_reg(uint8_t reg, uint8_t *value) {
  666. return board_i2c_read(tas58xx_device_handle, reg, value, sizeof(uint8_t));
  667. }
  668. /* ================== 15-Band Parametric EQ ================== */
  669. #include "dac_tas58xx_eq_data.h"
  670. #define BQ_COEFF_BOOK 0xAA /* TAS5825M coefficient book */
  671. #define BQ_COEFF_SIZE 20 /* bytes per biquad (5 × 4) */
  672. /* Book / Page / Register for EQ mode control */
  673. #define EQ_MODE_BOOK 0x8C
  674. #define EQ_MODE_PAGE 0x0B
  675. #define EQ_MODE_REG 0x28
  676. #define EQ_MODE_SIZE 8 /* 4 bytes gang_eq + 4 bytes bypass_eq */
  677. /* 15 center frequencies matching mrtoy-me/esphome-tas58xx reference */
  678. static const float eq_center_freq[TAS58XX_EQ_BANDS] = {
  679. 20.0f, 31.5f, 50.0f, 80.0f, 125.0f, 200.0f, 315.0f, 500.0f,
  680. 800.0f, 1250.0f, 2000.0f, 3150.0f, 5000.0f, 8000.0f, 16000.0f,
  681. };
  682. /* 1.0 in 5.27 fixed-point (1 sign + 4 int + 27 frac = 32-bit) */
  683. #define FP_ONE 0x08000000
  684. /* ---------- helpers ---------- */
  685. /** Select a book/page for coefficient access. */
  686. static inline esp_err_t select_book_page(uint8_t book, uint8_t page) {
  687. esp_err_t err;
  688. err = tas58xx_write_reg(REG_PAGE_SEL, 0x00);
  689. if (err != ESP_OK) {
  690. return err;
  691. }
  692. err = tas58xx_write_reg(REG_BOOK_SEL, book);
  693. if (err != ESP_OK) {
  694. return err;
  695. }
  696. return tas58xx_write_reg(REG_PAGE_SEL, page);
  697. }
  698. /** Return to Book 0, Page 0. */
  699. static inline esp_err_t select_default_page(void) {
  700. esp_err_t err;
  701. err = tas58xx_write_reg(REG_PAGE_SEL, 0x00);
  702. if (err != ESP_OK) {
  703. return err;
  704. }
  705. err = tas58xx_write_reg(REG_BOOK_SEL, 0x00);
  706. if (err != ESP_OK) {
  707. return err;
  708. }
  709. return tas58xx_write_reg(REG_PAGE_SEL, 0x00);
  710. }
  711. /**
  712. * Write a single biquad's 5 coefficients (20 bytes, big-endian) to the
  713. * TAS5825M coefficient RAM.
  714. * Caller must already have selected the coefficient Book.
  715. */
  716. static esp_err_t write_biquad_coeff(uint8_t page, uint8_t reg_start,
  717. const int32_t coeff[5]) {
  718. esp_err_t err;
  719. /* Select coefficient page */
  720. err = tas58xx_write_reg(REG_PAGE_SEL, page);
  721. if (err != ESP_OK) {
  722. return err;
  723. }
  724. uint8_t buf[BQ_COEFF_SIZE];
  725. for (int i = 0; i < 5; i++) {
  726. buf[i * 4 + 0] = (uint8_t)((coeff[i] >> 24) & 0xFF);
  727. buf[i * 4 + 1] = (uint8_t)((coeff[i] >> 16) & 0xFF);
  728. buf[i * 4 + 2] = (uint8_t)((coeff[i] >> 8) & 0xFF);
  729. buf[i * 4 + 3] = (uint8_t)((coeff[i]) & 0xFF);
  730. }
  731. return board_i2c_write(tas58xx_device_handle, reg_start, buf, BQ_COEFF_SIZE);
  732. }
  733. /**
  734. * Write a single biquad's pre-computed 20-byte coefficient block to the
  735. * TAS5825M coefficient RAM. The caller must already have selected the
  736. * correct book (0xAA); this function selects the page and writes the data.
  737. */
  738. static esp_err_t write_biquad_raw(uint8_t page, uint8_t sub_addr,
  739. const uint8_t data[EQ_COEFF_BYTES]) {
  740. esp_err_t err;
  741. err = tas58xx_write_reg(REG_PAGE_SEL, page);
  742. if (err != ESP_OK) {
  743. return err;
  744. }
  745. return board_i2c_write(tas58xx_device_handle, sub_addr, data, EQ_COEFF_BYTES);
  746. }
  747. static esp_err_t write_dsp_coeff32(uint8_t page, uint8_t reg, int32_t val) {
  748. esp_err_t err = tas58xx_write_reg(REG_PAGE_SEL, page);
  749. if (err != ESP_OK) {
  750. return err;
  751. }
  752. uint8_t buf[4] = {(uint8_t)(val >> 24), (uint8_t)(val >> 16),
  753. (uint8_t)(val >> 8), (uint8_t)(val)};
  754. return board_i2c_write(tas58xx_device_handle, reg, buf, 4);
  755. }
  756. /**
  757. * Write default coefficient values for all DSP signal-path blocks in
  758. * Book 0x8C
  759. */
  760. static esp_err_t write_dsp_signal_path_defaults(void) {
  761. esp_err_t err = ESP_OK;
  762. switch (tas58xx_model) {
  763. case TAS58XX_MODEL_TAS5805M: {
  764. ESP_LOGD(TAG, "DSP: writing signal-path defaults (Books 0x8C + 0xAA)");
  765. /*
  766. * ── Book 0xAA: ALL biquad coefficient RAM ──
  767. *
  768. * We must initialize EVERY BQ slot in Book 0xAA:
  769. * - 30 EQ BQs (15 L + 15 R) — from tas58xx_eq_left_addr /
  770. * tas58xx_eq_right_addr
  771. */
  772. err = select_book_page(0xAA, 0x00);
  773. if (err != ESP_OK) {
  774. select_default_page();
  775. return err;
  776. }
  777. /* Unity BQ: B0=1.0 (5.27), B1=B2=A1=A2=0 */
  778. static const int32_t unity_bq[5] = {FP_ONE, 0, 0, 0, 0};
  779. /*
  780. * ── EQ BQs (30 total, Pages 0x01-0x06) ──
  781. */
  782. for (int bq = 0; bq < TAS58XX_EQ_BANDS; bq++) {
  783. write_biquad_coeff(tas5805m_eq_left_addr[bq].page,
  784. tas5805m_eq_left_addr[bq].sub_addr, unity_bq);
  785. write_biquad_coeff(tas5805m_eq_right_addr[bq].page,
  786. tas5805m_eq_right_addr[bq].sub_addr, unity_bq);
  787. }
  788. err = select_default_page();
  789. s_dsp_defaults_written = true;
  790. ESP_LOGD(TAG, "DSP: signal-path defaults written (Book 0x8C + 0xAA)");
  791. } break;
  792. case TAS58XX_MODEL_TAS5825M: {
  793. ESP_LOGD(TAG, "DSP: writing signal-path defaults (Books 0x8C + 0xAA)");
  794. /*
  795. * ── Book 0x8C: control coefficients ──
  796. * All values from SLAA786A Table 9 (Process Flow 1).
  797. */
  798. err = select_book_page(0x8C, 0x00);
  799. if (err != ESP_OK) {
  800. return err;
  801. }
  802. /* Volume softening filter alpha (Page 0x01 Reg 0x2C) */
  803. write_dsp_coeff32(0x01, 0x2C, 0x00E2C46B);
  804. /*
  805. * DRC — 3-band Dynamic Range Compression (Pages 0x06–0x07)
  806. */
  807. write_dsp_coeff32(0x06, 0x58, 0x00800000); /* DRC1 mixer gain (unity) */
  808. write_dsp_coeff32(0x06, 0x5C, 0x00800000); /* DRC2 mixer gain (unity) */
  809. write_dsp_coeff32(0x06, 0x60, 0x00800000); /* DRC3 mixer gain (unity) */
  810. /* DRC1 time constants */
  811. write_dsp_coeff32(0x06, 0x64, 0x7FFFFFFF); /* DRC1 Energy */
  812. write_dsp_coeff32(0x06, 0x68, 0x7FFFFFFF); /* DRC1 Attack */
  813. write_dsp_coeff32(0x06, 0x6C, 0x7FFFFFFF); /* DRC1 Decay */
  814. /* DRC1 slopes and thresholds */
  815. write_dsp_coeff32(0x06, 0x70, 0x00000000); /* K0_1 (no compression) */
  816. write_dsp_coeff32(0x06, 0x74, 0x00000000); /* K1_1 */
  817. write_dsp_coeff32(0x06, 0x78, 0x00000000); /* K2_1 */
  818. write_dsp_coeff32(0x06, 0x7C, (int32_t)0xE7000000); /* T1_1 threshold */
  819. write_dsp_coeff32(0x07, 0x08, (int32_t)0xFE800000); /* T2_1 threshold */
  820. write_dsp_coeff32(0x07, 0x0C, 0x00000000); /* off1_1 */
  821. write_dsp_coeff32(0x07, 0x10, 0x00000000); /* off2_1 */
  822. /* DRC2 time constants */
  823. write_dsp_coeff32(0x07, 0x14, 0x7FFFFFFF); /* DRC2 Energy */
  824. write_dsp_coeff32(0x07, 0x18, 0x7FFFFFFF); /* DRC2 Attack */
  825. write_dsp_coeff32(0x07, 0x1C, 0x7FFFFFFF); /* DRC2 Decay */
  826. /* DRC2 slopes and thresholds */
  827. write_dsp_coeff32(0x07, 0x20, 0x00000000); /* k0_2 */
  828. write_dsp_coeff32(0x07, 0x24, 0x00000000); /* k1_2 */
  829. write_dsp_coeff32(0x07, 0x28, 0x00000000); /* k2_2 */
  830. write_dsp_coeff32(0x07, 0x2C, (int32_t)0xE7000000); /* t1_2 */
  831. write_dsp_coeff32(0x07, 0x30, (int32_t)0xFE800000); /* t2_2 */
  832. write_dsp_coeff32(0x07, 0x34, 0x00000000); /* off1_2 */
  833. write_dsp_coeff32(0x07, 0x38, 0x00000000); /* off2_2 */
  834. /* DRC3 time constants */
  835. write_dsp_coeff32(0x07, 0x3C, 0x7FFFFFFF); /* DRC3 Energy */
  836. write_dsp_coeff32(0x07, 0x40, 0x7FFFFFFF); /* DRC3 Attack */
  837. write_dsp_coeff32(0x07, 0x44, 0x7FFFFFFF); /* DRC3 Decay */
  838. /* DRC3 slopes and thresholds */
  839. write_dsp_coeff32(0x07, 0x48, 0x00000000); /* k0_3 */
  840. write_dsp_coeff32(0x07, 0x4C, 0x00000000); /* k1_3 */
  841. write_dsp_coeff32(0x07, 0x50, 0x00000000); /* k2_3 */
  842. write_dsp_coeff32(0x07, 0x54, (int32_t)0xE7000000); /* t1_3 */
  843. write_dsp_coeff32(0x07, 0x58, (int32_t)0xFE800000); /* t2_3 */
  844. write_dsp_coeff32(0x07, 0x5C, 0x00000000); /* off1_3 */
  845. write_dsp_coeff32(0x07, 0x60, 0x00000000); /* off2_3 */
  846. /* FS Clipper (Page 0x07) */
  847. write_dsp_coeff32(0x07, 0x64, 0x00800000); /* THD Boost (unity) */
  848. write_dsp_coeff32(0x07, 0x6C, 0x3FFFFFFF); /* CH-L Fine Volume */
  849. write_dsp_coeff32(0x07, 0x70, 0x3FFFFFFF); /* CH-R Fine Volume */
  850. /* DPEQ Control (Page 0x09) */
  851. write_dsp_coeff32(0x09, 0x28, 0x02DEAD00); /* DPEQ sense energy alpha */
  852. write_dsp_coeff32(0x09, 0x2C, 0x74013901); /* DPEQ threshold gain */
  853. write_dsp_coeff32(0x09, 0x30, 0x0020C49B); /* DPEQ threshold offset */
  854. /* Spatializer (Page 0x0A) */
  855. write_dsp_coeff32(0x0A, 0x38, 0x00000000); /* Spatializer level (off) */
  856. /* Output Crossbar (Page 0x0A) — default: straight stereo */
  857. write_dsp_coeff32(0x0A, 0x64, 0x00800000); /* Dig L ← L (unity) */
  858. write_dsp_coeff32(0x0A, 0x68, 0x00000000); /* Dig L ← R (zero) */
  859. write_dsp_coeff32(0x0A, 0x6C, 0x00000000); /* Dig R ← L (zero) */
  860. write_dsp_coeff32(0x0A, 0x70, 0x00800000); /* Dig R ← R (unity) */
  861. write_dsp_coeff32(0x0A, 0x74, 0x00800000); /* Ana L ← L (unity) */
  862. write_dsp_coeff32(0x0A, 0x78, 0x00000000); /* Ana L ← R (zero) */
  863. write_dsp_coeff32(0x0A, 0x7C, 0x00000000); /* Ana R ← L (zero) */
  864. write_dsp_coeff32(0x0B, 0x08, 0x00800000); /* Ana R ← R (unity) */
  865. /* Volume Control (Page 0x0B) */
  866. write_dsp_coeff32(0x0B, 0x0C, 0x00800000); /* CH-L Volume (unity) */
  867. write_dsp_coeff32(0x0B, 0x10, 0x00800000); /* CH-R Volume (unity) */
  868. /* Input Mixer (Page 0x0B) */
  869. write_dsp_coeff32(0x0B, 0x14, 0x00800000); /* L → L (unity) */
  870. write_dsp_coeff32(0x0B, 0x18, 0x00000000); /* R → L (zero) */
  871. write_dsp_coeff32(0x0B, 0x1C, 0x00000000); /* L → R (zero) */
  872. write_dsp_coeff32(0x0B, 0x20, 0x00800000); /* R → R (unity) */
  873. /* Bypass DC Block (Page 0x0B) */
  874. write_dsp_coeff32(0x0B, 0x24, 0x00000000);
  875. /* EQ Control (Page 0x0B) */
  876. write_dsp_coeff32(0x0B, 0x28, 0x00000000); /* GangEQ = 0 */
  877. write_dsp_coeff32(0x0B, 0x2C, 0x00000000); /* BypassEQ = 0 */
  878. /* Level Meter (Page 0x0B) */
  879. write_dsp_coeff32(0x0B, 0x30, 0x00A7264A); /* Softening filter alpha */
  880. write_dsp_coeff32(0x0B, 0x34, 0x00000000); /* Level meter input mux */
  881. /* Bank Switch (Page 0x0C) */
  882. write_dsp_coeff32(0x0C, 0x20, 0x00000000);
  883. /*
  884. * ── Book 0xAA: ALL biquad coefficient RAM ──
  885. *
  886. * We must initialize EVERY BQ slot in Book 0xAA:
  887. * - 30 EQ BQs (15 L + 15 R) — from tas58xx_eq_left_addr /
  888. * tas58xx_eq_right_addr
  889. * - 8 DRC crossover BQs — linear layout from Page 0x07:0x78
  890. * - 3 DPEQ BQs — Pages 0x09-0x0A
  891. * - 2 Spatializer BQs — Page 0x0A
  892. */
  893. err = select_book_page(0xAA, 0x00);
  894. if (err != ESP_OK) {
  895. select_default_page();
  896. return err;
  897. }
  898. /* Unity BQ: B0=1.0 (5.27), B1=B2=A1=A2=0 */
  899. static const int32_t unity_bq[5] = {FP_ONE, 0, 0, 0, 0};
  900. /*
  901. * ── DRC crossover BQs (8 total, Pages 0x07-0x09) ──
  902. * Linear from Page 0x07 Reg 0x78.
  903. */
  904. /* DRC low BQ1: 0x07:0x78 → crosses to 0x08 (use individual writes) */
  905. write_dsp_coeff32(0x07, 0x78, FP_ONE);
  906. write_dsp_coeff32(0x07, 0x7C, 0x00000000);
  907. write_dsp_coeff32(0x08, 0x08, 0x00000000);
  908. write_dsp_coeff32(0x08, 0x0C, 0x00000000);
  909. write_dsp_coeff32(0x08, 0x10, 0x00000000);
  910. /* DRC low BQ2: 0x08:0x14 (fits on page) */
  911. write_biquad_coeff(0x08, 0x14, unity_bq);
  912. /* DRC high BQ1: 0x08:0x28 (fits on page) */
  913. write_biquad_coeff(0x08, 0x28, unity_bq);
  914. /* DRC high BQ2: 0x08:0x3C (fits on page) */
  915. write_biquad_coeff(0x08, 0x3C, unity_bq);
  916. /* DRC mid BQ1: 0x08:0x50 (fits on page) */
  917. write_biquad_coeff(0x08, 0x50, unity_bq);
  918. /* DRC mid BQ2: 0x08:0x64 (fits: 0x64+19=0x77) */
  919. write_biquad_coeff(0x08, 0x64, unity_bq);
  920. /* DRC mid BQ3: 0x08:0x78 → crosses to 0x09 (use individual writes) */
  921. write_dsp_coeff32(0x08, 0x78, FP_ONE);
  922. write_dsp_coeff32(0x08, 0x7C, 0x00000000);
  923. write_dsp_coeff32(0x09, 0x08, 0x00000000);
  924. write_dsp_coeff32(0x09, 0x0C, 0x00000000);
  925. write_dsp_coeff32(0x09, 0x10, 0x00000000);
  926. /* DRC mid BQ4: 0x09:0x14 (fits on page) */
  927. write_biquad_coeff(0x09, 0x14, unity_bq);
  928. /*
  929. * ── DPEQ BQs (3 total, Pages 0x09-0x0A) ──
  930. */
  931. write_biquad_coeff(0x09, 0x34, unity_bq); /* DPEQ sense BQ */
  932. write_biquad_coeff(0x09, 0x5C, unity_bq); /* DPEQ low-level path BQ */
  933. write_biquad_coeff(0x0A, 0x0C, unity_bq); /* DPEQ high-level path BQ */
  934. /*
  935. * ── Spatializer BQs (2 total, Page 0x0A) ──
  936. */
  937. write_biquad_coeff(0x0A, 0x3C, unity_bq); /* Spatializer BQ1 */
  938. write_biquad_coeff(0x0A, 0x50, unity_bq); /* Spatializer BQ2 */
  939. /*
  940. * ── EQ BQs (30 total, Pages 0x01-0x06) ──
  941. */
  942. for (int bq = 0; bq < TAS58XX_EQ_BANDS; bq++) {
  943. write_biquad_coeff(tas5825m_eq_left_addr[bq].page,
  944. tas5825m_eq_left_addr[bq].sub_addr, unity_bq);
  945. write_biquad_coeff(tas5825m_eq_right_addr[bq].page,
  946. tas5825m_eq_right_addr[bq].sub_addr, unity_bq);
  947. }
  948. err = select_default_page();
  949. s_dsp_defaults_written = true;
  950. ESP_LOGD(TAG, "DSP: signal-path defaults written (Book 0x8C + 0xAA)");
  951. } break;
  952. default:
  953. ESP_LOGE(TAG, "Unknown TAS58XX model %d in write_dsp_signal_path_defaults",
  954. tas58xx_model);
  955. return ESP_ERR_INVALID_STATE;
  956. }
  957. return err;
  958. }
  959. static esp_err_t ensure_custom_coeffs_mode(void) {
  960. // Only applicable to TAS5825M
  961. uint8_t dsp_ctrl;
  962. esp_err_t err = tas58xx_read_reg(REG_DSP_CTRL, &dsp_ctrl);
  963. if (err != ESP_OK) {
  964. return err;
  965. }
  966. if (dsp_ctrl & 0x01) {
  967. /* Mute while we reconfigure the entire coefficient RAM */
  968. uint8_t saved_ctrl2 = 0;
  969. tas58xx_read_reg(REG_DEVICE_CTRL2, &saved_ctrl2);
  970. bool was_unmuted = !(saved_ctrl2 & CTRL2_MUTE);
  971. if (was_unmuted) {
  972. tas58xx_write_reg(REG_DEVICE_CTRL2, saved_ctrl2 | CTRL2_MUTE);
  973. vTaskDelay(pdMS_TO_TICKS(5)); /* let mute take effect */
  974. }
  975. /* Write all signal-path coefficients first */
  976. if (!s_dsp_defaults_written) {
  977. err = write_dsp_signal_path_defaults();
  978. if (err != ESP_OK) {
  979. if (was_unmuted) {
  980. tas58xx_write_reg(REG_DEVICE_CTRL2, saved_ctrl2);
  981. }
  982. return err;
  983. }
  984. }
  985. /* Now safe to clear USE_DEFAULT_COEFFS */
  986. ESP_LOGD(TAG, "DSP: clearing USE_DEFAULT_COEFFS");
  987. err = tas58xx_write_reg(REG_DSP_CTRL, dsp_ctrl & ~0x01);
  988. /* Verify the bit was actually cleared */
  989. {
  990. uint8_t verify = 0xFF;
  991. tas58xx_read_reg(REG_DSP_CTRL, &verify);
  992. uint8_t pgm = 0xFF;
  993. tas58xx_read_reg(REG_DSP_PGM_MODE, &pgm);
  994. ESP_LOGD(TAG,
  995. "DSP: post-clear DSP_CTRL=0x%02X (expect 0x00) "
  996. "DSP_PGM_MODE=0x%02X (expect 0x01)",
  997. verify, pgm);
  998. if (verify & 0x01) {
  999. ESP_LOGE(TAG, "DSP: USE_DEFAULT_COEFFS still set!");
  1000. }
  1001. if (pgm != 0x01) {
  1002. ESP_LOGW(TAG,
  1003. "DSP: unexpected DSP_PGM_MODE — process flow may be wrong! "
  1004. "EQ addresses assume PF1 (0x01)");
  1005. }
  1006. }
  1007. /* Unmute */
  1008. if (was_unmuted) {
  1009. vTaskDelay(pdMS_TO_TICKS(5));
  1010. tas58xx_write_reg(REG_DEVICE_CTRL2, saved_ctrl2);
  1011. }
  1012. }
  1013. return err;
  1014. }
  1015. /**
  1016. * Program one biquad on both channels using pre-computed 20-byte coefficient
  1017. * blocks from dac_tas58xx_eq_data.h.
  1018. *
  1019. * Enters Book 0xAA, writes CH-L then CH-R, returns to Book 0 / Page 0.
  1020. * Assumes the caller holds REG_LOCK.
  1021. */
  1022. static esp_err_t program_biquad_raw(int bq,
  1023. const uint8_t data[EQ_COEFF_BYTES]) {
  1024. esp_err_t err;
  1025. if (tas58xx_model == TAS58XX_MODEL_TAS5825M) {
  1026. /* Ensure DSP has all signal-path defaults before using custom coefficients
  1027. */
  1028. err = ensure_custom_coeffs_mode();
  1029. if (err != ESP_OK) {
  1030. return err;
  1031. }
  1032. }
  1033. /* Enter coefficient book */
  1034. err = select_book_page(BQ_COEFF_BOOK, 0x00);
  1035. if (err != ESP_OK) {
  1036. goto out;
  1037. }
  1038. const eq_bq_addr_t *eq_left_addr = (tas58xx_model == TAS58XX_MODEL_TAS5805M)
  1039. ? tas5805m_eq_left_addr
  1040. : tas5825m_eq_left_addr;
  1041. const eq_bq_addr_t *eq_right_addr = (tas58xx_model == TAS58XX_MODEL_TAS5805M)
  1042. ? tas5805m_eq_right_addr
  1043. : tas5825m_eq_right_addr;
  1044. /* Channel 1 (Left) */
  1045. err =
  1046. write_biquad_raw(eq_left_addr[bq].page, eq_left_addr[bq].sub_addr, data);
  1047. if (err != ESP_OK) {
  1048. ESP_LOGE(TAG, "EQ: CH1 BQ%d raw write failed: %s", bq,
  1049. esp_err_to_name(err));
  1050. goto out;
  1051. }
  1052. /* Channel 2 (Right) */
  1053. err = write_biquad_raw(eq_right_addr[bq].page, eq_right_addr[bq].sub_addr,
  1054. data);
  1055. if (err != ESP_OK) {
  1056. ESP_LOGE(TAG, "EQ: CH2 BQ%d raw write failed: %s", bq,
  1057. esp_err_to_name(err));
  1058. goto out;
  1059. }
  1060. ESP_LOGD(TAG,
  1061. "EQ: BQ%d raw write OK (L page=0x%02X:0x%02X, R page=0x%02X:0x%02X)",
  1062. bq, eq_left_addr[bq].page, eq_left_addr[bq].sub_addr,
  1063. eq_right_addr[bq].page, eq_right_addr[bq].sub_addr);
  1064. out:
  1065. select_default_page();
  1066. return err;
  1067. }
  1068. static esp_err_t write_eq_mode(bool enable) {
  1069. esp_err_t err;
  1070. switch (tas58xx_model) {
  1071. case TAS58XX_MODEL_TAS5805M: {
  1072. select_default_page();
  1073. uint8_t value = enable ? 0x08 : 0x09; /* bit0 = BYPASS_EQ */
  1074. err = tas58xx_write_reg(REG_DSP_MISC, value);
  1075. if (err != ESP_OK) {
  1076. ESP_LOGE(TAG, "EQ: mode write failed: %s", esp_err_to_name(err));
  1077. } else {
  1078. ESP_LOGD(TAG, "EQ: %s", enable ? "ENABLED" : "BYPASSED");
  1079. }
  1080. } break;
  1081. case TAS58XX_MODEL_TAS5825M: {
  1082. err = select_book_page(EQ_MODE_BOOK, EQ_MODE_PAGE);
  1083. if (err != ESP_OK) {
  1084. return err;
  1085. }
  1086. uint8_t mode_data[EQ_MODE_SIZE] = {
  1087. 0x00, 0x80, 0x00, 0x00, /* gang_eq = 0x00800000 */
  1088. 0x00, 0x00, 0x00, enable ? 0x00 : 0x01, /* bypass_eq */
  1089. };
  1090. err = board_i2c_write(tas58xx_device_handle, EQ_MODE_REG, mode_data,
  1091. EQ_MODE_SIZE);
  1092. if (err != ESP_OK) {
  1093. ESP_LOGE(TAG, "EQ: mode write failed: %s", esp_err_to_name(err));
  1094. } else {
  1095. ESP_LOGD(TAG, "EQ: %s", enable ? "ENABLED" : "BYPASSED");
  1096. }
  1097. select_default_page();
  1098. } break;
  1099. default:
  1100. ESP_LOGE(TAG, "Unknown TAS58XX model %d in write_eq_mode", tas58xx_model);
  1101. return ESP_ERR_INVALID_STATE;
  1102. }
  1103. return err;
  1104. }
  1105. /* ---------- Public API ---------- */
  1106. esp_err_t tas58xx_eq_enable(bool enable) {
  1107. REG_LOCK();
  1108. esp_err_t err;
  1109. if (tas58xx_model == TAS58XX_MODEL_TAS5825M) {
  1110. /* Ensure DSP defaults are written before touching EQ mode */
  1111. err = ensure_custom_coeffs_mode();
  1112. if (err != ESP_OK) {
  1113. REG_UNLOCK();
  1114. return err;
  1115. }
  1116. }
  1117. err = write_eq_mode(enable);
  1118. REG_UNLOCK();
  1119. return err;
  1120. }
  1121. esp_err_t tas58xx_eq_set_band(int band, float gain_db) {
  1122. if (band < 0 || band >= TAS58XX_EQ_BANDS) {
  1123. ESP_LOGE(TAG, "EQ: invalid band %d", band);
  1124. return ESP_ERR_INVALID_ARG;
  1125. }
  1126. /* Clamp gain to integer dB range of pre-computed table */
  1127. int gain_int = (int)roundf(gain_db);
  1128. if (gain_int > (int)TAS58XX_EQ_MAX_GAIN_DB) {
  1129. gain_int = (int)TAS58XX_EQ_MAX_GAIN_DB;
  1130. }
  1131. if (gain_int < (int)TAS58XX_EQ_MIN_GAIN_DB) {
  1132. gain_int = (int)TAS58XX_EQ_MIN_GAIN_DB;
  1133. }
  1134. int idx = gain_int + EQ_GAIN_OFFSET;
  1135. ESP_LOGD(TAG, "EQ: band %d (%.0f Hz) -> %+d dB (table idx %d)", band,
  1136. eq_center_freq[band], gain_int, idx);
  1137. REG_LOCK();
  1138. esp_err_t err = program_biquad_raw(band, eq_coeff_table[idx][band].bytes);
  1139. REG_UNLOCK();
  1140. return err;
  1141. }
  1142. esp_err_t tas58xx_eq_set_all(const float gains_db[TAS58XX_EQ_BANDS]) {
  1143. if (!gains_db) {
  1144. return ESP_ERR_INVALID_ARG;
  1145. }
  1146. REG_LOCK();
  1147. /* Mute to prevent DSP glitches while bulk-updating coefficients */
  1148. uint8_t saved_ctrl2 = 0;
  1149. tas58xx_read_reg(REG_DEVICE_CTRL2, &saved_ctrl2);
  1150. if (!(saved_ctrl2 & CTRL2_MUTE)) {
  1151. tas58xx_write_reg(REG_DEVICE_CTRL2, saved_ctrl2 | CTRL2_MUTE);
  1152. }
  1153. esp_err_t first_err = ESP_OK;
  1154. for (int i = 0; i < TAS58XX_EQ_BANDS; i++) {
  1155. int gain_int = (int)roundf(gains_db[i]);
  1156. if (gain_int > (int)TAS58XX_EQ_MAX_GAIN_DB) {
  1157. gain_int = (int)TAS58XX_EQ_MAX_GAIN_DB;
  1158. }
  1159. if (gain_int < (int)TAS58XX_EQ_MIN_GAIN_DB) {
  1160. gain_int = (int)TAS58XX_EQ_MIN_GAIN_DB;
  1161. }
  1162. int idx = gain_int + EQ_GAIN_OFFSET;
  1163. esp_err_t err = program_biquad_raw(i, eq_coeff_table[idx][i].bytes);
  1164. if (err != ESP_OK && first_err == ESP_OK) {
  1165. first_err = err;
  1166. }
  1167. }
  1168. /* Restore original mute state */
  1169. tas58xx_write_reg(REG_DEVICE_CTRL2, saved_ctrl2);
  1170. REG_UNLOCK();
  1171. return first_err;
  1172. }
  1173. esp_err_t tas58xx_eq_flat(void) {
  1174. ESP_LOGD(TAG, "EQ: resetting all bands to flat");
  1175. /* Index for 0 dB gain = unity passthrough */
  1176. const int flat_idx = EQ_GAIN_OFFSET;
  1177. REG_LOCK();
  1178. /* Mute during bulk update */
  1179. uint8_t saved_ctrl2 = 0;
  1180. tas58xx_read_reg(REG_DEVICE_CTRL2, &saved_ctrl2);
  1181. if (!(saved_ctrl2 & CTRL2_MUTE)) {
  1182. tas58xx_write_reg(REG_DEVICE_CTRL2, saved_ctrl2 | CTRL2_MUTE);
  1183. }
  1184. esp_err_t first_err = ESP_OK;
  1185. for (int i = 0; i < TAS58XX_EQ_BANDS; i++) {
  1186. esp_err_t err = program_biquad_raw(i, eq_coeff_table[flat_idx][i].bytes);
  1187. if (err != ESP_OK && first_err == ESP_OK) {
  1188. first_err = err;
  1189. }
  1190. }
  1191. /* Enable EQ after programming flat coefficients */
  1192. if (first_err == ESP_OK) {
  1193. first_err = write_eq_mode(true);
  1194. }
  1195. /* Restore original mute state */
  1196. tas58xx_write_reg(REG_DEVICE_CTRL2, saved_ctrl2);
  1197. REG_UNLOCK();
  1198. return first_err;
  1199. }
  1200. float tas58xx_eq_get_center_freq(int band) {
  1201. if (band < 0 || band >= TAS58XX_EQ_BANDS) {
  1202. return 0.0f;
  1203. }
  1204. return eq_center_freq[band];
  1205. }