| // 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. |
| |
| #include "cadence-hpnfc.h" |
| |
| #include <endian.h> |
| #include <lib/device-protocol/pdev.h> |
| #include <lib/fzl/vmo-mapper.h> |
| #include <lib/zx/time.h> |
| #include <zircon/threads.h> |
| |
| #include <ddk/debug.h> |
| #include <ddk/platform-defs.h> |
| #include <fbl/algorithm.h> |
| #include <fbl/alloc_checker.h> |
| #include <fbl/auto_call.h> |
| #include <fbl/auto_lock.h> |
| |
| #include "cadence-hpnfc-reg.h" |
| #include "src/devices/nand/drivers/cadence-hpnfc/cadence-hpnfc-bind.h" |
| |
| namespace { |
| |
| struct JedecIdMap { |
| uint8_t jedec_id[2]; |
| const char* manufacturer; |
| const char* device; |
| uint32_t page_size; |
| uint32_t pages_per_block; |
| uint32_t num_blocks; |
| uint32_t ecc_bits; |
| uint32_t oob_size; |
| }; |
| |
| constexpr JedecIdMap kJedecIdMap[] = { |
| { |
| .jedec_id = {0x98, 0xdc}, |
| .manufacturer = "Toshiba", |
| .device = "TC58NVG2S0Hxxxx", |
| .page_size = 4096, |
| .pages_per_block = 64, |
| .num_blocks = 2048, |
| .ecc_bits = 8, |
| .oob_size = 256, |
| }, |
| }; |
| |
| // Row address bits 5 and below are the page address, 6 and above are the block address. |
| constexpr uint32_t kBlockAddressIndex = 6; |
| constexpr uint32_t kPagesPerBlock = 1 << kBlockAddressIndex; |
| // Selects BCH correction strength 48 from BCH config registers. |
| constexpr uint32_t kEccCorrectionStrength = 5; |
| |
| constexpr uint32_t kMaxOobSize = 32; |
| |
| constexpr uint32_t kParameterPageSize = 256; |
| static_assert(kParameterPageSize % sizeof(uint32_t) == 0); |
| constexpr uint8_t kParameterPageSignature[] = {0x4f, 0x4e, 0x46, 0x49}; |
| |
| // Only the first two bytes are needed but the controller requires that we round up to eight bytes. |
| constexpr uint32_t kJedecIdSize = 8; |
| |
| // These values were taken from the bootloader NAND driver. |
| constexpr zx::duration kWaitDelay = zx::usec(50); |
| constexpr uint32_t kTimeoutCount = 8000; |
| |
| constexpr uint32_t kBytesToMebibytes = 1024 * 1024; |
| |
| inline uint16_t ReadParameterPage16(const uint8_t* buffer, uint32_t offset) { |
| const uint16_t* buffer16 = reinterpret_cast<const uint16_t*>(buffer); |
| ZX_DEBUG_ASSERT(offset % sizeof(uint16_t) == 0); |
| return letoh16(buffer16[offset / sizeof(uint16_t)]); |
| } |
| |
| inline uint32_t ReadParameterPage32(const uint8_t* buffer, uint32_t offset) { |
| const uint32_t* buffer32 = reinterpret_cast<const uint32_t*>(buffer); |
| ZX_DEBUG_ASSERT(offset % sizeof(uint32_t) == 0); |
| return letoh32(buffer32[offset / sizeof(uint32_t)]); |
| } |
| |
| } // namespace |
| |
| namespace rawnand { |
| |
| // TODO(bradenkell): Use DMA. |
| |
| zx_status_t CadenceHpnfc::Create(void* ctx, zx_device_t* parent) { |
| ddk::PDev pdev(parent); |
| if (!pdev.is_valid()) { |
| zxlogf(ERROR, "%s: Failed to get ZX_PROTOCOL_PLATFORM_DEVICE", __FILE__); |
| return ZX_ERR_NO_RESOURCES; |
| } |
| |
| std::optional<ddk::MmioBuffer> mmio; |
| zx_status_t status = pdev.MapMmio(0, &mmio); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s: Failed to map MMIO: %d", __FILE__, status); |
| return status; |
| } |
| |
| std::optional<ddk::MmioBuffer> fifo_mmio; |
| if ((status = pdev.MapMmio(1, &fifo_mmio)) != ZX_OK) { |
| zxlogf(ERROR, "%s: Failed to map FIFO MMIO: %d", __FILE__, status); |
| return status; |
| } |
| |
| zx::interrupt interrupt; |
| if ((status = pdev.GetInterrupt(0, &interrupt)) != ZX_OK) { |
| zxlogf(ERROR, "%s: Failed to get interrupt: %d", __FILE__, status); |
| return status; |
| } |
| |
| fbl::AllocChecker ac; |
| auto device = fbl::make_unique_checked<CadenceHpnfc>(&ac, parent, *std::move(mmio), |
| *std::move(fifo_mmio), std::move(interrupt)); |
| if (!ac.check()) { |
| zxlogf(ERROR, "%s: Failed to allocate device memory", __FILE__); |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| if ((status = device->StartInterruptThread()) != ZX_OK) { |
| return status; |
| } |
| |
| if ((status = device->Init()) != ZX_OK) { |
| device->StopInterruptThread(); |
| return status; |
| } |
| |
| if ((status = device->Bind()) != ZX_OK) { |
| device->StopInterruptThread(); |
| return status; |
| } |
| |
| __UNUSED auto* dummy = device.release(); |
| return ZX_OK; |
| } |
| |
| zx_status_t CadenceHpnfc::Bind() { |
| zx_status_t status = DdkAdd("cadence-hpnfc"); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s: DdkAdd failed: %d", __FILE__, status); |
| } |
| return status; |
| } |
| |
| bool CadenceHpnfc::WaitForRBn() { |
| auto rbn = RbnSettings::Get().ReadFrom(&mmio_); |
| for (uint32_t i = 0; !rbn.rbn() && i < kTimeoutCount; i++) { |
| zx::nanosleep(zx::deadline_after(kWaitDelay)); |
| rbn.ReadFrom(&mmio_); |
| } |
| return rbn.rbn(); |
| } |
| |
| bool CadenceHpnfc::WaitForThread() { |
| auto reg = TrdStatus::Get().ReadFrom(&mmio_); |
| for (uint32_t i = 0; reg.thread_busy(0) && i < kTimeoutCount; i++) { |
| zx::nanosleep(zx::deadline_after(kWaitDelay)); |
| reg.ReadFrom(&mmio_); |
| } |
| return !reg.thread_busy(0); |
| } |
| |
| zx_status_t CadenceHpnfc::WaitForSdmaTrigger() { |
| if (sync_completion_wait(&completion_, zx::sec(10).get()) != ZX_OK) { |
| zxlogf(ERROR, "%s: Timed out waiting for FIFO data", __FILE__); |
| return ZX_ERR_TIMED_OUT; |
| } |
| |
| fbl::AutoLock lock(&lock_); |
| |
| sync_completion_reset(&completion_); |
| zx_status_t status = sdma_status_; |
| sdma_status_ = ZX_ERR_BAD_STATE; |
| |
| return status; |
| } |
| |
| bool CadenceHpnfc::WaitForCommandComplete() { |
| if (sync_completion_wait(&completion_, zx::sec(10).get()) != ZX_OK) { |
| zxlogf(ERROR, "%s: Timed out waiting for command to complete", __FILE__); |
| return false; |
| } |
| |
| fbl::AutoLock lock(&lock_); |
| |
| sync_completion_reset(&completion_); |
| bool complete = cmd_complete_; |
| cmd_complete_ = false; |
| |
| return complete; |
| } |
| |
| zx_status_t CadenceHpnfc::StartInterruptThread() { |
| fbl::AutoLock lock(&lock_); |
| int thread_status = thrd_create_with_name( |
| &interrupt_thread_, |
| [](void* ctx) -> int { return reinterpret_cast<CadenceHpnfc*>(ctx)->InterruptThread(); }, |
| this, "cadence-hpnfc-thread"); |
| if (thread_status != thrd_success) { |
| zxlogf(ERROR, "%s: Failed to create interrupt thread", __FILE__); |
| return thrd_status_to_zx_status(thread_status); |
| } |
| |
| thread_started_ = true; |
| return ZX_OK; |
| } |
| |
| void CadenceHpnfc::StopInterruptThread() { |
| bool should_join = false; |
| { |
| fbl::AutoLock lock(&lock_); |
| should_join = thread_started_; |
| } |
| |
| interrupt_.destroy(); |
| |
| if (should_join) { |
| thrd_join(interrupt_thread_, nullptr); |
| } |
| } |
| |
| zx_status_t CadenceHpnfc::Init() { |
| CmdStatusPtr::Get().ReadFrom(&mmio_).set_thread_status_select(0).WriteTo(&mmio_); |
| |
| IntrStatus::Get().ReadFrom(&mmio_).clear().WriteTo(&mmio_); |
| IntrEnable::Get() |
| .FromValue(0) |
| .set_interrupts_enable(1) |
| .set_sdma_error_enable(1) |
| .set_sdma_trigger_enable(1) |
| .set_cmd_ignored_enable(1) |
| .WriteTo(&mmio_); |
| |
| if (!WaitForThread()) |
| return ZX_ERR_TIMED_OUT; |
| |
| CmdReg1::Get().FromValue(0).WriteTo(&mmio_); |
| CmdReg0::Get() |
| .FromValue(0) |
| .set_command_type(CmdReg0::kCommandTypePio) |
| .set_thread_number(0) |
| .set_volume_id(0) |
| .set_command_code(CmdReg0::kCommandCodeReset) |
| .WriteTo(&mmio_); |
| |
| if (!WaitForRBn()) |
| return ZX_ERR_TIMED_OUT; |
| |
| if (PopulateNandInfoOnfi() != ZX_OK && PopulateNandInfoJedec() != ZX_OK) { |
| zxlogf(ERROR, "%s: Failed to get NAND device info", __FILE__); |
| return ZX_ERR_NOT_FOUND; |
| } |
| |
| // TODO(bradenkell): Check the NAND info we got against the corresponding values in the |
| // partition map metadata. |
| // TODO(bradenkell): Calculate the following values instead of hard coding them. |
| |
| NfDevLayout::Get() |
| .FromValue(0) |
| .set_block_addr_idx(kBlockAddressIndex) |
| .set_lun_count(1) |
| .set_pages_per_block(kPagesPerBlock) |
| .WriteTo(&mmio_); |
| |
| const uint32_t sector_size = nand_info_.page_size / 2; |
| TransferCfg0::Get().FromValue(0).set_sector_count(2).WriteTo(&mmio_); |
| TransferCfg1::Get() |
| .FromValue(0) |
| .set_last_sector_size(sector_size + nand_info_.oob_size) |
| .set_sector_size(sector_size) |
| .WriteTo(&mmio_); |
| |
| EccConfig0::Get() |
| .FromValue(0) |
| .set_correction_strength(kEccCorrectionStrength) |
| .set_scrambler_enable(0) |
| .set_erase_detection_enable(1) |
| .set_ecc_enable(1) |
| .WriteTo(&mmio_); |
| EccConfig1::Get().FromValue(0).WriteTo(&mmio_); |
| |
| return ZX_OK; |
| } |
| |
| size_t CadenceHpnfc::CopyFromFifo(void* buffer, size_t size) { |
| const size_t word_count = size / sizeof(uint32_t); |
| |
| if (buffer == nullptr) { |
| for (uint32_t i = 0; i < word_count; i++) { |
| fifo_mmio_.Read32(0); |
| } |
| |
| return 0; |
| } |
| |
| uint32_t* const word_buffer = reinterpret_cast<uint32_t*>(buffer); |
| for (uint32_t i = 0; i < word_count; i++) { |
| word_buffer[i] = fifo_mmio_.Read32(0); |
| } |
| |
| return word_count; |
| } |
| |
| void CadenceHpnfc::CopyToFifo(const void* buffer, size_t size) { |
| const size_t word_count = size / sizeof(uint32_t); |
| |
| if (buffer == nullptr) { |
| for (uint32_t i = 0; i < word_count; i++) { |
| fifo_mmio_.Write32(0xffff'ffff, 0); |
| } |
| } else { |
| const uint32_t* const word_buffer = reinterpret_cast<const uint32_t*>(buffer); |
| for (uint32_t i = 0; i < word_count; i++) { |
| fifo_mmio_.Write32(word_buffer[i], 0); |
| } |
| } |
| } |
| |
| zx_status_t CadenceHpnfc::PopulateNandInfoJedec() { |
| uint8_t jedec_id[kJedecIdSize] = {}; |
| zx_status_t status = DoGenericCommand(kInstructionTypeReadId, jedec_id, sizeof(jedec_id)); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s: Failed to read ID: %d", __FILE__, status); |
| return status; |
| } |
| |
| for (size_t i = 0; i < std::size(kJedecIdMap); i++) { |
| if (kJedecIdMap[i].jedec_id[0] == jedec_id[0] && kJedecIdMap[i].jedec_id[1] == jedec_id[1]) { |
| nand_info_.page_size = kJedecIdMap[i].page_size; |
| nand_info_.pages_per_block = kJedecIdMap[i].pages_per_block; |
| nand_info_.num_blocks = kJedecIdMap[i].num_blocks; |
| nand_info_.ecc_bits = kJedecIdMap[i].ecc_bits; |
| nand_info_.oob_size = std::min(kJedecIdMap[i].oob_size, kMaxOobSize); |
| nand_info_.nand_class = fuchsia_hardware_nand_Class_PARTMAP; |
| memset(nand_info_.partition_guid, 0, sizeof(nand_info_.partition_guid)); |
| |
| const uint64_t capacity = static_cast<uint64_t>(nand_info_.page_size) * |
| nand_info_.pages_per_block * nand_info_.num_blocks; |
| |
| zxlogf(INFO, "CadenceHpnfc: Found NAND device %s with capacity %ld MiB", |
| kJedecIdMap[i].device, capacity / kBytesToMebibytes); |
| |
| return ZX_OK; |
| } |
| } |
| |
| return ZX_ERR_NOT_FOUND; |
| } |
| |
| zx_status_t CadenceHpnfc::PopulateNandInfoOnfi() { |
| uint8_t parameter_page[kParameterPageSize] = {}; |
| zx_status_t status = |
| DoGenericCommand(kInstructionTypeReadParameterPage, parameter_page, sizeof(parameter_page)); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s: Failed to read parameter page: %d", __FILE__, status); |
| return status; |
| } |
| |
| if (memcmp(parameter_page, kParameterPageSignature, sizeof(kParameterPageSignature)) != 0) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| constexpr uint32_t kDeviceModelOffset = 44; |
| constexpr uint32_t kDeviceModelSize = 20; |
| constexpr uint32_t kPageSizeOffset = 80; |
| constexpr uint32_t kOobSizeOffset = 84; |
| constexpr uint32_t kPagesPerBlockOffset = 92; |
| constexpr uint32_t kBlocksPerLunOffset = 96; |
| constexpr uint32_t kLunsOffset = 100; |
| constexpr uint32_t kEccBitsCorrectabilityOffset = 112; |
| |
| // TODO(bradenkell): Read the Extended ECC Information if this is 0xff. |
| ZX_DEBUG_ASSERT(parameter_page[kEccBitsCorrectabilityOffset] != 0xff); |
| |
| nand_info_.page_size = ReadParameterPage32(parameter_page, kPageSizeOffset); |
| nand_info_.pages_per_block = ReadParameterPage32(parameter_page, kPagesPerBlockOffset); |
| nand_info_.num_blocks = |
| ReadParameterPage32(parameter_page, kBlocksPerLunOffset) * parameter_page[kLunsOffset]; |
| nand_info_.ecc_bits = parameter_page[kEccBitsCorrectabilityOffset]; |
| nand_info_.oob_size = |
| std::min<uint32_t>(ReadParameterPage16(parameter_page, kOobSizeOffset), kMaxOobSize); |
| nand_info_.nand_class = fuchsia_hardware_nand_Class_PARTMAP; |
| memset(nand_info_.partition_guid, 0, sizeof(nand_info_.partition_guid)); |
| |
| ZX_DEBUG_ASSERT(nand_info_.page_size % sizeof(uint32_t) == 0); |
| ZX_DEBUG_ASSERT(nand_info_.oob_size % sizeof(uint32_t) == 0); |
| |
| const uint64_t capacity = static_cast<uint64_t>(nand_info_.page_size) * |
| nand_info_.pages_per_block * nand_info_.num_blocks; |
| |
| char model[kDeviceModelSize + 1]; |
| memcpy(model, parameter_page + kDeviceModelOffset, kDeviceModelSize); |
| |
| char* const first_space = reinterpret_cast<char*>(memchr(model, ' ', kDeviceModelSize)); |
| model[first_space ? (first_space - model) : kDeviceModelSize] = '\0'; |
| |
| zxlogf(INFO, "CadenceHpnfc: Found NAND device %s with capacity %ld MiB", model, |
| capacity / kBytesToMebibytes); |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t CadenceHpnfc::DoGenericCommand(uint32_t instruction, uint8_t* out_data, uint32_t size) { |
| if (!WaitForThread()) |
| return ZX_ERR_TIMED_OUT; |
| |
| IntrStatus::Get().ReadFrom(&mmio_).clear().WriteTo(&mmio_); |
| |
| CmdReg2Command::Get().FromValue(0).set_instruction_type(instruction).WriteTo(&mmio_); |
| CmdReg3::Get().FromValue(0).WriteTo(&mmio_); |
| CmdReg0::Get().FromValue(0).set_command_type(CmdReg0::kCommandTypeGeneric).WriteTo(&mmio_); |
| |
| if (!WaitForRBn()) |
| return ZX_ERR_TIMED_OUT; |
| |
| CmdReg1::Get().FromValue(0).WriteTo(&mmio_); |
| CmdReg2Data::Get().FromValue(0).set_instruction_type(kInstructionTypeData).WriteTo(&mmio_); |
| CmdReg3::Get().FromValue(0).set_last_sector_size(size).set_sector_count(1).WriteTo(&mmio_); |
| CmdReg0::Get().FromValue(0).set_command_type(CmdReg0::kCommandTypeGeneric).WriteTo(&mmio_); |
| |
| zx_status_t status = WaitForSdmaTrigger(); |
| if (status != ZX_OK) |
| return status; |
| |
| CopyFromFifo(out_data, size); |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t CadenceHpnfc::RawNandReadPageHwecc(uint32_t nandpage, void* out_data_buffer, |
| size_t data_size, size_t* out_data_actual, |
| void* out_oob_buffer, size_t oob_size, |
| size_t* out_oob_actual, uint32_t* out_ecc_correct) { |
| if (data_size < nand_info_.page_size || oob_size < nand_info_.oob_size) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| if (!WaitForThread()) |
| return ZX_ERR_TIMED_OUT; |
| |
| IntrStatus::Get().ReadFrom(&mmio_).clear().WriteTo(&mmio_); |
| |
| CmdReg1::Get().FromValue(0).set_address(nandpage).WriteTo(&mmio_); |
| CmdReg2Dma::Get().FromValue(0).WriteTo(&mmio_); |
| CmdReg3::Get().FromValue(0).WriteTo(&mmio_); |
| CmdReg0::Get() |
| .FromValue(0) |
| .set_command_type(CmdReg0::kCommandTypePio) |
| .set_dma_sel(0) |
| .set_command_code(CmdReg0::kCommandCodeReadPage) |
| .WriteTo(&mmio_); |
| |
| zx_status_t status = WaitForSdmaTrigger(); |
| if (status != ZX_OK) |
| return status; |
| |
| const uint32_t sdma_size = SdmaSize::Get().ReadFrom(&mmio_).reg_value(); |
| if (sdma_size != nand_info_.page_size + nand_info_.oob_size) { |
| zxlogf(ERROR, "%s: Expected %u bytes in FIFO, got %u", __FILE__, |
| nand_info_.page_size + nand_info_.oob_size, sdma_size); |
| return ZX_ERR_IO; |
| } |
| |
| data_size = CopyFromFifo(out_data_buffer, nand_info_.page_size); |
| oob_size = CopyFromFifo(out_oob_buffer, nand_info_.oob_size); |
| |
| auto cmd_status = CmdStatus::Get().ReadFrom(&mmio_); |
| |
| if (out_data_actual != nullptr) { |
| *out_data_actual = data_size; |
| } |
| if (out_oob_actual != nullptr) { |
| *out_oob_actual = oob_size; |
| } |
| if (out_ecc_correct != nullptr) { |
| *out_ecc_correct = cmd_status.max_errors(); |
| } |
| |
| if (cmd_status.ecc_error()) { |
| return ZX_ERR_IO_DATA_INTEGRITY; |
| } else if (cmd_status.bus_error() || cmd_status.fail() || cmd_status.dev_error() || |
| cmd_status.cmd_error()) { |
| return ZX_ERR_IO; |
| } |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t CadenceHpnfc::RawNandWritePageHwecc(const void* data_buffer, size_t data_size, |
| const void* oob_buffer, size_t oob_size, |
| uint32_t nandpage) { |
| if (data_size < nand_info_.page_size || oob_size < nand_info_.oob_size) { |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| if (!WaitForThread()) |
| return ZX_ERR_TIMED_OUT; |
| |
| IntrStatus::Get().ReadFrom(&mmio_).clear().WriteTo(&mmio_); |
| |
| CmdReg1::Get().FromValue(0).set_address(nandpage).WriteTo(&mmio_); |
| CmdReg2Dma::Get().FromValue(0).WriteTo(&mmio_); |
| CmdReg3::Get().FromValue(0).WriteTo(&mmio_); |
| CmdReg0::Get() |
| .FromValue(0) |
| .set_command_type(CmdReg0::kCommandTypePio) |
| .set_dma_sel(0) |
| .set_command_code(CmdReg0::kCommandCodeProgramPage) |
| .WriteTo(&mmio_); |
| |
| zx_status_t status = WaitForSdmaTrigger(); |
| if (status != ZX_OK) |
| return status; |
| |
| const uint32_t sdma_size = SdmaSize::Get().ReadFrom(&mmio_).reg_value(); |
| if (SdmaSize::Get().ReadFrom(&mmio_).reg_value() != nand_info_.page_size + nand_info_.oob_size) { |
| zxlogf(ERROR, "%s: Expected %u bytes in FIFO, got %u", __FILE__, |
| nand_info_.page_size + nand_info_.oob_size, sdma_size); |
| return ZX_ERR_IO; |
| } |
| |
| CopyToFifo(data_buffer, nand_info_.page_size); |
| CopyToFifo(oob_buffer, nand_info_.oob_size); |
| |
| auto cmd_status = CmdStatus::Get().ReadFrom(&mmio_); |
| if (cmd_status.bus_error() || cmd_status.fail() || cmd_status.dev_error() || |
| cmd_status.ecc_error() || cmd_status.cmd_error()) { |
| return ZX_ERR_IO; |
| } |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t CadenceHpnfc::RawNandEraseBlock(uint32_t nandpage) { |
| if (!WaitForThread()) |
| return ZX_ERR_TIMED_OUT; |
| |
| IntrStatus::Get().ReadFrom(&mmio_).clear().WriteTo(&mmio_); |
| |
| CmdReg1::Get().FromValue(0).set_address(nandpage).WriteTo(&mmio_); |
| CmdReg2Dma::Get().FromValue(0).WriteTo(&mmio_); |
| CmdReg3::Get().FromValue(0).WriteTo(&mmio_); |
| CmdReg0::Get() |
| .FromValue(0) |
| .set_command_type(CmdReg0::kCommandTypePio) |
| .set_interrupt_enable(1) |
| .set_command_code(CmdReg0::kCommandCodeEraseBlock) |
| .WriteTo(&mmio_); |
| |
| if (!WaitForCommandComplete()) |
| return ZX_ERR_TIMED_OUT; |
| |
| auto cmd_status = CmdStatus::Get().ReadFrom(&mmio_); |
| if (cmd_status.bus_error() || cmd_status.fail() || cmd_status.dev_error() || |
| cmd_status.max_errors() || cmd_status.ecc_error() || cmd_status.cmd_error()) { |
| return ZX_ERR_IO; |
| } |
| |
| return ZX_OK; |
| } |
| |
| int CadenceHpnfc::InterruptThread() { |
| for (;;) { |
| zx_status_t status = interrupt_.wait(nullptr); |
| if (status == ZX_ERR_CANCELED) { |
| break; |
| } |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s: Interrupt wait failed: %d", __FILE__, status); |
| return thrd_error; |
| } |
| |
| auto intr_status = IntrStatus::Get().ReadFrom(&mmio_).WriteTo(&mmio_); |
| auto thrd_status = TrdCompIntrStatus::Get().ReadFrom(&mmio_).WriteTo(&mmio_); |
| |
| fbl::AutoLock lock(&lock_); |
| |
| if (intr_status.sdma_trigger()) { |
| sdma_status_ = ZX_OK; |
| sync_completion_signal(&completion_); |
| } else if (intr_status.cmd_ignored()) { |
| sdma_status_ = ZX_ERR_NOT_SUPPORTED; |
| sync_completion_signal(&completion_); |
| } else if (intr_status.sdma_error()) { |
| sdma_status_ = ZX_ERR_IO; |
| sync_completion_signal(&completion_); |
| } else if (thrd_status.thread_complete(0)) { |
| cmd_complete_ = true; |
| sync_completion_signal(&completion_); |
| } |
| } |
| |
| return thrd_success; |
| } |
| |
| zx_status_t CadenceHpnfc::RawNandGetNandInfo(nand_info_t* out_info) { |
| memcpy(out_info, &nand_info_, sizeof(nand_info_)); |
| return ZX_OK; |
| } |
| |
| void CadenceHpnfc::DdkUnbind(ddk::UnbindTxn txn) { |
| StopInterruptThread(); |
| txn.Reply(); |
| } |
| |
| void CadenceHpnfc::DdkRelease() { delete this; } |
| |
| } // namespace rawnand |
| |
| static zx_driver_ops_t cadence_hpnfc_driver_ops = []() -> zx_driver_ops_t { |
| zx_driver_ops_t ops = {}; |
| ops.version = DRIVER_OPS_VERSION; |
| ops.bind = rawnand::CadenceHpnfc::Create; |
| return ops; |
| }(); |
| |
| ZIRCON_DRIVER(cadence_hpnfc, cadence_hpnfc_driver_ops, "zircon", "0.1"); |