|  | /* | 
|  | * Copyright (c) 2020 Peter Bigot Consulting, LLC | 
|  | * | 
|  | * SPDX-License-Identifier: Apache-2.0 | 
|  | */ | 
|  |  | 
|  | #include <zephyr.h> | 
|  | #include <device.h> | 
|  | #include <drivers/flash.h> | 
|  | #include <jesd216.h> | 
|  | #include <stdio.h> | 
|  | #include <inttypes.h> | 
|  | #include <string.h> | 
|  |  | 
|  | #if DT_NODE_HAS_STATUS(DT_INST(0, jedec_spi_nor), okay) | 
|  | #define FLASH_DEVICE DT_LABEL(DT_INST(0, jedec_spi_nor)) | 
|  | #elif DT_NODE_HAS_STATUS(DT_INST(0, nordic_qspi_nor), okay) | 
|  | #define FLASH_DEVICE DT_LABEL(DT_INST(0, nordic_qspi_nor)) | 
|  | #else | 
|  | #error Unsupported flash driver | 
|  | #define FLASH_DEVICE "" | 
|  | #endif | 
|  |  | 
|  | typedef void (*dw_extractor)(const struct jesd216_param_header *php, | 
|  | const struct jesd216_bfp *bfp); | 
|  |  | 
|  | static const char * const mode_tags[] = { | 
|  | [JESD216_MODE_044] = "QSPI XIP", | 
|  | [JESD216_MODE_088] = "OSPI XIP", | 
|  | [JESD216_MODE_111] = "1-1-1", | 
|  | [JESD216_MODE_112] = "1-1-2", | 
|  | [JESD216_MODE_114] = "1-1-4", | 
|  | [JESD216_MODE_118] = "1-1-8", | 
|  | [JESD216_MODE_122] = "1-2-2", | 
|  | [JESD216_MODE_144] = "1-4-4", | 
|  | [JESD216_MODE_188] = "1-8-8", | 
|  | [JESD216_MODE_222] = "2-2-2", | 
|  | [JESD216_MODE_444] = "4-4-4", | 
|  | }; | 
|  |  | 
|  | static void summarize_dw1(const struct jesd216_param_header *php, | 
|  | const struct jesd216_bfp *bfp) | 
|  | { | 
|  | uint32_t dw1 = sys_le32_to_cpu(bfp->dw1); | 
|  |  | 
|  | printf("DTR Clocking %ssupported\n", | 
|  | (dw1 & JESD216_SFDP_BFP_DW1_DTRCLK_FLG) ? "" : "not "); | 
|  |  | 
|  | static const char *const addr_bytes[] = { | 
|  | [JESD216_SFDP_BFP_DW1_ADDRBYTES_VAL_3B] = "3-Byte only", | 
|  | [JESD216_SFDP_BFP_DW1_ADDRBYTES_VAL_3B4B] = "3- or 4-Byte", | 
|  | [JESD216_SFDP_BFP_DW1_ADDRBYTES_VAL_4B] = "4-Byte only", | 
|  | [3] = "Reserved", | 
|  | }; | 
|  |  | 
|  | printf("Addressing: %s\n", addr_bytes[(dw1 & JESD216_SFDP_BFP_DW1_ADDRBYTES_MASK) | 
|  | >> JESD216_SFDP_BFP_DW1_ADDRBYTES_SHFT]); | 
|  |  | 
|  | static const char *const bsersz[] = { | 
|  | [0] = "Reserved 00b", | 
|  | [JESD216_SFDP_BFP_DW1_BSERSZ_VAL_4KSUP] = "uniform", | 
|  | [2] = "Reserved 01b", | 
|  | [JESD216_SFDP_BFP_DW1_BSERSZ_VAL_4KNOTSUP] = "not uniform", | 
|  | }; | 
|  |  | 
|  | printf("4-KiBy erase: %s\n", bsersz[(dw1 & JESD216_SFDP_BFP_DW1_BSERSZ_MASK) | 
|  | >> JESD216_SFDP_BFP_DW1_BSERSZ_SHFT]); | 
|  |  | 
|  | for (size_t mode = 0; mode < ARRAY_SIZE(mode_tags); ++mode) { | 
|  | const char *tag = mode_tags[mode]; | 
|  |  | 
|  | if (tag) { | 
|  | struct jesd216_instr cmd; | 
|  | int rc = jesd216_bfp_read_support(php, bfp, | 
|  | (enum jesd216_mode_type)mode, | 
|  | &cmd); | 
|  |  | 
|  | if (rc == 0) { | 
|  | printf("Support %s\n", tag); | 
|  | } else if (rc > 0) { | 
|  | printf("Support %s: instr %02Xh, %u mode clocks, %u waits\n", | 
|  | tag, cmd.instr, cmd.mode_clocks, cmd.wait_states); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | static void summarize_dw2(const struct jesd216_param_header *php, | 
|  | const struct jesd216_bfp *bfp) | 
|  | { | 
|  | printf("Flash density: %u bytes\n", (uint32_t)(jesd216_bfp_density(bfp) / 8)); | 
|  | } | 
|  |  | 
|  | static void summarize_dw89(const struct jesd216_param_header *php, | 
|  | const struct jesd216_bfp *bfp) | 
|  | { | 
|  | struct jesd216_erase_type etype; | 
|  | uint32_t typ_ms; | 
|  | int typ_max_mul; | 
|  |  | 
|  | for (uint8_t idx = 1; idx < JESD216_NUM_ERASE_TYPES; ++idx) { | 
|  | if (jesd216_bfp_erase(bfp, idx, &etype) == 0) { | 
|  | typ_max_mul = jesd216_bfp_erase_type_times(php, bfp, | 
|  | idx, &typ_ms); | 
|  |  | 
|  | printf("ET%u: instr %02Xh for %u By", idx, etype.cmd, | 
|  | (uint32_t)BIT(etype.exp)); | 
|  | if (typ_max_mul > 0) { | 
|  | printf("; typ %u ms, max %u ms", | 
|  | typ_ms, typ_max_mul * typ_ms); | 
|  | } | 
|  | printf("\n"); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | static void summarize_dw11(const struct jesd216_param_header *php, | 
|  | const struct jesd216_bfp *bfp) | 
|  | { | 
|  | struct jesd216_bfp_dw11 dw11; | 
|  |  | 
|  | if (jesd216_bfp_decode_dw11(php, bfp, &dw11) != 0) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | printf("Chip erase: typ %u ms, max %u ms\n", | 
|  | dw11.chip_erase_ms, dw11.typ_max_factor * dw11.chip_erase_ms); | 
|  |  | 
|  | printf("Byte program: type %u + %u * B us, max %u + %u * B us\n", | 
|  | dw11.byte_prog_first_us, dw11.byte_prog_addl_us, | 
|  | dw11.typ_max_factor * dw11.byte_prog_first_us, | 
|  | dw11.typ_max_factor * dw11.byte_prog_addl_us); | 
|  |  | 
|  | printf("Page program: typ %u us, max %u us\n", | 
|  | dw11.page_prog_us, | 
|  | dw11.typ_max_factor * dw11.page_prog_us); | 
|  |  | 
|  | printf("Page size: %u By\n", dw11.page_size); | 
|  | } | 
|  |  | 
|  | static void summarize_dw12(const struct jesd216_param_header *php, | 
|  | const struct jesd216_bfp *bfp) | 
|  | { | 
|  | uint32_t dw12 = sys_le32_to_cpu(bfp->dw10[2]); | 
|  | uint32_t dw13 = sys_le32_to_cpu(bfp->dw10[3]); | 
|  |  | 
|  | /* Inverted logic flag: 1 means not supported */ | 
|  | if ((dw12 & JESD216_SFDP_BFP_DW12_SUSPRESSUP_FLG) != 0) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | uint8_t susp_instr = dw13 >> 24; | 
|  | uint8_t resm_instr = dw13 >> 16; | 
|  | uint8_t psusp_instr = dw13 >> 8; | 
|  | uint8_t presm_instr = dw13 >> 0; | 
|  |  | 
|  | printf("Suspend: %02Xh ; Resume: %02Xh\n", | 
|  | susp_instr, resm_instr); | 
|  | if ((susp_instr != psusp_instr) | 
|  | || (resm_instr != presm_instr)) { | 
|  | printf("Program suspend: %02Xh ; Resume: %02Xh\n", | 
|  | psusp_instr, presm_instr); | 
|  | } | 
|  | } | 
|  |  | 
|  | static void summarize_dw14(const struct jesd216_param_header *php, | 
|  | const struct jesd216_bfp *bfp) | 
|  | { | 
|  | struct jesd216_bfp_dw14 dw14; | 
|  |  | 
|  | if (jesd216_bfp_decode_dw14(php, bfp, &dw14) != 0) { | 
|  | return; | 
|  | } | 
|  | printf("DPD: Enter %02Xh, exit %02Xh ; delay %u ns ; poll 0x%02x\n", | 
|  | dw14.enter_dpd_instr, dw14.exit_dpd_instr, | 
|  | dw14.exit_delay_ns, dw14.poll_options); | 
|  | } | 
|  |  | 
|  | static void summarize_dw15(const struct jesd216_param_header *php, | 
|  | const struct jesd216_bfp *bfp) | 
|  | { | 
|  | struct jesd216_bfp_dw15 dw15; | 
|  |  | 
|  | if (jesd216_bfp_decode_dw15(php, bfp, &dw15) != 0) { | 
|  | return; | 
|  | } | 
|  | printf("HOLD or RESET Disable: %ssupported\n", | 
|  | dw15.hold_reset_disable ? "" : "un"); | 
|  | printf("QER: %u\n", dw15.qer); | 
|  | if (dw15.support_044) { | 
|  | printf("0-4-4 Mode methods: entry 0x%01x ; exit 0x%02x\n", | 
|  | dw15.entry_044, dw15.exit_044); | 
|  | } else { | 
|  | printf("0-4-4 Mode: not supported"); | 
|  | } | 
|  | printf("4-4-4 Mode sequences: enable 0x%02x ; disable 0x%01x\n", | 
|  | dw15.enable_444, dw15.disable_444); | 
|  | } | 
|  |  | 
|  | static void summarize_dw16(const struct jesd216_param_header *php, | 
|  | const struct jesd216_bfp *bfp) | 
|  | { | 
|  | struct jesd216_bfp_dw16 dw16; | 
|  |  | 
|  | if (jesd216_bfp_decode_dw16(php, bfp, &dw16) != 0) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | uint32_t dw1 = sys_le32_to_cpu(bfp->dw1); | 
|  | uint8_t addr_support = (dw1 & JESD216_SFDP_BFP_DW1_ADDRBYTES_MASK) | 
|  | >> JESD216_SFDP_BFP_DW1_ADDRBYTES_SHFT; | 
|  |  | 
|  | /* Don't display bits when 4-byte addressing is not supported. */ | 
|  | if (addr_support != JESD216_SFDP_BFP_DW1_ADDRBYTES_VAL_3B) { | 
|  | printf("4-byte addressing support: enter 0x%02x, exit 0x%03x\n", | 
|  | dw16.enter_4ba, dw16.exit_4ba); | 
|  | } | 
|  | printf("Soft Reset and Rescue Sequence support: 0x%02x\n", | 
|  | dw16.srrs_support); | 
|  | printf("Status Register 1 support: 0x%02x\n", | 
|  | dw16.sr1_interface); | 
|  | } | 
|  |  | 
|  | /* Indexed from 1 to match JESD216 data word numbering */ | 
|  | static const dw_extractor extractor[] = { | 
|  | [1] = summarize_dw1, | 
|  | [2] = summarize_dw2, | 
|  | [8] = summarize_dw89, | 
|  | [11] = summarize_dw11, | 
|  | [12] = summarize_dw12, | 
|  | [14] = summarize_dw14, | 
|  | [15] = summarize_dw15, | 
|  | [16] = summarize_dw16, | 
|  | }; | 
|  |  | 
|  | static void dump_bfp(const struct jesd216_param_header *php, | 
|  | const struct jesd216_bfp *bfp) | 
|  | { | 
|  | uint8_t dw = 1; | 
|  | uint8_t limit = MIN(1U + php->len_dw, ARRAY_SIZE(extractor)); | 
|  |  | 
|  | printf("Summary of BFP content:\n"); | 
|  | while (dw < limit) { | 
|  | dw_extractor ext = extractor[dw]; | 
|  |  | 
|  | if (ext != 0) { | 
|  | ext(php, bfp); | 
|  | } | 
|  | ++dw; | 
|  | } | 
|  | } | 
|  |  | 
|  | static void dump_bytes(const struct jesd216_param_header *php, | 
|  | const uint32_t *dw) | 
|  | { | 
|  | char buffer[4 * 3 + 1]; /* B1 B2 B3 B4 */ | 
|  | uint8_t nw = 0; | 
|  |  | 
|  | printf(" [\n\t"); | 
|  | while (nw < php->len_dw) { | 
|  | const uint8_t *u8p = (const uint8_t *)&dw[nw]; | 
|  | ++nw; | 
|  |  | 
|  | bool emit_nl = (nw == php->len_dw) || ((nw % 4) == 0); | 
|  |  | 
|  | sprintf(buffer, "%02x %02x %02x %02x", | 
|  | u8p[0], u8p[1], u8p[2], u8p[3]); | 
|  | if (emit_nl) { | 
|  | printf("%s\n\t", buffer); | 
|  | } else { | 
|  | printf("%s  ", buffer); | 
|  | } | 
|  | } | 
|  | printf("];\n"); | 
|  | } | 
|  |  | 
|  | void main(void) | 
|  | { | 
|  | const struct device *dev = device_get_binding(FLASH_DEVICE); | 
|  |  | 
|  | if (!dev) { | 
|  | printf("%s: device not found\n", FLASH_DEVICE); | 
|  | return; | 
|  | } | 
|  |  | 
|  | const uint8_t decl_nph = 5; | 
|  | union { | 
|  | uint8_t raw[JESD216_SFDP_SIZE(decl_nph)]; | 
|  | struct jesd216_sfdp_header sfdp; | 
|  | } u; | 
|  | const struct jesd216_sfdp_header *hp = &u.sfdp; | 
|  | int rc = flash_sfdp_read(dev, 0, u.raw, sizeof(u.raw)); | 
|  |  | 
|  | if (rc != 0) { | 
|  | printf("Read SFDP not supported: device not JESD216-compliant " | 
|  | "(err %d)\n", rc); | 
|  | return; | 
|  | } | 
|  |  | 
|  | uint32_t magic = jesd216_sfdp_magic(hp); | 
|  |  | 
|  | if (magic != JESD216_SFDP_MAGIC) { | 
|  | printf("SFDP magic %08x invalid", magic); | 
|  | return; | 
|  | } | 
|  |  | 
|  | printf("%s: SFDP v %u.%u AP %x with %u PH\n", dev->name, | 
|  | hp->rev_major, hp->rev_minor, hp->access, 1 + hp->nph); | 
|  |  | 
|  | const struct jesd216_param_header *php = hp->phdr; | 
|  | const struct jesd216_param_header *phpe = php + MIN(decl_nph, 1 + hp->nph); | 
|  |  | 
|  | while (php != phpe) { | 
|  | uint16_t id = jesd216_param_id(php); | 
|  | uint32_t addr = jesd216_param_addr(php); | 
|  |  | 
|  | printf("PH%u: %04x rev %u.%u: %u DW @ %x\n", | 
|  | (uint32_t)(php - hp->phdr), id, php->rev_major, php->rev_minor, | 
|  | php->len_dw, addr); | 
|  |  | 
|  | uint32_t dw[php->len_dw]; | 
|  |  | 
|  | rc = flash_sfdp_read(dev, addr, dw, sizeof(dw)); | 
|  | if (rc != 0) { | 
|  | printf("Read failed: %d\n", rc); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (id == JESD216_SFDP_PARAM_ID_BFP) { | 
|  | const struct jesd216_bfp *bfp = (struct jesd216_bfp *)dw; | 
|  |  | 
|  | dump_bfp(php, bfp); | 
|  | printf("size = <%u>;\n", (uint32_t)jesd216_bfp_density(bfp)); | 
|  | printf("sfdp-bfp ="); | 
|  | } else { | 
|  | printf("sfdp-%04x =", id); | 
|  | } | 
|  |  | 
|  | dump_bytes(php, dw); | 
|  |  | 
|  | ++php; | 
|  | } | 
|  |  | 
|  | uint8_t id[3]; | 
|  |  | 
|  | rc = flash_read_jedec_id(dev, id); | 
|  | if (rc == 0) { | 
|  | printf("jedec-id = [%02x %02x %02x];\n", | 
|  | id[0], id[1], id[2]); | 
|  | } else { | 
|  | printf("JEDEC ID read failed: %d\n", rc); | 
|  | } | 
|  | } |