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#include <fbl/limits.h>
7#include <soc/aml-common/aml-tdm-audio.h>
8
9//static
10fbl::unique_ptr<AmlTdmDevice> AmlTdmDevice::Create(ddk::MmioBuffer mmio,
11                                                   ee_audio_mclk_src_t src,
12                                                   aml_tdm_out_t tdm_dev,
13                                                   aml_frddr_t frddr_dev,
14                                                   aml_tdm_mclk_t mclk) {
15
16    //A and B FRDDR have 128 lines in fifo, C has 256
17    uint32_t fifo_depth = 128;
18    if (frddr_dev == FRDDR_C) {
19        fifo_depth = 256;
20    }
21
22    fbl::AllocChecker ac;
23    auto tdm = fbl::unique_ptr<AmlTdmDevice>(new (&ac)
24        AmlTdmDevice(fbl::move(mmio), src, tdm_dev, frddr_dev, mclk, fifo_depth));
25    if (!ac.check()) {
26        return nullptr;
27    }
28
29    tdm->InitRegs();
30
31    return tdm;
32}
33
34void AmlTdmDevice::InitRegs() {
35    //Enable the audio domain clocks used by this instance.
36    AudioClkEna((EE_AUDIO_CLK_GATE_TDMOUTA << tdm_ch_) |
37                (EE_AUDIO_CLK_GATE_FRDDRA << frddr_ch_) |
38                EE_AUDIO_CLK_GATE_ARB);
39
40    //Set chosen mclk channels input to selected source
41    //Since this is init, set the divider to max value assuming it will
42    //    be set to proper value later (slower is safer from circuit standpoint)
43    //Leave disabled for now.
44    zx_off_t ptr = EE_AUDIO_MCLK_A_CTRL + (mclk_ch_ * sizeof(uint32_t));
45    mmio_.Write32((clk_src_ << 24) | 0xffff, ptr);
46
47    //Set the sclk and lrclk sources to the chosen mclk channel
48    ptr = EE_AUDIO_CLK_TDMOUT_A_CTL + tdm_ch_ * sizeof(uint32_t);
49    mmio_.Write32((0x03 << 30) | (mclk_ch_ << 24) | (mclk_ch_ << 20), ptr);
50
51    //Enable DDR ARB, and enable this ddr channels bit.
52    mmio_.SetBits32((1 << 31) | (1 << (4 + frddr_ch_)), EE_AUDIO_ARB_CTRL);
53
54    //Disable the FRDDR Channel
55    //Only use one buffer
56    //Interrupts off
57    //ack delay = 0
58    //set destination tdm block and enable that selection
59    mmio_.Write32(tdm_ch_ | (1 << 3), GetFrddrOffset(FRDDR_CTRL0_OFFS));
60    //use entire fifo, start transfer request when fifo is at 1/2 full
61    //set the magic force end bit(12) to cause fetch from start
62    //    -this only happens when the bit is set from 0->1 (edge)
63    mmio_.Write32((1 << 12) | ((fifo_depth_ -1) << 24) | (((fifo_depth_ / 2) - 1) << 16),
64        GetFrddrOffset(FRDDR_CTRL1_OFFS));
65
66    //Value to be inserted in a slot if it is muted
67    mmio_.Write32(0x00000000, GetTdmOffset(TDMOUT_MUTE_VAL_OFFS));
68    //Value to be inserted in a slot if it is masked
69    mmio_.Write32(0x00000000, GetTdmOffset(TDMOUT_MASK_VAL_OFFS));
70}
71
72/* Notes
73    -div is desired divider minus 1. (want /100? write 99)
74*/
75zx_status_t AmlTdmDevice::SetMclkDiv(uint32_t div) {
76    //check that divider is in range
77    ZX_DEBUG_ASSERT(div < (1 << kMclkDivBits));
78
79    zx_off_t ptr = EE_AUDIO_MCLK_A_CTRL + (mclk_ch_ * sizeof(uint32_t));
80    //disable and clear out old divider value
81    mmio_.ClearBits32((1 << 31) | ((1 << kMclkDivBits) - 1), ptr);
82
83    mmio_.SetBits32((1 << 31) | (div & ((1 << kMclkDivBits) - 1)), ptr);
84    return ZX_OK;
85}
86
87uint32_t AmlTdmDevice::GetRingPosition() {
88    return mmio_.Read32(GetFrddrOffset(FRDDR_STATUS2_OFFS)) -
89           mmio_.Read32(GetFrddrOffset(FRDDR_START_ADDR_OFFS));
90}
91/* Notes:
92    -sdiv is desired divider -1 (Want a divider of 10? write a value of 9)
93    -sclk needs to be at least 2x mclk.  writing a value of 0 (/1) to sdiv
94        will result in no sclk being generated on the sclk pin.  However, it
95        appears that it is running properly as a lrclk is still generated at
96        an expected rate (lrclk is derived from sclk)
97*/
98zx_status_t AmlTdmDevice::SetSclkDiv(uint32_t sdiv,
99                                     uint32_t lrduty,
100                                     uint32_t lrdiv) {
101    ZX_DEBUG_ASSERT(sdiv < (1 << kSclkDivBits));
102    ZX_DEBUG_ASSERT(lrdiv < (1 << kLRclkDivBits));
103    //lrduty is in sclk cycles, so must be less than lrdiv
104    ZX_DEBUG_ASSERT(lrduty < lrdiv);
105
106    zx_off_t ptr = EE_AUDIO_MST_A_SCLK_CTRL0 + (2 * mclk_ch_ * sizeof(uint32_t));
107    mmio_.Write32((0x3 << 30) |    //Enable the channel
108                  (sdiv << 20) |   //sclk divider sclk=mclk/sdiv
109                  (lrduty << 10) | //lrclk duty cycle in sclk cycles
110                  (lrdiv << 0),    //lrclk = sclk/lrdiv
111                  ptr);
112    mmio_.Write32(0, ptr + sizeof(uint32_t)); //Clear delay lines for phases
113    return ZX_OK;
114}
115
116void AmlTdmDevice::AudioClkEna(uint32_t audio_blk_mask) {
117    mmio_.SetBits32(audio_blk_mask, EE_AUDIO_CLK_GATE_EN);
118}
119
120void AmlTdmDevice::AudioClkDis(uint32_t audio_blk_mask) {
121    mmio_.ClearBits32(audio_blk_mask, EE_AUDIO_CLK_GATE_EN);
122}
123
124zx_status_t AmlTdmDevice::SetBuffer(zx_paddr_t buf, size_t len) {
125    //Ensure ring buffer resides in lower memory (dma pointers are 32-bit)
126    //    and len is at least 8 (size of each dma operation)
127    if (((buf + len - 1) > fbl::numeric_limits<uint32_t>::max()) || (len < 8)) {
128        return ZX_ERR_INVALID_ARGS;
129    }
130
131    //Write32 the start and end pointers.  Each fetch is 64-bits, so end poitner
132    //    is pointer to the last 64-bit fetch (inclusive)
133    mmio_.Write32(static_cast<uint32_t>(buf), GetFrddrOffset(FRDDR_START_ADDR_OFFS));
134    mmio_.Write32(static_cast<uint32_t>(buf + len - 8),
135                GetFrddrOffset(FRDDR_FINISH_ADDR_OFFS));
136    return ZX_OK;
137}
138
139/*
140    bit_offset - bit position in frame where first slot will appear
141                    (position 0 is concurrent with frame sync)
142    num_slots - number of slots per frame minus one
143    bits_per_slot - width of each slot minus one
144    bits_per_sample - number of bits in sample minus one
145*/
146void AmlTdmDevice::ConfigTdmOutSlot(uint8_t bit_offset, uint8_t num_slots,
147                                    uint8_t bits_per_slot, uint8_t bits_per_sample) {
148
149    uint32_t reg = bits_per_slot | (num_slots << 5) | (bit_offset << 15);
150    mmio_.Write32(reg, GetTdmOffset(TDMOUT_CTRL0_OFFS));
151
152    reg = (bits_per_sample << 8) | (frddr_ch_ << 24);
153    if (bits_per_sample <= 8) {
154        // 8 bit sample, left justify in frame, split 64-bit dma fetch into 8 samples
155        reg |= (0 << 4);
156    } else if (bits_per_sample <= 16) {
157        // 16 bit sample, left justify in frame, split 64-bit dma fetch into 2 samples
158        reg |= (2 << 4);
159    } else {
160        // 32/24 bit sample, left justify in slot, split 64-bit dma fetch into 2 samples
161        reg |= (4 << 4);
162    }
163    mmio_.Write32(reg, GetTdmOffset(TDMOUT_CTRL1_OFFS));
164
165    // assign left ch to slot 1, right to slot 1
166    mmio_.Write32(0x00000010, GetTdmOffset(TDMOUT_SWAP_OFFS));
167    // unmask first two slots
168    mmio_.Write32(0x00000003, GetTdmOffset(TDMOUT_MASK0_OFFS));
169}
170
171// Stops the tdm from clocking data out of fifo onto bus
172void AmlTdmDevice::TdmOutDisable() {
173    mmio_.ClearBits32(1 << 31, GetTdmOffset(TDMOUT_CTRL0_OFFS));
174}
175// Enables the tdm to clock data out of fifo onto bus
176void AmlTdmDevice::TdmOutEnable() {
177    mmio_.SetBits32(1 << 31, GetTdmOffset(TDMOUT_CTRL0_OFFS));
178}
179
180void AmlTdmDevice::FRDDREnable() {
181    //Set the load bit, will make sure things start from beginning of buffer
182    mmio_.SetBits32(1 << 12, GetFrddrOffset(FRDDR_CTRL1_OFFS));
183    mmio_.SetBits32(1 << 31, GetFrddrOffset(FRDDR_CTRL0_OFFS));
184}
185
186void AmlTdmDevice::FRDDRDisable() {
187    // Clear the load bit (this is the bit that forces the initial fetch of
188    //    start address into current ptr)
189    mmio_.ClearBits32(1 << 12, GetFrddrOffset(FRDDR_CTRL1_OFFS));
190    // Disable the frddr channel
191    mmio_.ClearBits32(1 << 31, GetFrddrOffset(FRDDR_CTRL0_OFFS));
192}
193
194void AmlTdmDevice::Sync() {
195    mmio_.ClearBits32(3 << 28, GetTdmOffset(TDMOUT_CTRL0_OFFS));
196    mmio_.SetBits32(1 << 29, GetTdmOffset(TDMOUT_CTRL0_OFFS));
197    mmio_.SetBits32(1 << 28, GetTdmOffset(TDMOUT_CTRL0_OFFS));
198}
199
200// Resets frddr mechanisms to start at beginning of buffer
201//   starts the frddr (this will fill the fifo)
202//   starts the tdm to clock out data on the bus
203// returns the start time
204uint64_t AmlTdmDevice::Start() {
205    uint64_t a, b;
206
207    Sync();
208    FRDDREnable();
209    a = zx_clock_get(ZX_CLOCK_MONOTONIC);
210    TdmOutEnable();
211    b = zx_clock_get(ZX_CLOCK_MONOTONIC);
212    return ((b - a) >> 1) + a;
213}
214
215void AmlTdmDevice::Stop() {
216    TdmOutDisable();
217    FRDDRDisable();
218}
219
220void AmlTdmDevice::Shutdown() {
221    Stop();
222
223    // Disable the output signals
224    zx_off_t ptr = EE_AUDIO_CLK_TDMOUT_A_CTL + tdm_ch_ * sizeof(uint32_t);
225    mmio_.ClearBits32(0x03 << 30, ptr);
226
227    // Disable the audio domain clocks used by this instance.
228    AudioClkDis((EE_AUDIO_CLK_GATE_TDMOUTA << tdm_ch_) |
229                (EE_AUDIO_CLK_GATE_FRDDRA << frddr_ch_));
230
231    //Note: We are leaving the ARB unit clocked as well as MCLK and
232    //  SCLK generation units since it is possible they are used by
233    //  some other audio driver outside of this instance
234}
235