blob: 90550c0596a6c204794813cab48a5f1a17421b44 [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <limits>
#include <memory>
#include <utility>
#include <ddk/debug.h>
#include <fbl/alloc_checker.h>
#include <soc/aml-common/aml-pdm-audio.h>
// Filter configurations
// mode 1 lpf1
static const uint32_t lpf1m1[] = {
0x000014, 0xffffb2, 0xfffed9, 0xfffdce, 0xfffd45, 0xfffe32, 0x000147, 0x000645, 0x000b86,
0x000e21, 0x000ae3, 0x000000, 0xffeece, 0xffdca8, 0xffd212, 0xffd7d1, 0xfff2a7, 0x001f4c,
0x0050c2, 0x0072aa, 0x006ff1, 0x003c32, 0xffdc4e, 0xff6a18, 0xff0fef, 0xfefbaf, 0xff4c40,
0x000000, 0x00ebc8, 0x01c077, 0x02209e, 0x01c1a4, 0x008e60, 0xfebe52, 0xfcd690, 0xfb8fa5,
0xfba498, 0xfd9812, 0x0181ce, 0x06f5f3, 0x0d112f, 0x12a958, 0x169686, 0x18000e, 0x169686,
0x12a958, 0x0d112f, 0x06f5f3, 0x0181ce, 0xfd9812, 0xfba498, 0xfb8fa5, 0xfcd690, 0xfebe52,
0x008e60, 0x01c1a4, 0x02209e, 0x01c077, 0x00ebc8, 0x000000, 0xff4c40, 0xfefbaf, 0xff0fef,
0xff6a18, 0xffdc4e, 0x003c32, 0x006ff1, 0x0072aa, 0x0050c2, 0x001f4c, 0xfff2a7, 0xffd7d1,
0xffd212, 0xffdca8, 0xffeece, 0x000000, 0x000ae3, 0x000e21, 0x000b86, 0x000645, 0x000147,
0xfffe32, 0xfffd45, 0xfffdce, 0xfffed9, 0xffffb2, 0x000014,
};
constexpr uint32_t kLpf1m1Len = static_cast<uint32_t>(countof(lpf1m1));
// mode 1 lpf3
static const uint32_t lpf3m1[] = {
0x000000, 0x000081, 0x000000, 0xfffedb, 0x000000, 0x00022d, 0x000000, 0xfffc46, 0x000000,
0x0005f7, 0x000000, 0xfff6eb, 0x000000, 0x000d4e, 0x000000, 0xffed1e, 0x000000, 0x001a1c,
0x000000, 0xffdcb0, 0x000000, 0x002ede, 0x000000, 0xffc2d1, 0x000000, 0x004ebe, 0x000000,
0xff9beb, 0x000000, 0x007dd7, 0x000000, 0xff633a, 0x000000, 0x00c1d2, 0x000000, 0xff11d5,
0x000000, 0x012368, 0x000000, 0xfe9c45, 0x000000, 0x01b252, 0x000000, 0xfdebf6, 0x000000,
0x0290b8, 0x000000, 0xfcca0d, 0x000000, 0x041d7c, 0x000000, 0xfa8152, 0x000000, 0x07e9c6,
0x000000, 0xf28fb5, 0x000000, 0x28b216, 0x3fffde, 0x28b216, 0x000000, 0xf28fb5, 0x000000,
0x07e9c6, 0x000000, 0xfa8152, 0x000000, 0x041d7c, 0x000000, 0xfcca0d, 0x000000, 0x0290b8,
0x000000, 0xfdebf6, 0x000000, 0x01b252, 0x000000, 0xfe9c45, 0x000000, 0x012368, 0x000000,
0xff11d5, 0x000000, 0x00c1d2, 0x000000, 0xff633a, 0x000000, 0x007dd7, 0x000000, 0xff9beb,
0x000000, 0x004ebe, 0x000000, 0xffc2d1, 0x000000, 0x002ede, 0x000000, 0xffdcb0, 0x000000,
0x001a1c, 0x000000, 0xffed1e, 0x000000, 0x000d4e, 0x000000, 0xfff6eb, 0x000000, 0x0005f7,
0x000000, 0xfffc46, 0x000000, 0x00022d, 0x000000, 0xfffedb, 0x000000, 0x000081, 0x000000,
};
constexpr uint32_t kLpf3m1Len = static_cast<uint32_t>(countof(lpf3m1));
// osr64 lpf2
static const uint32_t lpf2osr64[] = {
0x00050a, 0xfff004, 0x0002c1, 0x003c12, 0xffa818, 0xffc87d, 0x010aef, 0xff5223, 0xfebd93,
0x028f41, 0xff5c0e, 0xfc63f8, 0x055f81, 0x000000, 0xf478a0, 0x11c5e3, 0x2ea74d, 0x11c5e3,
0xf478a0, 0x000000, 0x055f81, 0xfc63f8, 0xff5c0e, 0x028f41, 0xfebd93, 0xff5223, 0x010aef,
0xffc87d, 0xffa818, 0x003c12, 0x0002c1, 0xfff004, 0x00050a,
};
constexpr uint32_t kLpf2osr64Len = static_cast<uint32_t>(countof(lpf2osr64));
// static
std::unique_ptr<AmlPdmDevice> AmlPdmDevice::Create(
ddk::MmioBuffer pdm_mmio, ddk::MmioBuffer audio_mmio, ee_audio_mclk_src_t pdm_clk_src,
uint32_t sysclk_div, uint32_t dclk_div, aml_toddr_t toddr_dev, metadata::AmlVersion version) {
// TODDR A has 256 64-bit lines in the FIFO, B and C have 128.
uint32_t fifo_depth = 128 * 8; // in bytes.
if (toddr_dev == TODDR_A) {
fifo_depth = 256 * 8;
}
fbl::AllocChecker ac;
auto pdm = std::unique_ptr<AmlPdmDevice>(
new (&ac) AmlPdmDevice(std::move(pdm_mmio), std::move(audio_mmio), pdm_clk_src, sysclk_div,
dclk_div, toddr_dev, fifo_depth, version));
if (!ac.check()) {
zxlogf(ERROR, "%s: Could not create AmlPdmDevice", __func__);
return nullptr;
}
pdm->InitRegs();
constexpr uint32_t default_frames_per_second = 48000;
pdm->ConfigFilters(default_frames_per_second);
return pdm;
}
void AmlPdmDevice::InitRegs() {
// Setup toddr block
switch (version_) {
case metadata::AmlVersion::kS905D2G:
audio_mmio_.Write32((0x02 << 13) | // Right justified 16-bit
(31 << 8) | // msb position of data out of pdm
(16 << 3) | // lsb position of data out of pdm
(0x04 << 0), // select pdm as data source
GetToddrOffset(TODDR_CTRL0_OFFS));
audio_mmio_.Write32(((fifo_depth_ / 8 / 2) << 16) | // trigger ddr when fifo half full
(0x02 << 8), // STATUS2 source is ddr position
GetToddrOffset(TODDR_CTRL1_OFFS));
break;
case metadata::AmlVersion::kS905D3G:
audio_mmio_.Write32((0x02 << 13) | // Right justified 16-bit
(31 << 8) | // msb position of data out of pdm
(16 << 3), // lsb position of data out of pdm
GetToddrOffset(TODDR_CTRL0_OFFS));
audio_mmio_.Write32((0x04 << 28) | // select pdm as data source
((fifo_depth_ / 8 / 2) << 12) | // trigger ddr when fifo half full
(0x02 << 8), // STATUS2 source is ddr position
GetToddrOffset(TODDR_CTRL1_OFFS));
break;
}
//*To keep things simple, we are using the same clock source for both the
// pdm sysclk and dclk. Sysclk needs to be ~100-200MHz per AmLogic recommendations.
// dclk is osr*fs
//*Sysclk must be configured, enabled, and PDM audio clock gated prior to
// accessing any of the registers mapped via pdm_mmio. Writing without sysclk
// operating properly (and in range) will result in unknown results, reads
// will wedge the system.
audio_mmio_.Write32((clk_src_ << 24) | dclk_div_, EE_AUDIO_CLK_PDMIN_CTRL0);
audio_mmio_.Write32((1 << 31) | (clk_src_ << 24) | sysclk_div_, EE_AUDIO_CLK_PDMIN_CTRL1);
audio_mmio_.SetBits32((1 << 31) | (1 << toddr_ch_), EE_AUDIO_ARB_CTRL);
// Enable the audio domain clocks used by this instance.
AudioClkEna(EE_AUDIO_CLK_GATE_PDM | (EE_AUDIO_CLK_GATE_TODDRA << toddr_ch_) |
EE_AUDIO_CLK_GATE_ARB);
// It is now safe to write to pdm registers
// Ensure clocks are stable before accessing any of the pdm_mmio_ registers.
zx::nanosleep(zx::deadline_after(zx::msec(10)));
// Ensure system is in idle state in case we are re-initing hardware
// which was already running. Keep de-inited for 100ms with no pdm_dclk to
// ensure pdm microphones will start reliably.
Stop();
zx::nanosleep(zx::deadline_after(zx::msec(100)));
// Enable cts_pdm_clk gate (clock gate within pdm module)
pdm_mmio_.SetBits32(0x01, PDM_CLKG_CTRL);
pdm_mmio_.Write32((0x01 << 29), // 24bit output mode
PDM_CTRL);
// This sets the number of sysclk cycles between edge of dclk and when
// data is sampled. AmLogic material suggests this should be 3/4 of a
// dclk half-cycle. Go ahead and set all eight channels.
uint32_t samp_delay = 3 * (dclk_div_ + 1) / (4 * 2 * (sysclk_div_ + 1));
pdm_mmio_.Write32((samp_delay << 0) | (samp_delay << 8) | (samp_delay << 16) | (samp_delay << 24),
PDM_CHAN_CTRL);
pdm_mmio_.Write32((samp_delay << 0) | (samp_delay << 8) | (samp_delay << 16) | (samp_delay << 24),
PDM_CHAN_CTRL1);
}
void AmlPdmDevice::ConfigFilters(uint32_t frames_per_second) {
ZX_ASSERT(frames_per_second == 96000 || frames_per_second == 48000);
uint32_t gain_shift = (frames_per_second == 96000) ? 0xe : 0x15;
uint32_t downsample_rate = (frames_per_second == 96000) ? 0x4 : 0x8;
pdm_mmio_.Write32((1 << 31) | // Enable
(gain_shift << 24) | // Final gain shift parameter
(0x80 << 16) | // Final gain multiplier
(downsample_rate << 4) | // hcic downsample rate
(0x07 << 0), // hcic stage number (must be between 3-9)
PDM_HCIC_CTRL1);
// Note: The round mode field for the lowpass control registers is shown in AmLogic
// documentation to be occupying bits [16:15] fo the register. This was confirmed
// by amlogic to be an error in the datasheet and the correct position is [17:16]
pdm_mmio_.Write32((0x01 << 31) | // Enable filter
(0x01 << 16) | // Round mode
(0x02 << 12) | // Filter 1 downsample rate
(kLpf1m1Len << 0), // Number of taps in filter
PDM_F1_CTRL);
pdm_mmio_.Write32((0x01 << 31) | // Enable filter
(0x00 << 16) | // Round mode
(0x02 << 12) | // Filter 2 downsample rate
(kLpf2osr64Len << 0), // Number of taps in filter
PDM_F2_CTRL);
pdm_mmio_.Write32((0x01 << 31) | // Enable filter
(0x01 << 16) | // Round mode
(2 << 12) | // Filter 3 downsample rate
(kLpf3m1Len << 0), // Number of taps in filter
PDM_F3_CTRL);
pdm_mmio_.Write32((0x01 << 31) | // Enable filter
(0x0d << 16) | // Shift steps
(0x8000 << 0), // Output factor
PDM_HPF_CTRL);
// set coefficient index pointer to 0
pdm_mmio_.Write32(0x0000, PDM_COEFF_ADDR);
// Write coefficients to coefficient memory
// --these appear to be packed with the filter length in each filter
// control register being the mechanism that helps reference them
for (uint32_t i = 0; i < countof(lpf1m1); i++) {
pdm_mmio_.Write32(lpf1m1[i], PDM_COEFF_DATA);
}
for (uint32_t i = 0; i < countof(lpf2osr64); i++) {
pdm_mmio_.Write32(lpf2osr64[i], PDM_COEFF_DATA);
}
for (uint32_t i = 0; i < countof(lpf3m1); i++) {
pdm_mmio_.Write32(lpf3m1[i], PDM_COEFF_DATA);
}
// set coefficient index pointer back to 0
pdm_mmio_.Write32(0x0000, PDM_COEFF_ADDR);
}
void AmlPdmDevice::SetMute(uint8_t mute_mask) {
pdm_mmio_.ModifyBits<uint32_t>(static_cast<uint32_t>(mute_mask) << 20, 0xff << 20, PDM_CTRL);
}
void AmlPdmDevice::SetRate(uint32_t frames_per_second) {
ZX_ASSERT(frames_per_second == 48000 || frames_per_second == 96000);
ConfigFilters(frames_per_second);
}
uint32_t AmlPdmDevice::GetRingPosition() {
uint32_t pos = audio_mmio_.Read32(GetToddrOffset(TODDR_STATUS2_OFFS));
uint32_t base = audio_mmio_.Read32(GetToddrOffset(TODDR_START_ADDR_OFFS));
return (pos - base);
}
void AmlPdmDevice::AudioClkEna(uint32_t audio_blk_mask) {
audio_mmio_.SetBits32(audio_blk_mask, EE_AUDIO_CLK_GATE_EN);
}
void AmlPdmDevice::AudioClkDis(uint32_t audio_blk_mask) {
audio_mmio_.ClearBits32(audio_blk_mask, EE_AUDIO_CLK_GATE_EN);
}
zx_status_t AmlPdmDevice::SetBuffer(zx_paddr_t buf, size_t len) {
// Ensure ring buffer resides in lower memory (dma pointers are 32-bit)
// and len is at least 8 (size of each dma operation)
if (((buf + len - 1) > std::numeric_limits<uint32_t>::max()) || (len < 8)) {
return ZX_ERR_INVALID_ARGS;
}
// Write32 the start and end pointers. Each fetch is 64-bits, so end pointer
// is pointer to the last 64-bit fetch (inclusive)
audio_mmio_.Write32(static_cast<uint32_t>(buf), GetToddrOffset(TODDR_START_ADDR_OFFS));
audio_mmio_.Write32(static_cast<uint32_t>(buf), GetToddrOffset(TODDR_INIT_ADDR_OFFS));
audio_mmio_.Write32(static_cast<uint32_t>(buf + len - 8), GetToddrOffset(TODDR_FINISH_ADDR_OFFS));
return ZX_OK;
}
// Stops the pdm from clocking
void AmlPdmDevice::PdmInDisable() {
audio_mmio_.ClearBits32(1 << 31, EE_AUDIO_CLK_PDMIN_CTRL0);
pdm_mmio_.ClearBits32((1 << 31) | (1 << 16), PDM_CTRL);
}
// Enables the pdm to clock data
void AmlPdmDevice::PdmInEnable() {
// Start pdm_dclk
audio_mmio_.SetBits32(1 << 31, EE_AUDIO_CLK_PDMIN_CTRL0);
pdm_mmio_.SetBits32((1 << 31) | (1 << 16), PDM_CTRL);
}
// Takes channels out of reset and enables them.
void AmlPdmDevice::ConfigPdmIn(uint8_t mask) {
pdm_mmio_.ModifyBits<uint32_t>((mask << 8) | (mask << 0), (0xff << 8) | (0xff << 0), PDM_CTRL);
}
void AmlPdmDevice::TODDREnable() {
// Set the load bit, will make sure things start from beginning of buffer
audio_mmio_.SetBits32(1 << 31, GetToddrOffset(TODDR_CTRL0_OFFS));
}
void AmlPdmDevice::TODDRDisable() {
// Clear the load bit (this is the bit that forces the initial fetch of
// start address into current ptr)
audio_mmio_.ClearBits32(1 << 31, GetToddrOffset(TODDR_CTRL0_OFFS));
audio_mmio_.ClearBits32(1 << 25, GetToddrOffset(TODDR_CTRL1_OFFS));
}
void AmlPdmDevice::Sync() {
pdm_mmio_.ClearBits32(1 << 16, PDM_CTRL);
pdm_mmio_.SetBits32(1 << 16, PDM_CTRL);
}
// Resets frddr mechanisms to start at beginning of buffer
// starts the frddr (this will fill the fifo)
// starts the tdm to clock out data on the bus
// returns the start time
uint64_t AmlPdmDevice::Start() {
uint64_t a, b;
Sync();
TODDREnable();
a = zx_clock_get_monotonic();
PdmInEnable();
b = zx_clock_get_monotonic();
return ((b - a) >> 1) + a;
}
void AmlPdmDevice::Stop() {
PdmInDisable();
TODDRDisable();
}
void AmlPdmDevice::Shutdown() { Stop(); }