| // Copyright 2019 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_SDHCI_SDHCI_H_ |
| #define SRC_DEVICES_BLOCK_DRIVERS_SDHCI_SDHCI_H_ |
| |
| #include <fuchsia/hardware/sdhci/cpp/banjo.h> |
| #include <fuchsia/hardware/sdmmc/cpp/banjo.h> |
| #include <lib/ddk/io-buffer.h> |
| #include <lib/mmio/mmio.h> |
| #include <lib/sdmmc/hw.h> |
| #include <lib/sync/completion.h> |
| #include <lib/zircon-internal/thread_annotations.h> |
| #include <lib/zx/bti.h> |
| #include <lib/zx/interrupt.h> |
| #include <zircon/threads.h> |
| |
| #include <ddktl/device.h> |
| #include <fbl/auto_lock.h> |
| #include <fbl/mutex.h> |
| |
| #include "sdhci-reg.h" |
| |
| namespace sdhci { |
| |
| class Sdhci; |
| using DeviceType = ddk::Device<Sdhci, ddk::Unbindable>; |
| |
| class Sdhci : public DeviceType, public ddk::SdmmcProtocol<Sdhci, ddk::base_protocol> { |
| public: |
| // Visible for testing. |
| struct AdmaDescriptor96 { |
| uint16_t attr; |
| uint16_t length; |
| uint64_t address; |
| } __PACKED; |
| static_assert(sizeof(AdmaDescriptor96) == 12, "unexpected ADMA2 descriptor size"); |
| |
| struct AdmaDescriptor64 { |
| uint16_t attr; |
| uint16_t length; |
| uint32_t address; |
| } __PACKED; |
| static_assert(sizeof(AdmaDescriptor64) == 8, "unexpected ADMA2 descriptor size"); |
| |
| Sdhci(zx_device_t* parent, fdf::MmioBuffer regs_mmio_buffer, zx::bti bti, zx::interrupt irq, |
| const ddk::SdhciProtocolClient sdhci, uint64_t quirks, uint64_t dma_boundary_alignment) |
| : DeviceType(parent), |
| regs_mmio_buffer_(std::move(regs_mmio_buffer)), |
| irq_(std::move(irq)), |
| sdhci_(sdhci), |
| bti_(std::move(bti)), |
| quirks_(quirks), |
| dma_boundary_alignment_(dma_boundary_alignment) {} |
| |
| virtual ~Sdhci() = default; |
| |
| static zx_status_t Create(void* ctx, zx_device_t* parent); |
| |
| void DdkRelease(); |
| void DdkUnbind(ddk::UnbindTxn txn); |
| |
| zx_status_t SdmmcHostInfo(sdmmc_host_info_t* out_info); |
| zx_status_t SdmmcSetSignalVoltage(sdmmc_voltage_t voltage) TA_EXCL(mtx_); |
| zx_status_t SdmmcSetBusWidth(sdmmc_bus_width_t bus_width) TA_EXCL(mtx_); |
| zx_status_t SdmmcSetBusFreq(uint32_t bus_freq) TA_EXCL(mtx_); |
| zx_status_t SdmmcSetTiming(sdmmc_timing_t timing) TA_EXCL(mtx_); |
| void SdmmcHwReset() TA_EXCL(mtx_); |
| zx_status_t SdmmcPerformTuning(uint32_t cmd_idx) TA_EXCL(mtx_); |
| zx_status_t SdmmcRequest(sdmmc_req_t* req) TA_EXCL(mtx_); |
| 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. |
| zx_status_t Init(); |
| |
| uint32_t base_clock() const { return base_clock_; } |
| |
| protected: |
| // Visible for testing. |
| enum class RequestStatus { |
| IDLE, |
| COMMAND, |
| TRANSFER_DATA_DMA, |
| READ_DATA_PIO, |
| WRITE_DATA_PIO, |
| BUSY_RESPONSE, |
| }; |
| |
| virtual zx_status_t WaitForReset(const SoftwareReset mask); |
| virtual zx_status_t WaitForInterrupt() { return irq_.wait(nullptr); } |
| virtual zx_status_t PinRequestPages(sdmmc_req_t* req, zx_paddr_t* phys, size_t pagecount); |
| |
| RequestStatus GetRequestStatus() TA_EXCL(mtx_) { |
| fbl::AutoLock lock(&mtx_); |
| if (cmd_req_ != nullptr) { |
| return RequestStatus::COMMAND; |
| } |
| if (data_req_ != nullptr) { |
| const bool has_data = data_req_->cmd_flags & SDMMC_RESP_DATA_PRESENT; |
| const bool busy_response = data_req_->cmd_flags & SDMMC_RESP_LEN_48B; |
| |
| if (has_data) { |
| if (data_req_->use_dma) { |
| return RequestStatus::TRANSFER_DATA_DMA; |
| } |
| if (data_req_->cmd_flags & SDMMC_CMD_READ) { |
| return RequestStatus::READ_DATA_PIO; |
| } |
| return RequestStatus::WRITE_DATA_PIO; |
| } |
| if (busy_response) { |
| return RequestStatus::BUSY_RESPONSE; |
| } |
| } |
| return RequestStatus::IDLE; |
| } |
| |
| fdf::MmioBuffer regs_mmio_buffer_; |
| |
| // DMA descriptors, visible for testing |
| ddk::IoBuffer iobuf_ = {}; |
| |
| private: |
| static void PrepareCmd(sdmmc_req_t* req, TransferMode* transfer_mode, Command* command); |
| |
| bool SupportsAdma2() const { |
| return (info_.caps & SDMMC_HOST_CAP_DMA) && !(quirks_ & SDHCI_QUIRK_NO_DMA); |
| } |
| |
| zx_status_t WaitForInhibit(const PresentState mask) const; |
| zx_status_t WaitForInternalClockStable() const; |
| |
| void CompleteRequestLocked(sdmmc_req_t* req, zx_status_t status) TA_REQ(mtx_); |
| void CmdStageCompleteLocked() TA_REQ(mtx_); |
| void DataStageReadReadyLocked() TA_REQ(mtx_); |
| void DataStageWriteReadyLocked() TA_REQ(mtx_); |
| void TransferCompleteLocked() TA_REQ(mtx_); |
| void ErrorRecoveryLocked() TA_REQ(mtx_); |
| |
| int IrqThread() TA_EXCL(mtx_); |
| |
| template <typename DescriptorType> |
| zx_status_t BuildDmaDescriptor(sdmmc_req_t* req, DescriptorType* descs); |
| zx_status_t StartRequestLocked(sdmmc_req_t* req) TA_REQ(mtx_); |
| zx_status_t FinishRequest(sdmmc_req_t* req); |
| |
| zx::interrupt irq_; |
| thrd_t irq_thread_; |
| |
| const ddk::SdhciProtocolClient sdhci_; |
| |
| zx::bti bti_; |
| |
| // Held when a command or action is in progress. |
| fbl::Mutex mtx_; |
| |
| // Current command request |
| sdmmc_req_t* cmd_req_ TA_GUARDED(mtx_) = nullptr; |
| // Current data line request |
| sdmmc_req_t* data_req_ TA_GUARDED(mtx_) = nullptr; |
| // Current block id to transfer (PIO) |
| uint16_t data_blockid_ TA_GUARDED(mtx_) = 0; |
| // Set to true if the data stage completed before the command stage |
| bool data_done_ TA_GUARDED(mtx_) = false; |
| // used to signal request complete |
| sync_completion_t req_completion_; |
| |
| // Controller info |
| sdmmc_host_info_t info_ = {}; |
| |
| // Controller specific quirks |
| const uint64_t quirks_; |
| const uint64_t dma_boundary_alignment_; |
| |
| // Base clock rate |
| uint32_t base_clock_ = 0; |
| |
| ddk::InBandInterruptProtocolClient interrupt_cb_; |
| }; |
| |
| } // namespace sdhci |
| |
| #endif // SRC_DEVICES_BLOCK_DRIVERS_SDHCI_SDHCI_H_ |