blob: 2a8b78ce58de2913ab32bfc66018f339b0a46d98 [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.
#ifndef SRC_DEVICES_BLOCK_DRIVERS_MTK_SDMMC_MTK_SDMMC_H_
#define SRC_DEVICES_BLOCK_DRIVERS_MTK_SDMMC_MTK_SDMMC_H_
#include <fuchsia/hardware/gpio/cpp/banjo.h>
#include <fuchsia/hardware/platform/device/c/banjo.h>
#include <fuchsia/hardware/sdmmc/cpp/banjo.h>
#include <lib/ddk/io-buffer.h>
#include <lib/ddk/phys-iter.h>
#include <lib/mmio/mmio.h>
#include <lib/sync/completion.h>
#include <lib/zircon-internal/thread_annotations.h>
#include <lib/zx/interrupt.h>
#include <threads.h>
#include <utility>
#include <ddktl/device.h>
#include <fbl/auto_lock.h>
#include <soc/mt8167/mt8167-sdmmc.h>
#include "mtk-sdmmc-reg.h"
namespace sdmmc {
constexpr uint32_t kPageMask = PAGE_SIZE - 1;
struct RequestStatus {
RequestStatus() : cmd_status(ZX_OK), data_status(ZX_OK) {}
RequestStatus(zx_status_t status) : cmd_status(status), data_status(ZX_OK) {}
RequestStatus(zx_status_t cmd, zx_status_t data) : cmd_status(cmd), data_status(data) {}
zx_status_t Get() const { return cmd_status == ZX_OK ? data_status : cmd_status; }
zx_status_t cmd_status;
zx_status_t data_status;
};
class TuneWindow;
class MtkSdmmc;
using DeviceType = ddk::Device<MtkSdmmc>;
class MtkSdmmc : public DeviceType, public ddk::SdmmcProtocol<MtkSdmmc, ddk::base_protocol> {
public:
static zx_status_t Create(void* ctx, zx_device_t* parent);
virtual ~MtkSdmmc() = default;
void DdkRelease();
zx_status_t Bind();
zx_status_t SdmmcHostInfo(sdmmc_host_info_t* info);
zx_status_t SdmmcSetSignalVoltage(sdmmc_voltage_t voltage);
zx_status_t SdmmcSetBusWidth(sdmmc_bus_width_t bus_width);
zx_status_t SdmmcSetBusFreq(uint32_t bus_freq);
zx_status_t SdmmcSetTiming(sdmmc_timing_t timing);
void SdmmcHwReset();
zx_status_t SdmmcPerformTuning(uint32_t cmd_idx);
zx_status_t SdmmcRequest(sdmmc_req_t* req);
zx_status_t SdmmcRegisterInBandInterrupt(const in_band_interrupt_protocol_t* interrupt_cb);
zx_status_t SdmmcRegisterVmo(uint32_t vmo_id, uint8_t client_id, zx::vmo vmo, uint64_t offset,
uint64_t size, uint32_t vmo_rights);
zx_status_t SdmmcUnregisterVmo(uint32_t vmo_id, uint8_t client_id, zx::vmo* out_vmo);
zx_status_t SdmmcRequestNew(const sdmmc_req_new_t* req, uint32_t out_response[4]);
// Visible for testing.
MtkSdmmc(zx_device_t* parent, ddk::MmioBuffer mmio, zx::bti bti, const sdmmc_host_info_t& info,
zx::interrupt irq, const ddk::GpioProtocolClient& reset_gpio,
const ddk::GpioProtocolClient& power_en_gpio, const board_mt8167::MtkSdmmcConfig& config)
: DeviceType(parent),
req_(nullptr),
mmio_(std::move(mmio)),
bti_(std::move(bti)),
info_(info),
irq_(std::move(irq)),
cmd_status_(ZX_OK),
reset_gpio_(reset_gpio),
power_en_gpio_(power_en_gpio),
config_(config) {}
// Visible for testing.
zx_status_t Init();
// Visible for testing.
protected:
virtual zx_status_t WaitForInterrupt(zx::time* timestamp);
int JoinIrqThread() { return thrd_join(irq_thread_, nullptr); }
fbl::Mutex mutex_;
sdmmc_req_t* req_ TA_GUARDED(mutex_);
private:
RequestStatus SdmmcRequestWithStatus(sdmmc_req_t* req);
// Prepares the VMO and the DMA engine for receiving data.
zx_status_t RequestPrepareDma(sdmmc_req_t* req) TA_REQ(mutex_);
// Creates the GPDMA and BDMA descriptors.
zx_status_t SetupDmaDescriptors(phys_iter_buffer_t* phys_iter_buf);
// Waits for the DMA engine to finish and unpins the VMO pages.
zx_status_t RequestFinishDma(sdmmc_req_t* req) TA_REQ(mutex_);
// Clears the FIFO in preparation for receiving data.
zx_status_t RequestPreparePolled(sdmmc_req_t* req) TA_REQ(mutex_);
// Polls the FIFO register for received data.
zx_status_t RequestFinishPolled(sdmmc_req_t* req) TA_REQ(mutex_);
RequestStatus SendTuningBlock(uint32_t cmd_idx, zx_handle_t vmo);
// Iterates over the possible delay values to find the optimal window. set_delay is a function
// that accepts and applies a uint32_t delay value, and do_request is a function that sends the
// request and returns its status. The test results are saved in window.
template <typename DelayCallback, typename RequestCallback>
void TestDelaySettings(DelayCallback&& set_delay, RequestCallback&& do_request,
TuneWindow* window);
int IrqThread();
// Finish the command portion of the request. Returns true if control should be passed back to
// the main thread or false if more interrupts are expected. This should be called from the IRQ
// thread with mutex_ held.
bool CmdDone(const MsdcInt& msdc_int) TA_REQ(mutex_);
ddk::MmioBuffer mmio_;
zx::bti bti_;
const sdmmc_host_info_t info_;
zx::interrupt irq_;
thrd_t irq_thread_;
io_buffer_t gpdma_buf_;
io_buffer_t bdma_buf_;
sync_completion_t req_completion_;
zx_status_t cmd_status_ TA_GUARDED(mutex_);
const ddk::GpioProtocolClient reset_gpio_;
const ddk::GpioProtocolClient power_en_gpio_;
const board_mt8167::MtkSdmmcConfig config_;
ddk::InBandInterruptProtocolClient interrupt_cb_;
};
// TuneWindow keeps track of the results of a series of tuning tests. It is expected that either
// Pass or Fail is called after each test, and that each subsequent delay value is greater than the
// one before it. The largest window of passing tests is determined as the tests are run, and at the
// end the optimal delay value is chosen as the middle of the largest window.
class TuneWindow {
public:
TuneWindow() : index_(0), best_start_(0), best_size_(0), current_start_(0), current_size_(0) {}
// The tuning test passed, update the current window size and the best window size if needed.
void Pass() {
current_size_++;
if (best_start_ == current_start_) {
best_size_ = current_size_;
}
if (current_size_ > best_size_) {
best_start_ = current_start_;
best_size_ = current_size_;
}
index_++;
}
// The tuning test failed, update the best window size if needed.
void Fail() {
current_start_ = index_ + 1;
current_size_ = 0;
index_++;
}
// Returns the best window size and sets result to the best delay value. If the window size is
// zero then no tuning tests passed.
uint32_t GetDelay(uint32_t* delay) const {
if (best_size_ != 0) {
*delay = best_start_ + (best_size_ / 2);
}
return best_size_;
}
private:
uint32_t index_;
uint32_t best_start_;
uint32_t best_size_;
uint32_t current_start_;
uint32_t current_size_;
};
} // namespace sdmmc
#endif // SRC_DEVICES_BLOCK_DRIVERS_MTK_SDMMC_MTK_SDMMC_H_