blob: 976a0959b7db5c3b261bb9eea9d1f649f52f1a8a [file] [log] [blame]
// Copyright 2018 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 "alc5514.h"
#include <endian.h>
#include <lib/ddk/debug.h>
#include <lib/ddk/driver.h>
#include <lib/ddk/platform-defs.h>
#include <lib/device-protocol/i2c.h>
#include <sys/types.h>
#include <zircon/assert.h>
#include <iterator>
#include <memory>
#include <fbl/algorithm.h>
#include <fbl/alloc_checker.h>
#include "alc5514-registers.h"
#include "src/media/audio/drivers/codecs/alc5514/alc5514_bind.h"
namespace audio {
namespace alc5514 {
uint32_t Alc5514Device::ReadReg(uint32_t addr) {
uint32_t buf = htobe32(addr);
uint32_t val = 0;
zx_status_t status = i2c_write_read_sync(&i2c_, &buf, sizeof(buf), &val, sizeof(val));
if (status != ZX_OK) {
zxlogf(ERROR, "alc5514: could not read reg addr: 0x%08x status: %d", addr, status);
return -1;
}
zxlogf(TRACE, "alc5514: register 0x%08x read 0x%08x", addr, betoh32(val));
return betoh32(val);
}
void Alc5514Device::WriteReg(uint32_t addr, uint32_t val) {
uint32_t buf[2];
buf[0] = htobe32(addr);
buf[1] = htobe32(val);
zx_status_t status = i2c_write_sync(&i2c_, buf, sizeof(buf));
if (status != ZX_OK) {
zxlogf(ERROR, "alc5514: could not write reg addr/val: 0x%08x/0x%08x status: %d", addr, val,
status);
}
zxlogf(TRACE, "alc5514: register 0x%08x write 0x%08x", addr, val);
}
void Alc5514Device::UpdateReg(uint32_t addr, uint32_t mask, uint32_t bits) {
uint32_t val = ReadReg(addr);
val = (val & ~mask) | bits;
WriteReg(addr, val);
}
void Alc5514Device::DumpRegs() {
uint32_t REGS[] = {
PWR_ANA1, PWR_ANA2, I2S_CTRL1, I2S_CTRL2, DIG_IO_CTRL,
PAD_CTRL1, DMIC_DATA_CTRL, DIG_SOURCE_CTRL, SRC_ENABLE, CLK_CTRL1,
CLK_CTRL2, ASRC_IN_CTRL, DOWNFILTER0_CTRL1, DOWNFILTER0_CTRL2, DOWNFILTER0_CTRL3,
DOWNFILTER1_CTRL1, DOWNFILTER1_CTRL2, DOWNFILTER1_CTRL3, ANA_CTRL_LDO10, ANA_CTRL_ADCFED,
VERSION_ID, DEVICE_ID,
};
for (uint i = 0; i < std::size(REGS); i++) {
zxlogf(INFO, "%04x: %08x", REGS[i], ReadReg(REGS[i]));
}
}
void Alc5514Device::DdkUnbind(ddk::UnbindTxn txn) { txn.Reply(); }
void Alc5514Device::DdkRelease() { delete this; }
zx_status_t Alc5514Device::Initialize() {
// The device can get confused if the I2C lines glitch together, as can happen
// during bootup as regulators are turned off an on. If it's in this glitched
// state the first i2c read will fail, so give it one chance to retry.
uint32_t device = ReadReg(DEVICE_ID);
if (device != DEVICE_ID_ALC5514) {
device = ReadReg(DEVICE_ID);
}
if (device != DEVICE_ID_ALC5514) {
zxlogf(INFO, "Device ID 0x%08x not supported", device);
return ZX_ERR_NOT_SUPPORTED;
}
// Reset device
WriteReg(RESET, RESET_VALUE);
// GPIO4 = I2S_MCLK
WriteReg(DIG_IO_CTRL, DIG_IO_CTRL_SEL_GPIO4_I2S_MCLK);
// TDM_O_2 source PCM_DATA1_L/R
// TDM_O_1 source PCM_DATA0_L/R
UpdateReg(
SRC_ENABLE, SRC_ENABLE_SRCOUT_1_INPUT_SEL_MASK | SRC_ENABLE_SRCOUT_2_INPUT_SEL_MASK,
SRC_ENABLE_SRCOUT_1_INPUT_SEL_PCM_DATA0_LR | SRC_ENABLE_SRCOUT_2_INPUT_SEL_PCM_DATA1_LR);
// Disable DLDO current limit control after power on
UpdateReg(ANA_CTRL_LDO10, ANA_CTRL_LDO10_DLDO_I_LIMIT_EN, 0);
// Unmute ADC front end L/R channel, set bias current = 3uA
WriteReg(ANA_CTRL_ADCFED, ANA_CTRL_ADCFED_BIAS_CTRL_3UA);
// Enable I2S ASRC clock (mystery bits)
WriteReg(ASRC_IN_CTRL, 0x00000003);
// Eliminate noise in ASRC case if the clock is asynchronous with LRCK (mystery bits)
WriteReg(DOWNFILTER0_CTRL3, 0x10000362);
WriteReg(DOWNFILTER1_CTRL3, 0x10000362);
// Hardcode PCM config
// TDM mode, 8x 16-bit slots, 4 channels, PCM-B
WriteReg(I2S_CTRL1, I2S_CTRL1_MODE_SEL_TDM_MODE | I2S_CTRL1_DATA_FORMAT_PCM_B |
I2S_CTRL1_TDMSLOT_SEL_RX_8CH | I2S_CTRL1_TDMSLOT_SEL_TX_8CH);
WriteReg(I2S_CTRL2, I2S_CTRL2_DOCKING_MODE_ENABLE | I2S_CTRL2_DOCKING_MODE_4CH);
// Set clk_sys_pre to I2S_MCLK
// frequency is 24576000
WriteReg(CLK_CTRL2, CLK_CTRL2_CLK_SYS_PRE_SEL_I2S_MCLK);
// DMIC clock = /8
// ADC1 clk = /3
// clk_sys_div_out = /2
// clk_adc_ana_256fs = /2
UpdateReg(CLK_CTRL1, CLK_CTRL1_CLK_DMIC_OUT_SEL_MASK | CLK_CTRL1_CLK_AD_ANA1_SEL_MASK,
CLK_CTRL1_CLK_DMIC_OUT_SEL_DIV8 | CLK_CTRL1_CLK_AD_ANA1_SEL_DIV3);
UpdateReg(CLK_CTRL2, CLK_CTRL2_CLK_SYS_DIV_OUT_MASK | CLK_CTRL2_SEL_ADC_OSR_MASK,
CLK_CTRL2_CLK_SYS_DIV_OUT_DIV2 | CLK_CTRL2_SEL_ADC_OSR_DIV2);
// Gain value referenced from CrOS
// Set ADC1/ADC2 capture gain to +23.6dB
UpdateReg(DOWNFILTER0_CTRL1, DOWNFILTER_CTRL_AD_AD_GAIN_MASK, 0x6E);
UpdateReg(DOWNFILTER0_CTRL2, DOWNFILTER_CTRL_AD_AD_GAIN_MASK, 0x6E);
UpdateReg(DOWNFILTER1_CTRL1, DOWNFILTER_CTRL_AD_AD_GAIN_MASK, 0x6E);
UpdateReg(DOWNFILTER1_CTRL2, DOWNFILTER_CTRL_AD_AD_GAIN_MASK, 0x6E);
// Power up
constexpr uint32_t pwr1 = PWR_ANA1_POW_CKDET | PWR_ANA1_POW_LDO18_IN | PWR_ANA1_POW_LDO18_ADC |
PWR_ANA1_POW_LDO21 | PWR_ANA1_POW_BG_LDO18 | PWR_ANA1_POW_BG_LDO21;
UpdateReg(PWR_ANA1, pwr1, pwr1);
constexpr uint32_t pwr2 =
PWR_ANA2_POW_PLL2 | PWR_ANA2_POW_PLL2_LDO | PWR_ANA2_POW_PLL1 | PWR_ANA2_POW_PLL1_LDO |
PWR_ANA2_POW_BG_MBIAS | PWR_ANA2_POW_MBIAS | PWR_ANA2_POW_VREF2 | PWR_ANA2_POW_VREF1 |
PWR_ANA2_POWR_LDO16 | PWR_ANA2_POWL_LDO16 | PWR_ANA2_POW_ADC2 | PWR_ANA2_POW_INPUT_BUF |
PWR_ANA2_POW_ADC1_R | PWR_ANA2_POW_ADC1_L | PWR_ANA2_POW2_BSTR | PWR_ANA2_POW2_BSTL |
PWR_ANA2_POW_BSTR | PWR_ANA2_POW_BSTL | PWR_ANA2_POW_ADCFEDR | PWR_ANA2_POW_ADCFEDL;
UpdateReg(PWR_ANA2, pwr2, pwr2);
// Enable DMIC1/2, ADC1, DownFilter0/1 clock
uint32_t clk_enable = CLK_CTRL1_CLK_AD_ANA1_EN | CLK_CTRL1_CLK_DMIC_OUT2_EN |
CLK_CTRL1_CLK_DMIC_OUT1_EN | CLK_CTRL1_CLK_AD1_EN | CLK_CTRL1_CLK_AD0_EN;
UpdateReg(CLK_CTRL1, clk_enable, clk_enable);
// Use tracking clock for DownFilter0/1
UpdateReg(CLK_CTRL2, CLK_CTRL2_AD1_TRACK | CLK_CTRL2_AD0_TRACK,
CLK_CTRL2_AD1_TRACK | CLK_CTRL2_AD0_TRACK);
// Enable path
UpdateReg(DIG_SOURCE_CTRL,
DIG_SOURCE_CTRL_AD1_INPUT_SEL_MASK | DIG_SOURCE_CTRL_AD0_INPUT_SEL_MASK,
DIG_SOURCE_CTRL_AD0_INPUT_SEL_DMIC1 | DIG_SOURCE_CTRL_AD1_INPUT_SEL_DMIC2);
// Unmute DMIC
UpdateReg(DOWNFILTER0_CTRL1, DOWNFILTER_CTRL_AD_DMIC_MIX_MUTE, 0);
UpdateReg(DOWNFILTER0_CTRL2, DOWNFILTER_CTRL_AD_DMIC_MIX_MUTE, 0);
UpdateReg(DOWNFILTER1_CTRL1, DOWNFILTER_CTRL_AD_DMIC_MIX_MUTE, 0);
UpdateReg(DOWNFILTER1_CTRL2, DOWNFILTER_CTRL_AD_DMIC_MIX_MUTE, 0);
// Unmute ADC
UpdateReg(DOWNFILTER0_CTRL1, DOWNFILTER_CTRL_AD_AD_MUTE, 0);
UpdateReg(DOWNFILTER0_CTRL2, DOWNFILTER_CTRL_AD_AD_MUTE, 0);
UpdateReg(DOWNFILTER1_CTRL1, DOWNFILTER_CTRL_AD_AD_MUTE, 0);
UpdateReg(DOWNFILTER1_CTRL2, DOWNFILTER_CTRL_AD_AD_MUTE, 0);
return ZX_OK;
}
zx_status_t Alc5514Device::Bind() {
zx_status_t st = device_get_protocol(parent(), ZX_PROTOCOL_I2C, &i2c_);
if (st != ZX_OK) {
zxlogf(ERROR, "alc5514: could not get I2C protocol: %d", st);
return st;
}
st = Initialize();
if (st != ZX_OK) {
return st;
}
return DdkAdd("alc5514");
}
zx_status_t Alc5514Device::Create(void* ctx, zx_device_t* parent) {
fbl::AllocChecker ac;
std::unique_ptr<Alc5514Device> dev(new (&ac) Alc5514Device(parent));
if (!ac.check()) {
zxlogf(ERROR, "alc5514: out of memory attempting to allocate device");
return ZX_ERR_NO_MEMORY;
}
zx_status_t st = dev->Bind();
if (st == ZX_OK) {
// devmgr is now in charge of the memory for dev
__UNUSED auto* ptr = dev.release();
return st;
}
return ZX_OK;
}
static constexpr zx_driver_ops_t driver_ops = []() {
zx_driver_ops_t ops = {};
ops.version = DRIVER_OPS_VERSION;
ops.bind = Alc5514Device::Create;
return ops;
}();
} // namespace alc5514
} // namespace audio
ZIRCON_DRIVER(alc5514, audio::alc5514::driver_ops, "zircon", "0.1");