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 <ddk/protocol/i2c-lib.h>
8 #include <zircon/device/audio-codec.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
17 namespace audio {
18 namespace alc5514 {
19
ReadReg(uint32_t addr)20 uint32_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
WriteReg(uint32_t addr,uint32_t val)33 void 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
UpdateReg(uint32_t addr,uint32_t mask,uint32_t bits)46 void 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
DumpRegs()52 void 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
DdkIoctl(uint32_t op,const void * in_buf,size_t in_len,void * out_buf,size_t out_len,size_t * actual)82 zx_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
DdkUnbind()87 void Alc5514Device::DdkUnbind() {
88 }
89
DdkRelease()90 void Alc5514Device::DdkRelease() {
91 delete this;
92 }
93
Initialize()94 zx_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 constexpr uint32_t pwr1 = PWR_ANA1_POW_CKDET |
158 PWR_ANA1_POW_LDO18_IN |
159 PWR_ANA1_POW_LDO18_ADC |
160 PWR_ANA1_POW_LDO21 |
161 PWR_ANA1_POW_BG_LDO18 |
162 PWR_ANA1_POW_BG_LDO21;
163 UpdateReg(PWR_ANA1, pwr1, pwr1);
164
165 constexpr uint32_t pwr2 = PWR_ANA2_POW_PLL2 |
166 PWR_ANA2_POW_PLL2_LDO |
167 PWR_ANA2_POW_PLL1 |
168 PWR_ANA2_POW_PLL1_LDO |
169 PWR_ANA2_POW_BG_MBIAS |
170 PWR_ANA2_POW_MBIAS |
171 PWR_ANA2_POW_VREF2 |
172 PWR_ANA2_POW_VREF1 |
173 PWR_ANA2_POWR_LDO16 |
174 PWR_ANA2_POWL_LDO16 |
175 PWR_ANA2_POW_ADC2 |
176 PWR_ANA2_POW_INPUT_BUF |
177 PWR_ANA2_POW_ADC1_R |
178 PWR_ANA2_POW_ADC1_L |
179 PWR_ANA2_POW2_BSTR |
180 PWR_ANA2_POW2_BSTL |
181 PWR_ANA2_POW_BSTR |
182 PWR_ANA2_POW_BSTL |
183 PWR_ANA2_POW_ADCFEDR |
184 PWR_ANA2_POW_ADCFEDL;
185 UpdateReg(PWR_ANA2, pwr2, pwr2);
186
187 // Enable DMIC1/2, ADC1, DownFilter0/1 clock
188 uint32_t clk_enable = CLK_CTRL1_CLK_AD_ANA1_EN |
189 CLK_CTRL1_CLK_DMIC_OUT2_EN |
190 CLK_CTRL1_CLK_DMIC_OUT1_EN |
191 CLK_CTRL1_CLK_AD1_EN |
192 CLK_CTRL1_CLK_AD0_EN;
193 UpdateReg(CLK_CTRL1, clk_enable, clk_enable);
194
195 // Use tracking clock for DownFilter0/1
196 UpdateReg(CLK_CTRL2, CLK_CTRL2_AD1_TRACK | CLK_CTRL2_AD0_TRACK,
197 CLK_CTRL2_AD1_TRACK | CLK_CTRL2_AD0_TRACK);
198
199 // Enable path
200 UpdateReg(DIG_SOURCE_CTRL,
201 DIG_SOURCE_CTRL_AD1_INPUT_SEL_MASK | DIG_SOURCE_CTRL_AD0_INPUT_SEL_MASK,
202 DIG_SOURCE_CTRL_AD0_INPUT_SEL_DMIC1 | DIG_SOURCE_CTRL_AD1_INPUT_SEL_DMIC2);
203
204 // Unmute DMIC
205 UpdateReg(DOWNFILTER0_CTRL1, DOWNFILTER_CTRL_AD_DMIC_MIX_MUTE, 0);
206 UpdateReg(DOWNFILTER0_CTRL2, DOWNFILTER_CTRL_AD_DMIC_MIX_MUTE, 0);
207 UpdateReg(DOWNFILTER1_CTRL1, DOWNFILTER_CTRL_AD_DMIC_MIX_MUTE, 0);
208 UpdateReg(DOWNFILTER1_CTRL2, DOWNFILTER_CTRL_AD_DMIC_MIX_MUTE, 0);
209
210 // Unmute ADC
211 UpdateReg(DOWNFILTER0_CTRL1, DOWNFILTER_CTRL_AD_AD_MUTE, 0);
212 UpdateReg(DOWNFILTER0_CTRL2, DOWNFILTER_CTRL_AD_AD_MUTE, 0);
213 UpdateReg(DOWNFILTER1_CTRL1, DOWNFILTER_CTRL_AD_AD_MUTE, 0);
214 UpdateReg(DOWNFILTER1_CTRL2, DOWNFILTER_CTRL_AD_AD_MUTE, 0);
215
216 return ZX_OK;
217 }
218
Bind()219 zx_status_t Alc5514Device::Bind() {
220 zx_status_t st = device_get_protocol(parent(), ZX_PROTOCOL_I2C, &i2c_);
221 if (st != ZX_OK) {
222 zxlogf(ERROR, "alc5514: could not get I2C protocol: %d\n", st);
223 return st;
224 }
225
226 st = Initialize();
227 if (st != ZX_OK) {
228 return st;
229 }
230
231 return DdkAdd("alc5514");
232 }
233
Create(zx_device_t * parent)234 fbl::unique_ptr<Alc5514Device> Alc5514Device::Create(zx_device_t* parent) {
235 fbl::AllocChecker ac;
236 fbl::unique_ptr<Alc5514Device> ret(new (&ac) Alc5514Device(parent));
237 if (!ac.check()) {
238 zxlogf(ERROR, "alc5514: out of memory attempting to allocate device\n");
239 return nullptr;
240 }
241 return ret;
242 }
243 } // namespace alc5514
244 } // namespace audio
245
246 extern "C" {
alc5514_bind_hook(void * ctx,zx_device_t * parent)247 zx_status_t alc5514_bind_hook(void* ctx, zx_device_t* parent) {
248 auto dev = audio::alc5514::Alc5514Device::Create(parent);
249 if (dev == nullptr) {
250 return ZX_ERR_NO_MEMORY;
251 }
252
253 zx_status_t st = dev->Bind();
254 if (st == ZX_OK) {
255 // devmgr is now in charge of the memory for dev
256 __UNUSED auto ptr = dev.release();
257 return st;
258 }
259
260 return ZX_OK;
261 }
262 } // extern "C"
263