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