1 // Copyright 2017 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 <string.h>
6 
7 #include <ddk/protocol/i2c-lib.h>
8 
9 #include <fbl/algorithm.h>
10 
11 #include "tas5760.h"
12 
13 namespace audio {
14 
15 constexpr float Tas5760::kMaxGain;
16 constexpr float Tas5760::kMinGain;
17 // clang-format off
18 constexpr uint8_t kRegPowerControl        = 0x01;
19 constexpr uint8_t kRegDigitalControl      = 0x02;
20 constexpr uint8_t kRegVolumeControlCnf    = 0x03;
21 constexpr uint8_t kRegLeftControl         = 0x04;
22 constexpr uint8_t kRegRightControl        = 0x05;
23 constexpr uint8_t kRegAnalogControl       = 0x06;
24 constexpr uint8_t kRegFaultCfgErrorStatus = 0x08;
25 constexpr uint8_t kRegDigitalClipper2     = 0x10;
26 constexpr uint8_t kRegDigitalClipper1     = 0x11;
27 // clang-format on
28 
29 // static
Create(const pdev_protocol_t & pdev,uint32_t index)30 fbl::unique_ptr<Tas5760> Tas5760::Create(const pdev_protocol_t& pdev, uint32_t index) {
31     size_t actual;
32     i2c_protocol_t i2c;
33     zx_status_t status = pdev_get_protocol(&pdev, ZX_PROTOCOL_I2C, index, &i2c,
34                                            sizeof(i2c_protocol_t), &actual);
35     if (status != ZX_OK) {
36         zxlogf(ERROR, "%s pdev_get_protocol failed %d\n", __FUNCTION__, status);
37         return nullptr;
38     }
39 
40     fbl::AllocChecker ac;
41     auto ptr = fbl::make_unique_checked<Tas5760>(&ac, i2c);
42     if (!ac.check()) {
43         return nullptr;
44     }
45     return ptr;
46 }
47 
Reset()48 zx_status_t Tas5760::Reset() {
49     return ZX_OK;
50 }
51 
SetGain(float gain)52 zx_status_t Tas5760::SetGain(float gain) {
53     gain = fbl::clamp(gain, kMinGain, kMaxGain);
54     // Datasheet: "DVC [Hex Value] = 0xCF + (DVC [dB] / 0.5 [dB] )".
55     uint8_t gain_reg = static_cast<uint8_t>(0xCF + gain / .5f);
56     zx_status_t status = WriteReg(kRegLeftControl, gain_reg);
57     if (status != ZX_OK) {
58         return status;
59     }
60     status = WriteReg(kRegRightControl, gain_reg);
61     if (status != ZX_OK) {
62         return status;
63     }
64     current_gain_ = gain;
65     return status;
66 }
67 
ValidGain(float gain)68 bool Tas5760::ValidGain(float gain) {
69     return (gain <= kMaxGain) && (gain >= kMinGain);
70 }
71 
Init()72 zx_status_t Tas5760::Init() {
73     Standby();
74     WriteReg(kRegDigitalControl, 0x05);   // no HPF, no boost, Single Speed, Stereo Left Justified.
75     WriteReg(kRegVolumeControlCnf, 0x80); // Fade enabled.
76     WriteReg(kRegAnalogControl, 0x51);    // PWM rate 16 x lrclk.
77     WriteReg(kRegDigitalClipper2, 0xFF);  // Disabled.
78     WriteReg(kRegDigitalClipper1, 0xFC);  // Disabled.
79     ExitStandby();
80     uint8_t val = 0;
81     ReadReg(kRegFaultCfgErrorStatus, &val);
82     if (val != 0x00) {
83         return ZX_ERR_INTERNAL;
84     }
85     return ZX_OK;
86 }
87 
Standby()88 zx_status_t Tas5760::Standby() {
89     uint8_t r;
90     ReadReg(kRegPowerControl, &r);
91     r = static_cast<uint8_t>(r & ~(0x01)); // SPK_SD.
92     r = static_cast<uint8_t>(r | (0x02));  // SPK_SLEEP.
93     WriteReg(kRegPowerControl, r);
94     return ZX_OK;
95 }
96 
ExitStandby()97 zx_status_t Tas5760::ExitStandby() {
98     uint8_t r;
99     ReadReg(kRegPowerControl, &r);
100     r = static_cast<uint8_t>(r | 0x01); // SPK_SD.
101     WriteReg(kRegPowerControl, r);
102     r = static_cast<uint8_t>(r & ~(0x02)); // SPK_SLEEP.
103     WriteReg(kRegPowerControl, r);
104     return ZX_OK;
105 }
106 
WriteReg(uint8_t reg,uint8_t value)107 zx_status_t Tas5760::WriteReg(uint8_t reg, uint8_t value) {
108     uint8_t write_buf[2];
109     write_buf[0] = reg;
110     write_buf[1] = value;
111     return i2c_write_sync(&i2c_, write_buf, 2);
112 }
113 
ReadReg(uint8_t reg,uint8_t * value)114 zx_status_t Tas5760::ReadReg(uint8_t reg, uint8_t* value) {
115     return i2c_write_read_sync(&i2c_, &reg, 1, value, 1);
116 }
117 } // audio
118