dac_tas57xx.c 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381
  1. /**
  2. * Implementation of control interface to TI TAX57xx DAC/Amp chips
  3. * tas5754m datasheet:
  4. * https://www.ti.com/lit/ds/symlink/tas5754m.pdf
  5. */
  6. #include "dac_tas57xx.h"
  7. #include "board_utils.h"
  8. #include <math.h>
  9. #include <stdio.h>
  10. #include <string.h>
  11. #include <sys/param.h>
  12. #include "driver/i2s_std.h"
  13. #include "driver/i2c_master.h"
  14. #include "esp_log.h"
  15. #include "freertos/FreeRTOS.h"
  16. #include "freertos/semphr.h"
  17. #include "freertos/task.h"
  18. #define TAS575x (0x98 >> 1)
  19. #define TAS578x (0x90 >> 1)
  20. // TAS578x device ID register (Book 0, Page 0)
  21. #define TAS578x_REG_DEVICE_ID 0x67
  22. #define I2C_TIMEOUT 100
  23. #define I2C_LINE_SPEED 100000
  24. static const char TAG[] = "TAS57xx DAC";
  25. struct tas57xx_cmd_s {
  26. uint8_t reg;
  27. uint8_t value;
  28. };
  29. // Registers applied after the HF config (not covered by the HF flow).
  30. // HF exits standby unmuted, so mute first to prevent pop.
  31. static const struct tas57xx_cmd_s tas57xx_init_seq[] = {
  32. {0x00, 0x00}, // select page 0
  33. {0x03, 0x11}, // mute both channels before any other change
  34. {0x0d, 0x10}, // use SCK for PLL
  35. {0x25, 0x08}, // ignore SCK halt
  36. {0x08, 0x10}, // Mute control enable (GPIO3)
  37. {0x54, 0x02}, // Mute output control
  38. {0x3D, 0x6C}, // Set chan B volume -70dB
  39. {0x3E, 0x6C}, // Set chan A volume -70dB
  40. {0xff, 0xff} // end of table
  41. };
  42. // Commands available - care to match ordinal with struct below
  43. typedef enum {
  44. TAS57XX_ACTIVE = 0,
  45. TAS57XX_STANDBY,
  46. TAS57XX_DOWN,
  47. TAS57XX_ANALOGUE_OFF,
  48. TAS57XX_ANALOGUE_ON,
  49. TAS57XX_SET_VOLUME_A_L,
  50. TAS57XX_SET_VOLUME_B_R,
  51. TAS57XX_MUTE,
  52. TAS57XX_UNMUTE,
  53. } tas57xx_cmd_e;
  54. static const struct tas57xx_cmd_s tas57xx_cmd[] = {
  55. {0x02, 0x00}, // TAS57XX_ACTIVE
  56. {0x02, 0x10}, // TAS57XX_STANDBY
  57. {0x02, 0x01}, // TAS57XX_DOWN
  58. {0x56, 0x10}, // TAS57XX_ANALOGUE_OFF
  59. {0x56, 0x00}, // TAS57XX_ANALOGUE_ON
  60. {0x3E, 0x30}, // TAS57XX_SET_VOLUME_A_L - Channel A
  61. {0x3D, 0x30}, // TAS57XX_SET_VOLUME_B_R - Channel B
  62. {0x03, 0x11}, // TAS57XX_MUTE (BA)
  63. {0x03, 0x00}, // TAS57XX_UNMUTE (BA)
  64. };
  65. static uint8_t tas57xx_addr;
  66. static i2c_master_bus_handle_t s_bus_handle = NULL;
  67. static i2c_master_dev_handle_t tas57xx_device_handle;
  68. static dac_power_mode_t s_power_state = DAC_POWER_OFF;
  69. static uint8_t *s_hf_buf = NULL; // Cached hybrid flow (TAS5754M only)
  70. static long s_hf_size = 0;
  71. static SemaphoreHandle_t s_dac_mutex = NULL;
  72. static esp_err_t write_cmd(tas57xx_cmd_e cmd, ...);
  73. static int tas57xx_detect(i2c_master_bus_handle_t s_bus_handle);
  74. /**
  75. * Write a hybrid flow configuration byte stream to the DAC.
  76. * Format: [reg, len, data[0..len-1], ...] terminated by 0xFF, 0xFF.
  77. * The HF config manages its own standby entry/exit.
  78. */
  79. static esp_err_t tas57xx_write_hf(const uint8_t *stream) {
  80. esp_err_t err;
  81. int pos = 0;
  82. while (!(stream[pos] == 0xFF && stream[pos + 1] == 0xFF)) {
  83. uint8_t reg = stream[pos];
  84. uint8_t len = stream[pos + 1];
  85. const uint8_t *data = &stream[pos + 2];
  86. err = board_i2c_write(tas57xx_device_handle, reg, data, len);
  87. if (err != ESP_OK) {
  88. ESP_LOGE(TAG, "HF write failed at offset %d (reg 0x%02X): %s", pos, reg,
  89. esp_err_to_name(err));
  90. return err;
  91. }
  92. pos += 2 + len;
  93. }
  94. ESP_LOGI(TAG, "HybridFlow loaded");
  95. return ESP_OK;
  96. }
  97. static esp_err_t tas57xx_init(void *i2c_bus) {
  98. esp_err_t err = ESP_OK;
  99. if (s_dac_mutex == NULL) {
  100. s_dac_mutex = xSemaphoreCreateMutex();
  101. if (s_dac_mutex == NULL) {
  102. ESP_LOGE(TAG, "Failed to create DAC mutex");
  103. return ESP_ERR_NO_MEM;
  104. }
  105. }
  106. s_bus_handle = (i2c_master_bus_handle_t)i2c_bus;
  107. if (s_bus_handle == NULL) {
  108. ESP_LOGE(TAG, "No I2C bus handle provided");
  109. return ESP_ERR_INVALID_ARG;
  110. }
  111. // Detect TAS57xx chip
  112. tas57xx_addr = tas57xx_detect(s_bus_handle);
  113. if (!tas57xx_addr) {
  114. ESP_LOGW(TAG, "No TAS57xx detected");
  115. return ESP_ERR_NOT_FOUND;
  116. }
  117. err = board_i2c_add_device(s_bus_handle, tas57xx_addr, I2C_LINE_SPEED,
  118. &tas57xx_device_handle);
  119. if (ESP_OK != err) {
  120. ESP_LOGE(TAG, "Could not add device to bus: %s", esp_err_to_name(err));
  121. return err;
  122. }
  123. // Read chip identity for feature availability
  124. if (tas57xx_addr == TAS578x) {
  125. uint8_t page = 0x00;
  126. board_i2c_write(tas57xx_device_handle, 0x00, &page, 1);
  127. uint8_t device_id = 0;
  128. if (board_i2c_read(tas57xx_device_handle, TAS578x_REG_DEVICE_ID, &device_id,
  129. 1) == ESP_OK) {
  130. ESP_LOGI(TAG, "TAS578x device ID: 0x%02X", device_id);
  131. }
  132. } else if (tas57xx_addr == TAS575x) {
  133. ESP_LOGI(TAG, "TAS575x detected (no device ID register)");
  134. }
  135. // Load and cache hybrid flow from SPIFFS for TAS575x (TAS5754M with miniDSP)
  136. static const char *hf_path = "/spiffs/hf/tas57xx_fw.bin";
  137. if (tas57xx_addr == TAS575x) {
  138. FILE *f = fopen(hf_path, "rb");
  139. if (f) {
  140. fseek(f, 0, SEEK_END);
  141. long size = ftell(f);
  142. fseek(f, 0, SEEK_SET);
  143. uint8_t *buf = malloc(size);
  144. if (buf && fread(buf, 1, size, f) == (size_t)size) {
  145. s_hf_buf = buf;
  146. s_hf_size = size;
  147. err = tas57xx_write_hf(s_hf_buf);
  148. } else {
  149. ESP_LOGE(TAG, "Failed to read HF file %s", hf_path);
  150. free(buf);
  151. err = ESP_ERR_NO_MEM;
  152. }
  153. fclose(f);
  154. if (err != ESP_OK) {
  155. return err;
  156. }
  157. } else {
  158. ESP_LOGI(TAG, "No HF file at %s, skipping", hf_path);
  159. }
  160. }
  161. // Apply additional init registers
  162. for (int i = 0; tas57xx_init_seq[i].reg != 0xff; i++) {
  163. err = board_i2c_write(tas57xx_device_handle, tas57xx_init_seq[i].reg,
  164. &tas57xx_init_seq[i].value, sizeof(uint8_t));
  165. if (err != ESP_OK) {
  166. ESP_LOGE(TAG, "Failed to write init reg 0x%02x: %s",
  167. tas57xx_init_seq[i].reg, esp_err_to_name(err));
  168. return err;
  169. }
  170. }
  171. return err;
  172. }
  173. static esp_err_t tas57xx_deinit(void) {
  174. esp_err_t err = ESP_OK;
  175. if (tas57xx_device_handle) {
  176. err = board_i2c_remove_device(tas57xx_device_handle);
  177. if (err != ESP_OK) {
  178. ESP_LOGE(TAG, "failed to remove from i2c bus, err: %s",
  179. esp_err_to_name(err));
  180. }
  181. tas57xx_device_handle = NULL;
  182. }
  183. s_bus_handle = NULL;
  184. free(s_hf_buf);
  185. s_hf_buf = NULL;
  186. if (s_dac_mutex != NULL) {
  187. vSemaphoreDelete(s_dac_mutex);
  188. s_dac_mutex = NULL;
  189. }
  190. s_hf_size = 0;
  191. return err;
  192. }
  193. /**
  194. * Re-apply HF config and init registers after a full shutdown.
  195. * Shutdown (reg 0x02=0x01) loses miniDSP RAM contents.
  196. */
  197. static void tas57xx_restore_config(void) {
  198. if (s_hf_buf) {
  199. esp_err_t err = tas57xx_write_hf(s_hf_buf);
  200. if (err != ESP_OK) {
  201. ESP_LOGE(TAG, "Failed to restore HF config: %s", esp_err_to_name(err));
  202. }
  203. }
  204. for (int i = 0; tas57xx_init_seq[i].reg != 0xff; i++) {
  205. board_i2c_write(tas57xx_device_handle, tas57xx_init_seq[i].reg,
  206. &tas57xx_init_seq[i].value, sizeof(uint8_t));
  207. }
  208. }
  209. static void tas57xx_enable_speaker(bool enable) {
  210. if (enable) {
  211. write_cmd(TAS57XX_ANALOGUE_ON);
  212. } else {
  213. write_cmd(TAS57XX_ANALOGUE_OFF);
  214. }
  215. }
  216. static void tas57xx_set_power_mode(dac_power_mode_t mode) {
  217. xSemaphoreTake(s_dac_mutex, portMAX_DELAY);
  218. tas57xx_enable_speaker(false);
  219. switch (mode) {
  220. case DAC_POWER_STANDBY:
  221. write_cmd(TAS57XX_MUTE);
  222. write_cmd(TAS57XX_STANDBY);
  223. if (s_power_state == DAC_POWER_OFF) {
  224. // Wait for standby state to settle before writing miniDSP config
  225. vTaskDelay(pdMS_TO_TICKS(50));
  226. tas57xx_restore_config();
  227. }
  228. break;
  229. case DAC_POWER_ON:
  230. write_cmd(TAS57XX_MUTE);
  231. write_cmd(TAS57XX_ACTIVE);
  232. // Allow PLL lock and charge pump settling before unmuting
  233. vTaskDelay(pdMS_TO_TICKS(50));
  234. write_cmd(TAS57XX_UNMUTE);
  235. tas57xx_enable_speaker(true);
  236. break;
  237. case DAC_POWER_OFF:
  238. write_cmd(TAS57XX_MUTE);
  239. write_cmd(TAS57XX_DOWN);
  240. break;
  241. default:
  242. ESP_LOGW(TAG, "Unhandled power mode");
  243. break;
  244. }
  245. s_power_state = mode;
  246. xSemaphoreGive(s_dac_mutex);
  247. }
  248. static void tas57xx_enable_line_out(bool enable) {
  249. (void)enable;
  250. ESP_LOGW(TAG, "Not supported yet");
  251. }
  252. static void tas57xx_set_volume(float volume_airplay_db) {
  253. xSemaphoreTake(s_dac_mutex, portMAX_DELAY);
  254. // Clamp AirPlay input range (-30 to 0)
  255. if (volume_airplay_db > 0.0f) {
  256. volume_airplay_db = 0.0f;
  257. }
  258. if (volume_airplay_db < -30.0f) {
  259. volume_airplay_db = -30.0f;
  260. }
  261. // Volume mapping (2:1 scaling):
  262. // AirPlay 0 dB -> DAC CONFIG_TAS57XX_MAX_VOLUME
  263. // AirPlay -25 dB -> DAC (MAX - 50)
  264. // AirPlay -30..-25 dB -> DAC mute(-127)..(MAX-50) (steep roll-off)
  265. float max_db = (float)CONFIG_TAS57XX_MAX_VOLUME;
  266. float db_level;
  267. if (volume_airplay_db >= -25.0f) {
  268. // 2:1 linear scaling: 25 dB AirPlay range -> 50 dB DAC range
  269. // AirPlay 0 -> MAX, AirPlay -25 -> MAX - 50
  270. db_level = max_db + (volume_airplay_db * 2.0f);
  271. } else {
  272. // Roll-off: map -30..-25 to -127..(MAX-50)
  273. // normalized: 0 at -30, 1 at -25
  274. float normalized = (volume_airplay_db + 30.0f) / 5.0f;
  275. float rolloff_top = max_db - 50.0f;
  276. db_level = -127.0f + normalized * (127.0f + rolloff_top);
  277. }
  278. // Clamp to DAC valid range
  279. if (db_level > 0.0f) {
  280. db_level = 0.0f;
  281. }
  282. if (db_level < -127.0f) {
  283. db_level = -127.0f;
  284. }
  285. // Convert dB to DAC register: reg = -dB * 2 (0x00=0dB, 0xFE=-127dB)
  286. uint8_t reg_val = (uint8_t)(-db_level * 2.0f);
  287. ESP_LOGD(TAG, "Volume: AirPlay %.1f dB -> DAC %.1f dB -> reg 0x%02X",
  288. volume_airplay_db, db_level, reg_val);
  289. write_cmd(TAS57XX_SET_VOLUME_A_L, reg_val);
  290. write_cmd(TAS57XX_SET_VOLUME_B_R, reg_val);
  291. xSemaphoreGive(s_dac_mutex);
  292. }
  293. const dac_ops_t dac_tas57xx_ops = {
  294. .init = tas57xx_init,
  295. .deinit = tas57xx_deinit,
  296. .set_volume = tas57xx_set_volume,
  297. .set_power_mode = tas57xx_set_power_mode,
  298. .enable_speaker = tas57xx_enable_speaker,
  299. .enable_line_out = tas57xx_enable_line_out,
  300. };
  301. static esp_err_t write_cmd(tas57xx_cmd_e cmd, ...) {
  302. va_list args;
  303. esp_err_t err = ESP_OK;
  304. va_start(args, cmd);
  305. switch (cmd) {
  306. case TAS57XX_SET_VOLUME_A_L:
  307. case TAS57XX_SET_VOLUME_B_R:
  308. uint8_t val = (uint8_t)va_arg(args, int);
  309. err = board_i2c_write(tas57xx_device_handle, tas57xx_cmd[cmd].reg, &val,
  310. sizeof(uint8_t));
  311. break;
  312. default:
  313. err = board_i2c_write(tas57xx_device_handle, tas57xx_cmd[cmd].reg,
  314. &(tas57xx_cmd[cmd].value), sizeof(uint8_t));
  315. }
  316. if (err != ESP_OK) {
  317. ESP_LOGE(TAG, "Failed i2c write to TAS57xx: %s", esp_err_to_name(err));
  318. }
  319. va_end(args);
  320. return err;
  321. }
  322. /**
  323. * Find a known chip ID on the I2C bus
  324. */
  325. static int tas57xx_detect(i2c_master_bus_handle_t s_bus_handle) {
  326. uint8_t supported_chips[] = {TAS578x, TAS575x};
  327. if (!s_bus_handle) {
  328. ESP_LOGE(TAG, "Invalid i2c handle!");
  329. return -1;
  330. }
  331. for (int i = 0; i < sizeof(supported_chips); i++) {
  332. if (ESP_OK ==
  333. i2c_master_probe(s_bus_handle, supported_chips[i], I2C_TIMEOUT)) {
  334. ESP_LOGI(TAG, "Detected TAS57xx at @0x%x", supported_chips[i]);
  335. return supported_chips[i];
  336. }
  337. }
  338. return 0;
  339. }