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