1// Copyright 2018 The Fuchsia Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include <ddk/debug.h> 6 7#include <zircon/device/audio-codec.h> 8#include <zircon/device/i2c.h> 9#include <zircon/assert.h> 10 11#include <fbl/algorithm.h> 12#include <fbl/alloc_checker.h> 13 14#include "alc5514.h" 15#include "alc5514-registers.h" 16 17namespace audio { 18namespace alc5514 { 19 20uint32_t Alc5514Device::ReadReg(uint32_t addr) { 21 uint32_t buf = htobe32(addr); 22 uint32_t val = 0; 23 zx_status_t status = i2c_write_read_sync(&i2c_, &buf, sizeof(buf), &val, sizeof(val)); 24 if (status != ZX_OK) { 25 zxlogf(ERROR, "alc5514: could not read reg addr: 0x%08x status: %d\n", addr, status); 26 return -1; 27 } 28 29 zxlogf(SPEW, "alc5514: register 0x%08x read 0x%08x\n", addr, betoh32(val)); 30 return betoh32(val); 31} 32 33void Alc5514Device::WriteReg(uint32_t addr, uint32_t val) { 34 uint32_t buf[2]; 35 buf[0] = htobe32(addr); 36 buf[1] = htobe32(val); 37 zx_status_t status = i2c_write_sync(&i2c_, buf, sizeof(buf)); 38 if (status != ZX_OK) { 39 zxlogf(ERROR, "alc5514: could not write reg addr/val: 0x%08x/0x%08x status: %d\n", addr, 40 val, status); 41 } 42 43 zxlogf(SPEW, "alc5514: register 0x%08x write 0x%08x\n", addr, val); 44} 45 46void Alc5514Device::UpdateReg(uint32_t addr, uint32_t mask, uint32_t bits) { 47 uint32_t val = ReadReg(addr); 48 val = (val & ~mask) | bits; 49 WriteReg(addr, val); 50} 51 52void Alc5514Device::DumpRegs() { 53 uint32_t REGS[] = { 54 PWR_ANA1, 55 PWR_ANA2, 56 I2S_CTRL1, 57 I2S_CTRL2, 58 DIG_IO_CTRL, 59 PAD_CTRL1, 60 DMIC_DATA_CTRL, 61 DIG_SOURCE_CTRL, 62 SRC_ENABLE, 63 CLK_CTRL1, 64 CLK_CTRL2, 65 ASRC_IN_CTRL, 66 DOWNFILTER0_CTRL1, 67 DOWNFILTER0_CTRL2, 68 DOWNFILTER0_CTRL3, 69 DOWNFILTER1_CTRL1, 70 DOWNFILTER1_CTRL2, 71 DOWNFILTER1_CTRL3, 72 ANA_CTRL_LDO10, 73 ANA_CTRL_ADCFED, 74 VERSION_ID, 75 DEVICE_ID, 76 }; 77 for (uint i = 0; i < fbl::count_of(REGS); i++) { 78 zxlogf(INFO, "%04x: %08x\n", REGS[i], ReadReg(REGS[i])); 79 } 80} 81 82zx_status_t Alc5514Device::DdkIoctl(uint32_t op, const void* in_buf, size_t in_len, 83 void* out_buf, size_t out_len, size_t* actual) { 84 return ZX_ERR_NOT_SUPPORTED; 85} 86 87void Alc5514Device::DdkUnbind() { 88} 89 90void Alc5514Device::DdkRelease() { 91 delete this; 92} 93 94zx_status_t Alc5514Device::Initialize() { 95 // The device can get confused if the I2C lines glitch together, as can happen 96 // during bootup as regulators are turned off an on. If it's in this glitched 97 // state the first i2c read will fail, so give it one chance to retry. 98 uint32_t device = ReadReg(DEVICE_ID); 99 if (device != DEVICE_ID_ALC5514) { 100 device = ReadReg(DEVICE_ID); 101 } 102 if (device != DEVICE_ID_ALC5514) { 103 zxlogf(INFO, "Device ID 0x%08x not supported\n", device); 104 return ZX_ERR_NOT_SUPPORTED; 105 } 106 107 // Reset device 108 WriteReg(RESET, RESET_VALUE); 109 110 // GPIO4 = I2S_MCLK 111 WriteReg(DIG_IO_CTRL, DIG_IO_CTRL_SEL_GPIO4_I2S_MCLK); 112 // TDM_O_2 source PCM_DATA1_L/R 113 // TDM_O_1 source PCM_DATA0_L/R 114 UpdateReg(SRC_ENABLE, SRC_ENABLE_SRCOUT_1_INPUT_SEL_MASK | SRC_ENABLE_SRCOUT_2_INPUT_SEL_MASK, 115 SRC_ENABLE_SRCOUT_1_INPUT_SEL_PCM_DATA0_LR | 116 SRC_ENABLE_SRCOUT_2_INPUT_SEL_PCM_DATA1_LR); 117 // Disable DLDO current limit control after power on 118 UpdateReg(ANA_CTRL_LDO10, ANA_CTRL_LDO10_DLDO_I_LIMIT_EN, 0); 119 // Unmute ADC front end L/R channel, set bias current = 3uA 120 WriteReg(ANA_CTRL_ADCFED, ANA_CTRL_ADCFED_BIAS_CTRL_3UA); 121 // Enable I2S ASRC clock (mystery bits) 122 WriteReg(ASRC_IN_CTRL, 0x00000003); 123 // Eliminate noise in ASRC case if the clock is asynchronous with LRCK (mystery bits) 124 WriteReg(DOWNFILTER0_CTRL3, 0x10000362); 125 WriteReg(DOWNFILTER1_CTRL3, 0x10000362); 126 127 // Hardcode PCM config 128 // TDM mode, 8x 16-bit slots, 4 channels, PCM-B 129 WriteReg(I2S_CTRL1, I2S_CTRL1_MODE_SEL_TDM_MODE | 130 I2S_CTRL1_DATA_FORMAT_PCM_B | 131 I2S_CTRL1_TDMSLOT_SEL_RX_8CH | 132 I2S_CTRL1_TDMSLOT_SEL_TX_8CH); 133 WriteReg(I2S_CTRL2, I2S_CTRL2_DOCKING_MODE_ENABLE | 134 I2S_CTRL2_DOCKING_MODE_4CH); 135 136 // Set clk_sys_pre to I2S_MCLK 137 // frequency is 24576000 138 WriteReg(CLK_CTRL2, CLK_CTRL2_CLK_SYS_PRE_SEL_I2S_MCLK); 139 140 // DMIC clock = /8 141 // ADC1 clk = /3 142 // clk_sys_div_out = /2 143 // clk_adc_ana_256fs = /2 144 UpdateReg(CLK_CTRL1, CLK_CTRL1_CLK_DMIC_OUT_SEL_MASK | CLK_CTRL1_CLK_AD_ANA1_SEL_MASK, 145 CLK_CTRL1_CLK_DMIC_OUT_SEL_DIV8 | CLK_CTRL1_CLK_AD_ANA1_SEL_DIV3); 146 UpdateReg(CLK_CTRL2, CLK_CTRL2_CLK_SYS_DIV_OUT_MASK | CLK_CTRL2_SEL_ADC_OSR_MASK, 147 CLK_CTRL2_CLK_SYS_DIV_OUT_DIV2 | CLK_CTRL2_SEL_ADC_OSR_DIV2); 148 149 // Gain value referenced from CrOS 150 // Set ADC1/ADC2 capture gain to +23.6dB 151 UpdateReg(DOWNFILTER0_CTRL1, DOWNFILTER_CTRL_AD_AD_GAIN_MASK, 0x6E); 152 UpdateReg(DOWNFILTER0_CTRL2, DOWNFILTER_CTRL_AD_AD_GAIN_MASK, 0x6E); 153 UpdateReg(DOWNFILTER1_CTRL1, DOWNFILTER_CTRL_AD_AD_GAIN_MASK, 0x6E); 154 UpdateReg(DOWNFILTER1_CTRL2, DOWNFILTER_CTRL_AD_AD_GAIN_MASK, 0x6E); 155 156 // Power up 157 WriteReg(PWR_ANA1, PWR_ANA1_EN_SLEEP_RESET | 158 PWR_ANA1_DMIC_DATA_IN2 | 159 PWR_ANA1_POW_CKDET | 160 PWR_ANA1_POW_PLL | 161 PWR_ANA1_POW_LDO18_IN | 162 PWR_ANA1_POW_LDO18_ADC | 163 PWR_ANA1_POW_LDO21 | 164 PWR_ANA1_POW_BG_LDO18 | 165 PWR_ANA1_POW_BG_LDO21); 166 WriteReg(PWR_ANA2, PWR_ANA2_POW_PLL2 | 167 PWR_ANA2_RSTB_PLL2 | 168 PWR_ANA2_POW_PLL2_LDO | 169 PWR_ANA2_POW_PLL1 | 170 PWR_ANA2_RSTB_PLL1 | 171 PWR_ANA2_POW_PLL1_LDO | 172 PWR_ANA2_POW_BG_MBIAS | 173 PWR_ANA2_POW_MBIAS | 174 PWR_ANA2_POW_VREF2 | 175 PWR_ANA2_POW_VREF1 | 176 PWR_ANA2_POWR_LDO16 | 177 PWR_ANA2_POWL_LDO16 | 178 PWR_ANA2_POW_ADC2 | 179 PWR_ANA2_POW_INPUT_BUF | 180 PWR_ANA2_POW_ADC1_R | 181 PWR_ANA2_POW_ADC1_L | 182 PWR_ANA2_POW2_BSTR | 183 PWR_ANA2_POW2_BSTL | 184 PWR_ANA2_POW_BSTR | 185 PWR_ANA2_POW_BSTL | 186 PWR_ANA2_POW_ADCFEDR | 187 PWR_ANA2_POW_ADCFEDL); 188 189 // Enable DMIC1/2, ADC1, DownFilter0/1 clock 190 uint32_t clk_enable = CLK_CTRL1_CLK_AD_ANA1_EN | 191 CLK_CTRL1_CLK_DMIC_OUT2_EN | 192 CLK_CTRL1_CLK_DMIC_OUT1_EN | 193 CLK_CTRL1_CLK_AD1_EN | 194 CLK_CTRL1_CLK_AD0_EN; 195 UpdateReg(CLK_CTRL1, clk_enable, clk_enable); 196 197 // Use tracking clock for DownFilter0/1 198 UpdateReg(CLK_CTRL2, CLK_CTRL2_AD1_TRACK | CLK_CTRL2_AD0_TRACK, 199 CLK_CTRL2_AD1_TRACK | CLK_CTRL2_AD0_TRACK); 200 201 // Enable path 202 UpdateReg(DIG_SOURCE_CTRL, 203 DIG_SOURCE_CTRL_AD1_INPUT_SEL_MASK | DIG_SOURCE_CTRL_AD0_INPUT_SEL_MASK, 204 DIG_SOURCE_CTRL_AD0_INPUT_SEL_DMIC1 | DIG_SOURCE_CTRL_AD1_INPUT_SEL_DMIC2); 205 206 // Unmute DMIC 207 UpdateReg(DOWNFILTER0_CTRL1, DOWNFILTER_CTRL_AD_DMIC_MIX_MUTE, 0); 208 UpdateReg(DOWNFILTER0_CTRL2, DOWNFILTER_CTRL_AD_DMIC_MIX_MUTE, 0); 209 UpdateReg(DOWNFILTER1_CTRL1, DOWNFILTER_CTRL_AD_DMIC_MIX_MUTE, 0); 210 UpdateReg(DOWNFILTER1_CTRL2, DOWNFILTER_CTRL_AD_DMIC_MIX_MUTE, 0); 211 212 // Unmute ADC 213 UpdateReg(DOWNFILTER0_CTRL1, DOWNFILTER_CTRL_AD_AD_MUTE, 0); 214 UpdateReg(DOWNFILTER0_CTRL2, DOWNFILTER_CTRL_AD_AD_MUTE, 0); 215 UpdateReg(DOWNFILTER1_CTRL1, DOWNFILTER_CTRL_AD_AD_MUTE, 0); 216 UpdateReg(DOWNFILTER1_CTRL2, DOWNFILTER_CTRL_AD_AD_MUTE, 0); 217 218 return ZX_OK; 219} 220 221zx_status_t Alc5514Device::Bind() { 222 zx_status_t st = device_get_protocol(parent(), ZX_PROTOCOL_I2C, &i2c_); 223 if (st != ZX_OK) { 224 zxlogf(ERROR, "alc5514: could not get I2C protocol: %d\n", st); 225 return st; 226 } 227 228 st = Initialize(); 229 if (st != ZX_OK) { 230 return st; 231 } 232 233 return DdkAdd("alc5514"); 234} 235 236fbl::unique_ptr<Alc5514Device> Alc5514Device::Create(zx_device_t* parent) { 237 fbl::AllocChecker ac; 238 fbl::unique_ptr<Alc5514Device> ret(new (&ac) Alc5514Device(parent)); 239 if (!ac.check()) { 240 zxlogf(ERROR, "alc5514: out of memory attempting to allocate device\n"); 241 return nullptr; 242 } 243 return ret; 244} 245} // namespace alc5514 246} // namespace audio 247 248extern "C" { 249zx_status_t alc5514_bind_hook(void* ctx, zx_device_t* parent) { 250 auto dev = audio::alc5514::Alc5514Device::Create(parent); 251 if (dev == nullptr) { 252 return ZX_ERR_NO_MEMORY; 253 } 254 255 zx_status_t st = dev->Bind(); 256 if (st == ZX_OK) { 257 // devmgr is now in charge of the memory for dev 258 __UNUSED auto ptr = dev.release(); 259 return st; 260 } 261 262 return ZX_OK; 263} 264} // extern "C" 265