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/protocol/i2c.h>
6#include <fbl/algorithm.h>
7#include <fbl/alloc_checker.h>
8
9#include "tas27xx.h"
10
11namespace audio {
12namespace astro {
13
14// static
15fbl::unique_ptr<Tas27xx> Tas27xx::Create(ddk::I2cChannel&& i2c) {
16    if (!i2c.is_valid()) {
17        return nullptr;
18    }
19
20    fbl::AllocChecker ac;
21
22    auto ptr = fbl::unique_ptr<Tas27xx>(new (&ac) Tas27xx());
23    if (!ac.check()) {
24        return nullptr;
25    }
26    ptr->i2c_ = fbl::move(i2c);
27
28    return ptr;
29}
30
31zx_status_t Tas27xx::Reset() {
32    return WriteReg(SW_RESET, 0x01);
33}
34
35zx_status_t Tas27xx::SetGain(float gain) {
36    gain = fbl::clamp(gain, GetMinGain(), GetMaxGain());
37    uint8_t gain_reg = static_cast<uint8_t>(-gain / kGainStep);
38
39    zx_status_t status;
40    status = WriteReg(PB_CFG2, gain_reg);
41    if (status == ZX_OK) {
42        current_gain_ = gain;
43    }
44    return status;
45}
46
47bool Tas27xx::ValidGain(float gain) {
48    return (gain <= kMaxGain) && (gain >= kMinGain);
49}
50
51zx_status_t Tas27xx::Init() {
52    zx_status_t status;
53
54    //Put part in active, but muted state
55    Standby();
56
57    // 128 clocks per frame, manually configure dividers
58    status = WriteReg(CLOCK_CFG, (0x06 << 2) | 1);
59    if (status != ZX_OK)
60        return status;
61
62    // 48kHz, FSYNC on high to low transition
63    // Disable autorate detection
64    status = WriteReg(TDM_CFG0, (1 << 4) | (0x03 << 1) | 1);
65    if (status != ZX_OK)
66        return status;
67
68    // Left justified, offset 0 bclk, clock on falling edge of sclk
69    //  our fsync is on falling edge, so first bit after falling edge is valid
70    status = WriteReg(TDM_CFG1, (0 << 1) | 1);
71    if (status != ZX_OK)
72        return status;
73
74    // Mono (L+R)/2, 32bit sample, 32bit slot
75    status = WriteReg(TDM_CFG2, (0x03 << 4) | (0x00 << 2) | 0x03);
76    if (status != ZX_OK)
77        return status;
78
79    // Left channel slot 0, Right channel slot 1
80    status = WriteReg(TDM_CFG3, (1 << 4) | 0);
81    if (status != ZX_OK)
82        return status;
83
84    // Initial gain -20db
85    SetGain(-20);
86
87    // Disable v and i sense, enter active mode
88    status = WriteReg(PWR_CTL, (0x03 << 2));
89    if (status != ZX_OK)
90        return status;
91
92    return ZX_OK;
93}
94
95//Standby puts the part in active, but muted state
96zx_status_t Tas27xx::Standby() {
97    return WriteReg(PWR_CTL, (0x03 << 2) | 0x01);
98}
99
100zx_status_t Tas27xx::ExitStandby() {
101    return WriteReg(PWR_CTL, (0x03 << 2));
102}
103
104uint8_t Tas27xx::ReadReg(uint8_t reg) {
105    uint8_t val;
106    i2c_.Read(reg, &val, 1);
107    return val;
108}
109
110zx_status_t Tas27xx::WriteReg(uint8_t reg, uint8_t value) {
111    uint8_t write_buf[2];
112    write_buf[0] = reg;
113    write_buf[1] = value;
114    return i2c_.Write(write_buf, 2);
115}
116} //namespace astro
117} //namespace audio
118