blob: a0ac035ed5dd513eb1f25e785de5a49cb2e328ad [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <string.h>
#include <ddk/protocol/i2c-lib.h>
#include <ddk/protocol/i2c.h>
#include <fbl/algorithm.h>
#include <fbl/alloc_checker.h>
#include "tlv320adc.h"
namespace audio {
namespace mt8167 {
constexpr float Tlv320adc::kMaxGain;
constexpr float Tlv320adc::kMinGain;
std::unique_ptr<Tlv320adc> Tlv320adc::Create(const ddk::I2cChannel& i2c, uint32_t i2c_index) {
fbl::AllocChecker ac;
auto ptr = std::unique_ptr<Tlv320adc>(new (&ac) Tlv320adc(i2c));
if (!ac.check()) {
return nullptr;
}
return ptr;
}
zx_status_t Tlv320adc::Reset() {
return WriteReg(0, 1, 0x01);
}
zx_status_t Tlv320adc::SetGain(float gain) {
gain = fbl::clamp(gain, kMinGain, kMaxGain);
// TODO(andresoportus): Add fine vol control at reg 82.
uint8_t gain_reg = static_cast<uint8_t>(gain * 2.f) & 0x7F;
zx_status_t status;
status = WriteReg(0, 83, gain_reg); // Left gain.
if (status != ZX_OK) {
return status;
}
status = WriteReg(0, 84, gain_reg); // Right gain.
if (status != ZX_OK) {
return status;
}
current_gain_ = gain;
return status;
}
bool Tlv320adc::ValidGain(float gain) {
return (gain <= kMaxGain) && (gain >= kMinGain);
}
zx_status_t Tlv320adc::Init() {
zx_status_t status = Standby();
uint8_t defaults[][2] = {
// Clocks.
{4, 0x00}, // PLL_CLKIN = MCLK (device pin), CODEC_CLKIN = MCLK (device pin).
// DMCLK (Digital Mic CLK, example range 1.45MHz to 4.8MHz) is based on MCLK, e.g.:
// DMCLK = MCLK 22.5792 MHz (from Aud1) % NADC % MADC = ADC_MOD_CLK (2.8224 MHz).
// DMCLK = MCLK 24.576 MHz (from Aud2) % NADC % MADC = ADC_MOD_CLK (3.072 MHz).
// We need to keep MADC x AOSR > IADC (188 for PRB_R1)
{18, 0x82}, // ADC NADC Clock Divider = 2, enabled.
{19, 0x84}, // ADC MADC Clock Divider = 4, enabled.
{20, 0x40}, // ADC AOSR = 64.
// ADC Audio Interface.
{27, 0x00}, // I2S, 16 bits, BCLK is input, WCLK is input, 3-stating of DOUT disabled.
{28, 0x00}, // Data Slot Offset Programmability 1 (Ch_Offset_1).
{37, 0x00}, // Data Slot Offset Programmability 2 (Ch_Offset_2).
{38, 0x00}, // L+R channels enabled.
// Pins.
{51, 0x28}, // DMCLK/GPIO2 Control, DMCLK out = ADC_MOD_CLK out for the digital microphone.
{52, 0x04}, // DMDIN/GPIO1 Control, DMDIN is in input mode.
// ADC Config.
{61, 0x01}, // ADC Processing Block Selection, PRB_R1.
{80, 0x01}, // ADC Digital-Microphone Polarity Select.
{83, 0x00}, // Left ADC Volume Control, 0dB.
{84, 0x00}, // Right ADC Volume Control, 0dB
{82, 0x00}, // ADC Fine Volume Control. Not muted, gain 0.
};
for (auto& i : defaults) {
status = WriteReg(0, i[0], i[1]);
if (status != ZX_OK) {
return status;
}
}
zx_nanosleep(zx_deadline_after(ZX_MSEC(100))); // To allow ADC clocks to sync, not on datasheet.
status = WriteReg(0, 53, 0x12); // DOUT (OUT Pin) Control bus keeper enabled,
// output for codec interface.
if (status != ZX_OK) {
return status;
}
return ExitStandby();
}
zx_status_t Tlv320adc::Standby() {
return WriteReg(0, 81, 0x02); // ADC Digital. ADC Off, use DMDIN, MIC off, no soft stepping.
}
zx_status_t Tlv320adc::ExitStandby() {
return WriteReg(0, 81, 0xCE); // ADC Digital. ADC On, use DMDIN, MIC on, no soft stepping.
}
zx_status_t Tlv320adc::WriteReg(uint8_t page, uint8_t reg, uint8_t value) {
uint8_t write_buf[2];
write_buf[0] = 0;
write_buf[1] = page;
i2c_.WriteSync(write_buf, 2);
write_buf[0] = reg;
write_buf[1] = value;
return i2c_.WriteSync(write_buf, 2);
}
} // namespace mt8167
} // namespace audio