|
|
|
@ -1,5 +1,6 @@ |
|
|
|
|
#include "dac.h" |
|
|
|
|
|
|
|
|
|
#include "esp_err.h" |
|
|
|
|
#include "i2c.h" |
|
|
|
|
#include "esp_log.h" |
|
|
|
|
#include "assert.h" |
|
|
|
@ -18,214 +19,72 @@ AudioDac::AudioDac(GpioExpander *gpio) { |
|
|
|
|
|
|
|
|
|
AudioDac::~AudioDac() {}; |
|
|
|
|
|
|
|
|
|
void AudioDac::Start(SampleRate sample_rate, BitDepth bit_depth) { |
|
|
|
|
// First check that the arguments look okay.
|
|
|
|
|
|
|
|
|
|
// Check that the bit clock (PLL input) is between 1MHz and 50MHz
|
|
|
|
|
uint32_t bckFreq = sample_rate * bit_depth * 2; |
|
|
|
|
if (bckFreq < 1000000 || bckFreq > 50000000) { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// 24 bits is not supported for 44.1kHz and 48kHz.
|
|
|
|
|
if ((sample_rate == SAMPLE_RATE_44_1K || sample_rate == SAMPLE_RATE_48K) |
|
|
|
|
&& bit_depth == BIT_DEPTH_24) { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Next, wait for the IC to boot up before we try configuring it.
|
|
|
|
|
bool is_booted = false; |
|
|
|
|
for (int i=0; i<10; i++) { |
|
|
|
|
uint8_t result = ReadPowerState(); |
|
|
|
|
is_booted = result >> 7;
|
|
|
|
|
if (is_booted) { |
|
|
|
|
break; |
|
|
|
|
} else { |
|
|
|
|
ESP_LOGI(TAG, "Waiting for boot..."); |
|
|
|
|
vTaskDelay(pdMS_TO_TICKS(1)); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
esp_err_t AudioDac::Start() { |
|
|
|
|
bool is_booted = WaitForPowerState([](bool booted, PowerState state){ return booted; }); |
|
|
|
|
if (!is_booted) { |
|
|
|
|
// TODO: properly handle
|
|
|
|
|
ESP_LOGE(TAG, "Timed out waiting for boot!"); |
|
|
|
|
return; |
|
|
|
|
ESP_LOGE(TAG, "Timed out waiting for boot"); |
|
|
|
|
return ESP_ERR_TIMEOUT; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Now do all the math required to correctly set up the internal clocks.
|
|
|
|
|
int p, j, d, r; |
|
|
|
|
int nmac, ndac, ncp, dosr, idac; |
|
|
|
|
if (sample_rate == SAMPLE_RATE_11_025K || |
|
|
|
|
sample_rate == SAMPLE_RATE_22_05K || |
|
|
|
|
sample_rate == SAMPLE_RATE_44_1K) { |
|
|
|
|
//44.1kHz and derivatives.
|
|
|
|
|
//P = 1, R = 2, D = 0 for all supported combinations.
|
|
|
|
|
//Set J to have PLL clk = 90.3168 MHz
|
|
|
|
|
p = 1; |
|
|
|
|
r = 2; |
|
|
|
|
j = 90316800 / bckFreq / r; |
|
|
|
|
d = 0; |
|
|
|
|
|
|
|
|
|
//Derive clocks from the 90.3168MHz PLL
|
|
|
|
|
nmac = 2; |
|
|
|
|
ndac = 16; |
|
|
|
|
ncp = 4; |
|
|
|
|
dosr = 8; |
|
|
|
|
idac = 1024; // DSP clock / sample rate
|
|
|
|
|
} else { |
|
|
|
|
//8kHz and multiples.
|
|
|
|
|
//PLL config for a 98.304 MHz PLL clk
|
|
|
|
|
if (bit_depth == BIT_DEPTH_24 && bckFreq > 1536000) |
|
|
|
|
p = 3; |
|
|
|
|
else if (bckFreq > 12288000) |
|
|
|
|
p = 2; |
|
|
|
|
else |
|
|
|
|
p = 1; |
|
|
|
|
|
|
|
|
|
r = 2; |
|
|
|
|
j = 98304000 / (bckFreq / p) / r; |
|
|
|
|
d = 0; |
|
|
|
|
|
|
|
|
|
//Derive clocks from the 98.304MHz PLL
|
|
|
|
|
switch (sample_rate) { |
|
|
|
|
case SAMPLE_RATE_16K: nmac = 6; break; |
|
|
|
|
case SAMPLE_RATE_32K: nmac = 3; break; |
|
|
|
|
default: nmac = 2; break; |
|
|
|
|
}; |
|
|
|
|
WriteRegister(Register::DE_EMPHASIS, 1 << 4); |
|
|
|
|
WriteVolume(100); |
|
|
|
|
|
|
|
|
|
WaitForPowerState([](bool booted, PowerState state){ |
|
|
|
|
return state == WAIT_FOR_CP || state == RAMP_UP || state == RUN || state == STANDBY; |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
ndac = 16; |
|
|
|
|
ncp = 4; |
|
|
|
|
dosr = 384000 / sample_rate; |
|
|
|
|
idac = 98304000 / nmac / sample_rate; // DSP clock / sample rate
|
|
|
|
|
return ESP_OK; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// FS speed mode
|
|
|
|
|
int speedMode; |
|
|
|
|
if (sample_rate <= SAMPLE_RATE_48K) { |
|
|
|
|
speedMode = 0; |
|
|
|
|
} else if (sample_rate <= SAMPLE_RATE_96K) { |
|
|
|
|
speedMode = 1; |
|
|
|
|
} else if (sample_rate <= SAMPLE_RATE_192K) { |
|
|
|
|
speedMode = 2; |
|
|
|
|
} else { |
|
|
|
|
speedMode = 3; |
|
|
|
|
void AudioDac::WriteVolume(uint8_t volume) { |
|
|
|
|
WriteRegister(Register::DIGITAL_VOLUME_L, volume); |
|
|
|
|
WriteRegister(Register::DIGITAL_VOLUME_R, volume); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Set correct I2S config
|
|
|
|
|
uint8_t i2s_format = 0; |
|
|
|
|
switch (bit_depth) { |
|
|
|
|
case BIT_DEPTH_16: i2s_format = 0x00; break; |
|
|
|
|
case BIT_DEPTH_24: i2s_format = 0x02; break; |
|
|
|
|
case BIT_DEPTH_32: i2s_format = 0x03; break; |
|
|
|
|
}; |
|
|
|
|
std::pair<bool, AudioDac::PowerState> AudioDac::ReadPowerState() { |
|
|
|
|
uint8_t result = 0; |
|
|
|
|
|
|
|
|
|
// We've calculated all of our data. Now assemble the big list of registers
|
|
|
|
|
// that we need to configure.
|
|
|
|
|
I2CTransaction transaction; |
|
|
|
|
transaction |
|
|
|
|
.start() |
|
|
|
|
.write_addr(kPCM5122Address, I2C_MASTER_WRITE) |
|
|
|
|
// All our registers are on the first page.
|
|
|
|
|
.write_ack(Register::PAGE_SELECT, 0) |
|
|
|
|
// Disable clock autoset and ignore SCK detection
|
|
|
|
|
.write_ack(Register::IGNORE_ERRORS, 0x1A) |
|
|
|
|
// Set PLL clock source to BCK
|
|
|
|
|
.write_ack(Register::PLL_CLOCK_SOURCE, 0x10) |
|
|
|
|
// Set DAC clock source to PLL output
|
|
|
|
|
.write_ack(Register::DAC_CLOCK_SOURCE, 0x10) |
|
|
|
|
|
|
|
|
|
// Configure PLL
|
|
|
|
|
.write_ack(Register::PLL_P, p - 1) |
|
|
|
|
.write_ack(Register::PLL_J, j) |
|
|
|
|
.write_ack(Register::PLL_D_MSB, (d >> 8) & 0x3F) |
|
|
|
|
.write_ack(Register::PLL_D_LSB, d & 0xFF) |
|
|
|
|
.write_ack(Register::PLL_R, r - 1) |
|
|
|
|
|
|
|
|
|
// Clock dividers
|
|
|
|
|
.write_ack(Register::DSP_CLOCK_DIV, nmac - 1) |
|
|
|
|
.write_ack(Register::DAC_CLOCK_DIV, ndac - 1) |
|
|
|
|
.write_ack(Register::NCP_CLOCK_DIV, ncp - 1) |
|
|
|
|
.write_ack(Register::OSR_CLOCK_DIV, dosr - 1) |
|
|
|
|
|
|
|
|
|
// IDAC (nb of DSP clock cycles per sample)
|
|
|
|
|
.write_ack(Register::IDAC_MSB, (idac >> 8) & 0xFF) |
|
|
|
|
.write_ack(Register::IDAC_LSB, idac & 0xFF) |
|
|
|
|
|
|
|
|
|
.write_ack(Register::FS_SPEED_MODE, speedMode) |
|
|
|
|
.write_ack(Register::I2S_FORMAT, i2s_format) |
|
|
|
|
|
|
|
|
|
.write_ack(DSP_BOOT_POWER_STATE) |
|
|
|
|
.start() |
|
|
|
|
.write_addr(kPCM5122Address, I2C_MASTER_READ) |
|
|
|
|
.read(&result, I2C_MASTER_NACK) |
|
|
|
|
.stop(); |
|
|
|
|
|
|
|
|
|
ESP_LOGI(TAG, "Configuring DAC"); |
|
|
|
|
// TODO: Handle this gracefully.
|
|
|
|
|
ESP_ERROR_CHECK(transaction.Execute()); |
|
|
|
|
|
|
|
|
|
// The DAC takes a moment to reconfigure itself. Give it some time before we
|
|
|
|
|
// start asking for its state.
|
|
|
|
|
vTaskDelay(pdMS_TO_TICKS(5)); |
|
|
|
|
bool is_booted = result >> 7; |
|
|
|
|
PowerState detail = (PowerState) (result & 0b1111); |
|
|
|
|
return std::pair(is_booted, detail); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// TODO: investigate why it's stuck waiting for CP voltage.
|
|
|
|
|
/*
|
|
|
|
|
bool is_configured = false; |
|
|
|
|
bool AudioDac::WaitForPowerState(std::function<bool(bool,AudioDac::PowerState)> predicate) { |
|
|
|
|
bool has_matched = false; |
|
|
|
|
for (int i=0; i<10; i++) { |
|
|
|
|
uint8_t result = ReadPowerState(); |
|
|
|
|
is_configured = (result & 0b1111) == 0b1001; |
|
|
|
|
if (is_configured) { |
|
|
|
|
std::pair<bool, PowerState> result = ReadPowerState(); |
|
|
|
|
has_matched = predicate(result.first, result.second); |
|
|
|
|
if (has_matched) { |
|
|
|
|
break; |
|
|
|
|
} else { |
|
|
|
|
ESP_LOGI(TAG, "Waiting for configure..."); |
|
|
|
|
ESP_LOGI(TAG, "Waiting for power state (was %d %x)", result.first, (uint8_t) result.second); |
|
|
|
|
vTaskDelay(pdMS_TO_TICKS(1)); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (!is_configured) { |
|
|
|
|
// TODO: properly handle
|
|
|
|
|
ESP_LOGE(TAG, "Timed out waiting for configure!"); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
*/ |
|
|
|
|
return has_matched; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
uint8_t AudioDac::ReadPowerState() { |
|
|
|
|
uint8_t result = 0; |
|
|
|
|
|
|
|
|
|
void AudioDac::WriteRegister(Register reg, uint8_t val) { |
|
|
|
|
I2CTransaction transaction; |
|
|
|
|
transaction |
|
|
|
|
.start() |
|
|
|
|
.write_addr(kPCM5122Address, I2C_MASTER_WRITE) |
|
|
|
|
.write_ack(DSP_BOOT_POWER_STATE) |
|
|
|
|
.start() |
|
|
|
|
.write_addr(kPCM5122Address, I2C_MASTER_READ) |
|
|
|
|
.read(&result, I2C_MASTER_NACK) |
|
|
|
|
.write_ack(reg, val) |
|
|
|
|
.stop(); |
|
|
|
|
|
|
|
|
|
// TODO: Retry once?
|
|
|
|
|
ESP_ERROR_CHECK(transaction.Execute()); |
|
|
|
|
|
|
|
|
|
return result; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void AudioDac::WriteVolume(uint8_t volume) { |
|
|
|
|
I2CTransaction transaction; |
|
|
|
|
transaction |
|
|
|
|
.start() |
|
|
|
|
.write_addr(kPCM5122Address, I2C_MASTER_WRITE) |
|
|
|
|
.write_ack(Register::DIGITAL_VOLUME_L, volume) |
|
|
|
|
.write_ack(Register::DIGITAL_VOLUME_R, volume) |
|
|
|
|
.stop(); |
|
|
|
|
|
|
|
|
|
ESP_ERROR_CHECK(transaction.Execute()); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void AudioDac::WritePowerMode(PowerMode mode) { |
|
|
|
|
switch (mode) { |
|
|
|
|
case ON: |
|
|
|
|
case STANDBY: |
|
|
|
|
// TODO: enable power switch.
|
|
|
|
|
break; |
|
|
|
|
case OFF: |
|
|
|
|
// TODO: disable power switch.
|
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} // namespace gay_ipod
|
|
|
|
|