diff options
| author | jacqueline <me@jacqueline.id.au> | 2022-10-05 18:25:07 +1100 |
|---|---|---|
| committer | jacqueline <me@jacqueline.id.au> | 2022-10-05 18:25:07 +1100 |
| commit | 6c793efa0d10ac747eab401d354c6f25fd9d66dd (patch) | |
| tree | 5955fc5a2b8c5bc481a2d57dfc5d450103e3aaf8 /main/dac.cpp | |
| parent | 122306d619ae583bbc2d30c39cf98479f83530f5 (diff) | |
| download | tangara-fw-6c793efa0d10ac747eab401d354c6f25fd9d66dd.tar.gz | |
Start on configuring the DAC over i2c
Diffstat (limited to 'main/dac.cpp')
| -rw-r--r-- | main/dac.cpp | 220 |
1 files changed, 220 insertions, 0 deletions
diff --git a/main/dac.cpp b/main/dac.cpp new file mode 100644 index 00000000..625c924a --- /dev/null +++ b/main/dac.cpp @@ -0,0 +1,220 @@ +#include "dac.h" + +#include "esp_log.h" +#include "assert.h" +#include "driver/i2c.h" +#include "gpio-expander.h" +#include "hal/i2c_types.h" +#include <cstdint> + +namespace gay_ipod { + +static const char* TAG = "AUDIODAC"; + +/** + * Utility method for writing to a register on the PCM5122. Writes two bytes: + * first the address for the register that we're writing to, and then the value + * to write. + * + * Note this function assumes that the correct page has already been selected. + */ +static void set_register(i2c_cmd_handle_t handle, uint8_t reg, uint8_t val) { + i2c_master_write_byte(handle, reg, true); + i2c_master_write_byte(handle, val, true); +} + +AudioDac::AudioDac(GpioExpander *gpio) { + this->gpio_ = 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; + } + + // 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; + }; + + ndac = 16; + ncp = 4; + dosr = 384000 / sample_rate; + idac = 98304000 / nmac / sample_rate; // DSP clock / sample rate + } + + // 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; + } + + // 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; + }; + + // We've calculated all of our data. Now assemble the big list of registers + // that we need to configure. + i2c_cmd_handle_t handle = i2c_cmd_link_create(); + if (handle == NULL) { + return; + } + + i2c_master_start(handle); + i2c_master_write_byte(handle, (kPCM5122Address << 1 | I2C_MASTER_WRITE), true); + + // All our registers are on the first page. + set_register(handle, Register::PAGE_SELECT, 0); + + // Disable clock autoset and ignore SCK detection + set_register(handle, Register::IGNORE_ERRORS, 0x1A); + // Set PLL clock source to BCK + set_register(handle, Register::PLL_CLOCK_SOURCE, 0x10); + // Set DAC clock source to PLL output + set_register(handle, Register::DAC_CLOCK_SOURCE, 0x10); + + // Configure PLL + set_register(handle, Register::PLL_P, p - 1); + set_register(handle, Register::PLL_J, j); + set_register(handle, Register::PLL_D_MSB, (d >> 8) & 0x3F); + set_register(handle, Register::PLL_D_LSB, d & 0xFF); + set_register(handle, Register::PLL_R, r - 1); + + // Clock dividers + set_register(handle, Register::DSP_CLOCK_DIV, nmac - 1); + set_register(handle, Register::DAC_CLOCK_DIV, ndac - 1); + set_register(handle, Register::NCP_CLOCK_DIV, ncp - 1); + set_register(handle, Register::OSR_CLOCK_DIV, dosr - 1); + + // IDAC (nb of DSP clock cycles per sample) + set_register(handle, Register::IDAC_MSB, (idac >> 8) & 0xFF); + set_register(handle, Register::IDAC_LSB, idac & 0xFF); + + set_register(handle, Register::FS_SPEED_MODE, speedMode); + set_register(handle, Register::I2S_FORMAT, i2s_format); + + i2c_master_stop(handle); + + // TODO: Handle this gracefully. + ESP_ERROR_CHECK(i2c_master_cmd_begin(I2C_NUM_0, handle, 50)); + + i2c_cmd_link_delete(handle); + + vTaskDelay(pdMS_TO_TICKS(10)); + + // TODO: Handle this gracefully. + assert(ReadPowerState() == 0x05); +} + +uint8_t AudioDac::ReadPowerState() { + i2c_cmd_handle_t handle = i2c_cmd_link_create(); + if (handle == NULL) { + return 0; + } + + uint8_t result = 0; + + i2c_master_start(handle); + i2c_master_write_byte(handle, (kPCM5122Address << 1 | I2C_MASTER_WRITE), true); + i2c_master_write_byte(handle, DSP_BOOT_POWER_STATE, true); + i2c_master_start(handle); + i2c_master_read_byte(handle, &result, I2C_MASTER_NACK); + i2c_master_stop(handle); + + ESP_ERROR_CHECK(i2c_master_cmd_begin(I2C_NUM_0, handle, 50)); + + i2c_cmd_link_delete(handle); + + return result; +} + +void AudioDac::WriteVolume(uint8_t volume) { + i2c_cmd_handle_t handle = i2c_cmd_link_create(); + if (handle == NULL) { + return; + } + + i2c_master_start(handle); + i2c_master_write_byte(handle, (kPCM5122Address << 1 | I2C_MASTER_WRITE), true); + + set_register(handle, Register::DIGITAL_VOLUME_L, volume); + set_register(handle, Register::DIGITAL_VOLUME_R, volume); + + i2c_master_stop(handle); + + i2c_master_cmd_begin(I2C_NUM_0, handle, 50); + + i2c_cmd_link_delete(handle); +} + +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 |
