| // 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 "src/devices/nand/drivers/aml-rawnand/aml-rawnand.h" |
| |
| #include <assert.h> |
| |
| #include <algorithm> |
| #include <memory> |
| |
| #include <fbl/algorithm.h> |
| #include <fbl/auto_lock.h> |
| |
| #include "src/devices/nand/drivers/aml-rawnand/aml-rawnand-bind.h" |
| |
| namespace amlrawnand { |
| |
| static constexpr uint32_t NAND_BUSWIDTH_16 = 0x00000002; |
| |
| struct NandSetup { |
| union { |
| uint32_t d32; |
| struct { |
| unsigned cmd : 22; |
| unsigned large_page : 1; |
| unsigned no_rb : 1; |
| unsigned a2 : 1; |
| unsigned reserved25 : 1; |
| unsigned page_list : 1; |
| unsigned sync_mode : 2; |
| unsigned size : 2; |
| unsigned active : 1; |
| } b; |
| } cfg; |
| uint16_t id; |
| uint16_t max; |
| }; |
| |
| struct NandCmd { |
| uint8_t type; |
| uint8_t val; |
| }; |
| |
| struct ExtInfo { |
| uint32_t read_info; |
| uint32_t new_type; |
| uint32_t page_per_blk; |
| uint32_t xlc; |
| uint32_t ce_mask; |
| uint32_t boot_num; |
| uint32_t each_boot_pages; |
| uint32_t bbt_occupy_pages; |
| uint32_t bbt_start_block; |
| }; |
| |
| struct NandPage0 { |
| NandSetup nand_setup; |
| unsigned char page_list[16]; |
| NandCmd retry_usr[32]; |
| ExtInfo ext_info; |
| }; |
| |
| // Controller ECC, OOB, RAND parameters. |
| struct AmlControllerParams { |
| int ecc_strength; // # of ECC bits per ECC page. |
| int user_mode; // OOB bytes every ECC page or per block ? |
| int rand_mode; // Randomize ? |
| int bch_mode; |
| }; |
| |
| AmlControllerParams AmlParams = { |
| 8, // Overwritten using BCH setting from page0. |
| 2, |
| // The 2 following values are overwritten by page0 contents. |
| 1, // rand-mode is 1 for page0. |
| AML_ECC_BCH60_1K, // This is the BCH setting for page0. |
| }; |
| |
| void AmlRawNand::NandctrlSetCfg(uint32_t val) { mmio_nandreg_.Write32(val, P_NAND_CFG); } |
| |
| void AmlRawNand::NandctrlSetTimingAsync(int bus_tim, int bus_cyc) { |
| static constexpr uint32_t lenmask = (static_cast<uint32_t>(1) << 12) - 1; |
| uint32_t value = mmio_nandreg_.Read32(P_NAND_CFG); |
| |
| value &= ~lenmask; |
| value |= (((bus_cyc & 31) | ((bus_tim & 31) << 5) | (0 << 10)) & lenmask); |
| mmio_nandreg_.Write32(value, P_NAND_CFG); |
| } |
| |
| void AmlRawNand::NandctrlSendCmd(uint32_t cmd) { mmio_nandreg_.Write32(cmd, P_NAND_CMD); } |
| |
| static const char* AmlEccString(uint32_t ecc_mode) { |
| const char* s; |
| |
| switch (ecc_mode) { |
| case AML_ECC_BCH8: |
| s = "AML_ECC_BCH8"; |
| break; |
| case AML_ECC_BCH8_1K: |
| s = "AML_ECC_BCH8_1K"; |
| break; |
| case AML_ECC_BCH24_1K: |
| s = "AML_ECC_BCH24_1K"; |
| break; |
| case AML_ECC_BCH30_1K: |
| s = "AML_ECC_BCH30_1K"; |
| break; |
| case AML_ECC_BCH40_1K: |
| s = "AML_ECC_BCH40_1K"; |
| break; |
| case AML_ECC_BCH50_1K: |
| s = "AML_ECC_BCH50_1K"; |
| break; |
| case AML_ECC_BCH60_1K: |
| s = "AML_ECC_BCH60_1K"; |
| break; |
| default: |
| s = "BAD ECC Algorithm"; |
| break; |
| } |
| return s; |
| } |
| |
| static uint32_t AmlGetEccPageSize(uint32_t ecc_mode) { |
| uint32_t ecc_page; |
| |
| switch (ecc_mode) { |
| case AML_ECC_BCH8: |
| ecc_page = 512; |
| break; |
| case AML_ECC_BCH8_1K: |
| case AML_ECC_BCH24_1K: |
| case AML_ECC_BCH30_1K: |
| case AML_ECC_BCH40_1K: |
| case AML_ECC_BCH50_1K: |
| case AML_ECC_BCH60_1K: |
| ecc_page = 1024; |
| break; |
| default: |
| ecc_page = 0; |
| break; |
| } |
| return ecc_page; |
| } |
| |
| static int AmlGetEccStrength(uint32_t ecc_mode) { |
| int ecc_strength; |
| |
| switch (ecc_mode) { |
| case AML_ECC_BCH8: |
| case AML_ECC_BCH8_1K: |
| ecc_strength = 8; |
| break; |
| case AML_ECC_BCH24_1K: |
| ecc_strength = 24; |
| break; |
| case AML_ECC_BCH30_1K: |
| ecc_strength = 30; |
| break; |
| case AML_ECC_BCH40_1K: |
| ecc_strength = 40; |
| break; |
| case AML_ECC_BCH50_1K: |
| ecc_strength = 50; |
| break; |
| case AML_ECC_BCH60_1K: |
| ecc_strength = 60; |
| break; |
| default: |
| ecc_strength = -1; |
| break; |
| } |
| return ecc_strength; |
| } |
| |
| void AmlRawNand::AmlCmdIdle(uint32_t time) { |
| uint32_t cmd = chip_select_ | AML_CMD_IDLE | (time & 0x3ff); |
| mmio_nandreg_.Write32(cmd, P_NAND_CMD); |
| } |
| |
| zx_status_t AmlRawNand::AmlWaitCmdFinish(zx::duration timeout, zx::duration first_interval, |
| zx::duration polling_interval) { |
| zx_status_t ret = ZX_OK; |
| zx::duration total_time; |
| |
| // Wait until cmd fifo is empty. |
| bool first = true; |
| while (true) { |
| uint32_t cmd_size = mmio_nandreg_.Read32(P_NAND_CMD); |
| uint32_t numcmds = (cmd_size >> 22) & 0x1f; |
| if (numcmds == 0) |
| break; |
| zx::duration sleep_interval = first ? first_interval : polling_interval; |
| first = false; |
| zx::nanosleep(zx::deadline_after(sleep_interval)); |
| total_time += sleep_interval; |
| if (total_time > timeout) { |
| ret = ZX_ERR_TIMED_OUT; |
| break; |
| } |
| } |
| if (ret == ZX_ERR_TIMED_OUT) |
| zxlogf(ERROR, "wait for empty cmd FIFO time out"); |
| return ret; |
| } |
| |
| void AmlRawNand::AmlCmdSeed(uint32_t seed) { |
| uint32_t cmd = AML_CMD_SEED | (0xc2 + (seed & 0x7fff)); |
| mmio_nandreg_.Write32(cmd, P_NAND_CMD); |
| } |
| |
| void AmlRawNand::AmlCmdN2M(uint32_t ecc_pages, uint32_t ecc_pagesize) { |
| uint32_t cmd = CMDRWGEN(AML_CMD_N2M, controller_params_.rand_mode, controller_params_.bch_mode, 0, |
| ecc_pagesize, ecc_pages); |
| mmio_nandreg_.Write32(cmd, P_NAND_CMD); |
| } |
| |
| void AmlRawNand::AmlCmdM2N(uint32_t ecc_pages, uint32_t ecc_pagesize) { |
| uint32_t cmd = CMDRWGEN(AML_CMD_M2N, controller_params_.rand_mode, controller_params_.bch_mode, 0, |
| ecc_pagesize, ecc_pages); |
| mmio_nandreg_.Write32(cmd, P_NAND_CMD); |
| } |
| |
| namespace { |
| |
| // Each copy of BL2 is prefixed by a single page of metadata telling us what |
| // ECC settings to use for NAND. But since we're reading these settings from |
| // NAND itself, the initial metadata read has to use fixed settings. |
| // |
| // These settings are exactly what the bootloader uses. |
| constexpr int kPage0RandMode = 1; |
| constexpr int kPage0BchMode = AML_ECC_BCH60_1K; |
| constexpr int kPage0ShortpageMode = 1; |
| constexpr int kPage0EccPageSize = 384; |
| // Even though all the metadata currently fits in a single 384-byte ECC page, |
| // read and write 8 pages for consistency with the bootloader code and to ensure |
| // compatibility with future devices that may put additional info here. |
| constexpr int kPage0NumEccPages = 8; |
| |
| } // namespace |
| |
| void AmlRawNand::AmlCmdM2NPage0() { |
| // When shortpage is turned on, page size is given in bytes/8. |
| static_assert(kPage0ShortpageMode == 1, "Fix pagesize calculation"); |
| uint32_t cmd = CMDRWGEN(AML_CMD_M2N, kPage0RandMode, kPage0BchMode, kPage0ShortpageMode, |
| kPage0EccPageSize / 8, kPage0NumEccPages); |
| mmio_nandreg_.Write32(cmd, P_NAND_CMD); |
| } |
| |
| void AmlRawNand::AmlCmdN2MPage0() { |
| // When shortpage is turned on, page size is given in bytes/8. |
| static_assert(kPage0ShortpageMode == 1, "Fix pagesize calculation"); |
| uint32_t cmd = CMDRWGEN(AML_CMD_N2M, kPage0RandMode, kPage0BchMode, kPage0ShortpageMode, |
| kPage0EccPageSize / 8, kPage0NumEccPages); |
| mmio_nandreg_.Write32(cmd, P_NAND_CMD); |
| } |
| |
| zx_status_t AmlRawNand::AmlWaitDmaFinish() { |
| AmlCmdIdle(0); |
| AmlCmdIdle(0); |
| // This timeout was 1048 seconds. Make this 1 second, similar |
| // to other codepaths where we wait for the cmd fifo to drain. |
| return AmlWaitCmdFinish(zx::msec(CMD_FINISH_TIMEOUT_MS), polling_timings_.cmd_flush.min, |
| polling_timings_.cmd_flush.interval); |
| } |
| |
| void* AmlRawNand::AmlInfoPtr(int i) { |
| auto p = reinterpret_cast<struct AmlInfoFormat*>(buffers_->info_buf); |
| return &p[i]; |
| } |
| |
| // In the case where user_mode == 2, info_buf contains one nfc_info_format |
| // struct per ECC page on completion of a read. This 8 byte structure has |
| // the 2 OOB bytes and ECC/error status. |
| zx_status_t AmlRawNand::AmlGetOOBByte(uint8_t* oob_buf, size_t* oob_actual) { |
| struct AmlInfoFormat* info; |
| int count = 0; |
| uint32_t ecc_pagesize, ecc_pages; |
| |
| ecc_pagesize = AmlGetEccPageSize(controller_params_.bch_mode); |
| ecc_pages = writesize_ / ecc_pagesize; |
| // user_mode is 2 in our case - 2 bytes of OOB for every ECC page. |
| if (controller_params_.user_mode != 2) |
| return ZX_ERR_NOT_SUPPORTED; |
| for (uint32_t i = 0; i < ecc_pages; i++) { |
| info = reinterpret_cast<struct AmlInfoFormat*>(AmlInfoPtr(i)); |
| oob_buf[count++] = static_cast<uint8_t>(info->info_bytes & 0xff); |
| oob_buf[count++] = static_cast<uint8_t>((info->info_bytes >> 8) & 0xff); |
| } |
| |
| if (oob_actual) { |
| *oob_actual = count; |
| } |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t AmlRawNand::AmlSetOOBByte(const uint8_t* oob_buf, size_t oob_size, uint32_t ecc_pages) { |
| struct AmlInfoFormat* info; |
| size_t count = 0; |
| |
| // user_mode is 2 in our case - 2 bytes of OOB for every ECC page. |
| if (controller_params_.user_mode != 2) |
| return ZX_ERR_NOT_SUPPORTED; |
| for (uint32_t i = 0; i < ecc_pages; i++) { |
| info = reinterpret_cast<struct AmlInfoFormat*>(AmlInfoPtr(i)); |
| |
| // If the caller didn't provide enough OOB bytes to fill all the pages, |
| // pad with zeros. |
| uint8_t low_byte = (count < oob_size) ? oob_buf[count] : 0x00; |
| count++; |
| uint8_t high_byte = (count < oob_size) ? oob_buf[count] : 0x00; |
| count++; |
| info->info_bytes = static_cast<uint16_t>(low_byte | (high_byte << 8)); |
| } |
| return ZX_OK; |
| } |
| |
| zx_status_t AmlRawNand::AmlGetECCCorrections(int ecc_pages, uint32_t nand_page, |
| uint32_t* ecc_corrected, bool* erased) { |
| struct AmlInfoFormat* info; |
| int bitflips = 0; |
| int erased_ecc_pages = 0; |
| uint8_t zero_bits; |
| |
| for (int i = 0; i < ecc_pages; i++) { |
| info = reinterpret_cast<struct AmlInfoFormat*>(AmlInfoPtr(i)); |
| if (info->ecc.eccerr_cnt == AML_ECC_UNCORRECTABLE_CNT) { |
| if (!controller_params_.rand_mode) { |
| zxlogf(WARNING, "%s: ECC failure (non-randomized)@%u", __func__, nand_page); |
| stats.failed++; |
| return ZX_ERR_IO_DATA_INTEGRITY; |
| } |
| // Why are we checking for zero_bits here ? |
| // To deal with blank NAND pages. A blank page is entirely 0xff. |
| // When read with scrambler, the page will be ECC uncorrectable, |
| // In theory, if there is a single zero-bit in the page, then that |
| // page is not a blank page. But in practice, even fresh NAND chips |
| // report a few errors on the read of a page (including blank pages) |
| // so we make allowance for a few bitflips. The threshold against |
| // which we test the zero-bits is one under which we can correct |
| // the bitflips when the page is written to. One option is to set |
| // this threshold to be exactly the ECC strength (this is aggressive). |
| // TODO(srmohan): What should the correct threshold be ? We could |
| // conservatively set this to a small value, or we could have this |
| // depend on the quality of the NAND, the wear of the NAND etc. |
| zero_bits = info->zero_bits & AML_ECC_UNCORRECTABLE_CNT; |
| if (zero_bits >= controller_params_.ecc_strength) { |
| zxlogf(WARNING, "%s: ECC failure (randomized)@%u zero_bits=%u", __func__, nand_page, |
| zero_bits); |
| stats.failed++; |
| return ZX_ERR_IO_DATA_INTEGRITY; |
| } |
| zxlogf(INFO, "%s: Blank Page@%u", __func__, nand_page); |
| bitflips = std::max(static_cast<uint8_t>(bitflips), static_cast<uint8_t>(zero_bits)); |
| ++erased_ecc_pages; |
| continue; |
| } |
| stats.ecc_corrected += info->ecc.eccerr_cnt; |
| bitflips = std::max(static_cast<uint8_t>(bitflips), static_cast<uint8_t>(info->ecc.eccerr_cnt)); |
| } |
| *ecc_corrected = bitflips; |
| *erased = false; |
| if (erased_ecc_pages == ecc_pages) { |
| *erased = true; |
| } else if (erased_ecc_pages != 0) { |
| zxlogf(WARNING, "%s: Partially erased nand page @%u", __func__, nand_page); |
| return ZX_ERR_IO_DATA_INTEGRITY; |
| } |
| return ZX_OK; |
| } |
| |
| zx_status_t AmlRawNand::AmlCheckECCPages(int ecc_pages) { |
| struct AmlInfoFormat* info; |
| |
| for (int i = 0; i < ecc_pages; i++) { |
| info = reinterpret_cast<struct AmlInfoFormat*>(AmlInfoPtr(i)); |
| if (info->ecc.completed == 0) |
| return ZX_ERR_IO; |
| } |
| return ZX_OK; |
| } |
| |
| zx_status_t AmlRawNand::AmlQueueRB() { |
| uint32_t cmd; |
| zx_status_t status; |
| |
| mmio_nandreg_.SetBits32((1 << 21), P_NAND_CFG); |
| AmlCmdIdle(NAND_TWB_TIME_CYCLE); |
| cmd = chip_select_ | AML_CMD_CLE | (NAND_CMD_STATUS & 0xff); |
| mmio_nandreg_.Write32(cmd, P_NAND_CMD); |
| AmlCmdIdle(NAND_TWB_TIME_CYCLE); |
| cmd = AML_CMD_RB | AML_CMD_IO6 | (1 << 16) | (0x18 & 0x1f); |
| mmio_nandreg_.Write32(cmd, P_NAND_CMD); |
| AmlCmdIdle(2); |
| |
| zx::time timestamp; |
| status = irq_.wait(×tamp); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s: IRQ wait failed", __func__); |
| return status; |
| } |
| |
| return status; |
| } |
| |
| void AmlRawNand::AmlCmdCtrl(int32_t cmd, uint32_t ctrl) { |
| if (cmd == NAND_CMD_NONE) |
| return; |
| if (ctrl & NAND_CLE) |
| cmd = chip_select_ | AML_CMD_CLE | (cmd & 0xff); |
| else |
| cmd = chip_select_ | AML_CMD_ALE | (cmd & 0xff); |
| mmio_nandreg_.Write32(cmd, P_NAND_CMD); |
| } |
| |
| uint8_t AmlRawNand::AmlReadByte() { |
| uint32_t cmd = chip_select_ | AML_CMD_DRD | 0; |
| NandctrlSendCmd(cmd); |
| |
| AmlCmdIdle(NAND_TWB_TIME_CYCLE); |
| |
| AmlCmdIdle(0); |
| AmlCmdIdle(0); |
| AmlWaitCmdFinish(zx::msec(CMD_FINISH_TIMEOUT_MS), zx::usec(10), zx::usec(10)); |
| return mmio_nandreg_.Read<uint8_t>(P_NAND_BUF); |
| } |
| |
| void AmlRawNand::AmlSetClockRate(uint32_t clk_freq) { |
| uint32_t always_on = 0x1 << 24; |
| uint32_t clk; |
| |
| // For Amlogic type AXG. |
| always_on = 0x1 << 28; |
| switch (clk_freq) { |
| case 24: |
| clk = 0x80000201; |
| break; |
| case 112: |
| clk = 0x80000249; |
| break; |
| case 200: |
| clk = 0x80000245; |
| break; |
| case 250: |
| clk = 0x80000244; |
| break; |
| default: |
| clk = 0x80000245; |
| break; |
| } |
| clk |= always_on; |
| mmio_clockreg_.Write32(clk, 0); |
| } |
| |
| void AmlRawNand::AmlClockInit() { |
| uint32_t sys_clk_rate, bus_cycle, bus_timing; |
| |
| sys_clk_rate = 200; |
| AmlSetClockRate(sys_clk_rate); |
| bus_cycle = 6; |
| bus_timing = bus_cycle + 1; |
| NandctrlSetCfg(0); |
| NandctrlSetTimingAsync(bus_timing, (bus_cycle - 1)); |
| NandctrlSendCmd(1 << 31); |
| } |
| |
| void AmlRawNand::AmlAdjustTimings(uint32_t tRC_min, uint32_t tREA_max, uint32_t RHOH_min) { |
| int sys_clk_rate, bus_cycle, bus_timing; |
| // NAND timing defaults. |
| static constexpr uint32_t TreaMaxDefault = 20; |
| static constexpr uint32_t RhohMinDefault = 15; |
| |
| if (!tREA_max) |
| tREA_max = TreaMaxDefault; |
| if (!RHOH_min) |
| RHOH_min = RhohMinDefault; |
| if (tREA_max > 30) |
| sys_clk_rate = 112; |
| else if (tREA_max > 16) |
| sys_clk_rate = 200; |
| else |
| sys_clk_rate = 250; |
| AmlSetClockRate(sys_clk_rate); |
| bus_cycle = 6; |
| bus_timing = bus_cycle + 1; |
| NandctrlSetCfg(0); |
| NandctrlSetTimingAsync(bus_timing, (bus_cycle - 1)); |
| NandctrlSendCmd(1 << 31); |
| } |
| |
| namespace { |
| |
| bool IsPage0NandPage(uint32_t nand_page) { |
| // Backup copies of page0 are located every 128 pages, |
| // with the last one at 896. |
| static constexpr uint32_t AmlPage0Step = 128; |
| static constexpr uint32_t AmlPage0MaxAddr = 896; |
| |
| return ((nand_page <= AmlPage0MaxAddr) && ((nand_page % AmlPage0Step) == 0)); |
| } |
| |
| // The ROM bootloader looks in the OOB bytes for magic values so we need |
| // to write them to all BL2 pages. |
| // |
| // Most NAND pages contain 8 bytes OOB userdata (4 ECC pages per NAND page x 2 |
| // userdata bytes per ECC page). Page0 metadata however uses shortpage mode with |
| // 8 ECC pages per NAND page, so we need up to 16 OOB userdata bytes. |
| constexpr uint8_t kRomMagicOobBuffer[] = {0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, |
| 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA}; |
| constexpr size_t kRomMagicOobSize = sizeof(kRomMagicOobBuffer); |
| |
| // Returns true if the given page number requires writing magic OOB values. |
| constexpr bool PageRequiresMagicOob(uint32_t nand_page) { |
| // BL2 lives in 0x0-0x3FFFFF, which is pages 0-1023. |
| return nand_page <= 1023; |
| } |
| |
| } // namespace |
| |
| zx_status_t AmlRawNand::RawNandReadPageHwecc(uint32_t nand_page, uint8_t* data, size_t data_size, |
| size_t* data_actual, uint8_t* oob, size_t oob_size, |
| size_t* oob_actual, uint32_t* ecc_correct) { |
| zx_status_t status; |
| uint32_t ecc_pagesize; |
| uint32_t ecc_pages; |
| bool erased; |
| bool page0 = IsPage0NandPage(nand_page); |
| |
| if (!page0) { |
| ecc_pagesize = AmlGetEccPageSize(controller_params_.bch_mode); |
| ecc_pages = writesize_ / ecc_pagesize; |
| } else { |
| ecc_pagesize = kPage0EccPageSize; |
| ecc_pages = kPage0NumEccPages; |
| } |
| // Send the page address into the controller. |
| onfi_->OnfiCommand(NAND_CMD_READ0, 0x00, nand_page, static_cast<uint32_t>(chipsize_), chip_delay_, |
| (controller_params_.options & NAND_BUSWIDTH_16)); |
| |
| fbl::AutoLock lock(&mutex_); |
| if (zx_status_t status = AmlRawNandAllocBufs(); status != ZX_OK) |
| return status; |
| |
| mmio_nandreg_.Write32(GENCMDDADDRL(AML_CMD_ADL, buffers_->data_buf_paddr), P_NAND_CMD); |
| mmio_nandreg_.Write32(GENCMDDADDRH(AML_CMD_ADH, buffers_->data_buf_paddr), P_NAND_CMD); |
| mmio_nandreg_.Write32(GENCMDIADDRL(AML_CMD_AIL, buffers_->info_buf_paddr), P_NAND_CMD); |
| mmio_nandreg_.Write32(GENCMDIADDRH(AML_CMD_AIH, buffers_->info_buf_paddr), P_NAND_CMD); |
| |
| if ((page0 && kPage0RandMode) || controller_params_.rand_mode) { |
| // Only need to set the seed if randomizing is enabled. |
| AmlCmdSeed(nand_page); |
| } |
| |
| if (!page0) { |
| AmlCmdN2M(ecc_pages, ecc_pagesize); |
| } else { |
| AmlCmdN2MPage0(); |
| } |
| |
| status = AmlWaitDmaFinish(); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s: AmlWaitDmaFinish failed %d", __func__, status); |
| return status; |
| } |
| status = AmlQueueRB(); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s: AmlQueueRB failed %d", __func__, status); |
| return ZX_ERR_INTERNAL; |
| } |
| status = AmlCheckECCPages(ecc_pages); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s: AmlCheckECCPages failed %d", __func__, status); |
| return status; |
| } |
| |
| status = AmlGetECCCorrections(ecc_pages, nand_page, ecc_correct, &erased); |
| if (status != ZX_OK) { |
| zxlogf(WARNING, "%s: Uncorrectable ECC error on read", __func__); |
| *ecc_correct = controller_params_.ecc_strength + 1; |
| } |
| |
| // Finally copy out the data and oob as needed. |
| if (oob != nullptr) { |
| size_t num_bytes; |
| // Need to try to fetch it first, just to get the oob_actual size |
| if (AmlGetOOBByte(reinterpret_cast<uint8_t*>(oob), &num_bytes) != ZX_OK) { |
| num_bytes = 0; |
| } |
| if (erased) { |
| memset(oob, 0xff, num_bytes); |
| } |
| if (oob_actual != nullptr) { |
| *oob_actual = num_bytes; |
| } |
| } |
| if (data != nullptr) { |
| // Page0 is always 384 bytes. |
| size_t num_bytes = (page0 ? 384 : writesize_); |
| // Clean up any possible bit flips on supposed erased pages. |
| if (erased) { |
| memset(data, 0xff, num_bytes); |
| } else { |
| memcpy(data, buffers_->data_buf, num_bytes); |
| } |
| if (data_actual) { |
| *data_actual = num_bytes; |
| } |
| } |
| |
| return status; |
| } |
| |
| // TODO : Right now, the driver uses a buffer for DMA, which |
| // is not needed. We should initiate DMA to/from pages passed in. |
| zx_status_t AmlRawNand::RawNandWritePageHwecc(const uint8_t* data, size_t data_size, |
| const uint8_t* oob, size_t oob_size, |
| uint32_t nand_page) { |
| zx_status_t status; |
| uint32_t ecc_pagesize = 0; // Initialize to silence compiler. |
| uint32_t ecc_pages; |
| bool page0 = IsPage0NandPage(nand_page); |
| |
| if (!page0) { |
| ecc_pagesize = AmlGetEccPageSize(controller_params_.bch_mode); |
| ecc_pages = writesize_ / ecc_pagesize; |
| } else { |
| ecc_pages = kPage0NumEccPages; |
| } |
| |
| fbl::AutoLock lock(&mutex_); |
| if (zx_status_t status = AmlRawNandAllocBufs(); status != ZX_OK) |
| return status; |
| |
| if (data != nullptr) { |
| memcpy(buffers_->data_buf, data, writesize_); |
| } |
| |
| if (PageRequiresMagicOob(nand_page)) { |
| // Writing the wrong OOB bytes will brick the device, raise an error if |
| // the caller tried to provide their own here. |
| if (oob != nullptr) { |
| zxlogf(ERROR, "%s: Cannot write provided OOB, page %u requires specific OOB bytes", __func__, |
| nand_page); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| oob = kRomMagicOobBuffer; |
| oob_size = kRomMagicOobSize; |
| } |
| |
| if (oob != nullptr) { |
| AmlSetOOBByte(reinterpret_cast<const uint8_t*>(oob), oob_size, ecc_pages); |
| } |
| |
| onfi_->OnfiCommand(NAND_CMD_SEQIN, 0x00, nand_page, static_cast<uint32_t>(chipsize_), chip_delay_, |
| (controller_params_.options & NAND_BUSWIDTH_16)); |
| mmio_nandreg_.Write32(GENCMDDADDRL(AML_CMD_ADL, buffers_->data_buf_paddr), P_NAND_CMD); |
| mmio_nandreg_.Write32(GENCMDDADDRH(AML_CMD_ADH, buffers_->data_buf_paddr), P_NAND_CMD); |
| mmio_nandreg_.Write32(GENCMDIADDRL(AML_CMD_AIL, buffers_->info_buf_paddr), P_NAND_CMD); |
| mmio_nandreg_.Write32(GENCMDIADDRH(AML_CMD_AIH, buffers_->info_buf_paddr), P_NAND_CMD); |
| |
| if ((page0 && kPage0RandMode) || controller_params_.rand_mode) { |
| // Only need to set the seed if randomizing is enabled. |
| AmlCmdSeed(nand_page); |
| } |
| |
| if (!page0) { |
| AmlCmdM2N(ecc_pages, ecc_pagesize); |
| } else { |
| AmlCmdM2NPage0(); |
| } |
| |
| status = AmlWaitDmaFinish(); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s: error from wait_dma_finish", __func__); |
| return status; |
| } |
| onfi_->OnfiCommand(NAND_CMD_PAGEPROG, -1, -1, static_cast<uint32_t>(chipsize_), chip_delay_, |
| (controller_params_.options & NAND_BUSWIDTH_16)); |
| status = onfi_->OnfiWait(zx::msec(AML_WRITE_PAGE_TIMEOUT), polling_timings_.write.min, |
| polling_timings_.write.interval); |
| |
| return status; |
| } |
| |
| zx_status_t AmlRawNand::RawNandEraseBlock(uint32_t nand_page) { |
| zx_status_t status; |
| |
| // nandblock has to be erasesize_ aligned. |
| if (nand_page % erasesize_pages_) { |
| zxlogf(ERROR, "%s: NAND block %u must be a erasesize_pages (%u) multiple", __func__, nand_page, |
| erasesize_pages_); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| onfi_->OnfiCommand(NAND_CMD_ERASE1, -1, nand_page, static_cast<uint32_t>(chipsize_), chip_delay_, |
| (controller_params_.options & NAND_BUSWIDTH_16)); |
| onfi_->OnfiCommand(NAND_CMD_ERASE2, -1, -1, static_cast<uint32_t>(chipsize_), chip_delay_, |
| (controller_params_.options & NAND_BUSWIDTH_16)); |
| status = onfi_->OnfiWait(zx::msec(AML_ERASE_BLOCK_TIMEOUT), polling_timings_.erase.min, |
| polling_timings_.erase.interval); |
| return status; |
| } |
| |
| zx_status_t AmlRawNand::AmlGetFlashType() { |
| uint8_t nand_maf_id, nand_dev_id; |
| uint8_t id_data[8]; |
| struct nand_chip_table* nand_chip; |
| |
| onfi_->OnfiCommand(NAND_CMD_RESET, -1, -1, static_cast<uint32_t>(chipsize_), chip_delay_, |
| (controller_params_.options & NAND_BUSWIDTH_16)); |
| onfi_->OnfiCommand(NAND_CMD_READID, 0x00, -1, static_cast<uint32_t>(chipsize_), chip_delay_, |
| (controller_params_.options & NAND_BUSWIDTH_16)); |
| // Read manufacturer and device IDs. |
| nand_maf_id = AmlReadByte(); |
| nand_dev_id = AmlReadByte(); |
| // Read again. |
| onfi_->OnfiCommand(NAND_CMD_READID, 0x00, -1, static_cast<uint32_t>(chipsize_), chip_delay_, |
| (controller_params_.options & NAND_BUSWIDTH_16)); |
| // Read entire ID string. |
| for (uint32_t i = 0; i < sizeof(id_data); i++) |
| id_data[i] = AmlReadByte(); |
| if (id_data[0] != nand_maf_id || id_data[1] != nand_dev_id) { |
| zxlogf(ERROR, "second ID read did not match %02x,%02x against %02x,%02x", nand_maf_id, |
| nand_dev_id, id_data[0], id_data[1]); |
| } |
| |
| zxlogf(INFO, "%s: manufacturer_id = %x, device_ide = %x, extended_id = %x", __func__, nand_maf_id, |
| nand_dev_id, id_data[3]); |
| nand_chip = onfi_->FindNandChipTable(nand_maf_id, nand_dev_id); |
| if (nand_chip == nullptr) { |
| zxlogf(ERROR, |
| "%s: Cound not find matching NAND chip. NAND chip unsupported." |
| " This is FATAL\n", |
| __func__); |
| return ZX_ERR_UNAVAILABLE; |
| } |
| if (nand_chip->extended_id_nand) { |
| // Initialize pagesize, eraseblk size, oobsize_ and |
| // buswidth from extended parameters queried just now. |
| uint8_t extid = id_data[3]; |
| |
| writesize_ = 1024 << (extid & 0x03); |
| extid = static_cast<uint8_t>(extid >> 2); |
| // Calc oobsize_. |
| oobsize_ = (8 << (extid & 0x01)) * (writesize_ >> 9); |
| extid = static_cast<uint8_t>(extid >> 2); |
| // Calc blocksize. Blocksize is multiples of 64KiB. |
| erasesize_ = (64 * 1024) << (extid & 0x03); |
| extid = static_cast<uint8_t>(extid >> 2); |
| // Get buswidth information. |
| bus_width_ = (extid & 0x01) ? NAND_BUSWIDTH_16 : 0; |
| } else { |
| // Initialize pagesize, eraseblk size, oobsize_ and |
| // buswidth from values in table. |
| writesize_ = nand_chip->page_size; |
| oobsize_ = nand_chip->oobsize; |
| erasesize_ = nand_chip->erase_block_size; |
| bus_width_ = nand_chip->bus_width; |
| } |
| erasesize_pages_ = erasesize_ / writesize_; |
| chipsize_ = nand_chip->chipsize; |
| page_shift_ = ffs(writesize_) - 1; |
| polling_timings_ = nand_chip->polling_timings; |
| |
| // We found a matching device in our database, use it to |
| // initialize. Adjust timings and set various parameters. |
| AmlAdjustTimings(nand_chip->timings.tRC_min, nand_chip->timings.tREA_max, |
| nand_chip->timings.RHOH_min); |
| // chip_delay is used OnfiCommand(), after sending down some commands |
| // to the NAND chip. |
| chip_delay_ = nand_chip->chip_delay_us; |
| zxlogf(INFO, |
| "NAND %s %s: chip size = %lu(GB), page size = %u, oob size = %u\n" |
| "eraseblock size = %u, chip delay (us) = %u\n", |
| nand_chip->manufacturer_name, nand_chip->device_name, chipsize_, writesize_, oobsize_, |
| erasesize_, chip_delay_); |
| return ZX_OK; |
| } |
| |
| zx_status_t AmlRawNand::RawNandGetNandInfo(nand_info_t* nand_info) { |
| uint64_t capacity; |
| zx_status_t status = ZX_OK; |
| |
| nand_info->page_size = writesize_; |
| nand_info->pages_per_block = erasesize_pages_; |
| capacity = chipsize_ * (1024 * 1024); |
| capacity /= erasesize_; |
| nand_info->num_blocks = static_cast<uint32_t>(capacity); |
| nand_info->ecc_bits = controller_params_.ecc_strength; |
| |
| nand_info->nand_class = NAND_CLASS_PARTMAP; |
| memset(&nand_info->partition_guid, 0, sizeof(nand_info->partition_guid)); |
| |
| if (controller_params_.user_mode == 2) |
| nand_info->oob_size = (writesize_ / AmlGetEccPageSize(controller_params_.bch_mode)) * 2; |
| else |
| status = ZX_ERR_NOT_SUPPORTED; |
| return status; |
| } |
| |
| void AmlRawNand::AmlSetEncryption() { mmio_nandreg_.SetBits32((1 << 17), P_NAND_CFG); } |
| |
| zx_status_t AmlRawNand::AmlReadPage0(uint8_t* data, size_t data_size, uint8_t* oob, size_t oob_size, |
| uint32_t nand_page, uint32_t* ecc_correct, int retries) { |
| zx_status_t status; |
| |
| retries++; |
| do { |
| status = RawNandReadPageHwecc(nand_page, data, data_size, nullptr, oob, oob_size, nullptr, |
| ecc_correct); |
| } while (status != ZX_OK && --retries > 0); |
| if (status != ZX_OK) |
| zxlogf(ERROR, "%s: Read error", __func__); |
| return status; |
| } |
| |
| zx_status_t AmlRawNand::AmlNandInitFromPage0() { |
| zx_status_t status; |
| NandPage0* page0; |
| uint32_t ecc_correct; |
| |
| std::unique_ptr<uint8_t[]> buffer(new uint8_t[writesize_]); |
| uint8_t* data = buffer.get(); |
| // There are 8 copies of page0 spaced apart by 128 pages |
| // starting at Page 0. Read the first we can. |
| for (uint32_t i = 0; i < 8; i++) { |
| status = AmlReadPage0(data, writesize_, nullptr, 0, i * 128, &ecc_correct, 3); |
| if (status == ZX_OK) |
| break; |
| } |
| if (status != ZX_OK) { |
| // Could not read any of the page0 copies. This is a fatal error. |
| zxlogf(ERROR, "%s: Page0 Read (all copies) failed", __func__); |
| return status; |
| } |
| |
| page0 = reinterpret_cast<NandPage0*>(data); |
| controller_params_.rand_mode = (page0->nand_setup.cfg.d32 >> 19) & 0x1; |
| controller_params_.bch_mode = (page0->nand_setup.cfg.d32 >> 14) & 0x7; |
| |
| controller_params_.ecc_strength = AmlGetEccStrength(controller_params_.bch_mode); |
| if (controller_params_.ecc_strength < 0) { |
| zxlogf(INFO, "%s: BAD ECC strength computed from BCH Mode", __func__); |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| zxlogf(INFO, "%s: NAND BCH Mode is %s", __func__, AmlEccString(controller_params_.bch_mode)); |
| return ZX_OK; |
| } |
| |
| zx_status_t AmlRawNand::AmlRawNandAllocBufs() { |
| if (buffers_) |
| return ZX_OK; |
| Buffers& buffers = buffers_.emplace(); |
| |
| // The iobuffers MUST be uncachable. Making these cachable, with |
| // cache flush/invalidate at the right places in the code does not |
| // work. We see data corruptions caused by speculative cache prefetching |
| // done by ARM. Note also that these corruptions are not easily reproducible. |
| zx_status_t status = buffers.data_buffer.Init( |
| bti_.get(), writesize_, IO_BUFFER_UNCACHED | IO_BUFFER_RW | IO_BUFFER_CONTIG); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "io_buffer_init(data_buffer) failed"); |
| buffers_.reset(); |
| return status; |
| } |
| ZX_DEBUG_ASSERT(writesize_ > 0); |
| status = buffers.info_buffer.Init(bti_.get(), writesize_, |
| IO_BUFFER_UNCACHED | IO_BUFFER_RW | IO_BUFFER_CONTIG); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "io_buffer_init(info_buffer) failed"); |
| buffers_.reset(); |
| return status; |
| } |
| buffers.data_buf = buffers.data_buffer.virt(); |
| buffers.info_buf = buffers.info_buffer.virt(); |
| buffers.data_buf_paddr = buffers.data_buffer.phys(); |
| buffers.info_buf_paddr = buffers.info_buffer.phys(); |
| return ZX_OK; |
| } |
| |
| zx_status_t AmlRawNand::AmlNandInit() { |
| zx_status_t status; |
| |
| // Do nand scan to get manufacturer and other info. |
| status = AmlGetFlashType(); |
| if (status != ZX_OK) |
| return status; |
| controller_params_.ecc_strength = AmlParams.ecc_strength; |
| controller_params_.user_mode = AmlParams.user_mode; |
| controller_params_.rand_mode = AmlParams.rand_mode; |
| static constexpr auto NAND_USE_BOUNCE_BUFFER = 0x1; |
| controller_params_.options = NAND_USE_BOUNCE_BUFFER; |
| controller_params_.bch_mode = AmlParams.bch_mode; |
| |
| // Note on OOB byte settings. |
| // The default config for OOB is 2 bytes per OOB page. This is the |
| // settings we use. So nothing to be done for OOB. If we ever need |
| // to switch to 16 bytes of OOB per NAND page, we need to set the |
| // right bits in the CFG register. |
| { |
| fbl::AutoLock lock(&mutex_); |
| status = AmlRawNandAllocBufs(); |
| if (status != ZX_OK) |
| return status; |
| } |
| |
| // Read one of the copies of page0, and use that to initialize |
| // ECC algorithm and rand-mode. |
| status = AmlNandInitFromPage0(); |
| |
| static constexpr uint32_t chipsel[2] = {NAND_CE0, NAND_CE1}; |
| // Force chip_select to 0. |
| chip_select_ = chipsel[0]; |
| |
| return status; |
| } |
| |
| void AmlRawNand::DdkRelease() { |
| // This should result in the dtors of all members to be |
| // called (so the MmioBuffers, bti, irq handle should get |
| // cleaned up). |
| delete this; |
| } |
| |
| void AmlRawNand::CleanUpIrq() { irq_.destroy(); } |
| |
| void AmlRawNand::DdkUnbind(ddk::UnbindTxn txn) { |
| CleanUpIrq(); |
| txn.Reply(); |
| } |
| |
| void AmlRawNand::DdkSuspend(ddk::SuspendTxn txn) { |
| { |
| fbl::AutoLock lock(&mutex_); |
| buffers_.reset(); |
| } |
| txn.Reply(ZX_OK, 0); |
| } |
| |
| zx_status_t AmlRawNand::Init() { |
| onfi_->Init([this](int32_t cmd, uint32_t ctrl) -> void { AmlCmdCtrl(cmd, ctrl); }, |
| [this]() -> uint8_t { return AmlReadByte(); }); |
| |
| AmlClockInit(); |
| zx_status_t status = AmlNandInit(); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "aml_raw_nand: AmlNandInit() failed - This is FATAL"); |
| CleanUpIrq(); |
| } |
| return status; |
| } |
| |
| zx_status_t AmlRawNand::Bind() { |
| zx_status_t status = DdkAdd("aml-raw_nand"); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s: DdkAdd failed", __FILE__); |
| CleanUpIrq(); |
| } |
| return status; |
| } |
| |
| zx_status_t AmlRawNand::Create(void* ctx, zx_device_t* parent) { |
| zx_status_t status; |
| |
| ddk::PDev pdev(parent); |
| if (!pdev.is_valid()) { |
| zxlogf(ERROR, "%s: ZX_PROTOCOL_PDEV not available", __FILE__); |
| return ZX_ERR_NO_RESOURCES; |
| } |
| |
| zx::bti bti; |
| if ((status = pdev.GetBti(0, &bti)) != ZX_OK) { |
| zxlogf(ERROR, "%s: pdev_get_bti failed", __FILE__); |
| return status; |
| } |
| |
| static constexpr uint32_t NandRegWindow = 0; |
| static constexpr uint32_t ClockRegWindow = 1; |
| std::optional<ddk::MmioBuffer> mmio_nandreg; |
| status = pdev.MapMmio(NandRegWindow, &mmio_nandreg); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s: pdev.MapMmio nandreg failed", __FILE__); |
| return status; |
| } |
| |
| std::optional<ddk::MmioBuffer> mmio_clockreg; |
| status = pdev.MapMmio(ClockRegWindow, &mmio_clockreg); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "%s: pdev.MapMmio clockreg failed", __FILE__); |
| return status; |
| } |
| |
| zx::interrupt irq; |
| if ((status = pdev.GetInterrupt(0, &irq)) != ZX_OK) { |
| zxlogf(ERROR, "%s: Failed to map interrupt", __FILE__); |
| return status; |
| } |
| fbl::AllocChecker ac; |
| std::unique_ptr<AmlRawNand> device( |
| new (&ac) AmlRawNand(parent, *std::move(mmio_nandreg), *std::move(mmio_clockreg), |
| std::move(bti), std::move(irq), std::make_unique<Onfi>())); |
| |
| if (!ac.check()) { |
| zxlogf(ERROR, "%s: AmlRawNand alloc failed", __FILE__); |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| if ((status = device->Init()) != ZX_OK) { |
| return status; |
| } |
| |
| if ((status = device->Bind()) != ZX_OK) { |
| return status; |
| } |
| |
| // devmgr is now in charge of the device. |
| __UNUSED auto* dummy = device.release(); |
| return ZX_OK; |
| } |
| |
| static constexpr zx_driver_ops_t amlrawnand_driver_ops = []() { |
| zx_driver_ops_t ops = {}; |
| ops.version = DRIVER_OPS_VERSION; |
| ops.bind = AmlRawNand::Create; |
| return ops; |
| }(); |
| |
| } // namespace amlrawnand |
| |
| ZIRCON_DRIVER(aml_rawnand, amlrawnand::amlrawnand_driver_ops, "zircon", "0.1"); |