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