[gauss][tdm] audio out driver
Change-Id: Ife7adbdf587bd0e7de0c0ba4e0792365136dcf0f
diff --git a/system/dev/audio/gauss-tdm/gauss-tdm-out.c b/system/dev/audio/gauss-tdm/gauss-tdm-out.c
new file mode 100644
index 0000000..76e270e
--- /dev/null
+++ b/system/dev/audio/gauss-tdm/gauss-tdm-out.c
@@ -0,0 +1,25 @@
+// Copyright 2017 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 <stdlib.h>
+#include <string.h>
+#include <ddk/binding.h>
+#include <ddk/device.h>
+#include <ddk/protocol/platform-defs.h>
+
+extern zx_status_t gauss_tdm_bind(void* ctx, zx_device_t* parent);
+extern void gauss_tdm_release(void*);
+
+static zx_driver_ops_t gauss_tdm_driver_ops = {
+ .version = DRIVER_OPS_VERSION,
+ .bind = gauss_tdm_bind,
+ .release = gauss_tdm_release,
+};
+
+// clang-format off
+ZIRCON_DRIVER_BEGIN(gauss_tdm, gauss_tdm_driver_ops, "gauss-tdm", "0.1", 3)
+ BI_ABORT_IF(NE, BIND_PLATFORM_DEV_VID, PDEV_VID_GOOGLE),
+ BI_ABORT_IF(NE, BIND_PLATFORM_DEV_PID, PDEV_PID_GAUSS),
+ BI_MATCH_IF(EQ, BIND_PLATFORM_DEV_DID, PDEV_DID_GAUSS_AUDIO_OUT),
+ZIRCON_DRIVER_END(gauss_tdm)
+// clang-format on
\ No newline at end of file
diff --git a/system/dev/audio/gauss-tdm/gauss-tdm-stream.cpp b/system/dev/audio/gauss-tdm/gauss-tdm-stream.cpp
new file mode 100644
index 0000000..486c76e
--- /dev/null
+++ b/system/dev/audio/gauss-tdm/gauss-tdm-stream.cpp
@@ -0,0 +1,743 @@
+// Copyright 2017 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 <audio-proto-utils/format-utils.h>
+#include <ddk/debug.h>
+#include <ddk/device.h>
+#include <fbl/limits.h>
+#include <string.h>
+#include <zircon/device/audio.h>
+#include <zx/vmar.h>
+
+#include "dispatcher-pool/dispatcher-thread-pool.h"
+#include "tas57xx.h"
+#include "tdm-audio-stream.h"
+
+namespace audio {
+namespace gauss {
+
+TdmOutputStream::~TdmOutputStream() {}
+
+// static
+zx_status_t TdmOutputStream::Create(zx_device_t* parent) {
+ auto domain = dispatcher::ExecutionDomain::Create();
+ if (domain == nullptr) {
+ return ZX_ERR_NO_MEMORY;
+ }
+ auto stream = fbl::AdoptRef(
+ new TdmOutputStream(parent, fbl::move(domain)));
+
+ platform_device_protocol_t proto;
+ zx_status_t res = device_get_protocol(parent, ZX_PROTOCOL_PLATFORM_DEV, &proto);
+ if (res != ZX_OK) {
+ return res;
+ }
+
+ size_t mmio_size;
+ void *regs;
+ res = pdev_map_mmio(&proto, 0, ZX_CACHE_POLICY_UNCACHED_DEVICE,
+ ®s, &mmio_size,
+ stream->regs_vmo_.reset_and_get_address());
+
+ if (res != ZX_OK) {
+ zxlogf(ERROR, "tdm-output-driver: failed to map mmio.\n");
+ return res;
+ }
+ stream->regs_ = static_cast<aml_tdm_regs_t*>(regs);
+
+ stream->SetModuleClocks();
+
+ //Sleep to let clocks stabilize in amps.
+ zx_nanosleep(zx_deadline_after(ZX_MSEC(20)));
+
+ res = device_get_protocol(parent, ZX_PROTOCOL_I2C, &stream->i2c_);
+ if ( res != ZX_OK) {
+ zxlogf(ERROR,"tdm-output-driver: failed to acquire i2c\n");
+ return res;
+ }
+
+ stream->left_sub_ = Tas57xx::Create(&stream->i2c_, 0);
+ if (!stream->left_sub_) {
+ return ZX_ERR_NO_RESOURCES;
+ }
+
+ stream->right_sub_ = Tas57xx::Create(&stream->i2c_, 1);
+ if (!stream->right_sub_) {
+ return ZX_ERR_NO_RESOURCES;
+ }
+ stream->tweeters_ = Tas57xx::Create(&stream->i2c_, 2);
+ if (!stream->tweeters_) {
+ return ZX_ERR_NO_RESOURCES;
+ }
+
+ /*TODO(hollande) - right now we are getting the irq via pdev, but would also like
+ a way to push down which tdm block and frddr blocks to use. will hard
+ code to TDMC and FRDDRC for now.
+ */
+
+ stream->notify_timer_ = dispatcher::Timer::Create();
+ dispatcher::Timer::ProcessHandler thandler(
+ [tdm = stream](dispatcher::Timer * timer)->zx_status_t {
+ OBTAIN_EXECUTION_DOMAIN_TOKEN(t, tdm->default_domain_);
+ return tdm->ProcessRingNotification();
+ });
+
+ stream->notify_timer_->Activate(stream->default_domain_, fbl::move(thandler));
+
+ res = stream->Bind("tdm-output-driver");
+ // if successful, we need to leak the stream reference since it holds this object
+ // and would otherwise go away once leaving scope of Create.
+ // Note: clang and gcc feel differently about different ways of doing this
+ // operation, below is a compromisse to appease both simultaneously.
+ if (res == ZX_OK) {
+ __UNUSED auto dummy = stream.leak_ref();
+ }
+
+ return ZX_OK;
+}
+
+zx_status_t TdmOutputStream::Bind(const char* devname) {
+ ZX_DEBUG_ASSERT(!supported_formats_.size());
+
+ zx_status_t res = AddFormats(&supported_formats_);
+ if (res != ZX_OK) {
+ zxlogf(ERROR,"Failed to add formats\n");
+ return res;
+ }
+
+ left_sub_->Standby();
+ left_sub_->Reset();
+ left_sub_->Init(0);
+ left_sub_->SetGain(current_gain_);
+ left_sub_->ExitStandby();
+
+ right_sub_->Standby();
+ right_sub_->Reset();
+ right_sub_->Init(1);
+ right_sub_->SetGain(current_gain_);
+ right_sub_->ExitStandby();
+
+ tweeters_->Standby();
+ tweeters_->Reset();
+ tweeters_->Init(0);
+ tweeters_->SetGain(current_gain_);
+ tweeters_->ExitStandby();
+
+ return TdmAudioStreamBase::DdkAdd(devname);
+}
+
+void TdmOutputStream::ReleaseRingBufferLocked() {
+ if (ring_buffer_virt_ != nullptr) {
+ ZX_DEBUG_ASSERT(ring_buffer_size_ != 0);
+ zx::vmar::root_self().unmap(reinterpret_cast<uintptr_t>(ring_buffer_virt_),
+ ring_buffer_size_);
+ ring_buffer_virt_ = nullptr;
+ ring_buffer_size_ = 0;
+ }
+ ring_buffer_vmo_.reset();
+}
+
+zx_status_t TdmOutputStream::AddFormats(
+ fbl::Vector<audio_stream_format_range_t>* supported_formats) {
+ if (!supported_formats)
+ return ZX_ERR_INVALID_ARGS;
+
+ // Record the min/max number of channels.
+ audio_stream_format_range_t range;
+ range.min_channels = 2;
+ range.max_channels = 2;
+ range.sample_formats = AUDIO_SAMPLE_FORMAT_16BIT;
+ range.min_frames_per_second = 49000;
+ range.max_frames_per_second = 49000;
+
+ fbl::AllocChecker ac;
+ supported_formats->reserve(1, &ac);
+ if (!ac.check()) {
+ return ZX_ERR_NO_MEMORY;
+ }
+ range.flags = ASF_RANGE_FLAG_FPS_CONTINUOUS;
+
+ supported_formats->push_back(range);
+
+ return ZX_OK;
+}
+
+void TdmOutputStream::DdkUnbind() {
+ // Close all of our client event sources if we have not already.
+ default_domain_->Deactivate();
+ // Quiet the data being output on tdm
+ regs_->tdmout[TDM_OUT_C].ctl0 &= ~(1 << 31);
+
+ // TODO(hollande) - implement more thorough teardown/reset of the hw state.
+
+ // Unpublish our device node.
+ DdkRemove();
+}
+
+void TdmOutputStream::DdkRelease() {
+ // Ensure execution domain has successfully deactivated.
+ ZX_DEBUG_ASSERT(!default_domain_ || default_domain_->deactivated());
+ // Reclaim our reference from the driver framework and let it go out of
+ // scope. If this is our last reference (it should be), we will destruct
+ // immediately afterwards.
+ auto thiz = fbl::internal::MakeRefPtrNoAdopt(this);
+}
+
+zx_status_t TdmOutputStream::DdkIoctl(uint32_t op,
+ const void* in_buf,
+ size_t in_len,
+ void* out_buf,
+ size_t out_len,
+ size_t* out_actual) {
+ // The only IOCTL we support is get channel.
+ if (op != AUDIO_IOCTL_GET_CHANNEL) {
+ return ZX_ERR_NOT_SUPPORTED;
+ }
+
+ if ((out_buf == nullptr) ||
+ (out_actual == nullptr) ||
+ (out_len != sizeof(zx_handle_t))) {
+ return ZX_ERR_INVALID_ARGS;
+ }
+
+ fbl::AutoLock lock(&lock_);
+
+ // Attempt to allocate a new driver channel and bind it to us. If we don't
+ // already have an stream_channel_, flag this channel is the privileged
+ // connection (The connection which is allowed to do things like change
+ // formats).
+ bool privileged = (stream_channel_ == nullptr);
+ auto channel = dispatcher::Channel::Create();
+ if (channel == nullptr)
+ return ZX_ERR_NO_MEMORY;
+
+ dispatcher::Channel::ProcessHandler phandler(
+ [ stream = fbl::WrapRefPtr(this), privileged ](dispatcher::Channel * channel)->zx_status_t {
+ OBTAIN_EXECUTION_DOMAIN_TOKEN(t, stream->default_domain_);
+ return stream->ProcessStreamChannel(channel, privileged);
+ });
+
+ dispatcher::Channel::ChannelClosedHandler chandler;
+ if (privileged) {
+ chandler = dispatcher::Channel::ChannelClosedHandler(
+ [stream = fbl::WrapRefPtr(this)](const dispatcher::Channel* channel)->void {
+ OBTAIN_EXECUTION_DOMAIN_TOKEN(t, stream->default_domain_);
+ stream->DeactivateStreamChannel(channel);
+ });
+ }
+
+ zx::channel client_endpoint;
+ zx_status_t res = channel->Activate(&client_endpoint,
+ default_domain_,
+ fbl::move(phandler),
+ fbl::move(chandler));
+ if (res == ZX_OK) {
+ if (privileged) {
+ ZX_DEBUG_ASSERT(stream_channel_ == nullptr);
+ stream_channel_ = channel;
+ }
+
+ *(reinterpret_cast<zx_handle_t*>(out_buf)) = client_endpoint.release();
+ *out_actual = sizeof(zx_handle_t);
+ }
+
+ return res;
+}
+
+#define HREQ(_cmd, _payload, _handler, _allow_noack, ...) \
+ case _cmd: \
+ if (req_size != sizeof(req._payload)) { \
+ zxlogf(ERROR, "Bad " #_cmd \
+ " response length (%u != %zu)\n", \
+ req_size, sizeof(req._payload)); \
+ return ZX_ERR_INVALID_ARGS; \
+ } \
+ if (!_allow_noack && (req.hdr.cmd & AUDIO_FLAG_NO_ACK)) { \
+ zxlogf(ERROR, "NO_ACK flag not allowed for " #_cmd "\n"); \
+ return ZX_ERR_INVALID_ARGS; \
+ } \
+ return _handler(channel, req._payload, ##__VA_ARGS__);
+zx_status_t TdmOutputStream::ProcessStreamChannel(dispatcher::Channel* channel, bool privileged) {
+ ZX_DEBUG_ASSERT(channel != nullptr);
+ fbl::AutoLock lock(&lock_);
+
+ union {
+ audio_proto::CmdHdr hdr;
+ audio_proto::StreamGetFmtsReq get_formats;
+ audio_proto::StreamSetFmtReq set_format;
+ audio_proto::GetGainReq get_gain;
+ audio_proto::SetGainReq set_gain;
+ audio_proto::PlugDetectReq plug_detect;
+ // TODO(hollande): add more commands here
+ } req;
+
+ static_assert(sizeof(req) <= 256,
+ "Request buffer is getting to be too large to hold on the stack!");
+
+ uint32_t req_size;
+ zx_status_t res = channel->Read(&req, sizeof(req), &req_size);
+ if (res != ZX_OK)
+ return res;
+
+ if ((req_size < sizeof(req.hdr) ||
+ (req.hdr.transaction_id == AUDIO_INVALID_TRANSACTION_ID)))
+ return ZX_ERR_INVALID_ARGS;
+
+ // Strip the NO_ACK flag from the request before selecting the dispatch target.
+ auto cmd = static_cast<audio_proto::Cmd>(req.hdr.cmd & ~AUDIO_FLAG_NO_ACK);
+ switch (cmd) {
+ HREQ(AUDIO_STREAM_CMD_GET_FORMATS, get_formats, OnGetStreamFormatsLocked, false);
+ HREQ(AUDIO_STREAM_CMD_SET_FORMAT, set_format, OnSetStreamFormatLocked, false, privileged);
+ HREQ(AUDIO_STREAM_CMD_GET_GAIN, get_gain, OnGetGainLocked, false);
+ HREQ(AUDIO_STREAM_CMD_SET_GAIN, set_gain, OnSetGainLocked, true);
+ HREQ(AUDIO_STREAM_CMD_PLUG_DETECT, plug_detect, OnPlugDetectLocked, true);
+ default:
+ zxlogf(ERROR, "Unrecognized stream command 0x%04x\n", req.hdr.cmd);
+ return ZX_ERR_NOT_SUPPORTED;
+ }
+}
+
+zx_status_t TdmOutputStream::ProcessRingBufferChannel(dispatcher::Channel* channel) {
+ ZX_DEBUG_ASSERT(channel != nullptr);
+ fbl::AutoLock lock(&lock_);
+
+ union {
+ audio_proto::CmdHdr hdr;
+ audio_proto::RingBufGetFifoDepthReq get_fifo_depth;
+ audio_proto::RingBufGetBufferReq get_buffer;
+ audio_proto::RingBufStartReq rb_start;
+ audio_proto::RingBufStopReq rb_stop;
+ // TODO(almasrymina): add more commands here
+ } req;
+
+ static_assert(sizeof(req) <= 256,
+ "Request buffer is getting to be too large to hold on the stack!");
+
+ uint32_t req_size;
+ zx_status_t res = channel->Read(&req, sizeof(req), &req_size);
+ if (res != ZX_OK)
+ return res;
+
+ if ((req_size < sizeof(req.hdr) ||
+ (req.hdr.transaction_id == AUDIO_INVALID_TRANSACTION_ID)))
+ return ZX_ERR_INVALID_ARGS;
+
+ // Strip the NO_ACK flag from the request before selecting the dispatch target.
+ auto cmd = static_cast<audio_proto::Cmd>(req.hdr.cmd & ~AUDIO_FLAG_NO_ACK);
+ switch (cmd) {
+ HREQ(AUDIO_RB_CMD_GET_FIFO_DEPTH, get_fifo_depth, OnGetFifoDepthLocked, false);
+ HREQ(AUDIO_RB_CMD_GET_BUFFER, get_buffer, OnGetBufferLocked, false);
+ HREQ(AUDIO_RB_CMD_START, rb_start, OnStartLocked, false);
+ HREQ(AUDIO_RB_CMD_STOP, rb_stop, OnStopLocked, false);
+ default:
+ zxlogf(ERROR, "Unrecognized ring buffer command 0x%04x\n", req.hdr.cmd);
+ return ZX_ERR_NOT_SUPPORTED;
+ }
+
+ return ZX_ERR_NOT_SUPPORTED;
+}
+#undef HREQ
+
+zx_status_t TdmOutputStream::OnGetStreamFormatsLocked(dispatcher::Channel* channel,
+ const audio_proto::StreamGetFmtsReq& req) {
+ ZX_DEBUG_ASSERT(channel != nullptr);
+ uint16_t formats_sent = 0;
+ audio_proto::StreamGetFmtsResp resp;
+
+ if (supported_formats_.size() > fbl::numeric_limits<uint16_t>::max()) {
+ zxlogf(ERROR, "Too many formats (%zu) to send during AUDIO_STREAM_CMD_GET_FORMATS request!\n",
+ supported_formats_.size());
+ return ZX_ERR_INTERNAL;
+ }
+
+ resp.hdr = req.hdr;
+ resp.format_range_count = static_cast<uint16_t>(supported_formats_.size());
+
+ do {
+ uint16_t todo, payload_sz;
+ zx_status_t res;
+
+ todo = fbl::min<uint16_t>(static_cast<uint16_t>(supported_formats_.size() - formats_sent),
+ AUDIO_STREAM_CMD_GET_FORMATS_MAX_RANGES_PER_RESPONSE);
+ payload_sz = static_cast<uint16_t>(sizeof(resp.format_ranges[0]) * todo);
+
+ resp.first_format_range_ndx = formats_sent;
+ ::memcpy(resp.format_ranges, supported_formats_.get() + formats_sent, payload_sz);
+
+ res = channel->Write(&resp, sizeof(resp));
+ if (res != ZX_OK) {
+ zxlogf(ERROR, "Failed to send get stream formats response (res %d)\n", res);
+ return res;
+ }
+
+ formats_sent = (uint16_t)(formats_sent + todo);
+ } while (formats_sent < supported_formats_.size());
+
+ return ZX_OK;
+}
+
+zx_status_t TdmOutputStream::OnSetStreamFormatLocked(dispatcher::Channel* channel,
+ const audio_proto::StreamSetFmtReq& req,
+ bool privileged) {
+ ZX_DEBUG_ASSERT(channel != nullptr);
+
+ zx::channel client_rb_channel;
+ audio_proto::StreamSetFmtResp resp;
+ bool found_one = false;
+
+ resp.hdr = req.hdr;
+
+ // Only the privileged stream channel is allowed to change the format.
+ if (!privileged) {
+ ZX_DEBUG_ASSERT(channel == stream_channel_.get());
+ resp.result = ZX_ERR_ACCESS_DENIED;
+ goto finished;
+ }
+
+ // Check the format for compatibility
+ for (const auto& fmt : supported_formats_) {
+ if (audio::utils::FormatIsCompatible(req.frames_per_second,
+ req.channels,
+ req.sample_format,
+ fmt)) {
+ found_one = true;
+ break;
+ }
+ }
+
+ if (!found_one) {
+ resp.result = ZX_ERR_INVALID_ARGS;
+ goto finished;
+ }
+
+ // Determine the frame size.
+ frame_size_ = audio::utils::ComputeFrameSize(req.channels, req.sample_format);
+ if (!frame_size_) {
+ zxlogf(ERROR, "Failed to compute frame size (ch %hu fmt 0x%08x)\n", req.channels,
+ req.sample_format);
+ resp.result = ZX_ERR_INTERNAL;
+ goto finished;
+ }
+
+ // Looks like we are going ahead with this format change. Tear down any
+ // exiting ring buffer interface before proceeding.
+ if (rb_channel_ != nullptr) {
+ rb_channel_->Deactivate();
+ rb_channel_.reset();
+ }
+
+ //A fifo is 256x64bit, B/C fifos are 128x64bit
+ // We are using the C fifo.
+ fifo_bytes_ = kFifoDepth * 8; // each fifo entry is 64bits wide
+
+ // Create a new ring buffer channel which can be used to move bulk data and
+ // bind it to us.
+ rb_channel_ = dispatcher::Channel::Create();
+ if (rb_channel_ == nullptr) {
+ resp.result = ZX_ERR_NO_MEMORY;
+ } else {
+ dispatcher::Channel::ProcessHandler phandler(
+ [stream = fbl::WrapRefPtr(this)](dispatcher::Channel * channel)->zx_status_t {
+ OBTAIN_EXECUTION_DOMAIN_TOKEN(t, stream->default_domain_);
+ return stream->ProcessRingBufferChannel(channel);
+ });
+
+ dispatcher::Channel::ChannelClosedHandler chandler(
+ [stream = fbl::WrapRefPtr(this)](const dispatcher::Channel* channel)->void {
+ OBTAIN_EXECUTION_DOMAIN_TOKEN(t, stream->default_domain_);
+ stream->DeactivateRingBufferChannel(channel);
+ });
+
+ resp.result = rb_channel_->Activate(&client_rb_channel,
+ default_domain_,
+ fbl::move(phandler),
+ fbl::move(chandler));
+ if (resp.result != ZX_OK) {
+ rb_channel_.reset();
+ }
+ }
+
+finished:
+ if (resp.result == ZX_OK) {
+ return channel->Write(&resp, sizeof(resp), fbl::move(client_rb_channel));
+ } else {
+ return channel->Write(&resp, sizeof(resp));
+ }
+}
+
+zx_status_t TdmOutputStream::OnGetGainLocked(dispatcher::Channel* channel,
+ const audio_proto::GetGainReq& req) {
+ ZX_DEBUG_ASSERT(channel != nullptr);
+ audio_proto::GetGainResp resp;
+
+ resp.hdr = req.hdr;
+ resp.cur_mute = false;
+ resp.cur_gain = current_gain_;
+ resp.can_mute = false;
+ resp.min_gain = -103.0;
+ resp.max_gain = 20.0;
+ resp.gain_step = 0.5;
+
+ return channel->Write(&resp, sizeof(resp));
+}
+
+zx_status_t TdmOutputStream::OnSetGainLocked(dispatcher::Channel* channel,
+ const audio_proto::SetGainReq& req) {
+ ZX_DEBUG_ASSERT(channel != nullptr);
+ if (req.hdr.cmd & AUDIO_FLAG_NO_ACK)
+ return ZX_OK;
+
+ audio_proto::SetGainResp resp;
+ resp.hdr = req.hdr;
+
+
+ bool illegal_mute = (req.flags & AUDIO_SGF_MUTE_VALID) && (req.flags & AUDIO_SGF_MUTE);
+ bool illegal_gain = (req.flags & AUDIO_SGF_GAIN_VALID) && (!left_sub_->ValidGain(req.gain));
+
+ if (!illegal_gain) {
+ left_sub_->SetGain(req.gain);
+ right_sub_->SetGain(req.gain);
+ tweeters_->SetGain(req.gain);
+ left_sub_->GetGain(¤t_gain_);
+ }
+
+ resp.cur_mute = false;
+ resp.cur_gain = current_gain_;
+ resp.result = (illegal_mute || illegal_gain)
+ ? ZX_ERR_INVALID_ARGS
+ : ZX_OK;
+
+ return channel->Write(&resp, sizeof(resp));
+}
+
+zx_status_t TdmOutputStream::OnPlugDetectLocked(dispatcher::Channel* channel,
+ const audio_proto::PlugDetectReq& req) {
+ if (req.hdr.cmd & AUDIO_FLAG_NO_ACK)
+ return ZX_OK;
+
+ audio_proto::PlugDetectResp resp;
+ resp.hdr = req.hdr;
+ resp.flags = static_cast<audio_pd_notify_flags_t>(AUDIO_PDNF_HARDWIRED |
+ AUDIO_PDNF_PLUGGED);
+ return channel->Write(&resp, sizeof(resp));
+}
+
+zx_status_t TdmOutputStream::OnGetFifoDepthLocked(dispatcher::Channel* channel,
+ const audio_proto::RingBufGetFifoDepthReq& req) {
+ audio_proto::RingBufGetFifoDepthResp resp;
+
+ resp.hdr = req.hdr;
+ resp.result = ZX_OK;
+ resp.fifo_depth = fifo_bytes_;
+
+ return channel->Write(&resp, sizeof(resp));
+}
+
+zx_status_t TdmOutputStream::SetModuleClocks() {
+
+ // enable mclk c, select fclk_div4 as source, divide by 20 to get 12.5MHz
+ // at 256 sclk/frame, this yields 48.828125kHz
+ // TODO(hollande) - switch to pll to get accurate timing for 48kHz
+ regs_->mclk_ctl[MCLK_C] = (1 << 31) | (6 << 24) | (19);
+
+ // configure mst_sclk_gen
+ regs_->sclk_ctl[MCLK_C].ctl0 = (0x03 << 30) | (1 << 20) | (0 << 10) | 255;
+ regs_->sclk_ctl[MCLK_C].ctl1 = 0x00000001;
+
+ regs_->clk_tdmout_ctl[TDM_OUT_C] = (0x03 << 30) | (2 << 24) | (2 << 20);
+
+ // Enable clock gates for PDM and TDM blocks
+ regs_->clk_gate_en |= (1 << 8) | (1 << 11);
+ return ZX_OK;
+}
+
+zx_status_t TdmOutputStream::OnGetBufferLocked(dispatcher::Channel* channel,
+ const audio_proto::RingBufGetBufferReq& req) {
+ audio_proto::RingBufGetBufferResp resp;
+ zx::vmo client_rb_handle;
+ uint32_t client_rights;
+
+ resp.hdr = req.hdr;
+ resp.result = ZX_ERR_INTERNAL;
+
+ // Unmap and release any previous ring buffer.
+ ReleaseRingBufferLocked();
+
+ // Compute the ring buffer size. It needs to be at least as big
+ // as the virtual fifo depth.
+ ZX_DEBUG_ASSERT(frame_size_ && ((fifo_bytes_ % frame_size_) == 0));
+ ZX_DEBUG_ASSERT(fifo_bytes_ && ((fifo_bytes_ % fifo_bytes_) == 0));
+ ring_buffer_size_ = req.min_ring_buffer_frames;
+ ring_buffer_size_ *= frame_size_;
+ if (ring_buffer_size_ < fifo_bytes_)
+ ring_buffer_size_ = fifo_bytes_;
+
+ // TODO - (hollande) Make this work with non contig vmo
+ resp.result = zx_vmo_create_contiguous(get_root_resource(), ring_buffer_size_, 0,
+ ring_buffer_vmo_.reset_and_get_address());
+
+ if (resp.result != ZX_OK) {
+ zxlogf(ERROR, "Failed to create ring buffer (size %u, res %d)\n", ring_buffer_size_,
+ resp.result);
+ goto finished;
+ }
+
+ uint64_t vsize;
+ uint32_t bytes_per_notification;
+
+ ring_buffer_vmo_.get_size(&vsize);
+ ZX_DEBUG_ASSERT(vsize <= fbl::numeric_limits<uint32_t>::max());
+ ring_buffer_size_ = static_cast<uint32_t>(vsize);
+
+ if (req.notifications_per_ring) {
+ bytes_per_notification = ring_buffer_size_ / req.notifications_per_ring;
+ } else {
+ bytes_per_notification = 0;
+ }
+ //TODO - (hollande) calculate this with current rate;
+ us_per_notification_ = (1000 * bytes_per_notification) /(48 * frame_size_);
+
+ // Ring buffer is contig, so get address of first page
+ zx_paddr_t temp_addr;
+ resp.result = ring_buffer_vmo_.op_range(ZX_VMO_OP_LOOKUP, 0, 4096,
+ &temp_addr, sizeof(temp_addr));
+ if (resp.result != ZX_OK) goto finished;
+
+ ring_buffer_phys_ = static_cast<uint32_t>(temp_addr);
+
+ // Create the client's handle to the ring buffer vmo and set it back to them.
+ client_rights = ZX_RIGHT_TRANSFER | ZX_RIGHT_MAP | ZX_RIGHT_READ | ZX_RIGHT_WRITE;
+
+ resp.result = ring_buffer_vmo_.duplicate(client_rights, &client_rb_handle);
+ if (resp.result != ZX_OK) {
+ zxlogf(ERROR, "Failed to duplicate ring buffer handle (res %d)\n", resp.result);
+ goto finished;
+ }
+
+finished:
+ zx_status_t res;
+ if (resp.result == ZX_OK) {
+ ZX_DEBUG_ASSERT(client_rb_handle.is_valid());
+ res = channel->Write(&resp, sizeof(resp), fbl::move(client_rb_handle));
+ } else {
+ res = channel->Write(&resp, sizeof(resp));
+ }
+
+ if (res != ZX_OK) {
+ zxlogf(ERROR,"Error in ring buffer creation\n");
+ ReleaseRingBufferLocked();
+ }
+
+ return res;
+}
+
+zx_status_t TdmOutputStream::ProcessRingNotification() {
+
+ if (running_) {
+ notify_timer_->Arm(zx_deadline_after(ZX_USEC(us_per_notification_)));
+ } else {
+ notify_timer_->Cancel();
+ }
+
+ audio_proto::RingBufPositionNotify resp;
+ resp.hdr.cmd = AUDIO_RB_POSITION_NOTIFY;
+
+ resp.ring_buffer_pos = regs_->frddr[2].status2 - ring_buffer_phys_;
+
+ fbl::AutoLock lock(&lock_);
+ if (rb_channel_) {
+ return rb_channel_->Write(&resp, sizeof(resp));
+ } else {
+ zxlogf(ERROR,"RingBufferNotification Failed - rb channel closed\n");
+ //return ok so the Timer can live on for later use.
+ return ZX_OK;
+ }
+}
+
+zx_status_t TdmOutputStream::OnStartLocked(dispatcher::Channel* channel,
+ const audio_proto::RingBufStartReq& req) {
+
+ running_ = true;
+ if (us_per_notification_ > 0) {
+ notify_timer_->Arm(zx_deadline_after(ZX_USEC(us_per_notification_)));
+ }
+
+ audio_proto::RingBufStartResp resp;
+
+ resp.hdr = req.hdr;
+ resp.result = ZX_OK;
+
+ regs_->arb_ctl |= (1 << 31) | (1 << 6);
+
+ regs_->frddr[2].ctl0 = (2 << 0);
+ // Set fifo depth and threshold to half the depth
+ regs_->frddr[2].ctl1 = (kFifoDepth << 24) | ((kFifoDepth / 2) << 16) | (0 << 8);
+
+ regs_->frddr[2].start_addr = (uint32_t)ring_buffer_phys_;
+ regs_->frddr[2].finish_addr = (uint32_t)(ring_buffer_phys_ + ring_buffer_size_ - 8);
+
+ regs_->tdmout[TDM_OUT_C].ctl0 = (1 << 15) | (7 << 5 ) | (31 << 0);
+
+ regs_->tdmout[TDM_OUT_C].ctl1 = (15 << 8) | (2 << 24) | (2 << 4);
+
+ regs_->tdmout[TDM_OUT_C].mask[0]=0x00000003;
+ regs_->tdmout[TDM_OUT_C].swap = 0x00000010;
+ regs_->tdmout[TDM_OUT_C].mask_val=0x00000000;
+ regs_->tdmout[TDM_OUT_C].mute_val=0x00000000;
+
+ //reset the module
+ regs_->tdmout[TDM_OUT_C].ctl0 &= ~(3 << 28);
+ regs_->tdmout[TDM_OUT_C].ctl0 |= (1 << 29);
+ regs_->tdmout[TDM_OUT_C].ctl0 |= (1 << 28);
+
+ //enable frddr
+ regs_->frddr[TDM_OUT_C].ctl0 |= (1 << 31);
+
+ //enable tdmout
+ regs_->tdmout[TDM_OUT_C].ctl0 |= (1 << 31);
+
+ resp.start_ticks = zx_ticks_get();
+ return channel->Write(&resp, sizeof(resp));
+}
+
+zx_status_t TdmOutputStream::OnStopLocked(dispatcher::Channel* channel,
+ const audio_proto::RingBufStopReq& req) {
+ notify_timer_->Cancel();
+ regs_->tdmout[TDM_OUT_C].ctl0 &= ~(1 << 31);
+ running_ = false;
+ audio_proto::RingBufStopResp resp;
+ resp.hdr = req.hdr;
+ resp.result = ZX_OK;
+ return channel->Write(&resp, sizeof(resp));
+}
+
+void TdmOutputStream::DeactivateStreamChannel(const dispatcher::Channel* channel) {
+ fbl::AutoLock lock(&lock_);
+
+ ZX_DEBUG_ASSERT(stream_channel_.get() == channel);
+ ZX_DEBUG_ASSERT(rb_channel_.get() != channel);
+ stream_channel_.reset();
+}
+
+void TdmOutputStream::DeactivateRingBufferChannel(const dispatcher::Channel* channel) {
+ notify_timer_->Cancel();
+ fbl::AutoLock lock(&lock_);
+
+ ZX_DEBUG_ASSERT(stream_channel_.get() != channel);
+ ZX_DEBUG_ASSERT(rb_channel_.get() == channel);
+
+ rb_channel_.reset();
+}
+
+} // namespace gauss
+} // namespace audio
+
+extern "C" zx_status_t gauss_tdm_bind(void* ctx, zx_device_t* device, void** cookie) {
+ audio::gauss::TdmOutputStream::Create(device);
+ return ZX_OK;
+}
+
+extern "C" void gauss_tdm_release(void*) {
+ audio::dispatcher::ThreadPool::ShutdownAll();
+}
diff --git a/system/dev/audio/gauss-tdm/rules.mk b/system/dev/audio/gauss-tdm/rules.mk
new file mode 100644
index 0000000..0f154fa
--- /dev/null
+++ b/system/dev/audio/gauss-tdm/rules.mk
@@ -0,0 +1,33 @@
+# Copyright 2017 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.
+
+LOCAL_DIR := $(GET_LOCAL_DIR)
+
+MODULE := $(LOCAL_DIR)
+
+MODULE_TYPE := driver
+
+MODULE_SRCS += \
+ $(LOCAL_DIR)/gauss-tdm-out.c \
+ $(LOCAL_DIR)/gauss-tdm-stream.cpp \
+ $(LOCAL_DIR)/tas57xx.cpp \
+
+MODULE_LIBS := \
+ system/ulib/c \
+ system/ulib/driver \
+ system/ulib/zircon \
+
+MODULE_STATIC_LIBS := \
+ system/dev/soc/aml-a113 \
+ system/ulib/audio-proto-utils \
+ system/ulib/audio-driver-proto \
+ system/ulib/ddk \
+ system/ulib/ddktl \
+ system/ulib/dispatcher-pool \
+ system/ulib/fbl \
+ system/ulib/sync \
+ system/ulib/zx \
+ system/ulib/zxcpp \
+
+include make/module.mk
diff --git a/system/dev/audio/gauss-tdm/tas57xx.cpp b/system/dev/audio/gauss-tdm/tas57xx.cpp
new file mode 100644
index 0000000..be977d6
--- /dev/null
+++ b/system/dev/audio/gauss-tdm/tas57xx.cpp
@@ -0,0 +1,91 @@
+// Copyright 2017 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 <ddk/protocol/i2c.h>
+#include <fbl/algorithm.h>
+#include <fbl/alloc_checker.h>
+
+
+#include "tas57xx.h"
+
+namespace audio {
+namespace gauss {
+
+constexpr float Tas57xx::kMaxGain;
+constexpr float Tas57xx::kMinGain;
+
+// static
+fbl::unique_ptr<Tas57xx> Tas57xx::Create(i2c_protocol_t *i2c, uint32_t index) {
+ fbl::AllocChecker ac;
+
+ auto ptr = fbl::unique_ptr<Tas57xx>(new (&ac) Tas57xx());
+ if (!ac.check())
+ return nullptr;
+
+ zx_status_t res = i2c_get_channel(i2c, index, &ptr->ch_);
+ if (res != ZX_OK) {
+ zxlogf(ERROR,"Tas57xx: failed to get i2c channel index %d - %d\n",index,res);
+ return nullptr;
+ }
+
+ return ptr;
+}
+Tas57xx::~Tas57xx() {}
+
+Tas57xx::Tas57xx() {}
+
+zx_status_t Tas57xx::Reset(){
+ return WriteReg(0x01, 0x01);
+}
+
+zx_status_t Tas57xx::SetGain(float gain) {
+ gain = fbl::clamp(gain, kMinGain, kMaxGain);
+
+ uint8_t gain_reg = static_cast<uint8_t>( 48 - gain*2);
+
+ zx_status_t status;
+ status = WriteReg(61,gain_reg);
+ if (status != ZX_OK) return status;
+ status = WriteReg(62,gain_reg);
+ if (status != ZX_OK) return status;
+ current_gain_ = gain;
+ return status;
+}
+
+zx_status_t Tas57xx::GetGain(float *gain) {
+ *gain = current_gain_;
+ return ZX_OK;
+}
+
+bool Tas57xx::ValidGain(float gain) {
+ return (gain <= kMaxGain) && (gain >= kMinGain);
+}
+
+zx_status_t Tas57xx::Init(uint8_t slot) {
+ if (slot > 7)
+ return ZX_ERR_INVALID_ARGS;
+ zx_status_t status;
+ status = WriteReg(40, 0x13);
+ if (status != ZX_OK) return status;
+ status = WriteReg(41, static_cast<uint8_t>(1 + 32*slot));
+ if (status != ZX_OK) return status;
+ return WriteReg(42, 0x22);
+}
+
+zx_status_t Tas57xx::Standby() {
+ return WriteReg(0x02, 0x10);
+}
+
+zx_status_t Tas57xx::ExitStandby() {
+ return WriteReg(0x02, 0x00);
+}
+
+zx_status_t Tas57xx::WriteReg(uint8_t reg, uint8_t value) {
+ uint8_t write_buf[2];
+ write_buf[0] = reg;
+ write_buf[1] = value;
+ return i2c_transact(&ch_, write_buf, 2, 0, NULL, NULL);
+}
+} //namespace gauss
+} //namespace audio
\ No newline at end of file
diff --git a/system/dev/audio/gauss-tdm/tas57xx.h b/system/dev/audio/gauss-tdm/tas57xx.h
new file mode 100644
index 0000000..ffbbb37
--- /dev/null
+++ b/system/dev/audio/gauss-tdm/tas57xx.h
@@ -0,0 +1,41 @@
+// Copyright 2017 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.
+
+#pragma once
+
+#include <ddk/debug.h>
+#include <ddk/protocol/i2c.h>
+#include <fbl/unique_ptr.h>
+
+namespace audio {
+namespace gauss {
+
+class Tas57xx : public fbl::unique_ptr<Tas57xx>{
+public:
+ static fbl::unique_ptr<Tas57xx> Create(i2c_protocol_t *i2c, uint32_t index);
+ bool ValidGain(float gain);
+ zx_status_t SetGain(float gain);
+ zx_status_t GetGain(float *gain);
+ zx_status_t Init(uint8_t slot);
+ zx_status_t Reset();
+ zx_status_t Standby();
+ zx_status_t ExitStandby();
+
+private:
+ friend class fbl::unique_ptr<Tas57xx>;
+ static constexpr float kMaxGain = 24.0;
+ static constexpr float kMinGain = -103.0;
+ Tas57xx();
+ ~Tas57xx();
+
+ zx_status_t WriteReg(uint8_t reg, uint8_t value);
+
+ zx_status_t SetStandby(bool stdby);
+
+ i2c_channel_t ch_;
+
+ float current_gain_ = 0;
+};
+} // namespace gauss
+} // namespace audio
\ No newline at end of file
diff --git a/system/dev/audio/gauss-tdm/tdm-audio-stream.h b/system/dev/audio/gauss-tdm/tdm-audio-stream.h
new file mode 100644
index 0000000..c63dd53
--- /dev/null
+++ b/system/dev/audio/gauss-tdm/tdm-audio-stream.h
@@ -0,0 +1,158 @@
+// Copyright 2017 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.
+
+#pragma once
+
+#include <ddk/protocol/i2c.h>
+#include <ddk/protocol/platform-device.h>
+#include <ddktl/device.h>
+#include <ddktl/device-internal.h>
+#include <zircon/listnode.h>
+#include <zx/vmo.h>
+#include <fbl/mutex.h>
+#include <fbl/vector.h>
+
+#include <audio-proto/audio-proto.h>
+#include <dispatcher-pool/dispatcher-channel.h>
+#include <dispatcher-pool/dispatcher-execution-domain.h>
+#include <dispatcher-pool/dispatcher-timer.h>
+#include <soc/aml-a113/aml-tdm.h>
+
+namespace audio {
+namespace gauss {
+
+struct TdmOutputStreamProtocol : public ddk::internal::base_protocol {
+ explicit TdmOutputStreamProtocol() {
+ ddk_proto_id_ = ZX_PROTOCOL_AUDIO_OUTPUT;
+ }
+};
+
+class TdmOutputStream;
+using TdmAudioStreamBase = ddk::Device<TdmOutputStream,
+ ddk::Ioctlable,
+ ddk::Unbindable>;
+
+class TdmOutputStream : public TdmAudioStreamBase,
+ public TdmOutputStreamProtocol,
+ public fbl::RefCounted<TdmOutputStream> {
+public:
+ static zx_status_t Create(zx_device_t* parent);
+
+ //void PrintDebugPrefix() const;
+
+ // DDK device implementation
+ void DdkUnbind();
+ void DdkRelease();
+ zx_status_t DdkIoctl(uint32_t op,
+ const void* in_buf, size_t in_len,
+ void* out_buf, size_t out_len, size_t* out_actual);
+
+private:
+ static int IrqThread(void* arg);
+
+ friend class fbl::RefPtr<TdmOutputStream>;
+
+ // TODO(hollande) - the fifo bytes are adjustable on the audio fifos and should be scaled
+ // with the desired sample rate. Since this first pass has a fixed sample
+ // sample rate we will set as constant for now.
+ // We are using fifo C at this stage, which is max of 128 (64-bit wide)
+ // Using 64 levels for now.
+ static constexpr uint8_t kFifoDepth = 0x40;
+
+ TdmOutputStream(zx_device_t* parent,
+ fbl::RefPtr<dispatcher::ExecutionDomain>&& default_domain)
+ : TdmAudioStreamBase(parent),
+ TdmOutputStreamProtocol(),
+ default_domain_(fbl::move(default_domain)),
+ create_time_(zx_time_get(ZX_CLOCK_MONOTONIC)) { }
+
+ virtual ~TdmOutputStream();
+
+ zx_status_t Bind(const char* devname);
+
+ void ReleaseRingBufferLocked() __TA_REQUIRES(lock_);
+
+ zx_status_t AddFormats(fbl::Vector<audio_stream_format_range_t>* supported_formats);
+
+ // Thunks for dispatching stream channel events.
+ zx_status_t ProcessStreamChannel(dispatcher::Channel* channel, bool privileged);
+ void DeactivateStreamChannel(const dispatcher::Channel* channel);
+
+ zx_status_t OnGetStreamFormatsLocked(dispatcher::Channel* channel,
+ const audio_proto::StreamGetFmtsReq& req)
+ __TA_REQUIRES(lock_);
+ zx_status_t OnSetStreamFormatLocked(dispatcher::Channel* channel,
+ const audio_proto::StreamSetFmtReq& req,
+ bool privileged)
+ __TA_REQUIRES(lock_);
+ zx_status_t OnGetGainLocked(dispatcher::Channel* channel, const audio_proto::GetGainReq& req)
+ __TA_REQUIRES(lock_);
+ zx_status_t OnSetGainLocked(dispatcher::Channel* channel, const audio_proto::SetGainReq& req)
+ __TA_REQUIRES(lock_);
+ zx_status_t OnPlugDetectLocked(dispatcher::Channel* channel,
+ const audio_proto::PlugDetectReq& req) __TA_REQUIRES(lock_);
+
+ // Thunks for dispatching ring buffer channel events.
+ zx_status_t ProcessRingBufferChannel(dispatcher::Channel * channel);
+
+ void DeactivateRingBufferChannel(const dispatcher::Channel* channel);
+
+ zx_status_t SetModuleClocks();
+
+ zx_status_t ProcessRingNotification();
+
+ // Stream command handlers
+ // Ring buffer command handlers
+ zx_status_t OnGetFifoDepthLocked(dispatcher::Channel* channel,
+ const audio_proto::RingBufGetFifoDepthReq& req) __TA_REQUIRES(lock_);
+ zx_status_t OnGetBufferLocked(dispatcher::Channel* channel,
+ const audio_proto::RingBufGetBufferReq& req) __TA_REQUIRES(lock_);
+ zx_status_t OnStartLocked(dispatcher::Channel* channel, const audio_proto::RingBufStartReq& req)
+ __TA_REQUIRES(lock_);
+ zx_status_t OnStopLocked(dispatcher::Channel* channel, const audio_proto::RingBufStopReq& req)
+ __TA_REQUIRES(lock_);
+
+ fbl::Mutex lock_;
+ fbl::Mutex req_lock_ __TA_ACQUIRED_AFTER(lock_);
+
+ // Dispatcher framework state
+ fbl::RefPtr<dispatcher::Channel> stream_channel_ __TA_GUARDED(lock_);
+ fbl::RefPtr<dispatcher::Channel> rb_channel_ __TA_GUARDED(lock_);
+ fbl::RefPtr<dispatcher::ExecutionDomain> default_domain_;
+
+
+ // control registers for the tdm block
+ aml_tdm_regs_t* regs_ = nullptr;
+ zx::vmo regs_vmo_;
+
+ fbl::RefPtr<dispatcher::Timer> notify_timer_;
+
+ // TODO(johngro) : support parsing and selecting from all of the format
+ // descriptors present for a stream, not just a single format (with multiple
+ // sample rates).
+ fbl::Vector<audio_stream_format_range_t> supported_formats_;
+
+ i2c_protocol_t i2c_;
+
+ fbl::unique_ptr<Tas57xx> left_sub_;
+ fbl::unique_ptr<Tas57xx> right_sub_;
+ fbl::unique_ptr<Tas57xx> tweeters_;
+
+ float current_gain_= -20.0;
+
+ uint32_t frame_size_;
+ uint32_t fifo_bytes_;
+
+ const zx_time_t create_time_;
+ uint32_t us_per_notification_ = 0;
+ volatile bool running_;
+
+ zx::vmo ring_buffer_vmo_;
+ void* ring_buffer_virt_ = nullptr;
+ uint32_t ring_buffer_phys_ = 0;
+ uint32_t ring_buffer_size_ = 0;
+};
+
+} // namespace usb
+} // namespace audio
diff --git a/system/dev/board/gauss/gauss-audio.c b/system/dev/board/gauss/gauss-audio.c
index a42aba6..ea399bf 100644
--- a/system/dev/board/gauss/gauss-audio.c
+++ b/system/dev/board/gauss/gauss-audio.c
@@ -7,6 +7,7 @@
#include <ddk/protocol/platform-defs.h>
#include <hw/reg.h>
#include <soc/aml-a113/a113-hw.h>
+#include <soc/aml-a113/aml-tdm.h>
#include <zircon/assert.h>
#include "gauss.h"
diff --git a/system/dev/soc/aml-a113/include/soc/aml-a113/aml-tdm.h b/system/dev/soc/aml-a113/include/soc/aml-a113/aml-tdm.h
new file mode 100644
index 0000000..cc13812
--- /dev/null
+++ b/system/dev/soc/aml-a113/include/soc/aml-a113/aml-tdm.h
@@ -0,0 +1,119 @@
+// Copyright 2017 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.
+
+#pragma once
+
+typedef struct {
+ uint32_t ctl0;
+ uint32_t ctl1;
+} aml_tdm_sclk_ctl_t;
+
+typedef enum {
+ MCLK_A,
+ MCLK_B,
+ MCLK_C,
+ MCLK_D,
+ MCLK_E,
+ MCLK_F
+} aml_tdm_mclk_t;
+
+typedef enum {
+ TDM_OUT_A,
+ TDM_OUT_B,
+ TDM_OUT_C
+} aml_tdm_out_t;
+
+typedef enum {
+ TDM_IN_A,
+ TDM_IN_B,
+ TDM_IN_C,
+ TDM_IN_LB
+} aml_tdm_in_t;
+
+typedef struct {
+ uint32_t ctl0;
+ uint32_t ctl1;
+ uint32_t start_addr;
+ uint32_t finish_addr;
+ uint32_t int_addr;
+ uint32_t status1;
+ uint32_t status2;
+ uint32_t start_addr_b;
+ uint32_t finish_addr_b;
+ uint32_t reserved[7];
+} aml_tdm_toddr_regs_t;
+
+typedef struct {
+ uint32_t ctl0;
+ uint32_t ctl1;
+ uint32_t start_addr;
+ uint32_t finish_addr;
+ uint32_t int_addr;
+ uint32_t status1;
+ uint32_t status2;
+ uint32_t start_addr_b;
+ uint32_t finish_addr_b;
+ uint32_t reserved[7];
+} aml_tdm_frddr_regs_t;
+
+typedef struct {
+ uint32_t ctl;
+ uint32_t swap;
+ uint32_t mask[4];
+ uint32_t stat;
+ uint32_t mute_val;
+ uint32_t mute[4];
+ uint32_t reserved[4];
+} aml_tdm_tdmin_regs_t;
+
+typedef struct {
+ uint32_t ctl0;
+ uint32_t ctl1;
+ uint32_t swap;
+ uint32_t mask[4];
+ uint32_t stat;
+ uint32_t gain[2];
+ uint32_t mute_val;
+ uint32_t mute[4];
+ uint32_t mask_val;
+} aml_tdm_tdmout_regs_t;
+
+typedef volatile struct aml_tdm_regs {
+
+ uint32_t clk_gate_en;
+ uint32_t mclk_ctl[6]; //mclk control - a,b,c,d,e,f
+ uint32_t reserved0[9];
+
+ aml_tdm_sclk_ctl_t sclk_ctl[6];
+ uint32_t reserved1[4];
+
+ uint32_t clk_tdmin_ctl[4]; //tdm in control - a,b,c,lb
+ uint32_t clk_tdmout_ctl[3]; //tdm out control - a,b,c
+
+ uint32_t clk_spdifin_ctl;
+ uint32_t clk_spdifout_ctl;
+ uint32_t clk_resample_ctl;
+ uint32_t clk_locker_ctl;
+ uint32_t clk_pdmin_ctl0;
+ uint32_t clk_pdmin_ctl1;
+ uint32_t reserved2[19];
+
+ aml_tdm_toddr_regs_t toddr[3];
+ aml_tdm_frddr_regs_t frddr[3];
+
+ uint32_t arb_ctl;
+ uint32_t reserved3[15];
+
+ uint32_t lb_ctl0;
+ uint32_t lb_ctl1;
+ uint32_t reserved4[14];
+
+ aml_tdm_tdmin_regs_t tdmin[4];
+ uint32_t reserved5[64];
+ aml_tdm_tdmout_regs_t tdmout[3];
+
+
+//TODO - still more regs, will add as needed
+
+} __PACKED aml_tdm_regs_t;