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