287 lines
15 KiB
C
287 lines
15 KiB
C
/*
|
|
* es8388.c — ES8388 codec driver (IDF 5.x, legacy I2C driver API).
|
|
*
|
|
* Register map reference: ES8388 datasheet rev 1.6 (Everest Semiconductor).
|
|
* Init sequence derived from:
|
|
* - Espressif esp-adf es8388.c (Apache 2.0)
|
|
* - schreibfaul1/ESP32-audioI2S ES8388 init (MIT)
|
|
* - AI-Thinker SDK AudioKit driver
|
|
*
|
|
* The sequence configures the ES8388 for:
|
|
* - Master clock: MCLK from ESP32 GPIO0 at 12.288 MHz (or 256*Fs at 16kHz)
|
|
* - I2S format: I2S Philips, 16-bit, stereo
|
|
* - DAC: LOUT1/ROUT1 → headphone amp (GPIO21 PA_ENABLE must be HIGH)
|
|
* - ADC: LINPUT1/RINPUT1 mic (differential), gain 24 dB
|
|
* - Sample rate: 16 kHz (MCLKDIV = 256Fs)
|
|
*/
|
|
|
|
#include "es8388.h"
|
|
#include "board_config.h"
|
|
|
|
#include <string.h>
|
|
|
|
#include "driver/i2c.h"
|
|
#include "driver/gpio.h"
|
|
#include "esp_log.h"
|
|
|
|
#define TAG "es8388"
|
|
|
|
/* ── ES8388 register addresses ──────────────────────────────────────────── */
|
|
#define ES8388_CHIP_CTL1 0x00 /* CHIP_CTL1 */
|
|
#define ES8388_CHIP_CTL2 0x01 /* CHIP_CTL2 */
|
|
#define ES8388_CHIP_POWER 0x02 /* CHIP_POWER (ADCPD, DACPD, etc.) */
|
|
#define ES8388_ADC_POWER 0x03 /* ADCPOWER */
|
|
#define ES8388_DAC_POWER 0x04 /* DACPOWER */
|
|
#define ES8388_CHIP_LP 0x05 /* CHIP_LP */
|
|
#define ES8388_CHIP_CTL3 0x06 /* CHIP_CTL3 */
|
|
#define ES8388_ADC_CTL1 0x09 /* ADCCONTROL1 — PGA gain */
|
|
#define ES8388_ADC_CTL2 0x0A /* ADCCONTROL2 — input select (LINSEL/RINSEL) */
|
|
#define ES8388_ADC_CTL3 0x0B /* ADCCONTROL3 — DS/DS filter select */
|
|
#define ES8388_ADC_CTL4 0x0C /* ADCCONTROL4 — I2S format / word length */
|
|
#define ES8388_ADC_CTL5 0x0D /* ADCCONTROL5 — MCLK divider (RATIO=256) */
|
|
#define ES8388_ADC_CTL7 0x0F /* ADCCONTROL7 — HPF enable/config */
|
|
#define ES8388_ADC_CTL8 0x10 /* ADCCONTROL8 — ADC L volume */
|
|
#define ES8388_ADC_CTL9 0x11 /* ADCCONTROL9 — ADC R volume */
|
|
#define ES8388_DAC_CTL1 0x17 /* DACCONTROL1 — I2S word len / format */
|
|
#define ES8388_DAC_CTL2 0x18 /* DACCONTROL2 — MCLK divider */
|
|
#define ES8388_DAC_CTL3 0x19 /* DACCONTROL3 — mute */
|
|
#define ES8388_DAC_CTL4 0x1A /* DACCONTROL4 — LDACVOL */
|
|
#define ES8388_DAC_CTL5 0x1B /* DACCONTROL5 — RDACVOL */
|
|
#define ES8388_DAC_CTL16 0x26 /* DACCONTROL16 — L/R mixer config */
|
|
#define ES8388_DAC_CTL17 0x27 /* DACCONTROL17 — L mixer gain */
|
|
#define ES8388_DAC_CTL20 0x2A /* DACCONTROL20 — R mixer gain */
|
|
#define ES8388_DAC_CTL21 0x2B /* DACCONTROL21 — ADC/DAC LRCK sync (bit7=1 for full-duplex) */
|
|
#define ES8388_DAC_CTL22 0x2C /* DACCONTROL22 — (unused in this config) */
|
|
#define ES8388_DAC_CTL23 0x2D /* DACCONTROL23 — (unused in this config) */
|
|
#define ES8388_DAC_CTL24 0x2E /* DACCONTROL24 — LOUT1VOL (OUT1 left volume) */
|
|
#define ES8388_DAC_CTL25 0x2F /* DACCONTROL25 — ROUT1VOL (OUT1 right volume) */
|
|
#define ES8388_DAC_CTL26 0x30 /* DACCONTROL26 — LOUT2VOL (OUT2 left volume) */
|
|
|
|
/* Volume register: 0x00=0dB (max), 0x21=-33dB step per 1.5dB, 0x24=mute. */
|
|
#define ES8388_VOL_MAX 0x00
|
|
#define ES8388_VOL_0DB 0x00
|
|
#define ES8388_VOL_MUTE 0x24
|
|
|
|
/* ── I2C helpers ─────────────────────────────────────────────────────────── */
|
|
|
|
static esp_err_t i2c_write_reg(uint8_t reg, uint8_t value)
|
|
{
|
|
uint8_t buf[2] = { reg, value };
|
|
esp_err_t ret = i2c_master_write_to_device(PLIP_I2C_PORT,
|
|
PLIP_ES8388_ADDR,
|
|
buf, sizeof(buf),
|
|
pdMS_TO_TICKS(100));
|
|
if (ret != ESP_OK) {
|
|
ESP_LOGE(TAG, "write reg 0x%02X=0x%02X failed: %s",
|
|
reg, value, esp_err_to_name(ret));
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
esp_err_t es8388_read_reg(uint8_t reg, uint8_t *value)
|
|
{
|
|
return i2c_master_write_read_device(PLIP_I2C_PORT,
|
|
PLIP_ES8388_ADDR,
|
|
®, 1,
|
|
value, 1,
|
|
pdMS_TO_TICKS(100));
|
|
}
|
|
|
|
esp_err_t es8388_write_reg(uint8_t reg, uint8_t value)
|
|
{
|
|
return i2c_write_reg(reg, value);
|
|
}
|
|
|
|
/* ── Public API ──────────────────────────────────────────────────────────── */
|
|
|
|
esp_err_t es8388_init(void)
|
|
{
|
|
/* Initialise I2C master on the A1S bus. */
|
|
i2c_config_t conf = {
|
|
.mode = I2C_MODE_MASTER,
|
|
.sda_io_num = PLIP_I2C_SDA,
|
|
.scl_io_num = PLIP_I2C_SCL,
|
|
.sda_pullup_en = GPIO_PULLUP_ENABLE,
|
|
.scl_pullup_en = GPIO_PULLUP_ENABLE,
|
|
.master.clk_speed = PLIP_I2C_FREQ_HZ,
|
|
};
|
|
esp_err_t ret = i2c_param_config(PLIP_I2C_PORT, &conf);
|
|
if (ret != ESP_OK) return ret;
|
|
ret = i2c_driver_install(PLIP_I2C_PORT, conf.mode, 0, 0, 0);
|
|
if (ret != ESP_OK && ret != ESP_ERR_INVALID_STATE) {
|
|
/* ESP_ERR_INVALID_STATE means the driver is already installed — ok. */
|
|
ESP_LOGE(TAG, "i2c_driver_install: %s", esp_err_to_name(ret));
|
|
return ret;
|
|
}
|
|
|
|
/* Quick device presence check. */
|
|
uint8_t chip_id = 0;
|
|
ret = es8388_read_reg(ES8388_CHIP_CTL1, &chip_id);
|
|
if (ret != ESP_OK) {
|
|
ESP_LOGE(TAG, "ES8388 not found on I2C bus (addr=0x%02X): %s",
|
|
PLIP_ES8388_ADDR, esp_err_to_name(ret));
|
|
return ret;
|
|
}
|
|
ESP_LOGI(TAG, "ES8388 detected: CHIP_CTL1=0x%02X", chip_id);
|
|
|
|
/* ── Full power-up + init sequence ─────────────────────────────────── */
|
|
|
|
/* 1. Reset. */
|
|
if (i2c_write_reg(ES8388_CHIP_CTL1, 0x80) != ESP_OK) return ESP_FAIL;
|
|
vTaskDelay(pdMS_TO_TICKS(20));
|
|
if (i2c_write_reg(ES8388_CHIP_CTL1, 0x00) != ESP_OK) return ESP_FAIL;
|
|
|
|
/* 2. Power down all blocks first (prevents pop on line-up). */
|
|
if (i2c_write_reg(ES8388_CHIP_POWER, 0xFF) != ESP_OK) return ESP_FAIL;
|
|
|
|
/* 3. CONTROL1: WORK_MODE = record+playback (0x10), VMIDSEL=10 (0x02).
|
|
* 0x12 = 0b00010010 — enables both ADC and DAC paths.
|
|
* Previous value 0x05 only enabled playback (WORK_MODE=00). */
|
|
if (i2c_write_reg(ES8388_CHIP_CTL1, 0x12) != ESP_OK) return ESP_FAIL;
|
|
|
|
/* 3b. CONTROL2 (0x01): VROI=0, LPVrefBuf=0, normal operation. */
|
|
if (i2c_write_reg(ES8388_CHIP_CTL2, 0x50) != ESP_OK) return ESP_FAIL;
|
|
|
|
/* 3c. Internal DLL stabilisation for LOW sample rates (16 kHz). UNDOCUMENTED
|
|
* registers 0x35/0x37/0x39 — the proven A252 driver (hardware/projects/
|
|
* slic-phone Es8388Driver.cpp) sets these to "disable internal DLL for low
|
|
* sample-rate stability". WITHOUT them the ADC clock domain is unstable at
|
|
* 16 kHz and the ADC outputs a frozen DC value (capture = flat, no AC) even
|
|
* though analog signal is present on the LIN pin — exactly our symptom. The
|
|
* DAC tolerates it, which is why playback worked but capture didn't. They
|
|
* must be set here in the boot sequence (a live poke can't re-lock the DLL). */
|
|
if (i2c_write_reg(0x35, 0xA0) != ESP_OK) return ESP_FAIL;
|
|
if (i2c_write_reg(0x37, 0xD0) != ESP_OK) return ESP_FAIL;
|
|
if (i2c_write_reg(0x39, 0xD0) != ESP_OK) return ESP_FAIL;
|
|
|
|
/* 4. Clock: MCLK divider = 256*Fs, DAC SRC = MCLK. */
|
|
if (i2c_write_reg(0x08, 0x00) != ESP_OK) return ESP_FAIL; /* MASTERMODE = slave */
|
|
|
|
/* 5. ADC power: power down to allow clean register writes. */
|
|
if (i2c_write_reg(ES8388_ADC_POWER, 0xFF) != ESP_OK) return ESP_FAIL;
|
|
|
|
/* 6. ADC config (follows Espressif esp-codec-dev reference sequence):
|
|
* - ADCCONTROL1 (0x09) = 0xBB: MIC PGA +24dB L and R
|
|
* - ADCCONTROL2 (0x0A) = 0x50: LINSEL=10 LIN2/RIN2 (telephone handset mic)
|
|
* Value 0x00 = LIN1 differential, 0x50 = LIN2 single-ended (A1S combiné)
|
|
* - ADCCONTROL3 (0x0B) = 0x02: DS filter select (required by reference)
|
|
* - ADCCONTROL4 (0x0C) = 0x0C: I2S Philips 16-bit word length
|
|
* - ADCCONTROL5 (0x0D) = 0x02: ADCFsMode SINGLE SPEED RATIO=256 (16kHz@MCLK 4.096MHz)
|
|
* - ADCCONTROL8/9 (0x10/0x11) = 0x00: ADC digital volume 0dB */
|
|
if (i2c_write_reg(ES8388_ADC_CTL1, 0x44) != ESP_OK) return ESP_FAIL; /* MIC PGA +12dB L+R (was +24dB: too hot for a close handset mic → clipping/feedback) */
|
|
/* ADCCONTROL2 (0x0A): input select. The K50835F SLIC handset transmit audio is
|
|
* wired to LIN2/RIN2 on this bench — PROVEN: speech captured (ACrms 196, crest 10.3)
|
|
* on 0x50, vs DC-only floating offset on 0x00 (LIN1). */
|
|
if (i2c_write_reg(ES8388_ADC_CTL2, 0x50) != ESP_OK) return ESP_FAIL; /* LIN2/RIN2 — SLIC handset mic */
|
|
if (i2c_write_reg(ES8388_ADC_CTL3, 0x02) != ESP_OK) return ESP_FAIL; /* DS filter sel */
|
|
if (i2c_write_reg(ES8388_ADC_CTL4, 0x0C) != ESP_OK) return ESP_FAIL; /* I2S 16-bit */
|
|
if (i2c_write_reg(ES8388_ADC_CTL5, 0x02) != ESP_OK) return ESP_FAIL; /* RATIO=256 */
|
|
if (i2c_write_reg(0x0E, 0x00) != ESP_OK) return ESP_FAIL; /* ADCCONTROL6: clear ADCSMUTE bit5 (reset default 0x30 = ADC output muted) */
|
|
if (i2c_write_reg(ES8388_ADC_CTL8, 0x00) != ESP_OK) return ESP_FAIL; /* ADC vol L 0dB */
|
|
if (i2c_write_reg(ES8388_ADC_CTL9, 0x00) != ESP_OK) return ESP_FAIL; /* ADC vol R 0dB */
|
|
|
|
/* 8. DAC I2S: 16-bit I2S Philips, MCLK/256, no softmute. */
|
|
if (i2c_write_reg(ES8388_DAC_CTL1, 0x18) != ESP_OK) return ESP_FAIL;
|
|
if (i2c_write_reg(ES8388_DAC_CTL2, 0x02) != ESP_OK) return ESP_FAIL; /* DACLRCKDIV=256 */
|
|
if (i2c_write_reg(ES8388_DAC_CTL3, 0x00) != ESP_OK) return ESP_FAIL; /* mute off */
|
|
|
|
/* 9. DAC volume: 0 dB on both channels. */
|
|
if (i2c_write_reg(ES8388_DAC_CTL4, ES8388_VOL_0DB) != ESP_OK) return ESP_FAIL;
|
|
if (i2c_write_reg(ES8388_DAC_CTL5, ES8388_VOL_0DB) != ESP_OK) return ESP_FAIL;
|
|
|
|
/* 10. Mixer: L→LOUT, R→ROUT (straight through, no cross-mix). */
|
|
if (i2c_write_reg(ES8388_DAC_CTL16, 0x1B) != ESP_OK) return ESP_FAIL;
|
|
if (i2c_write_reg(ES8388_DAC_CTL17, 0x90) != ESP_OK) return ESP_FAIL;
|
|
if (i2c_write_reg(ES8388_DAC_CTL20, 0x90) != ESP_OK) return ESP_FAIL;
|
|
|
|
/* 11. DACCONTROL21 (0x2B): ADC+DAC LRCK sync.
|
|
* Espressif reference es8388_start():
|
|
* 0xC0 = LINE mode (analog bypass, not ADC digital path)
|
|
* 0x80 = DAC+ADC digital record+playback mode (bit7 only)
|
|
* We want digital ADC recording + DAC playback → use 0x80. */
|
|
if (i2c_write_reg(ES8388_DAC_CTL21, 0x80) != ESP_OK) return ESP_FAIL;
|
|
|
|
/* 11b. Restart internal state machine (required after DACCONTROL21 change).
|
|
* Without this pulse, the ADC clock domain may not synchronise properly.
|
|
* Espressif reference sequence: CHIPPOWER=0xF0 then 0x00 to restart FSM. */
|
|
if (i2c_write_reg(ES8388_CHIP_POWER, 0xF0) != ESP_OK) return ESP_FAIL;
|
|
vTaskDelay(pdMS_TO_TICKS(5));
|
|
if (i2c_write_reg(ES8388_CHIP_POWER, 0x00) != ESP_OK) return ESP_FAIL;
|
|
vTaskDelay(pdMS_TO_TICKS(10));
|
|
|
|
/* DACCONTROL23 (0x2D): VROI=0, normal output. */
|
|
if (i2c_write_reg(ES8388_DAC_CTL23, 0x00) != ESP_OK) return ESP_FAIL;
|
|
|
|
/* 12. Volume: OUT1L/R = 0x1E (0dB), OUT2L = 0 (speaker vol set by PA separately). */
|
|
if (i2c_write_reg(ES8388_DAC_CTL24, 0x1E) != ESP_OK) return ESP_FAIL; /* LOUT1VOL */
|
|
if (i2c_write_reg(ES8388_DAC_CTL25, 0x1E) != ESP_OK) return ESP_FAIL; /* ROUT1VOL */
|
|
if (i2c_write_reg(ES8388_DAC_CTL26, 0x00) != ESP_OK) return ESP_FAIL; /* LOUT2VOL */
|
|
|
|
/* 13. DAC power: power up DAC L+R. */
|
|
if (i2c_write_reg(ES8388_DAC_POWER, 0x3C) != ESP_OK) return ESP_FAIL;
|
|
|
|
/* 14. ADC power: full power-up (all Pdn bits cleared = 0x00).
|
|
* 0x09 is the intermediate state (Espressif es8388_open end-state), but
|
|
* the full ADC + analog input power-up requires 0x00 (Espressif es8388_start(ADC)).
|
|
* Must come AFTER DACCONTROL21 state machine restart and ADCCONTROL6 unmute. */
|
|
if (i2c_write_reg(ES8388_ADC_POWER, 0x00) != ESP_OK) return ESP_FAIL;
|
|
vTaskDelay(pdMS_TO_TICKS(10)); /* let ADC analog settle after full power-up */
|
|
|
|
/* 15. Enable PA (power amplifier for speaker). */
|
|
gpio_set_direction(PLIP_PA_ENABLE, GPIO_MODE_OUTPUT);
|
|
gpio_set_level(PLIP_PA_ENABLE, 1);
|
|
|
|
/* Verify key register values to confirm ADC path is configured correctly. */
|
|
uint8_t ctl1=0, adcpwr=0, adcinsel=0, dacctl21=0, chippower=0, adcctl3=0;
|
|
es8388_read_reg(ES8388_CHIP_CTL1, &ctl1);
|
|
es8388_read_reg(ES8388_ADC_POWER, &adcpwr);
|
|
es8388_read_reg(ES8388_ADC_CTL2, &adcinsel); /* ADCCONTROL2 = input sel (0x50=LIN2) */
|
|
es8388_read_reg(ES8388_ADC_CTL3, &adcctl3); /* ADCCONTROL3 = DS filter (0x02) */
|
|
es8388_read_reg(ES8388_DAC_CTL21, &dacctl21); /* DACCONTROL21 = ADC+DAC LRCK sync (0xC0) */
|
|
es8388_read_reg(ES8388_CHIP_POWER, &chippower);
|
|
ESP_LOGI(TAG, "ES8388 regs: CTL1=0x%02X ADCPWR=0x%02X ADCINSEL=0x%02X ADCCTL3=0x%02X DACCTL21=0x%02X CHIPPOWER=0x%02X",
|
|
ctl1, adcpwr, adcinsel, adcctl3, dacctl21, chippower);
|
|
ESP_LOGI(TAG, "ES8388 init OK — PA enabled, DAC @ 0dB, ADC PGA +12dB, input=LIN2/RIN2 (SLIC handset), DACCTL21=0x80");
|
|
return ESP_OK;
|
|
}
|
|
|
|
esp_err_t es8388_set_volume(uint8_t vol)
|
|
{
|
|
/* ES8388 OUTx volume registers are GAIN, not attenuation: 0x00 = -45 dB
|
|
* (min) .. 0x21 = 0 dB (max); higher value = louder (>0x21 = mute/reserved).
|
|
* Map 0..100 → 0x00..0x21. (The previous code inverted this, so vol=100
|
|
* produced 0x00 = quietest — confirmed at the bench.)
|
|
* DACCONTROL24 (0x2E)=OUT1L, 25 (0x2F)=OUT1R, 26 (0x30)=OUT2L, 27 (0x31)=OUT2R.
|
|
* DACCONTROL21 (0x2B) is the ADC/DAC LRCK sync register — DO NOT touch here. */
|
|
if (vol > 100) vol = 100;
|
|
uint8_t reg_val = (uint8_t)((int)vol * 0x21 / 100);
|
|
if (reg_val > 0x21) reg_val = 0x21;
|
|
ESP_LOGI(TAG, "set_volume: %d%% -> reg=0x%02X (0x21=max,0dB)", vol, reg_val);
|
|
esp_err_t r = ESP_OK;
|
|
r |= i2c_write_reg(ES8388_DAC_CTL24, reg_val); /* OUT1 L volume */
|
|
r |= i2c_write_reg(ES8388_DAC_CTL25, reg_val); /* OUT1 R volume */
|
|
r |= i2c_write_reg(ES8388_DAC_CTL26, reg_val); /* OUT2 L volume */
|
|
r |= i2c_write_reg(0x31, reg_val); /* OUT2 R volume (DACCONTROL27) */
|
|
return r;
|
|
}
|
|
|
|
esp_err_t es8388_set_dac_volume(uint8_t atten)
|
|
{
|
|
/* DACCONTROL4 (0x04) = LDACVOL, DACCONTROL5 (0x05) = RDACVOL: DIGITAL DAC
|
|
* volume, applied BEFORE the analog output stages. 0x00 = 0 dB, each step
|
|
* = -0.5 dB, up to 0xC0 = -96 dB (mute). Lowering this gives analog
|
|
* headroom while keeping the output-stage volume (es8388_set_volume) high. */
|
|
if (atten > 0xC0) atten = 0xC0;
|
|
ESP_LOGI(TAG, "set_dac_volume: atten=0x%02X (-%.1f dB)", atten, atten * 0.5f);
|
|
esp_err_t r = i2c_write_reg(ES8388_DAC_CTL4, atten);
|
|
r |= i2c_write_reg(ES8388_DAC_CTL5, atten);
|
|
return r;
|
|
}
|
|
|
|
esp_err_t es8388_mute(bool mute)
|
|
{
|
|
uint8_t val = mute ? 0x04 : 0x00; /* bit2 = DACMUTE */
|
|
ESP_LOGI(TAG, "mute: %s", mute ? "on" : "off");
|
|
return i2c_write_reg(ES8388_DAC_CTL3, val);
|
|
}
|