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